绘图机制与图片处理-Android群英传

本文介绍Android的绘图机制,包括屏幕的尺寸信息,2D绘图中Canvas和paint的使用,XML绘图中bitmap\shape\selector\layer,如何用色彩矩阵处理图片的色彩(色调、饱和度、亮度),用变换矩阵进行图片处理以及画笔的特效处理

问题汇总:

带着问题学习、复习,效果更好,加油(ง •_•)ง!
1. 4.7寸手机的4.7寸是什么?
2. dp是什么?dp与px的区别。
3. Canvas和Paint的作用。
4. XML中Bitmap、shape、layer、selector作用
5. shape有哪几种重要属性,各自的作用?
6. Canvas的save()、restore()、tanslate()、rotate()的作用
7. 位图Bitmap包含哪两部分,ARGB是什么?
8. 色彩的三个要素和作用
9. ColorMatrix的作用,用ColorMatrix的作用改变色彩三要素的思路
10. 如何通过改变像素点的ARGB(以此来处理图片)
11. 如何用图形变换矩阵Matrix处理图片
12. 如何用像素块处理图片(drawBitmapMesh)
13. PorterDuffXfermode作用和应用。
14. shader作用和应用。
15. PathEffect的作用和应用

学习中看到有博客记录的很详细,有图:
小仙女-android群英传之绘图机制和处理技巧

1-屏幕尺寸信息

1-屏幕参数

  1. 屏幕大小指的是屏幕对角线的长度,使用“寸”度量,4.7寸手机、5.5寸手机
  2. PPI:每英寸像素(Pixels Per Inch)又被称为DPI,对角线的像素点数除以屏幕的大小得到的。

3-独立像素密度dp

Android用mdpi即密度值为160的屏幕作为标准,在这个屏幕上1px = 1dp。例如100dp的长度,在mdpi中为100px,而在hdpi中为150px。

因此dp用于屏幕适配最好。

比例换算表mdpihdpixhdpixxhdpi
dp1111
px11.523

2-2D绘图基础

系统提供Canvas提供绘图方法。还提供了各种绘制图像的API。如drawPoint点,drawLine线,矩形,多边形,弧,圆等。paint是重要的元素,能提供setAntiAlias()设置锯齿效果,setStyle()设置画笔的风格。通过画笔功能不同,组合各种API就能实现想要的效果。

实例:简单测试Canvas

public class TwoDActivity extends Activity {

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        /*将自定义控件设为ContentView-用于测试Canvas效果*/
        setContentView(new CustomeView(this));
    }

    /**-----------------------------------------
     *   自定义控件,重写draw方法,用于测试canvas
     * -----------------------------------------*/
    class CustomeView extends View{
        public CustomeView(Context context){
            super(context);
        }

        @Override
        public void draw(Canvas canvas) {

            /*---------------------------------
            *   绘制实心矩形
            * --------------------------------*/
            Paint paint = new Paint();
            paint.setStyle(Paint.Style.FILL); //画笔风格
            paint.setColor(Color.BLACK);      //画笔颜色
            canvas.drawRect(0, 0, 100, 100, paint);//绘制矩形

            /*---------------------------------
            *   绘制空心矩形
            * --------------------------------*/
            paint.setStyle(Paint.Style.STROKE); //画笔风格
            paint.setColor(Color.BLACK);        //画笔颜色
            canvas.drawRect(150, 0, 250, 100, paint);//绘制矩形

            /*-----------------------------------------------------------------
            *    在指定位置绘制文字-以(x,y)起点的水平直线为基准线,绘制直线Text
            * -----------------------------------------------------------------*/
            paint.setStyle(Paint.Style.FILL); //画笔风格
            paint.setTextSize(80);
            paint.setColor(Color.RED);        //画笔颜色
            canvas.drawText("Hello World!", 300, 100, paint);
            super.draw(canvas);
        }
    }
}

3-XML绘图基础

XML不仅仅可以作为Java中的一个布局文件、配置列表,还可以绘制图画等内容。

