图像色彩处理
Android对图片的处理,通常用到的数据结构就是位图—Bitmap,它包含了一张图片的所有数据。整个图片都是由点阵和颜色值组成的,点阵就是一个包含像素的矩阵,每一个元素对应着图片的一个像素,颜色值—ARGB,分别对应透明度、红、绿、蓝这四个通道分量,它们共同决定了每个像素点显示的颜色,对图片的色彩处理实际上就是对这些像素点的通道分量做调整。
在色彩处理中,通常从三个角度来描述一个图像。
- 色调:色彩的总体倾向
- 饱和度:颜色的纯度,从0(灰)到100%(饱和)来进行描述
- 亮度:颜色的相对明暗程度
在android中,系统使用一个颜色矩阵—ColorMatrix,来处理图像的这些色彩效果。Android中颜色矩阵是一个4x5的矩阵,它用来对图片的色彩进行处理。而对于每个像素点,都有一个颜色分量矩阵用来保存颜色的RGBA值,如下图所示:
在处理图像时,使用矩阵乘法运算AC来处理颜色分量矩阵,如下图所示:
计算过程:
R1 = a * R + b * G + c * B + d * A + e;
G1 = f * R + g * G + h * B + i * A + j;
B1 = k * R + l * G + m * B + n * A + o;
A1 = p * R + q * G + r * B + s * A + t;
可以发现,对于颜色矩阵A是按以下方式划分的:
* 第一行的a b c d e值决定新的颜色值中的R分量—红色
* 第二行的f g h i j值决定新的颜色值中的G分量—绿色
* 第三行的k l m n o值决定新的颜色值中的B分量—蓝色
* 第四行的p q r s t值决定新的颜色值中的A分量—透明度
* 矩阵A中的第五列—e j o t值分别决定每个分量中的offset,即偏移量
想要对原图片进行颜色的调整,就需要设置好用于调整颜色的矩阵A
通常有两种方法:
1、改变偏移量
将矩阵A的第五列的值进行修改,即改变颜色的偏移量,其他值保持初始矩阵的值
原图片每个像素点的矩阵红色和绿色的颜色分量都增加了100,红绿混合为黄色,最终会使得整张图片偏黄。
2、改变颜色系数
修改颜色分量中的某个系数值,其他值依然保持初始矩阵的值
矩阵运算后,原图片每个像素点的矩阵绿色的颜色分量会变为原来的两倍,最终使得原图片的色调偏绿。
改变色光属性
系统封装了一个类—ColorMatrix,通过这个类,可以很方便地通过改变矩阵值来处理颜色效果(色调、饱和度、亮度)。本质上是一个一维数组[a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t]。
调节色调(色彩的旋转运算):
ColorMatrix类提供了setRotate(int axis, float degrees)来调节颜色的色调。第一个参数,使用0、1、2来代表Red、Green、Blue三种颜色的处理,第二个参数,就是需要处理的值。
/**
* Set the rotation on a color axis by the specified values.
* <p>
* <code>axis=0</code> correspond to a rotation around the RED color
* <code>axis=1</code> correspond to a rotation around the GREEN color
* <code>axis=2</code> correspond to a rotation around the BLUE color
* </p>
*/
public void setRotate(int axis, float degrees) {
reset();
double radians = degrees * Math.PI / 180d;
float cosine = (float) Math.cos(radians);
float sine = (float) Math.sin(radians);
switch (axis) {
// Rotation around the red color
case 0:
mArray[6] = mArray[12] = cosine;
mArray[7] = sine;
mArray[11] = -sine;
break;
// Rotation around the green color
case 1:
mArray[0] = mArray[12] = cosine;
mArray[2] = -sine;
mArray[10] = sine;
break;
// Rotation around the blue color
case 2:
mArray[0] = mArray[6] = cosine;
mArray[1] = sine;
mArray[5] = -sine;
break;
default:
throw new RuntimeException();
}
}
调节饱和度
通过色彩的平移运算单独增强R,G,B的饱和度,ColorMatrix类提供了setSaturation(float sat)方法来整体调节图像的饱和度,参数代表设置颜色饱和度的值,当饱和度为0时,图像变成灰度图像,数值越大图像越饱和。
/**
* Set the matrix to affect the saturation of colors.
*
* @param sat A value of 0 maps the color to gray-scale. 1 is identity.
*/
public void setSaturation(float sat) {
reset();
float[] m = mArray;
final float invSat = 1 - sat;
final float R = 0.213f * invSat;
final float G = 0.715f * invSat;
final float B = 0.072f * invSat;
m[0] = R + sat; m[1] = G; m[2] = B;
m[5] = R; m[6] = G + sat; m[7] = B;
m[10] = R; m[11] = G; m[12] = B + sat;
}
调节亮度(色彩的缩放运算)
当三原色以相同的比例进行混合的时候,就会显示出白色,使用这个原理来改变一个图像的亮度,亮度为0时,图像变为全黑。ColorMatrix类提供setScale(float rScale, float gScale, float bScale, float aScale)方法来调节颜色的亮度值。
/**
* Set this colormatrix to scale by the specified values.
*/
public void setScale(float rScale, float gScale, float bScale,
float aScale) {
final float[] a = mArray;
for (int i = 19; i > 0; --i) {
a[i] = 0;
}
a[0] = rScale;
a[6] = gScale;
a[12] = bScale;
a[18] = aScale;
}
一些常用的图像颜色处理矩阵
- 灰度效果
- 图像反转
- 怀旧效果
- 去色效果
像素点分析
可以通过改变每个像素点的具体ARGB值,来达到处理一张图像效果的目的。系统提供了Bitmap.getPixel()方法来获取某个像素点,也提供了Bitmap.getPixels()方法来提取整个Bitmap中的像素点,并保存到一个数组中:
getPixels(int[] pixels, int offset, int stride, int x, int y, int width, int height)
当获取到具体的颜色值之后,就可以通过相应的算法来修改它的ARGB值,从而重构到一张新的图像。
常用图像像素点处理效果
—底片效果:
B.r = 255 - B.r;
B.g = 255 - B.g;
B.b = 255 - B.b;
—老照片效果:
r1 = (int) (0.393 * r + 0.769 * g + 0.189 * b);
g1 = (int) (0.349 * r + 0.686 * g + 0.168 * b);
b1 = (int) (0.272 * r + 0.534 * g + 0.131 * b);
—浮雕效果:
B.r = C.r - B.r + 127;
B.g = C.g - B.g + 127;
B.b = C.b - B.b + 127;
图形变换处理
Android系统对于图像的图形变换也是通过矩阵来进行处理的,每个像素点都表达了其坐标的X、Y信息,用于图形变换的矩阵是一个3x3的矩阵:
使用变换矩阵去处理每一个像素点的时候,与颜色矩阵的矩阵乘法一样:
X1 = a * X + b * Y + c
Y1 = d * X + e * Y + f
1 = g * X + h * Y + i
通常情况下,会让g = h = 0,i = 1,这样就使1 = g * X + h * Y + i恒成立。
与色彩变换矩阵的初始矩阵一样,图形变换矩阵也有一个初始矩阵:
图像的变形处理通常包含以下基本变换
- 平移变换
平移变换的坐标值变换过程就是将每个像素点都进行平移变换,从P(x0,y0)平移到P(x1,y1):
矩阵变换:
- 旋转变换
旋转变换即指一个点围绕一个中心旋转到一个新的点。当从P(x0,y0)点,以坐标原点O为旋转中心旋转到P(x,y)时,
可以得到:
x0 = r*cosα
y0 = r*sinα
x = r*cos(α+θ) = r*cosα*cosθ − r*sinα*sinθ = x0*cosθ − y0*sinθ
y = r*sin(α+θ) = r*sinα*cosθ + r*cosα*sinθ = y0*cosθ + x0*sinθ
矩阵变换如下:
以上是以坐标原点为旋转中心进行旋转变换,如果以任意点O为旋转中心来进行旋转变换,通常需要以下三个步骤:
1、将坐标原点平移到O点
2、使用前面讲的以坐标原点为中心的旋转方法进行旋转变换
3、将坐标原点还原
- 缩放变换
像素点是不存在缩放的概念,但是由于图像是由很多个像素点组成的,如果将每个点的坐标都进行相同比例的缩放,最终就会形成让整个图像缩放的效果
x1 = K1 * x0
y1 = K2 * y0
- 错切变换
错切变换是一种比较特殊的线性变换,错切变换的效果就是让所有点的X坐标(或者Y坐标)保持不变,而对应的Y坐标(或者X坐标)则按比例发生平移,且平移的大小和该点到Y轴(或者X轴)的距离成正比。错切变换通常包含两种——水平错切与垂直错切。
水平错切:
x1 = x0 + K1 * y0
y1 = y0
垂直错切:
x1 = x0
y1 = K2 * x0 + y0
像素块分析
和图像的色彩处理有两种方式一样,图像的变形处理也有使用矩阵和像素块分析两种方式,drawBitmapMesh()与操纵像素点来改变色彩的原理类似,是把图像分成了一个个的小块,然后通过改变每一个图像块来修改整个图像。
public void drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight,float[] verts, int vertOffset, int[] colors, int colorOffset, Paint paint);
要使用drawBitmapMesh()方法就需先将图片分割为若干个图像块。在图像上横纵各画N条线,而这横纵各N条线就交织成了NxN个点,而每个点的坐标则以x1,y1,x2,y2,...,xn,yn的形式保存在verts数组中,也就是说verts数组的每两位用来保存一个交织点,第一个是横坐标,第二个是纵坐标。而整个drawBitmapMesh()方法改变图像的方式,就是靠这些坐标值的改变来重新定义每一个图像块,从而达到图像效果处理的功能。使用这个方法可以实现很多图像特效如旗帜飞扬、水波纹等效果。
一些开源图像处理库
-
GPUImage for Android
GPUImage 是iOS下一个开源的基于GPU的图像处理库,提供各种各样的图像处理滤镜,并且支持照相机和摄像机的实时滤镜。GPUImage for Android是它在Android下的实现,同样也是开源的。其中提供了几十多种常见的图片滤镜API,且其机制是基于GPU渲染,处理速度相应也比较快,是一个不错的图片实时处理框架。
GitHub地址:https://github.com/CyberAgent/android-gpuimage
支持一百多种图片处理效果
OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉库,可以运行在Linux、Windows、Android和Mac OS操作系统上。
参考资料:《Android群英传》