在标签中填充Unicode字符
如何在Swing中“填充”标签中的Unicode字符?
我试图为最近编程的国际象棋程序制作一个用户界面(用上面看到的象棋棋子)。 在这里我使用Unicode字符来表示我的棋子( \u2654
到\u265F
)。
问题如下:
当我将我的棋子JLabel
的背景设置为白色时,整个标签都被填充了(在我的情况下,它是一个50 * 50px的白色正方形,正方形)。 这导致我的作品看起来像瓷砖而不是他们的照片。
当我把标签设置为不透明的时候,我只是得到我的棋子的cookies版本,而不是其内部填充的一个。 例如
有没有办法只填写字符?
如果没有,我想我会做一个精灵表,但我喜欢这个,因为我可以使用棋子的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); } }
这两行是通过Java-2D的魔法生成的。 诀窍是:
- 忽略“黑色”棋子的基础是我们的颜色实际上是来自“形状所包含的空间”。 那些白色的棋子比较大。
- 创build一个表示字符形状的
GlyphVector
。 这对Java-2D中的进一步操作非常重要。 - 创build一个
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("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( -rx + (spaceX / 2), -ry + (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)。
雪碧套
从Unicode字符渲染的棋子(64×64像素)的雪碧套 – 作为一个PNG透明BG。 对于对手来说,每个棋子都有6列x 2行(总大小为384×128像素)。
象棋棋子(青铜/锡) (11.64Kb)。
棋子与渐变填充(金/银) (13.61Kb)。
具有渐变填充的棋子(深青色/洋红色) (13.44Kb)。
国际象棋棋盘和雪碧组的代码
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( -rx + (spaceX / 2), -ry + (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
代码开发。
- UGlys -在GitHub的Unicode字形导致 。
我看到的问题是,字形的devise很容易区分传统的黑白棋子。 还要注意字体devise的变化。 您可以使用HSB色彩空间创build以色调为主题的部分,以保留黑白区分。 绿色和青色如下图所示。
附录:作为参考,这里是@ Andrew的字形形状方法的Mac OS X屏幕截图。 注意@安德鲁使用RenderingHints
的好处,因为图像是缩放的。
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); } }
最后,我发现制作spritesheet是解决这个问题的更简单,更简单的方法。 现在每件作品都对应于spritesheet中的graphics,而不是字符/字形。 正因为如此,这些作品不能很好地resize,但这不是最大的交易。
@安德鲁·汤普森对GlyphVector的想法看起来很有希望,但是将内部白色空间与外部白色空间分离的问题仍然很困难。
我仍然有一个(低效率的)想法,就是从一个非常小的字体开始制作大量的棋子字形,并且具有白色的前景色:
for (int i = 1; i < BOARD_WIDTH/8) { JLabel chessPiece =new JLabel("\u2654"); chessPiece.setForeground(Color.white); chessPiece.setFont(new Font("Sans-Serif", Font.PLAIN, i)); add(chessPiece); }
然后添加一个黑色的前景棋子:
JLabel chessPiece =new JLabel("\u2654"); chessPiece.setForeground(Color.black); chessPiece.setFont(new Font("Sans-Serif", Font.PLAIN, BOARD_WIDTH/8))); add(chessPiece);
请注意,我没有testing过这一点。