/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "BitmapRegionDecoder" #include "SkBitmap.h" #include "SkImageEncoder.h" #include "GraphicsJNI.h" #include "SkUtils.h" #include "SkTemplates.h" #include "SkPixelRef.h" #include "SkStream.h" #include "BitmapFactory.h" #include "AutoDecodeCancel.h" #include "SkBitmapRegionDecoder.h" #include "CreateJavaOutputStreamAdaptor.h" #include "Utils.h" #include #include "android_util_Binder.h" #include "android_nio_utils.h" #include "CreateJavaOutputStreamAdaptor.h" #include #include #include #include static jclass gFileDescriptor_class; static jfieldID gFileDescriptor_descriptor; #if 0 #define TRACE_BITMAP(code) code #else #define TRACE_BITMAP(code) #endif using namespace android; static SkMemoryStream* buildSkMemoryStream(SkStream *stream) { size_t bufferSize = 4096; size_t streamLen = 0; size_t len; char* data = (char*)sk_malloc_throw(bufferSize); while ((len = stream->read(data + streamLen, bufferSize - streamLen)) != 0) { streamLen += len; if (streamLen == bufferSize) { bufferSize *= 2; data = (char*)sk_realloc_throw(data, bufferSize); } } data = (char*)sk_realloc_throw(data, streamLen); SkMemoryStream* streamMem = new SkMemoryStream(); streamMem->setMemoryOwned(data, streamLen); return streamMem; } static jobject doBuildTileIndex(JNIEnv* env, SkStream* stream) { SkImageDecoder* decoder = SkImageDecoder::Factory(stream); int width, height; if (NULL == decoder) { doThrowIOE(env, "Image format not supported"); return nullObjectReturn("SkImageDecoder::Factory returned null"); } JavaPixelAllocator *javaAllocator = new JavaPixelAllocator(env, true); decoder->setAllocator(javaAllocator); JavaMemoryUsageReporter *javaMemoryReporter = new JavaMemoryUsageReporter(env); decoder->setReporter(javaMemoryReporter); javaAllocator->unref(); javaMemoryReporter->unref(); if (!decoder->buildTileIndex(stream, &width, &height)) { char msg[100]; snprintf(msg, sizeof(msg), "Image failed to decode using %s decoder", decoder->getFormatName()); doThrowIOE(env, msg); return nullObjectReturn("decoder->buildTileIndex returned false"); } SkBitmapRegionDecoder *bm = new SkBitmapRegionDecoder(decoder, width, height); return GraphicsJNI::createBitmapRegionDecoder(env, bm); } static jobject nativeNewInstanceFromByteArray(JNIEnv* env, jobject, jbyteArray byteArray, int offset, int length, jboolean isShareable) { /* If isShareable we could decide to just wrap the java array and share it, but that means adding a globalref to the java array object For now we just always copy the array's data if isShareable. */ AutoJavaByteArray ar(env, byteArray); SkStream* stream = new SkMemoryStream(ar.ptr() + offset, length, true); return doBuildTileIndex(env, stream); } static jobject nativeNewInstanceFromFileDescriptor(JNIEnv* env, jobject clazz, jobject fileDescriptor, jboolean isShareable) { NPE_CHECK_RETURN_ZERO(env, fileDescriptor); jint descriptor = env->GetIntField(fileDescriptor, gFileDescriptor_descriptor); SkStream *stream = NULL; struct stat fdStat; int newFD; if (fstat(descriptor, &fdStat) == -1) { doThrowIOE(env, "broken file descriptor"); return nullObjectReturn("fstat return -1"); } if (isShareable && S_ISREG(fdStat.st_mode) && (newFD = ::dup(descriptor)) != -1) { SkFDStream* fdStream = new SkFDStream(newFD, true); if (!fdStream->isValid()) { fdStream->unref(); return NULL; } stream = fdStream; } else { /* Restore our offset when we leave, so we can be called more than once with the same descriptor. This is only required if we didn't dup the file descriptor, but it is OK to do it all the time. */ AutoFDSeek as(descriptor); SkFDStream* fdStream = new SkFDStream(descriptor, false); if (!fdStream->isValid()) { fdStream->unref(); return NULL; } stream = buildSkMemoryStream(fdStream); fdStream->unref(); } return doBuildTileIndex(env, stream); } static jobject nativeNewInstanceFromStream(JNIEnv* env, jobject clazz, jobject is, // InputStream jbyteArray storage, // byte[] jboolean isShareable) { jobject largeBitmap = NULL; SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage, 1024); if (stream) { // for now we don't allow shareable with java inputstreams SkMemoryStream *mStream = buildSkMemoryStream(stream); largeBitmap = doBuildTileIndex(env, mStream); stream->unref(); } return largeBitmap; } static jobject nativeNewInstanceFromAsset(JNIEnv* env, jobject clazz, jint native_asset, // Asset jboolean isShareable) { SkStream* stream, *assStream; Asset* asset = reinterpret_cast(native_asset); assStream = new AssetStreamAdaptor(asset); stream = buildSkMemoryStream(assStream); assStream->unref(); return doBuildTileIndex(env, stream); } /* * nine patch not supported * * purgeable not supported * reportSizeToVM not supported */ static jobject nativeDecodeRegion(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd, int start_x, int start_y, int width, int height, jobject options) { SkImageDecoder *decoder = brd->getDecoder(); int sampleSize = 1; SkBitmap::Config prefConfig = SkBitmap::kNo_Config; bool doDither = true; bool preferQualityOverSpeed = false; if (NULL != options) { sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID); // initialize these, in case we fail later on env->SetIntField(options, gOptions_widthFieldID, -1); env->SetIntField(options, gOptions_heightFieldID, -1); env->SetObjectField(options, gOptions_mimeFieldID, 0); jobject jconfig = env->GetObjectField(options, gOptions_configFieldID); prefConfig = GraphicsJNI::getNativeBitmapConfig(env, jconfig); doDither = env->GetBooleanField(options, gOptions_ditherFieldID); preferQualityOverSpeed = env->GetBooleanField(options, gOptions_preferQualityOverSpeedFieldID); } decoder->setDitherImage(doDither); decoder->setPreferQualityOverSpeed(preferQualityOverSpeed); SkBitmap* bitmap = new SkBitmap; SkAutoTDelete adb(bitmap); AutoDecoderCancel adc(options, decoder); // To fix the race condition in case "requestCancelDecode" // happens earlier than AutoDecoderCancel object is added // to the gAutoDecoderCancelMutex linked list. if (NULL != options && env->GetBooleanField(options, gOptions_mCancelID)) { return nullObjectReturn("gOptions_mCancelID");; } SkIRect region; region.fLeft = start_x; region.fTop = start_y; region.fRight = start_x + width; region.fBottom = start_y + height; if (!brd->decodeRegion(bitmap, region, prefConfig, sampleSize)) { return nullObjectReturn("decoder->decodeRegion returned false"); } // update options (if any) if (NULL != options) { env->SetIntField(options, gOptions_widthFieldID, bitmap->width()); env->SetIntField(options, gOptions_heightFieldID, bitmap->height()); // TODO: set the mimeType field with the data from the codec. // but how to reuse a set of strings, rather than allocating new one // each time? env->SetObjectField(options, gOptions_mimeFieldID, getMimeTypeString(env, decoder->getFormat())); } // detach bitmap from its autotdeleter, since we want to own it now adb.detach(); SkPixelRef* pr; pr = bitmap->pixelRef(); // promise we will never change our pixels (great for sharing and pictures) pr->setImmutable(); // now create the java bitmap return GraphicsJNI::createBitmap(env, bitmap, false, NULL); } static int nativeGetHeight(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) { return brd->getHeight(); } static int nativeGetWidth(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) { return brd->getWidth(); } static void nativeClean(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) { delete brd; } /////////////////////////////////////////////////////////////////////////////// #include static JNINativeMethod gBitmapRegionDecoderMethods[] = { { "nativeDecodeRegion", "(IIIIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;", (void*)nativeDecodeRegion}, { "nativeGetHeight", "(I)I", (void*)nativeGetHeight}, { "nativeGetWidth", "(I)I", (void*)nativeGetWidth}, { "nativeClean", "(I)V", (void*)nativeClean}, { "nativeNewInstance", "([BIIZ)Landroid/graphics/BitmapRegionDecoder;", (void*)nativeNewInstanceFromByteArray }, { "nativeNewInstance", "(Ljava/io/InputStream;[BZ)Landroid/graphics/BitmapRegionDecoder;", (void*)nativeNewInstanceFromStream }, { "nativeNewInstance", "(Ljava/io/FileDescriptor;Z)Landroid/graphics/BitmapRegionDecoder;", (void*)nativeNewInstanceFromFileDescriptor }, { "nativeNewInstance", "(IZ)Landroid/graphics/BitmapRegionDecoder;", (void*)nativeNewInstanceFromAsset }, }; #define kClassPathName "android/graphics/BitmapRegionDecoder" static jclass make_globalref(JNIEnv* env, const char classname[]) { jclass c = env->FindClass(classname); SkASSERT(c); return (jclass)env->NewGlobalRef(c); } static jfieldID getFieldIDCheck(JNIEnv* env, jclass clazz, const char fieldname[], const char type[]) { jfieldID id = env->GetFieldID(clazz, fieldname, type); SkASSERT(id); return id; } int register_android_graphics_BitmapRegionDecoder(JNIEnv* env); int register_android_graphics_BitmapRegionDecoder(JNIEnv* env) { gFileDescriptor_class = make_globalref(env, "java/io/FileDescriptor"); gFileDescriptor_descriptor = getFieldIDCheck(env, gFileDescriptor_class, "descriptor", "I"); return android::AndroidRuntime::registerNativeMethods(env, kClassPathName, gBitmapRegionDecoderMethods, SK_ARRAY_COUNT(gBitmapRegionDecoderMethods)); }