Android 位图显示及位图模式的一些总结

 前言:最近项目在一些2.3.3或者2.3.4手机上总是崩溃(如摩托罗拉里程碑1),究其原因发现各种报“OOM”,通过分析内存增长方式发现,在“里程碑1”这款手机上时“APP的总内存”并不是随着“实际使用内存”的增长而增长。而把项目里使用的图片改成RGB_565模式就可以实现“内存自增长”。这是为什么呢?经分析发现RGB_565可以直接使用SDK的一些内存优化算法,“里程碑1”之所以换成RGB_565模式后能实现“内存自增长”就是因为SDK调用了内存优化算法。不用“RGB_565”为什么内存不能“自增长”呢?因为:SDK2.2及以前默认图片模式是RGB_565,从2.3.3开始默认改成了“ARGB_8888”但是图片处理代码是扩展的2.2.2的GC没有写好所以才会“内存不自增长”。

 

现在把改这问题中查到的对我有用的资料罗列如下:

一、android.graphics.Bitmap.Config是一个枚举类型,里面定义了位图的四种格式.起初有点奇怪,网上查了一下,大概指定了类型名字怪怪的缘由.
    从名字中可以看出:
    ALPHA_8:数字为8,图形参数应该由一个字节来表示,应该是一种8位的位图
    ARGB_4444:4+4+4+4=16,图形的参数应该由两个字节来表示,应该是一种16位的位图.
    ARGB_8888:8+8+8+8=32,图形的参数应该由四个字节来表示,应该是一种32位的位图.
    RGB_565:5+6+5=16,图形的参数应该由两个字节来表示,应该是一种16位的位图.
 
    网上讲,ALPHA_8,ARGB_4444,ARGB_8888都是透明的位图,也就是所字母A代表透明.
    ARGB_4444:意味着有四个参数,即A,R,G,B,每一个参数由4bit表示.
    同理:
    ARGB_8888:意味着有四个参数,即A,R,G,B,每一个参数由8bit来表示.
    同理:
    RGB_565:意味着有三个参数,R,G,B,三个参数分别占5bit,6bit,5bit.

    加一句:貌似Android要求图片代销必须是4的倍数与这些有关。

 

 

二、

1. 对图片本身进行操作

尽量不要使用 setImageBitmap、setImageResource、 BitmapFactory.decodeResource 来设置一张大图,因为这些方法在完成 decode 后,最终都是通过 Java 层的 createBitmap 来完成的,需要消耗更多内存。因此,改用先通过 BitmapFactory.decodeStream 方法,创建出一个 bitmap,再将其设为 ImageView 的 source,decodeStream 最大的秘密在于其直接调用 JNI>>nativeDecodeAsset() 来完成 decode,无需再使用 Java 层的 createBitmap,从而节省了 Java 层的空间。如果在读取时加上图片的 Config 参数,可以更有效的减少加载的内存,从而更有效阻止抛出内存异常。另外,decodeStream 直接拿图片来读取字节码了,不会根据机器的各种分辨率来自动适应,使用了 decodeStream 之后,需要在 hdpi 和 mdpi,ldpi 中配置相应的图片资源, 否则在不同分辨率机器上都是同样大小(像素点数量),显示出来的大小就不对了。


复制代码 代码如下:
InputStream is = this.getResources().openRawResource(R.drawable.pic);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inSampleSize = 2;
Bitmap btp =BitmapFactory.decodeStream(is,null,options);

以上代码即是读取 drawable 下名为 pic 图片的缩略图,长度、宽度都只有原图片的 1/2。图片大小减少,占用的内存自然也变小了。这么做的弊端是图片质量变差,inSampleSize 的值越大,图片的质量就越差。由于各手机厂商缩放图片的算法不同,在不同手机上的缩放图片质量可能会不同。

2. 调用图片的 recycle() 方法


复制代码 代码如下:
if(!bmp.isRecycle() ){
   bmp.recycle()    //回收图片所占的内存
   system.gc()      //提醒系统及时回收
}

这种方法其实不是真正降低图片内存的方法。主要目的是标记图片对象,方便回收图片对象的本地数据。图片对象的本地数据占用的内存最大,而且与程序 Java 部分的内存是分开计算的。所以经常出现 Java heap 足够使用,而图片发生 OutOfMemoryError 的情况。在图片不使用时调用该方法,可以有效降低图片本地数据的峰值,从而减少 OutOfMemoryError 的概率。不过调用了 recycle() 的图片对象处于“废弃”状态,调用时会造成程序错误。所以在无法保证该图片对象绝对不会被再次调用的情况下,不建议使用该方法。特别要注意已经用 setImageBitmap(Bitmap
img) 方法分配给控件的图片对象,可能会被系统类库调用,造成程序错误。

 

