BitmapRegionDecoder.cpp revision 60126efd7d905ca24822765c6dafac17fef278ab
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 "AutoDecodeCancel.h"
20#include "BitmapFactory.h"
21#include "CreateJavaOutputStreamAdaptor.h"
22#include "SkBitmap.h"
23#include "SkData.h"
24#include "GraphicsJNI.h"
25#include "SkImageEncoder.h"
26#include "SkUtils.h"
27#include "SkPixelRef.h"
28#include "SkStream.h"
29#include "Utils.h"
30#include "android_nio_utils.h"
31#include "android_util_Binder.h"
32#include "core_jni_helpers.h"
33
34#include <JNIHelp.h>
35#include <androidfw/Asset.h>
36#include <binder/Parcel.h>
37#include <jni.h>
38#include <sys/stat.h>
39
40using namespace android;
41
42class SkBitmapRegionDecoder {
43public:
44    SkBitmapRegionDecoder(SkImageDecoder* decoder, int width, int height) {
45        fDecoder = decoder;
46        fWidth = width;
47        fHeight = height;
48    }
49    ~SkBitmapRegionDecoder() {
50        SkDELETE(fDecoder);
51    }
52
53    bool decodeRegion(SkBitmap* bitmap, const SkIRect& rect,
54                      SkColorType pref, int sampleSize) {
55        fDecoder->setSampleSize(sampleSize);
56        return fDecoder->decodeSubset(bitmap, rect, pref);
57    }
58
59    SkImageDecoder* getDecoder() const { return fDecoder; }
60    int getWidth() const { return fWidth; }
61    int getHeight() const { return fHeight; }
62
63private:
64    SkImageDecoder* fDecoder;
65    int fWidth;
66    int fHeight;
67};
68
69// Takes ownership of the SkStreamRewindable. For consistency, deletes stream even
70// when returning null.
71static jobject createBitmapRegionDecoder(JNIEnv* env, SkStreamRewindable* stream) {
72    SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
73    int width, height;
74    if (NULL == decoder) {
75        SkDELETE(stream);
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    // This call passes ownership of stream to the decoder, or deletes on failure.
85    if (!decoder->buildTileIndex(stream, &width, &height)) {
86        char msg[100];
87        snprintf(msg, sizeof(msg), "Image failed to decode using %s decoder",
88                decoder->getFormatName());
89        doThrowIOE(env, msg);
90        SkDELETE(decoder);
91        return nullObjectReturn("decoder->buildTileIndex returned false");
92    }
93
94    SkBitmapRegionDecoder *bm = new SkBitmapRegionDecoder(decoder, width, height);
95    return GraphicsJNI::createBitmapRegionDecoder(env, bm);
96}
97
98static jobject nativeNewInstanceFromByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
99                                     jint offset, jint length, jboolean isShareable) {
100    /*  If isShareable we could decide to just wrap the java array and
101        share it, but that means adding a globalref to the java array object
102        For now we just always copy the array's data if isShareable.
103     */
104    AutoJavaByteArray ar(env, byteArray);
105    SkMemoryStream* stream = new SkMemoryStream(ar.ptr() + offset, length, true);
106
107    // the decoder owns the stream.
108    jobject brd = createBitmapRegionDecoder(env, stream);
109    return brd;
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 = jniGetFDFromFileDescriptor(env, fileDescriptor);
117
118    struct stat fdStat;
119    if (fstat(descriptor, &fdStat) == -1) {
120        doThrowIOE(env, "broken file descriptor");
121        return nullObjectReturn("fstat return -1");
122    }
123
124    SkAutoTUnref<SkData> data(SkData::NewFromFD(descriptor));
125    SkMemoryStream* stream = new SkMemoryStream(data);
126
127    // the decoder owns the stream.
128    jobject brd = createBitmapRegionDecoder(env, stream);
129    return brd;
130}
131
132static jobject nativeNewInstanceFromStream(JNIEnv* env, jobject clazz,
133                                  jobject is,       // InputStream
134                                  jbyteArray storage, // byte[]
135                                  jboolean isShareable) {
136    jobject brd = NULL;
137    // for now we don't allow shareable with java inputstreams
138    SkStreamRewindable* stream = CopyJavaInputStream(env, is, storage);
139
140    if (stream) {
141        // the decoder owns the stream.
142        brd = createBitmapRegionDecoder(env, stream);
143    }
144    return brd;
145}
146
147static jobject nativeNewInstanceFromAsset(JNIEnv* env, jobject clazz,
148                                 jlong native_asset, // Asset
149                                 jboolean isShareable) {
150    Asset* asset = reinterpret_cast<Asset*>(native_asset);
151    SkMemoryStream* stream = CopyAssetToStream(asset);
152    if (NULL == stream) {
153        return NULL;
154    }
155
156    // the decoder owns the stream.
157    jobject brd = createBitmapRegionDecoder(env, stream);
158    return brd;
159}
160
161/*
162 * nine patch not supported
163 *
164 * purgeable not supported
165 * reportSizeToVM not supported
166 */
167static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle,
168                                jint start_x, jint start_y, jint width, jint height, jobject options) {
169    SkBitmapRegionDecoder *brd = reinterpret_cast<SkBitmapRegionDecoder*>(brdHandle);
170    jobject tileBitmap = NULL;
171    SkImageDecoder *decoder = brd->getDecoder();
172    int sampleSize = 1;
173    SkColorType prefColorType = kUnknown_SkColorType;
174    bool doDither = true;
175    bool preferQualityOverSpeed = false;
176    bool requireUnpremultiplied = false;
177
178    if (NULL != options) {
179        sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
180        // initialize these, in case we fail later on
181        env->SetIntField(options, gOptions_widthFieldID, -1);
182        env->SetIntField(options, gOptions_heightFieldID, -1);
183        env->SetObjectField(options, gOptions_mimeFieldID, 0);
184
185        jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
186        prefColorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig);
187        doDither = env->GetBooleanField(options, gOptions_ditherFieldID);
188        preferQualityOverSpeed = env->GetBooleanField(options,
189                gOptions_preferQualityOverSpeedFieldID);
190        // Get the bitmap for re-use if it exists.
191        tileBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);
192        requireUnpremultiplied = !env->GetBooleanField(options, gOptions_premultipliedFieldID);
193    }
194
195    decoder->setDitherImage(doDither);
196    decoder->setPreferQualityOverSpeed(preferQualityOverSpeed);
197    decoder->setRequireUnpremultipliedColors(requireUnpremultiplied);
198    AutoDecoderCancel adc(options, decoder);
199
200    // To fix the race condition in case "requestCancelDecode"
201    // happens earlier than AutoDecoderCancel object is added
202    // to the gAutoDecoderCancelMutex linked list.
203    if (NULL != options && env->GetBooleanField(options, gOptions_mCancelID)) {
204        return nullObjectReturn("gOptions_mCancelID");;
205    }
206
207    SkIRect region;
208    region.fLeft = start_x;
209    region.fTop = start_y;
210    region.fRight = start_x + width;
211    region.fBottom = start_y + height;
212    SkBitmap bitmap;
213
214    if (tileBitmap != NULL) {
215        // Re-use bitmap.
216        GraphicsJNI::getSkBitmap(env, tileBitmap, &bitmap);
217    }
218
219    if (!brd->decodeRegion(&bitmap, region, prefColorType, sampleSize)) {
220        return nullObjectReturn("decoder->decodeRegion returned false");
221    }
222
223    // update options (if any)
224    if (NULL != options) {
225        env->SetIntField(options, gOptions_widthFieldID, bitmap.width());
226        env->SetIntField(options, gOptions_heightFieldID, bitmap.height());
227        // TODO: set the mimeType field with the data from the codec.
228        // but how to reuse a set of strings, rather than allocating new one
229        // each time?
230        env->SetObjectField(options, gOptions_mimeFieldID,
231                            getMimeTypeString(env, decoder->getFormat()));
232    }
233
234    if (tileBitmap != NULL) {
235        bitmap.notifyPixelsChanged();
236        return tileBitmap;
237    }
238
239    JavaPixelAllocator* allocator = (JavaPixelAllocator*) decoder->getAllocator();
240
241    int bitmapCreateFlags = 0;
242    if (!requireUnpremultiplied) bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Premultiplied;
243    return GraphicsJNI::createBitmap(env, allocator->getStorageObjAndReset(),
244            bitmapCreateFlags);
245}
246
247static jint nativeGetHeight(JNIEnv* env, jobject, jlong brdHandle) {
248    SkBitmapRegionDecoder *brd = reinterpret_cast<SkBitmapRegionDecoder*>(brdHandle);
249    return static_cast<jint>(brd->getHeight());
250}
251
252static jint nativeGetWidth(JNIEnv* env, jobject, jlong brdHandle) {
253    SkBitmapRegionDecoder *brd = reinterpret_cast<SkBitmapRegionDecoder*>(brdHandle);
254    return static_cast<jint>(brd->getWidth());
255}
256
257static void nativeClean(JNIEnv* env, jobject, jlong brdHandle) {
258    SkBitmapRegionDecoder *brd = reinterpret_cast<SkBitmapRegionDecoder*>(brdHandle);
259    delete brd;
260}
261
262///////////////////////////////////////////////////////////////////////////////
263
264static JNINativeMethod gBitmapRegionDecoderMethods[] = {
265    {   "nativeDecodeRegion",
266        "(JIIIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
267        (void*)nativeDecodeRegion},
268
269    {   "nativeGetHeight", "(J)I", (void*)nativeGetHeight},
270
271    {   "nativeGetWidth", "(J)I", (void*)nativeGetWidth},
272
273    {   "nativeClean", "(J)V", (void*)nativeClean},
274
275    {   "nativeNewInstance",
276        "([BIIZ)Landroid/graphics/BitmapRegionDecoder;",
277        (void*)nativeNewInstanceFromByteArray
278    },
279
280    {   "nativeNewInstance",
281        "(Ljava/io/InputStream;[BZ)Landroid/graphics/BitmapRegionDecoder;",
282        (void*)nativeNewInstanceFromStream
283    },
284
285    {   "nativeNewInstance",
286        "(Ljava/io/FileDescriptor;Z)Landroid/graphics/BitmapRegionDecoder;",
287        (void*)nativeNewInstanceFromFileDescriptor
288    },
289
290    {   "nativeNewInstance",
291        "(JZ)Landroid/graphics/BitmapRegionDecoder;",
292        (void*)nativeNewInstanceFromAsset
293    },
294};
295
296int register_android_graphics_BitmapRegionDecoder(JNIEnv* env)
297{
298    return android::RegisterMethodsOrDie(env, "android/graphics/BitmapRegionDecoder",
299            gBitmapRegionDecoderMethods, NELEM(gBitmapRegionDecoderMethods));
300}
301