/* * ****************************************************************************** * * Copyright 2015 See AUTHORS file. * * * * 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.overlap2d.plugins.ninepatch; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import javax.imageio.ImageIO; import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.image.AffineTransformOp; import java.awt.image.BufferedImage; import java.awt.image.WritableRaster; import java.io.File; import java.io.IOException; import java.util.Arrays; /** * Created by various artists on 8/18/2015. */ public class ImageUtils { private static final int NINEPATCH_PADDING = 1; private static final String OUTPUT_TYPE = "png"; /** Returns the pads, or null if the image had no pads or the pads match the splits. Pads are an int[4] that has left, right, * top, bottom. */ public int[] getPads (BufferedImage image, String name, int[] splits) { WritableRaster raster = image.getRaster(); int bottom = raster.getHeight() - 1; int right = raster.getWidth() - 1; int startX = getSplitPoint(raster, name, 1, bottom, true, true); int startY = getSplitPoint(raster, name, right, 1, true, false); // No need to hunt for the end if a start was never found. int endX = 0; int endY = 0; if (startX != 0) endX = getSplitPoint(raster, name, startX + 1, bottom, false, true); if (startY != 0) endY = getSplitPoint(raster, name, right, startY + 1, false, false); // Ensure pixels after the end are not invalid. getSplitPoint(raster, name, endX + 1, bottom, true, true); getSplitPoint(raster, name, right, endY + 1, true, false); // No pads. if (startX == 0 && endX == 0 && startY == 0 && endY == 0) { return null; } // -2 here is because the coordinates were computed before the 1px border was stripped. if (startX == 0 && endX == 0) { startX = -1; endX = -1; } else { if (startX > 0) { startX--; endX = raster.getWidth() - 2 - (endX - 1); } else { // If no start point was ever found, we assume full stretch. endX = raster.getWidth() - 2; } } if (startY == 0 && endY == 0) { startY = -1; endY = -1; } else { if (startY > 0) { startY--; endY = raster.getHeight() - 2 - (endY - 1); } else { // If no start point was ever found, we assume full stretch. endY = raster.getHeight() - 2; } } int[] pads = new int[] {startX, endX, startY, endY}; if (splits != null && Arrays.equals(pads, splits)) { return null; } return pads; } /** Returns the splits, or null if the image had no splits or the splits were only a single region. Splits are an int[4] that * has left, right, top, bottom. */ public int[] getSplits (BufferedImage image, String name) { WritableRaster raster = image.getRaster(); int startX = getSplitPoint(raster, name, 1, 0, true, true); int endX = getSplitPoint(raster, name, startX, 0, false, true); int startY = getSplitPoint(raster, name, 0, 1, true, false); int endY = getSplitPoint(raster, name, 0, startY, false, false); // Ensure pixels after the end are not invalid. getSplitPoint(raster, name, endX + 1, 0, true, true); getSplitPoint(raster, name, 0, endY + 1, true, false); // No splits, or all splits. if (startX == 0 && endX == 0 && startY == 0 && endY == 0) return null; // Subtraction here is because the coordinates were computed before the 1px border was stripped. if (startX != 0) { startX--; endX = raster.getWidth() - 2 - (endX - 1); } else { // If no start point was ever found, we assume full stretch. endX = raster.getWidth() - 2; } if (startY != 0) { startY--; endY = raster.getHeight() - 2 - (endY - 1); } else { // If no start point was ever found, we assume full stretch. endY = raster.getHeight() - 2; } return new int[] {startX, endX, startY, endY}; } /** Hunts for the start or end of a sequence of split pixels. Begins searching at (startX, startY) then follows along the x or y * axis (depending on value of xAxis) for the first non-transparent pixel if startPoint is true, or the first transparent pixel * if startPoint is false. Returns 0 if none found, as 0 is considered an invalid split point being in the outer border which * will be stripped. */ static private int getSplitPoint (WritableRaster raster, String name, int startX, int startY, boolean startPoint, boolean xAxis) { int[] rgba = new int[4]; int next = xAxis ? startX : startY; int end = xAxis ? raster.getWidth() : raster.getHeight(); int breakA = startPoint ? 255 : 0; int x = startX; int y = startY; while (next != end) { if (xAxis) x = next; else y = next; raster.getPixel(x, y, rgba); if (rgba[3] == breakA) return next; if (!startPoint && (rgba[0] != 0 || rgba[1] != 0 || rgba[2] != 0 || rgba[3] != 255)) { // error } next++; } return 0; } public BufferedImage extractImage(TextureAtlas.TextureAtlasData atlas, String regionName, int[] splits) { for (TextureAtlas.TextureAtlasData.Region region : atlas.getRegions()) { if(region.name.equals(regionName)) { TextureAtlas.TextureAtlasData.Page page = region.page; BufferedImage img = null; try { img = ImageIO.read(page.textureFile.file()); } catch (IOException e) { } region.splits = splits; return extractNinePatch(img, region); } } return null; } private BufferedImage extractImage (BufferedImage page, TextureAtlas.TextureAtlasData.Region region, int padding) { BufferedImage splitImage = null; // get the needed part of the page and rotate if needed if (region.rotate) { BufferedImage srcImage = page.getSubimage(region.left, region.top, region.height, region.width); splitImage = new BufferedImage(region.width, region.height, page.getType()); AffineTransform transform = new AffineTransform(); transform.rotate(Math.toRadians(90.0)); transform.translate(0, -region.width); AffineTransformOp op = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR); op.filter(srcImage, splitImage); } else { splitImage = page.getSubimage(region.left, region.top, region.width, region.height); } // draw the image to a bigger one if padding is needed if (padding > 0) { BufferedImage paddedImage = new BufferedImage(splitImage.getWidth() + padding * 2, splitImage.getHeight() + padding * 2, page.getType()); Graphics2D g2 = paddedImage.createGraphics(); g2.drawImage(splitImage, padding, padding, null); g2.dispose(); return paddedImage; } else { return splitImage; } } private BufferedImage extractNinePatch (BufferedImage page, TextureAtlas.TextureAtlasData.Region region) { BufferedImage splitImage = extractImage(page, region, NINEPATCH_PADDING); Graphics2D g2 = splitImage.createGraphics(); g2.setColor(Color.BLACK); // Draw the four lines to save the ninepatch's padding and splits int startX = region.splits[0] + NINEPATCH_PADDING; int endX = region.width - region.splits[1] + NINEPATCH_PADDING - 1; int startY = region.splits[2] + NINEPATCH_PADDING; int endY = region.height - region.splits[3] + NINEPATCH_PADDING - 1; if (endX >= startX) g2.drawLine(startX, 0, endX, 0); if (endY >= startY) g2.drawLine(0, startY, 0, endY); if (region.pads != null) { int padStartX = region.pads[0] + NINEPATCH_PADDING; int padEndX = region.width - region.pads[1] + NINEPATCH_PADDING - 1; int padStartY = region.pads[2] + NINEPATCH_PADDING; int padEndY = region.height - region.pads[3] + NINEPATCH_PADDING - 1; g2.drawLine(padStartX, splitImage.getHeight() - 1, padEndX, splitImage.getHeight() - 1); g2.drawLine(splitImage.getWidth() - 1, padStartY, splitImage.getWidth() - 1, padEndY); } g2.dispose(); return splitImage; } public void saveImage(BufferedImage image, String path) { try { ImageIO.write(image, OUTPUT_TYPE, new File(path)); } catch (IOException e) { e.printStackTrace(); } } }