图片压缩
颜色基础知识
真彩色,人眼能识别1千万种颜色,超过1千万的就是真彩色,24位色可显示2^24>1千6百万,是真彩色。
24位色,不带透明度,R、G、B各用8位,RGB888;
32位色,带透明度,A、R、G、B各用8位,ARGB8888;
不带透明度的16位色,RGB=565;
不带透明度的15位色,RGB=555;
不带透明度的8位色,RGB=332;
带透明度的8位色,ARGB=2222。
Windows系统,右键查看图片的详细信息,jpg一般是24位深,png是32位深,png带透明度。
色相:R、G、B为色相。
位深:每种色相的位深,表示一种色相从浅到深可以平分为多少份,三种色相的深度加起来就是颜色的色深。
24位色深中,R、G、B各用8位,即可分为2^8份。色相深度值一次为0x0、0x1、0x2...;
15位色深中,R、G、B各用5位,即可分为2^5=32份,色相深度值一次为0x0、0x8、0x10、0x18...;
24位色包含15位色的全部颜色的,同理包含16位色、8位色。
图片加载到内存后,每个像素点的颜色,都是用32位色(ARGB_8888)存储的,即4个字节,每个像素占用4个字节,再乘以像素点数就是在内存中占用空间,所以图片加载到内存后,是原大于在物理内存中的空间的。 java语言,把图片加载到内存后的bmp,bmp.getByteCount(),除以像素,刚好等于4,这就是因为每个像素点的颜色是用32位存储的。
图片压缩
分类为压缩存到磁盘中的大小,加载到内存中的大小,加载到内存后重复使用而缓存的大小。
质量压缩
质量压缩改变占用磁盘空间,不会改变占用内存空间。质量压缩降低图片质量,比如位深,所以png图片采用质量压缩后,磁盘占用减少的极为明显。但质量压缩不改变图片的分辨率,所以不能改变内存占用空间。
Bitmap sourceBmp = ...
ByteArrayOutputStream baos = new ByteArrayOutputStream();
sourceBmp.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] bytes = baos.toByteArray();
Bitmap bmp = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
saveBitmap("bmp.png", bmp);
质量压缩的核心是Bitmap.compress(CompressFormat format, int quality, OutputStream stream)
,quality控制压缩后的图片质量,最后为100,越小越糊。实际验证的总结,图片读取到内存为bitmap后,不经质量压缩,直接用流存为文件,文件就较原文件小,原因未知,这里我们记为X。采用compress压缩时,quality=100,保持到磁盘后的文件依然为X,但当我们减小quality时,就会看到保持到磁盘后的文件在减小。需要注意的是,这个前提是CompressFormat=JPEG
。如果选为PNG,那么不论quality设为多少,保存后的磁盘文件都不会变,为X。
改变内存大小的压缩方式。
降低采样率
当inSampleSize = 2时,X方向每2个点采样一个点,Y方向同理,所以bitmap内存占用减小为1/4。
当inSampleSize = 2时,X方向每2个点采样一个点,Y方向同理,所以bitmap内存占用减小为1/4。
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
Bitmap bitmap = BitmapFactory.decodeFile(f.getAbsolutePath(), options);
createScaledBitmap或Matrix
createScaledBitmap
是Bitmap的静态方法,其内部使用Matrix实现。Matrix变换是先将原图读入内存,再在原图的基础上重新采样。原图在局部变量中,用完即回收,新图存于缓存,可以重复使用。Matrix方法会造成瞬间的内存抖动。
Matrix相比采样率的优势,1、采样率只能是1/4、1/9、1/16,Matrix比较灵活;2、采样率是在4(9)个采样点选一个,抛弃剩余,Matrix根据一个采样区块内的各个像素点颜色算出权值。
Bitmap.createScaledBitmap(sourceBmp, 300, 300, true);
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 0.5f);
Bitmap bitmap = Bitmap.createBitmap(sourceBmp, 0, 0, sourceBmp.getWidth(), sourceBmp.getHeight(), matrix, true);
BitmapReginDecoder局部采样
FileInputStream fis = new FileInputStream(f);
BitmapRegionDecoder mRegionDecoder = BitmapRegionDecoder.newInstance(fis, false);
BitmapFactory.Options sOptions = new BitmapFactory.Options();
// sOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
// sOptions.inSampleSize = 2;
Rect mRect = new Rect();
mRect.top = 200;
mRect.left = 200;
mRect.right = 400;
mRect.bottom = 400;
Bitmap bitmap = mRegionDecoder.decodeRegion(mRect, sOptions);
局部采样就是不加载整张图片,从大图中挖一块。使用场景比如你想看清明上河图高清大图的某个细节,你只用把想看的细节这部分读入内存即可。
局部采用有参数BitmapFactory.Options,这意味着可以和采样率,位深等配合使用。
Bitmap自带的方法Bitmap.createBitmap也可以处理图片局部的bitmap,但也是要先读入整个图片,没有BitmapReginDecoder好用。
颜色模式
读取图片时,设置BitmapFactory.Options
BitmapFactory.Options.inPreferredConfig = Bitmap.Config.RGB_888; // RGB_565等
合适的drawable-hdpi
上面是应用外部读取图片,在应用内res/drawble中的图片,
设备从xhdpi读资源,图片放到xhdpi下,读入内存大小位X,(从外部读取也应该是X),但如果改图片放到hdpi及以下,占用内存将大于X,如果放到xxhdpi,占用内存将小于X。
scale = (flaot) targetDensity / density;// targetDensity:设备屏幕像素密度dpi,density:图片对应的文件夹的像素密度dpi
空间复用
BitmapFactory.Options options = new BitmapFactory.Options();
options.inMutable = true; // 图片复用,这个属性必须设置;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bing, options);
Log.i("kobe", "bitmap: " + bitmap);
Log.i("kobe", "bitmap, ByteCount: " + bitmap.getByteCount() + ", AllocationByteCount: " + bitmap.getAllocationByteCount());
options.inBitmap = bitmap; // 使用inBitmap属性,这个属性必须设置;
options.inMutable = true;
Bitmap newBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test, options);
Log.i("kobe", "newBitmap: " + newBitmap); // 复用对象的内存地址;
Log.i("kobe", "bitmap, ByteCount: " + bitmap.getByteCount() + ", AllocationByteCount: " + bitmap.getAllocationByteCount());
Log.i("kobe", "newBitmap, ByteCount: " + newBitmap.getByteCount() + ", AllocationByteCount: " + newBitmap.getAllocationByteCount());
===============上为代码,下为日志==========
sourceBmp: 1805760
bitmap: android.graphics.Bitmap@f75dd4a
bitmap, ByteCount: 1805760, AllocationByteCount: 1805760
newBitmap: android.graphics.Bitmap@f75dd4a
bitmap, ByteCount: 81608, AllocationByteCount: 1805760
newBitmap, ByteCount: 81608, AllocationByteCount: 1805760
空间复用,旧bmp的空间必须大于等于新bmp。复用过程是旧bmp的内存空间被释放,新bmp占用了这块空间。优势是不用位新bmp分配内存,直接占用旧内存即可,减少内存抖动。如果用ImageView.setImageBitmap(bmp),显示的是新的bmp。因为旧bmp引用指向的内存空间,已经被新bmp占用了。
正常情况下,ByteCount和AllocationByteCount是相同的,但发生空间复用就不同了。ByteCount是bmp实际占用的空间,AllocationByteCount是分配的空间。
另外还有BitmapFactory.Options三件套inScaled + inDensity + inTargetDensity ,我没看太懂。