读书笔记—— Android绘图机制与处理技巧

Android绘图机制与处理技巧

Android屏幕相关知识

屏幕参数

  1. 屏幕大小:指屏幕对角线的长度,通常用“寸”来度量。
  2. 分辨率:指手机屏幕的像素点个数。
  3. PPI:每英寸像素(pixels per inch)又被称为DPI(Dots Per Inch)。它是由对角线的像素点数除以屏幕的大小得到的。

    独立像素密度dp

    同样是100dp的长度,在mdpi中为100px,而在hdpi中为150dx。
    在mdpi中1dp=1px,在hdpi中1dp=1.5px,在xhdpi中1dp=2px,在xxhdpi中1dp=3px。

ldpi:mdpi:hdpi:xhdpi:xxhdpi=3:4:6:8:12。

DiaplayUtil:[https://github.com/rainyandsunny/AndroidUtil]

Android绘图技巧

  1. setAntiAlias(): 设置画笔的锯齿效果
  2. setColor(): 设置画笔的颜色
  3. setARGB(): 设置画笔的A、R、G、B值
  4. setAlpha(): 设置画笔的Alpha值
  5. setTextSize(): 设置字体的尺寸
  6. setStyle(): 设置画笔的风格(空心或实心)
  7. setStrokeWidth():设置空心边框的宽度

Android XML绘图

Bitmap

在xml中使用Bitmap非常简单,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/ic_launcher"
/>

Shape

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <corners
        android:radius="10dp"
        android:topLeftRadius="10dp"
        android:topRightRadius="10dp"
        android:bottomLeftRadius="10dp"
        android:bottomRightRadius="10dp"
        /><!--圆角-->
    <gradient
        android:angle="45"
        android:centerColor="@android:color/holo_green_light"
        android:endColor="@android:color/holo_blue_bright"
        android:gradientRadius="10"
        android:startColor="@android:color/background_light"
        android:type="linear"
        android:useLevel="true"
        /><!--渐变-->
    <padding
        android:left="10dp"
        android:top="10dp"
        android:right="10dp"
        android:bottom="10dp"
        /><!--内边距-->
    <size
        android:width="200dp"
        android:height="100dp"
        /><!--指定大小-->
    <solid
        android:color="@android:color/holo_red_dark"
        /><!--填充颜色-->
    <stroke
        android:width="4dp"
        android:color="@android:color/holo_blue_light"
        android:dashWidth="4dp"
        android:dashGap="0dp"
        /><!--指定边框-->

</shape>

gradient定义该形状里面为渐变色填充,startColor起始颜色,endColor结束颜色,angle表示方向角度。当angle=0时,渐变色是从左向右。 然后逆时针方向转,当angle=90时为从下往上。

Layer

类似于图层的效果。

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:drawable="@drawable/bitmap"
        />
    <item
        android:drawable="@drawable/bitmap"
        android:left="30.0dp"
        android:top="30.0dp"
        android:right="30.0dp"
        android:bottom="30.0dp"
        />

</layer-list>

Selector

selector的作用在于帮助开发者实现静态绘图中的点击事件反馈,通过给不同的事件设置不同的图像,从而在程序中根据用户输入,返回不同的效果。

Android图像处理技巧

Canvas

Canvas作为绘制图形的直接对象,提供了以下几个非常有用的方法。

  1. Canvas.save()
  2. Canvas.restore()
  3. Canvas.translate()
  4. Canvas.rotate()

Canvas.save(): 作用就是将之前的所有已绘制图像保存起来,让后续的操作就好像在一个新的图层上操作一样。

Canvas.restore(): 将我们在save()之后绘制的所有图像与save()之前的图像进行合并。

Canvas.translate():将画布原点平移至(x,y)

Canvas.rotate(): 画布旋转

Layer图层

一张复杂的画可以由很多个图层叠加起来,形成一个复杂的图像。在Android中,使用saveLayer()方法来创建一个图层,图层同样是基于栈的结构进行管理的。

Android通过调用saveLayerAlpha()、saveLayer()方法将一个图层入栈,使用restore()、restoreToCount()方法将一个图层出栈。入栈的时候,后面所有操作都发生在这个图层上,出栈的时候,则会把图像绘制到上层Canvas上。

Android图像处理之色彩特效处理

色彩矩阵分析

色调——物体传播的颜色
饱和度——颜色的纯度,从0(灰)到100%(饱和)
亮度——颜色的相对明暗程度

在Android中,系统使用一个颜色矩阵——ColorMatrix,来处理图像的这些色彩效果。Android中的颜色矩阵是一个4*5的数字矩阵,它用来对图片的色彩进行处理。而对于每个像素点,都有一个颜色分量矩阵用来保存颜色的RGBA值。

颜色矩阵如下:

颜色矩阵

矩阵乘法运算如下:

矩阵乘法运算

R1 = a*R + b*G + c*B + d*A + e;
G1 = f*R + g*G + h*B + i*A + j;
B1 = k*R + 1*G + m*B + n*A + o;
A1 = p*R + q*G + r*B + s*A + t;

由运算我们知道:
第一行的abcde值用来决定新的颜色值中的R——红色
第二行的fghij值用来决定新的颜色值中的G——绿色
第三行的klmno值用来决定新的颜色值中的B——蓝色
第四行的pqrst值用来决定新的颜色值中的A——透明度

矩阵A中的第五列——ejot值分别用来决定每个分量中的offset,即偏移量、

改变偏移量

从上面可以知道,只需要将第五列的值进行修改即可。即可改变颜色的偏移量,其他值保存初始矩阵的值。

改变颜色系数

如果修改颜色分量重的某个系数值,而其他值依然保存初始矩阵的值。

改变色光属性

Android中,系统封装了一个类——ColorMatrix,也就是前面说的颜色矩阵。

  1. 色调

Android系统提供了setRotate(in 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);
  1. 饱和度

Android系统提供了setSaturation(float sat)方法来设置颜色的饱和度,参数即代表设置颜色饱和度的值,当饱和度为0时,图像就变成灰度图像。

ColorMatrix saturationMatrix = new ColorMatrix();
saturationMatrix.setSaturation(saturation);
  1. 亮度

当三原色以相同的比例进行混合的时候,就会显示出白色。系统也是利用这个原理来改变一个图像的亮度的,代码如下。当亮度为0时,图像就变为全黑了。

ColorMatrix lumMatrix = new ColorMatrix();
lumMatrix.setScale(lum,lum,lum,1);

除了单独使用上面三种方式来进行颜色效果的处理之外,Android系统还封装了矩阵的乘法运算。它提供了postConcat()方法来将矩阵的作用效果混合,从而叠加处理效果。

ColorMatrix imageMatrix = new ColorMatrix();
imageMatrix.postConcat(hueMatrix);
imageMatrix.postConcat(saturationMatrix);
imageMatrix.postConcat(lumMatrix);

在设置好处理的颜色矩阵后,通过使用Paint类的setColorFilter()方法,将通过imageMatrix构造的ColorMatrixColorFilter对象传递进去
,并使用这个画笔来绘制原来的图像,从而将颜色矩阵作用到原图中。

paint.setColorFilter(new ColorMatrixColorFilter(imageMatrix));
canvas.drawBitmap(bm,0,0,paint);

Android系统不允许直接修改原图,必须通过原图创建一个同样大小的Bitmap,并将原图绘制到该Bitmap中,以一个副本的形式来修改图像。
如下:

Bitmap bmp = Bitmap.createBitmap(bm.getWidth(),bm.getHeight(),Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmp);
Paint paint = new Paint();
canvas.drawBitmap(bm,0,0,paint);

其中bm为原图,bmp为创建的副本。
常用图像颜色矩阵处理效果
  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. 图像反转
    -1,0,0,1,1,
    0,-1,0,1,1,
    0,0,-1,1,1,
    0,0,0,1,0,

  3. 怀旧效果
    0.393F,0.769F,0.189F,0,0,
    0.349F,0.686F,0.168F,0,0,
    0.272F,0.534F,0.131F,0,0,
    0,0,0,1,0

  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

  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

像素点分析

作为更加精确的图像处理方式,可通过改变每个像素点的具体ARGB值,来达到处理一张图像效果的目的。这里要注意的是,传递进来的原始图片是不能修改的(mutable),一般根据原始图片生成一张新的图片来修改。

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

然后就可以对各个像素进行相应的处理了:

r1 = (int)(0.393*r + 0.769*g + 0.189*b);
g1 = ...
b1 = ...

在通过如下所示代码将新的RGBA值合成像素点:

newPx[i] = Color.argb(a,r1,g1,b1);

最后将新的处理之后的像素点数组重新set给我们的bitmap,达到像素处理的目的。

bmp.setPixels(newPx,0,width,0,0,width,height);

常用图像像素点处理效果

  1. 底片效果

若存在B像素点,要求对B点对应的底片效果算法,代码如下:

B.r = 255 - B.r;
B.g = 255 - B.g;
B.b = 255 - B.b;
  1. 老照片效果

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

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);
  1. 浮雕效果

