“填充”标签中的 Unicode 字符

2022-09-04 19:21:29

如何在Swing中“填充”标签中的Unicode字符?

我正在尝试为我最近编程的国际象棋程序制作一个用户界面(使用如上所示的棋子)。在其中,我使用Unicode字符来表示我的棋子(通过)。\u2654\u265F

问题如下:

当我将棋子的背景设置为白色时,整个标签都被填充了(在我的情况下,它是一个50 * 50px的白色正方形,字符在上面)。这导致我的作品看起来像瓷砖,而不仅仅是它们的图片。JLabel

当我将标签设置为不透明时,我只得到一个饼干切割器版本的棋子,而不是一个内部填充的棋子。例如

Actual result

有没有办法只填充角色?

如果没有,我想我会做一个精灵表,但我喜欢这个,因为我可以使用棋子的方法作为标签。toString()

法典

import java.awt.*;
import javax.swing.*;
import java.util.Random;

class ChessBoard {

    static Font font = new Font("Sans-Serif", Font.PLAIN, 50);
    static Random rnd = new Random();

    public static void addUnicodeCharToContainer(
        String s, Container c, boolean randomColor) {

        JLabel l = new JLabel(s);
        l.setFont(font);
        if (randomColor) {
            int r = rnd.nextInt(255);
            int g = rnd.nextInt(255);
            int b = rnd.nextInt(255);

            l.setForeground(new Color(r,g,b));
            l.setBackground(new Color(255-r,255-g,255-b));
            l.setOpaque(true);
        }
        c.add(l);
    }

    public static void main(String[] args) {
        Runnable r = new Runnable() {

            @Override
            public void run() {
                JPanel gui = new JPanel(new GridLayout(0,6,4,4));

                String[] pieces = {
                    "\u2654","\u2655","\u2656","\u2657","\u2658","\u2659",
                    "\u265A","\u265B","\u265C","\u265D","\u265E","\u265F"
                };

                for (String piece : pieces) {
                    addUnicodeCharToContainer(piece,gui,false);
                }
                for (String piece : pieces) {
                    addUnicodeCharToContainer(piece,gui,true);
                }

                JOptionPane.showMessageDialog(null, gui);
            }
        };
        SwingUtilities.invokeLater(r);
    }
}

答案 1

Chess Pieces

这两行是通过Java-2D的巫术生成的。诀窍是:

  • 忽略“黑色”棋子,因为我们的颜色实际上来自“形状所包含的空间”。这些在白色棋子中更大。
  • 创建表示字符形状的 。这对于 Java-2D 中的进一步操作非常重要。GlyphVector
  • 创建图像的大小。Rectangle
  • subtract()从图像的形状开始的字符形状。
  • 将改变的形状分解为区域。
  • 用背景色填充区域,但跳过从 0.0,0.0 开始的单个区域(表示我们需要透明的最外层区域)。
  • 最后,使用轮廓颜色填充字符本身的形状。

法典

import java.awt.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
import java.util.*;

class ChessBoard {

    static Font font = new Font(Font.SANS_SERIF, Font.PLAIN, 50);
    static Random rnd = new Random();

    public static ArrayList<Shape> separateShapeIntoRegions(Shape shape) {
        ArrayList<Shape> regions = new ArrayList<Shape>();

        PathIterator pi = shape.getPathIterator(null);
        int ii = 0;
        GeneralPath gp = new GeneralPath();
        while (!pi.isDone()) {
            double[] coords = new double[6];
            int pathSegmentType = pi.currentSegment(coords);
            int windingRule = pi.getWindingRule();
            gp.setWindingRule(windingRule);
            if (pathSegmentType == PathIterator.SEG_MOVETO) {
                gp = new GeneralPath();
                gp.setWindingRule(windingRule);
                gp.moveTo(coords[0], coords[1]);
                System.out.println(ii++ + " \t" + coords[0] + "," + coords[1]);
            } else if (pathSegmentType == PathIterator.SEG_LINETO) {
                gp.lineTo(coords[0], coords[1]);
            } else if (pathSegmentType == PathIterator.SEG_QUADTO) {
                gp.quadTo(coords[0], coords[1], coords[2], coords[3]);
            } else if (pathSegmentType == PathIterator.SEG_CUBICTO) {
                gp.curveTo(
                        coords[0], coords[1],
                        coords[2], coords[3],
                        coords[4], coords[5]);
            } else if (pathSegmentType == PathIterator.SEG_CLOSE) {
                gp.closePath();
                regions.add(new Area(gp));
            } else {
                System.err.println("Unexpected value! " + pathSegmentType);
            }

            pi.next();
        }

        return regions;
    }

