\r
import java.awt.Color;\r
import java.awt.Graphics;\r
+import java.awt.Graphics2D;\r
import java.awt.image.BufferedImage;\r
import java.awt.image.WritableRaster;\r
import java.io.File;\r
\r
import javax.imageio.ImageIO;\r
\r
+import com.badlogic.gdx.scenes.scene2d.actions.RotateBy;\r
import com.badlogic.gdx.utils.MathUtils;\r
\r
public class SpriteSheetPacker {\r
private boolean pot = true;\r
private int padding = 0;\r
private boolean debug = false;\r
+ private boolean rotate = true;\r
+ private int maxWidth = 1024;\r
+ private int maxHeight = 1024;\r
\r
public SpriteSheetPacker (File inputDir, Filter filter, Direction direction, File outputDir, File packFile) throws IOException {\r
this.inputDir = inputDir;\r
ArrayList<File> files = getFiles(inputDir, filter, direction);\r
if (files == null) return;\r
\r
+ // Collect and squeeze images.\r
for (File file : files) {\r
if (file.isDirectory()) continue;\r
Image image = squeeze(file);\r
}\r
if (images.isEmpty()) return;\r
\r
+ // Print image names.\r
System.out.println(inputDir);\r
if (filter != rgba8888) System.out.println("Format: " + filter.name);\r
if (direction != null) System.out.println("Direction: " + direction);\r
- for (Image image : images)\r
- System.out.println("Packing... " + image.file.getName());\r
-\r
- Collections.sort(images, new Comparator<Image>() {\r
- public int compare (Image image1, Image image2) {\r
- return image1.getWidth() * image1.getHeight() - image2.getWidth() * image2.getHeight();\r
- }\r
- });\r
\r
xPadding = images.size() > 1 && direction != Direction.x && direction != Direction.xy ? padding : 0;\r
yPadding = images.size() > 1 && direction != Direction.y && direction != Direction.xy ? padding : 0;\r
\r
outputDir.mkdirs();\r
- String prefix = inputDir.getParentFile().getName();\r
-\r
+ String prefix = inputDir.getName();\r
writer = new FileWriter(packFile, true);\r
try {\r
while (!images.isEmpty())\r
}\r
\r
private void writePage (String prefix, File outputDir) throws IOException {\r
+ // Remove existing image pages in output dir.\r
int imageNumber = 1;\r
File outputFile = new File(outputDir, prefix + imageNumber + ".png");\r
while (outputFile.exists())\r
outputFile = new File(outputDir, prefix + ++imageNumber + ".png");\r
\r
writer.write("\n" + prefix + imageNumber + ".png\n");\r
- writer.write(direction + "\n");\r
+ writer.write("repeat: " + direction + "\n");\r
+ writer.write("filter: Linear,Linear\n"); // BOZO\r
\r
- // Try reasonably hard to find the smallest size that is also the smallest POT.\r
+ // Try reasonably hard to pack images into the smallest POT size.\r
Comparator bestComparator = null;\r
Comparator secondBestComparator = imageComparators.get(0);\r
- int maxWidth = 1024, maxHeight = 1024;\r
int bestWidth = 99999, bestHeight = 99999;\r
int secondBestWidth = 99999, secondBestHeight = 99999;\r
int bestUsedPixels = 0;\r
while (true) {\r
if (width > maxWidth && height > maxHeight) break;\r
for (Comparator comparator : imageComparators) {\r
+ // Pack as many images as possible, sorting the images different ways.\r
Collections.sort(images, comparator);\r
-\r
int usedPixels = insert(null, new ArrayList(images), width, height);\r
+ // Store the best pack, in case not all images fit on the max texture size.\r
if (usedPixels > bestUsedPixels) {\r
secondBestComparator = comparator;\r
secondBestWidth = width;\r
secondBestHeight = height;\r
}\r
+ // If all images fit and this sort is the best so far, take note.\r
if (usedPixels == -1) {\r
if (width * height < bestWidth * bestHeight) {\r
bestComparator = comparator;\r
}\r
if (bestComparator != null) break;\r
if (pot) {\r
+ // 64,64 then 64,128 then 128,64 then 128,128 then 128,256 etc.\r
if (i % 3 == 0) {\r
width *= 2;\r
i++;\r
i++;\r
}\r
} else {\r
+ // 64-127,64 then 64,64-127 then 128-255,128 then 128,128-255 etc.\r
if (i % 3 == 0) {\r
width++;\r
grownPixels++;\r
}\r
width = bestWidth;\r
height = bestHeight;\r
-\r
if (pot) {\r
width = MathUtils.nextPowerOfTwo(width);\r
height = MathUtils.nextPowerOfTwo(height);\r
int usedPixels = 0;\r
for (int i = images.size() - 1; i >= 0; i--) {\r
Image image = images.get(i);\r
- Node node = root.insert(image, canvas);\r
- if (node == null) continue;\r
+ Node node = root.insert(image, canvas, false);\r
+ if (node == null) {\r
+ if (rotate) node = root.insert(image, canvas, true);\r
+ if (node == null) continue;\r
+ }\r
usedPixels += image.getWidth() * image.getHeight();\r
images.remove(i);\r
if (canvas != null) {\r
- Graphics g = canvas.getGraphics();\r
+ System.out.println("Packing... " + image.file.getName());\r
+ Graphics2D g = (Graphics2D)canvas.getGraphics();\r
+ if (image.rotate) {\r
+ g.translate(node.left, node.top);\r
+ g.rotate(-90 * MathUtils.degreesToRadians);\r
+ g.translate(-node.left, -node.top);\r
+ g.translate(-image.getWidth(), 0);\r
+ }\r
g.drawImage(image, node.left, node.top, null);\r
+ if (image.rotate) {\r
+ g.translate(image.getWidth(), 0);\r
+ g.translate(node.left, node.top);\r
+ g.rotate(90 * MathUtils.degreesToRadians);\r
+ g.translate(-node.left, -node.top);\r
+ }\r
if (debug) {\r
g.setColor(Color.magenta);\r
- g.drawRect(node.left, node.top, image.getWidth() - 1, image.getHeight() - 1);\r
+ int imageWidth = image.getWidth();\r
+ int imageHeight = image.getHeight();\r
+ if (image.rotate)\r
+ g.drawRect(node.left, node.top, imageHeight - 1, imageWidth - 1);\r
+ else\r
+ g.drawRect(node.left, node.top, imageWidth - 1, imageHeight - 1);\r
}\r
}\r
}\r
this.height = height;\r
}\r
\r
- public Node insert (Image image, BufferedImage canvas) throws IOException {\r
+ public Node insert (Image image, BufferedImage canvas, boolean rotate) throws IOException {\r
if (this.image != null) return null;\r
if (child1 != null) {\r
- Node newNode = child1.insert(image, canvas);\r
+ Node newNode = child1.insert(image, canvas, rotate);\r
if (newNode != null) return newNode;\r
- return child2.insert(image, canvas);\r
+ return child2.insert(image, canvas, rotate);\r
+ }\r
+ int imageWidth = image.getWidth();\r
+ int imageHeight = image.getHeight();\r
+ if (rotate) {\r
+ int temp = imageWidth;\r
+ imageWidth = imageHeight;\r
+ imageHeight = temp;\r
}\r
- int neededWidth = image.getWidth() + xPadding;\r
- int neededHeight = image.getHeight() + yPadding;\r
+ int neededWidth = imageWidth + xPadding;\r
+ int neededHeight = imageHeight + yPadding;\r
if (neededWidth > width || neededHeight > height) return null;\r
if (neededWidth == width && neededHeight == height) {\r
this.image = image;\r
+ image.rotate = rotate;\r
write(canvas);\r
return this;\r
}\r
child1 = new Node(left, top, width, neededHeight);\r
child2 = new Node(left, top + neededHeight, width, height - neededHeight);\r
}\r
- return child1.insert(image, canvas);\r
+ return child1.insert(image, canvas, rotate);\r
}\r
\r
private void write (BufferedImage canvas) throws IOException {\r
if (imageName.endsWith("_pre")) imageName = imageName.substring(0, imageName.length() - 2);\r
\r
writer.write(imageName.replace("\\", "/") + "\n");\r
- writer.write(left + "\n");\r
- writer.write(top + "\n");\r
- writer.write(image.getWidth() + "\n");\r
- writer.write(image.getHeight() + "\n");\r
- writer.write(image.offsetX + "\n");\r
- writer.write(image.offsetY + "\n");\r
- writer.write(image.originalWidth + "\n");\r
- writer.write(image.originalHeight + "\n");\r
+ writer.write(" rotate: " + image.rotate + "\n");\r
+ writer.write(" xy: " + left + ", " + top + "\n");\r
+ writer.write(" size: " + image.getWidth() + ", " + image.getHeight() + "\n");\r
+ writer.write(" orig: " + image.originalWidth + ", " + image.originalHeight + "\n");\r
+ writer.write(" offset: " + image.offsetX + ", " + image.offsetY + "\n");\r
\r
Matcher matcher = numberedImagePattern.matcher(imageName);\r
if (matcher.matches())\r
- writer.write(Integer.parseInt(matcher.group(1)) + "\n");\r
+ writer.write("offset: " + Integer.parseInt(matcher.group(1)) + "\n");\r
else\r
- writer.write("0\n");\r
+ writer.write("offset: 0\n");\r
}\r
}\r
\r
final File file;\r
final int offsetX, offsetY;\r
final int originalWidth, originalHeight;\r
+ boolean rotate;\r
\r
public Image (File file, BufferedImage src, int left, int top, int newWidth, int newHeight) {\r
super(src.getColorModel(), src.getRaster().createWritableChild(left, top, newWidth, newHeight, 0, 0, null), src\r
\r
static private void process (File inputDir, File outputDir, File packFile) throws Exception {\r
if (outputDir.exists()) {\r
- String prefix = inputDir.getParentFile().getName();\r
+ String prefix = inputDir.getName();\r
for (File file : outputDir.listFiles())\r
if (file.getName().startsWith(prefix)) file.delete();\r
}\r
}\r
\r
public static void main (String[] args) throws Exception {\r
- process("C:/Dev/libgdx/tests/gdx-tests-lwjgl/data/New folder", "c:/temp/pack-out");\r
+ process("c:/temp/pack-in", "c:/temp/pack-out");\r
}\r
}\r
dirty = true;\r
}\r
\r
+ /**\r
+ * Rotates this sprite 90 degrees. This rotation is unaffected by {@link #setRotation(float)} and {@link #rotate(float)}.\r
+ */\r
+ public void rotate90 (boolean clockwise) {\r
+ float[] vertices = this.vertices;\r
+\r
+ float temp = width;\r
+ width = height;\r
+ height = temp;\r
+\r
+ temp = vertices[X1];\r
+ vertices[X1] = vertices[X4];\r
+ vertices[X4] = vertices[X3];\r
+ vertices[X3] = vertices[X2];\r
+ vertices[X2] = temp;\r
+ \r
+ temp = vertices[Y1];\r
+ vertices[Y1] = vertices[Y4];\r
+ vertices[Y4] = vertices[Y3];\r
+ vertices[Y3] = vertices[Y2];\r
+ vertices[Y2] = temp;\r
+\r
+ temp = vertices[V1];\r
+ vertices[V1] = vertices[V4];\r
+ vertices[V4] = vertices[V3];\r
+ vertices[V3] = vertices[V2];\r
+ vertices[V2] = temp;\r
+\r
+ temp = vertices[U1];\r
+ vertices[U1] = vertices[U4];\r
+ vertices[U4] = vertices[U3];\r
+ vertices[U3] = vertices[U2];\r
+ vertices[U2] = temp;\r
+\r
+ if (rotation != 0 || scaleX != 1 || scaleY != 1) dirty = true;\r
+ }\r
+\r
public void setScale(float scaleXY) {\r
- this.scaleX = this.scaleY = scaleXY;\r
+ this.scaleX = scaleXY;\r
+ this.scaleY = scaleXY;\r
dirty = true;\r
}\r
\r
float localY = -originY * scaleY;\r
float localX2 = (-originX + width) * scaleX;\r
float localY2 = (-originY + height) * scaleY;\r
- float cos = MathUtils.cosDeg(rotation);\r
- float sin = MathUtils.sinDeg(rotation);\r
+ if (scaleX != 1 || scaleY != 1) {\r
+ localX *= scaleX;\r
+ localY *= scaleY;\r
+ localX2 *= scaleX;\r
+ localY2 *= scaleY;\r
+ }\r
float worldOriginX = this.x + originX;\r
float worldOriginY = this.y + originY;\r
-\r
- float x1 = localX * cos - localY * sin + worldOriginX;\r
- float y1 = localY * cos + localX * sin + worldOriginY;\r
- vertices[X1] = x1;\r
- vertices[Y1] = y1;\r
-\r
- float x2 = localX * cos - localY2 * sin + worldOriginX;\r
- float y2 = localY2 * cos + localX * sin + worldOriginY;\r
- vertices[X2] = x2;\r
- vertices[Y2] = y2;\r
-\r
- float x3 = localX2 * cos - localY2 * sin + worldOriginX;\r
- float y3 = localY2 * cos + localX2 * sin + worldOriginY;\r
- vertices[X3] = x3;\r
- vertices[Y3] = y3;\r
-\r
- vertices[X4] = x1 + (x3 - x2);\r
- vertices[Y4] = y3 - (y2 - y1);\r
+ if (rotation != 0) {\r
+ float cos = MathUtils.cosDeg(rotation);\r
+ float sin = MathUtils.sinDeg(rotation);\r
+ \r
+ float x1 = localX * cos - localY * sin + worldOriginX;\r
+ float y1 = localY * cos + localX * sin + worldOriginY;\r
+ vertices[X1] = x1;\r
+ vertices[Y1] = y1;\r
+ \r
+ float x2 = localX * cos - localY2 * sin + worldOriginX;\r
+ float y2 = localY2 * cos + localX * sin + worldOriginY;\r
+ vertices[X2] = x2;\r
+ vertices[Y2] = y2;\r
+ \r
+ float x3 = localX2 * cos - localY2 * sin + worldOriginX;\r
+ float y3 = localY2 * cos + localX2 * sin + worldOriginY;\r
+ vertices[X3] = x3;\r
+ vertices[Y3] = y3;\r
+ \r
+ vertices[X4] = x1 + (x3 - x2);\r
+ vertices[Y4] = y3 - (y2 - y1);\r
+ } else {\r
+ float x1 = localX + worldOriginX;\r
+ float y1 = localY + worldOriginY;\r
+ float x2 = localX2 + worldOriginX;\r
+ float y2 = localY2 + worldOriginY;\r
+ \r
+ vertices[X1] = x1;\r
+ vertices[Y1] = y1;\r
+ \r
+ vertices[X2] = x1;\r
+ vertices[Y2] = y2;\r
+ \r
+ vertices[X3] = x2;\r
+ vertices[Y3] = y2;\r
+ \r
+ vertices[X4] = x2;\r
+ vertices[Y4] = y1;\r
+ }\r
}\r
return vertices;\r
}\r
import java.util.List;\r
import java.util.PriorityQueue;\r
\r
-import com.badlogic.gdx.Files.FileType;\r
import com.badlogic.gdx.Gdx;\r
import com.badlogic.gdx.files.FileHandle;\r
+import com.badlogic.gdx.graphics.Texture.TextureFilter;\r
import com.badlogic.gdx.graphics.Texture.TextureWrap;\r
+import com.badlogic.gdx.utils.GdxRuntimeException;\r
\r
-import static com.badlogic.gdx.graphics.Texture.TextureWrap.*;\r
import static com.badlogic.gdx.graphics.Texture.TextureFilter.*;\r
-import com.badlogic.gdx.utils.GdxRuntimeException;\r
+import static com.badlogic.gdx.graphics.Texture.TextureWrap.*;\r
\r
/**\r
* Loads images from texture atlases created by SpriteSheetPacker.<br>\r
* A SpriteSheet must be disposed to free up the resources consumed by the backing textures.\r
*/\r
public class SpriteSheet {\r
+ static private final String[] tuple = new String[2];\r
+\r
private final ArrayList<Texture> textures = new ArrayList(4);\r
private final PackedSprite[] images;\r
\r
else if (pageImage == null) {\r
FileHandle file = imagesDir.child(line);\r
\r
- // BOZO - Get filter from file?\r
-\r
- String direction = reader.readLine();\r
- TextureWrap wrapX = ClampToEdge;\r
- TextureWrap wrapY = ClampToEdge;\r
+ String direction = readValue(reader);\r
+ TextureWrap repeatX = ClampToEdge;\r
+ TextureWrap repeatY = ClampToEdge;\r
if (direction.equals("x"))\r
- wrapX = Repeat;\r
+ repeatX = Repeat;\r
else if (direction.equals("y"))\r
- wrapY = Repeat;\r
+ repeatY = Repeat;\r
else if (direction.equals("xy")) {\r
- wrapX = Repeat;\r
- wrapY = Repeat;\r
+ repeatX = Repeat;\r
+ repeatY = Repeat;\r
}\r
\r
- Texture texture = Gdx.graphics.newTexture(file, Linear, Linear, ClampToEdge, ClampToEdge);\r
+ readTuple(reader);\r
+ TextureFilter min = TextureFilter.valueOf(tuple[0]);\r
+ TextureFilter max = TextureFilter.valueOf(tuple[1]);\r
+\r
+ Texture texture = Gdx.graphics.newTexture(file, min, max, repeatX, repeatY);\r
textures.add(texture);\r
\r
pageImage = new Sprite(texture);\r
} else {\r
- int left = Integer.parseInt(reader.readLine());\r
- int top = Integer.parseInt(reader.readLine());\r
- int width = Integer.parseInt(reader.readLine());\r
- int height = Integer.parseInt(reader.readLine());\r
- int offsetX = Integer.parseInt(reader.readLine());\r
- int offsetY = Integer.parseInt(reader.readLine());\r
- int originalWidth = Integer.parseInt(reader.readLine());\r
- int originalHeight = Integer.parseInt(reader.readLine());\r
- PackedSprite image = new PackedSprite(pageImage, left, top, width, height);\r
- image.setPosition(offsetX, offsetY);\r
+ boolean rotate = Boolean.valueOf(readValue(reader));\r
+\r
+ readTuple(reader);\r
+ int left = Integer.parseInt(tuple[0]);\r
+ int top = Integer.parseInt(tuple[1]);\r
+\r
+ readTuple(reader);\r
+ int width = Integer.parseInt(tuple[0]);\r
+ int height = Integer.parseInt(tuple[1]);\r
+\r
+ PackedSprite image;\r
+ if (rotate) {\r
+ image = new PackedSprite(pageImage, left, top, height, width);\r
+ image.rotate90(true);\r
+ } else\r
+ image = new PackedSprite(pageImage, left, top, width, height);\r
image.name = line;\r
- image.offsetX = offsetX;\r
- image.offsetY = offsetY;\r
- image.originalWidth = originalWidth;\r
- image.originalHeight = originalHeight;\r
- image.index = Integer.parseInt(reader.readLine());\r
+\r
+ readTuple(reader);\r
+ image.originalWidth = Integer.parseInt(tuple[0]);\r
+ image.originalHeight = Integer.parseInt(tuple[1]);\r
+\r
+ readTuple(reader);\r
+ image.offsetX = Integer.parseInt(tuple[0]);\r
+ image.offsetY = Integer.parseInt(tuple[1]);\r
+ image.setPosition(image.offsetX, image.offsetY);\r
+\r
+ image.index = Integer.parseInt(readValue(reader));\r
if (image.index == -1) image.index = Integer.MAX_VALUE;\r
+\r
sortedSprites.add(image);\r
}\r
}\r
}\r
};\r
\r
+ static private String readValue (BufferedReader reader) throws IOException {\r
+ String line = reader.readLine();\r
+ int colon = line.indexOf(':');\r
+ if (colon == -1) throw new GdxRuntimeException("Invalid line: " + line);\r
+ return line.substring(colon + 1).trim();\r
+ }\r
+\r
+ static private void readTuple (BufferedReader reader) throws IOException {\r
+ String line = reader.readLine();\r
+ int colon = line.indexOf(':');\r
+ int comma = line.indexOf(',');\r
+ if (colon == -1 || comma == -1 || comma < colon + 1) throw new GdxRuntimeException("Invalid line: " + line);\r
+ tuple[0] = line.substring(colon + 1, comma).trim();\r
+ tuple[1] = line.substring(comma + 1).trim();\r
+ }\r
+\r
/**\r
* A sprite that provides additional information about the packed image it represents. A PackedSprite's position is relative to\r
* the bottom left of the original image, before whitespace was removed for packing.\r