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