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