Fix download splitter potentially throwing OOM on huge images (#7099)

* Fix download splitter potentially throwing OOM on huge images

Also move the splitting to ImageUtil

* Change variable name and logcat output
This commit is contained in:
FourTOne5
2022-05-11 03:06:18 +06:00
committed by GitHub
parent e7ed130f2a
commit 9f655e0d41
4 changed files with 90 additions and 66 deletions

View File

@@ -1,8 +1,6 @@
package eu.kanade.tachiyomi.data.download
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.webkit.MimeTypeMap
import com.hippo.unifile.UniFile
import com.jakewharton.rxrelay.BehaviorRelay
@@ -29,8 +27,6 @@ import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.saveTo
import eu.kanade.tachiyomi.util.system.ImageUtil
import eu.kanade.tachiyomi.util.system.ImageUtil.isAnimatedAndSupported
import eu.kanade.tachiyomi.util.system.ImageUtil.isTallImage
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.async
import logcat.LogPriority
@@ -42,12 +38,9 @@ import rx.subscriptions.CompositeSubscription
import uy.kohesive.injekt.injectLazy
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.util.zip.CRC32
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import kotlin.math.ceil
import kotlin.math.min
/**
* This class is the one in charge of downloading chapters.
@@ -353,9 +346,7 @@ class Downloader(
.onBackpressureLatest()
// Do when page is downloaded.
.doOnNext { page ->
if (preferences.splitTallImages().get()) {
splitTallImage(page, tmpDir)
}
splitTallImageIfNeeded(page, tmpDir)
notifier.onProgressChange(download)
}
.toList()
@@ -364,6 +355,7 @@ class Downloader(
.doOnNext { ensureSuccessfulDownload(download, mangaDir, tmpDir, chapterDirname) }
// If the page list threw, it will resume here
.onErrorReturn { error ->
logcat(LogPriority.ERROR, error)
download.status = Download.State.ERROR
notifier.onError(error.message, download.chapter.name, download.manga.title)
download
@@ -487,6 +479,18 @@ class Downloader(
return MimeTypeMap.getSingleton().getExtensionFromMimeType(mime) ?: "jpg"
}
private fun splitTallImageIfNeeded(page: Page, tmpDir: UniFile) {
if (!preferences.splitTallImages().get()) return
val filename = String.format("%03d", page.number)
val imageFile = tmpDir.listFiles()?.find { it.name!!.startsWith("$filename.") }
?: throw Error(context.getString(R.string.download_notifier_split_page_not_found, page.number))
val imageFilePath = imageFile.filePath
?: throw Error(context.getString(R.string.download_notifier_split_page_path_not_found, page.number))
ImageUtil.splitTallImage(imageFile, imageFilePath)
}
/**
* Checks if the download was successful.
*
@@ -557,48 +561,6 @@ class Downloader(
tmpDir.delete()
}
/**
* Splits tall images to improve performance of reader
*/
private fun splitTallImage(page: Page, tmpDir: UniFile) {
val filename = String.format("%03d", page.number)
val imageFile = tmpDir.listFiles()?.find { it.name!!.startsWith("$filename.") }
?: throw Error(context.getString(R.string.download_notifier_split_page_not_found, page.number))
if (isAnimatedAndSupported(imageFile.openInputStream()) || !isTallImage(imageFile.openInputStream())) {
return
}
val bitmap = BitmapFactory.decodeFile(imageFile.filePath)
val splitsCount = bitmap.height / context.resources.displayMetrics.heightPixels + 1
val heightPerSplit = ceil(bitmap.height / splitsCount.toDouble()).toInt()
logcat { "Splitting height ${bitmap.height} by $splitsCount * $heightPerSplit" }
try {
(0 until splitsCount).forEach { split ->
logcat { "Split #$split at y=${split * heightPerSplit}" }
val splitPath = imageFile.filePath!!.substringBeforeLast(".") + "__${"%03d".format(split + 1)}.jpg"
val splitHeight = split * heightPerSplit
FileOutputStream(splitPath).use { stream ->
Bitmap.createBitmap(
bitmap,
0,
splitHeight,
bitmap.width,
min(heightPerSplit, bitmap.height - splitHeight),
).compress(Bitmap.CompressFormat.JPEG, 100, stream)
}
}
imageFile.delete()
} catch (e: Exception) {
// Image splits were not successfully saved so delete them and keep the original image
(0 until splitsCount)
.map { imageFile.filePath!!.substringBeforeLast(".") + "__${"%03d".format(it + 1)}.jpg" }
.forEach { File(it).delete() }
throw e
}
}
/**
* Completes a download. This method is called in the main thread.
*/