1-Bitmap

可以将图片直接转为bitmap,用于程序中。

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

2-Shape

XML绘图精华所在,无鸾是拟物化、扁平化还是渐变都可以。

shape属性

主要是6部分属性,省略部分具体细节,实际内容写写代码通过效果就可以掌握。(下列代码可以直接复制,可以看到效果)

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <!--shape可以为rectangle(默认),oval,line,ring-->

    <!--shape为rectangle使用, 默认为1dp-->
    <corners android:radius="5dp"/>
    <!--填充颜色-->
    <solid android:color="@color/colorAccent"/>
    <!--渐变色-->
    <gradient
        android:centerX="1"
        android:centerY="20"
        android:centerColor="@color/colorPrimary"
        android:endColor="@color/colorPrimaryDark"
        android:startColor="@color/colorAccent"
        android:type="linear"/>
    <padding />
    <!--制定边框, dashwidth虚线宽度,dashGap虚线间隔-->
    <stroke
        android:color="#334444"
        android:width="20dp"
        android:dashWidth="6dp"
        android:dashGap="2dp"/>
    <!--指定大小,一般在ImageView中配合scaleType属性使用-->
    <size />

</shape>

3-Layer

类似于Photoshop图层,可以实现图层。

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:drawable="@color/colorAccent"/>
    <item
        android:drawable="@color/colorPrimary"
        android:left="50dp"
        android:top="50dp"
        android:right="50dp"
        android:bottom="50dp"/>
</layer-list>

4-Selector

Selector用于帮助开发者实现静态绘图中的事件反馈,通过给不同事件设置图像,在获取相应输入后,返回相应结果。

如同Button点击时候的效果,可以用selector自定义View的触摸反馈。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true"
          android:drawable="@color/colorPrimaryDark">
        <shape />
    </item>
    <item android:state_pressed="false"
          android:drawable="@color/colorAccent">
        <shape />
    </item>
</selector>

这里就是点击和非点击时,显示相应图片,显示效果可以通过shape来实现。

CSDN关于selector的博客:http://blog.csdn.net/pcaxb/article/details/47317251

4-Android绘图技巧

1-Canvas

Canvas中有四个重要的方法:
1. Canvas.save():将之前绘制内容都保存起来,接下来的绘制就好像在新的图层进行绘制一样。
2. Canvas.restore():合并图层操作,将save之后的图像和save之前的图像合并起来。
3. Canvas.tanslate():平移,将坐标系进行平移,绘制都以之后的坐标系为参照。
4. Canvas.rotate():旋转,将坐标系进行旋转。避免了复杂运算,化繁为简。

绘制时钟

自定义时钟View

private int mCenterX = 550; //自定义圆心X
    private int mCenterY = 800; //自定义圆心Y
    private int mRadius = 500; //圆的半径

    /**-----------------------------------------
     *           自定义钟表
     * -----------------------------------------*/
    class ClockView extends View{
        public ClockView(Context context){
            super(context);
        }

        @Override
        protected void onDraw(Canvas canvas) {
            //绘制圆形
            Paint paint = new Paint();
            paint.setAntiAlias(true);
            paint.setStyle(Paint.Style.STROKE);
            paint.setStrokeWidth(5);
            canvas.drawCircle(mCenterX, mCenterY, mRadius, paint);

            /*--------------------------------------------
            *  绘制刻度线: 1. 先在0点位置绘制竖线
            *             2. 将坐标系旋转30度
            *             3. 继续原样绘制竖线
            *             4. 循环绘制,最终绘制出所有刻度线
            * -------------------------------------------*/
            paint = new Paint();
            paint.setStrokeWidth(3);
            for(int i = 0; i < 12; i++){
                //大刻度线
                if(i == 0 || i == 3 || i == 6 || i == 9){
                    paint.setColor(Color.RED);
                    paint.setStrokeWidth(5);
                    canvas.drawLine(mCenterX, mCenterY-mRadius, mCenterX, mCenterY-mRadius+60, paint);
                    String strTime = i+"";
                    int textSize = 40;
                    paint.setTextSize(textSize);
                    canvas.drawText(strTime, mCenterX-textSize/3, mCenterY-mRadius+60+textSize, paint);
                }else{
                    paint.setColor(Color.BLACK);
                    paint.setStrokeWidth(3);
                    canvas.drawLine(mCenterX, mCenterY-mRadius, mCenterX, mCenterY-mRadius+30, paint);
                    int textSize = 20;
                    String strTime = i+"";
                    paint.setTextSize(textSize);
                    canvas.drawText(strTime, mCenterX-textSize/3, mCenterY-mRadius+30+textSize, paint);
                }
                //旋转
                canvas.rotate(30, mCenterX, mCenterY);
            }
            super.onDraw(canvas);
        }
    }