3. 以最省内存的方式读取本地资源的图片


复制代码 代码如下:

public static Bitmap readBitMap(Context context, int resId){ 
   BitmapFactory.Options opt = new BitmapFactory.Options(); 
   opt.inPreferredConfig = Bitmap.Config.RGB_565; 
   opt.inPurgeable = true; 
   opt.inInputShareable = true; 
   // 获取资源图片 
   InputStream is = context.getResources().openRawResource(resId); 
   return BitmapFactory.decodeStream(is,null,opt); 
}


Android 中加载图片的颜色模式有四种,分别是:ALPHA_8:每个像素占用 1byte 内存、ARGB_4444:每个像素占用 2byte 内存、ARGB_8888:每个像素占用 4byte 内存、RGB_565:每个像素占用 2byte 内存。Android默认的颜色模式为ARGB_8888,这个颜色模式色彩最细腻,显示质量最高。但同样的,占用的内存也最大。以上代码即是将图片资源以 RGB_565 (或以 ARGB_4444)模式读出。内存减少虽然不如第一种方法明显,但是对于大多数图片,看不出与 ARGB_8888 模式有什么差别。不过在读取有渐变效果的图片时,可能有颜色条出现。另外,会影响图片的特效处理。

 

4. 使用 Matrix 对象放大的图片如何更改颜色模式:

虽然使用 Matrix 对象放大图片,必定会耗费更多的内存,但有时候也不得不这样做。放大后的图片使用的 ARGB_8888 颜色模式,就算原图片是ARGB_4444 颜色模式也一样,而且没有办法在放大时直接指定颜色模式。可以采用以下办法更改图片颜色模式。


复制代码 代码如下:
Matrix matrix = new Matrix();
float newWidth = 200;       // 图片放大后的宽度
float newHeight = 300;      // 图片放大后的长度
matrix.postScale(newWidth / img.getWidth(), newHeight/ img.getHeight());
Bitmap img1 = Bitmap.createBitmap(img, 0, 0, img.getWidth(), img.getHeight(), matrix, true);// 得到放大图片
img2 = img1.copy(Bitmap.Config.ARGB_4444, false);                           // 得到 ARGB_4444 颜色模式的图片
img = null;
img1 = null;


这里比起本来的图片额外生成了一个图片对象 img1。然则体系会主动收受接管 img1,所以实际内存还是削减了。

归结起来还是以缩略图模式读取图片和削减图片中每个像素占用的内存最为有效。 这两种办法固然有效,然则也有各自的弊病。实际开辟中还是应当按照景象酌情应用。最王道的办法,还是避免垃圾对象的产生。例如在 ListView 的应用中,复用 convertView 等。若是应用 AsyncTask 加载图片,要及时将引用的 ImageView 对象置为 null。因为 AsyncTask 是用线程池实现的,所以此中引用的对象可能会拥有很长的生命周期,造成 GC 无法开释。我还是信赖 Android 的内存收受接管机制的,recycle 什么的固然必然程度上有效,但总感觉不合适 Java 内存收受接管的原则。

详细出处参考:http://www.jb51.net/article/34761.htm

 

三、

Android系统支持常见的图片资源格式,如jpg、png、gif等。这几种主要图片格式的特点如下:

1.    Gif格式图片的特点

透明性:Gif是一种布尔透明类型,既它可以是全透明,也可以是全不透明,但是它并没有半透明(alpha 透明)。

动画:Gif这种格式支持动画。

无损耗性:Gif是一种无损耗的图像格式,这也意味着你可以对gif图片做任何操作也不会使  得图像质量产生损耗。

水平扫描:Gif是使用了一种叫作LZW的算法进行压缩的,当压缩gif的过程中,像素是由上到下水平压缩的,这也意味着同等条件下,横向的gif图片比竖向 的gif图片更加小。例如500*10的图片比10*500的图片更加小。

间隔渐进显示:Gif支持可选择性的间隔渐进显示

