‘Rellenar’ caracteres Unicode en tags

¿Cómo ‘llenar’ caracteres Unicode en tags en Swing?

Estoy tratando de hacer una interfaz de usuario para el progtwig de ajedrez que he progtwigdo recientemente (con piezas de ajedrez como las que se ven arriba). En él estoy usando caracteres Unicode para representar mis piezas de ajedrez ( \u2654 a \u265F ).

El problema es el siguiente:

Cuando establezco el fondo de mi pieza de ajedrez JLabel en blanco, la etiqueta completa se llena (en mi caso, es un cuadrado de 50 * 50px blanco con el personaje en la parte superior). Esto lleva a que mis piezas se vean como baldosas en lugar de solo sus imágenes.

Cuando configuro la etiqueta como opaca, obtengo una versión cortadora de cookies de mi pieza de ajedrez, no una con sus entrañas llenas. P.EJ

Resultado actual

¿Hay alguna manera de llenar solo al personaje?

Si no, creo que haré una hoja de sprites, pero me gusta porque puedo usar los métodos de las toString() ajedrez para las tags.

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

Piezas de ajedrez

Las dos filas se generan a través de la hechicería de Java-2D. El truco es:

  • Ignora las piezas de ajedrez ‘negras’ sobre la base de que nuestro color proviene de ‘los espacios que contiene la forma’. Esos son más grandes en las piezas de ajedrez blancas.
  • Crea un GlyphVector que represente la forma del personaje. Esto es importante para otras operaciones en Java-2D.
  • Crea un Rectangle del tamaño de la imagen.
  • subtract() la forma del personaje de la forma de la imagen.
  • Divide esa forma alterada en regiones.
  • Complete las regiones con el color de fondo, pero omita la región única que comienza en 0.0,0.0 (que representa la región más externa que necesitamos transparente).
  • Finalmente, completa la forma del personaje usando el color de 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); } } 

Tablero de ajedrez

Esto es lo que podría parecer un Tablero de ajedrez (22.81 Kb).

Tablero de ajedrez sin adornos

Sprite establece

Conjuntos de Sprite de piezas de ajedrez (64x64 píxeles) representados en caracteres Unicode, como PNG con BG transparente. Cada uno tiene 6 columnas para las piezas x 2 filas para los oponentes (tamaño total 384x128 píxeles).

Piezas de ajedrez con relleno sólido (bronce / peltre) (11.64Kb).

Juego de fichas de ajedrez

Piezas de ajedrez con relleno de degradado (oro / plata) (13.61Kb).

Juego de fichas de piezas de ajedrez con color de relleno degradado

Piezas de ajedrez con relleno de degradado (cian / magenta más oscuro) (13.44Kb).

Juego de fichas de piezas de ajedrez con color de relleno degradado

Código para tablero de ajedrez y conjunto de sprites

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

Ver también

  • Desarrollado a partir del código GlyphVector como se ve en esta respuesta .

  • Resulto en UGlys - Unicode Glyphs en GitHub.

El problema que veo es que los glifos se diseñaron para distinguir fácilmente las piezas de ajedrez tradicionales en blanco y negro. Tenga en cuenta también la variación en el diseño de fuente. Es posible que pueda crear piezas con temas de tono que conserven la distinción en blanco y negro utilizando el espacio de color HSB . El verde y el cian se muestran a continuación.

Imagen HSB

Adición: como referencia, aquí hay una captura de pantalla de Mac OS X del enfoque de forma de glifo de @ Andrew. Tenga en cuenta el beneficio del uso que @ Andrew hace de RenderingHints medida que la imagen se escala.

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

Al final, descubrí que hacer una hoja de sprites es la forma más fácil y simple de resolver el problema. Cada pieza ahora corresponde a un gráfico dentro de la hoja de sprites en lugar de un carácter / glifo. Debido a esto, las piezas no se pueden redimensionar tan bien, pero ese no es el mejor negocio.

La idea de @Andrew Thompson con el GlyphVector parecía prometedora, pero la cuestión de separar el espacio blanco interno del espacio blanco exterior sigue siendo difícil.

Una idea (ineficaz) que todavía tengo es hacer una tonelada de glifos de piezas de ajedrez a partir de un tamaño de letra muy pequeño y con un color de fondo blanco:

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

luego agrega una última pieza de ajedrez con un primer plano negro:

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

Tenga en cuenta que no he probado esto.