‘Compila’ i caratteri Unicode nelle etichette

Come “riempire” i caratteri Unicode nelle etichette in Swing?

Sto cercando di creare un’interfaccia utente per il programma di scacchi che ho programmato di recente (con pezzi degli scacchi come quelli visti sopra). In esso sto usando caratteri unicode per rappresentare i miei pezzi degli scacchi ( \u2654 attraverso \u265F ).

Il problema è il seguente:

Quando ho impostato lo sfondo del mio pezzo di scacchi JLabel su qualcosa di simile al bianco, l’intera etichetta è piena (nel mio caso si tratta di un quadrato bianco 50 * 50px con il carattere in alto). Questo porta ai miei pezzi che sembrano tessere invece delle loro foto.

Quando ho reso l’etichetta opaca, ho appena ottenuto una versione di cookie cutter del mio pezzo degli scacchi, non uno con i suoi interni pieni. PER ESEMPIO

Risultato attuale

C’è un modo per riempire solo il personaggio?

Se no, credo che farò un foglio sprite ma mi piace perché posso usare i metodi toString() dei pezzi degli scacchi per le etichette.

Codice

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

Pezzi degli scacchi

Le due righe sono generate attraverso la stregoneria di Java-2D. Il trucco è:

  • Ignora i pezzi degli scacchi “neri” sulla base del fatto che il nostro colore proviene effettivamente dagli “spazi contenuti dalla forma”. Quelli sono più grandi nei pezzi degli scacchi bianchi.
  • Crea un GlyphVector che rappresenta la forma del personaggio. Questo è importante per ulteriori operazioni in Java-2D.
  • Crea un Rectangle della dimensione dell’immagine.
  • subtract() la forma del carattere dalla forma dell’immagine.
  • Rompi quella forma alterata in regioni.
  • Riempi le regioni con il colore di sfondo, ma salta la singola regione che inizia da 0.0.0.0 (che rappresenta la regione più esterna di cui abbiamo bisogno trasparente).
  • Infine, riempi la forma del personaggio stesso usando il colore del contorno.

Codice

 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.createTransformsdShape(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); } } 

Scacchiera

Questo è quello che potrebbe sembrare una scacchiera (22,81 Kb).

Scacchiera senza ornamenti

Set di sprite

Set di sprite di pezzi degli scacchi (64x64 pixel) resi da caratteri Unicode - come PNG con BG trasparente. Ognuno ha 6 colonne per i pezzi x 2 righe per gli avversari (dimensione totale 384x128 pixel).

Pezzi degli scacchi con riempimento solido (bronzo / peltro) (11,64Kb).

Set di tessere pezzo degli scacchi

Pezzi degli scacchi con riempimento sfumato (oro / argento) (13.61Kb).

Set di tessere pezzo degli scacchi con colore di riempimento sfumato

Pezzi degli scacchi con riempimento sfumato (ciano / magenta più scuro) (13.44Kb).

Set di tessere pezzo degli scacchi con colore di riempimento sfumato

Codice per scacchiera e set di 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.createTransformsdShape(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); } } 

Guarda anche

  • Sviluppato fuori dal codice GlyphVector come visto in questa risposta .

  • Risultato in UGlys - Glifi Unicode su GitHub.

Il problema che vedo è che i glifi sono stati progettati per distinguere facilmente i tradizionali pezzi degli scacchi in bianco e nero. Nota anche la variazione nel design dei font. Potresti essere in grado di creare pezzi a tema che preservano la distinzione in bianco e nero utilizzando lo spazio colore HSB . Verde e ciano sono raffigurati di seguito.

Immagine HSB

Addendum: per riferimento, ecco uno screenshot di Mac OS X dell’approccio alla forma del glifo di Andrew. Si noti il ​​vantaggio dell’uso di RenderingHints da parte di Andrew mentre l’immagine viene ridimensionata.

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

Alla fine, ho trovato la realizzazione di uno sprite per essere il modo più facile e semplice per risolvere il problema. Ogni pezzo ora corrisponde a un grafico all’interno dello sprite invece di un carattere / glifo. Per questo motivo, i pezzi non possono essere ridimensionati come bene ma questo non è il più grande affare.

L’idea di Andrew Thompson con GlyphVector sembrava promettente, ma la questione della separazione dello spazio bianco interno dallo spazio bianco esterno rimane difficile.

Un’idea (inefficiente) che ho ancora è quella di creare una tonnellata di glifi di pezzi di scacchi a partire da una dimensione del carattere molto piccola e con un colore del bianco di cui sopra:

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

quindi aggiungi un ultimo pezzo di scacchi con un primo piano nero:

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

Si noti che non l'ho provato.