屏幕密度掺入BitmapFactory里decodeFile与decodeResource的差异
http://blog.csdn.net/sevensundark/article/details/7616450
最近在项目中遇到的问题,一种是放在drawable下的资源文件,一种为下载后复制到data/data目录下的文件,同样的分辨率,放入相同layout配置的ImageView中,显示的大小却不一样。查看代码后,逻辑并无不同的地方,唯一的区别是:读取图片文件资源的时候,读drawable下用的decodeResource方法,读data/data下用的decodeFile方法。用分辨率320px*569px的手机执行的效果是,decodeResource方法读出的图片比decodeFile大一点,debug出的为放大了1.5倍。就现象而言,decodeResource方法自己似乎做了scale处理。
于是翻看源码,得到执行两种方法时的调用次序:
decodeFile(String pathName) ->decodeFile(pathName, null) -> decodeStream(stream, null, opts)
decodeResource(res, id) -> decodeResource(res, id, null) -> decodeResourceStream(res, value, is, null, opts) -> decodeStream(is, pad, opts)
对比各自处理,不同点发生在decodeResource会调用的decodeResourceStream方法中
- /**
- * Decode a new Bitmap from an InputStream. This InputStream was obtained from
- * resources, which we pass to be able to scale the bitmap accordingly.
- */
- 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;
- if (density == TypedValue.DENSITY_DEFAULT) {
- opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
- } else if (density != TypedValue.DENSITY_NONE) {
- opts.inDensity = density;
- }
- }
- if (opts.inTargetDensity == 0 && res != null) {
- opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
- }
- return decodeStream(is, pad, opts);
- }
这段逻辑,比decodeFile方法多了个Options属性值的设置,自己手头手机的实验结果来看,
opts.inDensity设成了DisplayMetrics.DENSITY_DEFAULT 为160
opts.inTargetDensity设成res.getDisplayMetrics().densityDpi 为240
就字面意思,inTargetDensity正好是inDensity的1.5倍,查了SDK如下
opts.inDensity的解释
- The pixel density to use for the bitmap. This will always result in the returned bitmap having a density set for it (see Bitmap.setDensity(int)).
- In addition, if inScaled is set (which it is by default} and this density does not match inTargetDensity, then the bitmap will be scaled to the target
- density before being returned.
opts.inTargetDensity的解释
- The pixel density of the destination this bitmap will be drawn to. This is used in conjunction with inDensity and inScaled to determine if and how to
- scale the bitmap before returning it.
总之
inDensity——用于位图的像素压缩比
inTargetDensity——用于目标位图的像素压缩比(要生成的位图)
inScaled——设置为true时进行图片压缩,从inDensity到inTargetDensity。
由上可知,decodeResourceStream方法根据手机屏幕的密度有一个缩放图片的过程,而decodeFile不会自动处理。为了避开这种差异性,将decodeFile方法的调用替换掉,先得到图片文件的InputStream,再直接调用decodeResourceStream,模拟decodeResource的操作。
其实,decodeResource中对图片文件的缩放可以理解成图片像素px与屏幕dip显示的转换,可为什么decodeFile没有做这一步?暂时不知道原因,可能是decodeResource多用于drawable下本app资源的读取,涉及到不同屏幕密度的区分吧。
另外关于DisplayMetrics的屏幕密度Density,官方的解释:
The logical density of the display. This is a scaling factor for the Density Independent Pixel unit, where one DIP is one pixel on an approximately 160 dpi screen (for example a 240x320, 1.5"x2" screen), providing the baseline of the system's display. Thus on a 160dpi screen this density value will be 1; on a 120 dpi screen it would be .75; etc.
This value does not exactly follow the real screen size (as given by xdpi
andydpi
, but rather is used to scale the size of the overall UI in steps based on gross changes in the display dpi. For example, a 240x320 screen will have a density of 1 even if its width is 1.8", 1.3", etc. However, if the screen resolution is increased to 320x480 but the screen size remained 1.5"x2" then the density would be increased (probably to 1.5).
上面一段不是太明白,自己的理解:屏幕密度与手机硬件有关,而这个Density是一个倍率值,相同的分辨率屏幕尺寸越小,密度就越大,dip与密度有关系,官方解释也说了“在160dip的屏幕上,1dip=1px”。它常用于程序中dip,px之间的转换
- public static int px2dip(Context context, float pxValue){
- final float scale = context.getResources().getDisplayMetrics().density;
- return (int)(pxValue / scale + 0.5f);
- }
- public static int dip2px(Context context, float dipValue){
- final float scale = context.getResources().getDisplayMetrics().density;
- return (int)(dipValue * scale + 0.5f);
- }
关于dip,px,屏幕密度的理解,可结合下面的内容理解(注:以下内容均转载自 http://blog.csdn.net/cgaanns/article/details/6180618)
- px(pixels)——像素:不同的设备显示效果相同,一般我们HVGA代表320x480像素,这个用的比较多。
- dip(device independent pixels)——设备独立像素:这个和设备硬件有关,一般哦我们为了支持WCGA、HVGA和QVGA推荐使用这个,不依赖于像素。等同于dp。
- sp(scaled pixels—best for text size)——带比例的像素。
- pt(points)——磅:1pt = 1/72英寸
- in(inches)——英寸
- mm(millimeters)——毫米
- sp由于是放大像素,主要是用于字体显示,由此根据google的建议,TextView的字体大小最好用sp做单位,而且查看TextView的源码可知Android默认使用水平作为字号单位。
- 在Android中最常用到的还是px和dip。但是这两个之间到底有什么区别呢?
- 在HVGA屏density=160;QVGA屏density=120;WVGA屏density=240;WQVGA屏density=120 density值表示每英寸有多少个显示点,与分辨率是两个概念。
- 不同density下屏幕分辨率信息,以480dip*800dip的 WVGA(density=240)为例。
- density=120时 屏幕实际分辨率为240px*400px (两个点对应一个分辨率)
- 状态栏和标题栏高各19px或者25dip
- 横屏是屏幕宽度400px 或者800dip,工作区域高度211px或者480dip
- 竖屏时屏幕宽度240px或者480dip,工作区域高度381px或者775dip
- density=160时 屏幕实际分辨率为320px*533px (3个点对应两个分辨率)
- 状态栏和标题栏高个25px或者25dip
- 横屏是屏幕宽度533px 或者800dip,工作区域高度295px或者480dip
- 竖屏时屏幕宽度320px或者480dip,工作区域高度508px或者775dip
- density=240时 屏幕实际分辨率为480px*800px (一个点对于一个分辨率)
- 状态栏和标题栏高个38px或者25dip
- 横屏是屏幕宽度800px 或者800dip,工作区域高度442px或者480dip
- 竖屏时屏幕宽度480px或者480dip,工作区域高度762px或者775dip
- apk的资源包中,当屏幕density=240时使用hdpi 标签的资源
- 当屏幕density=160时,使用mdpi标签的资源
- 当屏幕density=120时,使用ldpi标签的资源。
- 不加任何标签的资源是各种分辨率情况下共用的。
BitmapFactory.decodeResource加载图片缩小的原因及解决方法
http://www.cnblogs.com/LuoYer/archive/2011/01/06/1929098.html
声明:我是以2.0的代码为参考的,主要参考了BitmapFactory.java文件。
首先,在2.0应用中,res下有drawable-hdpi、drawable-mdpi、drawable-ldpi三个存放图片的文件夹,查资料看到如下描述:
这是分辨率的不同,H是高分辨率 M是中 L是低。
drawable- hdpi、drawable- mdpi、drawable-ldpi的区别:
(1)drawable-hdpi里面存放高分辨率的图片,如WVGA (480x800),FWVGA (480x854)
(2)drawable-mdpi里面存放中等分辨率的图片,如HVGA (320x480)
(3)drawable-ldpi里面存放低分辨率的图片,如QVGA (240x320)
开始不太理解,所以,看完代码后,先做了个实验,在三个文件夹下分别放入图片,通过下面的测试代码:
private
int
getTargetDensityByResource(Resources resources,
int
id) {
TypedValue value =
new
TypedValue();
resources.openRawResource(id, value);
Log.d(
"LuoYer"
,
"value.density: "
+ value.density);
return
value.density;
}
|
分别调用三个文件夹中的资源,打印分别为:240、160、120.
为什么看这个值呢?先看看我们调用的decodeResource方法在BitmapFactory.java中的实现:
public
static
Bitmap decodeResource(Resources res,
int
id, Options opts) {
Bitmap bm =
null
;
InputStream is =
null
;
try
{
final
TypedValue value =
new
TypedValue();
is = res.openRawResource(id, value);
bm = <strong>decodeResourceStream</strong>(res, value, is,
null
, opts);
}
catch
(Exception e) {
}
finally
{
try
{
if
(is !=
null
) is.close();
}
catch
(IOException e) {}
}
return
bm;
}
|
接着看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;
if
(density == TypedValue.DENSITY_DEFAULT) {
opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
}
else
if
(density != TypedValue.DENSITY_NONE) {
opts.inDensity = density;
}
}
if
(opts.inTargetDensity ==
0
&& res !=
null
) {
opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
}
return
decodeStream(is, pad, opts);
}
|
里面用到了value的density值来判断opts的inDensity的设置。
所以说,当我们从三个文件夹中获取资源的时候opts.inDensity的值分别会被设置成240、160、和120.
decodeResourceStream方法在对opts.inDensity设置之后,又进行了opts.inTargetDensity的设置,当其值为0的时候,会对其赋值。
如果,在测试函数中加入Log.d("LuoYer", "densityDpi: " + resources.getDisplayMetrics().densityDpi);在我的板子上会打印值160.
那么,opts的inDensity和inTargetDensity 对解析图片有什么关系呢?
通过decodeStream方法,最后会调用到finishDecode方法(此处仅列出计算示意,详细代码请查看BitmapFactory.java),其中,有在创建返回图片时设置缩放比例的计算:
final
int
density = opts.inDensity;
final
int
targetDensity = opts.inTargetDensity;
float
scale = targetDensity / (
float
)density;
|
最后的scale,就是缩放比例了,所以说,如果我们把图片资源放在了drawable-hdpi中,opts.inDensity的值为240,
而opts.inTargetDensity为0的情况下,会被设置为160. 这样,返回的图片就会按2/3(160/240)的比例被缩放了。
而在drawable-mdpi中的图片,就不会被缩小。
当然,这也是以resources.getDisplayMetrics().densityDpi的值为基础的。
==============================================================================
原因已经清楚了,那么,怎样解决呢?
有看到说:把图片放到drawable-mdpi中就可以了。 当然,在我前面叙述的情况下是可以的,但如果resources.getDisplayMetrics().densityDpi的值变化了,还会产生缩放的情况。
由于最后的图片创建用到了scale,那么,我们只需要保持density和targetDensity的一致,就可以避免缩放了,所以,我封装了一个解析函数:
private
Bitmap decodeResource(Resources resources,
int
id) {
TypedValue value =
new
TypedValue();
resources.openRawResource(id, value);
BitmapFactory.Options opts =
new
BitmapFactory.Options();
opts.inTargetDensity = value.density;
return
BitmapFactory.decodeResource(resources, id, opts);
}
|
这样,无论图片放在哪个文件夹里,都可以不必担心会被缩放了。
1
|
<em>第一次写博客,还有点不习惯,如有疑问,请及时提出,以便修改、完善。代码详情,请看
2.0
中BitmapFactory.java源码。</em>
|
1
|
<em>转载,请注明</em><a href=
"http://www.cnblogs.com/LuoYer/archive/2011/01/06/1929098.html"
target=
"_blank"
><em>http://www.cnblogs.com/LuoYer/archive/
2011
/
01
/
06
/
1929098
.html</em></a>
|