Android中ImageView的ColorFilter图像处

作者: C6C | 来源:发表于2018-04-08 23:08 被阅读236次

ImageView堪称是Android开发中最为常用的控件之一,但是在大多数情境下对其的使用都是最基础的展示一张源图片,可能部分童鞋不知道,ImageView可以直接对源图片进行特定的算法处理,ImageView的setColorFilter(ColorFilter colorFilter)方法可以满足大部分的图片的简单处理,这一过程甚至连自定义View都不需要。

1、ColorFilter简述

ColorFilter:通过Paint来修改图片每个像素的颜色,相当于可以操作图片中每个像素的颜色,可以达到你想要的图片处理效果,如图所示。


要达到这个效果只需要一句代码:imageView.setColorFilter(new LightingColorFilter(0x62ffff, 0x6f6100));

使用起来可以说是极其简单,只需通过setColorFilter(ColorFilter colorFilter)这个方法来实现,关键点在于ColorFilter这个类,ColorFilter是一个抽象类,不能直接实例化使用,我们使用其三个实现类LightingColorFilterPorterDuffColorFilterColorMatrixColorFilter

2、LightingColorFilter的使用

2.1、效果展示

可以先看一下LightingColorFilter可以实现的效果,然后具体再讲。

LightingColorFilter.gif

LightingColorFilter相当于ColorMatrixColorFilter的简单版,通过构造方法 LightingColorFilter(int mul, int add) 传入和颜色值格式相同的 int 值,其中mul用来和目标像素相乘,add用来和目标像素相加。

2.2、像素点颜色修改

需要提到一个点,是怎么通过mul和add来修改一个像素点的,我们知道一张图片在Android里头是一个Bitmap格式的位图,里头包含了图片所有的像素信息,比如一张100X100的图片,其包含了100X100=10000(个)像素点。
Android里头图片的默认格式是ARGB_8888格式的,意思是每个像素点包含以下四个通道值:透明度大小(A)、红色值大小(R),绿色值大小(G),蓝色值大小(G),每个值占8位内存,也就是0~255,所以我们如果要修改一个像素的值,需要同时操作像素点的四个分量,而在Android中,这个修改过程是通过ColorMatrix矩阵来实现的。


以上就是一个ColorMatrix,是一个4*5(4行5列)的矩阵,在Android中用一个数组来存储矩阵的20个值,再看一下每个像素点的颜色值表示:

每个像素点相当于一个向量(或者看做是5行1列的矩阵),除了上面讲的4个RGBA分量之外还多了一个值为1的分量,这是为了计算颜色值的偏移量,像素点的修改其实就是矩阵与向量的乘积:

经过矩阵和向量的线性代数基本运算之后,我们就得到后变换之后的颜色值,即:

R.r = aR + bG + cB + dA + e;  ->矩阵A的第一行a, b, c, d, e决定了变换后的R值
R.g = fR + gG + hB + iA + j;  ->矩阵A的第二行f, g, h, i, j决定了变换后的G值
R.b = kR + lG + mB + nA + o;  ->矩阵A的第三行k, i, m, n, o决定了变换后的B值
R.a = pR + qG + rB + sA + t;  ->矩阵A的第四行p, q, r, s, t决定了变换后的A值

简单点说的话,ColorMatrix就是为了方便的计算出颜色的变换值,没有矩阵的话要实现每一个像素点的四个分量的操控是极为困难和繁杂的,通过矩阵可以极其巧妙的实现了这个效果,如图所示,只需要一个计算公式。
值得提一下的是,像素点向量的第五个分量1可以通过矩阵A的第五列(是列不是行)e,j, o, t值实现颜色变换的偏移效果。

2.3、LightingColorFilter具体使用

LightingColorFilter可用于模拟简单的照明效果,通过构造函数LightingColorFilter(int mul, int add) 的两个参数值来实现,一个参数用于和源像素点颜色相乘,另一个用于和像素点源颜色相加:

R' = R * mul.R / 0xff + add.R  
G' = G * mul.G / 0xff + add.G  
B' = B * mul.B / 0xff + add.B 

可以看到的一点是计算公式里头没有涉及到A(透明度)值,也就是设置A(透明度)值对LightingColorFilter没有影响,不信的可以根据Demo效果图自己试玩一下,还有就是mul传入0xffffff,add传入0x000000值时颜色值保持不变。
所以要使用LightingColorFilter把两个颜色值传入即可:

@Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {

        //透明度也加上,透明度不参与计算,所以设置了也是默认无效的
        ...
        int plusColor = Color.argb(sBPlusA.getProgress(), sBPlusR.getProgress(), sBPlusG.getProgress(), sBPlusB.getProgress());
        tvPlusColorText.setText(plusText);
        tvPlusColor.setBackgroundColor(plusColor);

        ...
        int addColor = Color.argb(sBAddA.getProgress(), sBAddR.getProgress(), sBAddG.getProgress(), sBAddB.getProgress());
        tvAddColorText.setText(addText);
        tvAddColor.setBackgroundColor(addColor);
        
        ImageUtil.displayImageLighting(imageView, plusColor, addColor);
    }

/**
     * 通过乘积和加来操作的LightingColorFilter
     *
     * @param mul 乘积分量
     * @param add 加法分量
     */
    public static void displayImageLighting(ImageView imageView, int mul, int add) {
        imageView.setColorFilter(new LightingColorFilter(mul, add));
    }

比如你mul传入的颜色值是0x62ffaa,其中0x62就是mul.R值,0xff就是mul.G值, 0xaa就是mul.B值,add值同理。
通过效果图中的SeekBar可以试验出LightingColorFilter的所有效果,感兴趣的强烈建议自己试玩一下,这样可以更直观的理解。

3、PorterDuffColorFilter的使用

PorterDuffColorFilter的作用是通过构造方法PorterDuffColorFilter(int color, PorterDuff.Mode mode)传入源颜色值和PorterDuff.Mode来对图片的像素点颜色进行合成。
PorterDuff.Mode不熟悉的可以看下官网的介绍,指定不同的PorterDuff.Mode可以实现源颜色值和图片进行不同方式的混合来实现不同的效果。

PorterDuff.Mode主要分为两类:

  • 透明度合成模式(Alpha compositing modes):主要有CLEAR、SRC、DST、SRC_OVER、DST_OVER、SRC_IN、DST_IN、SRC_OUT、DST_OUT、SRC_ATOP、DST_ATOP;
  • 混合模式(Blending modes):主要有DARKEN、LIGHTEN、MULTIPLY、SCREEN、ADD、OVERLAY;

透明度合成模式在PorterDuffColorFilter中其实是没啥效果的,因为源颜色值和目标图片只有一方能够显示出来,一方会覆盖另一方,所以在PorterDuffColorFilter中能真正有用的PorterDuff.Mode是混合模式那一类,可以看下具体效果。

PoterDuffColorFilter.gif

可以通过SeekBar实现源颜色值,和选中不同的Mode来实现不同效果,这个效果实现起来也是很方便,这里只贴出关键代码:

@Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        ...
        mColor = Color.argb(sBA.getProgress(), sBR.getProgress(), sBG.getProgress(), sBB.getProgress());
        ...
        ImageUtil.displayImagePorterDuff(imageView, mColor, mode);
    }

/**
     * 通过源颜色值和PorterDuff模式来操作的PorterDuffColorFilter
     *
     * @param color 源颜色值
     * @param mode  PorterDuff模式
     */
    public static void displayImagePorterDuff(ImageView imageView, @ColorInt int color, @NonNull PorterDuff.Mode mode) {
        imageView.setColorFilter(new PorterDuffColorFilter(color, mode));
    }

可以自己试玩一下效果,可以试验出PorterDuffColorFilter所有能达到的效果。

4、ColorMatrixColorFilter的使用

ColorMatrixColorFilter的使用相比于LightingColorFilterPorterDuffColorFilter就强大多了,LightingColorFilterPorterDuffColorFilter就相当于ColorMatrixColorFilter的一个子集,算是简化版。
前面提到对图片中的像素点颜色进行转换需要用到一个叫做ColorMatrix的矩阵,LightingColorFilterPorterDuffColorFilter是你把颜色值传过去,本地代码帮你构建一个ColorMatrix对象,而在ColorMatrixColorFilter中就需要你自己构建一个ColorMatrix对象传进去。

4.1、ColorMatrix对象使用方式一

在彩色图片中,我们都知道表达一个像素点的颜色用RGB三原色来表示,但是这只是一种表达颜色的方式,还有其它的方式来表示颜色值,ColorMatrix可以帮助你通过HSI来表达颜色值,具体来讲就是:

  • 色调(Hue)—物体本来的颜色,比如说这个气球是红色的,指的就是这个;
  • 饱和度(Saturation)—颜色的纯度,灰度占的比例,灰度值越小颜色越纯,比如说这个气球颜色有些淡;
  • 亮度(Brightness)—侧重于表达明暗,比如在晚上和中午看到同一个气球是感觉不一样的;

ColorMatrix提供了API来操作生成指定的矩阵:

private static ColorMatrix colorMatrix = new ColorMatrix();
    /**
     * 色调,改变颜色
     */
    private static ColorMatrix hueMatrix = new ColorMatrix();
    /**
     * 饱和度,改变颜色的纯度
     */
    private static ColorMatrix saturationMatrix = new ColorMatrix();
    /**
     * 亮度,控制明暗
     */
    private static ColorMatrix brightnessMatrix = new ColorMatrix();

/**
     * 通过色调、饱和度、亮度来操作ImageView的ColorMatrixColorFilter
     *
     * @param hueValue        色调值
     * @param saturationValue 饱和度值
     * @param brightnessValue 亮度值
     */
    public static void displayImageColorMatrixHSB(ImageView imageView, float hueValue, float saturationValue, float brightnessValue) {
        //设置色相,为0°和360的时候相当于原图
        hueMatrix.reset();
        hueMatrix.setRotate(0, hueValue);
        hueMatrix.setRotate(1, hueValue);
        hueMatrix.setRotate(2, hueValue);

        //设置饱和度,为1的时候相当于原图
        saturationMatrix.reset();
        saturationMatrix.setSaturation(saturationValue);

        //亮度,为1的时候相当于原图
        brightnessMatrix.reset();
        brightnessMatrix.setScale(brightnessValue, brightnessValue, brightnessValue, 1);

        //将上面三种效果和选中的模式混合在一起
        colorMatrix.reset();
        colorMatrix.postConcat(hueMatrix);
        colorMatrix.postConcat(saturationMatrix);
        colorMatrix.postConcat(brightnessMatrix);

        imageView.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
    }

现在需要具体解释一下这些操作的意义。

4.1.1、ColorMatrix.reset():矩阵初始化

ColorMatrix.reset()的作用是重新初始化矩阵,啥意思呢,看一下上面提到过的图:

还记得这个图吗,就是矩阵和向量相乘来变换颜色值,其中A矩阵有一个特殊的形式,也就是:

把这个矩阵代入公式进行计算一下:

R.r = aR + bG + cB + dA + e = 1*R + 0*G + 0*B + 0*A + 0 = R;  
R.g = fR + gG + hB + iA + j = 0*R + 1*G + 0*B + 0*A + 0 = G;  
R.b = kR + lG + mB + nA + o = 0*R + 0*G + 1*B + 0*A + 0 = B; 
R.a = pR + qG + rB + sA + t = 0*R + 0*G + 0*B + 1*A + 0 = A;

相信已经看出来,这个特殊矩阵不会改变颜色值,我们称之为单位矩阵,通常把它当做初始化矩阵,ColorMatrix.reset()作用就是把矩阵变成单位矩阵,看源码验证一下:

ColorMatrix.java
/**
     * Set this colormatrix to identity:
     * <pre>
     * [ 1 0 0 0 0   - red vector
     *   0 1 0 0 0   - green vector
     *   0 0 1 0 0   - blue vector
     *   0 0 0 1 0 ] - alpha vector
     * </pre>
     */
    public void reset() {
        final float[] a = mArray;
        //把数组的全部项置零
        Arrays.fill(a, 0);
        //构建初始化矩阵
        a[0] = a[6] = a[12] = a[18] = 1;
    }
4.1.2、ColorMatrix.setRotate():色调配置

ColorMatrix通过setRotate(int axis, float degrees)来修改颜色的色调值,第一个参数代表颜色通道,0、1、2分别代表红、绿、蓝,第二个参数是要调整的大小,需要传入旋转的角度值,这是因为ColorMatrix是通过沿着不同的轴旋转的角度值来改变颜色的,这涉及到了旋转矩阵的知识,沿着各轴的旋转矩阵如下:

沿着R轴旋转
沿着G轴旋转
沿着B轴旋转

需要旋转颜色值的话,只需要构建出旋转矩阵即可,可以看下源码,发现源码做的就是这个事情:

ColorMatrix.java
/**
     * 沿着特定颜色轴旋转指定值
     * 
     * axis=0 围绕红色轴旋转
     * axis=1 围绕绿色轴旋转
     * axis=2 围绕蓝色轴旋转
     */
    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) {
        // 围绕红色轴旋转
        case 0:
            mArray[6] = mArray[12] = cosine;
            mArray[7] = sine;
            mArray[11] = -sine;
            break;
        // 围绕绿色轴旋转
        case 1:
            mArray[0] = mArray[12] = cosine;
            mArray[2] = -sine;
            mArray[10] = sine;
            break;
        // 围绕蓝色轴旋转
        case 2:
            mArray[0] = mArray[6] = cosine;
            mArray[1] = sine;
            mArray[5] = -sine;
            break;
        default:
            throw new RuntimeException();
        }
    }

因为传入的是角度值,所以我们只需要传入一个周期的角度就可以尝试出所有的效果,我这里传进去的是[0°, 360°]。