由以上特点看出只有256种颜色的gif图片不适合照片,但它适合对颜色要求不高的图形(比如说图标,图表等),它并不是最优的选择,我们会在后面中看到png是最优的选择。

2.    Jpeg格式图片特点

透明性、动画:它并不支持透明,也不支持动画。

    损耗性:除了一些比如说旋转(仅仅是90、180、270度旋转),裁切,从标准类型到先进类型,编辑图片的原数据之外,所有其它操作对jpeg图像的处理 都会使得它的质量损失。所以我们在编辑过程一般用png作为过渡格式。

      隔行渐进显示:它支持隔行渐进显示(但是ie浏览器并不支持这个属性,但是ie会在整个图像信息完全到达的时候显示)。
         由上可以看出Jpeg是最适web上面的摄影图片和数字照相机中(android壁纸默认就是jpg的)。

3.    Png格式图片分析

         Png这种图片格式包括了许多子类,但是在实践中大致可以分为256色的png(PNG8)和全色的png(PNG24、 PNG32),你完成可以用256色的png代替gif,用全色的png代替jpeg

      透明性:Png是完全支持alpha透明的(透明,半透明,不透明)。

      动画:它不支持动画

      无损耗性:png是一种无损耗的图像格式,这也意味着你可以对png图片做任何操作也不会使  得图像质量产生损耗。这也使得png可以作为jpeg编辑的过渡格式水平扫描像GIF一样,png也是水平扫描的,这样意味着水平重复颜色比垂直重复颜色的图片更小。

      间隔渐进显示:它支持间隔渐进显示,但是会造成图片大小变得更大

      其它图片格式与PNG比较:众所周知GIF适合图形,JPEG适合照片,PNG系列两种都适合。

A.       相比GIF:PNG 8除了不支持动画外,PNG8有GIF所有的特点,但是比GIF更加具有优势的是它支持alpha透明和更优的压缩。所以,大多数情况下,你都应该用 PNG8不是GIF(除了非常小的图片GIF会有更好的压缩外)。

B.       相比JPEG:JPEG比全色PNG具有更加好的压缩,因此也使得JPEG适合照片,但是编辑JPEG过程中容易造成质量的损失,所以全色PNG 适合作为编辑JPEG的过渡格式。

 

Photoshop制图几个概念:

1.    位深&通道

         在photoshop里,点击图像,模式然后可以选择制图的通道位深。8位、16位、32位有啥区别?和我们常说的24、32位图像是一个概念么?

A.       先说第一个,这里位表示的是红绿蓝三基色分别有2的N(8、16、32)次方个强度。位数越宽,那么它能表示的颜色也就越多,越真实。当然图片的容量就会越大,对图片处理起来需要的时间就越长。还有一个就是所有photoshop内置滤镜都支持8位通道,16、32位通道有的滤镜是不支持的,尤其是第三方外挂的。

B.       第二问的答案当然是否定的。我们可以用三基色来组成任意一种特定的颜色。Photoshop里面的位深指的是基色,我们常说的32位图像指的是特定的一种颜色。我们常见的8位通道RGB图像,3个通道共24位,即一张24位RGB图像里可表现大约1670万种颜色。ARGB就是在原来真彩色的基础上加一个alpha通道,使图像具备可透明性,这样就组成了我们常说的32位图。至于16、32位深一般只在专业领域可能会用到,原理一样,需要了解的,自己算下就ok;

2.    加噪点

         这是一种很凑合的做法,原理就是24位带渐变的真彩图以16位的增强色显示就会出现一圈圈光晕一样的轮廓失真。加入微小噪点,降低图片的清晰度,反而会让光晕失真均衡,从而感官上有一定改善。操作就是点滤镜,杂色,添加杂色,数值最好不超过1.5.

3.    储存为web和设备使用格式

         Photoshop里面专门对web和移动设备进行了相关的适配兼容,原因是像nokia大多的功能机都只支持增强色,所以在这个模式下可以输出相适应的图片资源。Android作为最优秀的移动系统之一,当然完全支持最高的规格,但不排除做一款定位低端,为了节约成本,把设备做成增强色。

 

四、这是一篇译文,原文地址:http://www.cnblogs.com/Dahaka/archive/2012/03/03/2374799.html

