在Android开发中,一般对图像的处理就是Bitmap(位图),它包含了图像的全部数据,即点阵和颜色值,点阵就是包含像素点的矩阵,而颜色值就是ARGB,分别代表透明、红色、绿色、蓝色通道,它们共同决定了像素点的颜色,今天我们来讲讲关于改变图像颜色的相关知识点。
先来一张实现效果图:

滤镜演示

颜色矩阵

对于图像来说,每一个像素点都有一个颜色矩阵分量来保存颜色,即下图的RGBA1(矩阵C,1表示颜色的偏移量),而在Android系统中,颜色矩阵是用一个4*5的数字矩阵来表示的(矩阵A,由一维数组构成)。

颜色矩阵

它们的乘积(矩阵R)即为屏幕上显示的图像颜色,这里的RGBA取值应在0~255之间。

颜色矩阵乘积

可能有点懵,没关系,我们来梳理一下:
1、矩阵C,也就是像素点的颜色分量,这个很简单,RGBA1,分别代表红、绿、蓝、透明度和色彩偏移值。
2、矩阵A,它是由一维数组构成的4 * 5的数字矩阵,其中4(行)分别代表RGBA,而5(列)是用来代表决定4(行)RGBA的RGBA和偏移量,有点绕,举个例子,第1行是R,然后这个R所呈现的形式是由abcde来共同决定的,比如红色,它有多种呈现方式,其它3行也是一样,以此类推即可。

根据线性代数的矩阵乘法,我们可以得出:

   R’ = a * R + b * G + c * B + d * A + e;
G’ = f * R + g * G + h * B + i * A + j;
B’ = k * R + l * G + m * B + n * A + o;
A’ = p * R + q * G + r * B + s * A + t;

此时我们得到了新的颜色值R’G’B’A’ ,以第1行R为例,如果我们让a=1,让b、c、d、e=0,此时我们可以推导出R’=1R+0G+0B+0A+0=1R,也就是等于原来的R,依次类推,我们可以构造出下图矩阵,这样使得Android的颜色矩阵乘以像素点的颜色矩阵分量还是等于原来的颜色值,这个矩阵也被称为单位矩阵,一般初始化图像的时候,我们会构建它。

单位矩阵

我们上面说了矩阵R是矩阵A和矩阵C的乘积,即为屏幕上所显示的图像颜色,那么如果我们要改变图像的颜色要通过什么方法呢?哈哈,相信你已经知道了,没错,要么改变矩阵A(Android颜色矩阵),要么改变矩阵C(图像颜色矩阵),我们来写个Demo程序,验证一下,我们构建一个界面,由ImageView和20个EditText组成(矩阵AC乘积),如图所示:

初始图片

现在我们改变下第一行(R通道)的最后一个数值,也就是R通道的颜色偏移值,我们改成100,看看图片效果:

改变红色通道偏移值

我们再来改变下第二行(G通道)的最后一个数值,也就是G通道的颜色偏移值,我们改成100,看看图片效果:

改变绿色通道偏移值

和我们预想的是一样的,因为在某颜色通道上偏移了值,那么该图片也就会向某颜色通道偏移颜色。

再来看下直接改变颜色系数,我们改变第三行(G通道)的值,把1修改成2,看看效果:

改变蓝色通道值

上面的效果验证是成功的,图片偏向了蓝色,当然颜色的改变是可以发生叠加的,比如三原色中,我们知道红色和绿色的混合后是黄色,如下图所示:

三原色

我们试着偏移这两个颜色通道,把R和G的颜色通道都偏移了100,看看效果:

image.png

非常的nice,验证成功,改变图像颜色的原理其实就是这个,虽然谷歌已经给我们提供了相关的类库,我们只需要简单的调用api就可以实现效果,不过我觉得学东西应该知其然更应该知其所以然,因为在未来的很多扩展中,这些原理性的东西会显得格外的重要,万丈高楼平地起,好了,现在我们可以来看下相关api了,这篇文章就围绕着一个类来讲——ColorMatrix。

ColorMatrix

