Detailed explanation of the use of graphics and text in Shader for Canvas drawing in Android

Overview

When we use Canvas in Android to draw various graphics, we can Paint.setShader(shader)set the shader for the brush Paint through the method, so that we can draw colorful graphics. So what is Shader? Students who have done GPU drawing should know this term. Shader means shader. We can understand that the various drawXXX methods in Canvas define the shape of the graphics, and the Shader in the brush defines the coloring and appearance of the graphics. The combination of the two determines the final color-filled graphics drawn by Canvas. .

The class android.graphics.Shaderhas five subclasses, namely: BitmapShader, LinearGradient, RadialGradient, SweepGradient and ComposeShader. The following describes the use of these classes in turn.


BitmapShader

BitmapShader, as the name implies, uses Bitmap to render and color the drawn graphics, in fact, it uses pictures to map the graphics.

The BitmapShader constructor looks like this:

BitmapShader(Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY)

The first parameter is the Bitmap object, which determines what image is used to map the drawn graphics.

The second and third parameters are enumeration values ​​of type Shader.TileMode, with the following three values: CLAMP, REPEAT and MIRROR.

CLAMP

CLAMP means that when the size of the drawn graphic is larger than the size of the Bitmap, the remaining space will be filled with the color of the four sides of the Bitmap.

We have a Bitmap like this:

write picture description here

Note that the four corners of our picture have certain arcs, that is, the pixels at the four corners of the Bitmap are transparent.
We use this Bitmap to demonstrate the effect of TileMode being CLAMP. The code is as follows:

BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
paint.setShader(bitmapShader);
canvas.drawRect(0, 0, bitmap.getWidth() * 2, bitmap.getHeight() * 2, paint);

The effect is as follows:

write picture description here

We can see that since the rectangular rectangular area we draw is larger than the Bitmap, the Bitmap fills the rectangular area with the outermost color of the right and lower sides. Since the pixel in the lower right corner of the original Bitmap is transparent, the lower right corner of the drawn rectangle is filled with transparency.

If the size of the graphic we draw is smaller than the Bitmap size, the effect looks like the original Bitmap has been cropped. The code is as follows:

write picture description here

We can see that when the size of the circle we draw is smaller than the size of the Bitmap, the effect seems to be that we use the drawn circle to clip the Bitmap.

REPEAT

REPEAT means that when the size of the graphics we draw is larger than the Bitmap size, the Bitmap will be used to repeatedly tile the entire drawn area.
The sample code looks like this:

BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
paint.setShader(bitmapShader);
canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), paint);

The effect is as follows:

write picture description here

MIRROR

Similar to REPEAT, when the size of the drawn graphic is larger than the Bitmap size, MIRROR will also use the Bitmap to repeatedly tile the entire drawing area. Unlike REPEAT, two adjacent Bitmaps are mirror images of each other.
The code looks like this:

BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR);
paint.setShader(bitmapShader);
canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), paint);

The effect is as follows:

write picture description here

The last thing to say is that when constructing BitmapShader, tileX and tileY can take different values, and they do not have to be consistent.


LinearGradient

We can create linear gradient effects with LinearGradient, which has two constructors:

LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1, Shader.TileMode tile)

LinearGradient(float x0, float y0, float x1, float y1, int[] colors, float[] positions, Shader.TileMode tile)

We focus on the first constructor, and it is easy to understand the second constructor on this basis.

LinearGradient is used to create a linear gradient effect. It is gradient along the direction of a line. The coordinates (x0, y0) are the starting point of the gradient line, and the coordinates (x1, y1) are the end points of the gradient line. It should be noted that both coordinates (x0, y0) and coordinates (x1, y1) are coordinates in the Canvas drawing coordinate system. color0 and color1 represent the starting color and ending color of the gradient, respectively. Similar to BitmapShader, LinearGradient also supports TileMode, with the following three values: CLAMP, REPEAT and MIRROR.

The code used is as follows:

LinearGradient linearGradient = new LinearGradient(100, 100, 500, 500, Color.GREEN, Color.BLUE, Shader.TileMode.CLAMP);
paint.setShader(linearGradient);
canvas.drawRect(100, 100, 500, 500, paint);

The effect is as follows:

write picture description here

Above we used CLAMP, but since the rectangle we draw is the same size as the gradient position, the CLAMP effect is not noticeable.

We make the drawing area bigger, still use CLAMP, this time draw a rectangle the size of the entire Canvas.

canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), paint);

The effect is as follows:
write picture description here

When we change CLAMP to REPEAT, we still draw a rectangle the size of the entire Canvas, and the effect is as follows:

write picture description here

When we use MIRROR to draw a rectangle the size of the entire Canvas, the effect is as follows:

write picture description here