若存在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变形矩阵——Matrix

Android的图形变换矩阵是一个3*3的矩阵:

图形变换矩阵

平移变换

平移变换

旋转变换

假设点P(x0,y0),其与X轴正方向夹角为α,那么:

x0 = rcosα
y0 = rsinα

以坐标原点为旋转中心旋转到P(x,y),则:

x = rcos(α+θ) = rcosαcosθ - rsinαsinθ = x0cosθ - y0sinθ
y = rsin(α+θ) = rsinαcosθ + rcosαsinθ = y0cosθ + x0sinθ
缩放变换
x = k1*x0
y = k2+y0
错切变换

水平错切:

水平错切

垂直错切:

垂直错切

错切变换的计算公式为:

x = x0 + K1*y0;
y = K2*x0 + y0;

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

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

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

canvas.drawBitmap(mBitmap,matrix,null);

Android中使用Matrix类来封装矩阵,并提供了以下几个方法来实现上面的几种变换方式:

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

Matrix类的set方法会重置矩阵中的所有值,而post和pre方法不会,这两个方法常用来。

后乘运算: 当前矩阵乘上参数代表的矩阵。
前乘运算: 参数代表的矩阵乘上当前矩阵。

像素块分析

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)坐标对的数目。

这个方法将图片分割为若干个图像块,靠改变网格交叉点的坐标值改变来重新定位每一个图像块,从而达到图像效果处理的功能。但是使用起来也非常复杂,关键在于计算,确定新的交叉点的坐标。

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

