Я создаю инструмент, который обнаруживает спрайты на листе спрайтов и преобразует каждый найденный спрайт в новый BufferedImage. Этот процесс работает, но непозволительно медленно с определенными форматами изображений, в основном прозрачными изображениями, такими как этот:
(Игровые активы Kenney — Animal Pack)
Я профилировал свой код и определил, что подавляющее большинство, более 99% времени моего приложения, тратится только на этот метод из-за множества вызовов getRGB()
.
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 - получить пиксель массив из изображения путем доступа к буферу растровых данных изображения, но это не работает в новейших версиях Java с расширением java.lang.ClassCastException: java.awt.image.DataBufferInt cannot be cast to java.awt.image.DataBufferByte
.
Другие камни преткновения включают обманчиво ненужное сочетание цветов, например, в строке 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(rgb, preserveAlpha)
. Теперь мне больше не нужно упаковывать 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
. Потрясающий!
ARGB
. Мне это нужно, потому что извлеченные спрайты должны иметь прозрачный фон. - person IAE   schedule 27.05.2016