BitmapRegionDecoder.cpp revision a3804cf77f0edd93f6247a055cdafb856b117eec
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#include "JNIHelp.h"
32
33#include <android_runtime/AndroidRuntime.h>
34#include "android_util_Binder.h"
35#include "android_nio_utils.h"
36#include "CreateJavaOutputStreamAdaptor.h"
37
38#include <binder/Parcel.h>
39#include <jni.h>
40#include <utils/Asset.h>
41#include <sys/stat.h>
42
43#if 0
44    #define TRACE_BITMAP(code)  code
45#else
46    #define TRACE_BITMAP(code)
47#endif
48
49using namespace android;
50
51static SkMemoryStream* buildSkMemoryStream(SkStream *stream) {
52    size_t bufferSize = 4096;
53    size_t streamLen = 0;
54    size_t len;
55    char* data = (char*)sk_malloc_throw(bufferSize);
56
57    while ((len = stream->read(data + streamLen,
58                    bufferSize - streamLen)) != 0) {
59        streamLen += len;
60        if (streamLen == bufferSize) {
61            bufferSize *= 2;
62            data = (char*)sk_realloc_throw(data, bufferSize);
63        }
64    }
65    data = (char*)sk_realloc_throw(data, streamLen);
66
67    SkMemoryStream* streamMem = new SkMemoryStream();
68    streamMem->setMemoryOwned(data, streamLen);
69    return streamMem;
70}
71
72static jobject doBuildTileIndex(JNIEnv* env, SkStream* stream) {
73    SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
74    int width, height;
75    if (NULL == decoder) {
76        doThrowIOE(env, "Image format not supported");
77        return nullObjectReturn("SkImageDecoder::Factory returned null");
78    }
79
80    JavaPixelAllocator *javaAllocator = new JavaPixelAllocator(env);
81    decoder->setAllocator(javaAllocator);
82    javaAllocator->unref();
83
84    if (!decoder->buildTileIndex(stream, &width, &height)) {
85        char msg[100];
86        snprintf(msg, sizeof(msg), "Image failed to decode using %s decoder",
87                decoder->getFormatName());
88        doThrowIOE(env, msg);
89        return nullObjectReturn("decoder->buildTileIndex returned false");
90    }
91
92    SkBitmapRegionDecoder *bm = new SkBitmapRegionDecoder(decoder, stream, width, height);
93
94    return GraphicsJNI::createBitmapRegionDecoder(env, bm);
95}
96
97static jobject nativeNewInstanceFromByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
98                                     int offset, int length, jboolean isShareable) {
99    /*  If isShareable we could decide to just wrap the java array and
100        share it, but that means adding a globalref to the java array object
101        For now we just always copy the array's data if isShareable.
102     */
103    AutoJavaByteArray ar(env, byteArray);
104    SkStream* stream = new SkMemoryStream(ar.ptr() + offset, length, true);
105    return doBuildTileIndex(env, stream);
106}
107
108static jobject nativeNewInstanceFromFileDescriptor(JNIEnv* env, jobject clazz,
109                                          jobject fileDescriptor, jboolean isShareable) {
110    NPE_CHECK_RETURN_ZERO(env, fileDescriptor);
111
112    jint descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor);
113    SkStream *stream = NULL;
114    struct stat fdStat;
115    int newFD;
116    if (fstat(descriptor, &fdStat) == -1) {
117        doThrowIOE(env, "broken file descriptor");
118        return nullObjectReturn("fstat return -1");
119    }
120
121    if (isShareable &&
122            S_ISREG(fdStat.st_mode) &&
123            (newFD = ::dup(descriptor)) != -1) {
124        SkFDStream* fdStream = new SkFDStream(newFD, true);
125        if (!fdStream->isValid()) {
126            fdStream->unref();
127            return NULL;
128        }
129        stream = fdStream;
130    } else {
131        /* Restore our offset when we leave, so we can be called more than once
132           with the same descriptor. This is only required if we didn't dup the
133           file descriptor, but it is OK to do it all the time.
134        */
135        AutoFDSeek as(descriptor);
136
137        SkFDStream* fdStream = new SkFDStream(descriptor, false);
138        if (!fdStream->isValid()) {
139            fdStream->unref();
140            return NULL;
141        }
142        stream = buildSkMemoryStream(fdStream);
143        fdStream->unref();
144    }
145
146    return doBuildTileIndex(env, stream);
147}
148
149static jobject nativeNewInstanceFromStream(JNIEnv* env, jobject clazz,
150                                  jobject is,       // InputStream
151                                  jbyteArray storage, // byte[]
152                                  jboolean isShareable) {
153    jobject largeBitmap = NULL;
154    SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage, 1024);
155
156    if (stream) {
157        // for now we don't allow shareable with java inputstreams
158        SkMemoryStream *mStream = buildSkMemoryStream(stream);
159        largeBitmap = doBuildTileIndex(env, mStream);
160        stream->unref();
161    }
162    return largeBitmap;
163}
164
165static jobject nativeNewInstanceFromAsset(JNIEnv* env, jobject clazz,
166                                 jint native_asset, // Asset
167                                 jboolean isShareable) {
168    SkStream* stream, *assStream;
169    Asset* asset = reinterpret_cast<Asset*>(native_asset);
170    assStream = new AssetStreamAdaptor(asset);
171    stream = buildSkMemoryStream(assStream);
172    assStream->unref();
173    return doBuildTileIndex(env, stream);
174}
175
176/*
177 * nine patch not supported
178 *
179 * purgeable not supported
180 * reportSizeToVM not supported
181 */
182static jobject nativeDecodeRegion(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd,
183        int start_x, int start_y, int width, int height, jobject options) {
184    SkImageDecoder *decoder = brd->getDecoder();
185    int sampleSize = 1;
186    SkBitmap::Config prefConfig = SkBitmap::kNo_Config;
187    bool doDither = true;
188    bool preferQualityOverSpeed = false;
189
190    if (NULL != options) {
191        sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
192        // initialize these, in case we fail later on
193        env->SetIntField(options, gOptions_widthFieldID, -1);
194        env->SetIntField(options, gOptions_heightFieldID, -1);
195        env->SetObjectField(options, gOptions_mimeFieldID, 0);
196
197        jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
198        prefConfig = GraphicsJNI::getNativeBitmapConfig(env, jconfig);
199        doDither = env->GetBooleanField(options, gOptions_ditherFieldID);
200        preferQualityOverSpeed = env->GetBooleanField(options,
201                gOptions_preferQualityOverSpeedFieldID);
202    }
203
204    decoder->setDitherImage(doDither);
205    decoder->setPreferQualityOverSpeed(preferQualityOverSpeed);
206    SkBitmap*           bitmap = new SkBitmap;
207    SkAutoTDelete<SkBitmap>       adb(bitmap);
208    AutoDecoderCancel   adc(options, decoder);
209
210    // To fix the race condition in case "requestCancelDecode"
211    // happens earlier than AutoDecoderCancel object is added
212    // to the gAutoDecoderCancelMutex linked list.
213    if (NULL != options && env->GetBooleanField(options, gOptions_mCancelID)) {
214        return nullObjectReturn("gOptions_mCancelID");;
215    }
216
217    SkIRect region;
218    region.fLeft = start_x;
219    region.fTop = start_y;
220    region.fRight = start_x + width;
221    region.fBottom = start_y + height;
222
223    if (!brd->decodeRegion(bitmap, region, prefConfig, sampleSize)) {
224        return nullObjectReturn("decoder->decodeRegion returned false");
225    }
226
227    // update options (if any)
228    if (NULL != options) {
229        env->SetIntField(options, gOptions_widthFieldID, bitmap->width());
230        env->SetIntField(options, gOptions_heightFieldID, bitmap->height());
231        // TODO: set the mimeType field with the data from the codec.
232        // but how to reuse a set of strings, rather than allocating new one
233        // each time?
234        env->SetObjectField(options, gOptions_mimeFieldID,
235                            getMimeTypeString(env, decoder->getFormat()));
236    }
237
238    // detach bitmap from its autodeleter, since we want to own it now
239    adb.detach();
240
241    SkPixelRef* pr = bitmap->pixelRef();
242    // promise we will never change our pixels (great for sharing and pictures)
243    pr->setImmutable();
244
245    JavaPixelAllocator* allocator = (JavaPixelAllocator*) decoder->getAllocator();
246    jbyteArray buff = allocator->getStorageObjAndReset();
247    return GraphicsJNI::createBitmap(env, bitmap, buff, false, NULL, -1);
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)
301{
302    return android::AndroidRuntime::registerNativeMethods(env, kClassPathName,
303            gBitmapRegionDecoderMethods, SK_ARRAY_COUNT(gBitmapRegionDecoderMethods));
304}
305