2-Layer

类似于Photoshop图层概念。Android使用saveLayer(),saveLayerAlpha()将一个图层入栈。restore(), restoreToCount()将一个图层出栈。入栈后,所有操作发生在该图层之上。出栈后,就会将图像绘制到上层Canvas上。

CSDN layer详细讲解:http://blog.csdn.net/cquwentao/article/details/51423371

5-图像处理:色彩特效处理

Android中最常用的就是位图Bitmap,Bitmap包含了点阵和颜色值。点阵就是包含像素的矩阵。颜色值就是ARGB-对应透明度、红色、绿色、蓝色。

这里只记录要点,详细内容请看原著或者网络博客。

1-色彩矩阵分析

图像描述的三个角度:
1. 色调-物体显示的颜色
2. 饱和度-颜色的纯度。0(灰度)到100%(饱和)来进行描述。
3. 亮度-颜色相对的明暗程度。

Android使用颜色矩阵ColorMatrix,4X5的矩阵。
初始矩阵:不会对颜色产生任何变化

-----偏移量
决定R10000
决定G01000
决定B00100
透明度00010

改变颜色

1-改变偏移量
1000
0100
0010
0001

红色和绿色分量都增加100,红绿结合,导致颜色偏黄。

2-改变颜色系数
1000
0200
0010
0001

会导致

3-改变色光属性

色调、饱和度、亮度,Android都封装了一些API来快速调整这些参数,不用计算矩阵值。

实例:通过seekbar选择设置图片的色调、饱和度和亮度。

布局文件:包含各属性的seekbar
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.administrator.featherdemos.BitmapActivity"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/imageview"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="4" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="horizontal">

        <TextView
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="center"
            android:text="色调:"
            android:textColor="#000"
            android:textSize="23dp" />

        <SeekBar
            android:id="@+id/hue_seekbar"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="4" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="horizontal">

        <TextView
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="center"
            android:text="饱和度:"
            android:textColor="#000"
            android:textSize="23dp" />

        <SeekBar
            android:id="@+id/saturation_seekbar"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="4" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="horizontal">

        <TextView
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="center"
            android:text="亮度:"
            android:textColor="#000"
            android:textSize="23dp" />

        <SeekBar
            android:id="@+id/lum_seekbar"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="4" />
    </LinearLayout>

</LinearLayout>
Activity使用
  1. 初始化控件
  2. handleImageEffect:利用ColorMatrix设置色调、饱和度、亮度
  3. 给seekbar设置监听器—获取数值,并调用handleImageEffect处理图片,将最终结果设置到ImageView中显示。
public class BitmapActivity extends Activity implements SeekBar.OnSeekBarChangeListener{

    ImageView imageView;
    SeekBar hueSeekBar;
    SeekBar saturationSeekBar;
    SeekBar lumSeekBar;

    Bitmap bitmap;

    float mHue = 0; //色调初始值为0, 原图色调
    float mSaturation = 1; //饱和度1,原图饱和度
    float mLum = 1; //亮度1,原图亮度

