Libgdx——使用pixmap绘制透明圆角矩形

1、简述

一、需求

最近在使用Libgdx进行游戏大厅开发,遇到这种需求:为个别文本控件(Label)设置纯色透明的圆角矩形背景。java

二、思路

Libgdx中的Label是提供背景设置的:对Label的Style的background属性进行设置便可,这个background是个Drawable,可使用图片做为Label的背景,很好很强大,但我这个项目中的Label背景只须要一种透明颜色而已,用图片来实现的话我以为并非一种很好的方式(有种杀鸡用牛刀的感受)。想来想去,认为Libgdx中的Pixmap能够帮助我实现这种需求,由于Pixmap是能够被用来绘制一个简单图形的,以后将pixmap转换成drawable赋值给background就行了:wordpress

Drawable bg = new TextureRegionDrawable(new TextureRegion(new Texture(pixmap)));
label.getStyle().background = bg;
复制代码

三、难点

然而,pixmap只提供了以下几种绘制图形的方法:google

pixmap.drawLine()		// 画线
pixmap.drawRectangle();		// 画矩形
pixmap.drawCircle();		// 画环
pixmap.fillTriangle();		// 填充三角形
pixmap.fillRectangle();		// 填充矩形
pixmap.fillCircle();		// 填充圆形
复制代码

我要的圆角矩形正好没有(毕竟圆角矩形不是简单图形是吧。。。),因而,通过google大法及本人的"缜密"思考以后,纯色透明圆角矩形实现出来了,本篇将记录两种实现圆角矩形的方案,下面开始进入正题。spa

2、方案一

这个方案借鉴了一个歪果人的博文,本文为我以后的方案二作了启发,这里就先把地址贴出来,方便从此再翻出来欣赏:翻译

博文原地址:LIBGDX – DRAWING A ROUNDED RECTANGLE PIXMAP3d

下面就开始强行“翻译”一下。code

一、原理

绘制出一个圆角矩形,实际上,能够经过使用填充了相同的颜色的2个矩形和4个圆圈来实现,这几个图形的摆放以下图所示。orm

二、实现

经过上图,能够很清晰的明白原做者的实现思想,下面就开始码代码(copy):cdn

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;
}
复制代码

三、效果

为了直观的看出效果,我把Demo的舞台背景渲染为黑色,圆角矩形设置为白色,下面列出demo中的部分代码:blog

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);
复制代码

四、缺陷

效果很棒,不得不说,歪果人的想法仍是挺好的,可是,当我把圆角矩形的颜色设置为白色透明时,这效果就恶心了,这里贴出白透明色的设置代码:

Color color = new Color(1, 1, 1, 0.5f);
复制代码

为何会这样,仔细想一想就能明白,这是由于pixmap在绘制这几个图形时,它们的重合部分透明度叠加了。

五、完善

既然知道了缘由,那有什么解决办法呢?这里列出我能想到的2个办法:

  1. 先使用不透明颜色进行绘制,待全部图形绘制完成后,再来设置总体的透明度。
  2. 先用一个pixmap绘制出不透明圆角矩形,而后遍历全部的像素点,若是该像素不是透明的,则在另外一个pixmap的相同位置,用rgb相同但a不一样的颜色再绘制一次。

第一个方法我以为是比较好的,感受实现上比较简单可靠,然而我始终没有找到能够对pixmap设置总体透明度的方法,因而我这里采用了第二个方法来实现:

public Pixmap getRoundedRectangle(Color color, int width, int height, int radius) {
    Pixmap pixmap = new Pixmap(width, height, Pixmap.Format.RGBA8888);
    // 一、保存原先的透明度
    float alpha = color.a;
    // 二、将透明度设置为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);
	// 三、若是原来的背景色存在透明度,则须要对图形总体重绘一次
    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;
}
复制代码

来看下效果,嗯,还能够吧。

3、方案二(我的认为比较完美的方案)

虽然用2个pixmap的方式能够"完美"地绘制出纯色透明圆角矩形,可是,每建立出1个透明圆角矩形都必须建立出2个pixmap来为之辅助,尽管最后会对旧的pixmap进行dispose,但总感受这种方案不是并最优方式。

一、原理

经过一番思考以后,我得出了这样一个结论:

既然最后在使用到第2个pixmap的时候须要遍历全部像素点来从新绘制一遍,那我干脆直接进行第2步(第1步绘制不透明矩形的步骤不要了),在遍历全部像素的时候把须要绘制到pixmap的像素点绘制出来不就行了吗?这样作还能够省掉一个pixmap的开销。

那么如今的问题就是,我怎么知道哪些像素应该被绘制,哪些像素不要被绘制呢?其实能够把圆角矩形当作是一个不完整的有缺角的矩形,而这些缺角正好就是不用被绘制的那些像素点。

经过观察,能够知道,四个缺角中的像素都有以下相同点:

  • 在绿线与蓝线组成的小矩形区域中;
  • 都不在圆上,换句话说就是点与圆心的距离超过半径。

二、实现

根据结论,代码实现以下:

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;
}
复制代码

为了方便理解,下面列出各个缺角的圆心与小矩形x与y的取值范围:

// bottom-left
// ------------圆心:(radius, radius)
// ------------矩形:([0,radius], [0,radius])
// top-left
// ------------圆心:(radius, height-radius)
// ------------矩形:([0,radius], [height-radius,height])
// bottom-right
// ------------圆心:(width-radius,radius)
// ------------矩形:([width-radius,width], [0,radius])
// top-right
// ------------圆心:(width-radius,height-radius)
// ------------矩形:([width-radius,width], [height-radius,height])
复制代码

结果是OK的,与方案一绘制出来的透明圆角矩形一致,而且少了一个pixmap的开销。

4、最后

最后,想多说两句,Libgdx做为一款优秀的Android端游戏开发引擎,网上的资料却至关的少,不少东西就算Google了也不必定能找到答案,本人也是最近才对其进行了解并上手使用,对于本文中所说的需求或许并非最好的解决方式,若是您有什么好的解决方案或建议,请不吝赐教,thx。

相关文章
相关标签/搜索