Improve edge detection performance (#1457)

* Optimize edge detection by decoding full image once

Refactors edge detection to decode the entire image at a downscaled resolution once, instead of decoding multiple regions. This improves performance by reducing repeated decoding operations and leverages direct pixel access for edge analysis. Adds a scale factor calculation to balance accuracy and speed for large images.

* Update EdgeDetector.kt

* Update EdgeDetector.kt

* Update EdgeDetector.kt
master
Stanislav Khromov 10 months ago committed by GitHub
parent 97f2ff3bbd
commit 97afa29785
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -25,6 +25,8 @@ import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.core.util.SynchronizedSieveCache import org.koitharu.kotatsu.core.util.SynchronizedSieveCache
import org.koitharu.kotatsu.core.util.ext.use import org.koitharu.kotatsu.core.util.ext.use
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
class EdgeDetector(private val context: Context) { class EdgeDetector(private val context: Context) {
@ -42,26 +44,38 @@ class EdgeDetector(private val context: Context) {
val size = runInterruptible { val size = runInterruptible {
decoder.init(context, imageSource) decoder.init(context, imageSource)
} }
val edges = coroutineScope { val scaleFactor = calculateScaleFactor(size)
listOf( val sampleSize = (1f / scaleFactor).toInt().coerceAtLeast(1)
async { detectLeftRightEdge(decoder, size, isLeft = true) },
async { detectTopBottomEdge(decoder, size, isTop = true) }, val fullBitmap = decoder.decodeRegion(
async { detectLeftRightEdge(decoder, size, isLeft = false) }, Rect(0, 0, size.x, size.y),
async { detectTopBottomEdge(decoder, size, isTop = false) }, sampleSize
).awaitAll() )
}
var hasEdges = false try {
for (edge in edges) { val edges = coroutineScope {
if (edge > 0) { listOf(
hasEdges = true async { detectLeftRightEdge(fullBitmap, size, sampleSize, isLeft = true) },
} else if (edge < 0) { async { detectTopBottomEdge(fullBitmap, size, sampleSize, isTop = true) },
return@withContext null async { detectLeftRightEdge(fullBitmap, size, sampleSize, isLeft = false) },
async { detectTopBottomEdge(fullBitmap, size, sampleSize, isTop = false) },
).awaitAll()
} }
} var hasEdges = false
if (hasEdges) { for (edge in edges) {
Rect(edges[0], edges[1], size.x - edges[2], size.y - edges[3]) if (edge > 0) {
} else { hasEdges = true
null } else if (edge < 0) {
return@withContext null
}
}
if (hasEdges) {
Rect(edges[0], edges[1], size.x - edges[2], size.y - edges[3])
} else {
null
}
} finally {
fullBitmap.recycle()
} }
} finally { } finally {
decoder.recycle() decoder.recycle()
@ -72,10 +86,15 @@ class EdgeDetector(private val context: Context) {
} }
} }
private fun detectLeftRightEdge(decoder: ImageRegionDecoder, size: Point, isLeft: Boolean): Int { private fun detectLeftRightEdge(bitmap: Bitmap, size: Point, sampleSize: Int, isLeft: Boolean): Int {
var width = size.x var width = size.x
val rectCount = size.x / BLOCK_SIZE val rectCount = size.x / BLOCK_SIZE
val maxRect = rectCount / 3 val maxRect = rectCount / 3
val blockPixels = IntArray(BLOCK_SIZE * BLOCK_SIZE)
val bitmapWidth = bitmap.width
val bitmapHeight = bitmap.height
for (i in 0 until rectCount) { for (i in 0 until rectCount) {
if (i > maxRect) { if (i > maxRect) {
return -1 return -1
@ -83,13 +102,24 @@ class EdgeDetector(private val context: Context) {
var dd = BLOCK_SIZE var dd = BLOCK_SIZE
for (j in 0 until size.y / BLOCK_SIZE) { for (j in 0 until size.y / BLOCK_SIZE) {
val regionX = if (isLeft) i * BLOCK_SIZE else size.x - (i + 1) * BLOCK_SIZE val regionX = if (isLeft) i * BLOCK_SIZE else size.x - (i + 1) * BLOCK_SIZE
decoder.decodeRegion(region(regionX, j * BLOCK_SIZE), 1).use { bitmap -> val regionY = j * BLOCK_SIZE
for (ii in 0 until minOf(BLOCK_SIZE, dd)) {
for (jj in 0 until BLOCK_SIZE) { // Convert to bitmap coordinates
val bi = if (isLeft) ii else BLOCK_SIZE - ii - 1 val bitmapX = regionX / sampleSize
if (bitmap[bi, jj].isNotWhite()) { val bitmapY = regionY / sampleSize
width = minOf(width, BLOCK_SIZE * i + ii) val blockWidth = min(BLOCK_SIZE / sampleSize, bitmapWidth - bitmapX)
dd-- val blockHeight = min(BLOCK_SIZE / sampleSize, bitmapHeight - bitmapY)
if (blockWidth > 0 && blockHeight > 0) {
bitmap.getPixels(blockPixels, 0, blockWidth, bitmapX, bitmapY, blockWidth, blockHeight)
for (ii in 0 until minOf(blockWidth, dd / sampleSize)) {
for (jj in 0 until blockHeight) {
val bi = if (isLeft) ii else blockWidth - ii - 1
val pixel = blockPixels[jj * blockWidth + bi]
if (pixel.isNotWhite()) {
width = minOf(width, BLOCK_SIZE * i + ii * sampleSize)
dd -= sampleSize
break break
} }
} }
@ -106,24 +136,40 @@ class EdgeDetector(private val context: Context) {
return width return width
} }
private fun detectTopBottomEdge(decoder: ImageRegionDecoder, size: Point, isTop: Boolean): Int { private fun detectTopBottomEdge(bitmap: Bitmap, size: Point, sampleSize: Int, isTop: Boolean): Int {
var height = size.y var height = size.y
val rectCount = size.y / BLOCK_SIZE val rectCount = size.y / BLOCK_SIZE
val maxRect = rectCount / 3 val maxRect = rectCount / 3
val blockPixels = IntArray(BLOCK_SIZE * BLOCK_SIZE)
val bitmapWidth = bitmap.width
val bitmapHeight = bitmap.height
for (j in 0 until rectCount) { for (j in 0 until rectCount) {
if (j > maxRect) { if (j > maxRect) {
return -1 return -1
} }
var dd = BLOCK_SIZE var dd = BLOCK_SIZE
for (i in 0 until size.x / BLOCK_SIZE) { for (i in 0 until size.x / BLOCK_SIZE) {
val regionX = i * BLOCK_SIZE
val regionY = if (isTop) j * BLOCK_SIZE else size.y - (j + 1) * BLOCK_SIZE val regionY = if (isTop) j * BLOCK_SIZE else size.y - (j + 1) * BLOCK_SIZE
decoder.decodeRegion(region(i * BLOCK_SIZE, regionY), 1).use { bitmap ->
for (jj in 0 until minOf(BLOCK_SIZE, dd)) { // Convert to bitmap coordinates
for (ii in 0 until BLOCK_SIZE) { val bitmapX = regionX / sampleSize
val bj = if (isTop) jj else BLOCK_SIZE - jj - 1 val bitmapY = regionY / sampleSize
if (bitmap[ii, bj].isNotWhite()) { val blockWidth = min(BLOCK_SIZE / sampleSize, bitmapWidth - bitmapX)
height = minOf(height, BLOCK_SIZE * j + jj) val blockHeight = min(BLOCK_SIZE / sampleSize, bitmapHeight - bitmapY)
dd--
if (blockWidth > 0 && blockHeight > 0) {
bitmap.getPixels(blockPixels, 0, blockWidth, bitmapX, bitmapY, blockWidth, blockHeight)
for (jj in 0 until minOf(blockHeight, dd / sampleSize)) {
for (ii in 0 until blockWidth) {
val bj = if (isTop) jj else blockHeight - jj - 1
val pixel = blockPixels[bj * blockWidth + ii]
if (pixel.isNotWhite()) {
height = minOf(height, BLOCK_SIZE * j + jj * sampleSize)
dd -= sampleSize
break break
} }
} }
@ -140,6 +186,20 @@ class EdgeDetector(private val context: Context) {
return height return height
} }
/**
* Calculate scale factor for performance optimization.
* Large images can be downscaled for edge detection without losing accuracy.
*/
private fun calculateScaleFactor(size: Point): Float {
val maxDimension = max(size.x, size.y)
return when {
maxDimension <= 1024 -> 1.0f
maxDimension <= 2048 -> 0.75f
maxDimension <= 4096 -> 0.5f
else -> 0.25f
}
}
companion object { companion object {
private const val BLOCK_SIZE = 100 private const val BLOCK_SIZE = 100

Loading…
Cancel
Save