blob: b6f39974ce6e8fdb613041dc7241c70f79b802f7 [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;
Mike Reed1b22b972009-07-17 11:21:47 -040026static jfieldID gOptions_nativeAllocFieldID;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027static jfieldID gOptions_widthFieldID;
28static jfieldID gOptions_heightFieldID;
29static jfieldID gOptions_mimeFieldID;
Ray Chen0a6a0e92009-04-02 01:31:49 -070030static jfieldID gOptions_mCancelID;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031
32static jclass gFileDescriptor_class;
33static jfieldID gFileDescriptor_descriptor;
34
35#if 0
36 #define TRACE_BITMAP(code) code
37#else
38 #define TRACE_BITMAP(code)
39#endif
40
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041///////////////////////////////////////////////////////////////////////////////
42
43class AutoDecoderCancel {
44public:
45 AutoDecoderCancel(jobject options, SkImageDecoder* decoder);
46 ~AutoDecoderCancel();
47
48 static bool RequestCancel(jobject options);
49
50private:
51 AutoDecoderCancel* fNext;
52 AutoDecoderCancel* fPrev;
53 jobject fJOptions; // java options object
54 SkImageDecoder* fDecoder;
55
56#ifdef SK_DEBUG
57 static void Validate();
58#else
59 static void Validate() {}
60#endif
61};
62
63static SkMutex gAutoDecoderCancelMutex;
64static AutoDecoderCancel* gAutoDecoderCancel;
65#ifdef SK_DEBUG
66 static int gAutoDecoderCancelCount;
67#endif
68
69AutoDecoderCancel::AutoDecoderCancel(jobject joptions,
70 SkImageDecoder* decoder) {
71 fJOptions = joptions;
72 fDecoder = decoder;
73
74 if (NULL != joptions) {
75 SkAutoMutexAcquire ac(gAutoDecoderCancelMutex);
76
77 // Add us as the head of the list
78 fPrev = NULL;
79 fNext = gAutoDecoderCancel;
80 if (gAutoDecoderCancel) {
81 gAutoDecoderCancel->fPrev = this;
82 }
83 gAutoDecoderCancel = this;
84
85 SkDEBUGCODE(gAutoDecoderCancelCount += 1;)
86 Validate();
87 }
88}
89
90AutoDecoderCancel::~AutoDecoderCancel() {
91 if (NULL != fJOptions) {
92 SkAutoMutexAcquire ac(gAutoDecoderCancelMutex);
93
94 // take us out of the dllist
95 AutoDecoderCancel* prev = fPrev;
96 AutoDecoderCancel* next = fNext;
97
98 if (prev) {
99 SkASSERT(prev->fNext == this);
100 prev->fNext = next;
101 } else {
102 SkASSERT(gAutoDecoderCancel == this);
103 gAutoDecoderCancel = next;
104 }
105 if (next) {
106 SkASSERT(next->fPrev == this);
107 next->fPrev = prev;
108 }
109
110 SkDEBUGCODE(gAutoDecoderCancelCount -= 1;)
111 Validate();
112 }
113}
114
115bool AutoDecoderCancel::RequestCancel(jobject joptions) {
116 SkAutoMutexAcquire ac(gAutoDecoderCancelMutex);
117
118 Validate();
119
120 AutoDecoderCancel* pair = gAutoDecoderCancel;
121 while (pair != NULL) {
122 if (pair->fJOptions == joptions) {
123 pair->fDecoder->cancelDecode();
124 return true;
125 }
126 pair = pair->fNext;
127 }
128 return false;
129}
130
131#ifdef SK_DEBUG
132// can only call this inside a lock on gAutoDecoderCancelMutex
133void AutoDecoderCancel::Validate() {
134 const int gCount = gAutoDecoderCancelCount;
135
136 if (gCount == 0) {
137 SkASSERT(gAutoDecoderCancel == NULL);
138 } else {
139 SkASSERT(gCount > 0);
140
141 AutoDecoderCancel* curr = gAutoDecoderCancel;
142 SkASSERT(curr);
143 SkASSERT(curr->fPrev == NULL);
144
145 int count = 0;
146 while (curr) {
147 count += 1;
148 SkASSERT(count <= gCount);
149 if (curr->fPrev) {
150 SkASSERT(curr->fPrev->fNext == curr);
151 }
152 if (curr->fNext) {
153 SkASSERT(curr->fNext->fPrev == curr);
154 }
155 curr = curr->fNext;
156 }
157 SkASSERT(count == gCount);
158 }
159}
160#endif
161
162///////////////////////////////////////////////////////////////////////////////
163
164using namespace android;
165
166class NinePatchPeeker : public SkImageDecoder::Peeker {
167public:
168 NinePatchPeeker() {
169 fPatchIsValid = false;
170 }
171
172 ~NinePatchPeeker() {
173 if (fPatchIsValid) {
174 free(fPatch);
175 }
176 }
177
178 bool fPatchIsValid;
179 Res_png_9patch* fPatch;
180
181 virtual bool peek(const char tag[], const void* data, size_t length) {
182 if (strcmp("npTc", tag) == 0 && length >= sizeof(Res_png_9patch)) {
183 Res_png_9patch* patch = (Res_png_9patch*) data;
184 size_t patchSize = patch->serializedSize();
185 assert(length == patchSize);
186 // You have to copy the data because it is owned by the png reader
187 Res_png_9patch* patchNew = (Res_png_9patch*) malloc(patchSize);
188 memcpy(patchNew, patch, patchSize);
189 // this relies on deserialization being done in place
190 Res_png_9patch::deserialize(patchNew);
191 patchNew->fileToDevice();
192 if (fPatchIsValid) {
193 free(fPatch);
194 }
195 fPatch = patchNew;
196 //printf("9patch: (%d,%d)-(%d,%d)\n",
197 // fPatch.sizeLeft, fPatch.sizeTop,
198 // fPatch.sizeRight, fPatch.sizeBottom);
199 fPatchIsValid = true;
200 } else {
201 fPatch = NULL;
202 }
203 return true; // keep on decoding
204 }
205};
206
207class AssetStreamAdaptor : public SkStream {
208public:
209 AssetStreamAdaptor(Asset* a) : fAsset(a) {}
210
Ray Chen0a6a0e92009-04-02 01:31:49 -0700211 virtual bool rewind() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800212 off_t pos = fAsset->seek(0, SEEK_SET);
Mike Reedc70e06b2009-04-24 11:09:12 -0400213 if (pos == (off_t)-1) {
214 SkDebugf("----- fAsset->seek(rewind) failed\n");
215 return false;
216 }
217 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800218 }
219
Ray Chen0a6a0e92009-04-02 01:31:49 -0700220 virtual size_t read(void* buffer, size_t size) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800221 ssize_t amount;
222
223 if (NULL == buffer) {
224 if (0 == size) { // caller is asking us for our total length
225 return fAsset->getLength();
226 }
227 // asset->seek returns new total offset
228 // we want to return amount that was skipped
229
230 off_t oldOffset = fAsset->seek(0, SEEK_CUR);
231 if (-1 == oldOffset) {
Mike Reedc70e06b2009-04-24 11:09:12 -0400232 SkDebugf("---- fAsset->seek(oldOffset) failed\n");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800233 return 0;
234 }
235 off_t newOffset = fAsset->seek(size, SEEK_CUR);
236 if (-1 == newOffset) {
Mike Reedc70e06b2009-04-24 11:09:12 -0400237 SkDebugf("---- fAsset->seek(%d) failed\n", size);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800238 return 0;
239 }
240 amount = newOffset - oldOffset;
241 } else {
242 amount = fAsset->read(buffer, size);
Mike Reedc70e06b2009-04-24 11:09:12 -0400243 if (amount <= 0) {
244 SkDebugf("---- fAsset->read(%d) returned %d\n", size, amount);
245 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800246 }
247
248 if (amount < 0) {
249 amount = 0;
250 }
251 return amount;
252 }
253
254private:
255 Asset* fAsset;
256};
257
258///////////////////////////////////////////////////////////////////////////////
259
260static inline int32_t validOrNeg1(bool isValid, int32_t value) {
261// return isValid ? value : -1;
262 SkASSERT((int)isValid == 0 || (int)isValid == 1);
263 return ((int32_t)isValid - 1) | value;
264}
265
266static jstring getMimeTypeString(JNIEnv* env, SkImageDecoder::Format format) {
267 static const struct {
268 SkImageDecoder::Format fFormat;
269 const char* fMimeType;
270 } gMimeTypes[] = {
271 { SkImageDecoder::kBMP_Format, "image/bmp" },
272 { SkImageDecoder::kGIF_Format, "image/gif" },
273 { SkImageDecoder::kICO_Format, "image/x-ico" },
274 { SkImageDecoder::kJPEG_Format, "image/jpeg" },
275 { SkImageDecoder::kPNG_Format, "image/png" },
276 { SkImageDecoder::kWBMP_Format, "image/vnd.wap.wbmp" }
277 };
278
279 const char* cstr = NULL;
280 for (size_t i = 0; i < SK_ARRAY_COUNT(gMimeTypes); i++) {
281 if (gMimeTypes[i].fFormat == format) {
282 cstr = gMimeTypes[i].fMimeType;
283 break;
284 }
285 }
286
287 jstring jstr = 0;
288 if (NULL != cstr) {
289 jstr = env->NewStringUTF(cstr);
290 }
291 return jstr;
292}
293
Mike Reedc70e06b2009-04-24 11:09:12 -0400294static bool optionsPurgeable(JNIEnv* env, jobject options) {
295 return options != NULL &&
296 env->GetBooleanField(options, gOptions_purgeableFieldID);
297}
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800298
Mike Reedc70e06b2009-04-24 11:09:12 -0400299static bool optionsShareable(JNIEnv* env, jobject options) {
300 return options != NULL &&
301 env->GetBooleanField(options, gOptions_shareableFieldID);
302}
303
Mike Reed1b22b972009-07-17 11:21:47 -0400304static bool optionsReportSizeToVM(JNIEnv* env, jobject options) {
305 return NULL == options ||
306 !env->GetBooleanField(options, gOptions_nativeAllocFieldID);
307}
308
Mike Reedc70e06b2009-04-24 11:09:12 -0400309static jobject nullObjectReturn(const char msg[]) {
310 if (msg) {
311 SkDebugf("--- %s\n", msg);
312 }
313 return NULL;
314}
315
316static SkPixelRef* installPixelRef(SkBitmap* bitmap, SkStream* stream,
Mike Reed17154412009-09-24 12:35:27 -0400317 int sampleSize, bool ditherImage) {
318 SkImageRef* pr;
Mike Reedc70e06b2009-04-24 11:09:12 -0400319 // only use ashmem for large images, since mmaps come at a price
Wei-Ta Chen2a2c5cd2009-06-03 14:08:04 -0700320 if (bitmap->getSize() >= 32 * 1024) {
Mike Reedc70e06b2009-04-24 11:09:12 -0400321 pr = new SkImageRef_ashmem(stream, bitmap->config(), sampleSize);
322 } else {
323 pr = new SkImageRef_GlobalPool(stream, bitmap->config(), sampleSize);
324 }
Mike Reed17154412009-09-24 12:35:27 -0400325 pr->setDitherImage(ditherImage);
Mike Reedc70e06b2009-04-24 11:09:12 -0400326 bitmap->setPixelRef(pr)->unref();
327 return pr;
328}
329
330// since we "may" create a purgeable imageref, we require the stream be ref'able
331// i.e. dynamically allocated, since its lifetime may exceed the current stack
332// frame.
333static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
Mike Reed36ad54a2010-03-10 10:28:10 -0500334 jobject options, bool allowPurgeable,
335 bool forcePurgeable = false) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800336 int sampleSize = 1;
337 SkImageDecoder::Mode mode = SkImageDecoder::kDecodePixels_Mode;
338 SkBitmap::Config prefConfig = SkBitmap::kNo_Config;
339 bool doDither = true;
Mike Reed36ad54a2010-03-10 10:28:10 -0500340 bool isPurgeable = forcePurgeable ||
341 (allowPurgeable && optionsPurgeable(env, options));
Mike Reed1b22b972009-07-17 11:21:47 -0400342 bool reportSizeToVM = optionsReportSizeToVM(env, options);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800343
344 if (NULL != options) {
345 sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
346 if (env->GetBooleanField(options, gOptions_justBoundsFieldID)) {
347 mode = SkImageDecoder::kDecodeBounds_Mode;
348 }
349 // initialize these, in case we fail later on
350 env->SetIntField(options, gOptions_widthFieldID, -1);
351 env->SetIntField(options, gOptions_heightFieldID, -1);
352 env->SetObjectField(options, gOptions_mimeFieldID, 0);
353
354 jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
355 prefConfig = GraphicsJNI::getNativeBitmapConfig(env, jconfig);
356 doDither = env->GetBooleanField(options, gOptions_ditherFieldID);
357 }
358
359 SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
360 if (NULL == decoder) {
Mike Reedc70e06b2009-04-24 11:09:12 -0400361 return nullObjectReturn("SkImageDecoder::Factory returned null");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800362 }
363
364 decoder->setSampleSize(sampleSize);
365 decoder->setDitherImage(doDither);
366
367 NinePatchPeeker peeker;
Mike Reed1b22b972009-07-17 11:21:47 -0400368 JavaPixelAllocator javaAllocator(env, reportSizeToVM);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800369 SkBitmap* bitmap = new SkBitmap;
370 Res_png_9patch dummy9Patch;
371
372 SkAutoTDelete<SkImageDecoder> add(decoder);
373 SkAutoTDelete<SkBitmap> adb(bitmap);
374
375 decoder->setPeeker(&peeker);
Mike Reedc70e06b2009-04-24 11:09:12 -0400376 if (!isPurgeable) {
377 decoder->setAllocator(&javaAllocator);
378 }
379
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800380 AutoDecoderCancel adc(options, decoder);
381
Ray Chen0a6a0e92009-04-02 01:31:49 -0700382 // To fix the race condition in case "requestCancelDecode"
383 // happens earlier than AutoDecoderCancel object is added
384 // to the gAutoDecoderCancelMutex linked list.
Mike Reedc70e06b2009-04-24 11:09:12 -0400385 if (NULL != options && env->GetBooleanField(options, gOptions_mCancelID)) {
386 return nullObjectReturn("gOptions_mCancelID");;
Ray Chen0a6a0e92009-04-02 01:31:49 -0700387 }
388
Mike Reedc70e06b2009-04-24 11:09:12 -0400389 SkImageDecoder::Mode decodeMode = mode;
390 if (isPurgeable) {
391 decodeMode = SkImageDecoder::kDecodeBounds_Mode;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800392 }
Mike Reedc70e06b2009-04-24 11:09:12 -0400393 if (!decoder->decode(stream, bitmap, prefConfig, decodeMode)) {
394 return nullObjectReturn("decoder->decode returned false");
395 }
396
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800397 // update options (if any)
398 if (NULL != options) {
399 env->SetIntField(options, gOptions_widthFieldID, bitmap->width());
400 env->SetIntField(options, gOptions_heightFieldID, bitmap->height());
401 // TODO: set the mimeType field with the data from the codec.
402 // but how to reuse a set of strings, rather than allocating new one
403 // each time?
404 env->SetObjectField(options, gOptions_mimeFieldID,
405 getMimeTypeString(env, decoder->getFormat()));
406 }
Mike Reedc70e06b2009-04-24 11:09:12 -0400407
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800408 // if we're in justBounds mode, return now (skip the java bitmap)
409 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
410 return NULL;
411 }
412
413 jbyteArray ninePatchChunk = NULL;
414 if (peeker.fPatchIsValid) {
415 size_t ninePatchArraySize = peeker.fPatch->serializedSize();
416 ninePatchChunk = env->NewByteArray(ninePatchArraySize);
417 if (NULL == ninePatchChunk) {
Mike Reedc70e06b2009-04-24 11:09:12 -0400418 return nullObjectReturn("ninePatchChunk == null");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800419 }
420 jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(ninePatchChunk,
421 NULL);
422 if (NULL == array) {
Mike Reedc70e06b2009-04-24 11:09:12 -0400423 return nullObjectReturn("primitive array == null");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800424 }
425 peeker.fPatch->serialize(array);
426 env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0);
427 }
428
429 // detach bitmap from its autotdeleter, since we want to own it now
430 adb.detach();
431
432 if (padding) {
433 if (peeker.fPatchIsValid) {
434 GraphicsJNI::set_jrect(env, padding,
435 peeker.fPatch->paddingLeft,
436 peeker.fPatch->paddingTop,
437 peeker.fPatch->paddingRight,
438 peeker.fPatch->paddingBottom);
439 } else {
440 GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
441 }
442 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800443
Mike Reedc70e06b2009-04-24 11:09:12 -0400444 SkPixelRef* pr;
445 if (isPurgeable) {
Mike Reed17154412009-09-24 12:35:27 -0400446 pr = installPixelRef(bitmap, stream, sampleSize, doDither);
Mike Reedc70e06b2009-04-24 11:09:12 -0400447 } else {
448 // if we get here, we're in kDecodePixels_Mode and will therefore
449 // already have a pixelref installed.
450 pr = bitmap->pixelRef();
451 }
452 // promise we will never change our pixels (great for sharing and pictures)
453 pr->setImmutable();
454 // now create the java bitmap
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800455 return GraphicsJNI::createBitmap(env, bitmap, false, ninePatchChunk);
456}
457
458static jobject nativeDecodeStream(JNIEnv* env, jobject clazz,
459 jobject is, // InputStream
460 jbyteArray storage, // byte[]
461 jobject padding,
462 jobject options) { // BitmapFactory$Options
463 jobject bitmap = NULL;
464 SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage);
465
466 if (stream) {
Mike Reedc70e06b2009-04-24 11:09:12 -0400467 // for now we don't allow purgeable with java inputstreams
468 bitmap = doDecode(env, stream, padding, options, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800469 stream->unref();
470 }
471 return bitmap;
472}
473
474static ssize_t getFDSize(int fd) {
475 off_t curr = ::lseek(fd, 0, SEEK_CUR);
476 if (curr < 0) {
477 return 0;
478 }
479 size_t size = ::lseek(fd, 0, SEEK_END);
480 ::lseek(fd, curr, SEEK_SET);
481 return size;
482}
483
484/** Restore the file descriptor's offset in our destructor
485 */
486class AutoFDSeek {
487public:
488 AutoFDSeek(int fd) : fFD(fd) {
489 fCurr = ::lseek(fd, 0, SEEK_CUR);
490 }
491 ~AutoFDSeek() {
492 if (fCurr >= 0) {
493 ::lseek(fFD, fCurr, SEEK_SET);
494 }
495 }
496private:
497 int fFD;
498 off_t fCurr;
499};
500
501static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz,
502 jobject fileDescriptor,
503 jobject padding,
504 jobject bitmapFactoryOptions) {
505 NPE_CHECK_RETURN_ZERO(env, fileDescriptor);
506
507 jint descriptor = env->GetIntField(fileDescriptor,
508 gFileDescriptor_descriptor);
Mike Reedc70e06b2009-04-24 11:09:12 -0400509
510 bool isPurgeable = optionsPurgeable(env, bitmapFactoryOptions);
511 bool isShareable = optionsShareable(env, bitmapFactoryOptions);
512 bool weOwnTheFD = false;
513 if (isPurgeable && isShareable) {
514 int newFD = ::dup(descriptor);
515 if (-1 != newFD) {
516 weOwnTheFD = true;
517 descriptor = newFD;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800518 }
519 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800520
Mike Reedc70e06b2009-04-24 11:09:12 -0400521 SkFDStream* stream = new SkFDStream(descriptor, weOwnTheFD);
522 SkAutoUnref aur(stream);
523 if (!stream->isValid()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800524 return NULL;
525 }
Mike Reedc70e06b2009-04-24 11:09:12 -0400526
527 /* Restore our offset when we leave, so we can be called more than once
528 with the same descriptor. This is only required if we didn't dup the
529 file descriptor, but it is OK to do it all the time.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800530 */
531 AutoFDSeek as(descriptor);
532
Wei-Ta Chen2a2c5cd2009-06-03 14:08:04 -0700533 /* Allow purgeable iff we own the FD, i.e., in the puregeable and
534 shareable case.
535 */
536 return doDecode(env, stream, padding, bitmapFactoryOptions, weOwnTheFD);
Mike Reedc70e06b2009-04-24 11:09:12 -0400537}
538
539/* make a deep copy of the asset, and return it as a stream, or NULL if there
540 was an error.
541 */
542static SkStream* copyAssetToStream(Asset* asset) {
543 // if we could "ref/reopen" the asset, we may not need to copy it here
544 off_t size = asset->seek(0, SEEK_SET);
545 if ((off_t)-1 == size) {
546 SkDebugf("---- copyAsset: asset rewind failed\n");
547 return NULL;
548 }
549
550 size = asset->getLength();
551 if (size <= 0) {
552 SkDebugf("---- copyAsset: asset->getLength() returned %d\n", size);
553 return NULL;
554 }
555
556 SkStream* stream = new SkMemoryStream(size);
557 void* data = const_cast<void*>(stream->getMemoryBase());
558 off_t len = asset->read(data, size);
559 if (len != size) {
560 SkDebugf("---- copyAsset: asset->read(%d) returned %d\n", size, len);
561 delete stream;
562 stream = NULL;
563 }
564 return stream;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800565}
566
567static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz,
568 jint native_asset, // Asset
569 jobject padding, // Rect
570 jobject options) { // BitmapFactory$Options
Mike Reedc70e06b2009-04-24 11:09:12 -0400571 SkStream* stream;
572 Asset* asset = reinterpret_cast<Asset*>(native_asset);
Mike Reed36ad54a2010-03-10 10:28:10 -0500573 // assets can always be rebuilt, so force this
574 bool forcePurgeable = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800575
Mike Reed36ad54a2010-03-10 10:28:10 -0500576 if (forcePurgeable || optionsPurgeable(env, options)) {
Mike Reedc70e06b2009-04-24 11:09:12 -0400577 // if we could "ref/reopen" the asset, we may not need to copy it here
578 // and we could assume optionsShareable, since assets are always RO
579 stream = copyAssetToStream(asset);
580 if (NULL == stream) {
581 return NULL;
582 }
583 } else {
584 // since we know we'll be done with the asset when we return, we can
585 // just use a simple wrapper
586 stream = new AssetStreamAdaptor(asset);
587 }
588 SkAutoUnref aur(stream);
Mike Reed36ad54a2010-03-10 10:28:10 -0500589 return doDecode(env, stream, padding, options, true, forcePurgeable);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800590}
591
592static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
593 int offset, int length, jobject options) {
Mike Reedc70e06b2009-04-24 11:09:12 -0400594 /* If optionsShareable() we could decide to just wrap the java array and
595 share it, but that means adding a globalref to the java array object
596 and managing its lifetime. For now we just always copy the array's data
597 if optionsPurgeable().
598 */
599 AutoJavaByteArray ar(env, byteArray);
600 SkStream* stream = new SkMemoryStream(ar.ptr() + offset, length,
601 optionsPurgeable(env, options));
602 SkAutoUnref aur(stream);
603 return doDecode(env, stream, NULL, options, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800604}
605
606static void nativeRequestCancel(JNIEnv*, jobject joptions) {
607 (void)AutoDecoderCancel::RequestCancel(joptions);
608}
609
610static jbyteArray nativeScaleNinePatch(JNIEnv* env, jobject, jbyteArray chunkObject, jfloat scale,
611 jobject padding) {
612
613 jbyte* array = env->GetByteArrayElements(chunkObject, 0);
614 if (array != NULL) {
615 size_t chunkSize = env->GetArrayLength(chunkObject);
616 void* storage = alloca(chunkSize);
617 android::Res_png_9patch* chunk = static_cast<android::Res_png_9patch*>(storage);
618 memcpy(chunk, array, chunkSize);
619 android::Res_png_9patch::deserialize(chunk);
620
621 chunk->paddingLeft = int(chunk->paddingLeft * scale + 0.5f);
622 chunk->paddingTop = int(chunk->paddingTop * scale + 0.5f);
623 chunk->paddingRight = int(chunk->paddingRight * scale + 0.5f);
624 chunk->paddingBottom = int(chunk->paddingBottom * scale + 0.5f);
625
626 for (int i = 0; i < chunk->numXDivs; i++) {
627 chunk->xDivs[i] = int(chunk->xDivs[i] * scale + 0.5f);
628 if (i > 0 && chunk->xDivs[i] == chunk->xDivs[i - 1]) {
629 chunk->xDivs[i]++;
630 }
631 }
632
633 for (int i = 0; i < chunk->numYDivs; i++) {
634 chunk->yDivs[i] = int(chunk->yDivs[i] * scale + 0.5f);
635 if (i > 0 && chunk->yDivs[i] == chunk->yDivs[i - 1]) {
636 chunk->yDivs[i]++;
637 }
638 }
639
640 memcpy(array, chunk, chunkSize);
641
642 if (padding) {
643 GraphicsJNI::set_jrect(env, padding, chunk->paddingLeft, chunk->paddingTop,
644 chunk->paddingRight, chunk->paddingBottom);
645 }
646
647 env->ReleaseByteArrayElements(chunkObject, array, 0);
648 }
649 return chunkObject;
650}
651
Mike Reedab4a0c12010-01-26 10:13:53 -0500652static void nativeSetDefaultConfig(JNIEnv* env, jobject, int nativeConfig) {
653 SkBitmap::Config config = static_cast<SkBitmap::Config>(nativeConfig);
654
655 // these are the only default configs that make sense for codecs right now
656 static const SkBitmap::Config gValidDefConfig[] = {
657 SkBitmap::kRGB_565_Config,
658 SkBitmap::kARGB_8888_Config,
659 };
660
661 for (size_t i = 0; i < SK_ARRAY_COUNT(gValidDefConfig); i++) {
662 if (config == gValidDefConfig[i]) {
663 SkImageDecoder::SetDeviceConfig(config);
664 break;
665 }
666 }
667}
668
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800669///////////////////////////////////////////////////////////////////////////////
670
671static JNINativeMethod gMethods[] = {
672 { "nativeDecodeStream",
673 "(Ljava/io/InputStream;[BLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
674 (void*)nativeDecodeStream
675 },
676
677 { "nativeDecodeFileDescriptor",
678 "(Ljava/io/FileDescriptor;Landroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
679 (void*)nativeDecodeFileDescriptor
680 },
681
682 { "nativeDecodeAsset",
683 "(ILandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
684 (void*)nativeDecodeAsset
685 },
686
687 { "nativeDecodeByteArray",
688 "([BIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
689 (void*)nativeDecodeByteArray
690 },
691
692 { "nativeScaleNinePatch",
693 "([BFLandroid/graphics/Rect;)[B",
694 (void*)nativeScaleNinePatch
Mike Reedab4a0c12010-01-26 10:13:53 -0500695 },
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800696
Mike Reedab4a0c12010-01-26 10:13:53 -0500697 { "nativeSetDefaultConfig", "(I)V", (void*)nativeSetDefaultConfig },
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800698};
699
700static JNINativeMethod gOptionsMethods[] = {
701 { "requestCancel", "()V", (void*)nativeRequestCancel }
702};
703
704static jclass make_globalref(JNIEnv* env, const char classname[]) {
705 jclass c = env->FindClass(classname);
706 SkASSERT(c);
707 return (jclass)env->NewGlobalRef(c);
708}
709
710static jfieldID getFieldIDCheck(JNIEnv* env, jclass clazz,
711 const char fieldname[], const char type[]) {
712 jfieldID id = env->GetFieldID(clazz, fieldname, type);
713 SkASSERT(id);
714 return id;
715}
716
717#define kClassPathName "android/graphics/BitmapFactory"
718
719#define RETURN_ERR_IF_NULL(value) \
720 do { if (!(value)) { assert(0); return -1; } } while (false)
721
722int register_android_graphics_BitmapFactory(JNIEnv* env);
723int register_android_graphics_BitmapFactory(JNIEnv* env) {
724 gOptions_class = make_globalref(env, "android/graphics/BitmapFactory$Options");
725 gOptions_justBoundsFieldID = getFieldIDCheck(env, gOptions_class, "inJustDecodeBounds", "Z");
726 gOptions_sampleSizeFieldID = getFieldIDCheck(env, gOptions_class, "inSampleSize", "I");
727 gOptions_configFieldID = getFieldIDCheck(env, gOptions_class, "inPreferredConfig",
728 "Landroid/graphics/Bitmap$Config;");
729 gOptions_ditherFieldID = getFieldIDCheck(env, gOptions_class, "inDither", "Z");
Mike Reedc70e06b2009-04-24 11:09:12 -0400730 gOptions_purgeableFieldID = getFieldIDCheck(env, gOptions_class, "inPurgeable", "Z");
731 gOptions_shareableFieldID = getFieldIDCheck(env, gOptions_class, "inInputShareable", "Z");
Mike Reed1b22b972009-07-17 11:21:47 -0400732 gOptions_nativeAllocFieldID = getFieldIDCheck(env, gOptions_class, "inNativeAlloc", "Z");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800733 gOptions_widthFieldID = getFieldIDCheck(env, gOptions_class, "outWidth", "I");
734 gOptions_heightFieldID = getFieldIDCheck(env, gOptions_class, "outHeight", "I");
735 gOptions_mimeFieldID = getFieldIDCheck(env, gOptions_class, "outMimeType", "Ljava/lang/String;");
Ray Chen0a6a0e92009-04-02 01:31:49 -0700736 gOptions_mCancelID = getFieldIDCheck(env, gOptions_class, "mCancel", "Z");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800737
738 gFileDescriptor_class = make_globalref(env, "java/io/FileDescriptor");
739 gFileDescriptor_descriptor = getFieldIDCheck(env, gFileDescriptor_class, "descriptor", "I");
740
741 int ret = AndroidRuntime::registerNativeMethods(env,
742 "android/graphics/BitmapFactory$Options",
743 gOptionsMethods,
744 SK_ARRAY_COUNT(gOptionsMethods));
745 if (ret) {
746 return ret;
747 }
748 return android::AndroidRuntime::registerNativeMethods(env, kClassPathName,
749 gMethods, SK_ARRAY_COUNT(gMethods));
750}