彩色特效处理

1.色彩矩阵分析

在色彩处理中,通常用以下三个角度来描述一个图像。

  • 色调——物体传播的颜色
  • 饱和度——颜色的纯度,从0(灰)到100%(饱和)来进行描述
  • 亮度——颜色的相对明暗程度
    在Android中,系统使用一个颜色矩阵——ColorMatrix,来处理图像的这些色彩效果。Android中的颜色矩阵是一个4×5的数字矩阵,它用来对图片的色彩进行处理。而对于每个像素点,都有一个颜色分量矩阵用来保存颜色的RGBA值,如下图所示:
    A=afkpbglqchmrdinsejotC=RGBA1
    在这个4×5的颜色矩阵中按以下方式划分。
  • 第一行的abcde值用来决定新的颜色值中的R——红色
  • 第二行的fghij值用来决定新的颜色值中的G——绿色
  • 第三行的klmno值用来决定新的颜色值中的B——蓝色
  • 第四行的pqrst值用来决定新的颜色值中的A——透明度
  • 矩阵A中的第五列——ejot值分别用来决定每个分量重的offset,即偏移量。

1.1 改变偏移量

A=10000100001000010000A=100001000010000110010000
在这个矩阵中修改了R,G所对应的颜色偏移量,那么最后的处理结果就是图像的红色,绿色分量增加了100.而我们知道,红色混合绿色会得到黄色,所以使得整个图像的色调偏黄色。

1.2 改变颜色系数

A=10000200001000010000
在这个矩阵中,改变了G分量所对应的系数g,这样的矩阵运算后G分量会变成以前的两倍,最终效果就是图像的色调更加偏绿。

1.3 改变色光属性

图像的色调,饱和度,亮度这三个属性在图像处理中的使用非常多,因此颜色矩阵中,也封装了一些API来快速调用这些参数,而不用每次都去计算矩阵的值。
在Android中,系统封装了一个类——ColorMatrix,也就是说前面的颜色矩阵。通过这个类,可以很方便地改变矩阵值来处理颜色效果。

ColorMatrix colorMatrix = new ColorMatrix();
  • 色调
    Android系统提供了setRotate(int axis, float degree)来帮助我们设置颜色的色调。第一个参数,系统分别使用0、1、2来代表Red、Green、Blue三种颜色的处理;而第二个参数,就是需要处理的值,代码如下:
ColorMatrix hueMatrix = new ColorMatrix();hueMatrix .setRotate(0,hue0);hueMatrix .setRotate(1,hue1);hueMatrix .setRotate(2,hue2);

通过这样的方法可以为RGB三种颜色分量分别重新设置不同的色调值。

  • 饱和度
    Android系统提供了setSaturation(float sat)方法来设置颜色的饱和度,参数代表设置颜色饱和度的值,当饱和度为0时,图像就变成灰度图像了。
ColorMatrix saturationMatrix=new ColorMatrix(); saturationMatrix.setSaturation(saturation);
  • 亮度
    当三原色以相同的比例进行混合的时候,就会显示出白色,系统正式使用这个原理来改变一个图像的亮度的,代码如下,当亮度为0时,图像就变成全黑了。
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);

2. 常用图像颜色矩阵处理效果

2.1 灰度效果

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,

2.2 图像反转

-1, 0, 0, 1, 1,  0,-1, 0, 1, 1, 0, 0,-1, 1, 1, 0, 0, 0, 1, 0,

2.3 怀旧效果

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,

2.4 去色效果

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,

2.5 高饱和度

 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,

3. 像素点分析

作为更加精确的图像处理方式,可以通过改变每个像素点的具体ARGB值,来达到处理一张图像效果的目的。需要注意的是,传递进来的原始图片是不能修改的,一般根据原始图片生成一张新的图片来修改。
在Android中,系统提供了Bitmap.getPixels()方法来帮我们提取整个Bitmap中的像素点,并保存到一个数组中。

bitmap.getPixels(pixels,offset,stride,x,y,width,height);

这几个参数的含义如下:

  • pixels——接收位图颜色值的数组
  • offset——写入到pixels[]中的第一个像素索引值
  • stride——pixels[]中的行间距
  • x——从位图中读取的第一个像素的x坐标值
  • y——从位图中读取的第一个像素的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);

4. 常用像素像素点处理效果

4.1 底片效果

若存在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;}

4.2 老照片效果

求某像素点的老照片效果算法,代码如下:

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);

4.3 浮雕效果

若存在ABC3个像素点,要求B点对应的浮雕效果算法,代码如下:

B.r=C.r-B.r+127;B.g=C.g-B.g+127;B.b=C.b-B.b+127;

5. 图形特效处理

5.1 Android变形矩阵——Matrix

对于图像的图形变换,Android系统也通过矩阵来进行处理。Android的图形变换矩阵是一个3×3的矩阵,如下:

A=adgbehcfiC=XY1R=X1Y11=AC
当使用变换矩阵去处理每一个像素点的时候,与颜色矩阵的矩阵乘法一样,计算公式如下:
X1=a×X+b×Y+cY1=d×X+e×Y+f1=g×X+h×Y+i
通常情况下,会让g=h=0,i=1,这样使1=g×X+h×Y+i恒成立。因此只需要着重关注上面几个参数就可以了。
图形初始矩阵和色彩变换初始矩阵一样:
100010001
图像的变形处理通常包含以下四类基本变换。

  • Translate——平移变换
  • Rotate——旋转变换
  • Scale——缩放变换
  • Skew——错切变换
