一、前期基础知识储备
Bitmap(位图文件),扩展名可以是.bmp或者.dib。位图是Windows标准格式图形文件,它将图像定义为由点(像素)组成,每个点可以由多种色彩表示,包括2、4、8、16、24和32位色彩。想象一下你以前测红绿色盲的时候,王医生拿给你看的那张红红绿绿小动物数字交通工具的图,那就是一个位图图片,由一个个像素组成。
一个位图图像的一个像素点,用RGBA四个值来描述,具体控制是通过一个4*5的矩阵来控制,每个像素点的RGBA又共同组成了位图的展现形式,这就意味着只要我们能控制像素点的RGBA值,就可以改变位图展示的效果。
基于此,Android中常用的对于图像色彩处理的常用方式有两种:
①使用ColorMatrix类来改变RGBA;
②使用专业的像素点算法来定点来改变RGBA。
二、上代码,具体实现通过ColorMatrix类进行色彩处理
我们需要知道在通常的色彩处理中,我们使用以下的专业词汇来描述一个图像:
色调——物体传播的颜色;
饱和度——颜色的纯度,从0(灰)到100%(饱和)来进行描述;
亮度——颜色的相对明暗程度;
在Android中,系统使用一个颜色矩阵——ColorMatrix,来处理图像的这些色彩效果。Android中的颜色矩阵是一个4×5的数字矩阵,它用来对图片的色彩进行处理。而对于每个像素点,都有一个颜色分量矩阵用来保存颜色的RGBA值,如下图所示:
在这个4×5的颜色矩阵中按以下方式划分:
第一行的abcde值用来决定新的颜色值中的R——红色
第二行的fghij值用来决定新的颜色值中的G——绿色
第三行的klmno值用来决定新的颜色值中的B——蓝色
第四行的pqrst值用来决定新的颜色值中的A——透明度
矩阵A中的第五列——ejot值分别用来决定每个分量重的offset,即偏移量。
1)色彩矩阵变化举例:
①改变偏移量
在这个矩阵中修改了R,G所对应的颜色偏移量,那么最后的处理结果就是图像的红色,绿色分量增加了100。而我们知道,红色混合绿色会得到黄色,所以使得整个图像的色调偏黄色。
②改变颜色系数
在这个矩阵中,改变了G分量所对应的系数g,这样的矩阵运算后G分量会变成以前的两倍,最终效果就是图像的色调更加偏绿。
2)使用ColorMatrix类来实现色彩处理:
图像的色调,饱和度,亮度这三个属性在图像处理中的使用非常多,因此颜色矩阵中,也封装了一些API来快速调用这些参数,而不用每次都去计算矩阵的值。
在Android中,系统封装了一个类——ColorMatrix,也就是说前面的颜色矩阵。通过这个类,可以很方便地改变矩阵值来处理颜色效果。官方文档中给出了三种ColorMatrix的构造方法:
ColorMatrix()
Create a new colormatrix initialized to identity (as if reset() had been called).
ColorMatrix(float[] src)
Create a new colormatrix initialized with the specified array of values.
ColorMatrix(ColorMatrix src)
Create a new colormatrix initialized with the specified colormatrix.
//通常情况下使用第一种即可,即ColorMatrix colorMatrix = new ColorMatrix();
①对于色调的处理,色调是传播出的颜色,所以RGB三个值都可以进行处理,Android系统提供了setRotate(int axis, float degree)来帮助我们设置颜色的色调。第一个参数,系统分别使用0、1、2来代表Red、Green、Blue三种颜色的处理;而第二个参数,就是需要处理的值,代码如下:
ColorMatrix hueMatrix = new ColorMatrix();
hueMatrix .setRotate(0,hue0);
hueMatrix .setRotate(1,hue1);
hueMatrix .setRotate(2,hue2);
②对于饱和度的处理,Android系统提供了setSaturation(float sat)方法来设置颜色的饱和度,参数代表设置颜色饱和度的值,当饱和度为0时,图像就变成灰度图像了,代码如下:
ColorMatrix saturationMatrix=new ColorMatrix();
saturationMatrix.setSaturation(saturation);
③对于亮度的处理,当三原色以相同的比例进行混合的时候,就会显示出白色,系统正式使用这个原理来改变一个图像的亮度的,代码如下,当亮度为0时,图像就变成全黑了,处理的代码如下:
ColorMatrix lumMatrix=new ColorMatrix();
lumMatrix.setScale(lum,lum,lum,1);
④色彩的混合处理,除了单独使用上面三种方式来进行颜色效果的处理之外,Android系统还封装了矩阵的乘法运算。它提供了postConcat()方法来将矩阵的作用效果混合,从而叠加处理效果,代码如下:
//将矩阵的作用效果混合,从而叠加处理效果
ColorMatrix imageMatrix=new ColorMatrix();
imageMatrix.posConcat(hueMatrix);
imageMatrix.posConcat(saturationMatrix);
imageMatrix.posConcat(lumMatrix);
paint.setColorFilter(new ColorMatrixColorFilter(imageMatrix));
canvas.drawBitmap(bitmap,0,0,paint);
最后通过paint.setColorFilter(newColorMatrixColorFilter(imageMatrix))设置给paint,并使用这个画笔来绘制原来的图像,从而将颜色矩阵作用到原图上,代码如下:
paint.setColorFilter(new ColorMatrixColorFilter(imageMatrix));
canvas.drawBitmap(bitmap,0,0,paint);
下面给出《Android群英传》中通过SeekBar调整色彩的经典代码段:
public class MainActivity extends AppCompatActivity implements SeekBar.OnSeekBarChangeListener{
private static final int MAX_VALUE = 255;
private static final int MID_VALUE = 127;
private ImageView mImageView;
private float mHue, mSaturation, mLum;
private Bitmap mBitmap;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.shuicai);
mImageView = (ImageView) findViewById(R.id.imageview);
SeekBar seekBarHue = (SeekBar) findViewById(R.id.seekbarHue);
SeekBar seekBarSaturation = (SeekBar) findViewById(R.id.seekbarSaturation);
SeekBar seekBarLum = (SeekBar) findViewById(R.id.seekbarLum);
seekBarHue.setOnSeekBarChangeListener(this);
seekBarSaturation.setOnSeekBarChangeListener(this);
seekBarLum.setOnSeekBarChangeListener(this);
//设置最大值
seekBarHue.setMax(MAX_VALUE);
seekBarSaturation.setMax(MAX_VALUE);
seekBarLum.setMax(MAX_VALUE);
//设置进度
seekBarHue.setProgress(MID_VALUE);
seekBarSaturation.setProgress(MID_VALUE);
seekBarLum.setProgress(MID_VALUE);
mImageView.setImageBitmap(mBitmap);
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
switch (seekBar.getId()) {
case R.id.seekbarHue://色调
mHue = (progress - MID_VALUE) * 1.0f / MID_VALUE * 180;
break;
case R.id.seekbarSaturation://饱和度
mSaturation = progress * 1.0f / MID_VALUE;
break;
case R.id.seekbarLum://亮度
mLum = progress * 1.0f / MID_VALUE;
break;
}
mImageView.setImageBitmap(ImageHelper.handleImageEffect(mBitmap, mHue, mSaturation, mLum));
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
}
public class ImageHelper {
public static Bitmap handleImageEffect(Bitmap bm, float hue, float saturation, float lum) {
//设置颜色的色调
ColorMatrix hueMatrix = new ColorMatrix();
//第一个参数,系统分别使用0、1、2来代表Red、Green、Blue三种颜色的处理;而第二个参数,就是需要处理的值
hueMatrix.setRotate(0, hue);
hueMatrix.setRotate(1, hue);
hueMatrix.setRotate(2, hue);
//设置颜色的饱和度
ColorMatrix saturationMatrix = new ColorMatrix();
//saturation参数即代表设置颜色的饱和度的值,当饱和度为0时,图像就变成灰度图像了
saturationMatrix.setSaturation(saturation);
//设置颜色的亮度
ColorMatrix lumMatrix = new ColorMatrix();
lumMatrix.setScale(lum, lum, lum, 1);
//将矩阵的作用效果混合,从而叠加处理效果
ColorMatrix imageMatrix = new ColorMatrix();
imageMatrix.postConcat(hueMatrix);
imageMatrix.postConcat(saturationMatrix);
imageMatrix.postConcat(lumMatrix);
/**
* 设置好处理的颜色矩阵后,通过使用Paint类的setColorFilter()方法,将通过imageMatrix构造的
* ColorMatrixColorFilter对象传递进去,并使用这个画笔来绘制原来的图像,从而将颜色矩阵作用到原图中
*/
Paint paint = new Paint();
paint.setColorFilter(new ColorMatrixColorFilter(imageMatrix));
/**
* Android系统也不允许直接修改原图,类似Photoshop中的锁定,必须通过原图创建一个同样大小的Bitmap,并将
* 原图绘制到该Bitmap中,以一个副本的形式来修改图像。
*/
Bitmap bitmap = Bitmap.createBitmap(bm.getWidth(), bm.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawBitmap(bm, 0, 0 ,paint);
return bitmap;
}
}
三、上代码,具体实现通过像素点算法进行色彩处理
作为更加精确的图像处理方式,可以通过改变每个像素点的具体RGBA值,来达到处理一张图像效果的目的。这里要注意的是传递进来的原始图片是不能修改的,一般根据原始图片生成一张新的图片来进行修改。
在Android中,系统提供了Bitmap.getPixels()方法来帮我们提取整个Bitmap中的像素点,并保存到一个数组中:
bitmap.getPixels(pixels,offset,stride,x,y,width,height);
这几个参数的含义如下:
pixels——接收位图颜色值的数组
offset——写入到pixels[]中的第一个像素索引值
stride——pixels[]中的行间距
x——从位图中读取的第一个像素的x坐标值
y——从位图中读取的第一个像素的y坐标值
width——从每一行中读取的像素宽度
height——读取的行数
通常情况下,可以使用如下代码:
bitmap.getPixels(oldPx,0,bm.getWidth(),0,0,width,height);
接下来就可以获取每个像素具体的ARGB了,如下:
color=oldPx[i];
r=Color.red(color);
g=Color.green(color);
b=Color.blue(color);
a=Color.alpha(color);
当获取到具体的颜色值后,就可以通过相应的算法来修改它的ARGB值。如下老照片效果效果:
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);
再通过如下代码将新的RGBA值合成像素点:
newPx[i]=Color.argb(a,r1,g1,b1);
最后通过如下代码,将处理后的像素点数组重新set给我们的Bitmap,从而达到图像处理的目的:
bitmap.setPixels(newPx,0,width,0,0,width,height);
下面以《Android群英传》中底片效果为例,具体实现:
public static Bitmap handleImageNegative(Bitmap bm){
int width = bm.getWidth();
int height - bm.getHeight();
int color;
int r,g,b,a;
Bitmap bmp=Bitmap.createBitmap(width,height,Bitmap.Config.ARGB_8888);
int[]oldPx=new int[width * height];
int[]newPx=new int[width * height];
bm.getPixels(oldPx,0,width,0,0,width,height);
for(int i=0;i<width * height;i++){
color=oldPx[i];
r=Color.red(color);
g=Color.green(color);
b=Color.blue(color);
a=Color.alpha(color);
//
r=255-r;
g=255-g;
b=255-b;
if(r>255){
r=255;
}else if(r<0){
r=0;
}
if(g>255){
g=255;
}else if(g<0){
g=0;
}
if(b>255){
b=255;
}else if(b<0){
b=0;
}
newPx[i]=Color.argb(a,r,g,b);
}
bmp.setPixels(newPx,0,width,0,0,widht,height);
return bmp;
}