关于这个ColorMatrix,其实它就是我们上面分析的那个4 * 5的数字矩阵,只是谷歌帮我们在这个类中封装好了许多简易的操作方法。
我们先来看下刚才那个Demo程序的实现,我们直接看核心代码:

                ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.set(float[] src);
mImageView.setColorFilter(new ColorMatrixColorFilter(colorMatrix));

首先我们构建出ColorMatrix对象,然后通过set方法把对应的4 * 5数字矩阵(一维数组)传入,根据它我们就可以构建出颜色过滤器ColorMatrixColorFilter,再通过ImageView设置即可,其实重点就在于这个4 * 5数组矩阵的数值,要搭配出好看的颜色滤镜,这个就需要专业做视觉效果和算法的同学提供了,我在Demo程序中整理了一些,文章末尾会给出Demo下载地址。

除了以上简单粗暴的set对应的颜色矩阵,ColorMatrix类中还提供了许多方法,比如可以调整图像的色相、饱和度、灰度等,还是以Demo的形式展开:

调整色相、饱和度、灰度

来看下核心代码:

    /**
* 调整图片的色相,饱和度,灰度
*
* @param srcBitmap
* @param rotate
* @param saturation
* @param scale
* @return
*/
public static Bitmap beautyImage(Bitmap srcBitmap, float rotate, float saturation, float scale) { //调整色相
ColorMatrix rotateMatrix = new ColorMatrix();
rotateMatrix.setRotate(0, rotate);
rotateMatrix.setRotate(1, rotate);
rotateMatrix.setRotate(2, rotate); //调整色彩饱和度
ColorMatrix saturationMatrix = new ColorMatrix();
saturationMatrix.setSaturation(saturation); //调整灰度
ColorMatrix scaleMatrix = new ColorMatrix();
scaleMatrix.setScale(scale, scale, scale, 1); //叠加效果
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.postConcat(rotateMatrix);
colorMatrix.postConcat(saturationMatrix);
colorMatrix.postConcat(scaleMatrix); //创建一个大小相同的空白Bitmap
Bitmap dstBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), Bitmap.Config.ARGB_8888);
//载入Canvas,Paint
Canvas canvas = new Canvas(dstBitmap);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
//绘图
canvas.drawBitmap(srcBitmap, 0, 0, paint); return dstBitmap;
}

依葫芦画瓢,我们构建出ColorMatrix对象,这边提供了三个方法:setRotate、saturationMatrix、scaleMatrix分别代表设置图象的色相、灰度、饱和度,来解释下这3个词:
色相:物体传播的颜色。
饱和度:颜色的纯度,从0到100。
灰度:颜色相对的明暗程度。

我们挑其中一个方法来讲:

    /**
* 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();
}
}

上面是setRotate的源码,根据注释我们可以知道第1个参数需要传入0、1、2,分别代表了RGB三种颜色通道,第2个参数是指程度的大小。从源码中我们可以发现,它的原理就是我们文章前面所提到的4 * 5的颜色矩阵,中间涉及到了一些角度的旋转,大概了解一下,把RGB用三维坐标系来表示,单位长度均为1,然后根据设置第1个参数值和第2个参数值来决定围绕哪个点旋转和旋转多少角度,根据三角函数的换算就可以得到对一个的偏移值了,这个我们了解即可,有兴趣的朋友可以自行查阅资料。剩下的2个方法saturationMatrix,scaleMatrix原理都是一样的,都是对颜色矩阵的数值进行操作而已,大家自行查阅,这里就不贴源码了。
附上滑竿传递值的代码(滑竿值的换算公式,这个是比较经典的做法,我也是通过查资料得知,了解即可):

    @Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
switch (seekBar.getId()) {
case R.id.seekBar_rotate:
mRotate = (mRotateSeekBar.getProgress() - 128f) * 1.0f / 128f * 180;
break;
case R.id.seekBar_saturation:
mSaturation = mSaturationSeekBar.getProgress() / 128f;
break;
case R.id.seekBar_scale:
mScale = mScaleSeekBar.getProgress() / 128f;
break;
} if (mBitmap != null) {
Bitmap bitmap = BeautyUtil.beautyImage(mBitmap, mRotate, mSaturation, mScale);
mImageView.setImageBitmap(bitmap);
} }

更改像素点的RGBA

要改变图片的颜色值,这里还有一种更加精确的做法,即对每个像素点进行RGBA的修改,我们以实现底片的效果为例,来看下核心代码:

    /**
* 通过更改图片像素点的RGBA值,生成底片效果
* @param scrBitmap
* @return
*/
public static Bitmap beautyImage(Bitmap scrBitmap) { int width = scrBitmap.getWidth();
int height = scrBitmap.getHeight();
int count = width * height; int[] oldPixels = new int[count];
int[] newPixels = new int[count]; scrBitmap.getPixels(oldPixels, 0, width, 0, 0, width, height); for (int i = 0; i < oldPixels.length; i++) {
int pixel = oldPixels[i];
int r = Color.red(pixel);
int g = Color.green(pixel);
int b = Color.blue(pixel); r = 255 - r;
g = 255 - g;
b = 255 - b; if (r > 255) {
r = 255;
}
if (g > 255) {
g = 255;
}
if (b > 255) {
b = 255;
} if (r < 0) {
r = 0;
}
if (g < 0) {
g = 0;
}
if (b < 0) {
b = 0;
} newPixels[i] = Color.rgb(r, g, b); } Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.setPixels(newPixels, 0, width, 0, 0, width, height); return bitmap;
}