5.1.1 平移变换
5.1.2 旋转变换
5.1.3 缩放变换
5.1.4 错切变换

矩阵变换规律:

ADGBEHCFIScale_XSkew_Y0Skew_XScale_Y0Trans_XTrans_Y1

  • A和E控制Scale——缩放变换
  • B和D控制Skew——错切变换
  • C和F控制Trans——平移变换
  • A、B、D、E共同控制Rotate——旋转变换

在图形变换矩阵中,同样是通过一个一维数组来模拟矩阵,并通过setValues()方法将一个一维数组转换为图形变换矩阵,代码如下:

float[] mImageMatrix=new float[9];Matrix matrix=new Matrix();matrix.setValues(mImageMatrix);

当获得变换矩阵后,就可以通过以下代码将一个图形以这个变换矩阵的形式绘制出来

canvas.drawBitmpa(mBitmap,matrix,null);

Android系统同样提供了一些API来简化矩阵的运算。Android中使用Matri类来封装矩阵,并提供了以下几个方法:

  • matri X.setRoate()——旋转变换
  • matri X.setTranslate()——平移变换
  • matri X.setScale()——缩放变换
  • matri X.setSkew()——错切变换
  • pre()和post()——提供矩阵的前乘和后乘运算

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);

5.2 像素块分析

drawBitmapMesh()与操纵像素点来改变色彩的原理类似,只不过是把图像分成一个个的小块,然后通过改变每一个图像块来修改整个图像。代码如下:

drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[]verts, int vertOffset, int[]colors, int colorOffset, Paint paint)

关键参数如下:

  • bitmap:将要扭曲的图像
  • meshWidth:需要的横向网格数目
  • meshHeight:需要的纵向网格数目
  • verts:网格交叉点坐标数组
  • vertOffset:verts数组中开始跳过的(x,y)坐标对的数目

6. Android图像处理之画笔特效处理

6.1 PorterDuffXfermode

首先看一下API Demo中一张经典的图:
Android图像处理之色彩特效处理(学习笔记)_第1张图片
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);

6.2 Shader

Shader又被称之为着色器、渲染器,它用来实现一系列的渐变,渲染效果。Android中的Shader包括以下几种。

  • BitmapShader——位图Shader
  • LinearGradient——线性Shader
  • RadialGradient——光束Shader
  • SweepGradient——梯度Shader
  • ComposeShader——混合Shader

除了第一个Shader以外,其他的Shader都比较正常,实现了名副其实的渐变,渲染效果。而与其他的Shader所产生的渐变不同,BitmapShader产生的是一个图像,这有点像Photoshop中的图像填充渐变,他的作用就是通过Paint对画布进行指定Binmap的填充,填充模式有三种:

  • CLAMP拉伸——拉伸的是图片最后的那一个像素,不断重复
  • REPEAT重复——横向,纵向不断重复
  • MIRROR镜像——横向不断翻转重复,纵向不断翻转重复
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);

6.3 PathEffect

  • CornerPathEffect——拐角处变得圆滑
  • DiscretePathEffect——线段上产生许多杂点
  • DashPathEffect——虚线
  • PathDashPathEffect——和DashPathEffect类似,在它的基础上,可以设置显示点的图形,即方形点的虚线,圆形点的虚线
  • ComposePathEffect——将任意两种路径特性组合起来形成新的效果
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);}

7. View的孪生兄弟——SurfaceView

7.1 SurfaceView和View的区别

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还是有所不同的,它们的区别主要体现在一下几点:

  • View主要适用于主动更新的情况下,而SurfaceView主要使用与被动更新,例如频繁的刷新。
  • View在主线程中对画面进行刷新,而SurfaceView通常会通过一个子线程来进行页面的刷新。
  • View在绘图时没有使用双缓冲机制,而SurfaceView在底层实现机制中就已经实现了双缓冲机制。

7.2 SurfaceView的使用

● 创建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

通常需要定义以下三个成员变量,代码如下:

//SurfaceHolderprivate SurfaceHolder mHolder;//用于绘图的Canvasprivate 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);            }        }    }}

更多相关文章

  1. Android UI设计之自定义TextView属性,实现带边框效果的TextView
  2. Android中如何使用ViewPager实现类似laucher左右拖动效果
  3. 仿网易新闻效果源码分析
  4. Android滤镜效果实现及原理分析
  5. [置顶] 我的Android进阶之旅------>Android颜色值(RGB)所支持的
  6. android 实现带清除效果的EditText(附带抖动效果)
  7. Android通讯录字母排序城市列表展示效果

随机推荐

  1. 浅谈Android中的MVP模式
  2. Qt for Android之2048实现
  3. 推荐几个可提高开发效率的Android(安卓)s
  4. 【iOS-cocos2d-X 游戏开发之三】Mac下配
  5. 图文来教你在eclipse下用gradle 来打包An
  6. Android(安卓)API 实验记录(一)
  7. ImageSwitcher的应用
  8. 谷歌I/O 2011开发者大会现场报道
  9. 「Android集成微信支付」面试题回答思路
  10. android穿越之旅--如何弹出一个非比寻常