天天看点

Android色彩特效处理之色调、饱和度、亮度、ColorMatrix精炼详解

Android色彩特效处理之色调、饱和度、亮度、ColorMatrix精炼详解

一、前期基础知识储备

Bitmap(位图文件),扩展名可以是.bmp或者.dib。位图是Windows标准格式图形文件,它将图像定义为由点(像素)组成,每个点可以由多种色彩表示,包括2、4、8、16、24和32位色彩。想象一下你以前测红绿色盲的时候,王医生拿给你看的那张红红绿绿小动物数字交通工具的图,那就是一个位图图片,由一个个像素组成。

Android色彩特效处理之色调、饱和度、亮度、ColorMatrix精炼详解
Android色彩特效处理之色调、饱和度、亮度、ColorMatrix精炼详解

一个位图图像的一个像素点,用RGBA四个值来描述,具体控制是通过一个4*5的矩阵来控制,每个像素点的RGBA又共同组成了位图的展现形式,这就意味着只要我们能控制像素点的RGBA值,就可以改变位图展示的效果。

基于此,Android中常用的对于图像色彩处理的常用方式有两种:

①使用ColorMatrix类来改变RGBA;

②使用专业的像素点算法来定点来改变RGBA。

二、上代码,具体实现通过ColorMatrix类进行色彩处理

Android色彩特效处理之色调、饱和度、亮度、ColorMatrix精炼详解

我们需要知道在通常的色彩处理中,我们使用以下的专业词汇来描述一个图像:

色调——物体传播的颜色;

饱和度——颜色的纯度,从0(灰)到100%(饱和)来进行描述;

亮度——颜色的相对明暗程度;

在Android中,系统使用一个颜色矩阵——ColorMatrix,来处理图像的这些色彩效果。Android中的颜色矩阵是一个4×5的数字矩阵,它用来对图片的色彩进行处理。而对于每个像素点,都有一个颜色分量矩阵用来保存颜色的RGBA值,如下图所示:

Android色彩特效处理之色调、饱和度、亮度、ColorMatrix精炼详解

在这个4×5的颜色矩阵中按以下方式划分:

第一行的abcde值用来决定新的颜色值中的R——红色

第二行的fghij值用来决定新的颜色值中的G——绿色

第三行的klmno值用来决定新的颜色值中的B——蓝色

第四行的pqrst值用来决定新的颜色值中的A——透明度

矩阵A中的第五列——ejot值分别用来决定每个分量重的offset,即偏移量。

1)色彩矩阵变化举例:

①改变偏移量

Android色彩特效处理之色调、饱和度、亮度、ColorMatrix精炼详解

在这个矩阵中修改了R,G所对应的颜色偏移量,那么最后的处理结果就是图像的红色,绿色分量增加了100。而我们知道,红色混合绿色会得到黄色,所以使得整个图像的色调偏黄色。

②改变颜色系数

Android色彩特效处理之色调、饱和度、亮度、ColorMatrix精炼详解

在这个矩阵中,改变了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;
    }
}
           

三、上代码,具体实现通过像素点算法进行色彩处理

Android色彩特效处理之色调、饱和度、亮度、ColorMatrix精炼详解

作为更加精确的图像处理方式,可以通过改变每个像素点的具体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;
}
           

继续阅读