    //SeekBar的中间值
    int MID_VALUE = 127;
    //SeekBar的最大值
    int MAX_VALUE = 255;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_bitmap);
        imageView = (ImageView) findViewById(R.id.imageview);
        //从本地文件获取drawble
        Drawable drawable = ContextCompat.getDrawable(this, R.drawable.jide);
        bitmap = ((BitmapDrawable)drawable).getBitmap();
        imageView.setImageBitmap(bitmap);

        hueSeekBar = (SeekBar) findViewById(R.id.hue_seekbar);
        hueSeekBar.setMax(MAX_VALUE);
        hueSeekBar.setProgress(MID_VALUE);

        saturationSeekBar = (SeekBar) findViewById(R.id.saturation_seekbar);
        saturationSeekBar.setMax(MAX_VALUE);
        saturationSeekBar.setProgress(MID_VALUE);

        lumSeekBar = (SeekBar) findViewById(R.id.lum_seekbar);
        lumSeekBar.setMax(MAX_VALUE);
        lumSeekBar.setProgress(MID_VALUE);

        hueSeekBar.setOnSeekBarChangeListener(this);
        saturationSeekBar.setOnSeekBarChangeListener(this);
        lumSeekBar.setOnSeekBarChangeListener(this);
    }

    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        switch(seekBar.getId()){
            case R.id.hue_seekbar:
                mHue = (progress - MID_VALUE) * 1.0F / MID_VALUE * 180;
                break;
            case R.id.saturation_seekbar:
                mSaturation = progress * 1.0F  / MID_VALUE;
                break;
            case R.id.lum_seekbar:
                mLum =  progress * 1.0F  / MID_VALUE;
                break;
            default:break;
        }
        imageView.setImageBitmap(handleImageEffect(mHue, mSaturation, mLum));
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {

    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {

    }

    /**----------------------------------------------------
     * 处理图像的色调,饱和度,亮度。
     *   因为Android中不能处理原Bitmap,需要处理Bitmap的副本,
     *   因此使用原Bitmap创建副本,进行处理。
     * ---------------------------------------------------*/
    public Bitmap handleImageEffect(float hue, float saturation, float lum){

        //创建副本Bitmap
        Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.jide);
        Bitmap bitmap = bmp.copy(Bitmap.Config.ARGB_8888,true);

        //创建画板、画笔
        Canvas canvas = new Canvas(bitmap);
        Paint paint = new Paint();

        /*-----------------------------------
        * 处理色调: setRotate中0,1,2代表R,G,B
        * -----------------------------------*/
        ColorMatrix hueColorMatrix = new ColorMatrix();
        hueColorMatrix.setRotate(0, hue);
        hueColorMatrix.setRotate(1, hue);
        hueColorMatrix.setRotate(2, hue);

        /*-----------------------------------
        *  设置饱和度
        * -----------------------------------*/
        ColorMatrix saColorMatrix = new ColorMatrix();
        saColorMatrix.setSaturation(saturation);

        /*-------------------------------------------
        * 设置亮度:将三原色相同比例混合显示出白色,以此提高亮度
        * ------------------------------------------*/
        ColorMatrix lumColorMatrix = new ColorMatrix();
        lumColorMatrix.setScale(lum, lum, lum, 1);

        /*---------------------------------
        *  将矩阵作用效果混合, 叠加效果。
        * --------------------------------*/
        ColorMatrix colorMatrix = new ColorMatrix();
        colorMatrix.postConcat(hueColorMatrix);
        colorMatrix.postConcat(saColorMatrix);
        colorMatrix.postConcat(lumColorMatrix);

        /*-------------------------
        *  设置矩阵, 进行绘制
        * ------------------------*/
        paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
        canvas.drawBitmap(bitmap, 0, 0, paint);
        return bitmap;
    }
}
drawble图片

我这里是ji.jpg,可以将该图片换为任何图片,切记图片不能太大,代码中没有进行OOM处理。

2-Android颜色矩阵-ColorMatrix

