blob: 4ad2eb062c396b31748430ca7cbeb157354c1b00 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001#define LOG_TAG "BitmapFactory"
2
3#include "SkImageDecoder.h"
4#include "SkPixelRef.h"
5#include "SkStream.h"
6#include "GraphicsJNI.h"
7#include "SkTemplates.h"
8#include "SkUtils.h"
9#include "CreateJavaOutputStreamAdaptor.h"
10
11#include <android_runtime/AndroidRuntime.h>
12#include <utils/Asset.h>
13#include <utils/ResourceTypes.h>
14#include <netinet/in.h>
15#include <sys/mman.h>
16
17static jclass gOptions_class;
18static jfieldID gOptions_justBoundsFieldID;
19static jfieldID gOptions_sampleSizeFieldID;
20static jfieldID gOptions_configFieldID;
21static jfieldID gOptions_ditherFieldID;
22static jfieldID gOptions_widthFieldID;
23static jfieldID gOptions_heightFieldID;
24static jfieldID gOptions_mimeFieldID;
Ray Chen0a6a0e92009-04-02 01:31:49 -070025static jfieldID gOptions_mCancelID;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026
27static jclass gFileDescriptor_class;
28static jfieldID gFileDescriptor_descriptor;
29
30#if 0
31 #define TRACE_BITMAP(code) code
32#else
33 #define TRACE_BITMAP(code)
34#endif
35
36//#define MIN_SIZE_TO_USE_MMAP (4*1024)
37
38///////////////////////////////////////////////////////////////////////////////
39
40class AutoDecoderCancel {
41public:
42 AutoDecoderCancel(jobject options, SkImageDecoder* decoder);
43 ~AutoDecoderCancel();
44
45 static bool RequestCancel(jobject options);
46
47private:
48 AutoDecoderCancel* fNext;
49 AutoDecoderCancel* fPrev;
50 jobject fJOptions; // java options object
51 SkImageDecoder* fDecoder;
52
53#ifdef SK_DEBUG
54 static void Validate();
55#else
56 static void Validate() {}
57#endif
58};
59
60static SkMutex gAutoDecoderCancelMutex;
61static AutoDecoderCancel* gAutoDecoderCancel;
62#ifdef SK_DEBUG
63 static int gAutoDecoderCancelCount;
64#endif
65
66AutoDecoderCancel::AutoDecoderCancel(jobject joptions,
67 SkImageDecoder* decoder) {
68 fJOptions = joptions;
69 fDecoder = decoder;
70
71 if (NULL != joptions) {
72 SkAutoMutexAcquire ac(gAutoDecoderCancelMutex);
73
74 // Add us as the head of the list
75 fPrev = NULL;
76 fNext = gAutoDecoderCancel;
77 if (gAutoDecoderCancel) {
78 gAutoDecoderCancel->fPrev = this;
79 }
80 gAutoDecoderCancel = this;
81
82 SkDEBUGCODE(gAutoDecoderCancelCount += 1;)
83 Validate();
84 }
85}
86
87AutoDecoderCancel::~AutoDecoderCancel() {
88 if (NULL != fJOptions) {
89 SkAutoMutexAcquire ac(gAutoDecoderCancelMutex);
90
91 // take us out of the dllist
92 AutoDecoderCancel* prev = fPrev;
93 AutoDecoderCancel* next = fNext;
94
95 if (prev) {
96 SkASSERT(prev->fNext == this);
97 prev->fNext = next;
98 } else {
99 SkASSERT(gAutoDecoderCancel == this);
100 gAutoDecoderCancel = next;
101 }
102 if (next) {
103 SkASSERT(next->fPrev == this);
104 next->fPrev = prev;
105 }
106
107 SkDEBUGCODE(gAutoDecoderCancelCount -= 1;)
108 Validate();
109 }
110}
111
112bool AutoDecoderCancel::RequestCancel(jobject joptions) {
113 SkAutoMutexAcquire ac(gAutoDecoderCancelMutex);
114
115 Validate();
116
117 AutoDecoderCancel* pair = gAutoDecoderCancel;
118 while (pair != NULL) {
119 if (pair->fJOptions == joptions) {
120 pair->fDecoder->cancelDecode();
121 return true;
122 }
123 pair = pair->fNext;
124 }
125 return false;
126}
127
128#ifdef SK_DEBUG
129// can only call this inside a lock on gAutoDecoderCancelMutex
130void AutoDecoderCancel::Validate() {
131 const int gCount = gAutoDecoderCancelCount;
132
133 if (gCount == 0) {
134 SkASSERT(gAutoDecoderCancel == NULL);
135 } else {
136 SkASSERT(gCount > 0);
137
138 AutoDecoderCancel* curr = gAutoDecoderCancel;
139 SkASSERT(curr);
140 SkASSERT(curr->fPrev == NULL);
141
142 int count = 0;
143 while (curr) {
144 count += 1;
145 SkASSERT(count <= gCount);
146 if (curr->fPrev) {
147 SkASSERT(curr->fPrev->fNext == curr);
148 }
149 if (curr->fNext) {
150 SkASSERT(curr->fNext->fPrev == curr);
151 }
152 curr = curr->fNext;
153 }
154 SkASSERT(count == gCount);
155 }
156}
157#endif
158
159///////////////////////////////////////////////////////////////////////////////
160
161using namespace android;
162
163class NinePatchPeeker : public SkImageDecoder::Peeker {
164public:
165 NinePatchPeeker() {
166 fPatchIsValid = false;
167 }
168
169 ~NinePatchPeeker() {
170 if (fPatchIsValid) {
171 free(fPatch);
172 }
173 }
174
175 bool fPatchIsValid;
176 Res_png_9patch* fPatch;
177
178 virtual bool peek(const char tag[], const void* data, size_t length) {
179 if (strcmp("npTc", tag) == 0 && length >= sizeof(Res_png_9patch)) {
180 Res_png_9patch* patch = (Res_png_9patch*) data;
181 size_t patchSize = patch->serializedSize();
182 assert(length == patchSize);
183 // You have to copy the data because it is owned by the png reader
184 Res_png_9patch* patchNew = (Res_png_9patch*) malloc(patchSize);
185 memcpy(patchNew, patch, patchSize);
186 // this relies on deserialization being done in place
187 Res_png_9patch::deserialize(patchNew);
188 patchNew->fileToDevice();
189 if (fPatchIsValid) {
190 free(fPatch);
191 }
192 fPatch = patchNew;
193 //printf("9patch: (%d,%d)-(%d,%d)\n",
194 // fPatch.sizeLeft, fPatch.sizeTop,
195 // fPatch.sizeRight, fPatch.sizeBottom);
196 fPatchIsValid = true;
197 } else {
198 fPatch = NULL;
199 }
200 return true; // keep on decoding
201 }
202};
203
204class AssetStreamAdaptor : public SkStream {
205public:
206 AssetStreamAdaptor(Asset* a) : fAsset(a) {}
207
Ray Chen0a6a0e92009-04-02 01:31:49 -0700208 virtual bool rewind() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800209 off_t pos = fAsset->seek(0, SEEK_SET);
210 return pos != (off_t)-1;
211 }
212
Ray Chen0a6a0e92009-04-02 01:31:49 -0700213 virtual size_t read(void* buffer, size_t size) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800214 ssize_t amount;
215
216 if (NULL == buffer) {
217 if (0 == size) { // caller is asking us for our total length
218 return fAsset->getLength();
219 }
220 // asset->seek returns new total offset
221 // we want to return amount that was skipped
222
223 off_t oldOffset = fAsset->seek(0, SEEK_CUR);
224 if (-1 == oldOffset) {
225 return 0;
226 }
227 off_t newOffset = fAsset->seek(size, SEEK_CUR);
228 if (-1 == newOffset) {
229 return 0;
230 }
231 amount = newOffset - oldOffset;
232 } else {
233 amount = fAsset->read(buffer, size);
234 }
235
236 if (amount < 0) {
237 amount = 0;
238 }
239 return amount;
240 }
241
242private:
243 Asset* fAsset;
244};
245
246///////////////////////////////////////////////////////////////////////////////
247
248static inline int32_t validOrNeg1(bool isValid, int32_t value) {
249// return isValid ? value : -1;
250 SkASSERT((int)isValid == 0 || (int)isValid == 1);
251 return ((int32_t)isValid - 1) | value;
252}
253
254static jstring getMimeTypeString(JNIEnv* env, SkImageDecoder::Format format) {
255 static const struct {
256 SkImageDecoder::Format fFormat;
257 const char* fMimeType;
258 } gMimeTypes[] = {
259 { SkImageDecoder::kBMP_Format, "image/bmp" },
260 { SkImageDecoder::kGIF_Format, "image/gif" },
261 { SkImageDecoder::kICO_Format, "image/x-ico" },
262 { SkImageDecoder::kJPEG_Format, "image/jpeg" },
263 { SkImageDecoder::kPNG_Format, "image/png" },
264 { SkImageDecoder::kWBMP_Format, "image/vnd.wap.wbmp" }
265 };
266
267 const char* cstr = NULL;
268 for (size_t i = 0; i < SK_ARRAY_COUNT(gMimeTypes); i++) {
269 if (gMimeTypes[i].fFormat == format) {
270 cstr = gMimeTypes[i].fMimeType;
271 break;
272 }
273 }
274
275 jstring jstr = 0;
276 if (NULL != cstr) {
277 jstr = env->NewStringUTF(cstr);
278 }
279 return jstr;
280}
281
282static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
283 jobject options) {
284
285 int sampleSize = 1;
286 SkImageDecoder::Mode mode = SkImageDecoder::kDecodePixels_Mode;
287 SkBitmap::Config prefConfig = SkBitmap::kNo_Config;
288 bool doDither = true;
289
290 if (NULL != options) {
291 sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
292 if (env->GetBooleanField(options, gOptions_justBoundsFieldID)) {
293 mode = SkImageDecoder::kDecodeBounds_Mode;
294 }
295 // initialize these, in case we fail later on
296 env->SetIntField(options, gOptions_widthFieldID, -1);
297 env->SetIntField(options, gOptions_heightFieldID, -1);
298 env->SetObjectField(options, gOptions_mimeFieldID, 0);
299
300 jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
301 prefConfig = GraphicsJNI::getNativeBitmapConfig(env, jconfig);
302 doDither = env->GetBooleanField(options, gOptions_ditherFieldID);
303 }
304
305 SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
306 if (NULL == decoder) {
307 return NULL;
308 }
309
310 decoder->setSampleSize(sampleSize);
311 decoder->setDitherImage(doDither);
312
313 NinePatchPeeker peeker;
314 JavaPixelAllocator allocator(env);
315 SkBitmap* bitmap = new SkBitmap;
316 Res_png_9patch dummy9Patch;
317
318 SkAutoTDelete<SkImageDecoder> add(decoder);
319 SkAutoTDelete<SkBitmap> adb(bitmap);
320
321 decoder->setPeeker(&peeker);
322 decoder->setAllocator(&allocator);
323
324 AutoDecoderCancel adc(options, decoder);
325
Ray Chen0a6a0e92009-04-02 01:31:49 -0700326 // To fix the race condition in case "requestCancelDecode"
327 // happens earlier than AutoDecoderCancel object is added
328 // to the gAutoDecoderCancelMutex linked list.
329 if (NULL != options) {
330 if (env->GetBooleanField(options, gOptions_mCancelID)) {
331 return NULL;
332 }
333 }
334
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800335 if (!decoder->decode(stream, bitmap, prefConfig, mode)) {
336 return NULL;
337 }
338
339 // update options (if any)
340 if (NULL != options) {
341 env->SetIntField(options, gOptions_widthFieldID, bitmap->width());
342 env->SetIntField(options, gOptions_heightFieldID, bitmap->height());
343 // TODO: set the mimeType field with the data from the codec.
344 // but how to reuse a set of strings, rather than allocating new one
345 // each time?
346 env->SetObjectField(options, gOptions_mimeFieldID,
347 getMimeTypeString(env, decoder->getFormat()));
348 }
349
350 // if we're in justBounds mode, return now (skip the java bitmap)
351 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
352 return NULL;
353 }
354
355 jbyteArray ninePatchChunk = NULL;
356 if (peeker.fPatchIsValid) {
357 size_t ninePatchArraySize = peeker.fPatch->serializedSize();
358 ninePatchChunk = env->NewByteArray(ninePatchArraySize);
359 if (NULL == ninePatchChunk) {
360 return NULL;
361 }
362 jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(ninePatchChunk,
363 NULL);
364 if (NULL == array) {
365 return NULL;
366 }
367 peeker.fPatch->serialize(array);
368 env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0);
369 }
370
371 // detach bitmap from its autotdeleter, since we want to own it now
372 adb.detach();
373
374 if (padding) {
375 if (peeker.fPatchIsValid) {
376 GraphicsJNI::set_jrect(env, padding,
377 peeker.fPatch->paddingLeft,
378 peeker.fPatch->paddingTop,
379 peeker.fPatch->paddingRight,
380 peeker.fPatch->paddingBottom);
381 } else {
382 GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
383 }
384 }
385
386 // promise we will never change our pixels (great for sharing and pictures)
387 SkPixelRef* ref = bitmap->pixelRef();
388 SkASSERT(ref);
389 ref->setImmutable();
390
391 return GraphicsJNI::createBitmap(env, bitmap, false, ninePatchChunk);
392}
393
394static jobject nativeDecodeStream(JNIEnv* env, jobject clazz,
395 jobject is, // InputStream
396 jbyteArray storage, // byte[]
397 jobject padding,
398 jobject options) { // BitmapFactory$Options
399 jobject bitmap = NULL;
400 SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage);
401
402 if (stream) {
403 bitmap = doDecode(env, stream, padding, options);
404 stream->unref();
405 }
406 return bitmap;
407}
408
409static ssize_t getFDSize(int fd) {
410 off_t curr = ::lseek(fd, 0, SEEK_CUR);
411 if (curr < 0) {
412 return 0;
413 }
414 size_t size = ::lseek(fd, 0, SEEK_END);
415 ::lseek(fd, curr, SEEK_SET);
416 return size;
417}
418
419/** Restore the file descriptor's offset in our destructor
420 */
421class AutoFDSeek {
422public:
423 AutoFDSeek(int fd) : fFD(fd) {
424 fCurr = ::lseek(fd, 0, SEEK_CUR);
425 }
426 ~AutoFDSeek() {
427 if (fCurr >= 0) {
428 ::lseek(fFD, fCurr, SEEK_SET);
429 }
430 }
431private:
432 int fFD;
433 off_t fCurr;
434};
435
436static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz,
437 jobject fileDescriptor,
438 jobject padding,
439 jobject bitmapFactoryOptions) {
440 NPE_CHECK_RETURN_ZERO(env, fileDescriptor);
441
442 jint descriptor = env->GetIntField(fileDescriptor,
443 gFileDescriptor_descriptor);
444
445#ifdef MIN_SIZE_TO_USE_MMAP
446 // First try to use mmap
447 size_t size = getFDSize(descriptor);
448 if (size >= MIN_SIZE_TO_USE_MMAP) {
449 void* addr = mmap(NULL, size, PROT_READ, MAP_PRIVATE, descriptor, 0);
450// SkDebugf("-------- mmap returned %p %d\n", addr, size);
451 if (MAP_FAILED != addr) {
452 SkMemoryStream strm(addr, size);
453 jobject obj = doDecode(env, &strm, padding, bitmapFactoryOptions);
454 munmap(addr, size);
455 return obj;
456 }
457 }
458#endif
459
460 // we pass false for closeWhenDone, since the caller owns the descriptor
461 SkFDStream file(descriptor, false);
462 if (!file.isValid()) {
463 return NULL;
464 }
465
466 /* Restore our offset when we leave, so the caller doesn't have to.
467 This is a real feature, so we can be called more than once with the
468 same descriptor.
469 */
470 AutoFDSeek as(descriptor);
471
472 return doDecode(env, &file, padding, bitmapFactoryOptions);
473}
474
475static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz,
476 jint native_asset, // Asset
477 jobject padding, // Rect
478 jobject options) { // BitmapFactory$Options
479 AssetStreamAdaptor mystream((Asset*)native_asset);
480
481 return doDecode(env, &mystream, padding, options);
482}
483
484static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
485 int offset, int length, jobject options) {
486 AutoJavaByteArray ar(env, byteArray);
487 SkMemoryStream stream(ar.ptr() + offset, length);
488
489 return doDecode(env, &stream, NULL, options);
490}
491
492static void nativeRequestCancel(JNIEnv*, jobject joptions) {
493 (void)AutoDecoderCancel::RequestCancel(joptions);
494}
495
496static jbyteArray nativeScaleNinePatch(JNIEnv* env, jobject, jbyteArray chunkObject, jfloat scale,
497 jobject padding) {
498
499 jbyte* array = env->GetByteArrayElements(chunkObject, 0);
500 if (array != NULL) {
501 size_t chunkSize = env->GetArrayLength(chunkObject);
502 void* storage = alloca(chunkSize);
503 android::Res_png_9patch* chunk = static_cast<android::Res_png_9patch*>(storage);
504 memcpy(chunk, array, chunkSize);
505 android::Res_png_9patch::deserialize(chunk);
506
507 chunk->paddingLeft = int(chunk->paddingLeft * scale + 0.5f);
508 chunk->paddingTop = int(chunk->paddingTop * scale + 0.5f);
509 chunk->paddingRight = int(chunk->paddingRight * scale + 0.5f);
510 chunk->paddingBottom = int(chunk->paddingBottom * scale + 0.5f);
511
512 for (int i = 0; i < chunk->numXDivs; i++) {
513 chunk->xDivs[i] = int(chunk->xDivs[i] * scale + 0.5f);
514 if (i > 0 && chunk->xDivs[i] == chunk->xDivs[i - 1]) {
515 chunk->xDivs[i]++;
516 }
517 }
518
519 for (int i = 0; i < chunk->numYDivs; i++) {
520 chunk->yDivs[i] = int(chunk->yDivs[i] * scale + 0.5f);
521 if (i > 0 && chunk->yDivs[i] == chunk->yDivs[i - 1]) {
522 chunk->yDivs[i]++;
523 }
524 }
525
526 memcpy(array, chunk, chunkSize);
527
528 if (padding) {
529 GraphicsJNI::set_jrect(env, padding, chunk->paddingLeft, chunk->paddingTop,
530 chunk->paddingRight, chunk->paddingBottom);
531 }
532
533 env->ReleaseByteArrayElements(chunkObject, array, 0);
534 }
535 return chunkObject;
536}
537
538///////////////////////////////////////////////////////////////////////////////
539
540static JNINativeMethod gMethods[] = {
541 { "nativeDecodeStream",
542 "(Ljava/io/InputStream;[BLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
543 (void*)nativeDecodeStream
544 },
545
546 { "nativeDecodeFileDescriptor",
547 "(Ljava/io/FileDescriptor;Landroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
548 (void*)nativeDecodeFileDescriptor
549 },
550
551 { "nativeDecodeAsset",
552 "(ILandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
553 (void*)nativeDecodeAsset
554 },
555
556 { "nativeDecodeByteArray",
557 "([BIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
558 (void*)nativeDecodeByteArray
559 },
560
561 { "nativeScaleNinePatch",
562 "([BFLandroid/graphics/Rect;)[B",
563 (void*)nativeScaleNinePatch
564 }
565
566};
567
568static JNINativeMethod gOptionsMethods[] = {
569 { "requestCancel", "()V", (void*)nativeRequestCancel }
570};
571
572static jclass make_globalref(JNIEnv* env, const char classname[]) {
573 jclass c = env->FindClass(classname);
574 SkASSERT(c);
575 return (jclass)env->NewGlobalRef(c);
576}
577
578static jfieldID getFieldIDCheck(JNIEnv* env, jclass clazz,
579 const char fieldname[], const char type[]) {
580 jfieldID id = env->GetFieldID(clazz, fieldname, type);
581 SkASSERT(id);
582 return id;
583}
584
585#define kClassPathName "android/graphics/BitmapFactory"
586
587#define RETURN_ERR_IF_NULL(value) \
588 do { if (!(value)) { assert(0); return -1; } } while (false)
589
590int register_android_graphics_BitmapFactory(JNIEnv* env);
591int register_android_graphics_BitmapFactory(JNIEnv* env) {
592 gOptions_class = make_globalref(env, "android/graphics/BitmapFactory$Options");
593 gOptions_justBoundsFieldID = getFieldIDCheck(env, gOptions_class, "inJustDecodeBounds", "Z");
594 gOptions_sampleSizeFieldID = getFieldIDCheck(env, gOptions_class, "inSampleSize", "I");
595 gOptions_configFieldID = getFieldIDCheck(env, gOptions_class, "inPreferredConfig",
596 "Landroid/graphics/Bitmap$Config;");
597 gOptions_ditherFieldID = getFieldIDCheck(env, gOptions_class, "inDither", "Z");
598 gOptions_widthFieldID = getFieldIDCheck(env, gOptions_class, "outWidth", "I");
599 gOptions_heightFieldID = getFieldIDCheck(env, gOptions_class, "outHeight", "I");
600 gOptions_mimeFieldID = getFieldIDCheck(env, gOptions_class, "outMimeType", "Ljava/lang/String;");
Ray Chen0a6a0e92009-04-02 01:31:49 -0700601 gOptions_mCancelID = getFieldIDCheck(env, gOptions_class, "mCancel", "Z");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800602
603 gFileDescriptor_class = make_globalref(env, "java/io/FileDescriptor");
604 gFileDescriptor_descriptor = getFieldIDCheck(env, gFileDescriptor_class, "descriptor", "I");
605
606 int ret = AndroidRuntime::registerNativeMethods(env,
607 "android/graphics/BitmapFactory$Options",
608 gOptionsMethods,
609 SK_ARRAY_COUNT(gOptionsMethods));
610 if (ret) {
611 return ret;
612 }
613 return android::AndroidRuntime::registerNativeMethods(env, kClassPathName,
614 gMethods, SK_ARRAY_COUNT(gMethods));
615}