Android APP complete basic tutorial (16) graphics system - image effects

Posted by carmasha on Sun, 06 Feb 2022 04:02:35 +0100

This part mainly introduces some common special effects based on Canvas's drawing system.

1 twist effect

Bitdrawpms is used to process the distorted image. This effect is mainly to display the distortion effects such as "water wave rippling" and "red flag flying" on the APP, which is very flexible. drawBitmapmesh is defined as follows:

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

There are several key parameters (other parameters are relatively easy to understand):

  • meshWidth and meshHeight: how many squares to divide the picture horizontally / vertically.
  • verts: this parameter is an array with a length len of (meshWidth+1)*(meshHeight+1)*2, which records the vertices of the distorted bitmap. The recording format is: (x1,y1), (x2,y2), (x3,y3)... This method mainly controls the distortion effect of Bitmap bitmap through these array elements (we will learn more in the actual demo below).

The actual effect of bitdrawpmesh distortion is shown as follows:

For more explanation of drawBitmapmesh method, please refer to the document: Detailed explanation of Android Canvas drawBitmapMesh.

@2. drawBitmapmesh actual combat (drawBitmapmesh effect display)

Realization function: touch the screen and display the distortion effect of drawBitmapmesh at the touch. The running effect of the program is as follows:

For this program, the key codes of custom View are as follows:

public class MyView extends View {
    private static final String TAG = "MyView";
    private Matrix initMatrix = new Matrix();
    private int h,w;
    //Related variables built for drawBitmapMesh, start
    private final static int WIDTH =20;
    private final static int HEIGHT =20;
    private final static int COUNT = (WIDTH+1)*(HEIGHT+1);
    private float[] verts = new float[COUNT*2];
    private float[] origs = new float[COUNT*2];
    //Related variables built for drawBitmapMesh, end
    public Bitmap bitmap = null;
    float offsetStartX=0.0f,offsetStartY=0.0f;//Centered display adjustment

    public MyView(Context context) {
        super(context);
        init(context);
    }

    private void init(Context context){
        //1 get bitmap
        initMatrix.reset();
        initMatrix.setScale(1.0f,1.0f);
        try {
            InputStream isImage = context.getAssets().open("test6.png");
            bitmap = BitmapFactory.decodeStream(isImage);
            w = bitmap.getWidth();
            h = bitmap.getHeight();
            bitmap = Bitmap.createBitmap(bitmap,0,0, w, h, initMatrix, true);
        } catch (IOException e) {
            e.printStackTrace();
        }

        //2. Build origs array (processing data) and verts array (saving original data) according to the specified method of drawBitmapMesh
        int index=0;
        for(int i=0;i<HEIGHT+1;i++){
            float fy=(h*i)/HEIGHT;
            for(int j=0;j<WIDTH+1;j++){
                float fx=(w*j)/WIDTH;
                //The record format is: (x1,y1), (x2,y2), (x3,y3), and so on
                origs[index*2+0]=verts[index*2+0]=fx;
                origs[index*2+1]=verts[index*2+1]=fy;
                index++;
            }
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.WHITE);
        //Center display
        offsetStartX = (canvas.getWidth()-bitmap.getWidth())*1.0f/2;
        offsetStartY = (canvas.getHeight()-bitmap.getHeight())*1.0f/2;
        canvas.translate(offsetStartX,offsetStartY);
        //5 bitmap drawing. When the vert array changes, the local part of the picture will be adjusted accordingly.
        canvas.drawBitmapMesh(bitmap,WIDTH,HEIGHT,verts,0,null,0,null);
    }

    private void wrap(float x,float y){
        //4 key algorithm, distortion effect design, and construct distortion effect according to the input event coordinates.
        for(int i=0;i<COUNT*2;i+=2){
            float dx = x-origs[i];
            float dy = y-origs[i+1];
            float d = (float)Math.sqrt(dx*dx+dy*dy);
            float pull = 200000.f/(d*d*d);
            if(pull>=1){
                verts[i] = x;
                verts[i+1] = y;
            }else{
                verts[i] = origs[i]+dx*pull;
                verts[i+1] = origs[i+1]+dy*pull;
            }
        }
        invalidate();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //3 coordinate correction is made here to match the central display.
        wrap(event.getX()-offsetStartX,event.getY()-offsetStartY);
        return true;
    }
}

The implementation code in MainActivity is:

public class MainActivity extends AppCompatActivity {
    private static String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new MyView(this));
    }
}

2 shader effects

@1. Concept basis of shader and interpretation of derived classes

Here, the filling mode of Paint brush is mainly used to show the filling effect of shader. In the Android APP, the shader class (for more information about shader class interpretation, see the documentation: Detailed explanation of Android Shader class )Is an abstract class. Its derived classes are as follows:

Shader is the meaning of shader (just like skin, but the clothes we see a person wearing also depend on the environment, such as light, shadow, etc., that is to say, shader can be understood as the final effect of the overall presentation of clothes on a person). For more interpretation of shader, please see the article: OpenGL Basics (01) Basics The second part of the.

@2. Actual combat of shader effect (show 5 kinds of shader derived special effects)

Realization function: respond to five shader effects by pressing the key, and the effects are as follows:

For this program, the key codes of custom View are as follows:

public class MyView extends View {
    private static final String TAG = "MyView";
    public Paint paint = new Paint();
    private Path path = new Path();

