Android图片浅谈
本文首发于个人微信公众号:Myoung。
欢迎大家订阅,点赞、留言。
mipmap图片
如果做 App 开发的话,AS 会在项目的 res 目录下,创建几个以 mipmap 开头的文件夹:
根据 Android 官方描述:
- Mipmap 仅仅用于存放 App 启动图标。
- AS 提供了一个快捷工具 Image Asset Studio 用于生成图标。
- Image Asset Studio会生成mdpi、hdpi、xhdpi、xxhdpi、xxxhdpi五种尺寸的图标。
图标最好不要随意定义尺寸,分辨率过低会模糊,过高徒增APK包大小。各种密度下的图标建议尺寸为
密度 | 建议尺寸 |
mdpi | 48*48 |
hdpi | 72*72 |
xhdpi | 96*96 |
xxhdpi | 144*144 |
xxxhdpi | 192*192 |
如果要上传到Google Play,还需要一张512*512的图片用于Google Play Store。
drawable图片
相关概念
- dpi
每英寸点数,全称dots per inch。用来表示屏幕密度,即屏幕物理区域中的像素量。高密度屏幕比低密度屏幕在给定物理区域的像素要多。
- dp
即dip,全称device independent pixel。设备独立像素,是一种虚拟像素单位,用于以密度无关方式表示布局维度或位置,以确保在不同密度的屏幕上正常显示UI。在160dpi的设备上,1dp=1px。
- density
设备的逻辑密度,是dip的缩放因子。以160dpi的屏幕为基线,density=dpi/160。
getResources().getDisplayMetrics().density
- sp
缩放独立像素,全称scale independent pixel。类似于dp,一般用于设置字体大小,可以根据用户设置的字体大小偏好来缩放。
六种通用密度
Android系统为了简化开发者为多种屏幕设计用户界面的方式,Android将实际屏幕尺寸和范围作了通用规定,称作“根据可用屏幕宽度管理屏幕尺寸的新技术”。
六种通用密度为:
密度 | dpi范围 |
ldpi(低) | ~120dpi |
mdpi(中) | ~160dpi |
hdpi(高) | ~240dpi |
xhdpi(超高) | ~320dpi |
xxhdpi(超超高) | ~480dpi |
xxxhdpi(超超超高) | ~640dpi |
通用密度是以mdpi(中)为基线配置的,此基线基于第一代Android设备(T-Mobile G1)的屏幕配置。
Android系统适配原则
目的:
- Android 为了更好地优化应用在不同屏幕密度下的用户体验。
提供的方法:
- 在项目的res目录下可以创建 drawab-[density]目录。
- density为6种通用密度名。
- Android系统会依据特定的原则来查找各drawable目录下的图片。
开发者:
- 在进行APP开发时,针对不同的屏幕密度,将图片放置于对应的drawable-[density]目录。
原则:
总结:
优先匹配最适合的图片 → 查找密度高的目录(升序)→ 查找nodpi目录 → 查找密度低的目录(降序)。
Android 在查找到图片后会根据当前设备的dpi对drawable-[density]目录中的图片进行缩放,那么什么情况下图片被放大,什么情况下图片被缩小呢?
图片的缩放
缩放规律
- 匹配目录
符合当前设备dpi的drawable目录。比如:
- dpi=320,匹配目录为drawable-xhdpi。
- dpi=150,匹配目录为drawable-mdpi。
- 图片的缩放规律
- 如果图片所在目录为匹配目录,则图片会根据设备dpi做适当的缩放调整。
- 如果图片所在目录dpi低于匹配目录,那么该图片被认为是为低密度设备需要的,现在要显示在高密度设备上,图片会被放大。
- 如果图片所在目录dpi高于匹配目录,那么该图片被认为是为高密度设备需要的,现在要显示在低密度设备上,图片会被缩小。
- 如果图片所在目录为drawable-nodpi,则无论设备dpi为多少,保留原图片大小,不进行缩放。
缩放倍数
- 缩放倍数
以mdpi为基线,各密度目录下的放大倍数(即缩放因子density)如下:
密度 | 放大倍数 |
ldpi | 0.75 |
mdpi | 1.0 |
hdpi | 1.5 |
xhdpi | 2.0 |
xxhdpi | 3.0 |
xxxhdpi | 4.0 |
- 更通用的缩放倍数计算公式
对于很多设备,其dpi并不刚好是六种通用密度最大dpi,这种情况下,各drawable-[density]目录下的图片放大倍数的计算公式:
验证
- 验证缩放
设备:1080×1920 - 420dpi
- 验证倍数
材料:在Sketch里简单绘制一张图,分别导出一倍图(1x)和三倍图(3x)。大小如下:
设备:1080×1920 - 420dpi
方法:分别把1倍图和3倍图置于drawable-mdpi和drawable-xxhdpi目录下
结果:
- drawable-mdpi
scale = 420/160
1x:98 × scale = 98 × 420/160 = 257
3x:294 × scale = 294 × 420/160 = 772
- drawable-xxhdpi
scale = 420/480
1x:98 × scale = 98 × 420/480 = 86
3x:294 × scale = 294 × 420/480 = 257
像素没有小数。
drawable-xxhdpi目录下,图片的宽高更接近于图片实际大小。
那么问题来了,Android上图片的缩放算法是怎样的呢?
Android图片压缩
质量压缩
更改图片的显示质量的一种压缩方式。
基本概念
- 三原色:RGB
- RGB通道
- alpha通道
- 位深
- 8位:2^8 = 2^2(B) 2^3(G) 2^3(R) = 256
- 16位:2^16 = 2^5(B) 2^6(G) 2^5(R) = 65536
- 24位:2^24 = 2^8(B) 2^8(G) 2^8(R) = 16777216
- 32位:alpha透明度 + 24位
位深越高,色彩越逼真。
压缩原理
不改变像素的前提下,通过改变位深来改变图片文件大小。
- 色彩逼真度下降,即失真(质量下降)。
- 压缩比 1~100。
- 压缩有三种格式:.JPEG、.PNG、.WEBP。
Android实现质量压缩:
//quality 为0~100,0表示最小体积,100表示最高质量,对应体积也是最大 bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
其中,PNG无法压缩。
Android代码流程:
Bitmap(Java)→ Bitmap_compress(cpp)→ Skia 图形引擎 → 第三方库
- libpng
- libjpeg
- libgif
再进一步的话,Android使用的其实是 Standard Huffman 算法。
- Huffman table
- optimize_coding
尺寸压缩
通过改变图片的像素尺寸来压缩图片的方式,即重新采样。
- 上采样:方法图像
- 下采样:缩小图像
Android中图像采用的两种方式:
- 邻近采样(Nearest Neighbour Resampling)
- 双线性采样(Bilinear Resampling)
邻近采样
邻近采样主要就是 inSampleSize 的使用:
BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 2; Bitmap compressBitmap = BitmapFactory.decodeFile("/sdcard/sample.png", options);
Google官方对 inSampleSize 的注释:
/** * If set to a value > 1, requests the decoder to subsample the original * image, returning a smaller image to save memory. The sample size is * the number of pixels in either dimension that correspond to a single * pixel in the decoded bitmap. For example, inSampleSize == 4 returns * an image that is 1/4 the width/height of the original, and 1/16 the * number of pixels. Any value <= 1 is treated the same as 1. Note: the * decoder uses a final value based on powers of 2, any other value will * be rounded down to the nearest power of 2. */
简单说,就是多个像素采样为一个像素。
举个栗子,如果 inSampleSize = 2,直接采样其中一个像素,另一个像素舍弃。
双线性采样
Android中双线性采样主要是通过 Matrix 来使用的:
Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/sample.png"); Bitmap compress = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth()/2, bitmap.getHeight()/2, true);
Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/sample.png"); Matrix matrix = new Matrix(); matrix.setScale(0.5f, 0.5f); bm = Bitmap.createBitmap(bitmap, 0, 0, bit.getWidth(), bit.getHeight(), matrix, true);
双线性采样内部使用的是双线性内插值算法。
每个位置像素使用 2✖️2 的源像素按相对位置进行两个方向的线性插值,根据位置取对应的权重,计算后得到目标像素。
x方向:
y方向:
最终插值结果:
对比
- 邻近采样
- 简单粗暴,效率高
- 失真较高
- 因为是像素舍弃,会导致锯齿明显
- 双线性采样
- 计算量较大,效率相对低些
- 保真相对较好一些
- 有一定的抗锯齿能力
- 具有低通滤波性质
其他算法
- Bicubic Resampling
- Lanczos Resampling
针对应届生技术人的随笔 文章被收录于专栏
介绍一些自身经验和积累,帮助一些应届毕业生更好的成长和发展。