通过删除java中的透明像素将图像裁剪为最小尺寸

我有一个精灵表,每个图像都以32×32单元为中心。 实际图像不是32×32,而是略小。 我想做的是拍摄一个单元格并裁剪透明像素,使图像尽可能小。

我将如何用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.. } 

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

我认为这正是你应该做的,循环遍历像素数组,检查alpha然后丢弃。 虽然例如当你有一个星形时,它不会调整图像的大小,因此要注意这一点。

在透明背景上的图像

有一个简单的解决方案 – 扫描每个像素。 该算法具有恒定的性能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, x); bottom = Math.max(bottom, x); left = Math.min(left, x); right = Math.max(right, x); } } } return image.getSubimage(left, top, right - left, bottom - top); } 

但这更有效:

 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)

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

有些事情可以改进。

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

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

    }

如果你的工作表已经有透明像素,那么getSubimage()返回的BufferedImage也会。 默认的Graphics2D 复合规则是AlphaComposite.SRC_OVER ,它应该足以用于drawImage()

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

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

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