How to Use the Shader for Canvas plotting in Android
Overview
When we use the Canvas in Android to draw various images, we can usePaint.setShader(shader)
Set shader for the Paint brush, so that you can draw a colorful image. So what is the Shader? Everyone who has done GPU graphics should know this word. Shader is what the coloring tool means. We can understand that the various drawXXX methods in the Canvas define the shape of the image, while the Shader in the paint brush defines the color and appearance of the image, the combination of the two determines the color-filled image drawn by the Canvas.
Classandroid.graphics.Shader
There are five sub-classes: BitmapShader, LinearGradient, RadialGradient, SweepGradient, and ComposeShader.
BitmapShader
BitmapShader, as its name implies, uses Bitmap to render and color the drawn image. In fact, it is used to map the image.
The BitmapShader constructor is as follows:
BitmapShader(Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY)
The first parameter is the Bitmap object, which determines which image is used to map the drawn image.
Both the second and third parameters are enumeration values of the Shader. TileMode type, including CLAMP, REPEAT, and MIRROR.
CLAMP
CLAMP indicates that when the size of the image is larger than that of Bitmap, the remaining space is filled with the color of the Bitmap four sides.
We have a Bitmap, as shown below:
Vce147SmtcTP8cvYtrzKx824w/e1xKGjPGJyIC8 + DQrO0sPHyrnTw7jDQml0bWFwo6zR3cq + fill "brush: java;">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:
As we can see, because the rectangle area we draw is larger than Bitmap, Bitmap fills the rectangle area with the color of the outermost layer on the right side and the bottom side. Because the pixels in the lower right corner of the original Bitmap are transparent, the bottom right corner of the drawn rectangle is transparent.
If the size of the image we plot is smaller than the Bitmap size, the effect looks like cropping the original Bitmap. The Code is as follows:
We can see that when the circular size we have drawn is smaller than the Bitmap size, the effect is that we have cropped Bitmap with the circle we have drawn.
REPEAT
REPEAT indicates that when the image size is larger than the Bitmap size, Bitmap is used to repeatedly tile the entire area.
The sample code is as follows:
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:
MIRROR
Like REPEAT, when the size of the drawn image is greater than the Bitmap size, MIRROR uses Bitmap to repeatedly tile the entire drawing area. Unlike REPEAT, two adjacent bitmaps are MIRROR images.
The Code is as follows:
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:
Finally, tileX and tileY can have different values when constructing BitmapShader.
LinearGradient
We can use LinearGradient to create a linear gradient. It 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)
Let's focus on the first constructor. It's easy to understand the second constructor.
LinearGradient is used to create a linear gradient effect. It is gradient along the direction of a straight line. The coordinate (x0, y0) is the starting point of this gradient line, and the coordinate (x1, y1) is the end point of the gradient line. It must be noted that the coordinates (x0, y0) and the coordinates (x1, y1) are the coordinates in the Canvas drawing coordinate system. Color0 and color1 indicate the starting color and ending color of the gradient respectively. Like BitmapShader, LinearGradient also supports TileMode, which has the following values: CLAMP, REPEAT, and MIRROR.
The Code 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:
We used CLAMP above, but since the size of the rectangle we drew is the same as that of the gradient position, the CLAMP effect is not obvious.
We will increase the area to be drawn, or use CLAMP to draw the rectangle of the entire Canvas size this time.
canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), paint);
The effect is as follows:
When we change the CLAMP to REPEAT, we still draw a rectangle of the entire Canvas size. The effect is as follows:
When we use MIRROR to draw a rectangle of the entire Canvas size, the effect is as follows:
In the second constructor of LinearGradient, you can use the colors parameter to input multiple color values. In this way, the color values specified in the colors array are used for Linear color interpolation. You can also specify the positions array. Each position in the array corresponds to the relative position of each color online segment in the colors array. The position value ranges from 0 to 1, and 0 indicates the starting position, 1 indicates the termination position. If the positions array is null, Android automatically sets the equal spacing for colors.
RadialGradient
We can use RadialGradient to create a radiation gradient from the center to the surrounding area, 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)
The two constructors are similar to the two constructors of LinearGradient. Here we will focus on the first constructor. On this basis, it is very easy to understand the second constructor.
RadialGradient is used to create a radiation gradient from the center to the surrounding area, so we need to input some circle parameters in its constructor. The coordinates (centerX, centerY) are the center of the circle, that is, the starting center color position. radius determines the circle radius and edgeColor at the circle radius. In this way, when the position moves from the center to the Circle contour, the color gradually changes from centerColor to edgeColor. RadialGradient also supports the TileMode parameter, which can be CLAMP, REPEAT, or MIRROR.
First, we use CLAMP as the TileMode. The Code is as follows:
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:
In, the rectangle we draw is the same size as the Canvas, and its size is greater than the circle size of the RadialGradient we define. We can see that when CLAMP is used as the TileMode, the color gradually changes from the green of the center of the circle to the blue color of the circle. edgeColor is filled in blue outside the circle.
When we change the CLAMP to REPEAT, we still draw the same rectangle. The effect is as follows:
We can see that the color ranges from green to blue as a gradient period spreading out from the center of the circle.
When we use MIRROR as the TileMode, we still draw the same rectangle. The effect is as follows:
We can see that the color is green-> blue-> green-> blue... Cyclically alternating transformations spread out from the center.
In the second constructor of RadialGradient, you can use the colors parameter to input multiple color values. In this way, the color values specified in the colors array are used for Linear color interpolation. You can also specify the stops array. Each stop in this array corresponds to the relative position of each color in the colors array in the radius. The stop value ranges from 0 to 1, and 0 indicates the center of the circle, 1 indicates the circumference position. If the stops array is null, Android automatically sets the equal spacing for colors.
SweepGradient
SweepGradient can be used to create a 360-degree color rotation gradient effect. Specifically, the color rotates around the center point 360 degrees clockwise, and the starting point is three o'clock.
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. First, we will explain the first constructor.
The coordinates (cx, cy) determine the center point and 360 degrees of rotation around the center point. Color0 indicates the color position of the starting point, while color1 indicates the color position of the ending point.
The Code is as follows:
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:
As shown in, we use canvas. the drawCircle () method draws a circle, and sets the center point of the SweepGradient to the center of the circle. We can see that the color is green from three o'clock and the gradient is rotated 360 degrees clockwise to blue.
In the second constructor of SweepGradient, We can input a colors color array, so that Android will perform color interpolation based on the input color 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 360 degrees. The value range of position is []. Both 0 and 1 indicate the three o'clock position, 0.25 indicates the six o'clock position, 0.5 indicates the nine o'clock position, and 0.75 indicates the 12 o'clock position, and so on. If the positions array is null, Android automatically sets the equal spacing for colors.
The Code is as follows:
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:
In the above Code, we will pass the three colors of red, green, and blue into the colors array, and through the positions array to specify their relative positions are 0, 0.5, 1, so red is the starting color, it is located at three o'clock; green is the middle color, which is located at nine o'clock; blue is the end color, which is also located at three o'clock.
Of course, the starting point color is not necessarily 0, and the ending point color is not necessarily 1. We will change the positions array to the following:
float[] positions = {0.25f, 0.5f, 0.75f};
The effect is as follows:
We can see that the color ratio changes. The starting color of red is 0.25, not 0, but from three o'clock, the color is red. The difference is that the ending color is blue, and the blue position is 0.75 or not 1, which corresponds to the location of 12 o'clock. The 90 degrees from 12 o'clock to three o'clock are transparent, it is not filled with colors. Pay attention when using it.
If we plot a rectangle of the entire Canvas size on this basis, the effect is as follows:
ComposeShader
ComposeShader, as its name implies, is a hybrid Shader. It can combine two Shader according to a certain Xfermode.
ComposeShader has two constructors:
ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode)ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode)
Here, we will briefly introduce Xfermode. Xfermode can be used to mix the newly drawn pixels with the existing pixels at the corresponding position on the Canvas according to the mixing rules. Xfermode has three subclasses: AvoidXfermode, PixelXorXfermode, and porterduxfermode. The first two classes are now deprecated by Android and mainly use porterduxfermode. The porterduxfermode constructor must specify the PorterDuff. Mode type. Therefore, the second constructor can be seen as a special case of the first constructor. We will mainly explain the second one. The two are similar.
We know that when Xfermode is used, the destination pixel DST and source pixel SRC are used. The source pixel refers to the pixel to be drawn to the Canvas, and the target pixel refers to the existing pixel at the corresponding position of the source pixel on the Canvas.
In the constructor, shaderA corresponds to the target pixel and shaderB corresponds to the source pixel.
One thing to note is that the ComposeShader class is not necessary, that is, we can create corresponding effects without this class. It is similar to an assistant class, which provides convenience for us to implement a certain effect, the following is an example.
We have the following transparent images:
The above picture is transparent, but there is a heart shape in the picture that is white and not transparent.
I want the gradient color to be filled in only? Area, the transparent part is not filled, the color gradient from green to blue, the gradient direction from the upper left to the lower right corner. We can achieve the above effect without using ComposeShader. The Code is as follows:
Int bitmapWidth = bitmap. getWidth (); int bitmapHeight = bitmap. getHeight (); // Add the drawing code to the canvas. saveLayer () and canvas. canvas between restore. saveLayer (0, 0, bitmapWidth, bitmapHeight, null, Canvas. ALL_SAVE_FLAG); // creates a BitmapShader for plotting? BitmapShader bitmapShader = new BitmapShader (bitmap, Shader. tileMode. CLAMP, Shader. tileMode. CLAMP); // use BitmapShader as the shader paint used by the paint. setShader (bitmapShader); // use BitmapShader to draw a rectangular canvas. drawRect (0, 0, bitmapWidth, bitmapHeight, paint); // set the Xfermode of the paint brush to PorterDuff. mode. MULTIPLY mode paint. setXfermode (new porterduxfermode (PorterDuff. mode. MULTIPLY); // create a LinearGradient to generate a Color gradient effect from the upper left corner to the lower right corner LinearGradient linearGradient = new LinearGradient (0, 0, bitmapWidth, bitmapHeight, Color. GREEN, Color. BLUE, Shader. tileMode. CLAMP); // The shader paint used to create the LinearGradient as the paint drawing. setShader (linearGradient); // use LinearGradient to draw a rectangular canvas. drawRect (0, 0, bitmapWidth, bitmapHeight, paint); // Finally, remove the paint brush from Xfermode paint. setXfermode (null); canvas. restore ();
The effect is as follows:
Here we will analyze the code execution process together.
In the middle of our image? The shape area is pure white, and the pixel color value of this area is (255,255,255,255 ).? The area outside the shape area is transparent, and the pixel color value ARGB component of the area is ).
To use Xfermode, we place the drawing code between canvas. saveLayer () and canvas. restore (). For more information, see the blog post I mentioned above. Canvas. saveLayer () creates a new drawing layer, which is completely transparent. The code behind this layer is drawn to this layer rather than directly drawn to the Canvas.
We use Bitmap to create a BitmapShader and bind it to the Paint brush. When we use canvas. drawRect () to draw a rectangle, this BitmapShader will be used to fill the rectangle. At this time, the effect should be to draw a white heart shape on the newly created layer.
Then we created an instance of porterduxfermode and bound it to the paint brush using paint. setXfermode. The mode type of porterduxfermode 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 respectively, and the result of multiplication is used as the pixel after mixing. During the phase multiplication, the four ARGB components have been normalized from the [0,255] range to the [0.0, 1.0] range.
Then we created a LinearGradient to achieve linear color gradient. The color changes from the green in the upper left corner to the blue in the lower right corner. Then we bind the paint. setShader () method to the shader of the paint brush.
Next we call canvas. drawRect () to draw a rectangle of the same size. During painting, our paint brush has been bound to both Xfermode and Shader. First, canvas uses LinearGradient to draw a rectangular area with gradient. Then, based on the PorterDuff. Mode. MULTIPLY Type set by the paint brush, MULTIPLY the pixels in the area of the rectangle filled with gradient and the pixel colors in the heart-shaped image we drew in step 1. The pixels in the area of the rectangle filled with gradient are source pixels, And the pixels in the heart-shaped image drawn in step 1 are the target pixels. In the target pixel? The shape area is pure white, the pixel color is (255,255,255,255), and the normalized color is (,). The ARGB color component in the source pixel of the corresponding position is multiplied by it, the final color is the source pixel color, that is, the heart-shaped area is gradually colored by the source pixel. In the target pixel? The color outside the shape area is pure and transparent. The color is (,). The ARGB color component in the source pixel of the corresponding position is multiplied by it. The final color is still, 0, 0), that is, the area outside the heart is not colored, still transparent.
Finally, call the canvas. restore () method to draw the newly created layer to the Canvas, so that we can see the final effect.
Let's take a look at the above effect with ComposeShader. The Code is as follows:
Int bitmapWidth = bitmap. getWidth (); int bitmapHeight = bitmap. getHeight (); // create a BitmapShader for plotting? BitmapShader bitmapShader = new BitmapShader (bitmap, Shader. tileMode. CLAMP, Shader. tileMode. CLAMP); // create a LinearGradient to generate a Color gradient effect from the upper left corner to the lower right corner LinearGradient linearGradient = new LinearGradient (0, 0, bitmapWidth, bitmapHeight, Color. GREEN, Color. BLUE, Shader. tileMode. CLAMP); // bitmapShader corresponds to the target pixel, linearGradient corresponds to the source pixel, and pixel color mixing adopts the MULTIPLY mode ComposeShader composeShader = new ComposeShader (bitmapShader, linearGradient, PorterDuff. mode. MULTIPLY); // use the composeShader of the combination as the shaderpaint used by the paint drawing. setShader (composeShader); // use composeShader to draw a canvas in the rectangular area. drawRect (0, 0, bitmapWidth, bitmapHeight, paint );
With the same effect achieved by ComposeShader, I will not map any more. We can see that after using ComposeShader to achieve the same effect, the amount of code is significantly reduced, and we do not need to put the drawing code into the canvas. saveLayer () and canvas. between restore.
Based on the above example, we can draw the following conclusions:
Suppose we define two Shader variables, shaderA and shaderB, and instantiate the two Shader respectively.
You can use ComposeShader to combine the two. 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 is that canvas. drawXXX (..., Paint) The drawXXX method is called in the same way, and the input parameters are the same. For example, we call drawRect () in the previous two heart-shaped code examples () and the position and size of the rectangle are the same.
Summary
This article introduces five sub-classes of Shader: BitmapShader, LinearGradient, RadialGradient, SweepGradient, and ComposeShader. At last, I will explain the relatively complex example of ComposeShader. If you can see the example of ComposeShader at last, I believe you have fully understood the Shader.
If you think the article is okay, click below to help me pin it down. I hope this article will be helpful for you to use Shader for drawing!