我们需要知道生成底片的公式(网上有很多成熟的效果公式,有兴趣的朋友可以自行搜索下,这边只是举例,重点讲原理):

r=255-r;
g=255-g;
b=255-b;

首先我们调用Bitmap的getPixels方法,把像素数据存放在int数组里,然后根据Color.red,Color.green,Color.blue方法取出这些像素点的RGB值,然后进行公式的换算和边界值处理,再调用setPixels设置回创建的新的Bitmap,这样就完成了我们的底片效果了,来看下效果图:

底片效果图

补充

到这里文章就结束了,以上的效果因为只是Demo演示,有些细节是没有做处理的,在实际开发中,大家还是内存相关的问题,这边最后再说几句:
1、Bitmap的压缩加载以及回收要注意场合,不是所有时候都需要加载原图的,请根据业务场景合理规划。
2、ColorMatrix只是简单的对RGBA通道做了值的调整,如果想实现更加精细化的效果,还是需要配合底层C++来实现会更好。
3、市面上的一些美颜APP,其实并不是用如上方法来实现的,滤镜是个很复杂的东西,为了更好的渲染性能,达到实时美颜的效果,一半我们会采用GPU(OpenGL)来绘制,GPU可以达到像素点近毫秒级的并发处理,后续我也会写一些相关的文章。

最后来张最近被玩坏的王校长,IG牛逼~

IG牛逼

源码下载:

这里附上源码地址(欢迎Star,欢迎Fork):BeautyImageDemo

