de.erichseifert.vectorgraphics2d.util.GraphicsUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of VectorGraphics2D Show documentation
Show all versions of VectorGraphics2D Show documentation
A library for adding vector export to Java(R) Graphics2D.
The newest version!
/*
* VectorGraphics2D: Vector export for Java(R) Graphics2D
*
* (C) Copyright 2010-2017 Erich Seifert ,
* Michael Seifert
*
* This file is part of VectorGraphics2D.
*
* VectorGraphics2D is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VectorGraphics2D is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with VectorGraphics2D. If not, see .
*/
package de.erichseifert.vectorgraphics2d.util;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.PixelGrabber;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Set;
import javax.swing.ImageIcon;
/**
* Abstract class that contains utility functions for working with graphics.
* For example, this includes font handling.
*/
public abstract class GraphicsUtils {
private static final FontRenderContext FONT_RENDER_CONTEXT =
new FontRenderContext(null, false, true);
private static final String FONT_TEST_STRING =
"Falsches Üben von Xylophonmusik quält jeden größeren Zwerg";
/**
* Default constructor that prevents creation of class.
*/
GraphicsUtils() {
throw new UnsupportedOperationException();
}
/**
* This method returns {@code true} if the specified image has the
* possibility to store transparent pixels.
* Inspired by http://www.exampledepot.com/egs/java.awt.image/HasAlpha.html
* @param image Image that should be checked for alpha channel.
* @return {@code true} if the specified image can have transparent pixels,
* {@code false} otherwise
*/
public static boolean hasAlpha(Image image) {
ColorModel cm;
// If buffered image, the color model is readily available
if (image instanceof BufferedImage) {
BufferedImage bimage = (BufferedImage) image;
cm = bimage.getColorModel();
} else {
// Use a pixel grabber to retrieve the image's color model;
// grabbing a single pixel is usually sufficient
PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false);
try {
pg.grabPixels();
} catch (InterruptedException e) {
return false;
}
// Get the image's color model
cm = pg.getColorModel();
}
return cm.hasAlpha();
}
/**
* This method returns {@code true} if the specified image has at least one
* pixel that is not fully opaque.
* @param image Image that should be checked for non-opaque pixels.
* @return {@code true} if the specified image has transparent pixels,
* {@code false} otherwise
*/
public static boolean usesAlpha(Image image) {
if (image == null) {
return false;
}
BufferedImage bimage = toBufferedImage(image);
Raster alphaRaster = bimage.getAlphaRaster();
if (alphaRaster == null) {
return false;
}
DataBuffer dataBuffer = alphaRaster.getDataBuffer();
for (int i = 0; i < dataBuffer.getSize(); i++) {
int alpha = dataBuffer.getElem(i);
if (alpha < 255) {
return true;
}
}
return false;
}
/**
* Converts a rendered image instance to a {@code BufferedImage}.
* @param image Rendered image that should be converted.
* @return a buffered image containing the image pixels, or the original
* instance if the image already was of type {@code BufferedImage}.
*/
public static BufferedImage toBufferedImage(RenderedImage image) {
if (image instanceof BufferedImage) {
return (BufferedImage) image;
}
ColorModel cm = image.getColorModel();
WritableRaster raster = cm.createCompatibleWritableRaster(
image.getWidth(), image.getHeight());
boolean isRasterPremultiplied = cm.isAlphaPremultiplied();
Hashtable properties = null;
if (image.getPropertyNames() != null) {
properties = new Hashtable<>();
for (String key : image.getPropertyNames()) {
properties.put(key, image.getProperty(key));
}
}
BufferedImage bimage = new BufferedImage(cm, raster,
isRasterPremultiplied, properties);
image.copyData(raster);
return bimage;
}
/**
* This method returns a buffered image with the contents of an image.
* Taken from http://www.exampledepot.com/egs/java.awt.image/Image2Buf.html
* @param image Image to be converted
* @return a buffered image with the contents of the specified image
*/
public static BufferedImage toBufferedImage(Image image) {
if (image instanceof BufferedImage) {
return (BufferedImage) image;
}
// This code ensures that all the pixels in the image are loaded
image = new ImageIcon(image).getImage();
// Determine if the image has transparent pixels
boolean hasAlpha = hasAlpha(image);
// Create a buffered image with a format that's compatible with the
// screen
BufferedImage bimage;
GraphicsEnvironment ge = GraphicsEnvironment
.getLocalGraphicsEnvironment();
try {
// Determine the type of transparency of the new buffered image
int transparency = Transparency.OPAQUE;
if (hasAlpha) {
transparency = Transparency.TRANSLUCENT;
}
// Create the buffered image
GraphicsDevice gs = ge.getDefaultScreenDevice();
GraphicsConfiguration gc = gs.getDefaultConfiguration();
bimage = gc.createCompatibleImage(
image.getWidth(null), image.getHeight(null), transparency);
} catch (HeadlessException e) {
// The system does not have a screen
bimage = null;
}
if (bimage == null) {
// Create a buffered image using the default color model
int type = BufferedImage.TYPE_INT_RGB;
if (hasAlpha) {
type = BufferedImage.TYPE_INT_ARGB;
}
bimage = new BufferedImage(
image.getWidth(null), image.getHeight(null), type);
}
// Copy image to buffered image
Graphics g = bimage.createGraphics();
// Paint the image onto the buffered image
g.drawImage(image, 0, 0, null);
g.dispose();
return bimage;
}
public static Shape clone(Shape shape) {
if (shape == null) {
return null;
}
Shape clone;
if (shape instanceof Line2D) {
clone = (shape instanceof Line2D.Float) ?
new Line2D.Float() : new Line2D.Double();
((Line2D) clone).setLine((Line2D) shape);
} else if (shape instanceof Rectangle) {
clone = new Rectangle((Rectangle) shape);
} else if (shape instanceof Rectangle2D) {
clone = (shape instanceof Rectangle2D.Float) ?
new Rectangle2D.Float() : new Rectangle2D.Double();
((Rectangle2D) clone).setRect((Rectangle2D) shape);
} else if (shape instanceof RoundRectangle2D) {
clone = (shape instanceof RoundRectangle2D.Float) ?
new RoundRectangle2D.Float() : new RoundRectangle2D.Double();
((RoundRectangle2D) clone).setRoundRect((RoundRectangle2D) shape);
} else if (shape instanceof Ellipse2D) {
clone = (shape instanceof Ellipse2D.Float) ?
new Ellipse2D.Float() : new Ellipse2D.Double();
((Ellipse2D) clone).setFrame(((Ellipse2D) shape).getFrame());
} else if (shape instanceof Arc2D) {
clone = (shape instanceof Arc2D.Float) ?
new Arc2D.Float() : new Arc2D.Double();
((Arc2D) clone).setArc((Arc2D) shape);
} else if (shape instanceof Polygon) {
Polygon p = (Polygon) shape;
clone = new Polygon(p.xpoints, p.ypoints, p.npoints);
} else if (shape instanceof CubicCurve2D) {
clone = (shape instanceof CubicCurve2D.Float) ?
new CubicCurve2D.Float() : new CubicCurve2D.Double();
((CubicCurve2D) clone).setCurve((CubicCurve2D) shape);
} else if (shape instanceof QuadCurve2D) {
clone = (shape instanceof QuadCurve2D.Float) ?
new QuadCurve2D.Float() : new QuadCurve2D.Double();
((QuadCurve2D) clone).setCurve((QuadCurve2D) shape);
} else if (shape instanceof Path2D.Float) {
clone = new Path2D.Float(shape);
} else {
clone = new Path2D.Double(shape);
}
return clone;
}
private static class FontExpressivenessComparator implements Comparator {
private static final int[] STYLES = {
Font.PLAIN, Font.ITALIC, Font.BOLD, Font.BOLD | Font.ITALIC
};
public int compare(Font font1, Font font2) {
if (font1 == font2) {
return 0;
}
Set variantNames1 = new HashSet<>();
Set variantNames2 = new HashSet<>();
for (int style : STYLES) {
variantNames1.add(font1.deriveFont(style).getPSName());
variantNames2.add(font2.deriveFont(style).getPSName());
}
if (variantNames1.size() < variantNames2.size()) {
return 1;
} else if (variantNames1.size() > variantNames2.size()) {
return -1;
}
return font1.getName().compareTo(font2.getName());
}
}
private static final FontExpressivenessComparator FONT_EXPRESSIVENESS_COMPARATOR =
new FontExpressivenessComparator();
private static boolean isLogicalFontFamily(String family) {
return (Font.DIALOG.equals(family) ||
Font.DIALOG_INPUT.equals(family) ||
Font.SANS_SERIF.equals(family) ||
Font.SERIF.equals(family) ||
Font.MONOSPACED.equals(family));
}
/**
* Try to guess physical font from the properties of a logical font, like
* "Dialog", "Serif", "Monospaced" etc.
* @param logicalFont Logical font object.
* @param testText Text used to determine font properties.
* @return An object of the first matching physical font. The original font
* object is returned if it was a physical font or no font matched.
*/
public static Font getPhysicalFont(Font logicalFont, String testText) {
String logicalFamily = logicalFont.getFamily();
if (!isLogicalFontFamily(logicalFamily)) {
return logicalFont;
}
final TextLayout logicalLayout =
new TextLayout(testText, logicalFont, FONT_RENDER_CONTEXT);
// Create a list of matches sorted by font expressiveness (in descending order)
Queue physicalFonts =
new PriorityQueue<>(1, FONT_EXPRESSIVENESS_COMPARATOR);
Font[] allPhysicalFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
for (Font physicalFont : allPhysicalFonts) {
String physicalFamily = physicalFont.getFamily();
// Skip logical fonts
if (isLogicalFontFamily(physicalFamily)) {
continue;
}
// Derive identical variant of physical font
physicalFont = physicalFont.deriveFont(
logicalFont.getStyle(), logicalFont.getSize2D());
TextLayout physicalLayout =
new TextLayout(testText, physicalFont, FONT_RENDER_CONTEXT);
// Compare various properties of physical and logical font
if (physicalLayout.getBounds().equals(logicalLayout.getBounds()) &&
physicalLayout.getAscent() == logicalLayout.getAscent() &&
physicalLayout.getDescent() == logicalLayout.getDescent() &&
physicalLayout.getLeading() == logicalLayout.getLeading() &&
physicalLayout.getAdvance() == logicalLayout.getAdvance() &&
physicalLayout.getVisibleAdvance() == logicalLayout.getVisibleAdvance()) {
// Store matching font in list
physicalFonts.add(physicalFont);
}
}
// Return a valid font even when no matching font could be found
if (physicalFonts.isEmpty()) {
return logicalFont;
}
return physicalFonts.poll();
}
public static Font getPhysicalFont(Font logicalFont) {
return getPhysicalFont(logicalFont, FONT_TEST_STRING);
}
public static BufferedImage getAlphaImage(BufferedImage image) {
WritableRaster alphaRaster = image.getAlphaRaster();
int width = image.getWidth();
int height = image.getHeight();
ColorModel cm;
WritableRaster raster;
// TODO Handle bitmap masks (work on ImageDataStream is necessary)
/*
if (image.getTransparency() == BufferedImage.BITMASK) {
byte[] arr = {(byte) 0, (byte) 255};
cm = new IndexColorModel(1, 2, arr, arr, arr);
raster = Raster.createPackedRaster(DataBuffer.TYPE_BYTE,
width, height, 1, 1, null);
} else {*/
ColorSpace colorSpace = ColorSpace.getInstance(ColorSpace.CS_GRAY);
int[] bits = {8};
cm = new ComponentColorModel(colorSpace, bits, false, true,
Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
raster = cm.createCompatibleWritableRaster(width, height);
//}
BufferedImage alphaImage = new BufferedImage(cm, raster, false, null);
int[] alphaValues = new int[image.getWidth()*alphaRaster.getNumBands()];
for (int y = 0; y < image.getHeight(); y++) {
alphaRaster.getPixels(0, y, image.getWidth(), 1, alphaValues);
// FIXME Don't force 8-bit alpha channel (see TODO above)
if (image.getTransparency() == BufferedImage.BITMASK) {
for (int i = 0; i < alphaValues.length; i++) {
if (alphaValues[i] > 0) {
alphaValues[i] = 255;
}
}
}
alphaImage.getRaster().setPixels(0, y, image.getWidth(), 1, alphaValues);
}
return alphaImage;
}
public static boolean equals(Shape shapeA, Shape shapeB) {
PathIterator pathAIterator = shapeA.getPathIterator(null);
PathIterator pathBIterator = shapeB.getPathIterator(null);
if (pathAIterator.getWindingRule() != pathBIterator.getWindingRule()) {
return false;
}
double[] pathASegment = new double[6];
double[] pathBSegment = new double[6];
while (!pathAIterator.isDone()) {
int pathASegmentType = pathAIterator.currentSegment(pathASegment);
int pathBSegmentType = pathBIterator.currentSegment(pathBSegment);
if (pathASegmentType != pathBSegmentType) {
return false;
}
for (int segmentIndex = 0; segmentIndex < pathASegment.length; segmentIndex++) {
if (pathASegment[segmentIndex] != pathBSegment[segmentIndex]) {
return false;
}
}
pathAIterator.next();
pathBIterator.next();
}
// When the iterator of shapeA is done and shapeA equals shapeB,
// the iterator of shapeB must also be done
if (!pathBIterator.isDone()) {
return false;
}
return true;
}
}