//将三种效果融合起来
ColorMatrix imageMatrix = new ColorMatrix();
imageMatrix.postConcat(hueMatrix);
imageMatrix.postConcat(saturationMatrix);
imageMatrix.postConcat(lumMatrix);
paint.setColorFilter(new ColorMatrixColorFilter(imageMatrix));
canvas.drawBitmap(bitmap,0,0,paint);
return currentBitmap;
}
}
这里,用到了Canvas类,canvas类是一个画布类,后面进行的操作都会在画布上进行,而不是在原图上。建立了三个ColorMatrix,进行了不同方面的操作,并用postConcat方法将这些Matrix进行融合。通过setColorFilter方法来修改paint的相关属性,然后在canvas上将图片绘制出来。最后将修改后的图片返回出来。
回到我们的界面。设计是用三个Seekbar来分别控制色相、饱和度、亮度三个属性。
<?xml version="1.0" encoding="utf-8"?><RelativeLayout 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/image_view"
android:layout_width=“300dp”
android:layout_height=“300dp”
android:scaleType=“centerCrop”
android:layout_marginTop=“24dp”
android:layout_marginBottom=“24dp”
android:layout_centerHorizontal=“true”/>
<SeekBar
android:id="@+id/seekbar_hue"
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:layout_margin=“10dp”
android:layout_below="@id/image_view"/>
<SeekBar
android:id="@+id/seekbar_saturation"
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:layout_margin=“10dp”
android:layout_below="@id/seekbar_hue"/>
<SeekBar
android:id="@+id/seekbar_lum"
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:layout_margin=“10dp”
android:layout_below="@id/seekbar_saturation"/>
然后编辑PrimaryColorActivity。这里我们定义了一个最大值及中间值,可以让它从中间值开始变化,然后通过一系列方法来分别获取色相、饱和度、亮度的值。
public class PrimaryColorActivity extends AppCompatActivity implements SeekBar.OnSeekBarChangeListener{
private ImageView mImageView;
private SeekBar mSeekbarHue,mSeekbarSaturation,mSeekbarLum;
private final static int MAX_VALUE = 255; //最大值
private final static int MID_VALUE = 127; //中间值
private float mHue,mSaturation,mLum;
private Bitmap bitmap;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_primary_color);
//获取图片
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test1);
mImageView = (ImageView)findViewById(R.id.image_view);
mImageView.setImageBitmap(bitmap);
mSeekbarHue = (SeekBar)findViewById(R.id.seekbar_hue);
mSeekbarSaturation = (SeekBar)findViewById(R.id.seekbar_saturation);
mSeekbarLum = (SeekBar)findViewById(R.id.seekbar_lum);
mSeekbarHue.setOnSeekBarChangeListener(this);
mSeekbarSaturation.setOnSeekBarChangeListener(this);
mSeekbarLum.setOnSeekBarChangeListener(this);
mSeekbarHue.setMax(MAX_VALUE);
mSeekbarSaturation.setMax(MAX_VALUE);
mSeekbarLum.setMax(MAX_VALUE);
mSeekbarHue.setProgress(MID_VALUE);
mSeekbarSaturation.setProgress(MID_VALUE);
mSeekbarLum.setProgress(MID_VALUE);
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
switch (seekBar.getId()){
case R.id.seekbar_hue:
//将0-255的值转换为色调值
mHue = (progress - MID_VALUE)1.0F/MID_VALUE180;
break;
case R.id.seekbar_saturation:
//将0-255值转换为饱和度值
mSaturation = progress*1.0F/MID_VALUE;
break;
case R.id.seekbar_lum:
//将0-255的值转换为亮度值
mLum = progress*1.0F/MID_VALUE;
break;
}
mImageView.setImageBitmap(ImageUtils.handleImageEffect(bitmap,
mHue,mSaturation,mLum));
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
}
运行后,拖动Seekbar,可以发现,我们成功地改变了图片的各项属性
原理分析——矩阵变换
我们之前用到了ColorMatrix类,众所周知,Matrix是矩阵的意思,所以这里实际上我们是通过操作矩阵来处理图像。Android中提供了如图的颜色矩阵,来帮助我们进行图像效果的处理, 图像中的每一个点都是一个矩阵分量,是由R、G、B、A和1组成:
我们用这个颜色矩阵乘上像素点对应的颜色矩阵分量,就能得到一个新的矩阵R1G1B1A1。如图:
这样,我们就把一个像素点通过颜色矩阵变换成了新的像素点(颜色调整后的效果)。
我们把如图的矩阵称为初始化矩阵,因为它乘上原来的像素点后,仍不变。
下面我们看一下这个矩阵,它在原来矩阵的基础上把两个地方的0变为了100,导致的后果就是原来的R G的值变为了它们加上100的值,也就是每个像素点的R G值都增加了100:
同样的,我们可以看一下这样的一个矩阵,它在原来的初始化矩阵的基础上,把矩阵G上的一个1变为了2,带入后可发现,G变为了原来的两倍,效果就是将整个图像的Green增加了两倍
我们可以发现,颜色矩阵的四行分别控制着像素点的R G B A四个属性,而颜色矩阵的第五列,我们称它为颜色偏移量,它不会直接改变某个颜色的系数,而是在原来的基础上调整整个颜色。
我们要改变一个颜色,不仅仅可以改变偏移量,还可以改变颜色的系数。
用矩阵变换来调整图像效果
我们基于原来的工程,增加一个Activity,并让MainActivity的第二个按钮跳转到此Activity。
首先是布局,我们制作如下的布局,准备在GridLayout中添加20个EditText来代表矩阵,用按钮来应用矩阵。
<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:orientation=“vertical”
android:layout_width=“match_parent”
android:layout_height=“match_parent”>
<ImageView
android:id="@+id/image_view"
android:layout_width=“match_parent”
android:layout_height=“0dp”
android:layout_weight=“2”/>
<GridLayout
android:id="@+id/group"
android:layout_width=“match_parent”
android:layout_height=“0dp”
android:layout_weight=“3”
android:rowCount=“4”
android:columnCount=“5”
<LinearLayout
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:orientation=“horizontal”>
<Button
android:id="@+id/btn_change"
android:layout_width=“0dp”
android:layout_height=“wrap_content”
android:layout_weight=“1”
android:text=“改变”/>
<Button
android:id="@+id/btn_reset"
android:layout_width=“0dp”
android:layout_height=“wrap_content”
android:layout_weight=“1”
android:text=“重置”/>
<
然后,我们修改Activity的代码,动态将EditText添加到GridText,来映射我们的ColorMatrix。通过改变EditText来改变我们的ColorMatrix,使图片效果变化。
public class ColorMatrixActivity extends AppCompatActivity {
private ImageView mImageView;
private GridLayout mGroup;
private Bitmap bitmap;
private int mEtWidth,mEtHeight;
private EditText[] mEditTexts = new EditText[20];
private float[] mColorMatrix = new float[20]; //对应矩阵
private Button changeButton;
private Button resetButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_color_matrix);
bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.test1);
mImageView = (ImageView) findViewById(R.id.image_view);
mImageView.setImageBitmap(bitmap);
mGroup = (GridLayout) findViewById(R.id.group);
//动态创建EditText,填充GridLayout
//由于在onCreate中,mGroup还没有创建完成,无法获取宽高
//所以通过post方法,在控件绘制完毕后,执行Runnable的具体方法
mGroup.post(new Runnable() {
@Override
public void run() {
mEtWidth = mGroup.getWidth()/5;
mEtHeight = mGroup.getHeight()/4;
addEditText();
initMatrix();
}
});
changeButton = (Button)findViewById(R.id.btn_change);
changeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
changeButtonEvent();
}
});
resetButton = (Button)findViewById(R.id.btn_reset);
resetButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
resetButtonEvent();
}
});
}
private void addEditText(){
for (int i=0;i<20;i++){
EditText editText = new EditText(ColorMatrixActivity.this);
mEditTexts[i] = editText;
mGroup.addView(editText,mEtWidth,mEtHeight);
}
}
private void initMatrix(){
for (int i=0;i<20;i++){
if (i%6 == 0){
//i为第0、6、12、18位时
mColorMatrix[i]=1;
mEditTexts[i].setText(String.valueOf(1));
}else{
mColorMatrix[i]=0;
mEditTexts[i].setText(String.valueOf(0));
}
}
}
private void getMatrix(){
for (int i=0;i<20;i++){
mColorMatrix[i] = Float.valueOf(mEditTexts[i].getText().toString());
}
}
private void setImageMatrix(){
Bitmap currentBitmap = Bitmap.createBitmap(bitmap.getWidth(),bitmap.getHeight(), Bitmap.Config.ARGB_8888);
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.set(mColorMatrix);
Canvas canvas = new Canvas(currentBitmap);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
canvas.drawBitmap(bitmap,0,0,paint);
mImageView.setImageBitmap(currentBitmap);
}
public void changeButtonEvent(){
getMatrix();
setImageMatrix();
}
public void resetButtonEvent(){
initMatrix();
setImageMatrix();
}
}
可以看到,成功改变了图片效果,效果如图
学到这里,我们便可以解答之前为什么要用这样的参数来改变色相、饱和度和亮度了。以亮度为例,我们来看看setScale方法的源码。可以发现,ColorMatrix在内部也是使用这样一个颜色数组,同时将6的倍数位设置为相应的值,以此来改变亮度,说明我们如果在颜色矩阵中想要改变亮度,只需要将每个颜色的值同时提高即可。
/**
- Set this colormatrix to scale by the specified values.
*/
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;
}
通过这些研究,我们可以这样总结:
图像处理,实际上就是研究不同颜色矩阵对图像的处理效果
比如我们在一些图像处理app中常见的怀旧效果,通过下图的设置方法即可得到
通过像素点进行图像处理
图像经过放大后,会呈现一个个点阵,每一个点实际上就是一个像素点。通过RGB的颜色配比,就可以显示出不同的颜色。
下面是一些对像素点处理形成图像特效的例子:
底片效果
对于ABC三个像素点,求B点的底片效果的算法如下。实际上就是对每个坐标点计算它的反色,即可得到
B.r = 255 - B.r;
B.g = 255 - B.g;
B.b = 255 - B.b;
老照片效果
求老照片效果对像素点的算法如下,其中pixR就是当前像素点的R值,以此类推。
newR = (int)(0.393 * pixR + 0.769 * pixG + 0.189 * pixB);
newG = (int)(0.349 * pixR + 0.686 * pixG + 0.168 * pixB);
newB = (int)(0.272 * pixR + 0.534 * pixG + 0.131 * pixB);
浮雕效果
对于ABC三个点,求B点的浮雕效果的算法如下。
B.r = C.r - B.r + 127;
B.g = C.g - B.g + 127;
B.b = C.b - B.b + 127;
下面,我们就通过对像素点的修改来改变图像的显示效果。
先新建一个新的Activity,并在MainActivity的第三个按钮中加上跳转到它的方法。
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:orientation=“vertical”
android:layout_width=“match_parent”
android:layout_height=“match_parent”>
<LinearLayout
android:layout_width=“match_parent”
android:layout_height=“0dp”
android:layout_weight=“1”>
<ImageView
android:id="@+id/image_view1"
android:layout_width=“0dp”
android:layout_height=“match_parent”
android:layout_weight=“1”/>
<ImageView
android:id="@+id/image_view2"
android:layout_width=“0dp”
android:layout_height=“match_parent”
android:layout_weight=“1”/>
<LinearLayout
android:layout_width=“match_parent”
android:layout_height=“0dp”
android:layout_weight=“1”>
<ImageView
android:id="@+id/image_view3"
android:layout_width=“0dp”
android:layout_height=“match_parent”
android:layout_weight=“1”/>
<ImageView
android:id="@+id/image_view4"
android:layout_width=“0dp”
android:layout_height=“match_parent”
android:layout_weight=“1”/>
加入相应特效方法
我们在ImageUtils中加入几个新方法,分别做不同的处理
反色效果
代码如下,我们在里面新建了一个对应图片的像素的数组,然后通过getPixels方法获取所有像素。
getPixels的第二个参数是代表起点的偏移量,第三个参数是控制读取数组时的行距,一般使用width,后面两个参数代表第一次读取像素点的坐标,倒数第二个参数代表我们从bitmap中读取的宽度,最后一个是读取的高度。
然后我们分别对每个像素点通过Color类的red green blue alpha方法来获取r g b a四个值,并通过算法改变它的rgb值,并通过Color的argb方法转换为新的像素数组。需要注意的是,改变它的rgb值时,需要判断一下有没有超过0-255的限制,有的话则赋值为255或0。
Bitmap currentBitmap = Bitmap.createBitmap(width,height,
Bitmap.Config.ARGB_8888);
int[] oldPx = new int[width*height]; //存储像素点数组
int[] newPx = new int[width*height];
bitmap.getPixels(oldPx,0,width,0,0,width,height);
for (int i=0;i<width*height;i++){