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