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#include "SkTScopedPtr.h"
33
34#include <android_runtime/AndroidRuntime.h>
35#include "android_util_Binder.h"
36#include "android_nio_utils.h"
37#include "CreateJavaOutputStreamAdaptor.h"
38
39#include <binder/Parcel.h>
40#include <jni.h>
41#include <androidfw/Asset.h>
42#include <sys/stat.h>
43
44#if 0
45    #define TRACE_BITMAP(code)  code
46#else
47    #define TRACE_BITMAP(code)
48#endif
49
50using namespace android;
51
52static SkMemoryStream* buildSkMemoryStream(SkStream *stream) {
53    size_t bufferSize = 4096;
54    size_t streamLen = 0;
55    size_t len;
56    char* data = (char*)sk_malloc_throw(bufferSize);
57
58    while ((len = stream->read(data + streamLen,
59                    bufferSize - streamLen)) != 0) {
60        streamLen += len;
61        if (streamLen == bufferSize) {
62            bufferSize *= 2;
63            data = (char*)sk_realloc_throw(data, bufferSize);
64        }
65    }
66    data = (char*)sk_realloc_throw(data, streamLen);
67
68    SkMemoryStream* streamMem = new SkMemoryStream();
69    streamMem->setMemoryOwned(data, streamLen);
70    return streamMem;
71}
72
73static jobject doBuildTileIndex(JNIEnv* env, SkStream* stream) {
74    SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
75    int width, height;
76    if (NULL == decoder) {
77        doThrowIOE(env, "Image format not supported");
78        return nullObjectReturn("SkImageDecoder::Factory returned null");
79    }
80
81    JavaPixelAllocator *javaAllocator = new JavaPixelAllocator(env);
82    decoder->setAllocator(javaAllocator);
83    javaAllocator->unref();
84
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        return nullObjectReturn("decoder->buildTileIndex returned false");
91    }
92
93    SkBitmapRegionDecoder *bm = new SkBitmapRegionDecoder(decoder, stream, width, height);
94
95    return GraphicsJNI::createBitmapRegionDecoder(env, bm);
96}
97
98static jobject nativeNewInstanceFromByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
99                                     int offset, int 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    SkStream* stream = new SkMemoryStream(ar.ptr() + offset, length, true);
106    return doBuildTileIndex(env, stream);
107}
108
109static jobject nativeNewInstanceFromFileDescriptor(JNIEnv* env, jobject clazz,
110                                          jobject fileDescriptor, jboolean isShareable) {
111    NPE_CHECK_RETURN_ZERO(env, fileDescriptor);
112
113    jint descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor);
114    SkStream *stream = NULL;
115    struct stat fdStat;
116    int newFD;
117    if (fstat(descriptor, &fdStat) == -1) {
118        doThrowIOE(env, "broken file descriptor");
119        return nullObjectReturn("fstat return -1");
120    }
121
122    if (isShareable &&
123            S_ISREG(fdStat.st_mode) &&
124            (newFD = ::dup(descriptor)) != -1) {
125        SkFDStream* fdStream = new SkFDStream(newFD, true);
126        if (!fdStream->isValid()) {
127            fdStream->unref();
128            return NULL;
129        }
130        stream = fdStream;
131    } else {
132        /* Restore our offset when we leave, so we can be called more than once
133           with the same descriptor. This is only required if we didn't dup the
134           file descriptor, but it is OK to do it all the time.
135        */
136        AutoFDSeek as(descriptor);
137
138        SkFDStream* fdStream = new SkFDStream(descriptor, false);
139        if (!fdStream->isValid()) {
140            fdStream->unref();
141            return NULL;
142        }
143        stream = buildSkMemoryStream(fdStream);
144        fdStream->unref();
145    }
146
147    return doBuildTileIndex(env, stream);
148}
149
150static jobject nativeNewInstanceFromStream(JNIEnv* env, jobject clazz,
151                                  jobject is,       // InputStream
152                                  jbyteArray storage, // byte[]
153                                  jboolean isShareable) {
154    jobject largeBitmap = NULL;
155    SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage, 1024);
156
157    if (stream) {
158        // for now we don't allow shareable with java inputstreams
159        SkMemoryStream *mStream = buildSkMemoryStream(stream);
160        largeBitmap = doBuildTileIndex(env, mStream);
161        stream->unref();
162    }
163    return largeBitmap;
164}
165
166static jobject nativeNewInstanceFromAsset(JNIEnv* env, jobject clazz,
167                                 jint native_asset, // Asset
168                                 jboolean isShareable) {
169    SkStream* stream, *assStream;
170    Asset* asset = reinterpret_cast<Asset*>(native_asset);
171    assStream = new AssetStreamAdaptor(asset);
172    stream = buildSkMemoryStream(assStream);
173    assStream->unref();
174    return doBuildTileIndex(env, stream);
175}
176
177/*
178 * nine patch not supported
179 *
180 * purgeable not supported
181 * reportSizeToVM not supported
182 */
183static jobject nativeDecodeRegion(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd,
184                                int start_x, int start_y, int width, int height, jobject options) {
185    jobject tileBitmap = NULL;
186    SkImageDecoder *decoder = brd->getDecoder();
187    int sampleSize = 1;
188    SkBitmap::Config prefConfig = SkBitmap::kNo_Config;
189    bool doDither = true;
190    bool preferQualityOverSpeed = false;
191
192    if (NULL != options) {
193        sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
194        // initialize these, in case we fail later on
195        env->SetIntField(options, gOptions_widthFieldID, -1);
196        env->SetIntField(options, gOptions_heightFieldID, -1);
197        env->SetObjectField(options, gOptions_mimeFieldID, 0);
198
199        jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
200        prefConfig = GraphicsJNI::getNativeBitmapConfig(env, jconfig);
201        doDither = env->GetBooleanField(options, gOptions_ditherFieldID);
202        preferQualityOverSpeed = env->GetBooleanField(options,
203                gOptions_preferQualityOverSpeedFieldID);
204        // Get the bitmap for re-use if it exists.
205        tileBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);
206    }
207
208    decoder->setDitherImage(doDither);
209    decoder->setPreferQualityOverSpeed(preferQualityOverSpeed);
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    SkBitmap* bitmap = NULL;
225    SkTScopedPtr<SkBitmap> adb;
226
227    if (tileBitmap != NULL) {
228        // Re-use bitmap.
229        bitmap = GraphicsJNI::getNativeBitmap(env, tileBitmap);
230    }
231    if (bitmap == NULL) {
232        bitmap = new SkBitmap;
233        adb.reset(bitmap);
234    }
235
236    if (!brd->decodeRegion(bitmap, region, prefConfig, sampleSize)) {
237        return nullObjectReturn("decoder->decodeRegion returned false");
238    }
239
240    // update options (if any)
241    if (NULL != options) {
242        env->SetIntField(options, gOptions_widthFieldID, bitmap->width());
243        env->SetIntField(options, gOptions_heightFieldID, bitmap->height());
244        // TODO: set the mimeType field with the data from the codec.
245        // but how to reuse a set of strings, rather than allocating new one
246        // each time?
247        env->SetObjectField(options, gOptions_mimeFieldID,
248                            getMimeTypeString(env, decoder->getFormat()));
249    }
250
251    if (tileBitmap != NULL) {
252        return tileBitmap;
253    }
254
255    // detach bitmap from its autodeleter, since we want to own it now
256    adb.release();
257
258    JavaPixelAllocator* allocator = (JavaPixelAllocator*) decoder->getAllocator();
259    jbyteArray buff = allocator->getStorageObjAndReset();
260    return GraphicsJNI::createBitmap(env, bitmap, buff, false, NULL, NULL, -1);
261}
262
263static int nativeGetHeight(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) {
264    return brd->getHeight();
265}
266
267static int nativeGetWidth(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) {
268    return brd->getWidth();
269}
270
271static void nativeClean(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) {
272    delete brd;
273}
274
275///////////////////////////////////////////////////////////////////////////////
276
277#include <android_runtime/AndroidRuntime.h>
278
279static JNINativeMethod gBitmapRegionDecoderMethods[] = {
280    {   "nativeDecodeRegion",
281        "(IIIIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
282        (void*)nativeDecodeRegion},
283
284    {   "nativeGetHeight", "(I)I", (void*)nativeGetHeight},
285
286    {   "nativeGetWidth", "(I)I", (void*)nativeGetWidth},
287
288    {   "nativeClean", "(I)V", (void*)nativeClean},
289
290    {   "nativeNewInstance",
291        "([BIIZ)Landroid/graphics/BitmapRegionDecoder;",
292        (void*)nativeNewInstanceFromByteArray
293    },
294
295    {   "nativeNewInstance",
296        "(Ljava/io/InputStream;[BZ)Landroid/graphics/BitmapRegionDecoder;",
297        (void*)nativeNewInstanceFromStream
298    },
299
300    {   "nativeNewInstance",
301        "(Ljava/io/FileDescriptor;Z)Landroid/graphics/BitmapRegionDecoder;",
302        (void*)nativeNewInstanceFromFileDescriptor
303    },
304
305    {   "nativeNewInstance",
306        "(IZ)Landroid/graphics/BitmapRegionDecoder;",
307        (void*)nativeNewInstanceFromAsset
308    },
309};
310
311#define kClassPathName  "android/graphics/BitmapRegionDecoder"
312
313int register_android_graphics_BitmapRegionDecoder(JNIEnv* env)
314{
315    return android::AndroidRuntime::registerNativeMethods(env, kClassPathName,
316            gBitmapRegionDecoderMethods, SK_ARRAY_COUNT(gBitmapRegionDecoderMethods));
317}
318