在色彩处理中,通常用以下三个角度来描述一个图像。
图像的色调,饱和度,亮度这三个属性在图像处理中的使用非常多,因此颜色矩阵中,也封装了一些API来快速调用这些参数,而不用每次都去计算矩阵的值。
在Android中,系统封装了一个类——ColorMatrix,也就是说前面的颜色矩阵。通过这个类,可以很方便地改变矩阵值来处理颜色效果。
ColorMatrix colorMatrix = new ColorMatrix();
ColorMatrix hueMatrix = new ColorMatrix();
hueMatrix .setRotate(0,hue0);
hueMatrix .setRotate(1,hue1);
hueMatrix .setRotate(2,hue2);
通过这样的方法可以为RGB三种颜色分量分别重新设置不同的色调值。
ColorMatrix saturationMatrix=new ColorMatrix(); saturationMatrix.setSaturation(saturation);
ColorMatrix lumMatrix=new ColorMatrix(); lumMatrix.setScale(lum,lum,lum,1);
除了单独使用上面三种方式来进行颜色效果的处理之外,Android系统还封装了矩阵的乘法运算。它提供了postConcat()方法来将矩阵的作用效果混合,从而叠加处理效果,代码如下:
ColorMatrix imageMatrix=new ColorMatrix();
imageMatrix.posConcat(hueMatrix);
imageMatrix.posConcat(saturationMatrix);
imageMatrix.posConcat(lumMatrix);
最后通过paint.setColorFilter(new ColorMatrixColorFilter(imageMatrix))设置给paint,并使用这个画笔来绘制原来的图像,从而将颜色矩阵作用到原图上。
paint.setColorFilter(new ColorMatrixColorFilter(imageMatrix));
canvas.drawBitmap(bitmap,0,0,paint);
0.33f,0.59f,0.11f,0, 0,
0.33f,0.59f,0.11f,0, 0,
0.33f,0.59f,0.11f,0, 0,
0, 0, 0, 1, 0,
-1, 0, 0, 1, 1, 0,-1, 0, 1, 1,
0, 0,-1, 1, 1,
0, 0, 0, 1, 0,
0.393f,0.769f,0.189f,0, 0,
0.349f,0.686f.0.168f,0, 0,
0.242f,0.534f,0.131f,0, 0,
0, 0, 0, 0, 0,
1.5f, 1.5f, 1.5f, 0, -1,
1.5f, 1.5f, 1.5f, 0, -1,
1.5f, 1.5f, 1.5f, 0, -1,
0, 0, 0, 1, 0,
1.438f,-0.122f,-0.016f, 0,-0.03f,
-0.062f, 1.378f,-0.016f, 0, 0.05f,
-0.062f,-0.122f, 1.483f, 0,-0.02f
0, 0, 0, 1, 0,
作为更加精确的图像处理方式,可以通过改变每个像素点的具体ARGB值,来达到处理一张图像效果的目的。需要注意的是,传递进来的原始图片是不能修改的,一般根据原始图片生成一张新的图片来修改。
在Android中,系统提供了Bitmap.getPixels()方法来帮我们提取整个Bitmap中的像素点,并保存到一个数组中。
bitmap.getPixels(pixels,offset,stride,x,y,width,height);
这几个参数的含义如下:
bitmap.getPixels(oldPx,0,bm.getWidth(),0,0,width,height);
接下来就可以获取每个像素具体的ARGB了,如下:
color=oldPx[i];
r=Color.red(color);
g=Color.green(color);
b=Color.blue(color);
a=Color.alpha(color);
当获取到具体的颜色值后,就可以通过相应的算法来修改它的ARGB值。如下老照片效果效果:
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);
再通过如下代码将新的RGBA值合成像素点:
newPx[i]=Color.argb(a,r1,g1,b1);
最后通过如下代码,将处理后的像素点数组重新set给我们的Bitmap,从而达到图像处理的目的。
bitmap.setPixels(newPx,0,width,0,0,width,height);
若存在ABC3个像素点,要求B点对应的底片效果算法,代码如下:
B.r=255-B.r;
B.g=255-B.g;
B.b=255-B.b;
实现代码如下:
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,widht,height);
return bmp;
}
求某像素点的老照片效果算法,代码如下:
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);
若存在ABC3个像素点,要求B点对应的浮雕效果算法,代码如下:
B.r=C.r-B.r+127;
B.g=C.g-B.g+127;
B.b=C.b-B.b+127;
对于图像的图形变换,Android系统也通过矩阵来进行处理。Android的图形变换矩阵是一个3×3的矩阵,如下:
矩阵变换规律:
在图形变换矩阵中,同样是通过一个一维数组来模拟矩阵,并通过setValues()方法将一个一维数组转换为图形变换矩阵,代码如下:
float[] mImageMatrix=new float[9];
Matrix matrix=new Matrix();
matrix.setValues(mImageMatrix);
当获得变换矩阵后,就可以通过以下代码将一个图形以这个变换矩阵的形式绘制出来
canvas.drawBitmpa(mBitmap,matrix,null);
Android系统同样提供了一些API来简化矩阵的运算。Android中使用Matri类来封装矩阵,并提供了以下几个方法:
Matri类的set方法会重置矩阵中的所有值,而post和pre方法不会,这两个方法常用来实现矩阵的混合作用。不过要注意的是,矩阵运算并不满足交换律,所以矩阵乘法的前乘和后乘是两种不同的运算方式。例如:
(1)先平移到(300,100)
(2)再旋转45度
(3)最后平移到(200,200)
如果使用后乘运算,表示当前矩阵乘上参数代表的矩阵
matri X.setRotate(45);
matri X.postTranslate(200,200);
如果使用前乘运算,表示参数代表的矩阵乘上当前矩阵
matri X.setTranslate(200,200);
matri X.preRotate(45);
drawBitmapMesh()与操纵像素点来改变色彩的原理类似,只不过是把图像分成一个个的小块,然后通过改变每一个图像块来修改整个图像。代码如下:
drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[]verts, int vertOffset, int[]colors, int colorOffset, Paint paint)
关键参数如下:
首先看一下API Demo中一张经典的图:
ProterDuffXfermode设置的是两个图层交集区域的显示方式,dst是先画的图形,而src是后画的图形。
经常用到DST_IN、SRC_IN模式来实现将一个矩形图片变成圆角或者圆形图片的效果。
如下:先用一个普通画笔画画一个遮罩层,再用带ProterDuffXfermode的画笔将图形画在罩层上,这样就可以通过上面所说的效果来混合两个图像了。
mBitmap=BitmapFactory.decodeResourse(getResoures(),R.drawable.test1);
mOut=Bitmap.createBitmap(mBitmap.getWidth(),mBitmap.getHeight(),Bitmap.Config.ARGB_8888);
Canvas canvas=new Canvas();
mPaint=new Paint();
mPaint.setAntiAlias(true);
canvas.drawRoundRect(0,0,mBitmap.getWidth(),mBitmap.getHeight(),80,80,mPaint);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(mBitmap,0,0,mPaint);
Shader又被称之为着色器、渲染器,它用来实现一系列的渐变,渲染效果。Android中的Shader包括以下几种。
除了第一个Shader以外,其他的Shader都比较正常,实现了名副其实的渐变,渲染效果。而与其他的Shader所产生的渐变不同,BitmapShader产生的是一个图像,这有点像Photoshop中的图像填充渐变,他的作用就是通过Paint对画布进行指定Binmap的填充,填充模式有三种:
mBitmap=BitmapFactory.decodeResource(getResources(),R.drawable.test);
mBitmapShader=new BitmapShader(mBitmap,Shader.TileMode.CLAMP,Shader.TileMode.CLAMP);
mPaint=new Paint();
mPaint.setShader(mBitmapShader);
canvas.drawCircle(500,250,200,mPaint);
paint.setShader(new LinearGradient(0,0,400,400,Color.BLUE,Color.YELLOW,Shader.TileMode.REPEAT));
canvas.drawRect(0,0,400,400,paint);
mEffects[0]=null;
mEffects[1]=new CornerPathEffect();
mEffects[2]=new DiscretePathEffect();
mEffects[3]=new DashPathEffect();
Path path=new Path();
path.addRect(0,0,8,8,Path.Direction.CWW);
mEffects[4]=new PathDashPathEffect(path,12,0,PathDashPathEffect.Style.TPTATE);
mEffects[5]=ComposePathEffect(mEffect[3],mEffects[1]);
for(int i=0;i<mEffects.length;i++){
mPaint.setPathEffect(mEffects[i]);
canvas.drawPath(mPath,mPaint);
canvas.translate(0,200);
}
View通过刷新来重绘视图,Android系统通过发出VSYNC信号来进行屏幕的重绘,刷新的间隔时间为16ms。如果在16ms内View完成你所需要执行的所有操作,那么用户在视觉上,就不会产生卡顿的感觉;而如果执行的操作逻辑太多,特别是需要频繁刷新的界面上,例如游戏界面,那么就不会不断阻塞主线程,从而导致画面卡顿。很多时候在自定义View的Log中经常会看到如下警告:
“Skipped 47 frames!The application may be doing too much work on its main thread"
这些警告的产生很多情况下就是因为在绘制过程中,处理的逻辑太多造成的。
因此,Android系统提供了SurfaceView组建来解决这个问题。SurfaceView可以说是View的孪生兄弟,但它与View还是有所不同的,它们的区别主要体现在一下几点:
● 创建SurfaceView
创建自定义的SurfaceView继承自SurfaceView,并实现两个接口——SurfaceHolder。Callback和Runnable,代码如下:
public class SurfaceViewTemplate extends SurfaceView implements SurfaceHolder.Callback, Runnable
通过实现这两个接口没就需要在自定义的SurfaceView中实现接口的方法:
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
分别对应SurfaceView的创建,改变和销毁过程。
对于Runnable接口,需要实现run()方法,代码:
@Override
public void run() {
}
●初始化SurfaceView
通常需要定义以下三个成员变量,代码如下:
//SurfaceHolder
private SurfaceHolder mHolder;
//用于绘图的Canvas
private Canvas mCanvas;
//子线程标志位
private boolean mIsDrawing;
初始化方法中对SurfaceHolder初始化,并注册SurfaceHolder的回调方法。
mHolder = getHolder(); mHolder.addCallback(this);
在SurfaceView中没我们也通过Canvas来进行绘图,而另一个标志位,则是用来控制子线程的。
●使用SurfaceView
通过SurfaceHolder对象的lockCanvas()方法,就可以获得当前的Canvas绘图对象。获取到的Canvas对象还是继续上次的Canvas对象,而不是一个新的对象。因此之前的绘图操作都将被保留,如果需要擦除,则可以在绘制前,通过drawColor()方法进行清屏操作。
绘制的时候,充分利用SurfaceView的三个回调方法,在SurfaceCreate()方法中开启子线程进行绘制,而子线程使用一个while(mIsDrawing)的循环来不停的进行绘制,而在绘制的具体逻辑中,通过lockCanvas()方法获取Canvas对象进行绘制,并通过unlockCanvasAndPost(mCanvas)方法对画布内容进行提交。整个SurfaceView的末班代码如下:
package com.example.hp.dragviewgroup;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
/** * Created by hp on 2016/1/20. */
public class SurfaceViewTemplate extends SurfaceView implements SurfaceHolder.Callback, Runnable {
//SurfaceHolder
private SurfaceHolder mHolder;
//用于绘图的Canvas
private Canvas mCanvas;
//子线程标志位
private boolean mIsDrawing;
public SurfaceViewTemplate(Context context) {
super(context);
initView();
}
public SurfaceViewTemplate(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public SurfaceViewTemplate(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private void initView() {
mHolder = getHolder();
mHolder.addCallback(this);
setFocusable(true);
setFocusableInTouchMode(true);
//控制是否在屏幕应保持修改的值。
this.setKeepScreenOn(true);
// mHolder.setFormat(PixelFormat.OPAQUE);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
mIsDrawing = true;
new Thread(this).start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mIsDrawing = false;
}
@Override
public void run() {
while (mIsDrawing) {
draw();
}
}
private void draw() {
try {
mCanvas = mHolder.lockCanvas();
//draw something
} catch (Exception e) {
} finally {
if (mCanvas != null) {
mHolder.unlockCanvasAndPost(mCanvas);
}
}
}
}