Package com.wbknez.ktour.ui

Source Code of com.wbknez.ktour.ui.UnicodePieceFactory$White

/**
* Copyright 2014 Will Knez
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wbknez.ktour.ui;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;

/**
* A factory that can produce an image of a chess piece by rendering a Unicode
* character onto a pixel buffer.
*
* <p>
* This is, more or less, a cleaned up version of Andrew Thompson's answer from
* the following <i>StackOverflow</i> question:
*  <a href="https://stackoverflow.com/questions/18686199/fill-unicode-characters-in-labels">Java: Fill Unicode Characters in Labels</a>
* </p>
*/
public class UnicodePieceFactory {

    /**
     * Represents a chess piece as a Unicode string.
     */
    private static final class Piece {

        /** The Unicode representation of this chess piece. */
        private final String unicodeString;

        /**
         * Constructor.
         *
         * @param unicodeString
         *        The Unicode representation to use.
         * @throws NullPointerException
         *         If <code>unicodeString</code> is <code>null</code>.
         */
        public Piece(final String unicodeString) {
            if(unicodeString == null) {
                throw new NullPointerException();
            }

            this.unicodeString = unicodeString;
        }

        /**
         * Returns the Unicode representation of this chess piece.
         *
         * @return The Unicode representation used.
         */
        public String getUnicodeRepresentation() {
            return this.unicodeString;
        }

        @Override
        public String toString() {
            return this.getUnicodeRepresentation();
        }
    }

    /**
     * Represents the class of chess pieces that are White.
     */
    public static final class White {
        /** A white king. */
        public static final Piece King      = new Piece("\u2654");
        /** A white queen. */
        public static final Piece Queen     = new Piece("\u2655");
        /** A white rook. */
        public static final Piece Rook      = new Piece("\u2656");
        /** A white bishop. */
        public static final Piece Bishop    = new Piece("\u2657");
        /** A white knight. */
        public static final Piece Knight    = new Piece("\u2658");
        /** A white pawn. */
        public static final Piece Pawn      = new Piece("\u2659");
    }

    /**
     * Represents the class of chess pieces that are Black.
     */
    public static final class Black {
        /** A black king. */
        public static final Piece King      = new Piece("\u265A");
        /** A black queen. */
        public static final Piece Queen     = new Piece("\u265B");
        /** A black rook. */
        public static final Piece Rook      = new Piece("\u265C");
        /** A black bishop. */
        public static final Piece Bishop    = new Piece("\u265D");
        /** A black pawn. */
        public static final Piece Knight    = new Piece("\u265E");
        /** A black king. */
        public static final Piece Pawn      = new Piece("\u265F");
    }

    /**
     * Default rendering hints.
     *
     * <p>
     * For use when custom rendering hints are not desired.
     * </p>
     */
    public static final RenderingHints DefaultRenderingHints =
            new RenderingHints(null);