【译】Android位图颜色模式的问题

  最近开始了android上的编程之旅,在了解2D图形编程时,令人蛋疼的发觉android上仅支持ARGB8888、ARGB4444、RGB565以及Alpha 8这么几种颜色模式,而不支持RGB888这种格式。原本以为即使不支持RGB888我用ARGB8888总行吧,但后来了解到,即使我在内存中用ARGB888颜色模型表示图像,在该图像拷贝到屏幕帧缓冲区的过程中,它也会变成RGB565颜色模式。我们知道,RGB565最多只能表示2^16=65536种图像,这对于RGB888所能表示的2^24=16777216种颜色来说显然在表现力上要略逊一筹。这集中表现在显示某些带有渐变效果的图片时,出现了一条条的颜色带,而不是原始的平滑的渐变效果。后来得知android使用了Dither(抖动)这种技术,以欺骗人类眼球的方式加以补偿。

  当然,以这个问题为出发点,后来又引发了诸多问题,而这篇文章解决了我不少问题,特在这里翻译出来供大家分享之。

======

  在学习AvoidXfermode类的时候,我遇到了图片颜色不能正确显示的问题。我画了一个平滑渐变的色谱图片,然后用AvoidXfermode的Target功能将替换该图片里一种颜色替换为另外的颜色。但问题是,我想要替换的颜色并没有按照我所预想的一样被AvoidXfermode替换掉。原来,我的PNG图片被自动从24位的RGB888颜色模式转换为了16位的RGB565颜色模式,这使得图片中颜色的原始数值被改变了。

  现在市面上几乎所有的设备都是16位色的屏幕,这意味着不管你在代码里使用什么颜色格式的图片,在某个时候它们都会被转化为16位图片,这样它们才能被显示在屏幕上。这种转换会占用处理器资源,会以时间和电池寿命的形式给用户造成损失。所以,我们并不期望这种转换在绘图时会发生,相反的,我们希望它还要在尽可能早的阶段里被完成。Android是一个智能的系统,在有些情况下Android会自动帮你完成这些转换工作,所以你并不需要担心这些问题。在大部分情况下,这个特性是相当棒的,它大大减少了开发者的工作量,也减少你的资源大小同时节省了处理器时间和电池寿命。但是,如果你希望在程序里将图片以一种指定的颜色格式处理,这种自动转化机制会让你头大的。幸运的是,我们有办法阻止自动转换的发生,让你的图片以你期望的颜色格式储存。

  不过,既然这种自动转换既节省内存和处理器时间又保护电池寿命,那么为什么有人会不希望这种转换发生呢?Android程序中大部分的图片都只需要简单的载入并显示就行了。但是在一些教程以及我之前提到的例子中,我们需要在图片显示之前对它们做一些操作。为了得到最佳的效果,我们希望载入的图片在处理时尽能可能的保持最好的质量,因为在处理过程中过早的降低质量将对最终效果造成不良影响。

  好了,这就是说在图片处理好之前,我们不希望被Android横插一脚。但是,Android系统将在什么时候替我们执行这些转换呢?答案是转换将在3个地方发生:

  • 编译时图片资源被编译到软件包里去时
  • 当图片被从资源中载入为Bitmap时
  • 当图片被绘制时

  我们将看看这三类情况,并且了解如何避免这些转换。

程序编译时

  当你在项目“res/drawable”文件夹下放置图片的时候,意味着你告诉Android:如果需要的话,在构建程序的时候将图片转换为16位图片。该转换发生的必要条件有哪些?无论你图片的原始格式是什么,如果你的图片没有alpha通道,在你的软件构建的时候Android会将它转换为本地16位色图。你有两种方法组织转换发生:

  • 给图片添加alpha通道
  • 将图片放置到“res/raw”目录下而不是“res/drawable”

  通过给图片添加alpha通道,Android将不会尝试将图片转换为16位色图,这是因为RGB565颜色模式不带alpha通道。将一张带有alpha通道的图片转换为RGB565颜色格式会使半透明信息丢失,所以任何带有alpha通道的图片将被储存为32位的ARGB8888图片资源。通过将图片放置到“res/raw”目录下,我们告诉Android这个资源包含原始数据,它不应该在构建时更改。所以,对于我们放置到该目录下的任何图片都不会发生自动转换。但不幸的是,如果我们放在raw目录下的图片不带有alpha通道的话,这个方法还是有问题。这个问题在我们载入图片时显露了出来:

