2

我有一个精灵表,每个图像都以 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..
}

我目前的想法是测试中心的每个像素,看看它是否透明,但我想知道是否会有更快/更清洁的方法来做到这一点。

4

6 回答 6

6

透明背景上的图像

有一个简单的解决方案——扫描每个像素。下面的算法具有恒定的性能 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)

于 2016-04-29T12:54:30.500 回答
3

这段代码对我有用。该算法很简单,它从图片的左/上/右/下进行迭代,并找到不透明的列/行中的第一个像素。然后它会记住修剪后图片的新角,最后返回原始图像的子图像。

有些事情可以改进。

  1. 该算法期望,数据中有 alpha 字节。如果没有,它将在数组异常的索引上失败。

  2. 该算法期望,图片中至少有一个不透明像素。如果图片完全透明,它将失败。

    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);
    

    }

于 2014-06-02T16:25:17.370 回答
2

我认为这正是你应该做的,遍历像素数组,检查 alpha 然后丢弃。尽管例如当您具有星形时,它不会将图像大小调整为更小,但请注意这一点。

于 2010-07-11T20:59:22.967 回答
1

上面代码的简单修复。我使用 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;
}
于 2019-12-01T18:17:54.803 回答
1

[您好,我尝试了以下方法。在图像文件 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 raster) { //字符顶部

    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 和最小边界框 idle = testing.png]图像

于 2020-09-07T15:53:20.653 回答
0

如果您的工作表已经有透明像素,则也将BufferedImage返回getSubimage()。默认的Graphics2D 复合规则AlphaComposite.SRC_OVER,它应该足以满足drawImage().

如果子图像具有不同的背景颜色,请使用LookupOp带有四分量LookupTable的 a,将与背景匹配的颜色的 alpha 分量设置为零。

我只作为最后的手段遍历像素光栅。

附录:额外的透明像素可能会干扰碰撞检测等。裁剪它们需要WritableRaster直接使用。我不是从中心开始工作,而是从边界开始,使用一对可以一次修改行或列的getPixels()/setPixels()方法。如果整行或整列的 alpha 为零,请在以后获得子图像时将其标记为消除。

于 2010-07-11T22:09:50.930 回答