package com.kreative.paint.material.sprite; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import javax.imageio.ImageIO; public class SpriteSheetReader { public static final long PNG_MAGIC_NUMBER = 0x89504E470D0A1A0AL; public static final int PNG_CHUNK_SPNF = 0x73704E46; public static class Options { public static final int HOTSPOT_CENTER = Integer.MIN_VALUE; private int slicingType; private int slicingWidth; private int slicingHeight; private int hotspotX; private int hotspotY; private ArrayOrdering slicingOrder; private ColorTransform transform; private int presentationColumns; private int presentationRows; private ArrayOrdering presentationOrder; private int intent; private int parentMultiplicity; private int firstChildOffset; public Options() { setDefaultSlicingStrip(); setDefaultHotspot(HOTSPOT_CENTER, HOTSPOT_CENTER); setDefaultSlicingOrder(ArrayOrdering.LTR_TTB); setDefaultColorTransform(ColorTransform.NONE); setDefaultPresentationAuto(); setDefaultIntent(0); setDefaultStructureFlat(); } public Options setDefaultSlicingNone() { this.slicingType = 0; return this; } public Options setDefaultSlicingStrip() { this.slicingType = 1; return this; } public Options setDefaultSlicingFixed(int cw, int ch) { this.slicingType = 2; this.slicingWidth = cw; this.slicingHeight = ch; return this; } public Options setDefaultHotspot(int hx, int hy) { this.hotspotX = hx; this.hotspotY = hy; return this; } public Options setDefaultSlicingOrder(ArrayOrdering order) { this.slicingOrder = order; return this; } public Options setDefaultColorTransform(ColorTransform transform) { this.transform = transform; return this; } public Options setDefaultPresentationAuto() { this.presentationColumns = -1; this.presentationRows = -1; this.presentationOrder = ArrayOrdering.LTR_TTB; return this; } public Options setDefaultPresentation(int columns, int rows, ArrayOrdering order) { this.presentationColumns = columns; this.presentationRows = rows; this.presentationOrder = order; return this; } public Options setDefaultIntent(int intent) { this.intent = intent; return this; } public Options setDefaultStructureFlat() { this.parentMultiplicity = 0; this.firstChildOffset = 0; return this; } public Options setDefaultStructureSingleParent(boolean excludeParent) { this.parentMultiplicity = 1; this.firstChildOffset = excludeParent ? 1 : 0; return this; } public Options setDefaultStructureMultipleParents(boolean excludeParents) { this.parentMultiplicity = 2; this.firstChildOffset = excludeParents ? 1 : 0; return this; } } public static SpriteSheet readSpriteSheet(File file, Options o) throws IOException { String name = file.getName(); name = name.replaceFirst("^#[0-9]+ ", ""); name = name.replaceFirst("\\.[a-zA-Z0-9]+$", ""); name = name.trim(); BufferedImage image = ImageIO.read(file); return readSpriteSheet(name, file, image, o); } public static SpriteSheet readSpriteSheet(String name, byte[] data, Options o) throws IOException { ByteArrayInputStream in = new ByteArrayInputStream(data); BufferedImage image = ImageIO.read(in); in.close(); return readSpriteSheet(name, data, image, o); } public static SpriteSheet readSpriteSheet(String name, InputStream in, Options o) throws IOException { in.mark(0x100000); BufferedImage image = ImageIO.read(in); in.reset(); return readSpriteSheet(name, in, image, o); } public static SpriteSheet readSpriteSheet(String name, File file, BufferedImage image, Options o) throws IOException { InputStream in = new FileInputStream(file); SpriteSheet sheet = readSpriteSheet(name, in, image, o); in.close(); return sheet; } public static SpriteSheet readSpriteSheet(String name, byte[] data, BufferedImage image, Options o) throws IOException { InputStream in = new ByteArrayInputStream(data); SpriteSheet sheet = readSpriteSheet(name, in, image, o); in.close(); return sheet; } public static SpriteSheet readSpriteSheet(String name, InputStream in, BufferedImage image, Options o) throws IOException { DataInputStream data = new DataInputStream(in); if (data.readLong() == PNG_MAGIC_NUMBER) { while (data.available() > 0) { int cl = data.readInt(); int ct = data.readInt(); byte[] cd = new byte[cl]; data.read(cd); data.readInt(); if (ct == PNG_CHUNK_SPNF) { DataInputStream chunk = new DataInputStream(new ByteArrayInputStream(cd)); SpriteSheet sheet = readSpriteSheet(chunk, image); chunk.close(); return sheet; } } } return createSpriteSheet(name, image, o); } public static SpriteSheet createSpriteSheet(String name, BufferedImage image, Options o) { if (o == null) o = new Options(); int w = image.getWidth(); int h = image.getHeight(); int cw, ch; switch (o.slicingType) { default: case 0: cw = w; ch = h; break; case 1: cw = ch = Math.min(w, h); break; case 2: cw = o.slicingWidth; ch = o.slicingHeight; break; } int chx = (o.hotspotX == Options.HOTSPOT_CENTER) ? (cw / 2) : (o.hotspotX < 0) ? (cw + o.hotspotX) : o.hotspotX; int chy = (o.hotspotY == Options.HOTSPOT_CENTER) ? (ch / 2) : (o.hotspotY < 0) ? (ch + o.hotspotY) : o.hotspotY; int cols = w / cw; int rows = h / ch; int pcols = o.presentationColumns; int prows = o.presentationRows; if (pcols < 0 && prows < 0) { pcols = cols; prows = rows; } else if (pcols < 0) { pcols = (prows == 0) ? cols : (((cols * rows) + prows - 1) / prows); } else if (prows < 0) { prows = (pcols == 0) ? rows : (((cols * rows) + pcols - 1) / pcols); } SpriteSheet sheet = new SpriteSheet( image, name, o.intent, pcols, prows, o.presentationOrder ); sheet.slices.add(new SpriteSheetSlice( 0, 0, cw, ch, chx, chy, cw, ch, cols, rows, o.slicingOrder, o.transform )); if (o.parentMultiplicity <= 0) { int fc = o.firstChildOffset; int ns = cols * rows; SpriteTreeNode.Leaf l = new SpriteTreeNode.Leaf(name, fc, 0, ns - fc); sheet.root.children.add(l); } else if (o.parentMultiplicity == 1) { int fc = o.firstChildOffset; int ns = cols * rows; SpriteTreeNode.Branch b = new SpriteTreeNode.Branch(name, 0, 0); SpriteTreeNode.Leaf l = new SpriteTreeNode.Leaf(name, fc, 0, ns - fc); b.children.add(l); sheet.root.children.add(b); } else if (o.parentMultiplicity >= 2) { int fc = o.firstChildOffset; int np = o.slicingOrder.vertical ? cols : rows; int nc = o.slicingOrder.vertical ? rows : cols; for (int p = 0, i = 0; p < np; p++, i += nc) { SpriteTreeNode.Branch b = new SpriteTreeNode.Branch(name, i, 0); SpriteTreeNode.Leaf l = new SpriteTreeNode.Leaf(name, i + fc, 0, nc - fc); b.children.add(l); sheet.root.children.add(b); } } return sheet; } private static SpriteSheet readSpriteSheet(DataInputStream chunk, BufferedImage image) throws IOException { String name = (chunk.available() > 0) ? chunk.readUTF() : ""; int intent = (chunk.available() > 0) ? chunk.readUnsignedShort() : 0; int cols = (chunk.available() > 0) ? chunk.readUnsignedShort() : 0; int rows = (chunk.available() > 0) ? chunk.readUnsignedShort() : 0; ArrayOrdering order = ArrayOrdering.fromIntValue((chunk.available() > 0) ? chunk.readUnsignedShort() : 0); SpriteSheet sheet = new SpriteSheet(image, name, intent, cols, rows, order); int sliceCount = (chunk.available() > 0) ? chunk.readUnsignedShort() : 0; for (int i = 0; i < sliceCount; i++) sheet.slices.add(readSlice(chunk, image)); int nodeCount = (chunk.available() > 0) ? chunk.readUnsignedShort() : 0; for (int i = 0; i < nodeCount; i++) sheet.root.children.add(readTreeNode(chunk)); return sheet; } private static SpriteSheetSlice readSlice(DataInputStream chunk, BufferedImage image) throws IOException { int w = image.getWidth(); int h = image.getHeight(); int sx = (chunk.available() > 0) ? chunk.readUnsignedShort() : 0; int sy = (chunk.available() > 0) ? chunk.readUnsignedShort() : 0; int cw = (chunk.available() > 0) ? chunk.readUnsignedShort() : Math.min(w - sx, h - sy); int ch = (chunk.available() > 0) ? chunk.readUnsignedShort() : Math.min(w - sx, h - sy); int chx = (chunk.available() > 0) ? chunk.readUnsignedShort() : (cw / 2); int chy = (chunk.available() > 0) ? chunk.readUnsignedShort() : (ch / 2); int cdx = (chunk.available() > 0) ? chunk.readUnsignedShort() : cw; int cdy = (chunk.available() > 0) ? chunk.readUnsignedShort() : ch; int cols = (chunk.available() > 0) ? chunk.readUnsignedShort() : ((w - sx + cdx - cw) / cdx); int rows = (chunk.available() > 0) ? chunk.readUnsignedShort() : ((h - sy + cdy - ch) / cdy); ArrayOrdering order = ArrayOrdering.fromIntValue((chunk.available() > 0) ? chunk.readUnsignedShort() : 0); ColorTransform transform = new ColorTransform((chunk.available() > 0) ? chunk.readUnsignedShort() : 0); return new SpriteSheetSlice(sx, sy, cw, ch, chx, chy, cdx, cdy, cols, rows, order, transform); } private static SpriteTreeNode readTreeNode(DataInputStream chunk) throws IOException { String name = (chunk.available() > 0) ? chunk.readUTF() : ""; int index = (chunk.available() > 0) ? chunk.readUnsignedShort() : 0; int duration = (chunk.available() > 0) ? chunk.readUnsignedShort() : 0; int count = (chunk.available() > 0) ? chunk.readUnsignedShort() : 0; if (count < 0x8000) { return new SpriteTreeNode.Leaf(name, index, duration, count); } else { SpriteTreeNode.Branch node = new SpriteTreeNode.Branch(name, index, duration); for (int i = 0x8000; i < count; i++) node.children.add(readTreeNode(chunk)); return node; } } }