我有一个精灵表,其中每个图像都位于 32x32 单元格的中心。 实际图像不是 32x32,而是稍小一些。 我想做的是获取一个单元格并裁剪透明像素,使图像尽可能小。
我该如何在 Java (JDK 6) 中做到这一点?
这是我目前如何将图块表分解为单元格的示例:
BufferedImage tilesheet = ImageIO.read(getClass().getResourceAsStream("/sheet.png");
for (int i = 0; i < 15; i++) {
Image img = tilesheet.getSubimage(i * 32, 0, 32, 32);
// crop here..
}
我当前的想法是测试中心的每个像素,看看它是否透明,但我想知道是否有更快/更干净的方法来做到这一点。
有一个简单的解决方案——扫描每个像素。下面的算法具有恒定的性能
O(w•h)
。
private static BufferedImage trimImage(BufferedImage image) {
int width = image.getWidth();
int height = image.getHeight();
int top = height / 2;
int bottom = top;
int left = width / 2 ;
int right = left;
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
if (image.getRGB(x, y) != 0){
top = Math.min(top, y);
bottom = Math.max(bottom, y);
left = Math.min(left, x);
right = Math.max(right, x);
}
}
}
return image.getSubimage(left, top, right - left + 1, bottom - top + 1);
}
但这更有效:
private static BufferedImage trimImage(BufferedImage image) {
WritableRaster raster = image.getAlphaRaster();
int width = raster.getWidth();
int height = raster.getHeight();
int left = 0;
int top = 0;
int right = width - 1;
int bottom = height - 1;
int minRight = width - 1;
int minBottom = height - 1;
top:
for (;top <= bottom; top++){
for (int x = 0; x < width; x++){
if (raster.getSample(x, top, 0) != 0){
minRight = x;
minBottom = top;
break top;
}
}
}
left:
for (;left < minRight; left++){
for (int y = height - 1; y > top; y--){
if (raster.getSample(left, y, 0) != 0){
minBottom = y;
break left;
}
}
}
bottom:
for (;bottom > minBottom; bottom--){
for (int x = width - 1; x >= left; x--){
if (raster.getSample(x, bottom, 0) != 0){
minRight = x;
break bottom;
}
}
}
right:
for (;right > minRight; right--){
for (int y = bottom; y >= top; y--){
if (raster.getSample(right, y, 0) != 0){
break right;
}
}
}
return image.getSubimage(left, top, right - left + 1, bottom - top + 1);
}
该算法遵循 pepan 的答案(见上文)的想法,并且效率提高了 2 到 4 倍。不同之处在于:它从不扫描任何像素两次,并尝试缩小每个阶段的搜索范围。
该方法性能最差的情况是
O(w•h–a•b)
这段代码对我有用。该算法很简单,它从图片的左/上/右/下迭代,找到列/行中不透明的第一个像素。然后它会记住修剪图片的新角,最后返回原始图像的子图像。
还有一些可以改进的地方。
算法期望数据中存在 alpha 字节。如果不存在,它将在索引超出数组异常时失败。
算法期望图片中至少有一个非透明像素。如果图片完全透明就会失败。
private static BufferedImage trimImage(BufferedImage img) {
final byte[] pixels = ((DataBufferByte) img.getRaster().getDataBuffer()).getData();
int width = img.getWidth();
int height = img.getHeight();
int x0, y0, x1, y1; // the new corners of the trimmed image
int i, j; // i - horizontal iterator; j - vertical iterator
leftLoop:
for (i = 0; i < width; i++) {
for (j = 0; j < height; j++) {
if (pixels[(j*width+i)*4] != 0) { // alpha is the very first byte and then every fourth one
break leftLoop;
}
}
}
x0 = i;
topLoop:
for (j = 0; j < height; j++) {
for (i = 0; i < width; i++) {
if (pixels[(j*width+i)*4] != 0) {
break topLoop;
}
}
}
y0 = j;
rightLoop:
for (i = width-1; i >= 0; i--) {
for (j = 0; j < height; j++) {
if (pixels[(j*width+i)*4] != 0) {
break rightLoop;
}
}
}
x1 = i+1;
bottomLoop:
for (j = height-1; j >= 0; j--) {
for (i = 0; i < width; i++) {
if (pixels[(j*width+i)*4] != 0) {
break bottomLoop;
}
}
}
y1 = j+1;
return img.getSubimage(x0, y0, x1-x0, y1-y0);
}
我认为这正是你应该做的,循环遍历像素数组,检查 alpha 然后丢弃。例如,尽管当您具有星形形状时,它不会将图像大小调整得更小,但请注意这一点。
对上面代码的简单修复。我使用 RGB 的中位数并修复了 x 和 y 的 min() 函数:
private static BufferedImage trim(BufferedImage img) {
int width = img.getWidth();
int height = img.getHeight();
int top = height / 2;
int bottom = top;
int left = width / 2 ;
int right = left;
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
if (isFg(img.getRGB(x, y))){
top = Math.min(top, y);
bottom = Math.max(bottom, y);
left = Math.min(left, x);
right = Math.max(right, x);
}
}
}
return img.getSubimage(left, top, right - left, bottom - top);
}
private static boolean isFg(int v) {
Color c = new Color(v);
return(isColor((c.getRed() + c.getGreen() + c.getBlue())/2));
}
private static boolean isColor(int c) {
return c > 0 && c < 255;
}
[嗨,我尝试了以下方法。在图像文件中idle1.png是带有大透明框的图像,而testing.png是带有最小边界框的相同图像
'BufferedImage tempImg = (ImageIO.read(new File(fileNPath)));
WritableRaster tempRaster = tempImg.getAlphaRaster();
int x1 = getX1(tempRaster);
int y1 = getY1(tempRaster);
int x2 = getX2(tempRaster);
int y2 = getY2(tempRaster);
System.out.println("x1:"+x1+" y1:"+y1+" x2:"+x2+" y2:"+y2);
BufferedImage temp = tempImg.getSubimage(x1, y1, x2 - x1, y2 - y1);
//for idle1.png
String filePath = fileChooser.getCurrentDirectory() + "\\"+"testing.png";
System.out.println("filePath:"+filePath);
ImageIO.write(temp,"png",new File(filePath));
获取函数在哪里
public int getY1(WritableRaster 栅格) { //字符顶部
for (int y = 0; y < raster.getHeight(); y++) {
for (int x = 0; x < raster.getWidth(); x++) {
if (raster.getSample(x, y,0) != 0) {
if(y>0) {
return y - 1;
}else{
return y;
}
}
}
}
return 0;
}
public int getY2(WritableRaster raster) {
//ground plane of character
for (int y = raster.getHeight()-1; y > 0; y--) {
for (int x = 0; x < raster.getWidth(); x++) {
if (raster.getSample(x, y,0) != 0) {
return y + 1;
}
}
}
return 0;
}
public int getX1(WritableRaster raster) {
//left side of character
for (int x = 0; x < raster.getWidth(); x++) {
for (int y = 0; y < raster.getHeight(); y++) {
if (raster.getSample(x, y,0) != 0) {
if(x > 0){
return x - 1;
}else{
return x;
}
}
}
}
return 0;
}
public int getX2(WritableRaster raster) {
//right side of character
for (int x = raster.getWidth()-1; x > 0; x--) {
for (int y = 0; y < raster.getHeight(); y++) {
if (raster.getSample(x, y,0) != 0) {
return x + 1;
}
}
}
return 0;
}'[Look at Idle1.png and the minimum bounding box idle = testing.png][1]
感谢您对 Michael 的帮助。查看 Idle1.png 和最小边界框闲置=testing.png]此处为图像
如果您的工作表已有透明像素,则
BufferedImage
返回的 getSubimage()
也会有。默认 Graphics2D
复合规则 是 AlphaComposite.SRC_OVER
,这应该足以满足 drawImage()
。
LookupOp
和四分量 LookupTable
,将与背景匹配的颜色的 Alpha 分量设置为零。
我只会将遍历像素栅格作为最后的手段。
附录:额外的透明像素可能会干扰碰撞检测等。裁剪它们将需要直接使用
WritableRaster
。我不是从中心向外工作,而是从边框开始,使用一对可以一次修改行或列的 getPixels()
/setPixels()
方法。如果整行或整列的 Alpha 值为零,请在稍后获得子图像时将其标记为消除。
我想在 Oleg Mikhailov 第一个算法上添加注释
它工作完美,除非图像的一侧“没有像素(在中间分开,垂直或水平)
例如,仅占据图像下半部分的精灵无论如何都会被裁剪到图像的中间,因为它找不到顶部变量“上方”的像素
我的解决方案是将“顶部”设置为“高度”(而不是高度的一半), “底部”为 0, “左”到“宽” 并“右”到 0
因此限制将位于图像的错误一侧并移动到正确的位置
它不应该改变算法的复杂性,因为除了最后一行之外,从不使用 4 个位置变量)