使用4X5颜色矩阵进行色彩处理。通过设置具体矩阵某元素来处理色彩。这里使用GridLayout动态创建4X5的EditText。

布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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/imageView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="3" />

    <GridLayout
        android:id="@+id/mGroup"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="3"
        android:columnCount="5"
        android:rowCount="4">

    </GridLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1.5"
        android:orientation="vertical">

        <Button
            android:id="@+id/btn_change"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:padding="10dp"
            android:text="Change"
            android:onClick="btnChange"
            android:textSize="20sp" />

        <Button
            android:id="@+id/btn_reset"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:padding="10dp"
            android:onClick="btnReset"
            android:text="Reset"
            android:textSize="20sp" />
    </LinearLayout>
</LinearLayout>

Activity

获取矩阵中数据,并通过ColorMatrix作用到图像上。

public class ColorMatrixActivity extends Activity {

    private Bitmap bitmap;
    private ImageView mImageView;
    private GridLayout mGroup;
    private EditText [] mEts = new EditText[20];
    private int mEtWidth,mEtHeight;
    private float[] mColorMatrix = new float[20];

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_color_matrix);

        bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.jide);

        mImageView = (ImageView) findViewById(R.id.imageView);
        mGroup = (GridLayout) findViewById(R.id.mGroup);
        mImageView.setImageBitmap(bitmap);
        //无法在OnCreate中获取到View的宽高,通过post在视图创建后测量
        mGroup.post(new Runnable() {
            @Override
            public void run() {
                //获取宽高信息
                mEtWidth = mGroup.getWidth()/5;
                mEtHeight = mGroup.getHeight()/4;
                addEts();
                initMatrix();
            }
        });

    }

    /*--------------------------------------
    *  将矩阵数组(一维)通过ColorMatrix作用到图像上
    *  以此对图片进行色彩处理
    * ------------------------------------*/
    private void setImageMatrix(){
        Bitmap bmp = Bitmap.createBitmap(bitmap.getWidth(),bitmap.getHeight(),
                Bitmap.Config.ARGB_8888);
        ColorMatrix colorMatrix = new 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);
    }


    //添加EditText
    private void addEts(){
        for(int i = 0;i < 20;i++){
            EditText editText = new EditText(ColorMatrixActivity.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));
            }
        }
    }

    //获取矩阵值
    private void getMatrix(){
        for(int i = 0 ;i < 20;i++){
            mColorMatrix[i] = Float.valueOf(mEts[i].getText().toString());
        }
    }

    //作用点击事件
    public void btnChange(View view) {
        getMatrix();
        setImageMatrix();
    }

    //重置矩阵效果
    public void btnReset(View view) {
        initMatrix();
        getMatrix();
        setImageMatrix();
    }
}

3-常用色彩处理效果

有灰度、反转、怀旧等。

反转矩阵

-1001
0-101
00-11
0001

其余不赘述,请查询资料。

4-像素点处理

可以通过改变每个像素点的ARGB值,进行色彩处理。Android提供Bitmap.getPixels(colorArray,省略...)来提取整个图片的像素点,并保存到数组中。

r = Color.red(colorArray);
g = Color.green(colorArray);
b = Color.blue(colorArray);
a = Color.alpha(colorArray);

获取到原ARGB后,通过计算得到新的ARGB。
r1 = i* r + j* g + k* b;

newPix[i] = Color.argb(a, r1, g1, b1);
bitmap.setPixels(newPix, ...);

使用不同参数,就能达到老照片、浮雕等效果。

6-图形特效处理

图形变换类似于颜色变换。使用3X3变换矩阵进行变换。可以进行平移变换、缩放变换、旋转变换、错切变换。

1-矩阵Matrix

使用矩阵Matrix对图片进行平移、缩放、旋转、错切变换。

public class PictureEditActivity extends Activity {

