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
42 static jclass gFileDescriptor_class;
43 static jfieldID gFileDescriptor_descriptor;
44
45 #if 0
46 #define TRACE_BITMAP(code) code
47 #else
48 #define TRACE_BITMAP(code)
49 #endif
50
51 using namespace android;
52
buildSkMemoryStream(SkStream * stream)53 static 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
doBuildTileIndex(JNIEnv * env,SkStream * stream)74 static 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, true);
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
nativeNewInstanceFromByteArray(JNIEnv * env,jobject,jbyteArray byteArray,int offset,int length,jboolean isShareable)102 static 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
nativeNewInstanceFromFileDescriptor(JNIEnv * env,jobject clazz,jobject fileDescriptor,jboolean isShareable)113 static 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
nativeNewInstanceFromStream(JNIEnv * env,jobject clazz,jobject is,jbyteArray storage,jboolean isShareable)155 static 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
nativeNewInstanceFromAsset(JNIEnv * env,jobject clazz,jint native_asset,jboolean isShareable)171 static 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 */
nativeDecodeRegion(JNIEnv * env,jobject,SkBitmapRegionDecoder * brd,int start_x,int start_y,int width,int height,jobject options)188 static 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 autotdeleter, since we want to own it now
245 adb.detach();
246
247 SkPixelRef* pr;
248 pr = bitmap->pixelRef();
249 // promise we will never change our pixels (great for sharing and pictures)
250 pr->setImmutable();
251 // now create the java bitmap
252 return GraphicsJNI::createBitmap(env, bitmap, false, NULL);
253 }
254
nativeGetHeight(JNIEnv * env,jobject,SkBitmapRegionDecoder * brd)255 static int nativeGetHeight(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) {
256 return brd->getHeight();
257 }
258
nativeGetWidth(JNIEnv * env,jobject,SkBitmapRegionDecoder * brd)259 static int nativeGetWidth(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) {
260 return brd->getWidth();
261 }
262
nativeClean(JNIEnv * env,jobject,SkBitmapRegionDecoder * brd)263 static void nativeClean(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) {
264 delete brd;
265 }
266
267 ///////////////////////////////////////////////////////////////////////////////
268
269 #include <android_runtime/AndroidRuntime.h>
270
271 static JNINativeMethod gBitmapRegionDecoderMethods[] = {
272 { "nativeDecodeRegion",
273 "(IIIIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
274 (void*)nativeDecodeRegion},
275
276 { "nativeGetHeight", "(I)I", (void*)nativeGetHeight},
277
278 { "nativeGetWidth", "(I)I", (void*)nativeGetWidth},
279
280 { "nativeClean", "(I)V", (void*)nativeClean},
281
282 { "nativeNewInstance",
283 "([BIIZ)Landroid/graphics/BitmapRegionDecoder;",
284 (void*)nativeNewInstanceFromByteArray
285 },
286
287 { "nativeNewInstance",
288 "(Ljava/io/InputStream;[BZ)Landroid/graphics/BitmapRegionDecoder;",
289 (void*)nativeNewInstanceFromStream
290 },
291
292 { "nativeNewInstance",
293 "(Ljava/io/FileDescriptor;Z)Landroid/graphics/BitmapRegionDecoder;",
294 (void*)nativeNewInstanceFromFileDescriptor
295 },
296
297 { "nativeNewInstance",
298 "(IZ)Landroid/graphics/BitmapRegionDecoder;",
299 (void*)nativeNewInstanceFromAsset
300 },
301 };
302
303 #define kClassPathName "android/graphics/BitmapRegionDecoder"
304
make_globalref(JNIEnv * env,const char classname[])305 static jclass make_globalref(JNIEnv* env, const char classname[]) {
306 jclass c = env->FindClass(classname);
307 SkASSERT(c);
308 return (jclass)env->NewGlobalRef(c);
309 }
310
getFieldIDCheck(JNIEnv * env,jclass clazz,const char fieldname[],const char type[])311 static jfieldID getFieldIDCheck(JNIEnv* env, jclass clazz,
312 const char fieldname[], const char type[]) {
313 jfieldID id = env->GetFieldID(clazz, fieldname, type);
314 SkASSERT(id);
315 return id;
316 }
317
318 int register_android_graphics_BitmapRegionDecoder(JNIEnv* env);
register_android_graphics_BitmapRegionDecoder(JNIEnv * env)319 int register_android_graphics_BitmapRegionDecoder(JNIEnv* env)
320 {
321
322 gFileDescriptor_class = make_globalref(env, "java/io/FileDescriptor");
323 gFileDescriptor_descriptor = getFieldIDCheck(env, gFileDescriptor_class, "descriptor", "I");
324 return android::AndroidRuntime::registerNativeMethods(env, kClassPathName,
325 gBitmapRegionDecoderMethods, SK_ARRAY_COUNT(gBitmapRegionDecoderMethods));
326 }
327