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