BitmapRegionDecoder.cpp revision 58c1579ce2634de31d24429c1b870d4256ee4f21
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, true);
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
195    if (NULL != options) {
196        sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
197        // initialize these, in case we fail later on
198        env->SetIntField(options, gOptions_widthFieldID, -1);
199        env->SetIntField(options, gOptions_heightFieldID, -1);
200        env->SetObjectField(options, gOptions_mimeFieldID, 0);
201
202        jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
203        prefConfig = GraphicsJNI::getNativeBitmapConfig(env, jconfig);
204        doDither = env->GetBooleanField(options, gOptions_ditherFieldID);
205    }
206
207    decoder->setDitherImage(doDither);
208    SkBitmap*           bitmap = new SkBitmap;
209    SkAutoTDelete<SkBitmap>       adb(bitmap);
210    AutoDecoderCancel   adc(options, decoder);
211
212    // To fix the race condition in case "requestCancelDecode"
213    // happens earlier than AutoDecoderCancel object is added
214    // to the gAutoDecoderCancelMutex linked list.
215    if (NULL != options && env->GetBooleanField(options, gOptions_mCancelID)) {
216        return nullObjectReturn("gOptions_mCancelID");;
217    }
218
219    SkIRect region;
220    region.fLeft = start_x;
221    region.fTop = start_y;
222    region.fRight = start_x + width;
223    region.fBottom = start_y + height;
224
225    if (!brd->decodeRegion(bitmap, region, prefConfig, sampleSize)) {
226        return nullObjectReturn("decoder->decodeRegion returned false");
227    }
228
229    // update options (if any)
230    if (NULL != options) {
231        env->SetIntField(options, gOptions_widthFieldID, bitmap->width());
232        env->SetIntField(options, gOptions_heightFieldID, bitmap->height());
233        // TODO: set the mimeType field with the data from the codec.
234        // but how to reuse a set of strings, rather than allocating new one
235        // each time?
236        env->SetObjectField(options, gOptions_mimeFieldID,
237                            getMimeTypeString(env, decoder->getFormat()));
238    }
239
240    // detach bitmap from its autotdeleter, since we want to own it now
241    adb.detach();
242
243    SkPixelRef* pr;
244    pr = bitmap->pixelRef();
245    // promise we will never change our pixels (great for sharing and pictures)
246    pr->setImmutable();
247    // now create the java bitmap
248    return GraphicsJNI::createBitmap(env, bitmap, false, NULL);
249}
250
251static int nativeGetHeight(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) {
252    return brd->getHeight();
253}
254
255static int nativeGetWidth(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) {
256    return brd->getWidth();
257}
258
259static void nativeClean(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) {
260    delete brd;
261}
262
263///////////////////////////////////////////////////////////////////////////////
264
265#include <android_runtime/AndroidRuntime.h>
266
267static JNINativeMethod gBitmapRegionDecoderMethods[] = {
268    {   "nativeDecodeRegion",
269        "(IIIIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
270        (void*)nativeDecodeRegion},
271
272    {   "nativeGetHeight", "(I)I", (void*)nativeGetHeight},
273
274    {   "nativeGetWidth", "(I)I", (void*)nativeGetWidth},
275
276    {   "nativeClean", "(I)V", (void*)nativeClean},
277
278    {   "nativeNewInstance",
279        "([BIIZ)Landroid/graphics/BitmapRegionDecoder;",
280        (void*)nativeNewInstanceFromByteArray
281    },
282
283    {   "nativeNewInstance",
284        "(Ljava/io/InputStream;[BZ)Landroid/graphics/BitmapRegionDecoder;",
285        (void*)nativeNewInstanceFromStream
286    },
287
288    {   "nativeNewInstance",
289        "(Ljava/io/FileDescriptor;Z)Landroid/graphics/BitmapRegionDecoder;",
290        (void*)nativeNewInstanceFromFileDescriptor
291    },
292
293    {   "nativeNewInstance",
294        "(IZ)Landroid/graphics/BitmapRegionDecoder;",
295        (void*)nativeNewInstanceFromAsset
296    },
297};
298
299#define kClassPathName  "android/graphics/BitmapRegionDecoder"
300
301static jclass make_globalref(JNIEnv* env, const char classname[]) {
302    jclass c = env->FindClass(classname);
303    SkASSERT(c);
304    return (jclass)env->NewGlobalRef(c);
305}
306
307static jfieldID getFieldIDCheck(JNIEnv* env, jclass clazz,
308                                const char fieldname[], const char type[]) {
309    jfieldID id = env->GetFieldID(clazz, fieldname, type);
310    SkASSERT(id);
311    return id;
312}
313
314int register_android_graphics_BitmapRegionDecoder(JNIEnv* env);
315int register_android_graphics_BitmapRegionDecoder(JNIEnv* env)
316{
317
318    gFileDescriptor_class = make_globalref(env, "java/io/FileDescriptor");
319    gFileDescriptor_descriptor = getFieldIDCheck(env, gFileDescriptor_class, "descriptor", "I");
320    return android::AndroidRuntime::registerNativeMethods(env, kClassPathName,
321            gBitmapRegionDecoderMethods, SK_ARRAY_COUNT(gBitmapRegionDecoderMethods));
322}
323