    ImageView imageView;
    static float data = 10f; //自定义的数据
    Bitmap bitmap;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_picture_edit);

        imageView = (ImageView) findViewById(R.id.pic_edit_imageview);
        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.jide);
        //imageView.setImageBitmap(bitmap);

        Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                editPic();
            }
        }, 1000);

    }

    public void editPic(){
        Canvas canvas = new Canvas();
        /*------------------------------------
        * 使用矩阵处理图片
        * -----------------------------------*/
        float[] mImageMatrix = new float[9];
        Matrix matrix = new Matrix();
        matrix.setValues(mImageMatrix);//转为矩阵
        canvas.drawBitmap(bitmap, matrix, null);

        //set会覆盖整个矩阵
        matrix.setRotate(data); //旋转
        matrix.setTranslate(data, data);//平移
        matrix.setScale(data, data); // 缩放
        matrix.setSkew(data, data); //错切
        /*------------------------------
        * 矩阵混合
        *  pre()前乘: 当前矩阵 X 该次使用的矩阵
        *  post()后乘: 该次使用的矩阵 X当前矩阵
        * ------------------------------*/
        matrix.preRotate(data); //会用 当前矩阵 X 该次的旋转矩阵
        matrix.postScale(data, data); // 本次缩放矩阵 X 当前矩阵
        /*
        *  1.先旋转45度
        *  2.再平移(200, 200)
        * */
        //后乘: 旋转后平移
        matrix.setRotate(45f);
        matrix.postTranslate(200, 200);
        //先乘: 平移前旋转
        matrix.setTranslate(200, 200);
        matrix.preRotate(45f);

        /*---------------------
        *         测试:
        * --------------------*/
        Bitmap bmp = Bitmap.createBitmap(bitmap.getWidth(),bitmap.getHeight(),
                Bitmap.Config.ARGB_8888);        //创造bitmp的复制品bmp
        canvas = new Canvas(bmp);                //用bmp创造画布
        canvas.drawBitmap(bitmap, matrix, null); //根据bitmap以matrix变化后的图片, 在bmp上进行绘制
        imageView.setImageBitmap(bmp);
    }
}

2-像素块

例如色彩处理中像素点的处理,将图片分为像素块,改变像素块来处理图片。
drawBitmapMesh()
canvas.drawBitmapMesh(bitmap,WIDTH,HEIGHT,verts,0,null,0,null);,将第五个参数的坐标数组传入,bitmap会根据像素块数组,将WIDTH X HEIGHT网格中,每个焦点的坐标进行相应的修改。

public class FlagImage extends View {
    Bitmap bitmap;
    //定义两个常量,这两个常量指定该图片横向20格,纵向上都被划分为10格
    private final int WIDTH = 30;
    private final int HEIGHT = 30;
    //记录该图像上包含的231个顶点
    private final int COUNT = (WIDTH +1) * (HEIGHT + 1);
    //定义一个数组,记录Bitmap上的21*11个点的坐标
    private  final  float[] verts = new float[COUNT * 2];
    //定义一个数组,记录Bitmap上的21*11个点经过扭曲后的坐标
    //对图片扭曲的关键就是修改该数组里元素的值
    private  final  float[] orig = new float[COUNT * 2];

    //振幅大小
    private final float A = 10;
    private float k = 0; //旗帜飞扬

    public FlagImage(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.jide);

