One, a brief introduction

1, requirements,

Recently, while developing a game lobby using Libgdx, I came across the need to create a solid color transparent rectangle with rounded corners for individual text controls (labels).

2, train of thought

The Label in Libgdx provides the background: You can set the background property of the Style of the Label, which is a Drawable, and you can use the image as the background of the Label, which is very good and powerful, but the Label background in my project only needs a transparent color. I don’t think it’s a very good way to do it with pictures. Pixmap in Libgdx can be used to drawa simple graph. Then convert Pixmap to a drawable and assign it to background:

Drawable bg = new TextureRegionDrawable(new TextureRegion(new Texture(pixmap)));
label.getStyle().background = bg;
Copy the code

3, the difficulties in

However, PixMap only provides the following methods for drawing graphics:

pixmap.drawLine()		Draw a line / /
pixmap.drawRectangle();		/ / draw a rectangle
pixmap.drawCircle();		/ / draw ring
pixmap.fillTriangle();		// Fill the triangle
pixmap.fillRectangle();		// Fill the rectangle
pixmap.fillCircle();		// Fill the circle
Copy the code

The rounded rectangle I asked for just doesn’t have it. So, after Google’s great method and my “careful” thinking, the solid color transparent rounded rectangle is realized. This paper will record two ways to realize the rounded rectangle.

Plan 1

This plan draws on a crooked fruit person’s blog post, this article for my later plan two did inspiration, here first to post the address, convenient for the future to turn out to enjoy:

LIBGDX — DRAWING A ROUNDED RECTANGLE PIXMAP

Here’s a forced “translation”.

1, the principle of

Draw a rounded rectangle. In fact, this can be done by using 2 rectangles and 4 circles filled with the same color, as shown below.

2, implementation,

Through the above figure, you can clearly understand the realization of the original author, the following code code (copy) :

public Pixmap getRoundedRectangle(int width, int height, int radius, int color) {
    Pixmap pixmap = new Pixmap(width, height, Pixmap.Format.RGBA8888);
    pixmap.setColor(color);
    // Pink rectangle
    pixmap.fillRectangle(0, radius, pixmap.getWidth(), pixmap.getHeight() - 2 * radius);
    // Green rectangle
    pixmap.fillRectangle(radius, 0, pixmap.getWidth() - 2 * radius, pixmap.getHeight());
    // Bottom-left circle
    pixmap.fillCircle(radius, radius, radius);
    // Top-left circle
    pixmap.fillCircle(radius, pixmap.getHeight() - radius, radius);
    // Bottom-right circle
    pixmap.fillCircle(pixmap.getWidth() - radius, radius, radius);
    // Top-right circle
    pixmap.fillCircle(pixmap.getWidth() - radius, pixmap.getHeight() - radius, radius);
    return pixmap;
}
Copy the code

Effect of 3,

To see the effect visually, I rendered the stage background of the Demo to black and the rounded rectangle to white. Here is some code in the Demo:

Texture roundedRectangle = new Texture(getRoundedRectangle(color, width, height, radius));
Image image = new Image(roundedRectangle);
image.setPosition(Gdx.graphics.getWidth() / 2, Gdx.graphics.getHeight() / 2, Align.center);
addActor(image);
Copy the code

4, defect

The effect is great, and I have to say, the idea is good, but when I set the color of the rounded rectangle to white and transparent, the effect is disgusting. Here is the white and transparent setting code:

Color color = new Color(1.1.1.0.5 f);
Copy the code

If you think about it, the reason for this is that when PixMap draws these shapes, their overlap overlaps their transparency.

5, perfect

Now that we know why, what’s the solution? Here are two things I can think of:

  1. Draw with an opaque color first, and then set the overall transparency after all the graphics are drawn.
  2. First draw the opaque rounded rectangle with one Pixmap, then traverse all pixels, if the pixel is not transparent, draw it again with the same RGB color but a different color at the same location in another Pixmap.

I think the first method is better, and it is relatively simple and reliable to implement. However, I have not found a way to set the overall transparency of pixMap, so I use the second method to implement:

