问题
Possible Duplicate:
PySide: Separating a spritesheet / Separating an image into contiguous regions of color
Given a .png image with transparent pixels and a grid of individual animation frames (where the last row need not be full), how would you automatically find the dimensions of each individual frame, and detect how many frames are in the .png?
I am trying to convert the resources from the creative-commons treasure-trove of Glitch into our internal format, and I am having problems isolating frame information from the raw .pngs.

In this case, I can find that frames are 189 x 230 px; but looking this up requires time, and there are a lot of images to potentially look up.
I would like to split the image into frames for use inside a Java Swing desktop application. I can load the image using ImageIO
into a BufferedImage
, and easily check for pixel transparency. There are only a few possible frame sizes: given a 945x690 frame as in the example, and assuming a minimal side of 50px, the only plausible frame-widths are 5 x 189 (correct), 7 x 135, or 9 x 105.
So, how would you find the frame size? This does not need to be tremendously efficient, as resource conversion is a one-time problem. Pseudo-code answers are fine; I am mostly interested in the algorithm.
Note: PySide: Separating a spritesheet / Separating an image into contiguous regions of color explains how to deal with non-animation sprite-sheets, with irregularly sized images within the sheet. I am interested in detecting rows x columns, which can be solved with a much simpler algorithm (see accepted answer).
回答1:
Since all the images are framed by a single color, you could look for bars of 'frame border color' in columns and rows across the larger image.
Use the number of columns and rows obtained, in relation to the size of the image (width x height), to determine the pixel sizes of each sub-image.

import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
import javax.swing.border.LineBorder;
import java.net.*;
import java.net.URL;
import java.util.ArrayList;
import javax.imageio.ImageIO;
class TileSetUtility {
/** Divide the tile into tiles based on the number of cols & rows
* supplied. Exclude any images that are a solid color. */
public static ArrayList<BufferedImage> getTiles(
BufferedImage tile, int cols, int rows) {
int w = tile.getWidth();
int h = tile.getHeight();
int wT = w / cols;
int hT = h / rows;
if (wT * cols != w || hT * rows != h) {
throw new IllegalArgumentException("Tile is not an even " +
"multiple of pixels of WxCols or HxRows!");
}
ArrayList<BufferedImage> tiles = new ArrayList<BufferedImage>();
for (int x = 0; x < cols; x++) {
for (int y = 0; y < rows; y++) {
BufferedImage i = tile.getSubimage(x * wT, y * hT, wT, hT);
if (!isImageSolidColor(i)) {
tiles.add(i);
}
}
}
return tiles;
}
/** Takes an image that represents tiles of a tile set, and infers the
* number of columns based on the assumption that the color at 0x0 in the
* image represents a border color or frame for the contained tiles. */
public static int inferNumberColumns(BufferedImage img) {
boolean[] columnClear = new boolean[img.getWidth()];
// after this loop, we should have a series of contiguous regions
// of 'true' in the array.
for (int ii = 0; ii < columnClear.length; ii++) {
columnClear[ii] = isLineEmpty(img, ii, false);
}
return countContiguousRegions(columnClear);
}
/** Takes an image that represents tiles of a tile set, and infers the
* number of rows based on the assumption that the color at 0x0 in the
* image represents a border color or frame for the contained tiles. */
public static int inferNumberRows(BufferedImage img) {
boolean[] columnClear = new boolean[img.getHeight()];
// after this loop, we should have a series of contiguous regions
// of 'true' in the array.
for (int ii = 0; ii < columnClear.length; ii++) {
columnClear[ii] = isLineEmpty(img, ii, true);
}
return countContiguousRegions(columnClear);
}
/** Count the number of contiguous regions of 'true' */
public static int countContiguousRegions(boolean[] array) {
boolean newRegion = false;
int count = 0;
for (boolean bool : array) {
if (bool) {
if (newRegion) {
count++;
}
newRegion = false;
} else {
newRegion = true;
}
}
return count;
}
/** Determine if this entire column or row of image pixels is empty. */
public static boolean isLineEmpty(
BufferedImage img, int pos, boolean row) {
if (!row) {
for (int y = 0; y < img.getHeight(); y++) {
if (img.getRGB(pos, y) != img.getRGB(0, 0)) {
return false;
}
}
} else {
for (int x = 0; x < img.getWidth(); x++) {
if (img.getRGB(x, pos) != img.getRGB(0, 0)) {
return false;
}
}
}
return true;
}
/** Determine if this image is one solid color (implies redundant tile) */
public static boolean isImageSolidColor(BufferedImage img) {
int c = img.getRGB(0,0);
for (int x=0; x<img.getWidth(); x++) {
for (int y=0; y<img.getHeight(); y++) {
if (c!=img.getRGB(x,y)) {
return false;
}
}
}
return true;
}
public static void main(String[] args) throws Exception {
URL url = new URL("http://i.stack.imgur.com/ttXm6.png");
final BufferedImage tileSet = ImageIO.read(url);
Runnable r = new Runnable() {
@Override
public void run() {
JPanel gui = new JPanel(new BorderLayout(5, 5));
int cols = inferNumberColumns(tileSet);
System.out.println("tileSet cols: " + cols);
int rows = inferNumberRows(tileSet);
System.out.println("tileSet rows: " + rows);
ArrayList<BufferedImage> tiles = getTiles(tileSet, cols, rows);
JPanel p = new JPanel(new GridLayout(0, 7, 1, 1));
for (BufferedImage tile : tiles) {
JLabel l = new JLabel(new ImageIcon(tile));
l.setBorder(new LineBorder(Color.BLACK));
p.add(l);
}
gui.add(new JLabel(new ImageIcon(tileSet)));
JOptionPane.showMessageDialog(null, p);
}
};
// Swing GUIs should be created and updated on the EDT
SwingUtilities.invokeLater(r);
}
}
来源:https://stackoverflow.com/questions/14649528/automatically-find-frame-sizes-from-png-sprite-sheet