    public static void addColoredUnicodeCharToContainer(
            String s, Container c,
            Color bgColor, Color outlineColor, boolean blackSquare) {

        int sz = font.getSize();
        BufferedImage bi = new BufferedImage(
                sz, sz, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = bi.createGraphics();
        g.setRenderingHint(
                RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        g.setRenderingHint(
                RenderingHints.KEY_DITHERING,
                RenderingHints.VALUE_DITHER_ENABLE);
        g.setRenderingHint(
                RenderingHints.KEY_ALPHA_INTERPOLATION,
                RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);

        FontRenderContext frc = g.getFontRenderContext();
        GlyphVector gv = font.createGlyphVector(frc, s);
        Rectangle2D box1 = gv.getVisualBounds();

        Shape shape1 = gv.getOutline();
        Rectangle r = shape1.getBounds();
        System.out.println("shape rect: " + r);
        int spaceX = sz - r.width;
        int spaceY = sz - r.height;
        AffineTransform trans = AffineTransform.getTranslateInstance(
                -r.x + (spaceX / 2), -r.y + (spaceY / 2));
        System.out.println("Box2D " + trans);

        Shape shapeCentered = trans.createTransformedShape(shape1);

        Shape imageShape = new Rectangle2D.Double(0, 0, sz, sz);
        Area imageShapeArea = new Area(imageShape);
        Area shapeArea = new Area(shapeCentered);
        imageShapeArea.subtract(shapeArea);
        ArrayList<Shape> regions = separateShapeIntoRegions(imageShapeArea);
        g.setStroke(new BasicStroke(1));
        for (Shape region : regions) {
            Rectangle r1 = region.getBounds();
            if (r1.getX() < 0.001 && r1.getY() < 0.001) {
            } else {
                g.setColor(bgColor);
                g.fill(region);
            }
        }
        g.setColor(outlineColor);
        g.fill(shapeArea);
        g.dispose();

        JLabel l = new JLabel(new ImageIcon(bi), JLabel.CENTER);
        Color bg = (blackSquare ? Color.BLACK : Color.WHITE);
        l.setBackground(bg);
        l.setOpaque(true);
        c.add(l);
    }

    public static void main(String[] args) {
        Runnable r = new Runnable() {

            @Override
            public void run() {
                JPanel gui = new JPanel(new GridLayout(0, 6, 4, 4));

                String[] pieces = {
                    "\u2654", "\u2655", "\u2656", "\u2657", "\u2658", "\u2659"
                };

                boolean blackSquare = false;
                for (String piece : pieces) {
                    addColoredUnicodeCharToContainer(
                            piece, gui,
                            new Color(203,203,197),
                            Color.DARK_GRAY,
                            blackSquare);
                            blackSquare = !blackSquare;
                }
                            blackSquare = !blackSquare;
                for (String piece : pieces) {
                    addColoredUnicodeCharToContainer(
                            piece, gui,
                            new Color(192,142,60),
                            Color.DARK_GRAY,
                            blackSquare);
                            blackSquare = !blackSquare;
                }

                JOptionPane.showMessageDialog(null, gui);
            }
        };
        SwingUtilities.invokeLater(r);
    }
}

棋盘

这就是它作为棋盘(22.81 Kb)的样子。

Chess Board Unadorned

精灵集

从 Unicode 字符呈现的子画面棋子集(64x64 像素) - 作为具有透明 BG 的 PNG。每个都有6列用于棋子x 2行用于对手(总大小384x128像素)。

实心填充的棋子(青铜/锡)(11.64Kb)。

Chess Piece Tile Set

带渐变填充的棋子(金/银)(13.61Kb)。

Chess Piece Tile Set with Gradient Fill Color

带渐变填充的棋子(较深的青色/洋红色)(13.44Kb)。

Chess Piece Tile Set with Gradient Fill Color

国际象棋棋盘和精灵集的代码

import java.awt.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
import javax.swing.border.*;

import java.io.*;
import javax.imageio.ImageIO;
import java.util.*;
import java.util.logging.*;

class ChessBoard {

    /**
     * Unicodes for chess pieces.
     */
    static final String[] pieces = {
        "\u2654", "\u2655", "\u2656", "\u2657", "\u2658", "\u2659"
    };
    static final int KING = 0, QUEEN = 1, CASTLE = 2,
            BISHOP = 3, KNIGHT = 4, PAWN = 5;
    public static final int[] order = new int[]{
        CASTLE, KNIGHT, BISHOP, QUEEN, KING, BISHOP, KNIGHT, CASTLE
    };

    /*
     * Colors..
     */
    public static final Color outlineColor = Color.DARK_GRAY;
    public static final Color[] pieceColors = {
        new Color(203, 203, 197), new Color(192, 142, 60)
    };
    static final int WHITE = 0, BLACK = 1;

    /*
     * Font. The images use the font sizeXsize.
     */
    static Font font = new Font("Sans-Serif", Font.PLAIN, 64);

    public static ArrayList<Shape> separateShapeIntoRegions(Shape shape) {
        ArrayList<Shape> regions = new ArrayList<Shape>();

        PathIterator pi = shape.getPathIterator(null);
        int ii = 0;
        GeneralPath gp = new GeneralPath();
        while (!pi.isDone()) {
            double[] coords = new double[6];
            int pathSegmentType = pi.currentSegment(coords);
            int windingRule = pi.getWindingRule();
            gp.setWindingRule(windingRule);
            if (pathSegmentType == PathIterator.SEG_MOVETO) {
                gp = new GeneralPath();
                gp.setWindingRule(windingRule);
                gp.moveTo(coords[0], coords[1]);
            } else if (pathSegmentType == PathIterator.SEG_LINETO) {
                gp.lineTo(coords[0], coords[1]);
            } else if (pathSegmentType == PathIterator.SEG_QUADTO) {
                gp.quadTo(coords[0], coords[1], coords[2], coords[3]);
            } else if (pathSegmentType == PathIterator.SEG_CUBICTO) {
                gp.curveTo(
                        coords[0], coords[1],
                        coords[2], coords[3],
                        coords[4], coords[5]);
            } else if (pathSegmentType == PathIterator.SEG_CLOSE) {
                gp.closePath();
                regions.add(new Area(gp));
            } else {
                System.err.println("Unexpected value! " + pathSegmentType);
            }

            pi.next();
        }

        return regions;
    }

    public static BufferedImage getImageForChessPiece(
            int piece, int side, boolean gradient) {
        int sz = font.getSize();
        BufferedImage bi = new BufferedImage(
                sz, sz, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = bi.createGraphics();
        g.setRenderingHint(
                RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        g.setRenderingHint(
                RenderingHints.KEY_DITHERING,
                RenderingHints.VALUE_DITHER_ENABLE);
        g.setRenderingHint(
                RenderingHints.KEY_ALPHA_INTERPOLATION,
                RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);

        FontRenderContext frc = g.getFontRenderContext();
        GlyphVector gv = font.createGlyphVector(frc, pieces[piece]);
        Rectangle2D box1 = gv.getVisualBounds();

        Shape shape1 = gv.getOutline();
        Rectangle r = shape1.getBounds();
        int spaceX = sz - r.width;
        int spaceY = sz - r.height;
        AffineTransform trans = AffineTransform.getTranslateInstance(
                -r.x + (spaceX / 2), -r.y + (spaceY / 2));

        Shape shapeCentered = trans.createTransformedShape(shape1);

        Shape imageShape = new Rectangle2D.Double(0, 0, sz, sz);
        Area imageShapeArea = new Area(imageShape);
        Area shapeArea = new Area(shapeCentered);
        imageShapeArea.subtract(shapeArea);
        ArrayList<Shape> regions = separateShapeIntoRegions(imageShapeArea);
        g.setStroke(new BasicStroke(1));
        g.setColor(pieceColors[side]);
        Color baseColor = pieceColors[side];
        if (gradient) {
            Color c1 = baseColor.brighter();
            Color c2 = baseColor;
            GradientPaint gp = new GradientPaint(
                    sz/2-(r.width/4), sz/2-(r.height/4), c1, 
                    sz/2+(r.width/4), sz/2+(r.height/4), c2, 
                    false);
            g.setPaint(gp);
        } else {
            g.setColor(baseColor);
        }

        for (Shape region : regions) {
            Rectangle r1 = region.getBounds();
            if (r1.getX() < 0.001 && r1.getY() < 0.001) {
            } else {
                g.fill(region);
            }
        }
        g.setColor(outlineColor);
        g.fill(shapeArea);
        g.dispose();

        return bi;
    }

    public static void addColoredUnicodeCharToContainer(
            Container c,
            int piece,
            int side,
            Color bg,
            boolean gradient) {

        JLabel l = new JLabel(
                new ImageIcon(getImageForChessPiece(piece, side, gradient)),
                JLabel.CENTER);
        l.setBackground(bg);
        l.setOpaque(true);
        c.add(l);
    }

    public static void addPiecesToContainer(
            Container c,
            int intialSquareColor,
            int side,
            int[] pieces,
            boolean gradient) {

        for (int piece : pieces) {
            addColoredUnicodeCharToContainer(
                    c, piece, side,
                    intialSquareColor++%2 == BLACK ? Color.BLACK : Color.WHITE,
                    gradient);
        }
    }

    public static void addPiecesToContainer(
            Container c,
            Color bg,
            int side,
            int[] pieces,
            boolean gradient) {

        for (int piece : pieces) {
            addColoredUnicodeCharToContainer(
                    c, piece, side, bg, gradient);
        }
    }

    public static void addBlankLabelRow(Container c, int initialSquareColor) {
        for (int ii = 0; ii < 8; ii++) {
            JLabel l = new JLabel();
            Color bg = (initialSquareColor++ % 2 == BLACK
                    ? Color.BLACK : Color.WHITE);
            l.setBackground(bg);
            l.setOpaque(true);
            c.add(l);
        }
    }

    public static void main(String[] args) {
        final int[] pawnRow = new int[]{
            PAWN, PAWN, PAWN, PAWN, PAWN, PAWN, PAWN, PAWN
        };
        Runnable r = new Runnable() {

            @Override
            public void run() {
                int gradient = JOptionPane.showConfirmDialog(
                        null, "Use gradient fille color?");
                boolean gradientFill = gradient == JOptionPane.OK_OPTION;
                JPanel gui = new JPanel(new GridLayout(0, 8, 0, 0));
                gui.setBorder(new BevelBorder(
                        BevelBorder.LOWERED,
                        Color.GRAY.brighter(), Color.GRAY,
                        Color.GRAY.darker(), Color.GRAY));
                // set up a chess board
                addPiecesToContainer(gui, WHITE, BLACK, order, gradientFill);
                addPiecesToContainer(gui, BLACK, BLACK, pawnRow, gradientFill);

                addBlankLabelRow(gui, WHITE);
                addBlankLabelRow(gui, BLACK);
                addBlankLabelRow(gui, WHITE);
                addBlankLabelRow(gui, BLACK);

                addPiecesToContainer(gui, WHITE, WHITE, pawnRow, gradientFill);
                addPiecesToContainer(gui, BLACK, WHITE, order, gradientFill);

                JOptionPane.showMessageDialog(
                        null,
                        gui,
                        "Chessboard",
                        JOptionPane.INFORMATION_MESSAGE);

                JPanel tileSet = new JPanel(new GridLayout(0, 6, 0, 0));
                tileSet.setOpaque(false);
                int[] tileSetOrder = new int[]{
                    KING, QUEEN, CASTLE, KNIGHT, BISHOP, PAWN
                };
                addPiecesToContainer(
                        tileSet,
                        new Color(0, 0, 0, 0),
                        BLACK,
                        tileSetOrder, 
                        gradientFill);
                addPiecesToContainer(
                        tileSet,
                        new Color(0, 0, 0, 0),
                        WHITE,
                        tileSetOrder, 
                        gradientFill);
                int result = JOptionPane.showConfirmDialog(
                        null,
                        tileSet,
                        "Save this tileset?",
                        JOptionPane.OK_CANCEL_OPTION,
                        JOptionPane.QUESTION_MESSAGE);
                if (result == JOptionPane.OK_OPTION) {
                    BufferedImage bi = new BufferedImage(
                            tileSet.getWidth(),
                            tileSet.getHeight(),
                            BufferedImage.TYPE_INT_ARGB);
                    Graphics g = bi.createGraphics();
                    tileSet.paint(g);
                    g.dispose();

                    String gradientString = gradientFill ? "gradient" : "solid";
                    File f = new File(
                            "chess-pieces-tileset-" + gradientString + ".png");
                    try {
                        ImageIO.write(bi, "png", f);
                        Desktop.getDesktop().open(f);
                    } catch (IOException ex) {
                        Logger.getLogger(
                                ChessBoard.class.getName()).log(
                                Level.SEVERE, null, ex);
                    }
                }
            }
        };
        SwingUtilities.invokeLater(r);
    }
}

另请参见

  • 从代码中开发出来,如这个答案所示。GlyphVector


答案 2

我看到的问题是,字形被设计成很容易区分传统的黑白棋子。还要注意字体设计的变化。您可以使用HSB色彩空间创建色调主题的片段,以保留黑色和白色的区别。绿色和青色如下图所示。

HSB image

附录:作为参考,这是@Andrew字形形状方法的Mac OS X屏幕截图。请注意,在缩放图像时,@Andrew使用的好处。RenderingHints

shape image

import java.awt.Color;
import java.awt.Container;
import java.awt.Font;
import java.awt.GridLayout;
import java.util.Random;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

/** @see https://stackoverflow.com/a/18691662/230513 */

class ChessBoard {

    static Font font = new Font("Sans-Serif", Font.PLAIN, 64);
    static Random rnd = new Random();

    public static void addUnicodeCharToContainer(String s, Container c) {
        JLabel l = new JLabel(s);
        l.setFont(font);
        l.setOpaque(true);
        c.add(l);
    }

    public static void addWhite(String s, Container c, Float h) {
        JLabel l = new JLabel(s);
        l.setFont(font);
        l.setOpaque(true);
        l.setForeground(Color.getHSBColor(h, 1, 1));
        l.setBackground(Color.getHSBColor(h, 3 / 8f, 5 / 8f));
        c.add(l);
    }

    public static void addBlack(String s, Container c, Float h) {
        JLabel l = new JLabel(s);
        l.setFont(font);
        l.setOpaque(true);
        l.setForeground(Color.getHSBColor(h, 5 / 8f, 3 / 8f));
        l.setBackground(Color.getHSBColor(h, 7 / 8f, 7 / 8f));
        c.add(l);
    }

    public static void main(String[] args) {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                JPanel gui = new JPanel(new GridLayout(0, 6, 4, 4));
                String[] white = {
                    "\u2654", "\u2655", "\u2656", "\u2657", "\u2658", "\u2659"
                };
                String[] black = {
                    "\u265A", "\u265B", "\u265C", "\u265D", "\u265E", "\u265F"
                };
                for (String piece : white) {
                    addUnicodeCharToContainer(piece, gui);
                }
                for (String piece : white) {
                    addWhite(piece, gui, 2 / 6f);
                }
                for (String piece : black) {
                    addUnicodeCharToContainer(piece, gui);
                }
                for (String piece : black) {
                    addBlack(piece, gui, 3 / 6f);
                }
                JOptionPane.showMessageDialog(null, gui);
            }
        };
        SwingUtilities.invokeLater(r);
    }
}