public Pixmap getRoundedRectangle(Color color, int width, int height, int radius) {
    Pixmap pixmap = new Pixmap(width, height, Pixmap.Format.RGBA8888);
    // 1. Keep the original transparency
    float alpha = color.a;
    // 2. Start drawing rounded rectangles with transparency set to 1
    color.set(color.r, color.g, color.b, 1);
    pixmap.setColor(color);
    // Pink rectangle
    pixmap.fillRectangle(0, radius, pixmap.getWidth(), pixmap.getHeight() - 2 * radius);
	// Green rectangle
    pixmap.fillRectangle(radius, 0, pixmap.getWidth() - 2 * radius, pixmap.getHeight());
    // Bottom-left circle
    pixmap.fillCircle(radius, radius, radius);
    // Top-left circle
    pixmap.fillCircle(radius, pixmap.getHeight() - radius, radius);
    // Bottom-right circle
    pixmap.fillCircle(pixmap.getWidth() - radius, radius, radius);
    // Top-right circle
    pixmap.fillCircle(pixmap.getWidth() - radius, pixmap.getHeight() - radius, radius);
	// 3. If the original background color has transparency, the whole graph needs to be redrawn
    if(alpha ! =1) {
        Pixmap newPixmap = new Pixmap(pixmap.getWidth(), pixmap.getHeight(), pixmap.getFormat());
        int r = ((int) (255 * color.r) << 16);
        int g = ((int) (255 * color.g) << 8);
        int b = ((int) (255 * color.b));
        int a = ((int) (255 * alpha) << 24);
        int argb8888 = new Color(r | g | b | a).toIntBits();
        for (int y = 0; y < pixmap.getHeight(); y++) {
            for (int x = 0; x < pixmap.getWidth(); x++) {
                int pixel = pixmap.getPixel(x, y);
                if ((pixel & color.toIntBits()) == color.toIntBits()) {
                    newPixmap.drawPixel(x, y, argb8888);
                }
            }
        }
		pixmap.dispose();
        pixmap = newPixmap;
    }
    return pixmap;
}
Copy the code

Let’s see how it works. Yeah, it’s okay.

Iii. Plan 2 (Perfect plan in my opinion)

Although 2 pixmaps can be used to “perfectly” draw a pure color transparent rounded rectangle, 2 Pixmaps must be created for each transparent rounded rectangle. Although dispose old Pixmaps in the end, it always feels that this scheme is not the optimal method.

1, the principle of

After some reflection, I came to this conclusion:

Since I need to iterate over all the pixels in the second pixmap to redraw it, WHY not just go to the second step and draw the pixels that need to be drawn into pixMap while iterating over all the pixels? Doing so also saves the overhead of a PixMap.

So now the question is, how do I know which pixels to draw and which ones not to draw? You can think of a rounded rectangle as an incomplete rectangle with missing angles, which happen to be the pixels that aren’t being drawn.

Through observation, it can be known that the pixels in the four missing angles have the following similarities:

  • In a small rectangle of green and blue lines;
  • It’s not on the circle, in other words it’s farther from the center than the radius.

2, implementation,

According to the conclusion, the code implementation is as follows:

public Pixmap getRoundedRectangle(Color color, int width, int height, int radius) {
    Pixmap pixmap = new Pixmap(width, height, Pixmap.Format.RGBA8888);
    pixmap.setColor(color);
    for (int y = 0; y < pixmap.getHeight(); y++) {
        for (int x = 0; x < pixmap.getWidth(); x++) {
            if ((x >= 0 && x <= radius) && (y >= 0 && y <= radius)) { // bottom-left
                if (Math.sqrt((radius - x) * (radius - x) + (radius - y) * (radius - y)) > radius) {
                    continue; }}else if ((x >= 0 && x <= radius) && (y >= (height - radius) && y <= height)) { // top-left
                if (Math.sqrt((radius - x) * (radius - x) + ((height - radius) - y) * ((height - radius) - y)) > radius) {
                    continue; }}else if ((x >= (width - radius) && x <= width) && (y >= 0 && y <= radius)) {// bottom-right
                if (Math.sqrt(((width - radius) - x) * ((width - radius) - x) + (radius - y) * (radius - y)) > radius) {
                    continue; }}else if ((x >= (width - radius) && x <= width) && (y >= (height - radius) && y <= height)) {// top-right
                if (Math.sqrt(((width - radius) - x) * ((width - radius) - x) + ((height - radius) - y) * ((height - radius) - y)) > radius) {
                    continue; } } pixmap.drawPixel(x, y); }}return pixmap;
}
Copy the code

For easy understanding, the value ranges of the center of each missing Angle and small rectangle x and y are listed below:

/ / bottom left / / -- -- -- -- -- -- -- -- -- -- -- - the center of the circle (radius, the radius) / / -- -- -- -- -- -- -- -- -- -- -- -- rectangular ([0, the radius], [0, the radius]) / / top - left / / -- -- -- -- -- -- -- -- -- -- -- -- the circle (radius, height - the radius) / / -- -- -- -- -- -- -- -- -- -- -- -- rectangular ([0, the radius], [height - the radius and height]) / / bottom right / / -- -- -- -- -- -- -- -- -- -- -- - circle: (width - the radius, the radius) / / -- -- -- -- -- -- -- -- -- -- -- -- rectangular (/ width - the radius, the width, [0, the radius]) / / / / top - right -- -- -- -- -- -- -- -- -- -- -- -- center: (width - the radius, height - the radius) / / -- -- -- -- -- -- -- -- -- -- -- -- rectangular (/ width - the radius, the width, [height-radius,height])Copy the code

The result is OK, consistent with the transparent rounded rectangle drawn in scheme 1, and with one less pixMap overhead.

Four, the last

Libgdx is an excellent Game development engine for Android, but there is very little information on the web. Even if you Google Libgdx, you won’t be able to find the answers. I’ve only recently learned about Libgdx and started using it, so it may not be the best solution to the needs mentioned in this article. If you have any good solutions or suggestions, please feel free to comment, THX.