blob: 1fd15d687baf2b677a3666eaa2dabfe70742bf3c [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001#define LOG_TAG "BitmapFactory"
2
3#include "SkImageDecoder.h"
Mike Reedc70e06b2009-04-24 11:09:12 -04004#include "SkImageRef_ashmem.h"
5#include "SkImageRef_GlobalPool.h"
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006#include "SkPixelRef.h"
7#include "SkStream.h"
8#include "GraphicsJNI.h"
9#include "SkTemplates.h"
10#include "SkUtils.h"
11#include "CreateJavaOutputStreamAdaptor.h"
12
13#include <android_runtime/AndroidRuntime.h>
14#include <utils/Asset.h>
15#include <utils/ResourceTypes.h>
16#include <netinet/in.h>
17#include <sys/mman.h>
18
19static jclass gOptions_class;
20static jfieldID gOptions_justBoundsFieldID;
21static jfieldID gOptions_sampleSizeFieldID;
22static jfieldID gOptions_configFieldID;
23static jfieldID gOptions_ditherFieldID;
Mike Reedc70e06b2009-04-24 11:09:12 -040024static jfieldID gOptions_purgeableFieldID;
25static jfieldID gOptions_shareableFieldID;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026static jfieldID gOptions_widthFieldID;
27static jfieldID gOptions_heightFieldID;
28static jfieldID gOptions_mimeFieldID;
Ray Chen0a6a0e92009-04-02 01:31:49 -070029static jfieldID gOptions_mCancelID;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030
31static jclass gFileDescriptor_class;
32static jfieldID gFileDescriptor_descriptor;
33
34#if 0
35 #define TRACE_BITMAP(code) code
36#else
37 #define TRACE_BITMAP(code)
38#endif
39
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040///////////////////////////////////////////////////////////////////////////////
41
42class AutoDecoderCancel {
43public:
44 AutoDecoderCancel(jobject options, SkImageDecoder* decoder);
45 ~AutoDecoderCancel();
46
47 static bool RequestCancel(jobject options);
48
49private:
50 AutoDecoderCancel* fNext;
51 AutoDecoderCancel* fPrev;
52 jobject fJOptions; // java options object
53 SkImageDecoder* fDecoder;
54
55#ifdef SK_DEBUG
56 static void Validate();
57#else
58 static void Validate() {}
59#endif
60};
61
62static SkMutex gAutoDecoderCancelMutex;
63static AutoDecoderCancel* gAutoDecoderCancel;
64#ifdef SK_DEBUG
65 static int gAutoDecoderCancelCount;
66#endif
67
68AutoDecoderCancel::AutoDecoderCancel(jobject joptions,
69 SkImageDecoder* decoder) {
70 fJOptions = joptions;
71 fDecoder = decoder;
72
73 if (NULL != joptions) {
74 SkAutoMutexAcquire ac(gAutoDecoderCancelMutex);
75
76 // Add us as the head of the list
77 fPrev = NULL;
78 fNext = gAutoDecoderCancel;
79 if (gAutoDecoderCancel) {
80 gAutoDecoderCancel->fPrev = this;
81 }
82 gAutoDecoderCancel = this;
83
84 SkDEBUGCODE(gAutoDecoderCancelCount += 1;)
85 Validate();
86 }
87}
88
89AutoDecoderCancel::~AutoDecoderCancel() {
90 if (NULL != fJOptions) {
91 SkAutoMutexAcquire ac(gAutoDecoderCancelMutex);
92
93 // take us out of the dllist
94 AutoDecoderCancel* prev = fPrev;
95 AutoDecoderCancel* next = fNext;
96
97 if (prev) {
98 SkASSERT(prev->fNext == this);
99 prev->fNext = next;
100 } else {
101 SkASSERT(gAutoDecoderCancel == this);
102 gAutoDecoderCancel = next;
103 }
104 if (next) {
105 SkASSERT(next->fPrev == this);
106 next->fPrev = prev;
107 }
108
109 SkDEBUGCODE(gAutoDecoderCancelCount -= 1;)
110 Validate();
111 }
112}
113
114bool AutoDecoderCancel::RequestCancel(jobject joptions) {
115 SkAutoMutexAcquire ac(gAutoDecoderCancelMutex);
116
117 Validate();
118
119 AutoDecoderCancel* pair = gAutoDecoderCancel;
120 while (pair != NULL) {
121 if (pair->fJOptions == joptions) {
122 pair->fDecoder->cancelDecode();
123 return true;
124 }
125 pair = pair->fNext;
126 }
127 return false;
128}
129
130#ifdef SK_DEBUG
131// can only call this inside a lock on gAutoDecoderCancelMutex
132void AutoDecoderCancel::Validate() {
133 const int gCount = gAutoDecoderCancelCount;
134
135 if (gCount == 0) {
136 SkASSERT(gAutoDecoderCancel == NULL);
137 } else {
138 SkASSERT(gCount > 0);
139
140 AutoDecoderCancel* curr = gAutoDecoderCancel;
141 SkASSERT(curr);
142 SkASSERT(curr->fPrev == NULL);
143
144 int count = 0;
145 while (curr) {
146 count += 1;
147 SkASSERT(count <= gCount);
148 if (curr->fPrev) {
149 SkASSERT(curr->fPrev->fNext == curr);
150 }
151 if (curr->fNext) {
152 SkASSERT(curr->fNext->fPrev == curr);
153 }
154 curr = curr->fNext;
155 }
156 SkASSERT(count == gCount);
157 }
158}
159#endif
160
161///////////////////////////////////////////////////////////////////////////////
162
163using namespace android;
164
165class NinePatchPeeker : public SkImageDecoder::Peeker {
166public:
167 NinePatchPeeker() {
168 fPatchIsValid = false;
169 }
170
171 ~NinePatchPeeker() {
172 if (fPatchIsValid) {
173 free(fPatch);
174 }
175 }
176
177 bool fPatchIsValid;
178 Res_png_9patch* fPatch;
179
180 virtual bool peek(const char tag[], const void* data, size_t length) {
181 if (strcmp("npTc", tag) == 0 && length >= sizeof(Res_png_9patch)) {
182 Res_png_9patch* patch = (Res_png_9patch*) data;
183 size_t patchSize = patch->serializedSize();
184 assert(length == patchSize);
185 // You have to copy the data because it is owned by the png reader
186 Res_png_9patch* patchNew = (Res_png_9patch*) malloc(patchSize);
187 memcpy(patchNew, patch, patchSize);
188 // this relies on deserialization being done in place
189 Res_png_9patch::deserialize(patchNew);
190 patchNew->fileToDevice();
191 if (fPatchIsValid) {
192 free(fPatch);
193 }
194 fPatch = patchNew;
195 //printf("9patch: (%d,%d)-(%d,%d)\n",
196 // fPatch.sizeLeft, fPatch.sizeTop,
197 // fPatch.sizeRight, fPatch.sizeBottom);
198 fPatchIsValid = true;
199 } else {
200 fPatch = NULL;
201 }
202 return true; // keep on decoding
203 }
204};
205
206class AssetStreamAdaptor : public SkStream {
207public:
208 AssetStreamAdaptor(Asset* a) : fAsset(a) {}
209
Ray Chen0a6a0e92009-04-02 01:31:49 -0700210 virtual bool rewind() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800211 off_t pos = fAsset->seek(0, SEEK_SET);
Mike Reedc70e06b2009-04-24 11:09:12 -0400212 if (pos == (off_t)-1) {
213 SkDebugf("----- fAsset->seek(rewind) failed\n");
214 return false;
215 }
216 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800217 }
218
Ray Chen0a6a0e92009-04-02 01:31:49 -0700219 virtual size_t read(void* buffer, size_t size) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800220 ssize_t amount;
221
222 if (NULL == buffer) {
223 if (0 == size) { // caller is asking us for our total length
224 return fAsset->getLength();
225 }
226 // asset->seek returns new total offset
227 // we want to return amount that was skipped
228
229 off_t oldOffset = fAsset->seek(0, SEEK_CUR);
230 if (-1 == oldOffset) {
Mike Reedc70e06b2009-04-24 11:09:12 -0400231 SkDebugf("---- fAsset->seek(oldOffset) failed\n");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800232 return 0;
233 }
234 off_t newOffset = fAsset->seek(size, SEEK_CUR);
235 if (-1 == newOffset) {
Mike Reedc70e06b2009-04-24 11:09:12 -0400236 SkDebugf("---- fAsset->seek(%d) failed\n", size);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800237 return 0;
238 }
239 amount = newOffset - oldOffset;
240 } else {
241 amount = fAsset->read(buffer, size);
Mike Reedc70e06b2009-04-24 11:09:12 -0400242 if (amount <= 0) {
243 SkDebugf("---- fAsset->read(%d) returned %d\n", size, amount);
244 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800245 }
246
247 if (amount < 0) {
248 amount = 0;
249 }
250 return amount;
251 }
252
253private:
254 Asset* fAsset;
255};
256
257///////////////////////////////////////////////////////////////////////////////
258
259static inline int32_t validOrNeg1(bool isValid, int32_t value) {
260// return isValid ? value : -1;
261 SkASSERT((int)isValid == 0 || (int)isValid == 1);
262 return ((int32_t)isValid - 1) | value;
263}
264
265static jstring getMimeTypeString(JNIEnv* env, SkImageDecoder::Format format) {
266 static const struct {
267 SkImageDecoder::Format fFormat;
268 const char* fMimeType;
269 } gMimeTypes[] = {
270 { SkImageDecoder::kBMP_Format, "image/bmp" },
271 { SkImageDecoder::kGIF_Format, "image/gif" },
272 { SkImageDecoder::kICO_Format, "image/x-ico" },
273 { SkImageDecoder::kJPEG_Format, "image/jpeg" },
274 { SkImageDecoder::kPNG_Format, "image/png" },
275 { SkImageDecoder::kWBMP_Format, "image/vnd.wap.wbmp" }
276 };
277
278 const char* cstr = NULL;
279 for (size_t i = 0; i < SK_ARRAY_COUNT(gMimeTypes); i++) {
280 if (gMimeTypes[i].fFormat == format) {
281 cstr = gMimeTypes[i].fMimeType;
282 break;
283 }
284 }
285
286 jstring jstr = 0;
287 if (NULL != cstr) {
288 jstr = env->NewStringUTF(cstr);
289 }
290 return jstr;
291}
292
Mike Reedc70e06b2009-04-24 11:09:12 -0400293static bool optionsPurgeable(JNIEnv* env, jobject options) {
294 return options != NULL &&
295 env->GetBooleanField(options, gOptions_purgeableFieldID);
296}
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800297
Mike Reedc70e06b2009-04-24 11:09:12 -0400298static bool optionsShareable(JNIEnv* env, jobject options) {
299 return options != NULL &&
300 env->GetBooleanField(options, gOptions_shareableFieldID);
301}
302
303static jobject nullObjectReturn(const char msg[]) {
304 if (msg) {
305 SkDebugf("--- %s\n", msg);
306 }
307 return NULL;
308}
309
310static SkPixelRef* installPixelRef(SkBitmap* bitmap, SkStream* stream,
311 int sampleSize) {
312 SkPixelRef* pr;
313 // only use ashmem for large images, since mmaps come at a price
314 if (bitmap->getSize() >= 32 * 65536) {
315 pr = new SkImageRef_ashmem(stream, bitmap->config(), sampleSize);
316 } else {
317 pr = new SkImageRef_GlobalPool(stream, bitmap->config(), sampleSize);
318 }
319 bitmap->setPixelRef(pr)->unref();
320 return pr;
321}
322
323// since we "may" create a purgeable imageref, we require the stream be ref'able
324// i.e. dynamically allocated, since its lifetime may exceed the current stack
325// frame.
326static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
327 jobject options, bool allowPurgeable) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800328 int sampleSize = 1;
329 SkImageDecoder::Mode mode = SkImageDecoder::kDecodePixels_Mode;
330 SkBitmap::Config prefConfig = SkBitmap::kNo_Config;
331 bool doDither = true;
Mike Reedc70e06b2009-04-24 11:09:12 -0400332 bool isPurgeable = allowPurgeable && optionsPurgeable(env, options);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800333
334 if (NULL != options) {
335 sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
336 if (env->GetBooleanField(options, gOptions_justBoundsFieldID)) {
337 mode = SkImageDecoder::kDecodeBounds_Mode;
338 }
339 // initialize these, in case we fail later on
340 env->SetIntField(options, gOptions_widthFieldID, -1);
341 env->SetIntField(options, gOptions_heightFieldID, -1);
342 env->SetObjectField(options, gOptions_mimeFieldID, 0);
343
344 jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
345 prefConfig = GraphicsJNI::getNativeBitmapConfig(env, jconfig);
346 doDither = env->GetBooleanField(options, gOptions_ditherFieldID);
347 }
348
349 SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
350 if (NULL == decoder) {
Mike Reedc70e06b2009-04-24 11:09:12 -0400351 return nullObjectReturn("SkImageDecoder::Factory returned null");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800352 }
353
354 decoder->setSampleSize(sampleSize);
355 decoder->setDitherImage(doDither);
356
357 NinePatchPeeker peeker;
Mike Reedc70e06b2009-04-24 11:09:12 -0400358 JavaPixelAllocator javaAllocator(env);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800359 SkBitmap* bitmap = new SkBitmap;
360 Res_png_9patch dummy9Patch;
361
362 SkAutoTDelete<SkImageDecoder> add(decoder);
363 SkAutoTDelete<SkBitmap> adb(bitmap);
364
365 decoder->setPeeker(&peeker);
Mike Reedc70e06b2009-04-24 11:09:12 -0400366 if (!isPurgeable) {
367 decoder->setAllocator(&javaAllocator);
368 }
369
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800370 AutoDecoderCancel adc(options, decoder);
371
Ray Chen0a6a0e92009-04-02 01:31:49 -0700372 // To fix the race condition in case "requestCancelDecode"
373 // happens earlier than AutoDecoderCancel object is added
374 // to the gAutoDecoderCancelMutex linked list.
Mike Reedc70e06b2009-04-24 11:09:12 -0400375 if (NULL != options && env->GetBooleanField(options, gOptions_mCancelID)) {
376 return nullObjectReturn("gOptions_mCancelID");;
Ray Chen0a6a0e92009-04-02 01:31:49 -0700377 }
378
Mike Reedc70e06b2009-04-24 11:09:12 -0400379 SkImageDecoder::Mode decodeMode = mode;
380 if (isPurgeable) {
381 decodeMode = SkImageDecoder::kDecodeBounds_Mode;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800382 }
Mike Reedc70e06b2009-04-24 11:09:12 -0400383 if (!decoder->decode(stream, bitmap, prefConfig, decodeMode)) {
384 return nullObjectReturn("decoder->decode returned false");
385 }
386
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800387 // update options (if any)
388 if (NULL != options) {
389 env->SetIntField(options, gOptions_widthFieldID, bitmap->width());
390 env->SetIntField(options, gOptions_heightFieldID, bitmap->height());
391 // TODO: set the mimeType field with the data from the codec.
392 // but how to reuse a set of strings, rather than allocating new one
393 // each time?
394 env->SetObjectField(options, gOptions_mimeFieldID,
395 getMimeTypeString(env, decoder->getFormat()));
396 }
Mike Reedc70e06b2009-04-24 11:09:12 -0400397
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800398 // if we're in justBounds mode, return now (skip the java bitmap)
399 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
400 return NULL;
401 }
402
403 jbyteArray ninePatchChunk = NULL;
404 if (peeker.fPatchIsValid) {
405 size_t ninePatchArraySize = peeker.fPatch->serializedSize();
406 ninePatchChunk = env->NewByteArray(ninePatchArraySize);
407 if (NULL == ninePatchChunk) {
Mike Reedc70e06b2009-04-24 11:09:12 -0400408 return nullObjectReturn("ninePatchChunk == null");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800409 }
410 jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(ninePatchChunk,
411 NULL);
412 if (NULL == array) {
Mike Reedc70e06b2009-04-24 11:09:12 -0400413 return nullObjectReturn("primitive array == null");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800414 }
415 peeker.fPatch->serialize(array);
416 env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0);
417 }
418
419 // detach bitmap from its autotdeleter, since we want to own it now
420 adb.detach();
421
422 if (padding) {
423 if (peeker.fPatchIsValid) {
424 GraphicsJNI::set_jrect(env, padding,
425 peeker.fPatch->paddingLeft,
426 peeker.fPatch->paddingTop,
427 peeker.fPatch->paddingRight,
428 peeker.fPatch->paddingBottom);
429 } else {
430 GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
431 }
432 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800433
Mike Reedc70e06b2009-04-24 11:09:12 -0400434 SkPixelRef* pr;
435 if (isPurgeable) {
436 pr = installPixelRef(bitmap, stream, sampleSize);
437 } else {
438 // if we get here, we're in kDecodePixels_Mode and will therefore
439 // already have a pixelref installed.
440 pr = bitmap->pixelRef();
441 }
442 // promise we will never change our pixels (great for sharing and pictures)
443 pr->setImmutable();
444 // now create the java bitmap
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800445 return GraphicsJNI::createBitmap(env, bitmap, false, ninePatchChunk);
446}
447
448static jobject nativeDecodeStream(JNIEnv* env, jobject clazz,
449 jobject is, // InputStream
450 jbyteArray storage, // byte[]
451 jobject padding,
452 jobject options) { // BitmapFactory$Options
453 jobject bitmap = NULL;
454 SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage);
455
456 if (stream) {
Mike Reedc70e06b2009-04-24 11:09:12 -0400457 // for now we don't allow purgeable with java inputstreams
458 bitmap = doDecode(env, stream, padding, options, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800459 stream->unref();
460 }
461 return bitmap;
462}
463
464static ssize_t getFDSize(int fd) {
465 off_t curr = ::lseek(fd, 0, SEEK_CUR);
466 if (curr < 0) {
467 return 0;
468 }
469 size_t size = ::lseek(fd, 0, SEEK_END);
470 ::lseek(fd, curr, SEEK_SET);
471 return size;
472}
473
474/** Restore the file descriptor's offset in our destructor
475 */
476class AutoFDSeek {
477public:
478 AutoFDSeek(int fd) : fFD(fd) {
479 fCurr = ::lseek(fd, 0, SEEK_CUR);
480 }
481 ~AutoFDSeek() {
482 if (fCurr >= 0) {
483 ::lseek(fFD, fCurr, SEEK_SET);
484 }
485 }
486private:
487 int fFD;
488 off_t fCurr;
489};
490
491static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz,
492 jobject fileDescriptor,
493 jobject padding,
494 jobject bitmapFactoryOptions) {
495 NPE_CHECK_RETURN_ZERO(env, fileDescriptor);
496
497 jint descriptor = env->GetIntField(fileDescriptor,
498 gFileDescriptor_descriptor);
Mike Reedc70e06b2009-04-24 11:09:12 -0400499
500 bool isPurgeable = optionsPurgeable(env, bitmapFactoryOptions);
501 bool isShareable = optionsShareable(env, bitmapFactoryOptions);
502 bool weOwnTheFD = false;
503 if (isPurgeable && isShareable) {
504 int newFD = ::dup(descriptor);
505 if (-1 != newFD) {
506 weOwnTheFD = true;
507 descriptor = newFD;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800508 }
509 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800510
Mike Reedc70e06b2009-04-24 11:09:12 -0400511 SkFDStream* stream = new SkFDStream(descriptor, weOwnTheFD);
512 SkAutoUnref aur(stream);
513 if (!stream->isValid()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800514 return NULL;
515 }
Mike Reedc70e06b2009-04-24 11:09:12 -0400516
517 /* Restore our offset when we leave, so we can be called more than once
518 with the same descriptor. This is only required if we didn't dup the
519 file descriptor, but it is OK to do it all the time.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800520 */
521 AutoFDSeek as(descriptor);
522
Mike Reedc70e06b2009-04-24 11:09:12 -0400523 return doDecode(env, stream, padding, bitmapFactoryOptions, true);
524}
525
526/* make a deep copy of the asset, and return it as a stream, or NULL if there
527 was an error.
528 */
529static SkStream* copyAssetToStream(Asset* asset) {
530 // if we could "ref/reopen" the asset, we may not need to copy it here
531 off_t size = asset->seek(0, SEEK_SET);
532 if ((off_t)-1 == size) {
533 SkDebugf("---- copyAsset: asset rewind failed\n");
534 return NULL;
535 }
536
537 size = asset->getLength();
538 if (size <= 0) {
539 SkDebugf("---- copyAsset: asset->getLength() returned %d\n", size);
540 return NULL;
541 }
542
543 SkStream* stream = new SkMemoryStream(size);
544 void* data = const_cast<void*>(stream->getMemoryBase());
545 off_t len = asset->read(data, size);
546 if (len != size) {
547 SkDebugf("---- copyAsset: asset->read(%d) returned %d\n", size, len);
548 delete stream;
549 stream = NULL;
550 }
551 return stream;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800552}
553
554static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz,
555 jint native_asset, // Asset
556 jobject padding, // Rect
557 jobject options) { // BitmapFactory$Options
Mike Reedc70e06b2009-04-24 11:09:12 -0400558 SkStream* stream;
559 Asset* asset = reinterpret_cast<Asset*>(native_asset);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800560
Mike Reedc70e06b2009-04-24 11:09:12 -0400561 if (optionsPurgeable(env, options)) {
562 // if we could "ref/reopen" the asset, we may not need to copy it here
563 // and we could assume optionsShareable, since assets are always RO
564 stream = copyAssetToStream(asset);
565 if (NULL == stream) {
566 return NULL;
567 }
568 } else {
569 // since we know we'll be done with the asset when we return, we can
570 // just use a simple wrapper
571 stream = new AssetStreamAdaptor(asset);
572 }
573 SkAutoUnref aur(stream);
574 return doDecode(env, stream, padding, options, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800575}
576
577static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
578 int offset, int length, jobject options) {
Mike Reedc70e06b2009-04-24 11:09:12 -0400579 /* If optionsShareable() we could decide to just wrap the java array and
580 share it, but that means adding a globalref to the java array object
581 and managing its lifetime. For now we just always copy the array's data
582 if optionsPurgeable().
583 */
584 AutoJavaByteArray ar(env, byteArray);
585 SkStream* stream = new SkMemoryStream(ar.ptr() + offset, length,
586 optionsPurgeable(env, options));
587 SkAutoUnref aur(stream);
588 return doDecode(env, stream, NULL, options, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800589}
590
591static void nativeRequestCancel(JNIEnv*, jobject joptions) {
592 (void)AutoDecoderCancel::RequestCancel(joptions);
593}
594
595static jbyteArray nativeScaleNinePatch(JNIEnv* env, jobject, jbyteArray chunkObject, jfloat scale,
596 jobject padding) {
597
598 jbyte* array = env->GetByteArrayElements(chunkObject, 0);
599 if (array != NULL) {
600 size_t chunkSize = env->GetArrayLength(chunkObject);
601 void* storage = alloca(chunkSize);
602 android::Res_png_9patch* chunk = static_cast<android::Res_png_9patch*>(storage);
603 memcpy(chunk, array, chunkSize);
604 android::Res_png_9patch::deserialize(chunk);
605
606 chunk->paddingLeft = int(chunk->paddingLeft * scale + 0.5f);
607 chunk->paddingTop = int(chunk->paddingTop * scale + 0.5f);
608 chunk->paddingRight = int(chunk->paddingRight * scale + 0.5f);
609 chunk->paddingBottom = int(chunk->paddingBottom * scale + 0.5f);
610
611 for (int i = 0; i < chunk->numXDivs; i++) {
612 chunk->xDivs[i] = int(chunk->xDivs[i] * scale + 0.5f);
613 if (i > 0 && chunk->xDivs[i] == chunk->xDivs[i - 1]) {
614 chunk->xDivs[i]++;
615 }
616 }
617
618 for (int i = 0; i < chunk->numYDivs; i++) {
619 chunk->yDivs[i] = int(chunk->yDivs[i] * scale + 0.5f);
620 if (i > 0 && chunk->yDivs[i] == chunk->yDivs[i - 1]) {
621 chunk->yDivs[i]++;
622 }
623 }
624
625 memcpy(array, chunk, chunkSize);
626
627 if (padding) {
628 GraphicsJNI::set_jrect(env, padding, chunk->paddingLeft, chunk->paddingTop,
629 chunk->paddingRight, chunk->paddingBottom);
630 }
631
632 env->ReleaseByteArrayElements(chunkObject, array, 0);
633 }
634 return chunkObject;
635}
636
637///////////////////////////////////////////////////////////////////////////////
638
639static JNINativeMethod gMethods[] = {
640 { "nativeDecodeStream",
641 "(Ljava/io/InputStream;[BLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
642 (void*)nativeDecodeStream
643 },
644
645 { "nativeDecodeFileDescriptor",
646 "(Ljava/io/FileDescriptor;Landroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
647 (void*)nativeDecodeFileDescriptor
648 },
649
650 { "nativeDecodeAsset",
651 "(ILandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
652 (void*)nativeDecodeAsset
653 },
654
655 { "nativeDecodeByteArray",
656 "([BIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
657 (void*)nativeDecodeByteArray
658 },
659
660 { "nativeScaleNinePatch",
661 "([BFLandroid/graphics/Rect;)[B",
662 (void*)nativeScaleNinePatch
663 }
664
665};
666
667static JNINativeMethod gOptionsMethods[] = {
668 { "requestCancel", "()V", (void*)nativeRequestCancel }
669};
670
671static jclass make_globalref(JNIEnv* env, const char classname[]) {
672 jclass c = env->FindClass(classname);
673 SkASSERT(c);
674 return (jclass)env->NewGlobalRef(c);
675}
676
677static jfieldID getFieldIDCheck(JNIEnv* env, jclass clazz,
678 const char fieldname[], const char type[]) {
679 jfieldID id = env->GetFieldID(clazz, fieldname, type);
680 SkASSERT(id);
681 return id;
682}
683
684#define kClassPathName "android/graphics/BitmapFactory"
685
686#define RETURN_ERR_IF_NULL(value) \
687 do { if (!(value)) { assert(0); return -1; } } while (false)
688
689int register_android_graphics_BitmapFactory(JNIEnv* env);
690int register_android_graphics_BitmapFactory(JNIEnv* env) {
691 gOptions_class = make_globalref(env, "android/graphics/BitmapFactory$Options");
692 gOptions_justBoundsFieldID = getFieldIDCheck(env, gOptions_class, "inJustDecodeBounds", "Z");
693 gOptions_sampleSizeFieldID = getFieldIDCheck(env, gOptions_class, "inSampleSize", "I");
694 gOptions_configFieldID = getFieldIDCheck(env, gOptions_class, "inPreferredConfig",
695 "Landroid/graphics/Bitmap$Config;");
696 gOptions_ditherFieldID = getFieldIDCheck(env, gOptions_class, "inDither", "Z");
Mike Reedc70e06b2009-04-24 11:09:12 -0400697 gOptions_purgeableFieldID = getFieldIDCheck(env, gOptions_class, "inPurgeable", "Z");
698 gOptions_shareableFieldID = getFieldIDCheck(env, gOptions_class, "inInputShareable", "Z");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800699 gOptions_widthFieldID = getFieldIDCheck(env, gOptions_class, "outWidth", "I");
700 gOptions_heightFieldID = getFieldIDCheck(env, gOptions_class, "outHeight", "I");
701 gOptions_mimeFieldID = getFieldIDCheck(env, gOptions_class, "outMimeType", "Ljava/lang/String;");
Ray Chen0a6a0e92009-04-02 01:31:49 -0700702 gOptions_mCancelID = getFieldIDCheck(env, gOptions_class, "mCancel", "Z");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800703
704 gFileDescriptor_class = make_globalref(env, "java/io/FileDescriptor");
705 gFileDescriptor_descriptor = getFieldIDCheck(env, gFileDescriptor_class, "descriptor", "I");
706
707 int ret = AndroidRuntime::registerNativeMethods(env,
708 "android/graphics/BitmapFactory$Options",
709 gOptionsMethods,
710 SK_ARRAY_COUNT(gOptionsMethods));
711 if (ret) {
712 return ret;
713 }
714 return android::AndroidRuntime::registerNativeMethods(env, kClassPathName,
715 gMethods, SK_ARRAY_COUNT(gMethods));
716}