Java tutorial
/* * polymap.org * Copyright (C) 2015 individual contributors as indicated by the @authors tag. * All rights reserved. * * This 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. * * This software 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. */ package org.polymap.rhei.batik.engine.svg; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Toolkit; import java.awt.color.ColorSpace; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.ColorConvertOp; import java.awt.image.FilteredImageSource; import java.awt.image.ImageFilter; import java.awt.image.RGBImageFilter; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.List; import java.util.Optional; import javax.imageio.ImageIO; import org.apache.batik.bridge.BridgeContext; import org.apache.batik.bridge.GVTBuilder; import org.apache.batik.bridge.UserAgentAdapter; import org.apache.batik.dom.svg.SAXSVGDocumentFactory; import org.apache.batik.dom.svg.SVGDOMImplementation; import org.apache.batik.gvt.GraphicsNode; import org.apache.batik.transcoder.TranscoderException; import org.apache.batik.transcoder.TranscoderInput; import org.apache.batik.transcoder.TranscoderOutput; import org.apache.batik.transcoder.TranscodingHints; import org.apache.batik.transcoder.image.ImageTranscoder; import org.apache.batik.transcoder.image.PNGTranscoder; import org.apache.batik.util.SVGConstants; import org.apache.batik.util.XMLResourceDescriptor; import org.apache.commons.io.FilenameUtils; import org.polymap.rhei.batik.engine.svg.ImageConfiguration.ReplaceConfiguration; import org.w3c.dom.svg.SVGDocument; import com.google.common.base.Strings; /** * * @author Joerg Reichert <joerg@mapzone.io> * @author <a href="http://www.polymap.de">Falko Brutigam</a> */ public class Svg2Png { public void transcode(String pngPath, List<File> svgFiles, List<Scale> scales, List<ImageConfiguration> imageConfigurations) throws TranscoderException, IOException { for (File svgFile : svgFiles) { try (FileInputStream fis = new FileInputStream(svgFile)) { byte[] bytes = getImageAsBytes(fis); Bounds bounds = getInitialSVGBounds(svgFile); String svgFileName = FilenameUtils.getBaseName(svgFile.getName()); if (!pngPath.endsWith("/")) { pngPath += "/"; } for (ImageConfiguration imageConfiguration : imageConfigurations) { for (Scale scale : scales) { String baseName = FilenameUtils.getBaseName(svgFileName); String imagePath = pngPath + imageConfiguration.getName() + "/" + scale.name().substring(1) + "/" + baseName + ".png"; File pngFile = new File(imagePath.replace("file:", "")); pngFile.getParentFile().mkdirs(); transcode(pngFile, bytes, bounds, scale, imageConfiguration); } } } } } private byte[] getImageAsBytes(InputStream fis) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); int read = -1; while ((read = fis.read()) != -1) { out.write(read); } out.flush(); out.close(); fis.close(); return out.toByteArray(); } public void transcode(File pngFile, URL svgInput, Scale scale, ImageConfiguration imageConfiguration) throws TranscoderException, IOException { byte[] bytes = getImageAsBytes(svgInput.openStream()); Bounds bounds = getInitialSVGBounds(svgInput.toString(), new ByteArrayInputStream(bytes)); transcode(pngFile, bytes, bounds, scale, imageConfiguration); } public Bounds getInitialSVGBounds(File svgFile) throws IOException { try (FileInputStream fis = new FileInputStream(svgFile)) { String url = "file://" + svgFile.getAbsolutePath(); return getInitialSVGBounds(url, fis); } } public Bounds getInitialSVGBounds(String url, InputStream svgInput) throws IOException { try { String parser = XMLResourceDescriptor.getXMLParserClassName(); SAXSVGDocumentFactory f = new SAXSVGDocumentFactory(parser); SVGDocument doc = (SVGDocument) f.createDocument(url, svgInput); String widthStr = doc.getRootElement().getAttribute("width"); String heightStr = doc.getRootElement().getAttribute("height"); if (!Strings.isNullOrEmpty(widthStr) && !Strings.isNullOrEmpty(heightStr)) { int width = Integer.valueOf(doc.getRootElement().getAttribute("width")); int height = Integer.valueOf(doc.getRootElement().getAttribute("height")); return new Bounds(width, height); } else { BridgeContext ctx = new BridgeContext(new UserAgentAdapter()); GVTBuilder builder = new GVTBuilder(); GraphicsNode gvtRoot = builder.build(ctx, doc); Rectangle2D rc = gvtRoot.getSensitiveBounds(); if (rc == null) { System.err.println(url + " has no bounding box."); return new Bounds(0f, 0f); } else { return new Bounds(Double.valueOf(rc.getWidth()).floatValue(), Double.valueOf(rc.getHeight()).floatValue()); } } } finally { svgInput.close(); } } private void transcode(File pngFile, byte[] imageBytes, Bounds bounds, Scale scale, ImageConfiguration imageConfiguration) throws TranscoderException, IOException { TranscoderInput input = new TranscoderInput(new ByteArrayInputStream(imageBytes)); try { PNGTranscoder transcoder = new PNGTranscoder(); try (FileOutputStream writer = new FileOutputStream(pngFile)) { ByteArrayOutputStream out = new ByteArrayOutputStream(); TranscoderOutput output = new TranscoderOutput(out); Bounds newBounds = new Bounds(scale.getWidth(bounds), scale.getHeight(bounds)); transcoder.setTranscodingHints(createTranscodingHints(newBounds, imageConfiguration.getDepth())); transcoder.transcode(input, output); writer.write(out.toByteArray()); out.close(); writer.flush(); } BufferedImage originalImage = null; try { originalImage = ImageIO.read(pngFile); BufferedImage bufferedImage = transform(originalImage, imageConfiguration); try (FileOutputStream writer = new FileOutputStream(pngFile)) { ByteArrayOutputStream out = new ByteArrayOutputStream(); TranscoderOutput output = new TranscoderOutput(out); transcoder.writeImage(bufferedImage, output); writer.write(out.toByteArray()); out.close(); writer.flush(); } } catch (Exception e) { e.printStackTrace(); } } catch (Exception e) { System.err.println(e.getMessage()); e.printStackTrace(); throw e; } } public static BufferedImage transform(BufferedImage img, ImageConfiguration imageConfiguration) { ColorSpace imgCS = img.getColorModel().getColorSpace(); ColorSpace grayCS = ColorSpace.getInstance(ColorSpace.CS_GRAY); ColorConvertOp cop = new ColorConvertOp(imgCS, grayCS, null); BufferedImage finalThresholdImage = cop.filter(img, null); finalThresholdImage = shiftHue(finalThresholdImage, imageConfiguration); return finalThresholdImage; } private static BufferedImage shiftHue(BufferedImage img, ImageConfiguration imageConfiguration) { int height = img.getHeight(); int width = img.getWidth(); int imageType = 0; if (imageConfiguration.getColorType() == COLOR_TYPE.MONOCHROM) { imageType = BufferedImage.TYPE_BYTE_BINARY; } else if (imageConfiguration.getColorType() == COLOR_TYPE.GRAY) { imageType = BufferedImage.TYPE_BYTE_GRAY; } else if (imageConfiguration.getColorType() == COLOR_TYPE.RGB) { imageType = BufferedImage.TYPE_INT_RGB; } else { imageType = BufferedImage.TYPE_INT_ARGB; } BufferedImage finalThresholdImage = new BufferedImage(width, height, imageType); Float hueDelta = null; Float saturationDelta = null; Float brightnessDelta = null; if (imageConfiguration.getRgb() != null) { hueDelta = degreeToPercent(imageConfiguration.getRgb().getHSB()[0]); saturationDelta = imageConfiguration.getRgb().getHSB()[1]; brightnessDelta = imageConfiguration.getRgb().getHSB()[2]; } else { hueDelta = imageConfiguration.getAdjHue(); saturationDelta = imageConfiguration.getAdjSaturation(); brightnessDelta = imageConfiguration.getAdjBrightness(); } if (hueDelta == null) { hueDelta = 0f; } if (saturationDelta == null) { saturationDelta = 0f; } if (brightnessDelta == null) { brightnessDelta = 0f; } for (int x = 0; x < width; x++) { try { for (int y = 0; y < height; y++) { int rgb = img.getRGB(x, y); Color color = new Color(rgb); float[] hsb = getHsb(imageConfiguration, color); hsb = applyDeltas(imageConfiguration, hueDelta, saturationDelta, brightnessDelta, hsb); hsb = replaceColors(imageConfiguration, color, hsb); int newRGB = Color.HSBtoRGB(hsb[0], hsb[1], hsb[2]); finalThresholdImage.setRGB(x, y, newRGB); if (imageType == BufferedImage.TYPE_INT_ARGB) { setAlpha(finalThresholdImage, x, y, rgb); finalThresholdImage = makeColorsTransparent(finalThresholdImage, imageConfiguration, new Color(newRGB)); } } } catch (Exception e) { e.getMessage(); } } return finalThresholdImage; } /** * @param imageConfiguration * @param color * @param newRGB * @return */ private static BufferedImage makeColorsTransparent(BufferedImage finalThresholdImage, ImageConfiguration imageConfiguration, Color color) { boolean found = imageConfiguration.getTransparenceConfigurations().stream().anyMatch( tc -> tc.red == color.getRed() && tc.green == color.getGreen() && tc.blue == color.getBlue()); if (found) { finalThresholdImage = makeColorTransparent(finalThresholdImage, color); } return finalThresholdImage; } public static BufferedImage makeColorTransparent(BufferedImage im, final Color color) { ImageFilter filter = new RGBImageFilter() { private int shift = 0xFF000000; public int rgbToMakeTransparent = color.getRGB() | shift; public final int filterRGB(int x, int y, int rgb) { if ((rgb | shift) == rgbToMakeTransparent) { return 0x00FFFFFF & rgb; } return rgb; } }; Image newImage = Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(im.getSource(), filter)); BufferedImage bufferedImage = new BufferedImage(newImage.getWidth(null), newImage.getHeight(null), BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = bufferedImage.createGraphics(); g2.drawImage(newImage, 0, 0, null); g2.dispose(); return bufferedImage; } private static void setAlpha(BufferedImage finalThresholdImage, int x, int y, int rgb) { int bands = finalThresholdImage.getAlphaRaster().getSampleModel().getNumBands(); int alpha = (rgb >> 24) & 0xFF; for (int b = 0; b < bands; b++) { finalThresholdImage.getAlphaRaster().setSample(x, y, b, alpha); } } private static float[] getHsb(ImageConfiguration imageConfiguration, Color color) { float[] hsb = new float[3]; if (imageConfiguration.isInvert()) { Color.RGBtoHSB(255 - color.getRed(), 255 - color.getGreen(), 255 - color.getBlue(), hsb); } else { Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), hsb); } return hsb; } private static float[] replaceColors(ImageConfiguration imageConfiguration, Color color, final float[] currentHsb) { float[] hsb; Optional<ReplaceConfiguration> config = imageConfiguration.getReplaceConfigurations().stream() .filter(rc -> rc.getFrom() != null && rc.getTo() != null && rc.getFrom().red == color.getRed() && rc.getFrom().green == color.getGreen() && rc.getFrom().blue == color.getBlue()) .findFirst(); if (config.isPresent()) { hsb = config.get().getTo().getHSB(); hsb[0] = degreeToPercent(hsb[0]); } else { hsb = currentHsb; } return hsb; } private static float[] applyDeltas(ImageConfiguration imageConfiguration, Float hueDelta, Float saturationDelta, Float brightnessDelta, float[] hsb) { float adjustedHue = 0; float adjustedSaturation = 0; float adjustedBrightness = 0; if (imageConfiguration.isRelative()) { adjustedHue = makeInRange(hsb[0] + hueDelta); adjustedSaturation = makeInRange(hsb[1] + saturationDelta); adjustedBrightness = makeInRange(hsb[2] + brightnessDelta); } else { adjustedHue = hueDelta; adjustedSaturation = saturationDelta; adjustedBrightness = hsb[2]; } final float[] currentHsb = new float[] { adjustedHue, adjustedSaturation, adjustedBrightness }; return currentHsb; } /** * @param f * @return */ private static float degreeToPercent(float degree) { if (degree > 360) { degree = 360; } else if (degree < 0) { degree = 0; } return degree / 360; } private static float makeInRange(float adjustedHue) { if (adjustedHue > 1) { adjustedHue = 1; } else if (adjustedHue < 0) { adjustedHue = 1; } return adjustedHue; } private TranscodingHints createTranscodingHints(Bounds bounds, ColorDepth depth) { TranscodingHints transcoderHints = new TranscodingHints(); transcoderHints.put(ImageTranscoder.KEY_XML_PARSER_VALIDATING, Boolean.FALSE); transcoderHints.put(ImageTranscoder.KEY_DOM_IMPLEMENTATION, SVGDOMImplementation.getDOMImplementation()); transcoderHints.put(ImageTranscoder.KEY_DOCUMENT_ELEMENT_NAMESPACE_URI, SVGConstants.SVG_NAMESPACE_URI); transcoderHints.put(ImageTranscoder.KEY_DOCUMENT_ELEMENT, "svg"); transcoderHints.put(ImageTranscoder.KEY_WIDTH, bounds.getWidth()); transcoderHints.put(ImageTranscoder.KEY_HEIGHT, bounds.getHeight()); if (depth != null) { transcoderHints.put(PNGTranscoder.KEY_INDEXED, depth.getBit()); } return transcoderHints; } public static class Bounds { private final float width; private final float height; Bounds(float width, float height) { this.width = width; this.height = height; } public float getWidth() { return width; } public float getHeight() { return height; } } public enum COLOR_TYPE { MONOCHROM, GRAY, RGB, ARGB } }