图片载入时

  当使用BitmapFactory从你程序的资源中载入一张图片时,同样的自动转换机制会发生。如果你要载入的图片没有alpha通道,Android会将其转换为16位RGB565图片。不幸的是,即使我们将图片放置在“res/raw”目录下,这种转换依然会发生。根据在这个帖子里一个叫Romain Guy的Android开发者所说,有一个解决之道:

  “你需要做的就是将图片直接载入为ARGB888模式。当你调用BitmapFactory.decode*()的某一个重载方法时,你能够指定一系列的BitmapFactory.Option对象。你需要将Option对象中的inDither设置为false。这样将会使BitmapFactory不去尝试将24位色图转换为16位色图。”

  当时,我发觉这个方法不起作用,至少在Android 1.6下都不起作用。传入一系列参数使得inDither为false的确使得图片不被抖动(Dither)处理,但是这种方法并不能阻止颜色模式的转换。图片依然会被转换成16位的RGB565,这将会使得转换后的渐变图片出现颜色条带的现象。

  既然这个推荐的解决方案并不能满足我们的要求,那么在这个过程中唯一能保持你图片原貌的方法就是让你的图片带上alpha通道。当BitmapFactory发觉图片资源带有alpha通道,它便只能将图片解码为32位的ARGB888位图。

绘制时

  假设我们有两张图片。图片A是ARGB888位图,图片B是RGB565位图。如果我们敬爱那个图片A绘制到B上,那么图片A将需要被转换为B的颜色格式。辛运的是,这种转换是由Canvas的drawBitmap方法替我们包办了。这对我们来说是个好消息,因为这正是我们想要的。在我们要释放位图资源的时候,我们已经完成调整和操作图片,并且图片将被转换和显示。然而,由于我们从32位转换为16位颜色深度,这将会造成图像的失真。为了减小失真对图片的影响,我们能够控制将图片从32位转换为16位的时机。当将一个高色深的图片绘制到第色深的图片上时,默认是不会进行抖动处理的。对于包含渐变的图片而言,这会使得图片出现众所周知的“色带”问题,这是非常难看的。为了克服这个问题,我们需要告诉Android我们想要对结果进行抖动处理。抖动是这么一种处理,它将原始颜色做出一些改变,以骗过我们的眼睛,让我们在低色深图片中以为自己看到了一个平滑的渐变。

  需要记住的是,当我们处理图片的时候,图片必须总是32位ARGB8888模式。只有当我们完成处理图片后,他们才应该被转换成16位色的图片。由于任何不带alpha通道的图片,再被BitmapFactory载入的时候将被转换为RGB565,不管它们是在"res/drawable"还是在"res/raw"目录下。而确保他们被解码为32色ARGB8888的唯一手段就是保证你的图片带有alpha通道。

例子

  为了证明我所说,然我们做一个简单的测试。首先让我们写一个载入并以默认option显示两张图片的activity。两张图片都是平滑渐变的色谱,但是他们将被保存为不同的格式。第一张图将被保存为24位色RGB888的PNG图,第二张图片被保存为32位带有alpha通道的ARGB8888的PNG图片。两张图片有着完全一致的颜色数据,惟一的区别是一张带有alpha通道而另一张没有。保存两张图片,并把他们放置到你项目的"res/raw"目录下。然后,在你的activity中使用下列代码:

复制代码
 1 @Override

 2 public void onCreate(Bundle savedInstanceState) {

 3       super.onCreate(savedInstanceState);

 4 

 5      // Load both of our images from our application's resources.
 6      Resources r = getResources();

 7      Bitmap resource= BitmapFactory.decodeResource(r, R.raw.spectrum_gray_nodither_;

 8      Bitmap resource= BitmapFactory.decodeResource(r, R.raw.spectrum_gray_nodither_;

 9 

10      // Print some log statements to show what pixel format these images were decoded with.
11      Log.d("FormatTest","Resource " + resourcegetConfig()); // Resource RGB_
12      Log.d("FormatTest","Resource " + resourcegetConfig()); // Resource ARGB_8
13 
14 // Create two image views to show these bitmaps in.
15      ImageView image= new ImageView(this);

16      ImageView image= new ImageView(this);

17      imagesetImageBitmap(resource;

18      imagesetImageBitmap(resource;

19 

20  

21 

22      // Create a simple layout to show these two image views side-by-side.
23      LayoutParams wrap = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);

24      LayoutParams fill = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);

25      RelativeLayout.LayoutParams params= new RelativeLayout.LayoutParams(wrap);

26      paramsaddRule(RelativeLayout.CENTER_VERTICAL);

27      paramsaddRule(RelativeLayout.ALIGN_PARENT_LEFT);

28      RelativeLayout.LayoutParams params= new RelativeLayout.LayoutParams(wrap);

29      paramsaddRule(RelativeLayout.CENTER_VERTICAL);

30      paramsaddRule(RelativeLayout.ALIGN_PARENT_RIGHT);

31      RelativeLayout layout = new RelativeLayout(this);

32      layout.addView(image params;

33      layout.addView(image params;

34      layout.setBackgroundColor(Color.BLACK);

35 

36      // Show this layout in our activity.
37      setContentView(layout, fill);

38 

39 }
复制代码

  编译此项目并将它部署到你的设备上查看结果。我们看到24位色图片在左边,为32位色图片在右边。但是等一下!32位的图片看上去有一些色带出现,然而24位图片看上去却很平滑。到底是怎么了?让我们仔细看看图片,我们能发现:

  仔细检查后,我们发觉24位图被抖动处理了,而32位色图没有。考虑到对于任何显示到Android设备屏幕的图片,都将被转换为16位色格式,所以这两张图都将发生这个转换。由于24位色图不带有alpha通道,并且它被放置在"res/raw"目录下,所以它将在我们载入activity中的资源时被自动转换为16位色图。我们能通过检查Logcat里的消息来验证这一点。我们从BitmapFactory里得到的Bitmap对象实际上是RGB565格式的,而BitmapFactory足够聪明会帮我们抖动处理图片。而我们带有alpha通道的的32位色图,则被载入为了ARGB8888位图。在该图片对应的ImageView被绘制到屏幕时,它将被转换为本地16位色格式。这里我们看到的是绘制时发生的转换不会进行抖动处理。让我们看一下,如果我们指明转换时进行抖动处理,情况是否会好一些。在上例中19行添加如下几行代码:

// Enable dithering when our 32-bit image gets drawn.
Drawable drawable32 = image32.getDrawable();

drawable32.setDither(true);

  编译后上传至设备然后查看结果:


  啊,好了。现在我们的32位图也已经被抖动处理好了。这里我们简单地告诉ImageView我们想要Bitmap被被绘制时进行抖动处理。不幸的是这将影响绘图速度,所以这并非一个理想的解决方案。但我们能够消除这个性能问题,通过在我们将图片送至ImageView之前预先进行抖动处理:

复制代码
 1 @Override

 2 public void onCreate(Bundle savedInstanceState) {

 3 super.onCreate(savedInstanceState);

 4  

 5 // Load both of our images from our application's resources.
 6 Resources r = getResources();

 7 Bitmap resource24 = BitmapFactory.decodeResource(r, R.raw.spectrum_gray_nodither_24);

 8 Bitmap resource32 = BitmapFactory.decodeResource(r, R.raw.spectrum_gray_nodither_32);

 9  

10 // Print some log statements to show what pixel format these images were decoded with.
11 Log.d("FormatTest","Resource24: " + resource24.getConfig()); // Resource24: RGB_565
12 Log.d("FormatTest","Resource32: " + resource32.getConfig()); // Resource32: ARGB_8888
13  
14 // Save the dimensions of these images.
15 int width = resource24.getWidth();

16 int height = resource24.getHeight();

17  

18 // Create a 16-bit RGB565 bitmap that we will draw our 32-bit image to with dithering.
19 Bitmap final32 = Bitmap.createBitmap(width, height, Config.RGB_565);

20  

21 // Create a new paint object we will use to draw our bitmap with. This is how we tell
22 // Android that we want to dither the 32-bit image when it gets drawn to our 16-bit final
23 // bitmap.
24 Paint ditherPaint = new Paint();

25 ditherPaint.setDither(true);

26  

27 // Create a new canvas for our 16-bit final bitmap, and draw our 32-bit image to it with
28 // the paint object we just created.
29 Canvas canvas = new Canvas();

30 canvas.setBitmap(final32);

31 canvas.drawBitmap(resource32, 0, 0, ditherPaint);

32  

33 // Create two image views to show these bitmaps in.
34 ImageView image24 = new ImageView(this);

35 ImageView image32 = new ImageView(this);

36 image24.setImageBitmap(resource24);

37 image32.setImageBitmap(final32);

38  

39 // Create a simple layout to show these two image views side-by-side.
40 LayoutParams wrap = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);

41 LayoutParams fill = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);

42 RelativeLayout.LayoutParams params24 = new RelativeLayout.LayoutParams(wrap);

43 params24.addRule(RelativeLayout.CENTER_VERTICAL);

44 params24.addRule(RelativeLayout.ALIGN_PARENT_LEFT);

45 RelativeLayout.LayoutParams params32 = new RelativeLayout.LayoutParams(wrap);

46 params32.addRule(RelativeLayout.CENTER_VERTICAL);

47 params32.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);

48 RelativeLayout layout = new RelativeLayout(this);

49 layout.addView(image24, params24);

50 layout.addView(image32, params32);

51 layout.setBackgroundColor(Color.BLACK);

52  

53 // Show this layout in our activity.
54 setContentView(layout, fill);

55 }
复制代码

  运行这段代码将会产生之前一样的效果,但是通过提前抖动处理,我们避免了性能瓶颈。如果你只是简单地载入和显示你的图片到你的activity一次,这种处理并不是必要的。但是如果需要频繁地绘制,则需要提前抖动处理以避免浪费处理时间和电池寿命。

         这时候你可能会想了,为什么我们要做那么多额外的工作来确保我们的图片被解码为32位色图像,然而其结果却只是还要在我们绘图时做更多的工作来确保图片被抖动处理了呢?既然24位色被自动转换为了效果不错的16位色图而不需要你的任何干预,那么我们使用32位色图的理由是什么呢?实际上,对于上面的例子,我们还没有对这些位图做任何的处理和操作,所以被自动转换的24位色图片看上去和我们手动转换的32位色图片效果一样。他们最终都有着一样的像素数据。但是若是我们想要在显示之前对图片进行一些处理呢?咱们先试试看。在你的设备上运行下述代码:

  

复制代码
 1 @Override

 2 public void onCreate(Bundle savedInstanceState) {

 3 super.onCreate(savedInstanceState);

 4  

 5 // Load both of our images from our application's resources.
 6 Resources r = getResources();

 7 Bitmap resource24 = BitmapFactory.decodeResource(r, R.raw.spectrum_gray_nodither_24);

 8 Bitmap resource32 = BitmapFactory.decodeResource(r, R.raw.spectrum_gray_dithered_32);

 9  

10 Log.d("FormatTest","Resource24: " + resource24.getConfig()); // Resource24: RGB_565
11 Log.d("FormatTest","Resource32: " + resource32.getConfig()); // Resource32: ARGB_8888
12  
13 // Sadly, the images we have decoded from our resources are immutable. Since we want to
14 // change them, we need to copy them into new mutable bitmaps, giving each of them the same
15 // pixel format as their source.
16 Bitmap bitmap24 = resource24.copy(resource24.getConfig(), true);

17 Bitmap bitmap32 = resource32.copy(resource32.getConfig(), true);

18  

19 // Save the dimensions of these images.
20 int width = bitmap24.getWidth();

21 int height = bitmap24.getHeight();

22  

23 // Create a new paint object that we will use to manipulate our images. This will tell
24 // Android that we want to replace any color in our image that is even remotely similar to
25 // 0xFF307070 (a dark teal) with 0xFF000000 (black).
26 Paint avoid1Paint = new Paint();

27 avoid1Paint.setColor(0xFF000000);

28 avoid1Paint.setXfermode(new AvoidXfermode(0xFF307070, 255, AvoidXfermode.Mode.TARGET));

29  

30 // Make another paint object, but this one will replace any color that is similar to a
31 // 0xFF00C000 (green) with 0xFF0070D0 (skyish blue) instead.
32 Paint avoid2Paint = new Paint();

33 avoid2Paint.setColor(0xFF0070D0);

34 avoid2Paint.setXfermode(new AvoidXfermode(0xFF00C000, 245, AvoidXfermode.Mode.TARGET));

35  

36 Paint fadePaint = new Paint();

37 int[] fadeColors = {0x00000000, 0xFF000000, 0xFF000000, 0x00000000};

38 fadePaint.setShader(new LinearGradient(0, 0, 0, height, fadeColors, null,

39 LinearGradient.TileMode.CLAMP));

40 fadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));

41  

42 // Create a new canvas for our bitmaps, and draw a full-sized rectangle to each of them
43 // which will apply the paint object we just created.
44 Canvas canvas = new Canvas();

45 canvas.setBitmap(bitmap24);

46 canvas.drawRect(0, 0, width, height, avoid1Paint);

47 canvas.drawRect(0, 0, width, height, avoid2Paint);

48 canvas.drawRect(0, 0, width, height, fadePaint);

49 canvas.setBitmap(bitmap32);

50 canvas.drawRect(0, 0, width, height, avoid1Paint);

51 canvas.drawRect(0, 0, width, height, avoid2Paint);

52 canvas.drawRect(0, 0, width, height, fadePaint);

53  

54 // Create a 16-bit RGB565 bitmap that we will draw our 32-bit image to with dithering. We
55 // only need to do this for our 32-bit image, and not our 24-bit image, because it is
56 // already in the RGB565 format.
57 Bitmap final32 = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);

58  

59 // Create a new paint object that we will use to draw our bitmap with. This is how we tell
60 // Android that we want to dither the 32-bit image when it gets drawn to our 16-bit final
61 // bitmap.
62 Paint ditherPaint = new Paint();

63 ditherPaint.setDither(true);

64  

65 // Using our canvas from above, draw our 32-bit image to it with the paint object we just
66 // created.
67 canvas.setBitmap(final32);

68 canvas.drawBitmap(bitmap32, 0, 0, ditherPaint);

69  

70 // Create two image views to show these bitmaps in.
71 ImageView image24 = new ImageView(this);

72 ImageView image32 = new ImageView(this);

73 image24.setImageBitmap(bitmap24);

74 image32.setImageBitmap(final32);

75  

76 // Create a simple layout to show these two image views side-by-side.
77 LayoutParams wrap = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);

78 LayoutParams fill = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);

79 RelativeLayout.LayoutParams params24 = new RelativeLayout.LayoutParams(wrap);

80 params24.addRule(RelativeLayout.CENTER_VERTICAL);

81 params24.addRule(RelativeLayout.ALIGN_PARENT_LEFT);

82 RelativeLayout.LayoutParams params32 = new RelativeLayout.LayoutParams(wrap);

83 params32.addRule(RelativeLayout.CENTER_VERTICAL);

84 params32.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);

85 RelativeLayout layout = new RelativeLayout(this);

86 layout.addView(image24, params24);

87 layout.addView(image32, params32);

88 layout.setBackgroundColor(Color.BLACK);

89  

90 // Show this layout in our activity.
91 setContentView(layout, fill);

92 }
复制代码

         这里我们载入了和之前例子一样的图片,但我们现在使用Android内置的图形函数对他们预先进行了一些处理,而不是直接将它们添加到ImageView中显示。我们在显示两张图片前分别将它们都使用了三个不同的滤镜进行处理。要是你不明白23-52行代码是干嘛的,别担心,我会在以后的文章中详尽的介绍它们。而对于现在而言,你只需要知道我们的原始图片在运行时被我们的程序进行了巨大的变换。下图是最终的效果:

  正如你所看到的,在经过了许多操作后,24位色图有了巨大的失真,而32位色图的效果则依然很不错。除了”条带”和失真,你可以发现24位色图的颜色根本都不正确。那么,为什么24位色图会出现这种现象而32位色图却没有呢?原因就在于24位色图被Android自动转换为了RGB565格式图像。一张16位色图片仅能够显示65,535种不同颜色,而32位色图却能够显示16,777,215种颜色,还带有255级半透明效果。这意味着当我们处理16位色图时,处理过后的颜色不能够被16位色所支持的65,535种颜色精确地表示出来。所以处理后的颜色被截取到了最临近相似的颜色值去了。每次我们对图片进行操作时,这种截取都会发生,所以图片将变得更加不精确。而当时用32位色时,这种现象虽然依然会有,但是由于有着将近17兆的颜色可以表示,这种副作用对于大部分程序来说将可以被忽略。

         我希望我的这篇文章能帮到各位。我希望各位能从我的文章中学会的主要思想是:

  1. 要意识到Android的自动图片转换和抖动处理机制。当事情不照你所想发展时,知道Android是如何储存、载入以及绘制你的图像的话将大大减轻你的头痛。
  2. 如果你要对你的图像被载入后进行任何的处理,确保你的图片是ARGB8888格式的。
  3. 通过使用某种图形处理软件将你的图像保存为32位带alpha通道的PNG图片,你可以强制这张图片被载入为32位色。
  4. 当你处理完你的32位色图片后,记得启用抖动处理后再将该图片转换为16位RGB565色图。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值