Libgdx——使用pixmap繪製透明圓角矩形

一、簡述

1、需求

最近在使用Libgdx進行遊戲大廳開發,遇到這種需求:爲個別文本控件(Label)設置純色透明的圓角矩形背景。

2、思路

Libgdx中的Label是提供背景設置的:對Label的Style的background屬性進行設置即可,這個background是個Drawable,可以使用圖片作爲Label的背景,很好很強大,但我這個項目中的Label背景只需要一種透明顏色而已,用圖片來實現的話我覺得並不是一種很好的方式(有種殺雞用牛刀的感覺)。想來想去,認爲Libgdx中的Pixmap可以幫助我實現這種需求,因爲Pixmap是可以被用來繪製一個簡單圖形的,之後將pixmap轉換成drawable賦值給background就好了:

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

3、難點

然而,pixmap只提供瞭如下幾種繪製圖形的方法:

pixmap.drawLine()           // 畫線
pixmap.drawRectangle();     // 畫矩形
pixmap.drawCircle();        // 畫環
pixmap.fillTriangle();      // 填充三角形
pixmap.fillRectangle();     // 填充矩形
pixmap.fillCircle();        // 填充圓形

我要的圓角矩形正好沒有(畢竟圓角矩形不是簡單圖形是吧。。。),於是,經過google大法及本人的”縝密”思考之後,純色透明圓角矩形實現出來了,本篇將記錄兩種實現圓角矩形的方案,下面開始進入正題。

二、方案一

這個方案借鑑了一個歪果人的博文,本文爲我之後的方案二做了啓發,這裏就先把地址貼出來,方便今後再翻出來欣賞:

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

下面就開始強行“翻譯”一下。

1、原理

繪製出一個圓角矩形,實際上,可以通過使用填充了相同的顏色的2個矩形和4個圓圈來實現,這幾個圖形的擺放如下圖所示。

2、實現

通過上圖,可以很清晰的明白原作者的實現思想,下面就開始碼代碼(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;
}

3、效果

爲了直觀的看出效果,我把Demo的舞臺背景渲染爲黑色,圓角矩形設置爲白色,下面列出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);

4、缺陷

效果很棒,不得不說,歪果人的想法還是挺好的,但是,當我把圓角矩形的顏色設置爲白色透明時,這效果就噁心了,這裏貼出白透明色的設置代碼:

Color color = new Color(1, 1, 1, 0.5f);

爲什麼會這樣,仔細想想就能明白,這是因爲pixmap在繪製這幾個圖形時,它們的重合部分透明度疊加了。

5、完善

既然知道了原因,那有什麼解決辦法呢?這裏列出我能想到的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);
    // 1、保存原先的透明度
    float alpha = color.a;
    // 2、將透明度設置爲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 (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;
}

來看下效果,嗯,還可以吧。

三、方案二(個人認爲比較完美的方案)

雖然用2個pixmap的方式可以”完美”地繪製出純色透明圓角矩形,但是,每創建出1個透明圓角矩形都必須創建出2個pixmap來爲之輔助,儘管最後會對舊的pixmap進行dispose,但總感覺這種方案不是並最優方式。

1、原理

通過一番思考之後,我得出了這樣一個結論:

既然最後在使用到第2個pixmap的時候需要遍歷所有像素點來重新繪製一遍,那我乾脆直接進行第2步(第1步繪製不透明矩形的步驟不要了),在遍歷所有像素的時候把需要繪製到pixmap的像素點繪製出來不就好了嗎?這樣做還可以省掉一個pixmap的開銷。

那麼現在的問題就是,我怎麼知道哪些像素應該被繪製,哪些像素不要被繪製呢?其實可以把圓角矩形看成是一個不完整的有缺角的矩形,而這些缺角正好就是不用被繪製的那些像素點。

通過觀察,可以知道,四個缺角中的像素都有如下相同點:

  • 在綠線與藍線組成的小矩形區域中;
  • 都不在圓上,換句話說就是點與圓心的距離超過半徑。

2、實現

根據結論,代碼實現如下:

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的開銷。

四、最後

最後,想多說兩句,Libgdx作爲一款優秀的Android端遊戲開發引擎,網上的資料卻相當的少,很多東西就算Google了也不一定能找到答案,本人也是最近纔對其進行了解並上手使用,對於本文中所說的需求或許並不是最好的解決方式,如果您有什麼好的解決方案或建議,請不吝賜教,thx。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章