PorterDuffXfermode

PorterDuffXfermode设置的是两个图层交集区的显示方式,dst是先画的图形,而src是后画的图形。

SRC :只绘制源图像

DST :只绘制目标图像

DST_OVER :在源图像的顶部绘制目标图像

DST_IN :只在源图像和目标图像相交的地方绘制目标图像

DST_OUT :只在源图像和目标图像不相交的地方绘制目标图像

DST_ATOP :在源图像和目标图像相交的地方绘制目标图像,在不相交的地方绘制源图像

SRC_OVER :在目标图像的顶部绘制源图像

SRC_IN :只在源图像和目标图像相交的地方绘制源图像

SRC_OUT :只在源图像和目标图像不相交的地方绘制源图像

SRC_ATOP :在源图像和目标图像相交的地方绘制源图像,在不相交的地方绘制目标图像

XOR :在源图像和目标图像重叠之外的任何地方绘制他们,而在不重叠的地方不绘制任何内容

LIGHTEN :获得每个位置上两幅图像中最亮的像素并显示

DARKEN :获得每个位置上两幅图像中最暗的像素并显示

MULTIPLY :将每个位置的两个像素相乘,除以255,然后使用该值创建一个新的像素进行显示。结果颜色=顶部颜色*底部颜色/255

SCREEN :反转每个颜色,执行相同的操作(将他们相乘并除以255),然后再次反转。结果颜色=255-(((255-顶部颜色)*(255-底部颜色))/255)

图示

圆角,圆形图片处理,代码见:
https://github.com/rainyandsunny/AndroidUtil/tree/master/AndroidUtil/app/src/main/java/com/swjtu/deanstar/util

Shader

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

  1. BitmapShader——位图Shader
  2. LinearGradient——线性Shader
  3. RadialGradient——光束Shader
  4. SweepGradient——梯度Shader
  5. ComposeShader——混合Shader

除了第一个Shader以外,其他的Shader都比较正常,实现了真正的渐变、渲染效果。BitmapShader产生的是一个图像。它的作用就是通过Paint对画布进行制定Bitmap的填充,填充时有以下几种模式:

CLAMP——拉伸的是图片最后的那一个像素,不断重复
REPEAT重复——横向、纵向不断重复
MIRROR镜像——横向不断翻转重复,纵向不断翻转重复

例如,我们可以用CLAMP来画出圆角图片等。

 Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.timg,null);
Paint mPaint = new Paint();
Bitmap out = Bitmap.createBitmap(bitmap.getWidth()
        ,bitmap.getHeight(),Bitmap.Config.ARGB_8888);
BitmapShader mBitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP,Shader.TileMode.CLAMP);
mPaint.setShader(mBitmapShader);
Canvas canvas = new Canvas(out);
canvas.drawCircle(500,250,200,mPaint);

PathEffect

  1. CornerPathEffect

将拐角处变得圆滑

  1. DiscretePathEffect

使用这个效果,线段上就会出现很多杂点。

  1. DashPathEffect

绘制虚线。用一个数组来设置各个点之间的间隔,另一个参数phase则用来控制绘制时数组的一个偏移量。

  1. PathDashPathEffect

和DashPathEffect类似,它的功能更强大,可以设置显示点的图形,即方形点的虚线,圆形点的虚线。

  1. ComposePathEffect

通过ComposePathEffect来组合PathEffect,这个方法的功能就是将路径特性组合起来形成一个新的效果。

PathEffect

依次为:无效果、CornerPathEffect、DiscretePathEffect、PathDashPathEffect,ComposePathEffect(CornerPathEffect,DashPathEffect)。

SurfaceView的使用

SurfaceView与View的区别

View主要适用于主动更新的情况下,而SurfaceView主要适用于被动更新,例如频繁地刷新。

View 在主线程中对画面进行刷新,而SurfaceView通过一个子线程来进行画面的刷新。

View在绘图时没有使用双缓冲机制,而SurfaceView在底层实现中就已经实现了双缓冲机制。

SurfaceView的使用

SurfaceView的使用通常可以使用下面的模板:

1 创建SurfaceView

创建自定义的SurfaceView继承自SurfaceView,并实现两个接口——SurfaceHolder.Callback和Runnable。

public class SurfaceViewTemplate extends SurfaceView
implements SurfaceHolder.Callback,Runnable

分别实现如下方法:

@Override
public void surfaceCreated(SurfaceHolder holder){}

@Override
public void surfaceChanged(SurfaceHolder holder,int format,int width,int height){}

@Override
public void surfaceDestroyed(SurfaceHolder holder){}

对于Runnable接口,实现run()方法。

2. 初始化SurfaceView

在自定义SurfaceView的构造方法中,需要对SurfaceView进行初始化,在自定义的SurfaceView中,通常需要定义以下三个成员变量,代码如下:

//SurfaceHolder
private SurfaceHolder mHolder;

//用于绘图的Canvas
private Canvas mCanvas;

//子线程标志位
private boolean mIsDrawing;

初始化方法就是对SurfaceHolder进行初始化,通过以下代码来初始化一个SurfaceHolder对象,并注册SurfaceHolder的回调方法。

mHolder = getHolder();

mHolder.addCallback(this);

3. 使用SurfaceView

通过SurfaceHolder对象的lockCanvas()方法,就可以获得当前Canvas绘图。接下来,就可以与在View中进行的绘制操作一样进行绘制了。获取到的Canvas对象还是继续上次的Canvas对象,而不是一个新的对象。因此之前的操作都将被保留,如果需要擦除,则可以在绘制前,通过drawColor()方法来进行清屏操作。

绘制的时候,充分利用SurfaceView的三个回调方法,在surfaceCreated()方法中开启子线程进行绘制,而子线程使用一个while(mIsDrawing)的循环来不停地进行绘制,而在绘制的具体逻辑中,通过lockCanvas()方法获得的Canvas对象进行绘制,并通过unlockCanvasAndPost(mCanvas)方法对画布内容进行提交。

SurfaceView的使用示例代码见:

https://github.com/rainyandsunny/AndroidUtil/blob/master/AndroidUtil/app/src/main/java/com/swjtu/deanstar/androidutil/View/SurfaceViewTeplate.java