飞呀
1.色彩矩阵
图片是由点阵和颜色值组成的,点阵是一个包含像素的举证,每个元素对应着图片的一个像素。当我知道颜色和矩阵有关时比较惊讶,大学时学的线性代数在这里用上了。
在Android中处理图像的色彩效果的类是颜色矩阵ColorMatrix。颜色矩阵是一个4x5的数字矩阵。对于每个像素点,都有一个颜色分量矩阵用来保存颜色的RGBA值。
A
=
{
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
r
s
t
}
C
=
{
R
G
B
A
1
}
A= \left\{ \begin{matrix} a & b & c & d & e\\ f & g & h & i & j \\ k & l & m & n & o \\ p & q & r & s & t \end{matrix} \right\} C= \left\{ \begin{matrix} R \\ G \\ B \\ A \\ 1 \end{matrix} \right\}
A=⎩⎪⎪⎨⎪⎪⎧afkpbglqchmrdinsejot⎭⎪⎪⎬⎪⎪⎫C=⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧RGBA1⎭⎪⎪⎪⎪⎬⎪⎪⎪⎪⎫
矩阵A是一个4x5的颜色矩阵,在Android中使用一维数组的形式来存储[a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t]。矩阵C是一个颜色矩阵分量。
使用矩阵乘法运算AC来处理颜色分量矩阵,矩阵A、C,乘法运算过程,如下。
R
=
{
R
1
G
1
B
1
A
1
}
=
A
C
{
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
r
s
t
}
{
R
G
B
A
1
}
=
{
a
R
b
G
c
B
d
A
e
f
R
g
G
h
B
i
A
j
R
k
G
l
B
m
A
n
o
R
p
G
q
B
r
A
s
t
}
=
{
R
1
G
1
B
1
A
1
}
R =\left\{ \begin{matrix} R1\\G1 \\ B1 \\ A1 \end{matrix} \right\} =AC \\ \left\{ \begin{matrix} a & b & c & d & e\\ f & g & h & i & j \\ k & l & m & n & o \\ p & q & r & s & t \end{matrix} \right\} \left\{ \begin{matrix} R\\G \\ B \\ A \\1 \end{matrix} \right\} =\left\{ \begin{matrix} aR & bG & cB & dA & e\\ fR & gG & hB & iA & j \\ Rk & Gl & Bm & An & o \\ Rp & Gq & Br & As & t \end{matrix} \right\} =\left\{ \begin{matrix} R1\\G1 \\ B1 \\ A1 \end{matrix} \right\}
R=⎩⎪⎪⎨⎪⎪⎧R1G1B1A1⎭⎪⎪⎬⎪⎪⎫=AC⎩⎪⎪⎨⎪⎪⎧afkpbglqchmrdinsejot⎭⎪⎪⎬⎪⎪⎫⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧RGBA1⎭⎪⎪⎪⎪⎬⎪⎪⎪⎪⎫=⎩⎪⎪⎨⎪⎪⎧aRfRRkRpbGgGGlGqcBhBBmBrdAiAAnAsejot⎭⎪⎪⎬⎪⎪⎫=⎩⎪⎪⎨⎪⎪⎧R1G1B1A1⎭⎪⎪⎬⎪⎪⎫
R1 = a x R + b x G + c x B + d x A + e;
G1 = f x R + g x G + h x B + i x A + j;
B1 = k x R + l x G + m x B + n x A +o;
A1 = p x R + q x G + r x B + s x A + t;
A
=
{
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
r
s
t
}
A= \left\{ \begin{matrix} a & b & c & d & e\\ f & g & h & i & j \\ k & l & m & n & o \\ p & q & r & s & t \end{matrix} \right\}
A=⎩⎪⎪⎨⎪⎪⎧afkpbglqchmrdinsejot⎭⎪⎪⎬⎪⎪⎫
- 第一行的abcde值用来决定新的颜色值中的R——红色
- 第二行的fghij值用来决定新的颜色值中的G——绿色
- 第三行的klmno值用来决定新的颜色值中的B——蓝色
- 第四行的pqrst值用来决定新的颜色值中的A——透明色
第五列ejot值分别用来决定每个分量中的offset,即偏移量
R1 = a x R + b x G + c x B + d x A + e,令a=1,b、c、d、e都等于0,结果为R1 =R。构造一个矩阵,如下。
A
=
{
1
0
0
0
0
0
1
0
0
0
0
0
1
0
0
0
0
0
1
0
}
A= \left\{ \begin{matrix} 1 & 0 & 0 & 0 & 0\\ 0 & 1 & 0 & 0 & 0 \\ 0 &0 & 1& 0& 0 \\ 0 & 0 & 0 & 1 & 0 \end{matrix} \right\}
A=⎩⎪⎪⎨⎪⎪⎧10000100001000010000⎭⎪⎪⎬⎪⎪⎫
如果把这个矩阵代入R1=AC,R1=R。这个矩阵通常被用来作为初始颜色矩阵来使用,不会对原有颜色值进行任何变化。
改变颜色的两种方法:一是直接改变颜色的offset,二是改变对应RGBA的系数。
1.1改变偏移量
A
=
{
1
0
0
0
0
0
1
0
0
0
0
0
1
0
0
0
0
0
1
0
}
A
=
{
1
0
0
0
100
0
1
0
0
100
0
0
1
0
0
0
0
0
1
0
}
A= \left\{ \begin{matrix} 1 & 0 & 0 & 0 & 0\\ 0 & 1 & 0 & 0 & 0 \\ 0 &0 & 1& 0& 0 \\ 0 & 0 & 0 & 1 & 0 \end{matrix} \right\} A= \left\{ \begin{matrix} 1 & 0 & 0 & 0 & 100\\ 0 & 1 & 0 & 0 & 100 \\ 0 &0 & 1& 0& 0 \\ 0 & 0 & 0 & 1 & 0 \end{matrix} \right\}
A=⎩⎪⎪⎨⎪⎪⎧10000100001000010000⎭⎪⎪⎬⎪⎪⎫A=⎩⎪⎪⎨⎪⎪⎧100001000010000110010000⎭⎪⎪⎬⎪⎪⎫
红色、绿色分量增加了100,红绿混合会得到黄色,最后图片颜色偏黄。
1.2改变颜色系数
A
=
{
1
0
0
0
0
0
1
0
0
0
0
0
1
0
0
0
0
0
1
0
}
A
=
{
1
0
0
0
0
0
2
0
0
0
0
0
1
0
0
0
0
0
1
0
}
A= \left\{ \begin{matrix} 1 & 0 & 0 & 0 & 0\\ 0 & 1 & 0 & 0 & 0 \\ 0 &0 & 1& 0& 0 \\ 0 & 0 & 0 & 1 & 0 \end{matrix} \right\} A= \left\{ \begin{matrix} 1 & 0 & 0 & 0 & 0\\ 0 & 2 & 0 & 0 & 0\\ 0 &0 & 1& 0& 0 \\ 0 & 0 & 0 & 1 & 0 \end{matrix} \right\}
A=⎩⎪⎪⎨⎪⎪⎧10000100001000010000⎭⎪⎪⎬⎪⎪⎫A=⎩⎪⎪⎨⎪⎪⎧10000200001000010000⎭⎪⎪⎬⎪⎪⎫
改变的绿色,图片颜色偏绿,后面会有相应的展示。
2.使用ColorMatrix来改变图片的色光属性
2.1 色调
setRotate(int axis, float degrees),axis使用0、1、2来代表R、G、B三种颜色。degrees处理的值。
ColorMatrix hueMatrix = new ColorMatrix();
hueMatrix.setRotate(0, hue);
hueMatrix.setRotate(1, hue);
hueMatrix.setRotate(2, hue);
2.2 饱和度
setSaturation(float sat),当sat为0时,图片就变成黑白图片了。
ColorMatrix saturationMatrix = new ColorMatrix();
saturationMatrix.setSaturation(saturation);
2.3 亮度
setScale(float rScale, float gScale, float bScale,float aScale),当三原色以相同比例进行混合时,就会显示出白色,系统正是使用这个原理来改变一个图像的亮度。当亮度为0时,图片就变为黑色了。
ColorMatrix lumMatrix = new ColorMatrix();
lumMatrix.setScale(lum, lum, lum, 1);
2.4 效果混合postConcat
将上面三种ColorMatrix 叠加,需要通过ColorMatrix 的postConcat方法。
ColorMatrix imageMatrix = new ColorMatrix();
imageMatrix.postConcat(hueMatrix);
imageMatrix.postConcat(saturationMatrix);
imageMatrix.postConcat(lumMatrix);
2.5 示例
通过三个seekbar来控制图片效果的变化。
seekbar的监听事件
//MID_VALUE =127 seekbar最大值255
// float mHue = 0;
// float mStauration = 1.0f;
// float mLum = 1.0f;
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
int id = seekBar.getId();
switch (id) {
case R.id.sb_1:
mHue = (progress - MID_VALUE) * 1.0F / MID_VALUE * 180;
break;
case R.id.sb_2:
mStauration = progress * 1.0F / MID_VALUE;
break;
case R.id.sb_3:
mLum = progress * 1.0F / MID_VALUE;
break;
}
iv.setImageBitmap(handleImageEffect(bm, mHue, mStauration, mLum));
}
设置图像矩阵的代码
public Bitmap handleImageEffect(Bitmap bm,
float hue,
float saturation,
float lum) {
Bitmap bmp = Bitmap.createBitmap(
bm.getWidth(), bm.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmp);
Paint paint = new Paint();
ColorMatrix hueMatrix = new ColorMatrix();
hueMatrix.setRotate(0, hue);
hueMatrix.setRotate(1, hue);
hueMatrix.setRotate(2, hue);
ColorMatrix saturationMatrix = new ColorMatrix();
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(new ColorMatrixColorFilter(imageMatrix));
canvas.drawBitmap(bm, 0, 0, paint);
return bmp;
}
设置好颜色矩阵后,需要使用paint类的setColorFilter方法,将imageMatrix构造的ColorMatrixColorFilter对象传递进去。
Android系统不允许直接修改原图,必须通过原图创建一个同样大小的Bitmap,并将原图绘制到新的bitmap中。直接修改原图会报错java.lang.IllegalStateException: Immutable bitmap passed to Canvas constructor。
3.使用颜色矩阵来改变图片
接下来我们通过直接修改矩阵中的值来改变图片的显示效果。
布局文件xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/mm_iv"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2" />
<GridLayout
android:id="@+id/mm_gl"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="3"
android:columnCount="5"
android:rowCount="4" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:orientation="horizontal">
<Button
android:id="@+id/mm_but_change"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="改变" />
<Button
android:id="@+id/mm_but_reset"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_weight="1"
android:text="重置" />
</LinearLayout>
</LinearLayout>
动态创建edittext,添加到gridlayout
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mmactivity);
mm_but_change = findViewById(R.id.mm_but_change);
mm_but_reset = findViewById(R.id.mm_but_reset);
mm_iv = findViewById(R.id.mm_iv);
mm_gl = findViewById(R.id.mm_gl);
bm = BitmapFactory.decodeResource(getResources(), R.mipmap.dgdfg);
mm_iv.setImageBitmap(bm);
mm_gl.post(new Runnable() {
@Override
public void run() {
etWidth = mm_gl.getWidth() / 5;
etHeight = mm_gl.getHeight() / 4;
addEts();
initMatrix();
}
});
}
/**
* 添加edittext
*/
private void addEts() {
for (int i = 0; i < ets.length; i++) {
EditText editText = new EditText(this);
editText.setGravity(Gravity.CENTER);
ets[i] = editText;
mm_gl.addView(editText, etWidth, etHeight);
}
}
/**
* 设置edittext的值
*/
private void initMatrix() {
for (int i = 0; i < ets.length; i++) {
if (i % 6 == 0) {
ets[i].setText(1 + "");
} else {
ets[i].setText(0 + "");
}
}
}
获得修改后的EditText值,将矩阵值设置给颜色矩阵。
private void getMatrix() {
for (int i = 0; i < 20; i++) {
colorM[i] = Float.valueOf(ets[i].getText().toString());
}
}
private void setImageMatrix() {
Bitmap bitmap = Bitmap.createBitmap(bm.getWidth(), bm.getHeight(), Bitmap.Config.ARGB_8888);
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.set(colorM);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
canvas.drawBitmap(bm, 0, 0, paint);
mm_iv.setImageBitmap(bitmap);
}
最后,设置点击事件,重置是将图片还原,改变是将现有的矩阵作用到图片。
mm_but_change.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getMatrix();
setImageMatrix();
}
});
mm_but_reset.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
initMatrix();
getMatrix();
setImageMatrix();
}
});
3.1几种图片颜色矩阵处理效果
3.1.1 灰度效果
{
0.33
0.59
0.11
0
0
0.33
0.59
0.11
0
0
0.33
0.59
0.11
0
0
0
0
0
1
0
}
\left\{ \begin{matrix} 0.33 & 0.59 & 0.11 & 0 & 0\\ 0.33 & 0.59 & 0.11 & 0 & 0 \\ 0.33 &0.59 & 0.11& 0& 0 \\ 0 & 0 & 0 & 1 & 0 \end{matrix} \right\}
⎩⎪⎪⎨⎪⎪⎧0.330.330.3300.590.590.5900.110.110.11000010000⎭⎪⎪⎬⎪⎪⎫
3.1.2 图像颜色反转
{
−
1
0
0
1
1
0
−
1
0
1
1
0
0
−
1
1
1
0
0
0
1
0
}
\left\{ \begin{matrix} -1 & 0 & 0 & 1 & 1\\ 0 & -1 & 0 & 1 & 1 \\ 0 &0 & -1& 1& 1 \\ 0 & 0 & 0 & 1 & 0 \end{matrix} \right\}
⎩⎪⎪⎨⎪⎪⎧−10000−10000−1011111110⎭⎪⎪⎬⎪⎪⎫
3.1.3 怀旧效果
{
0.393
0.769
0.189
0
0
0.349
0.686
0.168
0
0
0.272
0.534
0.131
0
0
0
0
0
1
0
}
\left\{ \begin{matrix} 0.393 & 0.769 & 0.189 & 0 &0\\ 0.349 &0.686 & 0.168 & 0 & 0\\ 0.272 &0.534 & 0.131& 0& 0 \\ 0 & 0 & 0 & 1 & 0 \end{matrix} \right\}
⎩⎪⎪⎨⎪⎪⎧0.3930.3490.27200.7690.6860.53400.1890.1680.131000010000⎭⎪⎪⎬⎪⎪⎫
3.1.4 去色效果
{
1.5
1.5
1.5
0
−
1
1.5
1.5
1.5
0
−
1
1.5
1.5
1.5
0
−
1
0
0
0
1
0
}
\left\{ \begin{matrix} 1.5 & 1.5 & 1.5 & 0 &-1\\ 1.5 &1.5 & 1.5 & 0 & -1\\ 1.5 &1.5 & 1.5& 0& -1 \\ 0 & 0 & 0 & 1 & 0 \end{matrix} \right\}
⎩⎪⎪⎨⎪⎪⎧1.51.51.501.51.51.501.51.51.500001−1−1−10⎭⎪⎪⎬⎪⎪⎫
3.1.5 高饱和度
{
1.438
−
0.112
−
0.016
0
−
0.03
−
0.062
1.378
−
0.016
0
0.05
−
0.062
−
0.122
1.483
0
−
0.02
0
0
0
1
0
}
\left\{ \begin{matrix} 1.438 & -0.112 & -0.016 & 0 &-0.03\\ -0.062 &1.378 & -0.016 & 0 & 0.05\\ -0.062 &-0.122 & 1.483& 0& -0.02 \\ 0 & 0 & 0 & 1 & 0 \end{matrix} \right\}
⎩⎪⎪⎨⎪⎪⎧1.438−0.062−0.0620−0.1121.378−0.1220−0.016−0.0161.48300001−0.030.05−0.020⎭⎪⎪⎬⎪⎪⎫
4.像素点分析
通过改变每个像素点argb值,来达到修改图片。android系统提供了Bitmap.getPixels()方法来获取这个bitmap的像素点。
bm.getPixels(int[] pixels, int offset, int stride,
int x, int y, int width, int height)
pixels:接收每个点的颜色
offset:写入到pixels中的第一个像素索引值
stride:pixels中的行间距
x:从位图中读取的第一个像素的X坐标值
y:从位图中读取的第一个像素的y坐标值
width:从每一行中读取的像素宽度
height:读取的行数
一般使用
bm.getPixels(oldPx, 0, bm.getWidth(), 0, 0, width, height);
具体代码
public Bitmap handleImagePixelsOldPhoto(Bitmap bm) {
//由于不能修改原始图片,需要创建一个图片的副本bmp
Bitmap bmp = Bitmap.createBitmap(bm.getWidth(), bm.getHeight(),
Bitmap.Config.ARGB_8888);
int width = bm.getWidth();
int height = bm.getHeight();
int color = 0;
int r, g, b, a, r1, g1, b1;
//初始化像素数组的大小
int[] oldPx = new int[width * height];
int[] newPx = new int[width * height];
//获取像素点
bm.getPixels(oldPx, 0, bm.getWidth(), 0, 0, width, height);
for (int i = 0; i < width * height; i++) {
color = oldPx[i];
a = Color.alpha(color);
r = Color.red(color);
g = Color.green(color);
b = Color.blue(color);
//效果的公式,用来改变像素点的颜色
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);
//最大值255
if (r1 > 255) {
r1 = 255;
}
if (g1 > 255) {
g1 = 255;
}
if (b1 > 255) {
b1 = 255;
}
newPx[i] = Color.argb(a, r1, g1, b1);
}
bmp.setPixels(newPx, 0, width, 0, 0, width, height);
return bmp;
}
4.1常用图像像素点处理效果
4.1.1 底片效果
处理方法都是前辈们研究出来的,这里我们只是使用。贴出代码和效果图。
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, width, height);
return bmp;
}
4.1.2 浮雕效果
直接贴代码。
public static Bitmap handleImagePixelsRelief(Bitmap bm) {
Bitmap bmp = Bitmap.createBitmap(bm.getWidth(), bm.getHeight(),
Bitmap.Config.ARGB_8888);
int width = bm.getWidth();
int height = bm.getHeight();
int color = 0, colorBefore = 0;
int a, r, g, b;
int r1, g1, b1;
int[] oldPx = new int[width * height];
int[] newPx = new int[width * height];
bm.getPixels(oldPx, 0, bm.getWidth(), 0, 0, width, height);
for (int i = 1; i < width * height; i++) {
colorBefore = oldPx[i - 1];
a = Color.alpha(colorBefore);
r = Color.red(colorBefore);
g = Color.green(colorBefore);
b = Color.blue(colorBefore);
color = oldPx[i];
r1 = Color.red(color);
g1 = Color.green(color);
b1 = Color.blue(color);
r = (r - r1 + 127);
g = (g - g1 + 127);
b = (b - b1 + 127);
if (r > 255) {
r = 255;
}
if (g > 255) {
g = 255;
}
if (b > 255) {
b = 255;
}
newPx[i] = Color.argb(a, r, g, b);
}
bmp.setPixels(newPx, 0, width, 0, 0, width, height);
return bmp;
}