    public MyView(Context context,AttributeSet attrs){
        super(context,attrs);
        init();
    }

    private void init(){
        paint.setAntiAlias(true);//Anti-Aliasing 
        paint.setColor(Color.RED);//stroke color 
        paint.setStyle(Paint.Style.FILL);//Fill mode
        paint.setStrokeWidth(4f);//Set brush thickness
        paint.setTextSize(48f);
    }

    public void setShader(Shader shader){
        paint.setShader(shader);
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.WHITE);
        drawsth(canvas,paint,path,440f);
    }
	
    //Test: draw basic graphics, irregular graphics and text
    private void drawsth(Canvas canvas, Paint paint,Path path,float offset){

        //Paint draws a circle. Parameters: center coordinates (x,y), radius r, brush paint
        canvas.drawCircle(100f+offset,100f,90f,paint);
        //Paint draws a rectangle. Parameters: upper left corner coordinates (x,y), lower right corner coordinates (x,y), brush paint
        canvas.drawRect(10f+offset,200f,210f+offset,400f,paint);
        //Paint draws a rounded rectangle. Parameters: upper left corner coordinates (x,y), lower right corner coordinates (x,y), rounded radius in X direction, rounded radius in Y direction, brush paint
        canvas.drawRoundRect(10f+offset,410f,210f+offset,610f,40f,40f,paint);
        //Paint draws an ellipse. Parameters: coordinates of the upper left corner (x,y), coordinates of the lower right corner (x,y) [parameters represent the ellipse inscribed in the rectangle], brush paint
        canvas.drawOval(10f+offset,620f,210f+offset,720f,paint);

        //Path draws the triangle, moveto represents the first coordinate, and lineto represents the second and third coordinates respectively. and so on.
        path.moveTo(110f+offset,730f);
        path.lineTo(10f+offset,930f);
        path.lineTo(210f+offset,930f);
        path.close();
        canvas.drawPath(path,paint);
        //Path draws irregular multipoint polygons
        path.moveTo(10f+offset,950f);
        path.lineTo(50f+offset,1000f);
        path.lineTo(200f+offset,970f);
        path.lineTo(150f+offset,1070f);
        path.lineTo(10f+offset,1110f);
        path.lineTo(210f+offset,1130f);
        path.close();
        canvas.drawPath(path,paint);

        //Note: this coordinate (x,y) is not the upper left corner of the text, but a position close to the lower left corner.
        canvas.drawText("Test text",10f+offset,1190f,paint);
    }
}

The implementation code in MainActivity is as follows:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private static String TAG = "MainActivity";
    private Shader[] shaders = new Shader[5];
    private int[] colors = new int[]{Color.RED,Color.GREEN,Color.BLUE};
    private MyView myView;
    private Button btn1,btn2,btn3,btn4,btn5;
    private Bitmap bitmap = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.shaderlayout);
        Button btn1 = findViewById(R.id.btn1);
        Button btn2 = findViewById(R.id.btn2);
        Button btn3 = findViewById(R.id.btn3);
        Button btn4 = findViewById(R.id.btn4);
        Button btn5 = findViewById(R.id.btn5);
        myView = findViewById(R.id.myView);

        try {
            InputStream isImage = getAssets().open("test6.png");
            bitmap = BitmapFactory.decodeStream(isImage);
        } catch (IOException e) {
            e.printStackTrace();
        }
		//Create 5 kinds of derived class objects of shader s to test the effect respectively
        shaders[0] = new BitmapShader(bitmap,Shader.TileMode.REPEAT, Shader.TileMode.MIRROR);
        shaders[1] = new LinearGradient(0f,0f,100f,100f,colors,null, Shader.TileMode.REPEAT);
        shaders[2] = new RadialGradient(100f,100f,80f,colors,null, Shader.TileMode.REPEAT);
        shaders[3] = new SweepGradient(160f,160f,colors,null);
        shaders[4] = new ComposeShader(shaders[1],shaders[2], PorterDuff.Mode.ADD);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn1:myView.setShader(shaders[0]);break;
            case R.id.btn2:myView.setShader(shaders[1]);break;
            case R.id.btn3:myView.setShader(shaders[2]);break;
            case R.id.btn4:myView.setShader(shaders[3]);break;
            case R.id.btn5:myView.setShader(shaders[4]);break;
            default:break;
        }
    }
}

For this program, shaderlayout The XML layout reference file is as follows:

<?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">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:visibility="visible">

        <Button
            android:id="@+id/btn1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="20"
            android:onClick="onClick"
            android:text="@string/BitmapShader" />

        <Button
            android:id="@+id/btn2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="20"
            android:onClick="onClick"
            android:text="@string/LinearGradient" />

        <Button
            android:id="@+id/btn3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="20"
            android:onClick="onClick"
            android:text="@string/RadialGradient" />

        <Button
            android:id="@+id/btn4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="20"
            android:onClick="onClick"
            android:text="@string/SweepGradient" />

        <Button
            android:id="@+id/btn5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="20"
            android:onClick="onClick"
            android:text="@string/ComposeGradient" />
    </LinearLayout>

    <com.ags.myapplication.MyView
        android:id="@+id/myView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@mipmap/ic_launcher" />
</LinearLayout>

Topics: Android app