In the second constructor of LinearGradient, you can pass in multiple color values ​​through the parameters colors, so that the color linear interpolation will be performed together with the color values ​​specified in the colors array. You can also specify the positions array. Each position in the array corresponds to the relative position of each color in the colors array in the line segment. The value range of position is [0,1], 0 indicates the starting position, and 1 indicates the ending position. If the positions array is null, then Android will automatically set equally spaced positions for the colors.


RadialGradient

We can use RadialGradient to create a radial gradient effect that radiates from the center to the periphery, which has two constructors:

RadialGradient(float centerX, float centerY, float radius, int centerColor, int edgeColor, Shader.TileMode tileMode)

RadialGradient(float centerX, float centerY, float radius, int[] colors, float[] stops, Shader.TileMode tileMode)

These two constructors are very similar to the two constructors of LinearGradient. We will focus on the first constructor here. On this basis, it is very simple to understand the second constructor.

RadialGradient is used to create a radial gradient effect that radiates from the center to the surrounding, so we need to pass in some circle parameters in its constructor. The coordinates (centerX, centerY) are the center of the circle, that is, the position of the starting center color, radius The radius of the circle is determined, and the color at the radius of the circle is edgeColor, which determines that when the position moves from the center of the circle to the outline of the circle, the color gradually changes from centerColor to edgeColor. RadialGradient also supports the TileMode parameter, which has the following three values: CLAMP, REPEAT, and MIRROR.

We first use CLAMP as TileMode, the code looks like this:

int canvasWidth = canvas.getWidth();
int canvasHeight = canvas.getHeight();
float centerX = canvasWidth / 2f;
float centerY = canvasHeight / 2f;
float radius = canvasWidth / 4f;
RadialGradient radialGradient = new RadialGradient(centerX, centerY, radius, Color.GREEN, Color.BLUE, Shader.TileMode.MIRROR);
paint.setShader(radialGradient);
canvas.drawRect(0, 0, canvasWidth, canvasHeight, paint);

The effect is as follows:

write picture description here

In the image above, the rectangle we draw is the same size as the Canvas, and its size is larger than the size of the RadialGradient's circle we defined. We can see that when CLAMP is used as TileMode, the color gradients from green at the center of the circle to blue at the circumference, and the space outside the circle is filled with edgeColor blue.

When we change CLAMP to REPEAT, we still draw the same rectangle, and the effect is as follows:

write picture description here

We see that the color spreads outward from the center of the circle as a gradient cycle of green to blue.

When we use MIRROR as TileMode, we still draw the same rectangle, the effect is as follows:

write picture description here

We see that the colors spread out from the center of the circle in a cycle of green->blue->green->blue...

In the second constructor of RadialGradient, you can pass in multiple color values ​​through the parameters colors, so that the color linear interpolation will be performed together with the color values ​​specified in the colors array. You can also specify a stops array. Each stop in the array corresponds to the relative position of each color in the colors array in the radius. The value range of stop is [0,1], 0 means the center position, and 1 means the circumference position. If the stops array is null, then Android will automatically set equally spaced positions for the colors.


SweepGradient

SweepGradient can be used to create a 360-degree color rotating gradient effect. Specifically, the color is rotated 360 degrees clockwise around the center point, starting at the 3 o'clock position.

SweepGradient has two constructors:

SweepGradient(float cx, float cy, int color0, int color1)

SweepGradient(float cx, float cy, int[] colors, float[] positions)

SweepGradient does not support the TileMode parameter, let's explain the first constructor first.

The coordinates (cx, cy) determine the position of the center point, and it will rotate 360 ​​degrees around the center point. color0 represents the color position of the starting point, and color1 represents the color position of the end point.

The code looks like this:

int canvasWidth = canvas.getWidth();
int canvasHeight = canvas.getHeight();
float centerX = canvasWidth / 2f;
float centerY = canvasHeight / 2f;
float radius = canvasWidth / 4f;
SweepGradient sweepGradient = new SweepGradient(centerX, centerY, Color.GREEN, Color.BLUE);
paint.setShader(sweepGradient);
canvas.drawCircle(centerX, centerY, radius, paint);

The effect is as follows:

write picture description here

As shown above, we draw a circle with canvas.drawCircle() method, set the center point of SweepGradient at the center of the circle, we can see the color goes 360 clockwise from green at 3 o'clock Rotate the gradient to blue.

In the second constructor of SweepGradient, we can pass in a colors array, so that Android will interpolate the colors together based on the passed in array of colors. You can also specify the positions array. Each position in the array corresponds to the relative position of each color in the colors array in 360 degrees. The value range of position is [0,1]. Both 0 and 1 represent the 3 o’clock position, and 0.25 represents 6 o'clock, 0.5 for 9 o'clock, 0.75 for 12 o'clock, and so on. If the positions array is null, then Android will automatically set equally spaced positions for the colors.

The code looks like this:

int canvasWidth = canvas.getWidth();
int canvasHeight = canvas.getHeight();
float centerX = canvasWidth / 2f;
float centerY = canvasHeight / 2f;
float radius = canvasWidth / 4f;
int[] colors = {Color.RED, Color.GREEN, Color.BLUE};
float[] positions = {0f, 0.5f, 0f};
SweepGradient sweepGradient = new SweepGradient(centerX, centerY, colors, positions);
paint.setShader(sweepGradient);
canvas.drawCircle(centerX, centerY, radius, paint);

The effect is as follows:

write picture description here

In the above code, we pass the three colors of red, green and blue into the colors array, and specify their relative positions through the positions array as 0, 0.5, and 1, so red is the starting color, located at 3 o'clock; green is the middle Color, at 9 o'clock; blue is the finish color, also at 3 o'clock.

Of course, the position of the starting color is not necessarily 0, and the position of the ending color is not necessarily 1. We change the positions array to the following:

float[] positions = {0.25f, 0.5f, 0.75f};

The effect is as follows:

write picture description here

We see a change in the color scale of the colors. The position of the starting color red is 0.25 not 0, but the color is red from the 3 o'clock position. The difference is that the termination color is blue. The position of blue is 0.75 instead of 1. It corresponds to the 12 o'clock position. The 90-degree space from 12 o'clock to 3 o'clock is transparent and not filled with color. Everyone pay attention.

If we draw a rectangle the size of the entire Canvas on this basis, the effect is as follows:

write picture description here


ComposeShader

ComposeShader, as the name suggests, means mixing Shader, it can combine two Shaders according to a certain Xfermode .

ComposeShader有两个构造函数,如下所示:

ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode)

ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode)

Here is a brief introduction to Xfermode. Xfermode can be used to achieve color mixing between newly drawn pixels and existing pixels in corresponding positions on the Canvas according to the mixing rules. Xfermode has three subclasses: AvoidXfermode, PixelXorXfermode and PorterDuffXfermode. The first two classes are now abandoned by Android, and PorterDuffXfermode is mainly used now. The constructor of PorterDuffXfermode needs to specify the type of PorterDuff.Mode. So, the second constructor above can be seen as a special case of the first constructor. We mainly explain the second one, both of which are similar.

We know that when using Xfermode, there is a target pixel DST and a source pixel SRC. The source pixel refers to the pixel to be drawn on the Canvas, and the target pixel refers to the pixel that already exists in the corresponding position of the source pixel on the Canvas.

The shaderA in the constructor corresponds to the target pixel, and shaderB corresponds to the source pixel.

It should be noted that the ComposeShader class is not necessary, that is, we can create corresponding effects without this class. It is similar to a helper class, which provides convenience for us to achieve certain effects. The following examples illustrate.

We have the following transparent images:

write picture description here

The image above is transparent, but there is a heart pattern in the image that is white and opaque.
I want the gradient color to fill only the ❤-shaped area in the above image, the transparent part is not filled, the color gradient from green to blue, and the gradient direction is from the upper left corner to the lower right corner. We can achieve the above effect without ComposeShader, the code is as follows:

int bitmapWidth = bitmap.getWidth();
int bitmapHeight = bitmap.getHeight();
//将绘制代码放入到canvas.saveLayer()和canvas.restore()之间
canvas.saveLayer(0, 0, bitmapWidth, bitmapHeight, null, Canvas.ALL_SAVE_FLAG);
    //创建BitmapShader,用以绘制❤形
    BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
    //将BitmapShader作为画笔paint绘图所使用的shader
    paint.setShader(bitmapShader);
    //用BitmapShader绘制矩形
    canvas.drawRect(0, 0, bitmapWidth, bitmapHeight, paint);
    //将画笔的Xfermode设置为PorterDuff.Mode.MULTIPLY模式
    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
    //创建LinearGradient,用以产生从左上角到右下角的颜色渐变效果
    LinearGradient linearGradient = new LinearGradient(0, 0, bitmapWidth, bitmapHeight, Color.GREEN, Color.BLUE, Shader.TileMode.CLAMP);
    //将创建LinearGradient作为画笔paint绘图所使用的shader
    paint.setShader(linearGradient);
    //用LinearGradient绘制矩形
    canvas.drawRect(0, 0, bitmapWidth, bitmapHeight, paint);
    //最后将画笔去除掉Xfermode
    paint.setXfermode(null);
canvas.restore();

The effect is as follows:
write picture description here

