Downloader: Optimize split tall image (#7435)

This commit is contained in:
AntsyLich 2022-07-02 22:49:50 +06:00 committed by GitHub
parent deaded5af2
commit ff32ab09fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 57 additions and 18 deletions

View file

@ -493,7 +493,12 @@ class Downloader(
// check if the original page was previously splitted before then skip. // check if the original page was previously splitted before then skip.
if (imageFile.name!!.contains("__")) return true if (imageFile.name!!.contains("__")) return true
return ImageUtil.splitTallImage(imageFile, imageFilePath) return try {
ImageUtil.splitTallImage(imageFile, imageFilePath)
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
false
}
} }
/** /**

View file

@ -185,7 +185,7 @@ object ImageUtil {
* @return true if the height:width ratio is greater than 3. * @return true if the height:width ratio is greater than 3.
*/ */
private fun isTallImage(imageStream: InputStream): Boolean { private fun isTallImage(imageStream: InputStream): Boolean {
val options = extractImageOptions(imageStream, false) val options = extractImageOptions(imageStream, resetAfterExtraction = false)
return (options.outHeight / options.outWidth) > 3 return (options.outHeight / options.outWidth) > 3
} }
@ -197,16 +197,34 @@ object ImageUtil {
return true return true
} }
val options = extractImageOptions(imageFile.openInputStream(), false).apply { inJustDecodeBounds = false } val options = extractImageOptions(imageFile.openInputStream(), resetAfterExtraction = false).apply { inJustDecodeBounds = false }
// Values are stored as they get modified during split loop // Values are stored as they get modified during split loop
val imageHeight = options.outHeight val imageHeight = options.outHeight
val imageWidth = options.outWidth val imageWidth = options.outWidth
val splitHeight = getDisplayMaxHeightInPx val splitHeight = (getDisplayMaxHeightInPx * 1.5).toInt()
// -1 so it doesn't try to split when imageHeight = getDisplayHeightInPx // -1 so it doesn't try to split when imageHeight = getDisplayHeightInPx
val partCount = (imageHeight - 1) / getDisplayMaxHeightInPx + 1 val partCount = (imageHeight - 1) / splitHeight + 1
logcat { "Splitting ${imageHeight}px height image into $partCount part with estimated ${splitHeight}px per height" } val optimalSplitHeight = imageHeight / partCount
val splitDataList = (0 until partCount).fold(mutableListOf<SplitData>()) { list, index ->
list.apply {
// Only continue if the list is empty or there is image remaining
if (isEmpty() || imageHeight > last().bottomOffset) {
val topOffset = index * optimalSplitHeight
var outputImageHeight = min(optimalSplitHeight, imageHeight - topOffset)
val remainingHeight = imageHeight - (topOffset + outputImageHeight)
// If remaining height is smaller or equal to 1/3th of
// optimal split height then include it in current page
if (remainingHeight <= (optimalSplitHeight / 3)) {
outputImageHeight += remainingHeight
}
add(SplitData(index, topOffset, outputImageHeight))
}
}
}
val bitmapRegionDecoder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { val bitmapRegionDecoder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
BitmapRegionDecoder.newInstance(imageFile.openInputStream()) BitmapRegionDecoder.newInstance(imageFile.openInputStream())
@ -220,36 +238,52 @@ object ImageUtil {
return false return false
} }
try { logcat {
(0 until partCount).forEach { splitIndex -> "Splitting image with height of $imageHeight into $partCount part " +
val splitPath = imageFilePath.substringBeforeLast(".") + "__${"%03d".format(splitIndex + 1)}.jpg" "with estimated ${optimalSplitHeight}px height per split"
}
val topOffset = splitIndex * splitHeight return try {
val outputImageHeight = min(splitHeight, imageHeight - topOffset) splitDataList.forEach { splitData ->
val bottomOffset = topOffset + outputImageHeight val splitPath = splitImagePath(imageFilePath, splitData.index)
logcat { "Split #$splitIndex with topOffset=$topOffset height=$outputImageHeight bottomOffset=$bottomOffset" }
val region = Rect(0, topOffset, imageWidth, bottomOffset) val region = Rect(0, splitData.topOffset, imageWidth, splitData.bottomOffset)
FileOutputStream(splitPath).use { outputStream -> FileOutputStream(splitPath).use { outputStream ->
val splitBitmap = bitmapRegionDecoder.decodeRegion(region, options) val splitBitmap = bitmapRegionDecoder.decodeRegion(region, options)
splitBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream) splitBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
splitBitmap.recycle()
}
logcat {
"Success: Split #${splitData.index + 1} with topOffset=${splitData.topOffset} " +
"height=${splitData.outputImageHeight} bottomOffset=${splitData.bottomOffset}"
} }
} }
imageFile.delete() imageFile.delete()
return true true
} catch (e: Exception) { } catch (e: Exception) {
// Image splits were not successfully saved so delete them and keep the original image // Image splits were not successfully saved so delete them and keep the original image
(0 until partCount) splitDataList
.map { imageFilePath.substringBeforeLast(".") + "__${"%03d".format(it + 1)}.jpg" } .map { splitImagePath(imageFilePath, it.index) }
.forEach { File(it).delete() } .forEach { File(it).delete() }
logcat(LogPriority.ERROR, e) logcat(LogPriority.ERROR, e)
return false false
} finally { } finally {
bitmapRegionDecoder.recycle() bitmapRegionDecoder.recycle()
} }
} }
private fun splitImagePath(imageFilePath: String, index: Int) =
imageFilePath.substringBeforeLast(".") + "__${"%03d".format(index + 1)}.jpg"
data class SplitData(
val index: Int,
val topOffset: Int,
val outputImageHeight: Int,
) {
val bottomOffset = topOffset + outputImageHeight
}
/** /**
* Algorithm for determining what background to accompany a comic/manga page * Algorithm for determining what background to accompany a comic/manga page
*/ */