    static {
        DefaultRenderingHints.put(RenderingHints.KEY_ANTIALIASING,
                                    RenderingHints.VALUE_ANTIALIAS_ON);
        DefaultRenderingHints.put(RenderingHints.KEY_DITHERING,
                                    RenderingHints.VALUE_DITHER_ENABLE);
        DefaultRenderingHints.put(RenderingHints.KEY_ALPHA_INTERPOLATION,
                              RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
    }

    /**
     * Renders an image of the specified chess piece using Unicode symbols.
     *
     * @param piece
     *        The chess piece to create.
     * @param outlineColor
     *        The color to use for the border, or outline, of the piece.
     * @param fillColor
     *        The color to use for the "body", or fill, of the piece.
     * @param font
     *        The font to use for creating the piece. Please note, this font
     *        <b>must</b> support the Unicode character(s) required by
     *        <code>piece</code>.
     * @param hints
     *        The rendering hints to use when drawing the piece.
     * @param stroke
     *        The stroke to use for the border and outline of the piece; this
     *        allows the use of dashed or other non-solid lines.
     * @return The specified piece as an image.
     * @throws NullPointerException
     *         If <code>piece</code>, <code>outlineColor</code>,
     *         <code>fillColor</code>, <code>font</code>, <code>hints</code>, or
     *         <code>stroke</code> are <code>null</code>.
     */
    public BufferedImage createPiece(final Piece piece,
            final Color outlineColor, final Color fillColor, final Font font,
            final RenderingHints hints, final Stroke stroke) {
        if (piece == null) {
            throw new NullPointerException();
        }

        if (font == null) {
            throw new NullPointerException();
        }

        if (hints == null) {
            throw new NullPointerException();
        }

        if (outlineColor == null) {
            throw new NullPointerException();
        }

        if (fillColor == null) {
            throw new NullPointerException();
        }

        if (stroke == null) {
            throw new NullPointerException();
        }

        // Set up the rendering canvas.
        final int fontSize = font.getSize();
        final BufferedImage resultantImage =
                new BufferedImage(fontSize, fontSize,
                        BufferedImage.TYPE_INT_ARGB);
        final Graphics2D graphics2D = (Graphics2D) resultantImage.getGraphics();

        // Set the hints (if any) for font rendering.
        graphics2D.setRenderingHints(hints);

        final FontRenderContext fontContext = graphics2D.getFontRenderContext();
        final GlyphVector glyphVector =
                font.createGlyphVector(fontContext,
                        piece.getUnicodeRepresentation());

        // Obtain a glyph outline from the font.
        final Rectangle glyphBounds =
                this.convertToIntegerBounds(glyphVector.getVisualBounds());
        final Shape glyphShape = glyphVector.getOutline();

        // Create the "real" glyph, centered on a new, transparent image.
        final Shape centeredGlyphShape =
                this.getCenteredShapeByBounds(fontSize, glyphBounds,
                        glyphShape);
        final Area centeredGlyphArea = new Area(centeredGlyphShape);
        final Area pieceShape =
                this.getCroppedShapeByBounds(fontSize, centeredGlyphShape);

        // Split into pieces to be redrawn (via an outline).
        final ArrayList<Shape> splitRegions =
                this.splitShapeIntoRegionsByBounds(pieceShape);

        // Set the rendering options.
        graphics2D.setStroke(stroke);
        graphics2D.setColor(fillColor);

        // Draw.
        for (final Shape subRegion : splitRegions) {
            final Rectangle subBounds = subRegion.getBounds();

            if (Double.compare(subBounds.getX(), 0.0) != 0
                    || Double.compare(subBounds.getY(), 0.0) != 0) {
                graphics2D.fill(subRegion);
            }
        }

        // Fill in the outline.
        graphics2D.setColor(outlineColor);
        graphics2D.fill(centeredGlyphArea);

        // Clean up.
        graphics2D.dispose();
        return resultantImage;
    }

    /**
     * Converts the specified rectangle with double coordinates to a rectangle
     * that uses integer coordinates instead.
     *
     * @param rect
     *        The rectangle (that uses double coordinates) to convert.
     * @return A rectangle whose coordinates are the integer equivalents of the
     *         specified (parameter) rectangle.
     * @throws NullPointerException
     *         If <code>rect</code> is <code>null</code>.
     */
    private Rectangle convertToIntegerBounds(final Rectangle2D rect) {
        if (rect == null) {
            throw new NullPointerException();
        }

        final Rectangle rectangle = new Rectangle();

        rectangle.x = (int) rect.getX();
        rectangle.y = (int) rect.getY();
        rectangle.width = (int) rect.getWidth();
        rectangle.height = (int) rect.getHeight();

        return rectangle;
    }

    /**
     * Crops the specified shape to the space occupied by the specified font
     * size.
     *
     * @param fontSize
     *        The desired font size to crop to.
     * @param origin
     *        The original shape to use (and crop).
     * @return The specified shape reformatted to fit the specified font.
     * @throws NullPointerException
     *         If <code>origin</code> is <code>null</code>.
     */
    private Area getCroppedShapeByBounds(final int fontSize,
            final Shape origin) {
        if (origin == null) {
            throw new NullPointerException();
        }

        final Area croppedArea =
                new Area(new Rectangle2D.Double(0, 0, fontSize, fontSize));
        final Area originArea = new Area(origin);

        // Remove the original image from the cropped version, if necessary.
        croppedArea.subtract(originArea);

        return croppedArea;
    }

    /**
     * Centers the specified glyph in the space occupied by the specified font
     * size.
     *
     * <p>
     * This prevents the glyph from being rendered askew because of any
     * discrepancy between the desired font size and the size of the base glyph.
     * </p>
     *
     * @param fontSize
     *        The size of the font to use.
     * @param originBounds
     *        The bounds of the original shape.
     * @param origin
     *        The original shape to use.
     * @return A new shape that is "centered" on the region of space denoted by
     *         the font size.
     */
    private Shape getCenteredShapeByBounds(final int fontSize,
            final Rectangle originBounds, final Shape origin) {
        if (originBounds == null) {
            throw new NullPointerException();
        }

        if (origin == null) {
            throw new NullPointerException();
        }

        // Get the empty space between the glyph and the font space.
        final int xMargin = fontSize - (int) originBounds.getWidth();
        final int yMargin = fontSize - (int) originBounds.getHeight();

        // Create a transform to center the original shape in the new space.
        final AffineTransform transform =
                AffineTransform.getTranslateInstance((xMargin / 2)
                        + (-1 * (int) originBounds.getX()), (yMargin / 2)
                        + (-1 * (int) originBounds.getY()));

        return transform.createTransformedShape(origin);
    }

    /**
     * Split the specified shape into a list of sub-shapes, as defined by any
     * geometric paths it may contain within.
     *
     * @param origin
     *        The shape to iterate over, path-wise.
     * @return A list of shapes that denotes a path of sub-regions within the
     * original shape.
     * @throws NullPointerException
     *         If <code>origin</code> is <code>null</code>.
     */
    private ArrayList<Shape> splitShapeIntoRegionsByBounds(final Shape origin) {
        if (origin == null) {
            throw new NullPointerException();
        }

        final ArrayList<Shape> splitRegions = new ArrayList<>();
        final PathIterator pathIterator = origin.getPathIterator(null);
        final GeneralPath generalPath = new GeneralPath();

        while (!pathIterator.isDone()) {
            final double[] coordinates = new double[6];
            final int segmentType = pathIterator.currentSegment(coordinates);
            final int windingRule = pathIterator.getWindingRule();

            generalPath.setWindingRule(windingRule);

            switch (segmentType) {
                case PathIterator.SEG_MOVETO:
                    generalPath.reset();

                    generalPath.setWindingRule(windingRule);
                    generalPath.moveTo(coordinates[0], coordinates[1]);

                    break;
                case PathIterator.SEG_LINETO:
                    generalPath.lineTo(coordinates[0], coordinates[1]);
                    break;
                case PathIterator.SEG_QUADTO:
                    generalPath.quadTo(coordinates[0], coordinates[1],
                            coordinates[2], coordinates[3]);
                    break;
                case PathIterator.SEG_CUBICTO:
                    generalPath.curveTo(coordinates[0], coordinates[1],
                            coordinates[2], coordinates[3], coordinates[4],
                            coordinates[5]);
                    break;
                case PathIterator.SEG_CLOSE:
                    generalPath.closePath();

                    final Area pathArea = new Area(generalPath);
                    splitRegions.add(pathArea);

                    break;
                default:
                    throw new RuntimeException("Unknown segment type.");
            }

            pathIterator.next();
        }

        return splitRegions;
    }
}
TOP

Related Classes of com.wbknez.ktour.ui.UnicodePieceFactory$White

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.