"Заполнить" символы Юникода в ярлыках

Как "заполнить" символы Unicode в ярлыках в Swing?

21xA8.png

Я пытаюсь сделать пользовательский интерфейс для программы шахмат, которую я недавно запрограммировал (с шахматами, что-то вроде выше). В нем я использую символы юникода для представления моих шахматных фигур (\u2654 через \u265F).

Проблема заключается в следующем:

Когда я устанавливаю фон моей шахматной фигуры JLabel на что-то вроде белого, вся этикетка заполняется (в моем случае это белый квадрат размером 50 * 50 пикселей с символом сверху). Это приводит к тому, что мои кусочки выглядят как плитки, а не только их фотографии.

Когда я устанавливаю ярлык непрозрачным, я просто получаю версию резака для печенья моей шахматной фигуры, а не одну, заполненную ее внутренностями. Например.

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. Хитрость заключается в следующем:

  • Игнорируйте "черные" шахматные фигуры на том основании, что наш цвет действительно исходит из "пространств, содержащихся в форме". Они больше в белых шахматных фигурах.
  • Создайте GlyphVector, который представляет форму символа. Это важно для дальнейших операций в Java-2D.
  • Создайте 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(
                -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);
    }
}

Шахматная доска

Это то, что может выглядеть как Chess Board (22.81 Kb).

Chess Board Unadorned

Набор спрайтов

Спрайтовые наборы шахматных фигур (64x64 пикселя), отображаемые из символов Юникода - как PNG с прозрачным BG. Каждый из них имеет 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);
    }
}

См. также

ET1EH.png

zwezW.png

Ответ 2

Проблема, которую я вижу, заключается в том, что глифы были разработаны, чтобы легко отличить традиционные черно-белые шахматные фигуры. Обратите внимание также на изменение дизайна шрифтов. Вы можете создавать фрагменты с оттенком, которые сохраняют черное и белое различие, используя цветовое пространство HSB. Зеленые и голубые изображены ниже.

HSB image

Приложение: для справки, здесь снимок экрана Mac OS X в формате @Andrew для приближения к символу. Обратите внимание на преимущество использования @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);
    }
}

Ответ 3

В конце концов, я нашел, что создание спрайтов - это более простой и простой способ решить проблему. Каждый фрагмент теперь соответствует графике внутри спрайта вместо символа/символа. Из-за этого части не могут быть изменены так же красиво, но это не самая большая сделка.

@Идея Андрея Томпсона с 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);

Обратите внимание, что я не тестировал это.