我正在创建一个工具来检测精灵表中的精灵,并将每个找到的精灵转换为新的BufferedImage。这个过程有效,但某些图像格式 - 主要是透明图像 - 例如这个:
非常慢(Kenney's Game Assets - Animal Pack)
我已经对我的代码进行了分析,并确定绝大多数人,由于许多getRGB()
次调用,我的应用时间的99%以上仅用于此方法。
private fun findContiguousSprite(image: BufferedImage, startingPoint: Point, backgroundColor: Color): List<Point> {
val unvisited = LinkedList<Point>()
val visited = arrayListOf(startingPoint)
unvisited.addAll(neighbors(startingPoint, image).filter {
Color(image.getRGB(it.x, it.y)) != backgroundColor
})
while (unvisited.isNotEmpty()) {
val currentPoint = unvisited.pop()
val currentColor = Color(image.getRGB(currentPoint.x, currentPoint.y))
if (currentColor != backgroundColor) {
unvisited.addAll(neighbors(currentPoint, image).filter {
!visited.contains(it) &&
!unvisited.contains(it) &&
(Color(image.getRGB(it.x, it.y)) != backgroundColor)
})
visited.add(currentPoint)
}
}
return visited.distinct()
}
我试图通过访问图像的栅格数据缓冲区来优化提取rgb颜色的过程,如问题Java - get pixel array from image中所示,但是在最新版本的Java中java.lang.ClassCastException: java.awt.image.DataBufferInt cannot be cast to java.awt.image.DataBufferByte
会失败。< / p>
其他绊脚石包括欺骗性的不必要的颜色装箱,例如行Color(image.getRGB(it.x, it.y)) != backgroundColor
。但是,虽然image.getRGB()
默认在RGBA颜色空间中返回,但background.rgb
仅返回sRGB颜色空间。
问题:如何提高阅读BufferedImage
的效果,尤其是在透明图像的情况下?为什么它几乎任何其他的.png图像如此快速地抛出它除了这些?
注意:虽然代码在Kotlin中,但我接受Java或任何其他JVM语言作为答案。
代码转储:如果您需要完整的代码部分:
private fun findSpriteDimensions(image: BufferedImage,
backgroundColor: Color): List<Rectangle> {
val workingImage = image.copy()
val spriteDimensions = ArrayList<Rectangle>()
for (pixel in workingImage) {
val (point, color) = pixel
if (color != backgroundColor) {
logger.debug("Found a sprite starting at (${point.x}, ${point.y})")
val spritePlot = findContiguousSprite(workingImage, point, backgroundColor)
val spriteRectangle = spanRectangleFrom(spritePlot)
logger.debug("The identified sprite has an area of ${spriteRectangle.width}x${spriteRectangle.height}")
spriteDimensions.add(spriteRectangle)
workingImage.erasePoints(spritePlot, backgroundColor)
}
}
return spriteDimensions
}
private fun findContiguousSprite(image: BufferedImage, startingPoint: Point, backgroundColor: Color): List<Point> {
val unvisited = LinkedList<Point>()
val visited = arrayListOf(startingPoint)
unvisited.addAll(neighbors(startingPoint, image).filter {
Color(image.getRGB(it.x, it.y)) != backgroundColor
})
while (unvisited.isNotEmpty()) {
val currentPoint = unvisited.pop()
val currentColor = Color(image.getRGB(currentPoint.x, currentPoint.y))
if (currentColor != backgroundColor) {
unvisited.addAll(neighbors(currentPoint, image).filter {
!visited.contains(it) &&
!unvisited.contains(it) &&
(Color(image.getRGB(it.x, it.y)) != backgroundColor)
})
visited.add(currentPoint)
}
}
return visited.distinct()
}
private fun neighbors(point: Point, image: Image): List<Point> {
val points = ArrayList<Point>()
val imageWidth = image.getWidth(null) - 1
val imageHeight = image.getHeight(null) - 1
// Left neighbor
if (point.x > 0)
points.add(Point(point.x - 1, point.y))
// Right neighbor
if (point.x < imageWidth)
points.add(Point(point.x + 1, point.y))
// Top neighbor
if (point.y > 0)
points.add(Point(point.x, point.y - 1))
// Bottom neighbor
if (point.y < imageHeight)
points.add(Point(point.x, point.y + 1))
// Top-left neighbor
if (point.x > 0 && point.y > 0)
points.add(Point(point.x - 1, point.y - 1))
// Top-right neighbor
if (point.x < imageWidth && point.y > 0)
points.add(Point(point.x + 1, point.y - 1))
// Bottom-left neighbor
if (point.x > 0 && point.y < imageHeight - 1)
points.add(Point(point.x - 1, point.y + 1))
// Bottom-right neighbor
if (point.x < imageWidth && point.y < imageHeight)
points.add(Point(point.x + 1, point.y + 1))
return points
}
在@Durandal评论的推动下,我决定将我的ArrayList更改为HashSet。我还找到了一种使用Color的替代构造函数Color(rgb, preserveAlpha)
来保留alpha值的方法。现在,在比较这两个值之前,我不再需要getRGB()
框。
private fun findContiguousSprite(image: BufferedImage, point: Point, backgroundColor: Color): List<Point> {
val unvisited = LinkedList<Point>()
val visited = hashSetOf(point)
unvisited.addAll(neighbors(point, image).filter { image.getRGB(it.x, it.y) != backgroundColor.rgb })
while (unvisited.isNotEmpty()) {
val currentPoint = unvisited.pop()
val currentColor = image.getRGB(currentPoint.x, currentPoint.y)
if (currentColor != backgroundColor.rgb) {
unvisited.addAll(neighbors(currentPoint, image).filter {
!visited.contains(it) &&
!unvisited.contains(it) &&
image.getRGB(it.x, it.y) != backgroundColor.rgb
})
visited.add(currentPoint)
}
}
return visited.toList()
}
这在319ms
处理了上述图像。真棒!
答案 0 :(得分:1)
在ArrayList或LinkedList上使用contains具有复杂度O(n)。考虑到您在过滤器中进行许多 contains()调用,这很快就会变成很大的开销。查找 visited.contains()的复杂性随着处理图像的大小而增加(访问的像素越多,访问的像素越多,将复杂性转换为O(n ^ 2))。
降低此成本的最简单方法是使用具有快速包含的集合类型;在HashSet的情况下喜欢O(1)。 Set语义也比列表更适合您的要求,因为据我所知,访问/未访问的集合不应该允许重复。由于集合不允许按合同重复,因此可以消除一些显式包含检查;在你想要响应首次添加点的事件的地方,你也可以使用add() - 方法给出的布尔结果(当元素不存在并添加时为true)。
虽然装箱/拆箱确实需要花费一些时间,但其成本却是线性的。减少拳击开销将需要对代码进行相当多的更改,并且“仅”使您获得恒定的因子加速。标准集合不适用于基元(没有首先想要避免的自动装箱)。虽然有特殊目的第三方集合(Trove浮现在脑海中)处理原始而无需拳击。如果绝对必要,我只会采用这种方式,在可能的情况下更改为原始类型最有可能使代码更长,更混淆。