BitmapRegionDecoder.cpp revision afde46ed008f150e45e1b0d7e1dc588fc047b74f
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
69    SkMemoryStream* streamMem = new SkMemoryStream();
70    streamMem->setMemoryOwned(data, streamLen);
71    return streamMem;
72}
73
74static jobject doBuildTileIndex(JNIEnv* env, SkStream* stream) {
75    SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
76    int width, height;
77    if (NULL == decoder) {
78        doThrowIOE(env, "Image format not supported");
79        return nullObjectReturn("SkImageDecoder::Factory returned null");
80    }
81
82    JavaPixelAllocator *javaAllocator = new JavaPixelAllocator(env);
83    decoder->setAllocator(javaAllocator);
84    JavaMemoryUsageReporter *javaMemoryReporter = new JavaMemoryUsageReporter(env);
85    decoder->setReporter(javaMemoryReporter);
86    javaAllocator->unref();
87    javaMemoryReporter->unref();
88
89    if (!decoder->buildTileIndex(stream, &width, &height)) {
90        char msg[100];
91        snprintf(msg, sizeof(msg), "Image failed to decode using %s decoder",
92                decoder->getFormatName());
93        doThrowIOE(env, msg);
94        return nullObjectReturn("decoder->buildTileIndex returned false");
95    }
96
97    SkBitmapRegionDecoder *bm = new SkBitmapRegionDecoder(decoder, width, height);
98
99    return GraphicsJNI::createBitmapRegionDecoder(env, bm);
100}
101
102static jobject nativeNewInstanceFromByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
103                                     int offset, int length, jboolean isShareable) {
104    /*  If isShareable we could decide to just wrap the java array and
105        share it, but that means adding a globalref to the java array object
106        For now we just always copy the array's data if isShareable.
107     */
108    AutoJavaByteArray ar(env, byteArray);
109    SkStream* stream = new SkMemoryStream(ar.ptr() + offset, length, true);
110    return doBuildTileIndex(env, stream);
111}
112
113static jobject nativeNewInstanceFromFileDescriptor(JNIEnv* env, jobject clazz,
114                                          jobject fileDescriptor, jboolean isShareable) {
115    NPE_CHECK_RETURN_ZERO(env, fileDescriptor);
116
117    jint descriptor = env->GetIntField(fileDescriptor,
118                                       gFileDescriptor_descriptor);
119    SkStream *stream = NULL;
120    struct stat fdStat;
121    int newFD;
122    if (fstat(descriptor, &fdStat) == -1) {
123        doThrowIOE(env, "broken file descriptor");
124        return nullObjectReturn("fstat return -1");
125    }
126
127    if (isShareable &&
128            S_ISREG(fdStat.st_mode) &&
129            (newFD = ::dup(descriptor)) != -1) {
130        SkFDStream* fdStream = new SkFDStream(newFD, true);
131        if (!fdStream->isValid()) {
132            fdStream->unref();
133            return NULL;
134        }
135        stream = fdStream;
136    } else {
137        /* Restore our offset when we leave, so we can be called more than once
138           with the same descriptor. This is only required if we didn't dup the
139           file descriptor, but it is OK to do it all the time.
140        */
141        AutoFDSeek as(descriptor);
142
143        SkFDStream* fdStream = new SkFDStream(descriptor, false);
144        if (!fdStream->isValid()) {
145            fdStream->unref();
146            return NULL;
147        }
148        stream = buildSkMemoryStream(fdStream);
149        fdStream->unref();
150    }
151
152    return doBuildTileIndex(env, stream);
153}
154
155static jobject nativeNewInstanceFromStream(JNIEnv* env, jobject clazz,
156                                  jobject is,       // InputStream
157                                  jbyteArray storage, // byte[]
158                                  jboolean isShareable) {
159    jobject largeBitmap = NULL;
160    SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage, 1024);
161
162    if (stream) {
163        // for now we don't allow shareable with java inputstreams
164        SkMemoryStream *mStream = buildSkMemoryStream(stream);
165        largeBitmap = doBuildTileIndex(env, mStream);
166        stream->unref();
167    }
168    return largeBitmap;
169}
170
171static jobject nativeNewInstanceFromAsset(JNIEnv* env, jobject clazz,
172                                 jint native_asset, // Asset
173                                 jboolean isShareable) {
174    SkStream* stream, *assStream;
175    Asset* asset = reinterpret_cast<Asset*>(native_asset);
176    assStream = new AssetStreamAdaptor(asset);
177    stream = buildSkMemoryStream(assStream);
178    assStream->unref();
179    return doBuildTileIndex(env, stream);
180}
181
182/*
183 * nine patch not supported
184 *
185 * purgeable not supported
186 * reportSizeToVM not supported
187 */
188static jobject nativeDecodeRegion(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd,
189        int start_x, int start_y, int width, int height, jobject options) {
190    SkImageDecoder *decoder = brd->getDecoder();
191    int sampleSize = 1;
192    SkBitmap::Config prefConfig = SkBitmap::kNo_Config;
193    bool doDither = true;
194    bool preferQualityOverSpeed = false;
195
196    if (NULL != options) {
197        sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
198        // initialize these, in case we fail later on
199        env->SetIntField(options, gOptions_widthFieldID, -1);
200        env->SetIntField(options, gOptions_heightFieldID, -1);
201        env->SetObjectField(options, gOptions_mimeFieldID, 0);
202
203        jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
204        prefConfig = GraphicsJNI::getNativeBitmapConfig(env, jconfig);
205        doDither = env->GetBooleanField(options, gOptions_ditherFieldID);
206        preferQualityOverSpeed = env->GetBooleanField(options,
207                gOptions_preferQualityOverSpeedFieldID);
208    }
209
210    decoder->setDitherImage(doDither);
211    decoder->setPreferQualityOverSpeed(preferQualityOverSpeed);
212    SkBitmap*           bitmap = new SkBitmap;
213    SkAutoTDelete<SkBitmap>       adb(bitmap);
214    AutoDecoderCancel   adc(options, decoder);
215
216    // To fix the race condition in case "requestCancelDecode"
217    // happens earlier than AutoDecoderCancel object is added
218    // to the gAutoDecoderCancelMutex linked list.
219    if (NULL != options && env->GetBooleanField(options, gOptions_mCancelID)) {
220        return nullObjectReturn("gOptions_mCancelID");;
221    }
222
223    SkIRect region;
224    region.fLeft = start_x;
225    region.fTop = start_y;
226    region.fRight = start_x + width;
227    region.fBottom = start_y + height;
228
229    if (!brd->decodeRegion(bitmap, region, prefConfig, sampleSize)) {
230        return nullObjectReturn("decoder->decodeRegion returned false");
231    }
232
233    // update options (if any)
234    if (NULL != options) {
235        env->SetIntField(options, gOptions_widthFieldID, bitmap->width());
236        env->SetIntField(options, gOptions_heightFieldID, bitmap->height());
237        // TODO: set the mimeType field with the data from the codec.
238        // but how to reuse a set of strings, rather than allocating new one
239        // each time?
240        env->SetObjectField(options, gOptions_mimeFieldID,
241                            getMimeTypeString(env, decoder->getFormat()));
242    }
243
244    // detach bitmap from its autodeleter, since we want to own it now
245    adb.detach();
246
247    SkPixelRef* pr = bitmap->pixelRef();
248    // promise we will never change our pixels (great for sharing and pictures)
249    pr->setImmutable();
250
251    JavaPixelAllocator* allocator = (JavaPixelAllocator*) decoder->getAllocator();
252    jbyteArray buff = allocator->getStorageObjAndReset();
253    return GraphicsJNI::createBitmap(env, bitmap, buff, false, NULL, -1);
254}
255
256static int nativeGetHeight(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) {
257    return brd->getHeight();
258}
259
260static int nativeGetWidth(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) {
261    return brd->getWidth();
262}
263
264static void nativeClean(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) {
265    delete brd;
266}
267
268///////////////////////////////////////////////////////////////////////////////
269
270#include <android_runtime/AndroidRuntime.h>
271
272static JNINativeMethod gBitmapRegionDecoderMethods[] = {
273    {   "nativeDecodeRegion",
274        "(IIIIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
275        (void*)nativeDecodeRegion},
276
277    {   "nativeGetHeight", "(I)I", (void*)nativeGetHeight},
278
279    {   "nativeGetWidth", "(I)I", (void*)nativeGetWidth},
280
281    {   "nativeClean", "(I)V", (void*)nativeClean},
282
283    {   "nativeNewInstance",
284        "([BIIZ)Landroid/graphics/BitmapRegionDecoder;",
285        (void*)nativeNewInstanceFromByteArray
286    },
287
288    {   "nativeNewInstance",
289        "(Ljava/io/InputStream;[BZ)Landroid/graphics/BitmapRegionDecoder;",
290        (void*)nativeNewInstanceFromStream
291    },
292
293    {   "nativeNewInstance",
294        "(Ljava/io/FileDescriptor;Z)Landroid/graphics/BitmapRegionDecoder;",
295        (void*)nativeNewInstanceFromFileDescriptor
296    },
297
298    {   "nativeNewInstance",
299        "(IZ)Landroid/graphics/BitmapRegionDecoder;",
300        (void*)nativeNewInstanceFromAsset
301    },
302};
303
304#define kClassPathName  "android/graphics/BitmapRegionDecoder"
305
306static jclass make_globalref(JNIEnv* env, const char classname[]) {
307    jclass c = env->FindClass(classname);
308    SkASSERT(c);
309    return (jclass)env->NewGlobalRef(c);
310}
311
312static jfieldID getFieldIDCheck(JNIEnv* env, jclass clazz,
313                                const char fieldname[], const char type[]) {
314    jfieldID id = env->GetFieldID(clazz, fieldname, type);
315    SkASSERT(id);
316    return id;
317}
318
319int register_android_graphics_BitmapRegionDecoder(JNIEnv* env);
320int register_android_graphics_BitmapRegionDecoder(JNIEnv* env)
321{
322
323    gFileDescriptor_class = make_globalref(env, "java/io/FileDescriptor");
324    gFileDescriptor_descriptor = getFieldIDCheck(env, gFileDescriptor_class, "descriptor", "I");
325    return android::AndroidRuntime::registerNativeMethods(env, kClassPathName,
326            gBitmapRegionDecoderMethods, SK_ARRAY_COUNT(gBitmapRegionDecoderMethods));
327}
328