图像结构
我们首先了解一下图像的构成,例如一张PNG图片:
图片文件头由位固定的字节来描述的,以便向外说明这个文件是一个PNG文件。
十进制数
137 80 78 71 13 10 26 10
十六进制数
89 50 4E 47 0D 0A 1A 0A
用UE打开一个PNG文件的内容为:
可以看到都为十六进制的数据,我们不知道这些数据是什么,假定第一行的数据是这个PNG的标志,第2-8行可能记录的是解析规则等信息,再之后的则为数据,那么
在一个图像文件中,总体包含的数据分为两部分:
1.文件的标志信息;
2.文件的数据信息;
标志的信息主要用来识别该文件是不是PNG文件,而数据块则储存了图片的图像信息;
PNG定义了两种类型的数据块,一种是称为关键数据块(critical chunk),这是必需的数据块,另一种叫做辅助数据块(ancillary chunks),这是可选的数据块。关键数据块定义了4个标准数据块,每个PNG文件都必须包含它们,PNG读写软件也都必须要支持这些数据块。虽然PNG文件规范没有要求PNG编译码器对可选数据块进行编码和译码,但规范提倡支持可选数据块。
每个数据块都由4个域组成:
关键数据块
IHDR
文件头数据块IHDR(header chunk):它包含有PNG文件中存储的图像数据的基本信息,并要作为第一个数据块出现在PNG数据流中,而且一个PNG数据流中只能有一个文件头数据块。
颜色通道
保存图像颜色信息的通道称为颜色通道。
RGB color有合成意义的变化(color channel),就是人有合成意义地(只用肢体或使用工具)将红蓝绿三种(实色料或颜色)合成出(一个或多个实色料空间显示、阻挡显示、隐藏、消失,从合成后可以赋予意义、认为无意义、没有赋予的意义、没有发现的意义、没有认同的意义等多种意义;一种颜色或多种颜色显示、阻挡显示、隐藏、消失,从合成后可以赋予意义、认为无意义、没有赋予的意义、没有发现的意义、没有认同的意义等多种意义)。
每个图像都有一个或多个颜色通道,图像中默认的颜色通道数取决于其颜色模式,即一个图像的颜色模式将决定其颜色通道的数量。例如,CMYK图像默认有4个通道,分别为青色、洋红、黄色、黑色。在默认情况下,位图模式、灰度、双色调和索引颜色图像只有一个通道。RGB和Lab图像有3个通道,CMYK图像有4个通道。
颜色模式
颜色模式,是将某种颜色表现为数字形式的模型,或者说是一种记录图像颜色的方式。分为:RGB模式、CMYK模式、HSB模式、Lab颜色模式、位图模式、灰度模式、索引颜色模式、双色调模式和多通道模式。
总结
颜色通道取决于颜色模式。
通俗点说,我们要显示图片中的色彩的时候,用数字表示,例如RGB模式,例如R255-G0-B0用来显示当前像素的色彩,在显示的时候为红色,RGB为红绿蓝三种色彩进行混合,其对应颜色数值的大小范围为0~255, 即每个点都是由颜色模式所决定的颜色通道(色彩数值)混合形成我们想要的颜色,而要实现滤镜效果,其实就是对颜色通道值进行处理,在其原本的通道数值进行更改,达到更改图像色彩效果的目的,这就是我们所谓的滤镜。
颜色矩阵
在安卓中,颜色模式为RGBA,即在RGB模式上加了一个A(alpha)透明值,即为一个四通道的模式。
在安卓中有一个类叫ColorMatrix(颜色矩阵),其用一个4x5矩阵来进行描述,用于转换位图的颜色和alpha分量。
矩阵可以作为单个数组传递,并作如下处理:
ColorMatrix类的注解为:
[ a, b, c, d, e,
f, g, h, i, j,
k, l, m, n, o,
p, q, r, s, t ]
当应用到颜色 [R,G,B,A],结果颜色计算为:
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,A,G,B]的值在0~255之间
我们可以理解为
安卓中的颜色矩阵是一个4×5的数字矩阵:
会以一维数组的形式来存储:(float[]类型)
[ a, b, c, d, e,
f, g, h, i, j,
k, l, m, n, o,
p, q, r, s, t ]
即
new float[]{
a, b, c, d, e,
f, g, h, i, j,
k, l, m, n, o,
p, q, r, s, t
};
矩阵运算
当矩阵A的列数等于矩阵B的行数时,A与B可以相乘。
矩阵C的行数等于矩阵A的行数,C的列数等于B的列数。
乘积C的第m行第n列的元素等于矩阵A的第m行的元素与矩阵B的第n列对应元素乘积之和。
颜色矩阵运算
我们可通过ColorMatrix类生成指定的颜色矩阵A,
与颜色通道的原始色彩矩阵进行矩阵乘法运算。
这里有一个色彩矩阵分量C,代表着我们颜色通道的原始色彩:
矩阵R则代表通过矩阵乘法运算AC而得到的新的颜色:
R' = aR + bG + cB + dA + e;
G' = fR + gG + hB + iA + j;
B' = kR + lG + mB + nA + o;
A' = pR + qG + rB + sA + t;
我们这里可以看到,最后会加上最后一列的e, j, o, t,这是因为在安卓中(这个是安卓的颜色矩阵规则),前4列组成的才是四阶颜色矩阵:
而最后一列则是代表增量值,即4阶矩阵相乘后再加上这个增量,从而得到最终的颜色值。
颜色矩阵中,
- 第一行决定红色值
- 第一行决定绿色值
- 第一行决定蓝色值
- 第一行决定透明值
下面这个为原始矩阵,即对原色值不做任何改变:
下面我们通过代码来进行实现
首先我们先定义主页面
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:gravity="center"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btn_filter"
android:text="滤镜"
android:textSize="22sp"
android:onClick="onStartFilterActivity"
android:textAllCaps="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btn_custom"
android:layout_marginTop="10dp"
android:text="自定义"
android:textSize="22sp"
android:onClick="onStartCustomActivity"
android:textAllCaps="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</android.support.constraint.ConstraintLayout>
MainActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onStartFilterActivity(View view) {
startActivity(new Intent(this, FilterActivity.class));
}
public void onStartCustomActivity(View view) {
startActivity(new Intent(this, CustomFilterActivity.class));
}
}
实现两种功能,一个是采用固定的滤镜方式,一个是采用自定义的滤镜方式。
固定的滤镜方式
布局
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_tip1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="原图"
android:layout_marginLeft="10dp"
android:textColor="@android:color/holo_red_dark"
android:textSize="22sp" />
<ImageView
android:id="@+id/tv_src"
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/pic_xxx_01" />
<TextView
android:id="@+id/tv_tip2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:text="效果图"
android:textColor="@android:color/holo_red_dark"
android:textSize="22sp" />
<com.iigo.paint.FilterView
android:id="@+id/fv_filter"
app:img_id="@mipmap/pic_xxx_01"
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<LinearLayout
android:gravity="center"
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btn_draw_normal"
android:textAllCaps="false"
android:text="原图"
android:onClick="onDrawNormal"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btn_draw_negative"
android:textAllCaps="false"
android:text="底片"
android:onClick="onDrawNegative"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:gravity="center"
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btn_draw_retro"
android:textAllCaps="false"
android:text="复古"
android:onClick="onDrawRetro"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btn_draw_fair"
android:textAllCaps="false"
android:text="美颜"
android:onClick="onDrawFair"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:gravity="center"
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btn_draw_bandw"
android:textAllCaps="false"
android:text="黑白"
android:onClick="onDrawBAndW"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btn_draw_change"
android:textAllCaps="false"
android:text="发色"
android:onClick="onDrawChange"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
FilterActivity
public class FilterActivity extends AppCompatActivity {
private FilterView filterView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_filter);
filterView = findViewById(R.id.fv_filter);
}
public void onDrawNormal(View view){
filterView.drawNormal();
}
public void onDrawNegative(View view){
filterView.drawNegative();
}
public void onDrawRetro(View view){
filterView.drawRetro();
}
public void onDrawFair(View view){
filterView.drawFair();
}
public void onDrawBAndW(View view){
filterView.drawBlackAndWhite();
}
public void onDrawChange(View view){
filterView.drawChange();
}
}
点击对应效果的按钮进行效果查看
自定义的滤镜方式
布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.iigo.paint.FilterView
android:id="@+id/fv_filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp"
app:img_id="@mipmap/pic_xxx_01" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:orientation="horizontal">
<EditText
android:id="@+id/et_11"
android:text="1"
android:inputType="numberSigned|numberDecimal"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:gravity="center" />
<EditText
android:id="@+id/et_12"
android:text="0"
android:inputType="numberSigned|numberDecimal"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:gravity="center" />
<EditText
android:id="@+id/et_13"
android:text="0"
android:inputType="numberSigned|numberDecimal"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:gravity="center" />
<EditText
android:id="@+id/et_14"
android:text="0"
android:inputType="numberSigned|numberDecimal"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:gravity="center" />
<EditText
android:id="@+id/et_15"
android:text="0"
android:inputType="numberSigned|numberDecimal"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:gravity="center" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:orientation="horizontal">
<EditText
android:id="@+id/et_21"
android:text="0"
android:inputType="numberSigned|numberDecimal"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:gravity="center" />
<EditText
android:id="@+id/et_22"
android:text="1"
android:inputType="numberSigned|numberDecimal"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:gravity="center" />
<EditText
android:id="@+id/et_23"
android:text="0"
android:inputType="numberSigned|numberDecimal"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:gravity="center" />
<EditText
android:id="@+id/et_24"
android:text="0"
android:inputType="numberSigned|numberDecimal"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:gravity="center" />
<EditText
android:id="@+id/et_25"
android:text="0"
android:inputType="numberSigned|numberDecimal"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:gravity="center" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:orientation="horizontal">
<EditText
android:id="@+id/et_31"
android:text="0"
android:inputType="numberSigned|numberDecimal"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:gravity="center" />
<EditText
android:id="@+id/et_32"
android:text="0"
android:inputType="numberSigned|numberDecimal"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:gravity="center" />
<EditText
android:id="@+id/et_33"
android:text="1"
android:inputType="numberSigned|numberDecimal"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:gravity="center" />
<EditText
android:id="@+id/et_34"
android:text="0"
android:inputType="numberSigned|numberDecimal"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:gravity="center" />
<EditText
android:id="@+id/et_35"
android:text="0"
android:inputType="numberSigned|numberDecimal"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:gravity="center" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:orientation="horizontal">
<EditText
android:id="@+id/et_41"
android:text="0"
android:inputType="numberSigned|numberDecimal"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:gravity="center" />
<EditText
android:id="@+id/et_42"
android:text="0"
android:inputType="numberSigned|numberDecimal"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:gravity="center" />
<EditText
android:id="@+id/et_43"
android:text="0"
android:inputType="numberSigned|numberDecimal"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:gravity="center" />
<EditText
android:id="@+id/et_44"
android:text="1"
android:inputType="numberSigned|numberDecimal"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:gravity="center" />
<EditText
android:id="@+id/et_45"
android:text="0"
android:inputType="numberSigned|numberDecimal"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:gravity="center" />
</LinearLayout>
<Button
android:id="@+id/btn_draw"
android:layout_marginTop="10dp"
android:text="执行"
android:onClick="onDraw"
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btn_recover"
android:layout_marginTop="10dp"
android:text="还原为最原始数据"
android:onClick="onRecover"
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
CustomeFilterActivity代码为
public class CustomFilterActivity extends AppCompatActivity {
/**
* <pre>
* [ a, b, c, d, e,
* f, g, h, i, j,
* k, l, m, n, o,
* p, q, r, s, t ]</pre>
*
*/
private EditText et11;
private EditText et12;
private EditText et13;
private EditText et14;
private EditText et15;
private EditText et21;
private EditText et22;
private EditText et23;
private EditText et24;
private EditText et25;
private EditText et31;
private EditText et32;
private EditText et33;
private EditText et34;
private EditText et35;
private EditText et41;
private EditText et42;
private EditText et43;
private EditText et44;
private EditText et45;
private FilterView filterView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_custom);
initViews();
}
private void initViews() {
et11 = findViewById(R.id.et_11);
et12 = findViewById(R.id.et_12);
et13 = findViewById(R.id.et_13);
et14 = findViewById(R.id.et_14);
et15 = findViewById(R.id.et_15);
et21 = findViewById(R.id.et_21);
et22 = findViewById(R.id.et_22);
et23 = findViewById(R.id.et_23);
et24 = findViewById(R.id.et_24);
et25 = findViewById(R.id.et_25);
et31 = findViewById(R.id.et_31);
et32 = findViewById(R.id.et_32);
et33 = findViewById(R.id.et_33);
et34 = findViewById(R.id.et_34);
et35 = findViewById(R.id.et_35);
et41 = findViewById(R.id.et_41);
et42 = findViewById(R.id.et_42);
et43 = findViewById(R.id.et_43);
et44 = findViewById(R.id.et_44);
et45 = findViewById(R.id.et_45);
et11.addTextChangedListener(new MyTextWatcher(et11, "1"));
et12.addTextChangedListener(new MyTextWatcher(et12, "0"));
et13.addTextChangedListener(new MyTextWatcher(et13, "0"));
et14.addTextChangedListener(new MyTextWatcher(et14, "0"));
et15.addTextChangedListener(new MyTextWatcher(et15, "0"));
et21.addTextChangedListener(new MyTextWatcher(et21, "0"));
et22.addTextChangedListener(new MyTextWatcher(et22, "1"));
et23.addTextChangedListener(new MyTextWatcher(et23, "0"));
et24.addTextChangedListener(new MyTextWatcher(et24, "0"));
et25.addTextChangedListener(new MyTextWatcher(et25, "0"));
et31.addTextChangedListener(new MyTextWatcher(et31, "0"));
et32.addTextChangedListener(new MyTextWatcher(et32, "0"));
et33.addTextChangedListener(new MyTextWatcher(et33, "1"));
et34.addTextChangedListener(new MyTextWatcher(et34, "0"));
et35.addTextChangedListener(new MyTextWatcher(et35, "0"));
et41.addTextChangedListener(new MyTextWatcher(et41, "0"));
et42.addTextChangedListener(new MyTextWatcher(et42, "0"));
et43.addTextChangedListener(new MyTextWatcher(et43, "0"));
et44.addTextChangedListener(new MyTextWatcher(et44, "1"));
et45.addTextChangedListener(new MyTextWatcher(et45, "0"));
filterView = findViewById(R.id.fv_filter);
}
public void onRecover(View view){
et11.setText("1");
et12.setText("0");
et13.setText("0");
et14.setText("0");
et15.setText("0");
et21.setText("0");
et22.setText("1");
et23.setText("0");
et24.setText("0");
et25.setText("0");
et31.setText("0");
et32.setText("0");
et33.setText("1");
et34.setText("0");
et35.setText("0");
et41.setText("0");
et42.setText("0");
et43.setText("0");
et44.setText("1");
et45.setText("0");
}
public void onDraw(View view){
//若EditText为空,默认值为0
int n11 = Integer.valueOf(et11.getText().toString());
int n12 = Integer.valueOf(et12.getText().toString());
int n13 = Integer.valueOf(et13.getText().toString());
int n14 = Integer.valueOf(et14.getText().toString());
int n15 = Integer.valueOf(et15.getText().toString());
int n21 = Integer.valueOf(et21.getText().toString());
int n22 = Integer.valueOf(et22.getText().toString());
int n23 = Integer.valueOf(et23.getText().toString());
int n24 = Integer.valueOf(et24.getText().toString());
int n25 = Integer.valueOf(et25.getText().toString());
int n31 = Integer.valueOf(et31.getText().toString());
int n32 = Integer.valueOf(et32.getText().toString());
int n33 = Integer.valueOf(et33.getText().toString());
int n34 = Integer.valueOf(et34.getText().toString());
int n35 = Integer.valueOf(et35.getText().toString());
int n41 = Integer.valueOf(et41.getText().toString());
int n42 = Integer.valueOf(et42.getText().toString());
int n43 = Integer.valueOf(et43.getText().toString());
int n44 = Integer.valueOf(et44.getText().toString());
int n45 = Integer.valueOf(et45.getText().toString());
ColorMatrix colorMatrix = new ColorMatrix(new float[]{
n11, n12, n13, n14, n15,
n21, n22, n23, n24, n25,
n31, n32, n33, n34, n35,
n41, n42, n43, n44, n45,
});
filterView.drawCustom(colorMatrix);
}
private class MyTextWatcher implements TextWatcher {
private EditText editText;
private String defaultValue;
public MyTextWatcher(EditText editText, String defaultValue){
this.editText = editText;
this.defaultValue = defaultValue;
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (TextUtils.isEmpty(s.toString())){
editText.setText(defaultValue);
}
}
@Override
public void afterTextChanged(Editable s) {
}
}
}
点击执行按钮进行绘制,点击还原按钮还原为原始颜色矩阵数据。
最后自定义FilterView的代码为:
public class FilterView extends View {
private int imageId = -1;
private Paint paint;
private Bitmap bitmap;
private ColorMatrix cColorMatrix;
public enum TYPE{
NORMAL, //正常图片
NEGATIVE, //底片
RETRO, //复古
FAIR, //美颜
BAW,//黑白(black and white)
CHANGE, //发色,改变颜色
CUSTOM,//自定义
}
private TYPE currentType = TYPE.NORMAL;
public FilterView(Context context, int imageId) {
super(context);
this.imageId = imageId;
init(null);
}
public FilterView(Context context) {
super(context);
init(null);
}
public FilterView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public FilterView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
@SuppressLint("NewApi")
public FilterView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(attrs);
}
private void init(AttributeSet attrs) {
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
if (attrs == null){
return;
}
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.FilterView);
int count = a.getIndexCount();
for (int i = 0; i < count; i++){
int attr = a.getIndex(i);
switch (attr){
case R.styleable.FilterView_img_id:
imageId = a.getResourceId(attr, 0);
break;
}
}
a.recycle();
if (imageId == -1){
return;
}
try{
bitmap = BitmapFactory.decodeResource(getResources(), imageId);
}catch (Exception e){
e.printStackTrace();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (bitmap == null){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
//根据图片大小设置当前view的大小
super.onMeasure(MeasureSpec.makeMeasureSpec(bitmap.getWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(bitmap.getHeight(), MeasureSpec.EXACTLY));
//若要实现图片的自适应view的大小,可自行实现..
//...
//
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (bitmap == null){
return;
}
setLayerType(View.LAYER_TYPE_SOFTWARE,null);
paint.reset();
ColorMatrix colorMatrix = null;
RectF rectF = new RectF(0,0, bitmap.getWidth(), bitmap.getHeight());
paint.reset();
switch (currentType){
case NORMAL:
break;
case NEGATIVE:
colorMatrix = new ColorMatrix(new float[]{
-1, 0,0,0,255,
0,-1,0,0,255,
0,0,-1,0,255,
0,0,0,1,0,
});
break;
case RETRO:
colorMatrix = new ColorMatrix(new float[]{
1/2f,1/2f,1/2f,0,0,
1/3f, 1/3f,1/3f,0,0,
1/4f,1/4f,1/4f,0,0,
0,0,0,1,0,
});
break;
case FAIR:
colorMatrix = new ColorMatrix(new float[]{
1.25f, 0,0,0,0,
0,1.25f,0,0,0,
0,0,1.25f,0,0,
0,0,0,1.25f,0,
});
break;
case BAW:
colorMatrix = new ColorMatrix(new float[]{
0.213f, 0.715f,0.072f,0,0,
0.213f, 0.715f,0.072f,0,0,
0.213f, 0.715f,0.072f,0,0,
0,0,0,1,0,
});
break;
case CHANGE:
colorMatrix = new ColorMatrix(new float[]{
1, 0, 0, 0, 0,
0, 0, 1, 0, 0,
0, 1, 0, 0, 0,
0, 0, 0, 1, 0,
});
break;
case CUSTOM:
colorMatrix = cColorMatrix;
break;
default: break;
}
if (colorMatrix != null){
paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
}
canvas.drawBitmap(bitmap,null, rectF, paint);
}
private void performDraw(){
if (bitmap == null){
return;
}
invalidate();
}
/**
* 显示正常图片
* */
public void drawNormal(){
currentType = TYPE.NORMAL;
performDraw();
}
/**
* 显示底片效果
* */
public void drawNegative(){
currentType = TYPE.NEGATIVE;
performDraw();
}
/**
* 显示复古效果
* */
public void drawRetro(){
currentType = TYPE.RETRO;
performDraw();
}
/**
* 显示美颜效果
* */
public void drawFair(){
currentType = TYPE.FAIR;
performDraw();
}
/**
* 显示黑白效果
* */
public void drawBlackAndWhite(){
currentType = TYPE.BAW;
performDraw();
}
/**
* 显示发色效果,这里为红色和绿色交换
* */
public void drawChange(){
currentType = TYPE.CHANGE;
performDraw();
}
/**
* 显示自定义效果
*
* @param colorMatrix 指定的滤镜效果
* */
public void drawCustom(ColorMatrix colorMatrix){
currentType = TYPE.CUSTOM;
cColorMatrix = colorMatrix;
performDraw();
}
}
滤镜实现
我们只需要调用接口
colorMatrix = new ColorMatrix(new float[]{
1, 0, 0, 0, 0,
0, 0, 1, 0, 0,
0, 1, 0, 0, 0,
0, 0, 0, 1, 0,
});
paint.setColorFilter(new ColorMatrixColorFilter(colorMartrix));
便可实现滤镜效果
我们将R即红色增加1倍,即
colorMatrix = new ColorMatrix(new float[]{
2, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0,
});
即可看到图片变为红色,
将G即绿色增加1倍,即
colorMatrix = new ColorMatrix(new float[]{
1, 0, 0, 0, 0,
0, 2, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0,
});
即可看到图片变为绿色,
将R即红色和G即绿色同时增加1倍,即
colorMatrix = new ColorMatrix(new float[]{
2, 0, 0, 0, 0,
0, 2, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0,
});
即可看到图片颜色混合变为黄色,
我们还可以指定类似于底片的颜色,即
colorMatrix = new ColorMatrix(new float[]{
-1, 0,0,0,255,
0,-1,0,0,255,
0,0,-1,0,255,
0,0,0,1,0,
});
底片效果其实就是反相效果,将RGB的值取反,例如RGB的值分别为r,g,b,反相的意思就是取
255 - r, 255 - g, 255 - b的值。
黑白照片效果,即
colorMatrix = new ColorMatrix(new float[]{
0.213f, 0.715f,0.072f,0,0,
0.213f, 0.715f,0.072f,0,0,
0.213f, 0.715f,0.072f,0,0,
0,0,0,1,0,
});
是将我们的三通道变为单通道的灰度模式,去色原理:只要把R G B 三通道的色彩信息设置成一样,那么图像就会变成灰色, 同时为了保证图像亮度不变,同一个通道里的R+G+B =1。
demo地址为:https://github.com/samlss/Paint