最近在使用BitmapFactory对图片进行解码时,发现解码后的Bitamp的分辨率与原来图片的分辨率不一致。原图分辨率为1920*1080,解码后的Bitmap分辨率竟然为3640*2160,宽和高都为原来图片的两倍。这与预想的完全不一样,于是决定跟踪源码。
使用的解码方法为:
public static Bitmap decodeResource(Resources res, int id, Options opts);
try { final TypedValue value = new TypedValue(); is = res.openRawResource(id, value); bm = decodeResourceStream(res, value, is, null, opts); } catch (Exception e) { /* do nothing. If the exception happened on open, bm will be null. If it happened on close, bm is still valid. */ } finally { try { if (is != null) is.close(); } catch (IOException e) { // Ignore } }
public static Bitmap decodeResourceStream(Resources res, TypedValue value, InputStream is, Rect pad, Options opts) { if (opts == null) { opts = new Options(); } if (opts.inDensity == 0 && value != null) { final int density = value.density; <span style="color:#ff0000;">if (density == TypedValue.DENSITY_DEFAULT) { opts.inDensity = DisplayMetrics.DENSITY_DEFAULT; } else if (density != TypedValue.DENSITY_NONE) { opts.inDensity = density; }</span> } <span style="color:#ff0000;">if (opts.inTargetDensity == 0 && res != null) { opts.inTargetDensity = res.getDisplayMetrics().densityDpi; }</span> return decodeStream(is, pad, opts); }
然后查看Reference,发现了如下解释:
When this flag is set, if inDensity
and inTargetDensity
are not 0, the bitmap will be scaled to match inTargetDensity
when loaded, rather than relying on the graphics system scaling it each time it is drawn to a Canvas.
说明很清楚,这两个变量是控制Bitmap进行缩放的。如果isScaled为true,并且inDensity和inTargetDensity都不为0,那么Bitmap就会被缩放,以适应inTargetDensity。
但是,这里并没有说明是如何进行缩放的。
继续往下看源码,decodeResourceStream方法内部继续调用decodeStream方法。进入decodeStream方法,发现其最终调用了native方法:
if (is instanceof AssetManager.AssetInputStream) { final int asset = ((AssetManager.AssetInputStream) is).getAssetInt(); bm = nativeDecodeAsset(asset, outPadding, opts); } else { bm = decodeStreamInternal(is, outPadding, opts); }
代码太多,挑重点看。
BitmapFactory.cpp:265行
if (env->GetBooleanField(options, gOptions_scaledFieldID)) { const int density = env->GetIntField(options, gOptions_densityFieldID); const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID); const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID); if (density != 0 && targetDensity != 0 && density != screenDensity) { <span style="color:#ff0000;">scale = (float) targetDensity / density;</span> } }
继续往下看,BitmapFactory:346行:
int scaledWidth = decodingBitmap.width(); int scaledHeight = decodingBitmap.height(); if (willScale && mode != SkImageDecoder::kDecodeBounds_Mode) { scaledWidth = int(scaledWidth * scale + 0.5f); scaledHeight = int(scaledHeight * scale + 0.5f); }
最后再对Bitmap进行缩放:
if (willScale) { // This is weird so let me explain: we could use the scale parameter // directly, but for historical reasons this is how the corresponding // Dalvik code has always behaved. We simply recreate the behavior here. // The result is slightly different from simply using scale because of // the 0.5f rounding bias applied when computing the target image size <span style="color:#ff0000;">const float sx = scaledWidth / float(decodingBitmap.width()); const float sy = scaledHeight / float(decodingBitmap.height());</span> // TODO: avoid copying when scaled size equals decodingBitmap size SkBitmap::Config config = configForScaledOutput(decodingBitmap.config()); // FIXME: If the alphaType is kUnpremul and the image has alpha, the // colors may not be correct, since Skia does not yet support drawing // to/from unpremultiplied bitmaps. outputBitmap->setConfig(config, scaledWidth, scaledHeight, 0, decodingBitmap.alphaType()); if (!outputBitmap->allocPixels(outputAllocator, NULL)) { return nullObjectReturn("allocation failed for scaled bitmap"); } // If outputBitmap's pixels are newly allocated by Java, there is no need // to erase to 0, since the pixels were initialized to 0. if (outputAllocator != &javaAllocator) { outputBitmap->eraseColor(0); } SkPaint paint; paint.setFilterLevel(SkPaint::kLow_FilterLevel); <span style="color:#ff0000;">SkCanvas canvas(*outputBitmap); canvas.scale(sx, sy); canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);</span> }
整个调用流程如下: