[Android][压缩图片]

1.落笔缘由

在开发过程中,对大图进行图片压缩是很常见的,因为如果将一个未压缩过的上M的大图直接读到内存中,是很占内存的。在上传图片的时候,如果为了节省上传流量,也需要对图片进行压缩。不管出于什么原因,反正压缩图片用得很频繁就对了。

2.压缩的方式

从了解的情况来看,压缩一般有两种方式分别是质量压缩和尺寸压缩。

(1)质量压缩

图片有三种存在方式,一种是保存在硬盘或U盘等以文件file的形式存在的;一种是以流的形式存在的;还有一种是以bitmap的形式。质量压缩实现的是将以file形式存在的图片进行压缩,并不会对图片的像素进行压缩,所以质量压缩只会对以file形式存在的图片有作用。但是如果将file形式的图片读入内存中,以bitmap的形式存在,会发现压缩前后的bitmap大小是没有改变的,因为bigmap在内存中的大小是按像素计算的,而质量压缩不会改变像素。
质量压缩主要是通过在保持像素的前提下改变图片的位深及透明度等,来达到压缩图片的目的。这种情况就相对于像素点能表现得颜色变少了,所有通过质量压缩是有可能照成图片失真的。
质量压缩主要借助Bitmap中的compress方法实现:

public boolean compress(CompressFormat format, int quality, OutputStream stream) 
  • format是压缩后的图片的格式,可取值:Bitmap.CompressFormat .JPEG、.PNG、.WEBP。
  • quality的取值范围为[0,100],值越小,经过压缩后图片失真越严重,图片文件也会越小,100表示不压缩(PNG格式的图片会忽略quality值的设定,quality值无效)
  • stream指定压缩的图片输出的地方,比如某文件

1)质量压缩的步骤

<1>先获取bitmap的byte值

<2>用bitmap的byte除以用户给定的要压缩到的byte值,计算出要压缩的次数

2)具体实现

public void compressImage(Bitmap image, String outPath, int maxSize) throws IOException
{
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        int options = 100;//100表示不压缩图片,这里是想将图片读到ByteArrayOutputStream流中
        image.compress(Bitmap.CompressFormat.JPEG, options, os);
        //maxSize是用户希望将图片压缩成 多少kb,因为options的范围在0到100所以不能这里需要           //对options的值做一个判断
        //循环对图片进行压缩
        while (options>10&&os.toByteArray().length / 1024 > maxSize)
        {
                os.reset();
                options -= 10;
                image.compress(Bitmap.CompressFormat.JPEG, options, os);
        }
        FileOutputStream fos = new FileOutputStream(outPath);
        fos.write(os.toByteArray());
        fos.flush();
        fos.close();
}

(2)尺寸压缩

尺寸压缩会直接改变图片的像素,会改变图片在内存中的大小,所以以bitmap形式存在的图片经过尺寸压缩后占用内存会变小。

1)尺寸压缩步骤

<1>先获取图片的宽高,为了省内存,以只读去图片的边的形式获取宽高

    将BitmapFactory.Options的options.inJustDecodeBounds=true,再通过BitmapFactory.decodeFile(imgPath, options)方法,将图片的宽高像素保存到options中,就可以通过options.outWidth和options.outHeight获取宽高。

<2>根据用户提供的宽高,与原图的宽高进行对比,计算出缩放比

<3>将获取的缩放比赋值给inSampleSize

2)具体实现

public Bitmap compressImage(String imgPath, float pixelW, float pixelH)
{
        BitmapFactory.Options newOpts = new BitmapFactory.Options();
        // 开始读入图片,此时把options.inJustDecodeBounds 设回true,即只读边不读内容
        newOpts.inJustDecodeBounds = true;
        newOpts.inPreferredConfig = Config.RGB_565;
        // 将file形式的图片转为bitmap
        Bitmap bitmap = BitmapFactory.decodeFile(imgPath, newOpts);
        newOpts.inJustDecodeBounds = false;
        //原图的宽高
        int w = newOpts.outWidth;
        int h = newOpts.outHeight;
        // 想要缩放的目标尺寸
        float hh = pixelH;
        float ww = pixelW;
        // 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
        int be = 1;// be=1表示不缩放
        if (w > h && w > ww)
        {// 如果宽度大的话根据宽度固定大小缩放
                be = (int) (newOpts.outWidth / ww);
        } else if (w < h && h > hh)
        {// 如果高度高的话根据宽度固定大小缩放
                be = (int) (newOpts.outHeight / hh);
        }
        if (be <= 0)
                be = 1;
        newOpts.inSampleSize = be;// 设置缩放比例
        // 开始压缩图片,要读取整张图片内存,要将options.inJustDecodeBounds 设回false
        bitmap = BitmapFactory.decodeFile(imgPath, newOpts);
        return bitmap;
}

