‘Preencha’ caracteres Unicode em labels

Como preencher caracteres Unicode em labels no Swing?

Eu estou tentando fazer uma interface de usuário para o programa de xadrez que eu programei recentemente (com peças de xadrez, algo como visto acima). Nele estou usando caracteres unicode para representar minhas peças de xadrez ( \u2654 até \u265F ).

O problema é o seguinte:

Quando coloco o fundo da minha peça de xadrez JLabel em algo parecido com o branco, todo o label é preenchido (no meu caso, é um quadrado de 50 x 50 pixels de branco com o caractere no topo). Isso leva a minhas peças parecendo telhas em vez de apenas suas fotos.

Quando coloco o label em opaco, acabo de receber uma versão do meu pedaço de xadrez, não uma com o seu interior preenchido. POR EXEMPLO

Resultado atual

Existe uma maneira de preencher apenas o personagem?

Se não, acho que vou fazer uma folha de sprite, mas gosto disso porque posso usar os methods toString() das peças de xadrez para os labels.

Código

 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); } } 

Peças de xadrez

As duas linhas são geradas através do feitiço de Java-2D. O truque é:

  • Ignore as peças de xadrez ‘pretas’ com base no fato de que nossa cor está realmente vindo dos ‘espaços contidos pela forma’. Essas são maiores nas peças de xadrez brancas.
  • Crie um GlyphVector que represente a forma do personagem. Isso é importante para outras operações no Java-2D.
  • Crie um Rectangle do tamanho da imagem.
  • subtract() a forma do caractere da forma da imagem.
  • Quebre essa forma alterada em regiões.
  • Preencha as regiões com a cor de fundo, mas pule a região única que começa em 0.0,0.0 (representando a região mais externa que precisamos de transparência).
  • Finalmente, preencha a forma do caractere usando a cor do contorno.

Código

 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 separateShapeIntoRegions(Shape shape) { ArrayList regions = new ArrayList(); 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 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); } } 

Tabuleiro de xadrez

Isto é o que pode parecer como um tabuleiro de xadrez (22,81 Kb).

Tabuleiro de xadrez sem adornos

Conjuntos Sprite

Sprite conjuntos de peças de xadrez (64x64 pixels) processados ​​a partir de caracteres Unicode - como um PNG com BG transparente. Cada um tem 6 colunas para as peças x 2 linhas para os adversários (tamanho total de 384x128 pixels).

Peças de xadrez com preenchimento sólido (bronze / estanho) (11,64 Kb).

Conjunto de peças de xadrez

Peças de xadrez com preenchimento gradiente (ouro / prata) (13,61 Kb).

Conjunto de peças de xadrez com gradiente de cor de preenchimento

Peças de xadrez com preenchimento gradiente (ciano / magenta mais escuro) (13,44 Kb).

Conjunto de peças de xadrez com gradiente de cor de preenchimento

Código para Tabuleiro de Xadrez e Conjunto Sprite

 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 separateShapeIntoRegions(Shape shape) { ArrayList regions = new ArrayList(); 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 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); } } 

Veja também

  • Desenvolvido fora do código GlyphVector como visto nesta resposta .

  • Resultou em UGlys - Unicode Glyphs no GitHub.

O problema que vejo é que os glifos foram projetados para distinguir facilmente peças tradicionais de xadrez preto e branco. Observe também a variação no design da fonte. Você pode criar peças com temas de colors que preservam a distinção entre preto e branco usando o espaço de colors HSB . Verde e ciano estão ilustrados abaixo.

Imagem HSB

Adendo: Para referência, aqui está uma captura de canvas do Mac OS X da abordagem de forma de glifo do @Andrew . Observe o benefício do uso de RenderingHints de @ Andrew à medida que a imagem é dimensionada.

imagem de forma

 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); } } 

No final, descobri que fazer uma spritesheet é a maneira mais fácil e simples de lidar com o problema. Cada peça agora corresponde a um gráfico dentro da spritesheet em vez de um caractere / glifo. Por causa disso, as peças não podem ser redimensionadas tão bem, mas esse não é o maior negócio.

A idéia de Andrew Thompson com o GlyphVector parecia promissora, mas a questão de separar o espaço interno interno do espaço branco externo permanece difícil.

Uma idéia (ineficiente) que eu ainda tenho é fazer uma tonelada de glifos de peças de xadrez a partir de um tamanho de fonte muito pequeno e com uma cor de branco:

 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); } 

em seguida, adicione uma última peça de xadrez com um primeiro plano preto:

 JLabel chessPiece =new JLabel("\u2654"); chessPiece.setForeground(Color.black); chessPiece.setFont(new Font("Sans-Serif", Font.PLAIN, BOARD_WIDTH/8))); add(chessPiece); 

Note que eu não testei isso.