From 8b9faa383b08affb5f95e3b8ce5212985d90e505 Mon Sep 17 00:00:00 2001 From: AntsyLich <59261191+AntsyLich@users.noreply.github.com> Date: Mon, 19 Oct 2026 00:13:57 +0600 Subject: [PATCH] Remove the option to split tall images when downloading The long strip reader decodes every page in strips of about 4MB since b585613, so splitting at download time no longer helps it. The split also re-encoded the pieces as JPEG, turning PNG and WebP pages lossy, and sized them for the screen of the device that downloaded the chapter. --- CHANGELOG.md | 1 + .../settings/screen/SettingsDownloadScreen.kt | 5 - .../tachiyomi/data/download/Downloader.kt | 19 --- .../core/common/util/system/ImageUtil.kt | 128 ------------------ .../util/system/TallImageSplitCalculator.kt | 17 --- .../system/TallImageSplitCalculatorTest.kt | 37 ----- .../download/service/DownloadPreferences.kt | 2 - .../moko-resources/base/strings.xml | 3 - 8 files changed, 1 insertion(+), 211 deletions(-) delete mode 100644 core/common/src/main/kotlin/tachiyomi/core/common/util/system/TallImageSplitCalculator.kt delete mode 100644 core/common/src/test/java/tachiyomi/core/common/util/system/TallImageSplitCalculatorTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 41cae794a..423592307 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co ### Removed - Remove "Custom hardware bitmap threshold" and "Custom display profile" advanced settings ([@wwww-wwww](https://github.com/wwww-wwww)) ([#3786](https://github.com/mihonapp/mihon/pull/3786)) +- Remove the "Split tall images" download setting ([@AntsyLich](https://github.com/AntsyLich)) ## [v0.21.2] - 2026-10-13 ### Fixed diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDownloadScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDownloadScreen.kt index 7dfbd604d..17035b8db 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDownloadScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDownloadScreen.kt @@ -43,11 +43,6 @@ object SettingsDownloadScreen : SearchableSettings { preference = downloadPreferences.saveChaptersAsCBZ, title = stringResource(MR.strings.save_chapter_as_cbz), ), - Preference.PreferenceItem.SwitchPreference( - preference = downloadPreferences.splitTallImages, - title = stringResource(MR.strings.split_tall_images), - subtitle = stringResource(MR.strings.split_tall_images_summary), - ), Preference.PreferenceItem.SliderPreference( preference = downloadPreferences.parallelSourceLimit, valueRange = 1..10, diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index 105c153ef..ca17339b8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -551,8 +551,6 @@ class Downloader( } // When the page is ready, set page path, progress (just in case) and status - splitTallImageIfNeeded(page, tmpDir) - page.uri = file.uri page.progress = 100 page.status = Page.State.Ready @@ -645,23 +643,6 @@ class Downloader( return ImageUtil.getExtensionFromMimeType(mime) { file.openInputStream() } } - private fun splitTallImageIfNeeded(page: Page, tmpDir: UniFile) { - if (!downloadPreferences.splitTallImages.get()) return - - try { - val filenamePrefix = "%03d".format(Locale.ENGLISH, page.number) - val imageFile = tmpDir.listFiles()?.firstOrNull { it.name.orEmpty().startsWith(filenamePrefix) } - ?: error(context.stringResource(MR.strings.download_notifier_split_page_not_found, page.number)) - - // If the original page was previously split, then skip - if (imageFile.name.orEmpty().startsWith("${filenamePrefix}__")) return - - ImageUtil.splitTallImage(tmpDir, imageFile, filenamePrefix) - } catch (e: Exception) { - logcat(LogPriority.ERROR, e) { "Failed to split downloaded image" } - } - } - /** * Checks if the download was successful. * diff --git a/core/common/src/main/kotlin/tachiyomi/core/common/util/system/ImageUtil.kt b/core/common/src/main/kotlin/tachiyomi/core/common/util/system/ImageUtil.kt index b40468474..fdf0568ee 100644 --- a/core/common/src/main/kotlin/tachiyomi/core/common/util/system/ImageUtil.kt +++ b/core/common/src/main/kotlin/tachiyomi/core/common/util/system/ImageUtil.kt @@ -185,120 +185,6 @@ object ImageUtil { LEFT, } - /** - * Check whether the image is considered a tall image. - * - * @return true if the height:width ratio is greater than 3. - */ - private fun isTallImage(imageSource: BufferedSource): Boolean { - val options = extractImageOptions(imageSource) - return TallImageSplitCalculator.shouldSplit( - imageWidth = options.outWidth, - imageHeight = options.outHeight, - optimalImageHeight = optimalImageHeight, - ) - } - - /** - * Splits tall images to improve performance of reader - */ - fun splitTallImage(tmpDir: UniFile, imageFile: UniFile, filenamePrefix: String): Boolean { - val imageSource = imageFile.openInputStream().use { Buffer().readFrom(it) } - if (isAnimatedAndSupported(imageSource) || !isTallImage(imageSource)) { - return true - } - - val bitmapRegionDecoder = getBitmapRegionDecoder(imageSource.peek().inputStream()) - if (bitmapRegionDecoder == null) { - logcat { "Failed to create new instance of BitmapRegionDecoder" } - return false - } - - val options = extractImageOptions(imageSource).apply { - inJustDecodeBounds = false - } - val splitDataList = options.splitData - - return try { - splitDataList.forEach { splitData -> - val splitImageName = splitImageName(filenamePrefix, splitData.index) - // Remove pre-existing split if exists (this split shouldn't exist under normal circumstances) - tmpDir.findFile(splitImageName)?.delete() - - val splitFile = tmpDir.createFile(splitImageName)!! - - val region = Rect(0, splitData.topOffset, splitData.splitWidth, splitData.bottomOffset) - - splitFile.openOutputStream().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.splitHeight} bottomOffset=${splitData.bottomOffset}" - } - } - imageFile.delete() - true - } catch (e: Exception) { - // Image splits were not successfully saved so delete them and keep the original image - splitDataList - .map { splitImageName(filenamePrefix, it.index) } - .forEach { tmpDir.findFile(it)?.delete() } - logcat(LogPriority.ERROR, e) - false - } finally { - bitmapRegionDecoder.recycle() - } - } - - private fun splitImageName(filenamePrefix: String, index: Int) = "${filenamePrefix}__${"%03d".format( - Locale.ENGLISH, - index + 1, - )}.jpg" - - private val BitmapFactory.Options.splitData - get(): List { - val imageHeight = outHeight - val imageWidth = outWidth - - val partCount = TallImageSplitCalculator.calculatePartCount(imageHeight, optimalImageHeight) - val optimalSplitHeight = imageHeight / partCount - - logcat { - "Generating SplitData for image (height: $imageHeight): " + - "$partCount parts @ ${optimalSplitHeight}px height per part" - } - - return buildList { - val range = 0..= Build.VERSION_CODES.S) { - BitmapRegionDecoder.newInstance(imageStream) - } else { - @Suppress("DEPRECATION") - BitmapRegionDecoder.newInstance(imageStream, false) - } - } - - private val optimalImageHeight = getDisplayMaxHeightInPx * 2 } - -val getDisplayMaxHeightInPx: Int - get() = Resources.getSystem().displayMetrics.let { max(it.heightPixels, it.widthPixels) } diff --git a/core/common/src/main/kotlin/tachiyomi/core/common/util/system/TallImageSplitCalculator.kt b/core/common/src/main/kotlin/tachiyomi/core/common/util/system/TallImageSplitCalculator.kt deleted file mode 100644 index 9613812ae..000000000 --- a/core/common/src/main/kotlin/tachiyomi/core/common/util/system/TallImageSplitCalculator.kt +++ /dev/null @@ -1,17 +0,0 @@ -package tachiyomi.core.common.util.system - -internal object TallImageSplitCalculator { - - fun calculatePartCount(imageHeight: Int, optimalImageHeight: Int): Int { - require(imageHeight > 0) { "imageHeight must be positive" } - require(optimalImageHeight > 0) { "optimalImageHeight must be positive" } - // -1 so it doesn't try to split when imageHeight = optimalImageHeight - return (imageHeight - 1) / optimalImageHeight + 1 - } - - fun shouldSplit(imageWidth: Int, imageHeight: Int, optimalImageHeight: Int): Boolean { - require(imageWidth > 0) { "imageWidth must be positive" } - return imageHeight > imageWidth * 3 && - calculatePartCount(imageHeight, optimalImageHeight) > 1 - } -} diff --git a/core/common/src/test/java/tachiyomi/core/common/util/system/TallImageSplitCalculatorTest.kt b/core/common/src/test/java/tachiyomi/core/common/util/system/TallImageSplitCalculatorTest.kt deleted file mode 100644 index dd3f7a560..000000000 --- a/core/common/src/test/java/tachiyomi/core/common/util/system/TallImageSplitCalculatorTest.kt +++ /dev/null @@ -1,37 +0,0 @@ -package tachiyomi.core.common.util.system - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertFalse -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.Test - -class TallImageSplitCalculatorTest { - - @Test - fun `does not split when aspect ratio is tall but computed split count is one`() { - assertFalse( - TallImageSplitCalculator.shouldSplit( - imageWidth = 1024, - imageHeight = 4385, - optimalImageHeight = 4386, - ), - ) - } - - @Test - fun `splits when aspect ratio is tall and computed split count is greater than one`() { - assertTrue( - TallImageSplitCalculator.shouldSplit( - imageWidth = 1024, - imageHeight = 4385, - optimalImageHeight = 4384, - ), - ) - } - - @Test - fun `calculate part count rounds boundary correctly`() { - assertEquals(1, TallImageSplitCalculator.calculatePartCount(imageHeight = 4384, optimalImageHeight = 4384)) - assertEquals(2, TallImageSplitCalculator.calculatePartCount(imageHeight = 4385, optimalImageHeight = 4384)) - } -} diff --git a/domain/src/main/java/tachiyomi/domain/download/service/DownloadPreferences.kt b/domain/src/main/java/tachiyomi/domain/download/service/DownloadPreferences.kt index 5795dc89e..d2fb65a67 100644 --- a/domain/src/main/java/tachiyomi/domain/download/service/DownloadPreferences.kt +++ b/domain/src/main/java/tachiyomi/domain/download/service/DownloadPreferences.kt @@ -19,8 +19,6 @@ class DownloadPreferences( val saveChaptersAsCBZ: Preference = preferenceStore.getBoolean("save_chapter_as_cbz", true) - val splitTallImages: Preference = preferenceStore.getBoolean("split_tall_images", true) - val autoDownloadWhileReading: Preference = preferenceStore.getInt("auto_download_while_reading", 0) val removeAfterReadSlots: Preference = preferenceStore.getInt("remove_after_read_slots", -1) diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index 4474f5c77..f141009ba 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -577,8 +577,6 @@ Auto download while reading Only works if the current chapter + the next one are already downloaded. Save as CBZ archive - Split tall images - Improves reader performance Concurrent source downloads Concurrent page downloads Pages downloaded simultaneously per source @@ -1038,7 +1036,6 @@ No Wi-Fi connection available No network connection available Downloads paused - Page %d not found while splitting Couldn\'t find file path of page %d Checking downloads