3.需要了解的知识

(1)BitmapFactory.Option的inSampleSize值

inSampleSize值表示缩略图大小为原图大小的几分之一。例如,inSampleSize==4时则缩略图的宽和高都为原图的1/4,相应的图片大小就为原始大小的1/16,如果inSampleSize值<=1时则取1。需要注意的是,inSampleSize值只能是2的整数次幂,如果不是的话则会向下取得最大的2的整数次幂(整数次幂这个没验证过,有时间再验证)。

(2)compress(Bitmap.CompressFormat format, int quality, OutputStream stream)的quality

compress(Bitmap.CompressFormat format, int quality, OutputStream stream)方法中quality代表的是图片的质量,取值范围为0~100, 0表示压缩为最小质量,100表示压缩为最大质量(即保持原图质量)

(3)位深以及色深

网上很多说法,有人说位深和色深是同一个概念,但更多的是说他们是两个不同的概念,从百度百科两篇关于位深和色深的文章,
位深度
色深度
就我个人理解,其实他们描述的都是像素能够表现得颜色有多少种,我觉得他们应该指的是同一个概念。网上看到一个评论,“像素才可以表现出数字色彩,像素的色彩由RGB通道决定。位深度的描述对象是通道,色彩深度的描述对象是像素(或者图像)”。前面一句是对的,但是后面这句“位深度的描述对象是通道,色彩深度的描述对象是像素(或者图像)”,我感觉有问题。下面举例子来理解。

色深(Color Depth),也称之为色位深度,在某一分辨率下,每一个像素点可以有多少种色彩来描述,它的单位是“bit”(位)。典型的色深是8-bit、16-bit、24-bit和32-bit。深度数值越高,可以获得更多的色彩。
如果R,G,B三个通道可描述颜色位数分别8,8,8,,一个像素点的色彩深度是24-bit。如果R,G,B三个通道可描述颜色位数分别4,4,2,,一个像素点的色彩深度是10-bit。
从目前查的资料来看,我认为网上所说的位深度和色深是一个概念。
这个是Android studio里的,


[Android][压缩图片]_第1张图片
image.png

对这张图片,在电脑上右击点击属性查看该图片,可以看到也是32bit


[Android][压缩图片]_第2张图片
image.png
如下,A代表透明度;R代表红色;G代表绿色;B代表蓝色。

ALPHA_8 
表示8位Alpha位图,即A=8,一个像素点占用1个字节,它没有颜色,只有透明度 
ARGB_4444 
表示16位ARGB位图,即A=4,R=4,G=4,B=4,一个像素点占4+4+4+4=16位,2个字节 
ARGB_8888 
表示32位ARGB位图,即A=8,R=8,G=8,B=8,一个像素点占8+8+8+8=32位,4个字节 
RGB_565 
表示16位RGB位图,即R=5,G=6,B=5,它没有透明度,一个像素点占5+6+5=16位,2个字节

4.总结

(1)尺寸压缩不会失真,质量压缩有可能会失真(未验证过)
(2)图片格式,对应Bitmap的compress(Bitmap.CompressFormat format, int quality, OutputStream stream)方法中第一个参数。CompressFormat类是个枚举,有三个取值:JPEG、PNG和WEBP。
  1)PNG是无损格式(忽略质量设置),会导致方法中的第二个参数压缩质量失效。
  2)JPEG是一种针对相片影像而广泛使用的一种失真压缩标准方法。JPEG的压缩方式通常是破坏性资料压缩(lossy compression),意即在压缩过程中图像的品质会遭受到可见的破坏。
  3) WEBP格式是Google新推出的,据官方资料称“在质量相同的情况下,WEBP格式图像的体积要比JPEG格式图像小40%。”
(3)质量压缩对三种格式的图片的影响:
  1)PNG格式的确是忽略质量设置的,不同压缩质量下图片大小不变,所以PNG格式的图片只能通过改变图片尺寸进行压缩。
  2)quality越小(即压缩质量值越小),JPEG格式和WEBP格式的图片有较为明显的失真,但JPEG格式的图片失真更为严重。
(4)Bitmap一定要在使用完后调用recycle()回收,否则占用内存会不断增大

if (bitmap!=null&&!bitmap.isRecycled())
{
        bitmap.recycle();
        bitmap = null;
}

5.参考文章

http://blog.csdn.net/ymangu666/article/details/37729109
http://blog.csdn.net/alfred_c/article/details/50542741
http://blog.csdn.net/ymangu666/article/details/37729109
https://www.liangzl.com/get-article-detail-28355.html

你可能感兴趣的:([Android][压缩图片])