BitmapRegionDecoder.cpp revision 6b849e2123be98eb2a1a25b8abf0b13a279ce952
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#define LOG_TAG "BitmapRegionDecoder"
18
19#include "SkBitmap.h"
20#include "SkImageEncoder.h"
21#include "GraphicsJNI.h"
22#include "SkUtils.h"
23#include "SkTemplates.h"
24#include "SkPixelRef.h"
25#include "SkStream.h"
26#include "BitmapFactory.h"
27#include "AutoDecodeCancel.h"
28#include "SkBitmapRegionDecoder.h"
29#include "CreateJavaOutputStreamAdaptor.h"
30#include "Utils.h"
31
32#include <android_runtime/AndroidRuntime.h>
33#include "android_util_Binder.h"
34#include "android_nio_utils.h"
35#include "CreateJavaOutputStreamAdaptor.h"
36
37#include <binder/Parcel.h>
38#include <jni.h>
39#include <utils/Asset.h>
40#include <sys/stat.h>
41
42static jclass gFileDescriptor_class;
43static jfieldID gFileDescriptor_descriptor;
44
45#if 0
46    #define TRACE_BITMAP(code)  code
47#else
48    #define TRACE_BITMAP(code)
49#endif
50
51using namespace android;
52
53static SkMemoryStream* buildSkMemoryStream(SkStream *stream) {
54    size_t bufferSize = 4096;
55    size_t streamLen = 0;
56    size_t len;
57    char* data = (char*)sk_malloc_throw(bufferSize);
58
59    while ((len = stream->read(data + streamLen,
60                    bufferSize - streamLen)) != 0) {
61        streamLen += len;
62        if (streamLen == bufferSize) {
63            bufferSize *= 2;
64            data = (char*)sk_realloc_throw(data, bufferSize);
65        }
66    }
67    data = (char*)sk_realloc_throw(data, streamLen);
68    SkMemoryStream* streamMem = new SkMemoryStream();
69    streamMem->setMemoryOwned(data, streamLen);
70    return streamMem;
71}
72
73static jobject doBuildTileIndex(JNIEnv* env, SkStream* stream) {
74    SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
75    int width, height;
76    if (NULL == decoder) {
77        doThrowIOE(env, "Image format not supported");
78        return nullObjectReturn("SkImageDecoder::Factory returned null");
79    }
80
81    JavaPixelAllocator *javaAllocator = new JavaPixelAllocator(env, true);
82    decoder->setAllocator(javaAllocator);
83    JavaMemoryUsageReporter *javaMemoryReporter = new JavaMemoryUsageReporter(env);
84    decoder->setReporter(javaMemoryReporter);
85    javaAllocator->unref();
86    javaMemoryReporter->unref();
87
88    if (!decoder->buildTileIndex(stream, &width, &height)) {
89        char msg[100];
90        snprintf(msg, sizeof(msg), "Image failed to decode using %s decoder",
91                decoder->getFormatName());
92        doThrowIOE(env, msg);
93        return nullObjectReturn("decoder->buildTileIndex returned false");
94    }
95
96    SkBitmapRegionDecoder *bm = new SkBitmapRegionDecoder(decoder, width, height);
97
98    return GraphicsJNI::createBitmapRegionDecoder(env, bm);
99}
100
101static jobject nativeNewInstanceFromByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
102                                     int offset, int length, jboolean isShareable) {
103    /*  If isShareable we could decide to just wrap the java array and
104        share it, but that means adding a globalref to the java array object
105        For now we just always copy the array's data if isShareable.
106     */
107    AutoJavaByteArray ar(env, byteArray);
108    SkStream* stream = new SkMemoryStream(ar.ptr() + offset, length, true);
109    return doBuildTileIndex(env, stream);
110}
111
112static jobject nativeNewInstanceFromFileDescriptor(JNIEnv* env, jobject clazz,
113                                          jobject fileDescriptor, jboolean isShareable) {
114    NPE_CHECK_RETURN_ZERO(env, fileDescriptor);
115
116    jint descriptor = env->GetIntField(fileDescriptor,
117                                       gFileDescriptor_descriptor);
118    SkStream *stream = NULL;
119    struct stat fdStat;
120    int newFD;
121    if (fstat(descriptor, &fdStat) == -1) {
122        doThrowIOE(env, "broken file descriptor");
123        return nullObjectReturn("fstat return -1");
124    }
125
126    if (isShareable &&
127            S_ISREG(fdStat.st_mode) &&
128            (newFD = ::dup(descriptor)) != -1) {
129        SkFDStream* fdStream = new SkFDStream(newFD, true);
130        if (!fdStream->isValid()) {
131            fdStream->unref();
132            return NULL;
133        }
134        stream = fdStream;
135    } else {
136        SkFDStream* fdStream = new SkFDStream(descriptor, false);
137        if (!fdStream->isValid()) {
138            fdStream->unref();
139            return NULL;
140        }
141        stream = buildSkMemoryStream(fdStream);
142        fdStream->unref();
143    }
144
145    /* Restore our offset when we leave, so we can be called more than once
146       with the same descriptor. This is only required if we didn't dup the
147       file descriptor, but it is OK to do it all the time.
148    */
149    AutoFDSeek as(descriptor);
150
151    return doBuildTileIndex(env, stream);
152}
153
154static jobject nativeNewInstanceFromStream(JNIEnv* env, jobject clazz,
155                                  jobject is,       // InputStream
156                                  jbyteArray storage, // byte[]
157                                  jboolean isShareable) {
158    jobject largeBitmap = NULL;
159    SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage, 1024);
160
161    if (stream) {
162        // for now we don't allow shareable with java inputstreams
163        SkMemoryStream *mStream = buildSkMemoryStream(stream);
164        largeBitmap = doBuildTileIndex(env, mStream);
165        stream->unref();
166    }
167    return largeBitmap;
168}
169
170static jobject nativeNewInstanceFromAsset(JNIEnv* env, jobject clazz,
171                                 jint native_asset, // Asset
172                                 jboolean isShareable) {
173    SkStream* stream, *assStream;
174    Asset* asset = reinterpret_cast<Asset*>(native_asset);
175    assStream = new AssetStreamAdaptor(asset);
176    stream = buildSkMemoryStream(assStream);
177    assStream->unref();
178    return doBuildTileIndex(env, stream);
179}
180
181/*
182 * nine patch not supported
183 *
184 * purgeable not supported
185 * reportSizeToVM not supported
186 */
187static jobject nativeDecodeRegion(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd,
188        int start_x, int start_y, int width, int height, jobject options) {
189    SkImageDecoder *decoder = brd->getDecoder();
190    int sampleSize = 1;
191    SkBitmap::Config prefConfig = SkBitmap::kNo_Config;
192    bool doDither = true;
193
194    if (NULL != options) {
195        sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
196        // initialize these, in case we fail later on
197        env->SetIntField(options, gOptions_widthFieldID, -1);
198        env->SetIntField(options, gOptions_heightFieldID, -1);
199        env->SetObjectField(options, gOptions_mimeFieldID, 0);
200
201        jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
202        prefConfig = GraphicsJNI::getNativeBitmapConfig(env, jconfig);
203        doDither = env->GetBooleanField(options, gOptions_ditherFieldID);
204    }
205
206    decoder->setDitherImage(doDither);
207    SkBitmap*           bitmap = new SkBitmap;
208    SkAutoTDelete<SkBitmap>       adb(bitmap);
209    AutoDecoderCancel   adc(options, decoder);
210
211    // To fix the race condition in case "requestCancelDecode"
212    // happens earlier than AutoDecoderCancel object is added
213    // to the gAutoDecoderCancelMutex linked list.
214    if (NULL != options && env->GetBooleanField(options, gOptions_mCancelID)) {
215        return nullObjectReturn("gOptions_mCancelID");;
216    }
217
218    SkIRect region;
219    region.fLeft = start_x;
220    region.fTop = start_y;
221    region.fRight = start_x + width;
222    region.fBottom = start_y + height;
223
224    if (!brd->decodeRegion(bitmap, region, prefConfig, sampleSize)) {
225        return nullObjectReturn("decoder->decodeRegion returned false");
226    }
227
228    // update options (if any)
229    if (NULL != options) {
230        env->SetIntField(options, gOptions_widthFieldID, bitmap->width());
231        env->SetIntField(options, gOptions_heightFieldID, bitmap->height());
232        // TODO: set the mimeType field with the data from the codec.
233        // but how to reuse a set of strings, rather than allocating new one
234        // each time?
235        env->SetObjectField(options, gOptions_mimeFieldID,
236                            getMimeTypeString(env, decoder->getFormat()));
237    }
238
239    // detach bitmap from its autotdeleter, since we want to own it now
240    adb.detach();
241
242    SkPixelRef* pr;
243    pr = bitmap->pixelRef();
244    // promise we will never change our pixels (great for sharing and pictures)
245    pr->setImmutable();
246    // now create the java bitmap
247    return GraphicsJNI::createBitmap(env, bitmap, false, NULL);
248}
249
250static int nativeGetHeight(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) {
251    return brd->getHeight();
252}
253
254static int nativeGetWidth(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) {
255    return brd->getWidth();
256}
257
258static void nativeClean(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) {
259    delete brd;
260}
261
262///////////////////////////////////////////////////////////////////////////////
263
264#include <android_runtime/AndroidRuntime.h>
265
266static JNINativeMethod gBitmapRegionDecoderMethods[] = {
267    {   "nativeDecodeRegion",
268        "(IIIIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
269        (void*)nativeDecodeRegion},
270
271    {   "nativeGetHeight", "(I)I", (void*)nativeGetHeight},
272
273    {   "nativeGetWidth", "(I)I", (void*)nativeGetWidth},
274
275    {   "nativeClean", "(I)V", (void*)nativeClean},
276
277    {   "nativeNewInstance",
278        "([BIIZ)Landroid/graphics/BitmapRegionDecoder;",
279        (void*)nativeNewInstanceFromByteArray
280    },
281
282    {   "nativeNewInstance",
283        "(Ljava/io/InputStream;[BZ)Landroid/graphics/BitmapRegionDecoder;",
284        (void*)nativeNewInstanceFromStream
285    },
286
287    {   "nativeNewInstance",
288        "(Ljava/io/FileDescriptor;Z)Landroid/graphics/BitmapRegionDecoder;",
289        (void*)nativeNewInstanceFromFileDescriptor
290    },
291
292    {   "nativeNewInstance",
293        "(IZ)Landroid/graphics/BitmapRegionDecoder;",
294        (void*)nativeNewInstanceFromAsset
295    },
296};
297
298#define kClassPathName  "android/graphics/BitmapRegionDecoder"
299
300int register_android_graphics_BitmapRegionDecoder(JNIEnv* env);
301int register_android_graphics_BitmapRegionDecoder(JNIEnv* env)
302{
303    return android::AndroidRuntime::registerNativeMethods(env, kClassPathName,
304            gBitmapRegionDecoderMethods, SK_ARRAY_COUNT(gBitmapRegionDecoderMethods));
305}
306