From 2a5581df984b5bbc492426b309a925d39b368faa 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. (cherry picked from commit 8b9faa383b08affb5f95e3b8ce5212985d90e505) --- CHANGELOG.md | 3 + .../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, 3 insertions(+), 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 fa2f7d6f6f..264589d2d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co ### Improved - Create and restore backups faster and with less memory ([@NGB-Was-Taken](https://github.com/NGB-Was-Taken), [@AntsyLich](https://github.com/AntsyLich)) +### Removed +- Remove the "Split tall images" download setting ([@AntsyLich](https://github.com/AntsyLich)) + ## [v0.21.2] - 2026-10-13 ### Fixed - Fix long floating point numbers showing up in Comick progress ([@MajorTanya](https://github.com/MajorTanya)) ([#4090](https://github.com/mihonapp/mihon/pull/4090)) 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 7dfbd604df..17035b8dbc 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 105c153ef1..ca17339b89 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 304a6bf751..2088d06958 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 @@ -198,120 +198,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 - /** * Taken from Coil * (https://github.com/coil-kt/coil/blob/1674d3516f061aeacbe749a435b1924f9648fd41/coil-core/src/androidMain/kotlin/coil3/util/hardwareBitmaps.kt) @@ -699,6 +574,3 @@ object ImageUtil { else -> false } } - -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 9613812ae0..0000000000 --- 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 dd3f7a560d..0000000000 --- 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 5795dc89e2..d2fb65a677 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 87ae89ded4..a297bf8dbc 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -551,8 +551,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 @@ -1012,7 +1010,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