mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-04 08:08:55 +01:00 
			
		
		
		
	Downloader: Optimize split tall image (#7435)
This commit is contained in:
		@@ -493,7 +493,12 @@ class Downloader(
 | 
			
		||||
        // check if the original page was previously splitted before then skip.
 | 
			
		||||
        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
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -185,7 +185,7 @@ object ImageUtil {
 | 
			
		||||
     * @return true if the height:width ratio is greater than 3.
 | 
			
		||||
     */
 | 
			
		||||
    private fun isTallImage(imageStream: InputStream): Boolean {
 | 
			
		||||
        val options = extractImageOptions(imageStream, false)
 | 
			
		||||
        val options = extractImageOptions(imageStream, resetAfterExtraction = false)
 | 
			
		||||
        return (options.outHeight / options.outWidth) > 3
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -197,16 +197,34 @@ object ImageUtil {
 | 
			
		||||
            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
 | 
			
		||||
        val imageHeight = options.outHeight
 | 
			
		||||
        val imageWidth = options.outWidth
 | 
			
		||||
 | 
			
		||||
        val splitHeight = getDisplayMaxHeightInPx
 | 
			
		||||
        val splitHeight = (getDisplayMaxHeightInPx * 1.5).toInt()
 | 
			
		||||
        // -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) {
 | 
			
		||||
            BitmapRegionDecoder.newInstance(imageFile.openInputStream())
 | 
			
		||||
@@ -220,36 +238,52 @@ object ImageUtil {
 | 
			
		||||
            return false
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            (0 until partCount).forEach { splitIndex ->
 | 
			
		||||
                val splitPath = imageFilePath.substringBeforeLast(".") + "__${"%03d".format(splitIndex + 1)}.jpg"
 | 
			
		||||
        logcat {
 | 
			
		||||
            "Splitting image with height of $imageHeight into $partCount part " +
 | 
			
		||||
                "with estimated ${optimalSplitHeight}px height per split"
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
                val topOffset = splitIndex * splitHeight
 | 
			
		||||
                val outputImageHeight = min(splitHeight, imageHeight - topOffset)
 | 
			
		||||
                val bottomOffset = topOffset + outputImageHeight
 | 
			
		||||
                logcat { "Split #$splitIndex with topOffset=$topOffset height=$outputImageHeight bottomOffset=$bottomOffset" }
 | 
			
		||||
        return try {
 | 
			
		||||
            splitDataList.forEach { splitData ->
 | 
			
		||||
                val splitPath = splitImagePath(imageFilePath, splitData.index)
 | 
			
		||||
 | 
			
		||||
                val region = Rect(0, topOffset, imageWidth, bottomOffset)
 | 
			
		||||
                val region = Rect(0, splitData.topOffset, imageWidth, splitData.bottomOffset)
 | 
			
		||||
 | 
			
		||||
                FileOutputStream(splitPath).use { outputStream ->
 | 
			
		||||
                    val splitBitmap = bitmapRegionDecoder.decodeRegion(region, options)
 | 
			
		||||
                    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()
 | 
			
		||||
            return true
 | 
			
		||||
            true
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
            // Image splits were not successfully saved so delete them and keep the original image
 | 
			
		||||
            (0 until partCount)
 | 
			
		||||
                .map { imageFilePath.substringBeforeLast(".") + "__${"%03d".format(it + 1)}.jpg" }
 | 
			
		||||
            splitDataList
 | 
			
		||||
                .map { splitImagePath(imageFilePath, it.index) }
 | 
			
		||||
                .forEach { File(it).delete() }
 | 
			
		||||
            logcat(LogPriority.ERROR, e)
 | 
			
		||||
            return false
 | 
			
		||||
            false
 | 
			
		||||
        } finally {
 | 
			
		||||
            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
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user