4.1.3、ColorMatrix.setSaturation():饱和度配置

进源码里头看一下

ColorMatrix.java
/**
     * 设置矩阵饱和度
     *
     * @param sat的值为0时颜色是灰度图,1的时候不改变颜色值
     */
    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;
    }

通过源码可以知道系统是通过改变颜色矩阵中每个颜色分量的系数值来改变饱和度的,可以把这个最后构建成的矩阵展示出来。

饱和度调节矩阵

分别把当sat为0和1时代入进行计算,就知道为啥sat的值为0时颜色是灰度图,sat的值为1的时候不改变颜色值了。

4.1.4、ColorMatrix.setScale():亮度配置

ColorMatrix.setScale(float rScale, float gScale, float bScale,float aScale)方法可以按照不同的系数来控制四个通道分量值,可以看下其源码:

    /**
     * 设置颜色矩阵,按指定值进行缩放
     */
    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;
    }

把这个矩阵构建出来看一下:

亮度调整矩阵

当RGB三原色以相同的比例进行混合时显示出来的就是白色,所以通过同比例调节就可以控制颜色的亮度值,所以我们通过传入同样得系数就可以对图像进行亮度调整,如下:

        //亮度,为1的时候相当于原图
        brightnessMatrix.reset();
        brightnessMatrix.setScale(brightnessValue, brightnessValue, brightnessValue, 1);
4.1.5、ColorMatrix.postConcat():效果组合

ColorMatrix.(ColorMatrix postmatrix)封装了矩阵的乘法运算,用来将矩阵的作用效果混合,从而可以把效果叠加。

        //将上面三种效果和选中的模式混合在一起
        colorMatrix.reset();
        colorMatrix.postConcat(hueMatrix);
        colorMatrix.postConcat(saturationMatrix);
        colorMatrix.postConcat(brightnessMatrix);

这里的话,就是把色调调整、饱和度调整、亮度调整效果进行了叠加构建新的矩阵,其实就是进行了矩阵的乘法操作。

4.2、ColorMatrix对象使用方式二

前面的ColorMatrix提供了自带的API来进行图像处理,除此之外我们可以自己提供ColorMatrix来达到想要的效果。
比如说我想要一种图像反转的效果:

底片反转
  • 1、那就需要查一下底片反转的算法,我找到的一个算法是这样的:
 R = 255 - 1*R;
 G = 255 - 1*G;
 B = 255 - 1*B;
  • 2、然后就需要针对这个算法构建ColorMatrix这个颜色矩阵:
    图片反转矩阵
  • 3、现在只需要针对这个矩阵在代码里头实现一下:
/**
     * 底片反转效果
     * <p>
     * R = 255 - 1*R;
     * G = 255 - 1*G;
     * B = 255 - 1*B;
     * A = A;
     * <p>
     */
    private static final float[] DIPIAN = new float[]{
            -1, 0, 0, 0, 255,
            0, -1, 0, 0, 255,
            0, 0, -1, 0, 255,
            0, 0, 0, 1, 0
    };
  • 4、使用的时候把矩阵传进去就完成了:
    /**
     * 针对特定ColorMatrixColorFilter实现特殊效果
     */
    public static void displayImageColorMatrix(ImageView imageView, int mode) {
        float[] matrix = SpecialColorMatrix.getDefault();
        switch (mode) {
            case SpecialColorMatrix.MODE.DEFAULT:
                matrix = SpecialColorMatrix.getDefault();
                break;
            case SpecialColorMatrix.MODE.HUAIJIU:
                matrix = SpecialColorMatrix.getHuaiJiu();
                break;
            case SpecialColorMatrix.MODE.DIPIAN:
                matrix = SpecialColorMatrix.getDiPian();
                break;
            case SpecialColorMatrix.MODE.GRAY:
                matrix = SpecialColorMatrix.getGray();
                break;
            case SpecialColorMatrix.MODE.BRIGHT:
                matrix = SpecialColorMatrix.getBright();
                break;
            default:
        }
        imageView.setColorFilter(new ColorMatrixColorFilter(new ColorMatrix(matrix)));
    }

我在代码里头封装了几种常用效果的算法矩阵,以下是组合起来的效果图。


总结一下就是ColorFilter的三个子类的应用,难点还是在于矩阵的理解,内容比较多,最好的理解方式还是自己试一试,项目的地址https://github.com/JieYuShi/ImageViewProcess;

相关文章

网友评论

  • SamanLan:你还没说前后乘
    C6C:被发现了,讲矩阵乘法太麻烦了

本文标题:Android中ImageView的ColorFilter图像处

本文链接:https://www.haomeiwen.com/subject/imglhftx.html