Home | History | Annotate | Download | only in graphics
      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 
     50 using namespace android;
     51 
     52 static 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 
     73 static 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 
     98 static 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 
    109 static 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 
    150 static 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 
    166 static 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  */
    183 static 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 
    263 static int nativeGetHeight(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) {
    264     return brd->getHeight();
    265 }
    266 
    267 static int nativeGetWidth(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) {
    268     return brd->getWidth();
    269 }
    270 
    271 static void nativeClean(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) {
    272     delete brd;
    273 }
    274 
    275 ///////////////////////////////////////////////////////////////////////////////
    276 
    277 #include <android_runtime/AndroidRuntime.h>
    278 
    279 static 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 
    313 int register_android_graphics_BitmapRegionDecoder(JNIEnv* env)
    314 {
    315     return android::AndroidRuntime::registerNativeMethods(env, kClassPathName,
    316             gBitmapRegionDecoderMethods, SK_ARRAY_COUNT(gBitmapRegionDecoderMethods));
    317 }
    318