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