Here we still analyze the execution process of the code together.

  1. The ❤-shaped area in the middle of our image is pure white, and the ARGB component of the pixel color value in this area is (255, 255, 255, 255). The area outside the ❤-shaped area is pure transparent, and the pixel color value ARGB component of this area is (0,0,0,0).

  2. In order to use Xfermode, we put the drawing code between canvas.saveLayer() and canvas.restore(). Students who have questions about this can refer to the blog post I mentioned above. canvas.saveLayer() will create a new drawing layer, and the layer is fully transparent. The code behind us is drawn to this layer, not directly to Canvas.

  3. We created a BitmapShader with the above Bitmap and bound it to the brush Paint. When we draw a rectangle with canvas.drawRect(), it will be filled with the BitmapShader, and the effect at this time should be to draw a white heart shape on the newly created layer.

  4. Then we create an instance of PorterDuffXfermode and bind it to the brush paint via paint.setXfermode(). The mode type of PorterDuffXfermode is MULTIPLY. MULTIPLY means that the four ARGB components of the source pixel are multiplied by the four ARGB components corresponding to the target pixel, and the result of the multiplication is used as the mixed pixel. When multiplication is performed here, the four components of ARGB have been normalized from the interval [0, 255] to the interval [0.0, 1.0].

  5. Then we created a LinearGradient to implement the color linear gradient effect. The color ramps from green in the upper left corner to blue in the lower right corner. Then we bind it to the shader of the brush paint through the paint.setShader() method.

  6. Later we call canvas.drawRect() again to draw a rectangle of the same size. When drawing, our brush has bound both Xfermode and Shader. First, the canvas will use LinearGradient to draw a rectangular area with a gradient color. Then according to the PorterDuff.Mode.MULTIPLY type set by the brush, the pixels in the rectangular area filled with the gradient color are multiplied and mixed with the pixel color in the heart image we drew in step 3. The pixels in the gradient-filled rectangular area are the source pixels, and the pixels in the heart-shaped image drawn in step 3 are the destination pixels. The ❤-shaped area in the target pixel is pure white, its pixel color is (255, 255, 255, 255), the normalized color is (1, 1, 1, 1), and the ARGB color component in the source pixel at the corresponding position is multiplied by it, The final color is still the color of the source pixel, that is, the heart-shaped area is colored with a gradient by the source pixel. The color outside the ❤-shaped area in the target pixel is pure transparent, the color is (0, 0, 0, 0), and the ARGB color component in the source pixel at the corresponding position is multiplied by it, and the final color is still (0 in the target pixel) ,0,0,0), that is, the area outside the heart-shaped area is not colored, and the color is still transparent.

  7. Finally, draw the newly created layer to the Canvas by calling the canvas.restore() method, so that we can see the final effect.

Let's take a look at how to achieve the above effect with ComposeShader, the code is as follows:

int bitmapWidth = bitmap.getWidth();
int bitmapHeight = bitmap.getHeight();
//创建BitmapShader,用以绘制❤形
BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
//创建LinearGradient,用以产生从左上角到右下角的颜色渐变效果
LinearGradient linearGradient = new LinearGradient(0, 0, bitmapWidth, bitmapHeight, Color.GREEN, Color.BLUE, Shader.TileMode.CLAMP);
//bitmapShader对应目标像素,linearGradient对应源像素,像素颜色混合采用MULTIPLY模式
ComposeShader composeShader = new ComposeShader(bitmapShader, linearGradient, PorterDuff.Mode.MULTIPLY);
//将组合的composeShader作为画笔paint绘图所使用的shader
paint.setShader(composeShader);
//用composeShader绘制矩形区域
canvas.drawRect(0, 0, bitmapWidth, bitmapHeight, paint);

The effect achieved with ComposeShader is the same as the above picture, and I will not map it anymore. We can see that after using ComposeShader, the amount of code is significantly reduced to achieve the same effect, and we don't need to put the drawing code between canvas.saveLayer() and canvas.restore().

According to the above example, we can draw the following conclusions:
Suppose we define two Shader variables, shaderA and shaderB, and instantiate these two Shaders respectively.
The two can be combined using ComposeShader, the basic code is as follows:

ComposeShader composeShader = new ComposeShader(shaderA, shaderB, porterDuffMode);
paint.setShader(composeShader);
canvas.drawXXX(..., paint);

The above code is equivalent to the following code snippet:

canvas.saveLayer(left, top, right, bottom, null, Canvas.ALL_SAVE_FLAG);
    paint.setShader(shaderA);
    canvas.drawXXX(..., paint);
    paint.setXfermode(new PorterDuffXfermode(mode));
    paint.setShader(shaderB);
    canvas.drawXXX(..., paint);
    paint.setXfermode(null);
canvas.restore();

The premise that the above two code snippets are equivalent here is that the drawXXX method called in the canvas.drawXXX(..., paint) method in the two code snippets is the same, and the parameters passed in are the same, for example, we have two The drawRect() method is called in the heart-shaped code examples and the drawn rectangles are of the same position and size.


This article is transferred from: http://blog.csdn.net/iispring/article/details/50500106

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325773716&siteId=291194637