        float bitmapWidth = bitmap.getWidth();
        float bitmapHeight = bitmap.getHeight();
        int index = 0;
        for(int y = 0; y <= HEIGHT; y++){
            float fy = bitmapHeight * y / HEIGHT; //图像高度 乘以 y/总格数 可以获得 纵向y的坐标
            for(int x = 0;x<= WIDTH;x ++){
                float fx = bitmapWidth * x/WIDTH; //x的坐标值

                orig [index * 2 + 0] = verts [index * 2 + 0] = fx; //[(x1,y1), (x2,y2), (...)...]一维数组依次存储
                orig [index * 2 + 1] = verts [index * 2 + 1] = fy+100; //这里人为将坐标+100是为了让图像下移,避免扭曲后被屏幕遮挡。
                index += 1;
            }
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        flagWave();//旗帜飞扬
        k += 0.1F;
        //对bitmap按verts数组进行扭曲
        //从第一个点(由第5个参数0控制)开始扭曲
        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; //x值不变
                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;
            }
        }
    }

    private void flagWave(float cx, float cy){
        for(int i = 0; i < COUNT * 2; i += 2)
        {
            float dx = cx - orig[i + 0];
            float dy = cy - orig[i + 1];
            float dd = dx * dx + dy * dy;
            //计算每个坐标点与当前点(cx,cy)之间的距离
            float d = (float)Math.sqrt(dd);
            //计算扭曲度,距离当前点(cx,cy)越远,扭曲度越小
            float pull = 80000 / ((float)(dd * d));
            //对verts数组(保存bitmap 上21 * 21个点经过扭曲后的坐标)重新赋值
            if(pull >= 1)
            {
                verts[i + 0] = cx;
                verts[i + 1] = cy;
            }
            else
            {
                //控制各顶点向触摸事件发生点偏移
                verts[i + 0] = orig[i + 0] + dx * pull;
                verts[i + 1] = orig[i + 1] + dx * pull;
            }
        }
        //通知View组件重绘
        invalidate();
    }
    public boolean onTouchEvent(MotionEvent event)
    {
//        //调用warp方法根据触摸屏事件的坐标点来扭曲verts数组
//        flagWave(event.getX() , event.getY());
          return true;
    }
}

7-画笔特效处理

1-PorterDuffXfermode

用于设置两个图像交际区域的显示方式。以此可以制作圆形图片等效果。

圆形图片

先将画布变成圆角矩形。再将图片绘制到画布上。

        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.jide);
        Bitmap outBmp = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(outBmp); //用outbmp创建空白画布
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        canvas.drawRoundRect(0, 0, bitmap.getWidth(), bitmap.getHeight(), 80, 80, paint);//先画出圆角长方形

        /*
        * 将bitmap绘制到圆角矩形的画布上(outBmp)
        * */
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        canvas.drawBitmap(bitmap, 0, 0, paint);
        roundImageView.setImageBitmap(outBmp);

刮刮卡效果

  1. 准备背景bitmap
  2. 准备灰色的bitmap
  3. 在外层灰色bitmap上用透明画笔绘制出路径(PorterDuffXfermode会将透明度与灰色bitmap融合,使得灰色bitmap在路径上变成透明)
  4. 在onDraw(Canvas canvas)整个View的canvas上先绘制背景bitmap,再绘制外层灰色bitmap
public class CardImageView extends View {
    Bitmap mBgBitmap,mFgBitmap;
    Canvas mCanvas;
    Paint mPaint;
    Path mPath;

    public CardImageView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initCardImage();
    }

    private void initCardImage() {
        mPaint = new Paint();
        mPaint.setAlpha(0);  //将画笔的透明度设为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.jide); //内层图层:图片
        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);
    }
}

2-Shader

着色器、渐变器:用于渐变和渲染效果。主要有BitmapShader(位图)、LinearGradient(线性)、RadialGradient(光束)、SweepGradient(梯度)、ComposeShader(混合)

BitmapShader shader = new BitmapShader(bitmap, Shader.TitleMode.CLAMP, Shader.TitleMode.CLAMP); //初始化
Paint paint = new Paint();
paint.setShader(shader); //设置shader
canvas.drawCircle(500, 250, 200, paint); //绘制

其余的使用和例程就省略了,有兴趣的参考开头的博客或者原著。
主要就是选择需要的Shader,并且在Paint里面setShader,就可以去绘制了。

3-PathEffect

用想要的效果去绘制路径,比如让路径圆滑,出现噪点等。主要使用方法如下:

Path path = new Path(); //创造路径
...
PathEffect pe = new ComposePathEffect(...);
path.setPathEffect(pe); //设置路径
canvas.drawPath(path, paint); //绘制路径
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猎羽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值