6.1 屏幕的尺寸信息
6.1.1 屏幕参数6.1.2 系统屏幕密度6.1.3 独立像素密度dp6.1.4 单位转换6.2 2D绘图基础
6.3 Android XML绘图
6.3.1 Bitmap6.3.2 Shape6.3.3 Layer6.3.4 Selector6.4 Android绘图技巧
6.4.1 Canvas6.4.2 Layer图层6.5 Android图像处理之色彩特效处理
6.5.1 色彩矩阵分析6.5.2 Android颜色矩阵——ColorMatrix6.5.3 常用图像颜色矩阵处理效果6.5.4 像素点分析6.5.5 常用图像像素点处理效果6.6 Android图像处理之图形特效处理
6.6.1 Android变形矩阵——Matrix6.6.2 像素块分析6.7 Android图像处理之画笔特效处理
6.7.1 PorterDuffXfermode6.7.2 Shader6.7.3 PathEffect6.8 View之孪生兄弟——SurfaceView
6.8.1 SurfaceView与View的区别6.8.2 SurfaceView的使用6.8.3 SurfaceView实例6.1.1 屏幕参数
屏幕大小:屏幕对角线的长度,通常用寸来度量,比如4.7寸手机、5.5寸手机分辨率:手机屏幕的像素点个数,720x1280,宽有720个像素点,高有1280个像素点PPI:每英寸像素,又称DPI,由对角线像素点除以屏幕大小获得,通常440PPI就是非常高的屏幕宽度了6.1.2 系统屏幕密度
ldpi: 240x320,屏幕密度为120的手机设备 mdpi: 320x480,屏幕密度为160的手机设备(此为baseline,其他均以此为基准,在此设备上,1dp = 1px) hdpi: 480x800,屏幕密度为240的手机设备 xhdpi: 720x1280,屏幕密度为320的手机设备 xxhdpi:1080x1920,屏幕密度为480的手机设备6.1.3 独立像素密度
Android系统使用mdpi即密度值为160的屏幕作为标准,在这个屏幕上1px = 1dpldpi:mdpi:hdpi:xhdpi:xxhdpi=3:4:6:8:126.1.4 单位转换
/** * dp、sp转换成px的工具类 * */ public class DisplayUtils { /** * 将px值转换为dpi或者dp值,保持尺寸大小不变 * * @param content * @param pxValus * * @return */ public static int px2dip(Context context, float pxValus) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (pxValus / scale + 0.5f); } /** * 将dip和dp值转换为px值,保证尺寸大小不变 * * @param content * @param dipValus * * @return */ public static int dip2px(Context context, float dipValus) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dipValus / scale + 0.5f); } /** * 将px值转换为sp值,保证文字大小不变 * * @param content * @param pxValus * @return */ public static int px2sp(Context context, float pxValus) { final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; return (int) (pxValus / fontScale + 0.5f); } /** * 将sp值转换为px值,保证文字大小不变 * * @param content * @param spValus * @return */ public static int sp2px(Context context, float spValus) { final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; return (int) (spValus / fontScale + 0.5f); } }系统提供了TypedValue类帮助我们转换
/** * dp2px */ protected int dp2px(int dp){ return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dp,getResources().getDisplayMetrics()); } /** * sp2px */ protected int sp2px(int sp){ return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,sp,getResources().getDisplayMetrics()); }1、paint是画笔的意思,有如下几个常用重要的方法:
setAntiAlias():设置画笔的锯齿效果setColor():设置画笔的颜色setARGB():设置画笔的A、R、G、B值setAlpha():设置画笔的Alpha值setTextSize():设置字体的尺寸setStyle():设置画笔的风格(空心或实心)setStrokeWidth():设置空心边框的宽度2、设置Paint的Style可以画出空心或者实心的矩形
3、Canvas是画布的意思,有如下几个常用的绘图方法,每个方法都会传入一支画笔
canvas.drawPoint(x, y, paint):绘制点canvas.drawLine(startX, startY ,endX, endY, paint):绘制直线canvas.drawLines(new float[]{startX1, startY1, endX1, endY1,……,startXn, startYn, endXn, endYn}, paint):绘制多条直线canvas.drawRect(left, top, right, bottom, paint):绘制矩形canvas.drawRoundRect(left, top, right, bottom, radiusX, radiusY, paint):绘制圆角矩形canvas.drawCircle(circleX, circleY, radius, paint):绘制圆paint.setStyle(Paint.Style.STROKE); drawArc(left, top, right,bottom, startAngle, sweepAngle, true, paint):绘制扇形paint.setStyle(Paint.Style.STROKE); drawArc(left, top, right,bottom, startAngle, sweepAngle, false, paint):绘制弧形paint.setStyle(Paint.Style.FILL); drawArc(left, top, right,bottom, startAngle, sweepAngle, true, paint):绘制实心扇形paint.setStyle(Paint.Style.FILL); drawArc(left, top, right,bottom, startAngle, sweepAngle, false, paint):绘制实心弧形canvas.drawOval(left, top, right, bottom, paint):绘制椭圆canvas.drawText(text, startX, startY, paint):绘制文本canvas.drawPosText(text, new float[]{X1,Y1,X2,Y2,……Xn,Yn}, paint):在指定位置绘制文本Path path = new Path(); path.moveTo(50, 50); path.lineTo(100, 100); path.lineTo(100, 300); path.lineTo(300, 50); canvas.drawPath(path, paint):绘制路径6.3.1 Bitmap
通过这样在XML中使用Bitmap就可以将图片直接转换成了Bitmap在程序中使用说白了,很简单,就像我们在给界面布局的时候,给background或者是imageView设置一个图片一样 <?xml version="1.0" encoding="utf-8"?> <bitmap xmlns:android="http://schemas.android.com/apk/res/android" android:src="@mipmap/ic_launcher"> </bitmap>6.3.2 Shape
通过Shape可以在XML中绘制各种形状
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" //默认为rectangle // 矩形、椭圆、线、圆环 android:shape=["rectangle" | "oval" | "line" | "ring"] > <corners //当shape="rectangle"时使用,定义圆角 //半径,会被后面的单个半径属性覆盖,默认为1dp // 全部的圆角半径,左上、右上、左下、右下 android:radius="integer" android:topLeftRadius="integer" android:topRightRadius="integer" android:bottomLeftRadius="integer" android:bottomRightRadius="integer" /> <gradient //渐变 android:angle="integer"// 渐变角度,必须为45的倍数 android:centerX="integer"// 渐变中心X的位置,范围为0~1 android:centerY="integer"// 渐变中心Y的位置,范围为0~1 android:centerColor="integer"// 渐变中间点的颜色 android:endColor="color"//渐变结束点的颜色 android:gradientRadius="integer"// 渐变的半径 android:startColor="color"// 渐变开始的颜色 android:type=["linear" | "radial" | "sweep"]//渐变类型:线性、放射、扫描 android:useLevel=["true" | "false"] />// <padding android:left="integer" android:top="integer" android:right="integer" android:bottom="integer" /> <size //指定大小,一般在imageview配合scaleType属性使用 android:width="integer" android:height="integer" /> <solid //填充颜色 android:color="color" /> <stroke //指定边框 android:width="integer"// 边框宽度 android:color="color"//颜色 android:dashWidth="integer"//虚线宽度 android:dashGap="integer" />//虚线间隔宽度 </shape>6.3.3 Layer
Layer意为层的意思,我们可以将图像一层一层的叠起来,从下往上叠注意Item属性中的上下左右,意思是该层距离布局的距离,可以理解为变相的padding <?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <!--图片1--> <item android:drawable="@mipmap/ic_launcher"/> <!--图片2--> <item android:bottom="10dp" android:top="10dp" android:right="10dp" android:left="10dp" android:drawable="@mipmap/ic_launcher" /> </layer-list>6.3.4 Selector
Selector的作用在于帮助开发者实现静态绘图中的事件反馈,通过给不同的事件设置不同的图像,从而在程序中根据用户输入,返回不同的效果
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 默认时的背景图片--> <item android:drawable="@mipmap/ic_launcher" /> <!-- 没有焦点时的背景图片--> <item android:drawable="@mipmap/ic_launcher" android:state_window_focused="false" /> <!-- 非触摸模式下获得焦点并单击时的背景图片--> <item android:drawable="@mipmap/ic_launcher" android:state_pressed="true" android:state_window_focused="true" /> <!-- 触摸模式下单击时的背景图片--> <item android:drawable="@mipmap/ic_launcher" android:state_focused="false" android:state_pressed="true" /> <!--选中时的图片背景--> <item android:drawable="@mipmap/ic_launcher" android:state_selected="true" /> <!--获得焦点时的图片背景--> <item android:drawable="@mipmap/ic_launcher" android:state_focused="true" /> </selector>一些常用的属性,其实还是很简单的,就是两种状态
android:state_selected 选中android:state_focused 获得焦点android:state_pressed 点击android:state_enabled 设置是否响应事件,指所有事件android:state_checked 表示被选中,如在RadioButton上使用android:enterFadeDuration 状态改变时,新状态展示时的淡入时间,以毫秒为单位android:exitFadeDuration 状态改变时,旧状态消失时的淡出时间,以毫秒为单位6.4.1 Canvas
1、Canvas提供了以下几种非常有用的方法:
Canvas.save():保存画布,将之前所有已绘制图像保存起来,让后续的操作就好像在一个新的图层上操作一样Canvas.restore():在save()之后绘制的所有图像与save()之前的图像进行合并,可以理解为Photoshop中的合并图层操作Canvas.translate():画布平移,可以理解为将(0,0)坐标系移动到某个(x,y)位置Canvas.roate():画布翻转,将坐标系旋转了一定的角度2、通过一个实例——仪表盘,来理解这几个方法,将仪表盘分为以下几个元素:
仪表盘:外面的大圆盘刻度线:包含四个长的刻度线和其他短的刻度线刻度值:包含长刻度线对应的大的刻度值和其他小的刻度值指针:中间的指针,一粗一细两根指针 public class Clock extends View { private int mHeight, mWidth; public Clock(Context context) { super(context); } public Clock(Context context, AttributeSet attrs) { super(context, attrs); } public Clock(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onDraw(Canvas canvas) { // 获取宽高参数 mWidth = getMeasuredWidth(); mHeight = getMeasuredHeight(); // 画外圆 Paint paintCircle = new Paint(); paintCircle.setStyle(Paint.Style.STROKE); paintCircle.setAntiAlias(true); paintCircle.setStrokeWidth(5); // 画圆:圆心的x坐标,y坐标,半径 canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2, paintCircle); // 画刻度 Paint painDegree = new Paint(); paintCircle.setStrokeWidth(3); for (int i = 0; i < 24; i++) { // 区分整点与非整点 if (i == 0 || i == 6 || i == 12 || i == 18) { painDegree.setStrokeWidth(5); painDegree.setTextSize(30); // 画线,起始点x坐标,起始点Y坐标,结束点x坐标,结束点y坐标 canvas.drawLine(mWidth / 2, mHeight / 2 - mWidth / 2, mWidth / 2, mHeight / 2 - mWidth / 2 + 60, painDegree); String degree = String.valueOf(i); // 画字,文字,x坐标,y坐标 canvas.drawText(degree, mWidth / 2 - painDegree.measureText(degree) / 2, mHeight / 2 - mWidth / 2 + 90, painDegree); } else { painDegree.setStrokeWidth(3); painDegree.setTextSize(15); canvas.drawLine(mWidth / 2, mHeight / 2 - mWidth / 2, mWidth / 2, mHeight / 2 - mWidth / 2 + 30, painDegree); String degree = String.valueOf(i); canvas.drawText(degree, mWidth / 2 - painDegree.measureText(degree) / 2, mHeight / 2 - mWidth / 2 + 60, painDegree); } // 通过旋转画布简化坐标运算,旋转多少角度,以x,y坐标为中心 canvas.rotate(15, mWidth / 2, mHeight / 2); } // 画圆心 Paint paintPointer = new Paint(); paintPointer.setStrokeWidth(30); canvas.drawPoint(mWidth / 2, mHeight / 2, paintPointer); // 画指针 Paint paintHour = new Paint(); paintHour.setStrokeWidth(20); Paint paintMinute = new Paint(); paintMinute.setStrokeWidth(10); // 将上面画的都保存 canvas.save(); // 将坐标平移到中心点 canvas.translate(mWidth / 2, mHeight / 2); canvas.drawLine(0, 0, 100, 100, paintHour); canvas.drawLine(0, 0, 100, 200, paintMinute); // 再合并 canvas.restore(); } }6.4.2 Layer图层
一张复杂的画可以由很多个图层叠加起来,形成一个复杂的图像,使用saveLayer()方法来创建一个图层,图层同样是基于栈的结构进行管理的
@Override protected void onDraw(Canvas canvas) { canvas.drawColor(Color.WHITE); mPaint.setColor(Color.BLUE); canvas.drawCircle(150, 150, 100, mPaint); // 在调用此方法后,所有绘制都在此区域进行,可以理解为单独建了一个图层,并且可以设置透明度 canvas.saveLayerAlpha(0, 0,400,400,127,LAYER_TYPE_NONE); mPaint.setColor(Color.RED); canvas.drawCircle(200, 200, 100, mPaint); canvas.restore(); } 当透明度为127时,即半透明当透明度为255时,即完全不透明当透明度为0时,即完全透明Bitmap图片都是由点阵和颜色值组成的,所谓点阵就是一个包含像素的矩阵,每一个元素对应着图片的一个像素。而颜色值——ARGB,分别对应透明度、红、绿、蓝这四个通道分量,它们共同决定了每个像素点显示的颜色
6.5.1 色彩矩阵分析
在色彩处理中,我们通常用三个角度描述一张图片:
色调:物体传播的颜色饱和度:颜色的纯度,从0(灰)-100%(饱和)来进行描述亮度:颜色的相对明暗程度而在Android中,系统会使用一个颜色矩阵——ColorMatrix,来处理这些色彩的效果,Android中的颜色矩阵是4X5的数字矩阵,他用来对颜色色彩进行处理,而对于每一个像素点,都有一个颜色分量矩阵来保存ARGB值
在Android中是用一维数组来存储矩阵A的,C是一个颜色矩阵分量
根据前面对矩阵A、C的定义,通过矩阵乘法运算法则,可以得到:
矩阵运算的乘法计算过程如下:
从这个公式可以发现
第一行的abcde用来决定新的颜色值R——红色第二行的fghij用来决定新的颜色值G——绿色第三行的kimno用来决定新的颜色值B——蓝色第四行的pqrst用来决定新的颜色值A——透明度矩阵A中的第五列——ejot值分别用来决定每个分量中的offset,即偏移量矩阵变化的计算公式:
R1 = a x R + b x G + c x B + d x A + e;
如果让a = 1,b,c,d,e都等于0,那么计算的结果为R1 = R,因此我们可以构建一个矩阵
如果把这个矩阵公式带入R1 = AC,那么根据矩阵的乘法运算法则,可以得到R1 = R。因此,这个矩阵通常是用来作为初始的颜色矩阵来使用,他不会对原有颜色进行任何变化
那么当我们要变换颜色值的时候,通常有两种方法。一个是直接改变颜色的offset,即偏移量的值来修改颜色的分量。另一种方法直接改变对应RGBA值的系数来调整颜色分量的值
6.5.1.1 改变偏移量
从前面的分析中,可以知道要修改R1的值,只要将第五列的值进行修改即可。即改变颜色的偏移量,其它值保存初始矩阵的值,如图:
在上面这个矩阵中,我们修改了R、G所对应的颜色偏移量,那么最后的处理结果就是图像的红色、绿色分别增加了100。而我们知道,红色混合绿色会得到黄色,所以最终的颜色处理结果就是让整个图片的色调偏黄色
6.5.1.2 改变颜色系数
如果修改颜色分量中的某个系数值,而其他只依然保存初始矩阵的值,如图:
在上面这个矩阵中,改变了G分量所对应的系数g,这样在矩形运算后G分量会变成以前的两倍,最终效果就是图像的色调更加偏绿
6.5.1.3 改变色光属性
下面通过实例看看如何通过矩阵改变图像的色调、饱和度和亮度:
色调:setRotate(int axis, float degree),第一个参数分别使用0、1、2代表Red、Green、Blue三种颜色,第二参数需要处理的值 ColorMatrix hueMatrix = new ColorMatrix(); hueMatrix.setRotate(0, hue); hueMatrix.setRotate(1, hue); hueMatrix.setRotate(2, hue); 饱和度:setSaturation(float sat),参数代表设置饱和度的值,饱和度为0,图像就变成灰色图像了 ColorMatrix saturationMatrix = new ColorMatrix(); saturationMatrix.setSaturation(saturation); 亮度:setScale(float rscale,float gscale,float bscale,float ascale),参数分别是红、绿、蓝、透明度的亮度大小值 ColorMatrix lumMatrix = new ColorMatrix(); lumMatrix.setScale(lum, lum, lum, 1); 除了单独使用上面三种方式来进行颜色效果处理之外,还提供了postConcat()方法来将矩阵的作用效果混合,从而叠加处理效果 ColorMatrix imageMatrix = new ColorMatrix(); imageMatrix.postConcat(hueMatrix); imageMatrix.postConcat(saturationMatrix); imageMatrix.postConcat(lumMatrix);通过SeekBar来改变色调、饱和度、亮度:
public class PrimaryColor extends Activity implements SeekBar.OnSeekBarChangeListener { private static int MAX_VALUE = 255; private static int MID_VALUE = 127; private ImageView mImageView; private SeekBar mSeekbarhue, mSeekbarSaturation, mSeekbarLum; private float mHue, mStauration, mLum; private Bitmap bitmap; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.primary_color); bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test3); mImageView = (ImageView) findViewById(R.id.imageview); mSeekbarhue = (SeekBar) findViewById(R.id.seekbarHue); mSeekbarSaturation = (SeekBar) findViewById(R.id.seekbarSaturation); mSeekbarLum = (SeekBar) findViewById(R.id.seekbatLum); 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); mImageView.setImageBitmap(bitmap); } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { switch (seekBar.getId()) { case R.id.seekbarHue: mHue = (progress - MID_VALUE) * 1.0F / MID_VALUE * 180; break; case R.id.seekbarSaturation: mStauration = progress * 1.0F / MID_VALUE; break; case R.id.seekbatLum: mLum = progress * 1.0F / MID_VALUE; break; } mImageView.setImageBitmap(ImageHelper.handleImageEffect( bitmap, mHue, mStauration, mLum)); } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } }重点的处理:
public class ImageHelper { public static Bitmap handleImageEffect(Bitmap bm, float hue, float saturation, float lum) { Bitmap bmp = Bitmap.createBitmap( bm.getWidth(), bm.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bmp); Paint paint = new Paint(); // 色调 ColorMatrix hueMatrix = new ColorMatrix(); hueMatrix.setRotate(0, hue); hueMatrix.setRotate(1, hue); hueMatrix.setRotate(2, hue); // 饱和度 ColorMatrix saturationMatrix = new ColorMatrix(); saturationMatrix.setSaturation(saturation); // 亮度 ColorMatrix lumMatrix = new ColorMatrix(); lumMatrix.setScale(lum, lum, lum, 1); // 将所有的作用混合 ColorMatrix imageMatrix = new ColorMatrix(); imageMatrix.postConcat(hueMatrix); imageMatrix.postConcat(saturationMatrix); imageMatrix.postConcat(lumMatrix); // 将原图提取出来重画 paint.setColorFilter(new ColorMatrixColorFilter(imageMatrix)); canvas.drawBitmap(bm, 0, 0, paint); return bmp; } }6.5.2 Android颜色矩阵——ColorMatrix
模拟4 x 5颜色矩阵
public class ColorMatrix extends Activity { private ImageView mImageView; private GridLayout mGroup; private Bitmap bitmap; private int mEtWidth, mEtHeight; private EditText[] mEts = new EditText[20]; private float[] mColorMatrix = new float[20]; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.color_matrix); bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test1); mImageView = (ImageView) findViewById(R.id.imageview); mGroup = (GridLayout) findViewById(R.id.group); mImageView.setImageBitmap(bitmap); mGroup.post(new Runnable() { @Override public void run() { // 获取宽高信息 mEtWidth = mGroup.getWidth() / 5; mEtHeight = mGroup.getHeight() / 4; addEts(); initMatrix(); } }); } // 获取矩阵值 private void getMatrix() { for (int i = 0; i < 20; i++) { mColorMatrix[i] = Float.valueOf( mEts[i].getText().toString()); } } // 将矩阵值设置到图像 private void setImageMatrix() { Bitmap bmp = Bitmap.createBitmap( bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); android.graphics.ColorMatrix colorMatrix = new android.graphics.ColorMatrix(); colorMatrix.set(mColorMatrix); Canvas canvas = new Canvas(bmp); Paint paint = new Paint(); // 这行是关键代码,颜色矩阵传入画笔 paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix)); canvas.drawBitmap(bitmap, 0, 0, paint); mImageView.setImageBitmap(bmp); } // 作用矩阵效果 public void btnChange(View view) { getMatrix(); setImageMatrix(); } // 重置矩阵效果 public void btnReset(View view) { initMatrix(); getMatrix(); setImageMatrix(); } // 添加EditText private void addEts() { for (int i = 0; i < 20; i++) { EditText editText = new EditText(ColorMatrix.this); mEts[i] = editText; mGroup.addView(editText, mEtWidth, mEtHeight); } } // 初始化颜色矩阵为初始状态 private void initMatrix() { for (int i = 0; i < 20; i++) { if (i % 6 == 0) { mEts[i].setText(String.valueOf(1)); } else { mEts[i].setText(String.valueOf(0)); } } } }6.5.3 常用图像颜色矩阵处理效果
6.5.3.1 灰度效果
6.5.3.2 图像翻转
6.5.3.3 怀旧效果
6.5.3.4 去色效果
6.5.3.5 高饱和度
6.5.4 像素点分析
在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, bitmap.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 = (int) (0.349 * r + 0.686 * g + 0.168 * b); b1 = (int) (0.272 * r + 0.534 * g + 0.131 * b); newPx[i] = Color.argb(a, r1, b1, g1);最后使用我们的新像素值:
bmp.setPixels(newPx, 0, width, 0, 0, width, height);6.5.5 常用图像像素点处理效果
这些处理方法,算法都是专业人士研究的成果,我们没必要深究,直接拿来用就好了
public class ImageHelper { // 底片效果 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, width, height); return bmp; } // 老照片效果 public static Bitmap handleImagePixelsOldPhoto(Bitmap bm) { Bitmap bmp = Bitmap.createBitmap(bm.getWidth(), bm.getHeight(), Bitmap.Config.ARGB_8888); int width = bm.getWidth(); int height = bm.getHeight(); int color = 0; int r, g, b, a, r1, g1, b1; int[] oldPx = new int[width * height]; int[] newPx = new int[width * height]; bm.getPixels(oldPx, 0, bm.getWidth(), 0, 0, width, height); for (int i = 0; i < width * height; i++) { color = oldPx[i]; a = Color.alpha(color); r = Color.red(color); g = Color.green(color); b = Color.blue(color); 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); if (r1 > 255) { r1 = 255; } if (g1 > 255) { g1 = 255; } if (b1 > 255) { b1 = 255; } newPx[i] = Color.argb(a, r1, g1, b1); } bmp.setPixels(newPx, 0, width, 0, 0, width, height); return bmp; } // 浮雕效果 public static Bitmap handleImagePixelsRelief(Bitmap bm) { Bitmap bmp = Bitmap.createBitmap(bm.getWidth(), bm.getHeight(), Bitmap.Config.ARGB_8888); int width = bm.getWidth(); int height = bm.getHeight(); int color = 0, colorBefore = 0; int a, r, g, b; int r1, g1, b1; int[] oldPx = new int[width * height]; int[] newPx = new int[width * height]; bm.getPixels(oldPx, 0, bm.getWidth(), 0, 0, width, height); for (int i = 1; i < width * height; i++) { colorBefore = oldPx[i - 1]; a = Color.alpha(colorBefore); r = Color.red(colorBefore); g = Color.green(colorBefore); b = Color.blue(colorBefore); color = oldPx[i]; r1 = Color.red(color); g1 = Color.green(color); b1 = Color.blue(color); r = (r - r1 + 127); g = (g - g1 + 127); b = (b - b1 + 127); if (r > 255) { r = 255; } if (g > 255) { g = 255; } if (b > 255) { b = 255; } newPx[i] = Color.argb(a, r, g, b); } bmp.setPixels(newPx, 0, width, 0, 0, width, height); return bmp; } }6.6.1 Android变形矩阵——Matrix
对于图形变换,系统提供了3x3的矩阵来处理:
与颜色矩阵一样,计算方法通过矩阵乘法:
X1 = a x X +b x Y +c;
Y1 = d x X +e x Y +f;
1 = g x X +h x Y + i;
与颜色矩阵一样,也有一个初始矩阵:
图像的变形处理包含以下四类基本变换:
Translate:平移变换Rotate:旋转变换Scale:缩放变换Skew:错切变换6.6.1.1 平移变换
平移变换:即对每个像素点都进行平移变换,通过计算可以发现如下平移公式:
X = X0 + △X;
Y = Y0 + △Y;
6.6.1.2 旋转变换
旋转变换:即指一个点围绕一个中心旋转到一个新的点,发现如下旋转公式:
旋转变换:通过以下三步骤完成以任意点为旋转中心的旋转变换
将坐标原点平移到O点使用前面讲的以坐标原点为中心的旋转方法进行旋转变换将坐标原点还原6.6.1.3 缩放变换
缩放变换:将每个点的坐标进行同等比例的缩放,发现如下公式:
x = K1 X x0;
y = K2 X y0;
6.6.1.4 错切变换
错切变换:通常包含两种——水平错切与垂直错切,发现如下公式:
x = k1 + y0 + x0
y = k2 x x0 + y0
了解四种图形变换矩阵,可以通过setValues()方法将一个一维数组转换为图形变换矩阵:
private float [] mImageMatrix = new float[9]; Matrix matrix = new Matrix(); matrix.setValues(mImageMatrix); canvas.drawBitmap(mBitmmap,matrix,null);举个例子说明前乘和后乘的不同运算方式:
先平移到(300, 100)再旋转45度最后平移到(200, 200)如果使用后乘运算,当前矩阵乘上参数代表的矩阵,代码如下:
matrix.setRotate(45); matrix.postTranslate(200, 200);如果使用前乘运算,参数代表的矩阵乘上当前矩阵,代码如下:
matrix.setTranslate(200, 200); matrix.perRotate(45);6.6.2 像素块分析
drawBitmapMesh(),mesh意为网格的意思,此方法与操纵像素点来改变色彩的原理类似,只不过是把图像分成了一个个的小块,然后通过改变每一个图像块来修改整个图像:
canvas.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)坐标对的数目旗帜飞扬:
这个挺复杂,也没细看,有点懵逼啊
public class FlagBitmapMeshView extends View { private final int WIDTH = 200; private final int HEIGHT = 200; private int COUNT = (WIDTH + 1) * (HEIGHT + 1); private float[] verts = new float[COUNT * 2]; private float[] orig = new float[COUNT * 2]; private Bitmap bitmap; private float A; private float k = 1; public FlagBitmapMeshView(Context context) { super(context); initView(context); } public FlagBitmapMeshView(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public FlagBitmapMeshView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); } private void initView(Context context) { setFocusable(true); bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.test); float bitmapWidth = bitmap.getWidth(); float bitmapHeight = bitmap.getHeight(); int index = 0; for (int y = 0; y <= HEIGHT; y++) { float fy = bitmapHeight * y / HEIGHT; for (int x = 0; x <= WIDTH; x++) { float fx = bitmapWidth * x / WIDTH; orig[index * 2 + 0] = verts[index * 2 + 0] = fx; orig[index * 2 + 1] = verts[index * 2 + 1] = fy + 100; index += 1; } } A = 50; } @Override protected void onDraw(Canvas canvas) { flagWave(); k += 0.1F; canvas.drawBitmapMesh(bitmap, WIDTH, HEIGHT, verts, 0, null, 0, null); invalidate(); } private void flagWave() { for (int j = 0; j <= HEIGHT; j++) { for (int i = 0; i <= WIDTH; i++) { verts[(j * (WIDTH + 1) + i) * 2 + 0] += 0; float offsetY = (float) Math.sin((float) i / WIDTH * 2 * Math.PI + Math.PI * k); verts[(j * (WIDTH + 1) + i) * 2 + 1] = orig[(j * WIDTH + i) * 2 + 1] + offsetY * A; } } } }在前面提到过那些画笔的方法,这里就不多说了
6.7.1 PorterDuffXfermode
PorterDuffXfermod设置的是两个图层交集区域的显示方式,dst是先画的图形,而src是后画的图形
圆角矩形的例子:
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test1); mOut = Bitmap.createBitmap(mBitmap.getWidth(), mBitmap.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(mOut); 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);刮刮卡效果:
这里就是一个Path的使用,以及设置画笔的透明度为0,使用时尽量关闭硬件加速
public class XfermodeView extends View { private Bitmap mBgBitmap, mFgBitmap; private Paint mPaint; private Canvas mCanvas; private Path mPath; public XfermodeView(Context context) { super(context); init(); } public XfermodeView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public XfermodeView(Context context, AttributeSet attrs,int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { mPaint = new Paint(); mPaint.setAlpha(0); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeJoin(Paint.Join.ROUND); mPaint.setStrokeWidth(50); mPaint.setStrokeCap(Paint.Cap.ROUND); mPath = new Path(); mBgBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.test); mFgBitmap = Bitmap.createBitmap(mBgBitmap.getWidth(), mBgBitmap.getHeight(), Bitmap.Config.ARGB_8888); mCanvas = new Canvas(mFgBitmap); mCanvas.drawColor(Color.GRAY); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mPath.reset(); mPath.moveTo(event.getX(), event.getY()); break; case MotionEvent.ACTION_MOVE: mPath.lineTo(event.getX(), event.getY()); break; } mCanvas.drawPath(mPath, mPaint); invalidate(); return true; } @Override protected void onDraw(Canvas canvas) { canvas.drawBitmap(mBgBitmap, 0, 0, null); canvas.drawBitmap(mFgBitmap, 0, 0, null); } }6.7.2 Shader
Shader又被称为着色器。渲染器,它可以实现渲染,渐变等效果,Android中的Shader包括以下几种:
BitmapShader:位图ShaderLinearGradient:线性ShaderRadialGradient:光束ShaderSweepGradient:梯形ShaderComposeShader:混合Shader其中BitmapShader有三种模式可以选择:
CLAMP拉伸:拉伸的是图片最后的那一个像素,不断重复REPEAT重复:横向,纵向不断重复MIRROR镜像:横向不断翻转重复,纵向不断翻转重复圆形图片:
mBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.nice); mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP,Shader.TileMode.CLAMP); mPaint = new Paint(); mPaint.setShader(mBitmapShader); canvas.drawCircle(500,250,200,mPaint);把CLAMP改为REPEAT,拉伸变为重复
mBitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher); mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.REPEAT,Shader.TileMode.REPEAT); mPaint = new Paint(); mPaint.setShader(mBitmapShader); canvas.drawCircle(500,250,200,mPaint);使用LinearGradient:
mPaint = new Paint(); mPaint.setShader(new LinearGradient(0,0,400,400, Color.BLUE,Color.YELLOW, Shader.TileMode.REPEAT)); canvas.drawRect(0,0,400,400,mPaint);倒影效果图片
public class ReflectView extends View { private Bitmap mSrcBitmap, mRefBitmap; private Paint mPaint; private PorterDuffXfermode mXfermode; public ReflectView(Context context) { super(context); initRes(context); } public ReflectView(Context context, AttributeSet attrs) { super(context, attrs); initRes(context); } public ReflectView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initRes(context); } private void initRes(Context context) { mSrcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test); Matrix matrix = new Matrix(); matrix.setScale(1F, -1F); mRefBitmap = Bitmap.createBitmap(mSrcBitmap, 0, 0, mSrcBitmap.getWidth(), mSrcBitmap.getHeight(), matrix, true); mPaint = new Paint(); mPaint.setShader(new LinearGradient(0, mSrcBitmap.getHeight(), 0, mSrcBitmap.getHeight() + mSrcBitmap.getHeight() / 4, 0XDD000000, 0X10000000, Shader.TileMode.CLAMP)); mXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN); } @Override protected void onDraw(Canvas canvas) { canvas.drawColor(Color.BLACK); canvas.drawBitmap(mSrcBitmap, 0, 0, null); canvas.drawBitmap(mRefBitmap, 0, mSrcBitmap.getHeight(), null); mPaint.setXfermode(mXfermode); // 绘制渐变效果矩形 canvas.drawRect(0, mSrcBitmap.getHeight(), mRefBitmap.getWidth(), mSrcBitmap.getHeight() * 2, mPaint); mPaint.setXfermode(null); } }6.7.3 PathEffect
先来一张图:
Android提供的几种绘制PathEffect方式:
没效果CornerPathEffect:拐弯角变得圆滑DiscretePathEffect:线段上会产生许多杂点DashPathEffect:绘制虚线,用一个数据来设置各个点之间的间隔PathDashPathEffect:绘制虚线,可以使用方形点虚线和圆形点虚线ComposePathEffect:可任意组合两种路径(PathEffect)的特性来一个例子,每绘制一个Path,就将画布平移,从而让各种PathEffect依次绘制出来:
public class PathEffectView extends View{ private Path mPath; private PathEffect [] mEffect = new PathEffect[6]; private Paint mPaint; /** * 构造方法 * @param context * @param attrs */ public PathEffectView(Context context, AttributeSet attrs) { super(context, attrs); init(); } /** * 初始化 */ private void init() { mPaint = new Paint(); mPath = new Path(); mPath.moveTo(0,0); for (int i = 0; i<= 30;i++){ mPath.lineTo(i*35,(float)(Math.random()*100)); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mEffect[0] = null; mEffect[1] = new CornerPathEffect(30); mEffect[2] = new DiscretePathEffect(3.0F,5.0F); mEffect[3] = new DashPathEffect(new float[]{20,10,5,10},0); Path path = new Path(); path.addRect(0,0,8,8,Path.Direction.CCW); mEffect[4]= new PathDashPathEffect(path,12,0,PathDashPathEffect.Style.ROTATE); mEffect[5] = new ComposePathEffect(mEffect[3],mEffect[1]); for (int i = 0; i<mEffect.length;i++){ mPaint.setPathEffect(mEffect[i]); canvas.drawPath(mPath,mPaint); canvas.translate(0,200); } } }surface意为表面的
6.8.1 SurfaceView与View的区别
1、View的绘制刷新间隔时间为16ms,如果在16ms内完成你所需要执行的所有操作,那么在用户视觉上,就不会产生卡顿的感觉,否则,就会出现卡顿,所以可以考虑使用SurfaceView来替代View的绘制,通常在Log会看到这样的提示:
Skipped 47 frames! The application may be doing too much work on its main thread
2、SurfaceView与View的主要区别:
View主要适用于主动更新的情况下,而surfaceVicw主要适用于被动更新,例如频繁刷新View在主线程中对画面进行刷新,而surfaceView通常会通过一 个子线程来进行页面的刷新View在绘制时没有使用双缓冲机制,而surfaceVicw在底层实现机制中就已经实现了双缓冲机制总结一句话就是,如果你的自定义View需要频繁刷新,或者刷新数据处理量比较大,就可以考虑使用SurfaceView替代View
6.8.2 SurfaceView的使用
SurfaceView使用步骤:
创建SurfaceView继承自SurfaceView,并实现两个接口——SurfaceHolder.Callback和Runnable初始化SurfacHolder对象,并注册SurfaceHolder的回调方法通过SurfacHolder对象lockCanvas()方法获得Canvas对象进行绘制,并通过unlockCanvasAndPost(mCanvas)方法对画布内容进行提交整个使用SurfaceView的模板代码:
public class SurfaView extends SurfaceView implements SurfaceHolder.Callback, Runnable { //SurfaceHolder private SurfaceHolder mHolder; //用于绘制的Canvas private Canvas mCanvas; //子线程标志位 private boolean mIsDrawing; /** * 构造方法 * * @param context * @param attrs */ public SurfaView(Context context, AttributeSet attrs) { super(context, attrs); mHolder = getHolder(); mHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); setKeepScreenOn(true); } @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(); } catch (Exception e) { } finally { if (mCanvas != null) { //提交 mHolder.unlockCanvasAndPost(mCanvas); } } } }6.8.3 SurfaceView实例
1、正弦曲线
public class SinView extends SurfaceView implements SurfaceHolder.Callback, Runnable { private SurfaceHolder mHolder; private Canvas mCanvas; private boolean mIsDrawing; private int x = 0; private int y = 0; private Path mPath; private Paint mPaint; public SinView(Context context) { super(context); initView(); } public SinView(Context context, AttributeSet attrs) { super(context, attrs); initView(); } public SinView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initView(); } private void initView() { mHolder = getHolder(); mHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); this.setKeepScreenOn(true); mPath = new Path(); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(Color.RED); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(10); mPaint.setStrokeCap(Paint.Cap.ROUND); mPaint.setStrokeJoin(Paint.Join.ROUND); } @Override public void surfaceCreated(SurfaceHolder holder) { mIsDrawing = true; mPath.moveTo(0, 400); 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(); x += 1; y = (int) (100*Math.sin(x * 2 * Math.PI / 180) + 400); mPath.lineTo(x, y); } } private void draw() { try { mCanvas = mHolder.lockCanvas(); // SurfaceView背景 mCanvas.drawColor(Color.WHITE); mCanvas.drawPath(mPath, mPaint); } catch (Exception e) { } finally { if (mCanvas != null) mHolder.unlockCanvasAndPost(mCanvas); } } }2、绘图板
public class SimpleDraw extends SurfaceView implements SurfaceHolder.Callback, Runnable { private SurfaceHolder mHolder; private Canvas mCanvas; private boolean mIsDrawing; private Path mPath; private Paint mPaint; public SimpleDraw(Context context) { super(context); initView(); } public SimpleDraw(Context context, AttributeSet attrs) { super(context, attrs); initView(); } public SimpleDraw(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initView(); } private void initView() { mHolder = getHolder(); mHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); this.setKeepScreenOn(true); mPath = new Path(); mPaint = new Paint(); mPaint.setColor(Color.RED); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(20); } @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() { long start = System.currentTimeMillis(); while (mIsDrawing) { draw(); } long end = System.currentTimeMillis(); // 50 - 100 if (end - start < 100) { try { Thread.sleep(100 - (end - start)); } catch (InterruptedException e) { e.printStackTrace(); } } } private void draw() { try { mCanvas = mHolder.lockCanvas(); mCanvas.drawColor(Color.WHITE); mCanvas.drawPath(mPath, mPaint); } catch (Exception e) { } finally { if (mCanvas != null) mHolder.unlockCanvasAndPost(mCanvas); } } @Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mPath.moveTo(x, y); break; case MotionEvent.ACTION_MOVE: mPath.lineTo(x, y); break; case MotionEvent.ACTION_UP: break; } return true; } }这章东西确实比较多:
从屏幕的尺寸、参数信息、单位转换等
再到2D绘图,canvas和paint
再通过xml绘图,shape、Layer、Selector等等技巧
然后就是颜色处理,矩阵等等,比较复杂
再然后就是图形的处理
再然后就是一些画笔的处理
最后就是一个SurfaceView
反正感觉也是收获挺大,能有50%吸收了吧,还有剩下的就是慢慢的再学习,这块有的东西平时用到的可能不多,但是也得稍微的了解一下。