blob: 29c1075a3420dc2b5fe71710fddfc69f114a7300 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001#define LOG_TAG "BitmapFactory"
2
Joseph Wenf1f48bc2010-07-19 16:59:51 +08003#include "BitmapFactory.h"
Ben Wagner60126ef2015-08-07 12:13:48 -04004#include "CreateJavaOutputStreamAdaptor.h"
5#include "GraphicsJNI.h"
Leon Scrogginsa06d86a2011-03-02 16:56:54 -05006#include "NinePatchPeeker.h"
Matt Sarettb8adc9a2015-12-02 13:35:22 -05007#include "SkAndroidCodec.h"
8#include "SkBRDAllocator.h"
Leon Scroggins III7315f1b2013-09-10 20:26:05 -04009#include "SkFrontBufferedStream.h"
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080010#include "SkImageDecoder.h"
Leon Scroggins46cb9bd2014-03-06 15:36:39 -050011#include "SkMath.h"
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080012#include "SkPixelRef.h"
13#include "SkStream.h"
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080014#include "SkUtils.h"
Wei-Ta Chen6b849e22010-09-07 17:32:18 +080015#include "Utils.h"
Andreas Gampeed6b9df2014-11-20 22:02:20 -080016#include "core_jni_helpers.h"
Ben Wagner60126ef2015-08-07 12:13:48 -040017
18#include <JNIHelp.h>
Mathias Agopianb13b9bd2012-02-17 18:27:36 -080019#include <androidfw/Asset.h>
20#include <androidfw/ResourceTypes.h>
Chris Craikbd8db2e82014-08-20 16:31:57 -070021#include <cutils/compiler.h>
Ben Wagner60126ef2015-08-07 12:13:48 -040022#include <memory>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023#include <netinet/in.h>
Leon Scroggins III0102f8a2014-01-14 15:14:57 -050024#include <stdio.h>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025#include <sys/mman.h>
Joseph Wen2dcfbef2010-09-10 10:15:09 +080026#include <sys/stat.h>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027
Joseph Wenf1f48bc2010-07-19 16:59:51 +080028jfieldID gOptions_justBoundsFieldID;
29jfieldID gOptions_sampleSizeFieldID;
30jfieldID gOptions_configFieldID;
Chris Craik1abf5d62013-08-16 12:47:03 -070031jfieldID gOptions_premultipliedFieldID;
Romain Guy23610982011-01-17 12:51:55 -080032jfieldID gOptions_mutableFieldID;
Joseph Wenf1f48bc2010-07-19 16:59:51 +080033jfieldID gOptions_ditherFieldID;
Wei-Ta Chen953f9092010-12-03 14:06:18 -080034jfieldID gOptions_preferQualityOverSpeedFieldID;
Chris Craik905e8242013-06-05 09:59:05 -070035jfieldID gOptions_scaledFieldID;
36jfieldID gOptions_densityFieldID;
37jfieldID gOptions_screenDensityFieldID;
38jfieldID gOptions_targetDensityFieldID;
Joseph Wenf1f48bc2010-07-19 16:59:51 +080039jfieldID gOptions_widthFieldID;
40jfieldID gOptions_heightFieldID;
41jfieldID gOptions_mimeFieldID;
42jfieldID gOptions_mCancelID;
Chet Haase37f74ca2010-12-08 17:56:36 -080043jfieldID gOptions_bitmapFieldID;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044
Chris Craik47cd8e92014-07-08 17:13:08 -070045jfieldID gBitmap_ninePatchInsetsFieldID;
46
47jclass gInsetStruct_class;
48jmethodID gInsetStruct_constructorMethodID;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050using namespace android;
51
Matt Sarettb8adc9a2015-12-02 13:35:22 -050052jstring encodedFormatToString(JNIEnv* env, SkEncodedFormat format) {
53 const char* mimeType;
54 switch (format) {
55 case SkEncodedFormat::kBMP_SkEncodedFormat:
56 mimeType = "image/bmp";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080057 break;
Matt Sarettb8adc9a2015-12-02 13:35:22 -050058 case SkEncodedFormat::kGIF_SkEncodedFormat:
59 mimeType = "image/gif";
60 break;
61 case SkEncodedFormat::kICO_SkEncodedFormat:
62 mimeType = "image/x-ico";
63 break;
64 case SkEncodedFormat::kJPEG_SkEncodedFormat:
65 mimeType = "image/jpeg";
66 break;
67 case SkEncodedFormat::kPNG_SkEncodedFormat:
68 mimeType = "image/png";
69 break;
70 case SkEncodedFormat::kWEBP_SkEncodedFormat:
71 mimeType = "image/webp";
72 break;
73 case SkEncodedFormat::kWBMP_SkEncodedFormat:
74 mimeType = "image/vnd.wap.wbmp";
75 break;
76 default:
77 mimeType = nullptr;
78 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080079 }
80
Vladimir Marko7ab249a2015-01-06 18:17:52 +000081 jstring jstr = nullptr;
Matt Sarettb8adc9a2015-12-02 13:35:22 -050082 if (mimeType) {
Vladimir Marko7ab249a2015-01-06 18:17:52 +000083 // NOTE: Caller should env->ExceptionCheck() for OOM
84 // (can't check for nullptr as it's a valid return value)
Matt Sarettb8adc9a2015-12-02 13:35:22 -050085 jstr = env->NewStringUTF(mimeType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080086 }
87 return jstr;
88}
89
Chris Craikbd8db2e82014-08-20 16:31:57 -070090static void scaleDivRange(int32_t* divs, int count, float scale, int maxValue) {
91 for (int i = 0; i < count; i++) {
92 divs[i] = int32_t(divs[i] * scale + 0.5f);
93 if (i > 0 && divs[i] == divs[i - 1]) {
94 divs[i]++; // avoid collisions
95 }
96 }
97
98 if (CC_UNLIKELY(divs[count - 1] > maxValue)) {
99 // if the collision avoidance above put some divs outside the bounds of the bitmap,
100 // slide outer stretchable divs inward to stay within bounds
101 int highestAvailable = maxValue;
102 for (int i = count - 1; i >= 0; i--) {
103 divs[i] = highestAvailable;
104 if (i > 0 && divs[i] <= divs[i-1]){
105 // keep shifting
106 highestAvailable = divs[i] - 1;
107 } else {
108 break;
109 }
110 }
111 }
112}
113
114static void scaleNinePatchChunk(android::Res_png_9patch* chunk, float scale,
115 int scaledWidth, int scaledHeight) {
Romain Guy7b2f8b82012-03-19 17:18:54 -0700116 chunk->paddingLeft = int(chunk->paddingLeft * scale + 0.5f);
117 chunk->paddingTop = int(chunk->paddingTop * scale + 0.5f);
118 chunk->paddingRight = int(chunk->paddingRight * scale + 0.5f);
119 chunk->paddingBottom = int(chunk->paddingBottom * scale + 0.5f);
120
Chris Craikbd8db2e82014-08-20 16:31:57 -0700121 scaleDivRange(chunk->getXDivs(), chunk->numXDivs, scale, scaledWidth);
122 scaleDivRange(chunk->getYDivs(), chunk->numYDivs, scale, scaledHeight);
Romain Guy7b2f8b82012-03-19 17:18:54 -0700123}
124
Leon Scroggins46cb9bd2014-03-06 15:36:39 -0500125static SkColorType colorTypeForScaledOutput(SkColorType colorType) {
126 switch (colorType) {
127 case kUnknown_SkColorType:
128 case kIndex_8_SkColorType:
Mike Reed4a9c3892014-07-07 15:44:40 -0400129 return kN32_SkColorType;
Chris Craik905e8242013-06-05 09:59:05 -0700130 default:
131 break;
132 }
Leon Scroggins46cb9bd2014-03-06 15:36:39 -0500133 return colorType;
Chris Craik905e8242013-06-05 09:59:05 -0700134}
135
136class ScaleCheckingAllocator : public SkBitmap::HeapAllocator {
137public:
138 ScaleCheckingAllocator(float scale, int size)
139 : mScale(scale), mSize(size) {
140 }
141
142 virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
143 // accounts for scale in final allocation, using eventual size and config
Leon Scroggins46cb9bd2014-03-06 15:36:39 -0500144 const int bytesPerPixel = SkColorTypeBytesPerPixel(
145 colorTypeForScaledOutput(bitmap->colorType()));
Chris Craik905e8242013-06-05 09:59:05 -0700146 const int requestedSize = bytesPerPixel *
147 int(bitmap->width() * mScale + 0.5f) *
148 int(bitmap->height() * mScale + 0.5f);
149 if (requestedSize > mSize) {
150 ALOGW("bitmap for alloc reuse (%d bytes) can't fit scaled bitmap (%d bytes)",
151 mSize, requestedSize);
152 return false;
153 }
154 return SkBitmap::HeapAllocator::allocPixelRef(bitmap, ctable);
155 }
156private:
157 const float mScale;
158 const int mSize;
159};
160
Chris Craik7e8c03c2013-06-03 13:53:36 -0700161class RecyclingPixelAllocator : public SkBitmap::Allocator {
162public:
John Reckf29ed282015-04-07 07:32:03 -0700163 RecyclingPixelAllocator(android::Bitmap* bitmap, unsigned int size)
164 : mBitmap(bitmap), mSize(size) {
Chris Craik7e8c03c2013-06-03 13:53:36 -0700165 }
166
167 ~RecyclingPixelAllocator() {
Chris Craik7e8c03c2013-06-03 13:53:36 -0700168 }
169
170 virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
Leon Scroggins46cb9bd2014-03-06 15:36:39 -0500171 const SkImageInfo& info = bitmap->info();
Leon Scroggins IIIf35b9892015-07-31 10:38:40 -0400172 if (info.colorType() == kUnknown_SkColorType) {
Leon Scroggins46cb9bd2014-03-06 15:36:39 -0500173 ALOGW("unable to reuse a bitmap as the target has an unknown bitmap configuration");
Chris Craik7e8c03c2013-06-03 13:53:36 -0700174 return false;
175 }
Chris Craikcd0ba712013-09-06 14:40:30 -0700176
Leon Scroggins46cb9bd2014-03-06 15:36:39 -0500177 const int64_t size64 = info.getSafeSize64(bitmap->rowBytes());
178 if (!sk_64_isS32(size64)) {
179 ALOGW("bitmap is too large");
180 return false;
181 }
182
183 const size_t size = sk_64_asS32(size64);
184 if (size > mSize) {
Dan Albert46d84442014-11-18 16:07:51 -0800185 ALOGW("bitmap marked for reuse (%u bytes) can't fit new bitmap "
186 "(%zu bytes)", mSize, size);
Derek Sollenbergerb644a3b2014-01-17 15:45:10 -0500187 return false;
188 }
189
John Reckf29ed282015-04-07 07:32:03 -0700190 mBitmap->reconfigure(info, bitmap->rowBytes(), ctable);
John Reckae2e8b42015-05-06 14:55:05 -0700191 bitmap->setPixelRef(mBitmap->refPixelRef())->unref();
Chris Craikcd0ba712013-09-06 14:40:30 -0700192
Chris Craikcd0ba712013-09-06 14:40:30 -0700193 // since we're already allocated, we lockPixels right away
194 // HeapAllocator/JavaPixelAllocator behaves this way too
Chris Craik7e8c03c2013-06-03 13:53:36 -0700195 bitmap->lockPixels();
196 return true;
197 }
198
199private:
John Reckf29ed282015-04-07 07:32:03 -0700200 android::Bitmap* const mBitmap;
Chris Craik7e8c03c2013-06-03 13:53:36 -0700201 const unsigned int mSize;
202};
203
Chris Craik47cd8e92014-07-08 17:13:08 -0700204static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {
Matt Sarettb8adc9a2015-12-02 13:35:22 -0500205 // This function takes ownership of the input stream. Since the SkAndroidCodec
206 // will take ownership of the stream, we don't necessarily need to take ownership
207 // here. This is a precaution - if we were to return before creating the codec,
208 // we need to make sure that we delete the stream.
209 std::unique_ptr<SkStreamRewindable> streamDeleter(stream);
Romain Guy7b2f8b82012-03-19 17:18:54 -0700210
Matt Sarettb8adc9a2015-12-02 13:35:22 -0500211 // Set default values for the options parameters.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800212 int sampleSize = 1;
Matt Sarettb8adc9a2015-12-02 13:35:22 -0500213 bool onlyDecodeSize = false;
Mike Reed42a1d082014-07-07 18:06:18 -0400214 SkColorType prefColorType = kN32_SkColorType;
Romain Guy23610982011-01-17 12:51:55 -0800215 bool isMutable = false;
Chris Craik905e8242013-06-05 09:59:05 -0700216 float scale = 1.0f;
Chris Craik1abf5d62013-08-16 12:47:03 -0700217 bool requireUnpremultiplied = false;
Chet Haase37f74ca2010-12-08 17:56:36 -0800218 jobject javaBitmap = NULL;
Elliott Hughesa3804cf2011-04-11 16:50:19 -0700219
Matt Sarettb8adc9a2015-12-02 13:35:22 -0500220 // Update with options supplied by the client.
Romain Guy7b2f8b82012-03-19 17:18:54 -0700221 if (options != NULL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800222 sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
Matt Sarettb8adc9a2015-12-02 13:35:22 -0500223 // Correct a non-positive sampleSize. sampleSize defaults to zero within the
224 // options object, which is strange.
225 if (sampleSize <= 0) {
226 sampleSize = 1;
227 }
228
229 if (env->GetBooleanField(options, gOptions_justBoundsFieldID)) {
230 onlyDecodeSize = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800231 }
Romain Guy7b2f8b82012-03-19 17:18:54 -0700232
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800233 // initialize these, in case we fail later on
234 env->SetIntField(options, gOptions_widthFieldID, -1);
235 env->SetIntField(options, gOptions_heightFieldID, -1);
236 env->SetObjectField(options, gOptions_mimeFieldID, 0);
Elliott Hughesa3804cf2011-04-11 16:50:19 -0700237
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800238 jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
Mike Reed42a1d082014-07-07 18:06:18 -0400239 prefColorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig);
Romain Guy23610982011-01-17 12:51:55 -0800240 isMutable = env->GetBooleanField(options, gOptions_mutableFieldID);
Chris Craik1abf5d62013-08-16 12:47:03 -0700241 requireUnpremultiplied = !env->GetBooleanField(options, gOptions_premultipliedFieldID);
Chet Haase37f74ca2010-12-08 17:56:36 -0800242 javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);
Chris Craik905e8242013-06-05 09:59:05 -0700243
244 if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
245 const int density = env->GetIntField(options, gOptions_densityFieldID);
246 const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);
247 const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
248 if (density != 0 && targetDensity != 0 && density != screenDensity) {
249 scale = (float) targetDensity / density;
250 }
251 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800252 }
Chris Craik905e8242013-06-05 09:59:05 -0700253 const bool willScale = scale != 1.0f;
Romain Guy7b2f8b82012-03-19 17:18:54 -0700254
Matt Sarettb8adc9a2015-12-02 13:35:22 -0500255 // Create the codec.
256 NinePatchPeeker peeker;
257 std::unique_ptr<SkAndroidCodec> codec(SkAndroidCodec::NewFromStream(streamDeleter.release(),
258 &peeker));
259 if (!codec.get()) {
260 return nullObjectReturn("SkAndroidCodec::NewFromStream returned null");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800261 }
Elliott Hughesa3804cf2011-04-11 16:50:19 -0700262
Matt Sarett3b1b68d2015-12-14 13:08:33 -0500263 // Do not allow ninepatch decodes to 565. In the past, decodes to 565
264 // would dither, and we do not want to pre-dither ninepatches, since we
265 // know that they will be stretched. We no longer dither 565 decodes,
266 // but we continue to prevent ninepatches from decoding to 565, in order
267 // to maintain the old behavior.
268 if (peeker.mPatch && kRGB_565_SkColorType == prefColorType) {
269 prefColorType = kN32_SkColorType;
270 }
271
Matt Sarettb8adc9a2015-12-02 13:35:22 -0500272 // Determine the output size and return if the client only wants the size.
273 SkISize size = codec->getSampledDimensions(sampleSize);
274 if (options != NULL) {
275 jstring mimeType = encodedFormatToString(env, codec->getEncodedFormat());
276 if (env->ExceptionCheck()) {
Matt Sarettd31512b2015-12-09 15:16:31 -0500277 return nullObjectReturn("OOM in encodedFormatToString()");
Matt Sarettb8adc9a2015-12-02 13:35:22 -0500278 }
279 env->SetIntField(options, gOptions_widthFieldID, size.width());
280 env->SetIntField(options, gOptions_heightFieldID, size.height());
281 env->SetObjectField(options, gOptions_mimeFieldID, mimeType);
282
283 if (onlyDecodeSize) {
284 return nullptr;
285 }
286 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800287
John Reckf29ed282015-04-07 07:32:03 -0700288 android::Bitmap* reuseBitmap = nullptr;
Chris Craik9f583612013-05-20 18:13:47 -0700289 unsigned int existingBufferSize = 0;
Chris Craik7e8c03c2013-06-03 13:53:36 -0700290 if (javaBitmap != NULL) {
John Reckf29ed282015-04-07 07:32:03 -0700291 reuseBitmap = GraphicsJNI::getBitmap(env, javaBitmap);
John Reckae2e8b42015-05-06 14:55:05 -0700292 if (reuseBitmap->peekAtPixelRef()->isImmutable()) {
Derek Sollenberger2a6ecae2012-08-31 14:03:51 -0400293 ALOGW("Unable to reuse an immutable bitmap as an image decoder target.");
Chris Craik7e8c03c2013-06-03 13:53:36 -0700294 javaBitmap = NULL;
John Reckf29ed282015-04-07 07:32:03 -0700295 reuseBitmap = nullptr;
Chris Craik7e8c03c2013-06-03 13:53:36 -0700296 } else {
297 existingBufferSize = GraphicsJNI::getBitmapAllocationByteCount(env, javaBitmap);
Derek Sollenberger2a6ecae2012-08-31 14:03:51 -0400298 }
Chet Haase37f74ca2010-12-08 17:56:36 -0800299 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800300
Chris Craik905e8242013-06-05 09:59:05 -0700301 JavaPixelAllocator javaAllocator(env);
John Reckf29ed282015-04-07 07:32:03 -0700302 RecyclingPixelAllocator recyclingAllocator(reuseBitmap, existingBufferSize);
Chris Craik905e8242013-06-05 09:59:05 -0700303 ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize);
Matt Sarettb8adc9a2015-12-02 13:35:22 -0500304 SkBitmap::HeapAllocator heapAllocator;
305 SkBitmap::Allocator* decodeAllocator;
306 if (javaBitmap != nullptr && willScale) {
307 // This will allocate pixels using a HeapAllocator, since there will be an extra
308 // scaling step that copies these pixels into Java memory. This allocator
309 // also checks that the recycled javaBitmap is large enough.
310 decodeAllocator = &scaleCheckingAllocator;
311 } else if (javaBitmap != nullptr) {
312 decodeAllocator = &recyclingAllocator;
313 } else if (willScale) {
314 // This will allocate pixels using a HeapAllocator, since there will be an extra
315 // scaling step that copies these pixels into Java memory.
316 decodeAllocator = &heapAllocator;
317 } else {
318 decodeAllocator = &javaAllocator;
Chris Craik905e8242013-06-05 09:59:05 -0700319 }
320
Matt Sarett9e7cd632015-12-11 10:54:28 -0500321 // Set the decode colorType. This is necessary because we can't always support
322 // the requested colorType.
323 SkColorType decodeColorType = codec->computeOutputColorType(prefColorType);
Derek Sollenberger6b0437c2013-06-24 15:40:54 -0400324
Matt Sarettb8adc9a2015-12-02 13:35:22 -0500325 // Construct a color table for the decode if necessary
326 SkAutoTUnref<SkColorTable> colorTable(nullptr);
327 SkPMColor* colorPtr = nullptr;
328 int* colorCount = nullptr;
329 int maxColors = 256;
330 SkPMColor colors[256];
331 if (kIndex_8_SkColorType == decodeColorType) {
332 colorTable.reset(new SkColorTable(colors, maxColors));
Derek Sollenberger6b0437c2013-06-24 15:40:54 -0400333
Matt Sarettb8adc9a2015-12-02 13:35:22 -0500334 // SkColorTable expects us to initialize all of the colors before creating an
335 // SkColorTable. However, we are using SkBitmap with an Allocator to allocate
336 // memory for the decode, so we need to create the SkColorTable before decoding.
337 // It is safe for SkAndroidCodec to modify the colors because this SkBitmap is
338 // not being used elsewhere.
339 colorPtr = const_cast<SkPMColor*>(colorTable->readColors());
340 colorCount = &maxColors;
Derek Sollenberger6b0437c2013-06-24 15:40:54 -0400341 }
342
Matt Sarett9e7cd632015-12-11 10:54:28 -0500343 // Set the alpha type for the decode.
344 SkAlphaType alphaType = codec->computeOutputAlphaType(requireUnpremultiplied);
Matt Sarettb8adc9a2015-12-02 13:35:22 -0500345
346 const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(), decodeColorType,
347 alphaType);
348 SkImageInfo bitmapInfo = decodeInfo;
349 if (decodeColorType == kGray_8_SkColorType) {
350 // The legacy implementation of BitmapFactory used kAlpha8 for
351 // grayscale images (before kGray8 existed). While the codec
352 // recognizes kGray8, we need to decode into a kAlpha8 bitmap
353 // in order to avoid a behavior change.
354 bitmapInfo = SkImageInfo::MakeA8(size.width(), size.height());
355 }
Chris Craik7e8c03c2013-06-03 13:53:36 -0700356 SkBitmap decodingBitmap;
Matt Sarettb8adc9a2015-12-02 13:35:22 -0500357 if (!decodingBitmap.setInfo(bitmapInfo) ||
358 !decodingBitmap.tryAllocPixels(decodeAllocator, colorTable)) {
359 // SkAndroidCodec should recommend a valid SkImageInfo, so setInfo()
360 // should only only fail if the calculated value for rowBytes is too
361 // large.
362 // tryAllocPixels() can fail due to OOM on the Java heap, OOM on the
363 // native heap, or the recycled javaBitmap being too small to reuse.
364 return nullptr;
Mike Reedc70e06b2009-04-24 11:09:12 -0400365 }
366
Matt Sarettb8adc9a2015-12-02 13:35:22 -0500367 // Use SkAndroidCodec to perform the decode.
368 SkAndroidCodec::AndroidOptions codecOptions;
369 codecOptions.fZeroInitialized = (decodeAllocator == &javaAllocator) ?
370 SkCodec::kYes_ZeroInitialized : SkCodec::kNo_ZeroInitialized;
371 codecOptions.fColorPtr = colorPtr;
372 codecOptions.fColorCount = colorCount;
373 codecOptions.fSampleSize = sampleSize;
374 SkCodec::Result result = codec->getAndroidPixels(decodeInfo, decodingBitmap.getPixels(),
375 decodingBitmap.rowBytes(), &codecOptions);
376 switch (result) {
377 case SkCodec::kSuccess:
378 case SkCodec::kIncompleteInput:
379 break;
380 default:
Matt Sarett3b1b68d2015-12-14 13:08:33 -0500381 return nullObjectReturn("codec->getAndroidPixels() failed.");
Matt Sarettb8adc9a2015-12-02 13:35:22 -0500382 }
383
384 int scaledWidth = size.width();
385 int scaledHeight = size.height();
386 if (willScale) {
Romain Guy7b2f8b82012-03-19 17:18:54 -0700387 scaledWidth = int(scaledWidth * scale + 0.5f);
388 scaledHeight = int(scaledHeight * scale + 0.5f);
389 }
390
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800391 jbyteArray ninePatchChunk = NULL;
Chris Craik47cd8e92014-07-08 17:13:08 -0700392 if (peeker.mPatch != NULL) {
Romain Guy7b2f8b82012-03-19 17:18:54 -0700393 if (willScale) {
Chris Craikbd8db2e82014-08-20 16:31:57 -0700394 scaleNinePatchChunk(peeker.mPatch, scale, scaledWidth, scaledHeight);
Romain Guy7b2f8b82012-03-19 17:18:54 -0700395 }
396
Chris Craik47cd8e92014-07-08 17:13:08 -0700397 size_t ninePatchArraySize = peeker.mPatch->serializedSize();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800398 ninePatchChunk = env->NewByteArray(ninePatchArraySize);
Romain Guy7b2f8b82012-03-19 17:18:54 -0700399 if (ninePatchChunk == NULL) {
Mike Reedc70e06b2009-04-24 11:09:12 -0400400 return nullObjectReturn("ninePatchChunk == null");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800401 }
Romain Guy7b2f8b82012-03-19 17:18:54 -0700402
403 jbyte* array = (jbyte*) env->GetPrimitiveArrayCritical(ninePatchChunk, NULL);
404 if (array == NULL) {
Mike Reedc70e06b2009-04-24 11:09:12 -0400405 return nullObjectReturn("primitive array == null");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800406 }
Romain Guy7b2f8b82012-03-19 17:18:54 -0700407
Chris Craik47cd8e92014-07-08 17:13:08 -0700408 memcpy(array, peeker.mPatch, peeker.mPatchSize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800409 env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0);
410 }
411
Chris Craik47cd8e92014-07-08 17:13:08 -0700412 jobject ninePatchInsets = NULL;
413 if (peeker.mHasInsets) {
414 ninePatchInsets = env->NewObject(gInsetStruct_class, gInsetStruct_constructorMethodID,
415 peeker.mOpticalInsets[0], peeker.mOpticalInsets[1], peeker.mOpticalInsets[2], peeker.mOpticalInsets[3],
416 peeker.mOutlineInsets[0], peeker.mOutlineInsets[1], peeker.mOutlineInsets[2], peeker.mOutlineInsets[3],
Chris Craik77b5cad2014-07-30 18:23:07 -0700417 peeker.mOutlineRadius, peeker.mOutlineAlpha, scale);
Mathieu Chartiera08d10f2014-08-29 16:55:55 -0700418 if (ninePatchInsets == NULL) {
419 return nullObjectReturn("nine patch insets == null");
420 }
Amith Yamasaniec4a5042012-04-04 10:27:15 -0700421 if (javaBitmap != NULL) {
Chris Craik47cd8e92014-07-08 17:13:08 -0700422 env->SetObjectField(javaBitmap, gBitmap_ninePatchInsetsFieldID, ninePatchInsets);
Amith Yamasaniec4a5042012-04-04 10:27:15 -0700423 }
424 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800425
John Reckf29ed282015-04-07 07:32:03 -0700426 SkBitmap outputBitmap;
Romain Guy7b2f8b82012-03-19 17:18:54 -0700427 if (willScale) {
428 // This is weird so let me explain: we could use the scale parameter
429 // directly, but for historical reasons this is how the corresponding
430 // Dalvik code has always behaved. We simply recreate the behavior here.
431 // The result is slightly different from simply using scale because of
432 // the 0.5f rounding bias applied when computing the target image size
Chris Craik7e8c03c2013-06-03 13:53:36 -0700433 const float sx = scaledWidth / float(decodingBitmap.width());
434 const float sy = scaledHeight / float(decodingBitmap.height());
Romain Guy7b2f8b82012-03-19 17:18:54 -0700435
Matt Sarettb8adc9a2015-12-02 13:35:22 -0500436 // Set the allocator for the outputBitmap.
437 SkBitmap::Allocator* outputAllocator;
438 if (javaBitmap != nullptr) {
439 outputAllocator = &recyclingAllocator;
440 } else {
441 outputAllocator = &javaAllocator;
442 }
443
444 SkColorType scaledColorType = colorTypeForScaledOutput(decodingBitmap.colorType());
Leon Scroggins III8790be62013-12-03 16:26:51 -0500445 // FIXME: If the alphaType is kUnpremul and the image has alpha, the
446 // colors may not be correct, since Skia does not yet support drawing
447 // to/from unpremultiplied bitmaps.
John Reckf29ed282015-04-07 07:32:03 -0700448 outputBitmap.setInfo(SkImageInfo::Make(scaledWidth, scaledHeight,
Matt Sarettb8adc9a2015-12-02 13:35:22 -0500449 scaledColorType, decodingBitmap.alphaType()));
John Reckf29ed282015-04-07 07:32:03 -0700450 if (!outputBitmap.tryAllocPixels(outputAllocator, NULL)) {
Matt Sarettb8adc9a2015-12-02 13:35:22 -0500451 // This should only fail on OOM. The recyclingAllocator should have
452 // enough memory since we check this before decoding using the
453 // scaleCheckingAllocator.
Raph Levien005bfc62012-09-20 22:51:47 -0700454 return nullObjectReturn("allocation failed for scaled bitmap");
455 }
Leon Scroggins III1ffe7272013-09-19 11:34:06 -0400456
Romain Guy7b2f8b82012-03-19 17:18:54 -0700457 SkPaint paint;
Matt Sarettb8adc9a2015-12-02 13:35:22 -0500458 // kSrc_Mode instructs us to overwrite the unininitialized pixels in
459 // outputBitmap. Otherwise we would blend by default, which is not
460 // what we want.
461 paint.setXfermodeMode(SkXfermode::kSrc_Mode);
Mike Reed2a1ce8a2015-03-16 11:16:09 -0400462 paint.setFilterQuality(kLow_SkFilterQuality);
Romain Guy7b2f8b82012-03-19 17:18:54 -0700463
John Reckf29ed282015-04-07 07:32:03 -0700464 SkCanvas canvas(outputBitmap);
Romain Guy7b2f8b82012-03-19 17:18:54 -0700465 canvas.scale(sx, sy);
Chris Craik7e8c03c2013-06-03 13:53:36 -0700466 canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
467 } else {
John Reckf29ed282015-04-07 07:32:03 -0700468 outputBitmap.swap(decodingBitmap);
Romain Guy7b2f8b82012-03-19 17:18:54 -0700469 }
470
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800471 if (padding) {
Chris Craik47cd8e92014-07-08 17:13:08 -0700472 if (peeker.mPatch != NULL) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800473 GraphicsJNI::set_jrect(env, padding,
Chris Craik47cd8e92014-07-08 17:13:08 -0700474 peeker.mPatch->paddingLeft, peeker.mPatch->paddingTop,
475 peeker.mPatch->paddingRight, peeker.mPatch->paddingBottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800476 } else {
477 GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
478 }
479 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800480
Matt Sarettb8adc9a2015-12-02 13:35:22 -0500481 // If we get here, the outputBitmap should have an installed pixelref.
John Reckf29ed282015-04-07 07:32:03 -0700482 if (outputBitmap.pixelRef() == NULL) {
Marco Nelissenb2fe3be2012-05-07 11:24:13 -0700483 return nullObjectReturn("Got null SkPixelRef");
484 }
Romain Guy23610982011-01-17 12:51:55 -0800485
Chris Craik7e8c03c2013-06-03 13:53:36 -0700486 if (!isMutable && javaBitmap == NULL) {
Romain Guy23610982011-01-17 12:51:55 -0800487 // promise we will never change our pixels (great for sharing and pictures)
John Reckf29ed282015-04-07 07:32:03 -0700488 outputBitmap.setImmutable();
Romain Guy23610982011-01-17 12:51:55 -0800489 }
Chet Haase37f74ca2010-12-08 17:56:36 -0800490
Matt Sarettb8adc9a2015-12-02 13:35:22 -0500491 bool isPremultiplied = !requireUnpremultiplied;
492 if (javaBitmap != nullptr) {
John Reckf29ed282015-04-07 07:32:03 -0700493 GraphicsJNI::reinitBitmap(env, javaBitmap, outputBitmap.info(), isPremultiplied);
494 outputBitmap.notifyPixelsChanged();
Chet Haase37f74ca2010-12-08 17:56:36 -0800495 // If a java bitmap was passed in for reuse, pass it back
496 return javaBitmap;
497 }
Chris Craik1abf5d62013-08-16 12:47:03 -0700498
499 int bitmapCreateFlags = 0x0;
500 if (isMutable) bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Mutable;
Matt Sarettb8adc9a2015-12-02 13:35:22 -0500501 if (isPremultiplied) bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Premultiplied;
Chris Craik1abf5d62013-08-16 12:47:03 -0700502
Mike Reedc70e06b2009-04-24 11:09:12 -0400503 // now create the java bitmap
John Reckf29ed282015-04-07 07:32:03 -0700504 return GraphicsJNI::createBitmap(env, javaAllocator.getStorageObjAndReset(),
Chris Craik47cd8e92014-07-08 17:13:08 -0700505 bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800506}
507
Chris Craik905e8242013-06-05 09:59:05 -0700508static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
509 jobject padding, jobject options) {
Romain Guy7b2f8b82012-03-19 17:18:54 -0700510
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800511 jobject bitmap = NULL;
Ben Wagner60126ef2015-08-07 12:13:48 -0400512 std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800513
Leon Scroggins IIIca320212013-08-20 17:59:39 -0400514 if (stream.get()) {
Ben Wagner60126ef2015-08-07 12:13:48 -0400515 std::unique_ptr<SkStreamRewindable> bufferedStream(
Matt Sarett96ffbdc2016-02-11 17:03:54 -0500516 SkFrontBufferedStream::Create(stream.release(), SkCodec::MinBufferedBytesNeeded()));
Leon Scroggins III7315f1b2013-09-10 20:26:05 -0400517 SkASSERT(bufferedStream.get() != NULL);
Matt Sarettb8adc9a2015-12-02 13:35:22 -0500518 bitmap = doDecode(env, bufferedStream.release(), padding, options);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800519 }
520 return bitmap;
521}
522
Romain Guy7b2f8b82012-03-19 17:18:54 -0700523static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fileDescriptor,
524 jobject padding, jobject bitmapFactoryOptions) {
525
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800526 NPE_CHECK_RETURN_ZERO(env, fileDescriptor);
527
Derek Sollenberger5cb769d2014-09-24 09:20:09 -0400528 int descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor);
Mike Reedc70e06b2009-04-24 11:09:12 -0400529
Derek Sollenberger5827cb52013-07-26 14:58:06 -0400530 struct stat fdStat;
531 if (fstat(descriptor, &fdStat) == -1) {
532 doThrowIOE(env, "broken file descriptor");
533 return nullObjectReturn("fstat return -1");
534 }
535
Derek Sollenberger5cb769d2014-09-24 09:20:09 -0400536 // Restore the descriptor's offset on exiting this function. Even though
537 // we dup the descriptor, both the original and dup refer to the same open
538 // file description and changes to the file offset in one impact the other.
Jérôme Poichetd29c9022014-09-26 18:59:11 +0000539 AutoFDSeek autoRestore(descriptor);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800540
Derek Sollenberger5cb769d2014-09-24 09:20:09 -0400541 // Duplicate the descriptor here to prevent leaking memory. A leak occurs
542 // if we only close the file descriptor and not the file object it is used to
543 // create. If we don't explicitly clean up the file (which in turn closes the
544 // descriptor) the buffers allocated internally by fseek will be leaked.
545 int dupDescriptor = dup(descriptor);
546
547 FILE* file = fdopen(dupDescriptor, "r");
Leon Scroggins III0102f8a2014-01-14 15:14:57 -0500548 if (file == NULL) {
Derek Sollenberger5cb769d2014-09-24 09:20:09 -0400549 // cleanup the duplicated descriptor since it will not be closed when the
550 // file is cleaned up (fclose).
551 close(dupDescriptor);
Leon Scroggins III0102f8a2014-01-14 15:14:57 -0500552 return nullObjectReturn("Could not open file");
Leon Scroggins IIIf65183f2013-10-07 16:32:14 -0400553 }
Leon Scroggins III0102f8a2014-01-14 15:14:57 -0500554
Ben Wagner60126ef2015-08-07 12:13:48 -0400555 std::unique_ptr<SkFILEStream> fileStream(new SkFILEStream(file,
Leon Scroggins III34497892015-01-20 15:52:43 -0500556 SkFILEStream::kCallerPasses_Ownership));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800557
Yujie Qinc1d7b7f2016-02-29 14:00:29 +0100558 // If there is no offset for the file descriptor, we use SkFILEStream directly.
559 if (::lseek(descriptor, 0, SEEK_CUR) == 0) {
560 assert(isSeekable(dupDescriptor));
561 return doDecode(env, fileStream.release(), padding, bitmapFactoryOptions);
562 }
563
Leon Scroggins III0aa39dc2014-06-03 12:19:32 -0400564 // Use a buffered stream. Although an SkFILEStream can be rewound, this
565 // ensures that SkImageDecoder::Factory never rewinds beyond the
566 // current position of the file descriptor.
Ben Wagner60126ef2015-08-07 12:13:48 -0400567 std::unique_ptr<SkStreamRewindable> stream(SkFrontBufferedStream::Create(fileStream.release(),
Matt Sarett96ffbdc2016-02-11 17:03:54 -0500568 SkCodec::MinBufferedBytesNeeded()));
Leon Scroggins III2826e5f2014-02-05 19:46:02 -0500569
Matt Sarettb8adc9a2015-12-02 13:35:22 -0500570 return doDecode(env, stream.release(), padding, bitmapFactoryOptions);
Mike Reedc70e06b2009-04-24 11:09:12 -0400571}
572
Ashok Bhat36bef0b2014-01-20 20:08:01 +0000573static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jlong native_asset,
Chris Craik905e8242013-06-05 09:59:05 -0700574 jobject padding, jobject options) {
Romain Guy7b2f8b82012-03-19 17:18:54 -0700575
Mike Reedc70e06b2009-04-24 11:09:12 -0400576 Asset* asset = reinterpret_cast<Asset*>(native_asset);
Leon Scroggins III0aa39dc2014-06-03 12:19:32 -0400577 // since we know we'll be done with the asset when we return, we can
578 // just use a simple wrapper
Matt Sarettb8adc9a2015-12-02 13:35:22 -0500579 std::unique_ptr<AssetStreamAdaptor> stream(new AssetStreamAdaptor(asset));
580 return doDecode(env, stream.release(), padding, options);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800581}
582
583static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
Ashok Bhat36bef0b2014-01-20 20:08:01 +0000584 jint offset, jint length, jobject options) {
Romain Guy7b2f8b82012-03-19 17:18:54 -0700585
Mike Reedc70e06b2009-04-24 11:09:12 -0400586 AutoJavaByteArray ar(env, byteArray);
Matt Sarettb8adc9a2015-12-02 13:35:22 -0500587 std::unique_ptr<SkMemoryStream> stream(new SkMemoryStream(ar.ptr() + offset, length, false));
588 return doDecode(env, stream.release(), NULL, options);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800589}
590
Owen Lina9d0d472011-01-18 17:39:15 +0800591static jboolean nativeIsSeekable(JNIEnv* env, jobject, jobject fileDescriptor) {
Elliott Hughesa3804cf2011-04-11 16:50:19 -0700592 jint descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor);
Yujie Qinc1d7b7f2016-02-29 14:00:29 +0100593 return isSeekable(descriptor) ? JNI_TRUE : JNI_FALSE;
Owen Lina9d0d472011-01-18 17:39:15 +0800594}
595
John Reck41478772015-04-10 13:35:27 -0700596jobject decodeBitmap(JNIEnv* env, void* data, size_t size) {
Ben Wagner60126ef2015-08-07 12:13:48 -0400597 SkMemoryStream stream(data, size);
John Reck41478772015-04-10 13:35:27 -0700598 return doDecode(env, &stream, NULL, NULL);
599}
600
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800601///////////////////////////////////////////////////////////////////////////////
602
Daniel Micay76f6a862015-09-19 17:31:01 -0400603static const JNINativeMethod gMethods[] = {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800604 { "nativeDecodeStream",
605 "(Ljava/io/InputStream;[BLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
606 (void*)nativeDecodeStream
607 },
608
609 { "nativeDecodeFileDescriptor",
610 "(Ljava/io/FileDescriptor;Landroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
611 (void*)nativeDecodeFileDescriptor
612 },
613
614 { "nativeDecodeAsset",
Ashok Bhat36bef0b2014-01-20 20:08:01 +0000615 "(JLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800616 (void*)nativeDecodeAsset
617 },
618
619 { "nativeDecodeByteArray",
620 "([BIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
621 (void*)nativeDecodeByteArray
622 },
623
Owen Lina9d0d472011-01-18 17:39:15 +0800624 { "nativeIsSeekable",
625 "(Ljava/io/FileDescriptor;)Z",
626 (void*)nativeIsSeekable
627 },
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800628};
629
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800630int register_android_graphics_BitmapFactory(JNIEnv* env) {
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800631 jclass options_class = FindClassOrDie(env, "android/graphics/BitmapFactory$Options");
632 gOptions_bitmapFieldID = GetFieldIDOrDie(env, options_class, "inBitmap",
Chris Craik47cd8e92014-07-08 17:13:08 -0700633 "Landroid/graphics/Bitmap;");
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800634 gOptions_justBoundsFieldID = GetFieldIDOrDie(env, options_class, "inJustDecodeBounds", "Z");
635 gOptions_sampleSizeFieldID = GetFieldIDOrDie(env, options_class, "inSampleSize", "I");
636 gOptions_configFieldID = GetFieldIDOrDie(env, options_class, "inPreferredConfig",
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800637 "Landroid/graphics/Bitmap$Config;");
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800638 gOptions_premultipliedFieldID = GetFieldIDOrDie(env, options_class, "inPremultiplied", "Z");
639 gOptions_mutableFieldID = GetFieldIDOrDie(env, options_class, "inMutable", "Z");
640 gOptions_ditherFieldID = GetFieldIDOrDie(env, options_class, "inDither", "Z");
641 gOptions_preferQualityOverSpeedFieldID = GetFieldIDOrDie(env, options_class,
Wei-Ta Chen953f9092010-12-03 14:06:18 -0800642 "inPreferQualityOverSpeed", "Z");
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800643 gOptions_scaledFieldID = GetFieldIDOrDie(env, options_class, "inScaled", "Z");
644 gOptions_densityFieldID = GetFieldIDOrDie(env, options_class, "inDensity", "I");
645 gOptions_screenDensityFieldID = GetFieldIDOrDie(env, options_class, "inScreenDensity", "I");
646 gOptions_targetDensityFieldID = GetFieldIDOrDie(env, options_class, "inTargetDensity", "I");
647 gOptions_widthFieldID = GetFieldIDOrDie(env, options_class, "outWidth", "I");
648 gOptions_heightFieldID = GetFieldIDOrDie(env, options_class, "outHeight", "I");
649 gOptions_mimeFieldID = GetFieldIDOrDie(env, options_class, "outMimeType", "Ljava/lang/String;");
650 gOptions_mCancelID = GetFieldIDOrDie(env, options_class, "mCancel", "Z");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800651
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800652 jclass bitmap_class = FindClassOrDie(env, "android/graphics/Bitmap");
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800653 gBitmap_ninePatchInsetsFieldID = GetFieldIDOrDie(env, bitmap_class, "mNinePatchInsets",
Chris Craik47cd8e92014-07-08 17:13:08 -0700654 "Landroid/graphics/NinePatch$InsetStruct;");
655
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800656 gInsetStruct_class = MakeGlobalRefOrDie(env, FindClassOrDie(env,
657 "android/graphics/NinePatch$InsetStruct"));
658 gInsetStruct_constructorMethodID = GetMethodIDOrDie(env, gInsetStruct_class, "<init>",
659 "(IIIIIIIIFIF)V");
Chris Craik47cd8e92014-07-08 17:13:08 -0700660
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800661 return android::RegisterMethodsOrDie(env, "android/graphics/BitmapFactory",
662 gMethods, NELEM(gMethods));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800663}