Android图片解码分辨率问题

最近在使用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
	}
}

可以看到,其主要是调用了decodeResourceStream这个方法,继续进入decodeResourceStream方法:

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);
}

在这个方法的内部,对BitmapFactory.Options对象的参数进行设置,主要是inDensity和inTargetDensity。

然后查看Reference,发现了如下解释:

public boolean inScaled

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);
}

这里的native方法是在platform\frameworks\base\core\jni\android\graphics\BitmapFactory.cpp文件中定义的,并且这两个方法最终都调用了doDecode方法。


代码太多,挑重点看。

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>
	}
}


此处计算scale,其值为targetDensity与density的比。

继续往下看,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);
}

此处根据上面计算的scale,计算缩放后的宽和高scaledWidth和scaledHeight。

最后再对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>
}

现在已经很明显,最终是根据缩放厚哦的scaledWidth和scaledHeight以及原图大小,计算缩放比sx和sy,然后通过canvas来对Bitmap进行缩放。

整个调用流程如下:



你可能感兴趣的:(Android开发)