Android开发之图像处理那点事——滤镜的更多相关文章

  1. 心酸!30岁深漂失业3个月,从巅峰跌落谷底,大龄Android开发必须要懂的事!

    2021年3月,我的前同事,在我们群里说他准备回老家了,问我们有没有人可以暂时收养他的猫. --他说,这周末就要离开深圳了. 他失业了.3个多月没收入,还要交着房租,过年来之后找了快一个月的工作也没有 ...

  2. Android开发跳槽、简历和面试的那些事

    年后不久,就迎来了一年一度的招聘旺季,尤其,对于互联网行业来说,近些年的3月份被视为换工作的最高峰,已经没什么可以争议的了. 至今为止,在小组Android开发招聘这块,已经面试有近30人了.最后得出 ...

  3. 【Android开发那点破事】打开APP加载页面实现

    今天的破事呢就说说APP加载页面的实现.一般情况下,当APP打开的时候,我们需要做很多事情,比如检查网络连接啊,初始化一些配置啊等等.我们可以让这些事情在APP完全打开之前做完,然后呢在打开的过程中显 ...

  4. [Android开发那点破事]解决android.os.NetworkOnMainThreadException

    [Android开发那点破事]解决android.os.NetworkOnMainThreadException 昨天和女朋友换了手机,我的iPhone 4S 换了她得三星I9003.第一感觉就是好卡 ...

  5. Android 开发你需要了解的那些事

    本文微信公众号「AndroidTraveler」首发. 背景 最近部门有新入职员工,作为规划技术路线的导师,这边给新员工安排了学习路线. 除了基本的学习路线之外,每次沟通,我都留了一个小问题,让小伙伴 ...

  6. Android 开发日常积累

    Android 集合 Android 开源项目分类汇总 扔物线的 HenCoder 高级 Android 教程 hencoder HenCoder:给高级 Android 工程师的进阶手册 Andro ...

  7. 学会这些你就是Android 开发高手了!

    世界范围内,很多移动设备都是Android系统,Android开发可以说很有前景.对于安卓开发者来说,如果有个地方可以找到Android开发所有的资料,不需要一个网站一个网站的搜索,简直是件很开心的事 ...

  8. Android开发常用开源框架:图片处理

    https://blog.csdn.net/SGQ_CSDN/article/details/79910709 Android开发常用开源框架:图片处理 框架名称 功能描述 Android Unive ...

  9. [译]:Xamarin.Android开发入门——Hello,Android Multiscreen深入理解

    原文链接:Hello, Android Multiscreen_DeepDive. 译文链接:Xamarin.Android开发入门--Hello,Android Multiscreen深入理解. 本 ...

随机推荐

  1. Apache配置优化一(查看当前apache数据)

    1.查看当前的运行模块 httpd -l 2.查看httpd进程数 ps -ef | grep httpd | wc -l 3.查看请求80服务的client ip按照连接数排序 netstat -n ...

  2. Intellij IDEA 2016.3.4 注册激活--转

    对于Intellij IDEA 2016.3.4  可以填写注册server http://jetbrains.tech 来激活. 参考:https://www.haxotron.com/jetbra ...

  3. Node.js使用MySQL连接池示例

    下面是一个封装好的工具类: var fs = require('fs'); var mysql = require('mysql'); var pool = mysql.createPool({ ho ...

  4. Xena L23网络测试仪Valkyrie使用技巧100例:使用Xena官方在线演示设备 (编号00)

    需求# 1.新用户:没有硬件,想看看软件长什么样,好不好用,风格如何,怎么办? 2.代理商:没有硬件,想给客户Show一下Xena高大上的软件,怎么办? 3.老用户:邮件推送了新的软件版本,据说很多新 ...

  5. pip3 install pyinstaller 报错了的处理方法

    http://www.pyinstaller.org/downloads.html 下载压缩包 解压到本地后,在目录处cmd 执行命令 python setup.py install 然后执行pip ...

  6. Direct2D 学习笔记(3)图层 Layer

    利用图层Layer绘制资源网址:https://docs.microsoft.com/zh-cn/windows/win32/direct2d/direct2d-layers-overview   1 ...

  7. vscode常用插件小结

    工欲善其事,必先利其器. 个人用过的代码编辑器有sublime,webstrom,vscode,H5builder.综合比较下来还是更倾向于vscode. sublime是一款轻量级的编辑器,优点是启 ...

  8. coverage代码覆盖率的使用~~

    我们看下代码覆盖率的统计~,这个不必太揪心,觉得可以帮助你优化代码,可以看看,也不要带在意~ 1.先在cmd命令窗口在线安装coverage pip install coverage 2.安装完毕后我 ...

  9. Redis(1.16)Redis监控为什么是单线程?为什么快?

    [1]Redis的高并发和快速原因 1.redis是基于内存的,内存的读写速度非常快: 2.redis是单线程的,省去了很多上下文切换线程的时间: 3.redis使用多路复用技术,可以处理并发的连接. ...

  10. eclipse3.62开发第一个java程序HelloWorld

    [学习笔记] 用eclipse3.62开发第一个java程序: 使用eclipse之前们需要先配置一下jdk.window/preference/java/installed JREs,详细请见视频. ...