Add option to automatically split tall downloaded images (#7029)

* Auto split long images to improve performance of reader

* Auto split long images to improve performance of reader - fixed the sorting

* Improved performance of splitting by getting rid of 1 extra loop

* Cleaned up code and moved the functionality to work during the downloading process (unsure how this affects download speed)

* Replaced the import .* with the actual used imports

* Fixes for Bugs discovered during my testing

* Fixed last split missing bug.

* Reordered the download progress to be updated before splitting instead of after to reflect more meaningful progress of download

* Reverted last commit since it had no effect

* Improved progress tracking when a download is paused then resumed.

* Implemented the recommended changes to enhance the feature.

* Apply suggestions from code review

Co-authored-by: arkon <arkon@users.noreply.github.com>

* Update app/src/main/res/values/strings.xml

Co-authored-by: arkon <arkon@users.noreply.github.com>

Co-authored-by: arkon <arkon@users.noreply.github.com>
This commit is contained in:
S97 2022-05-07 05:17:27 +03:00 committed by GitHub
parent c4088bad12
commit aa11902aa1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 95 additions and 3 deletions

View File

@ -1,7 +1,10 @@
package eu.kanade.tachiyomi.data.download
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.webkit.MimeTypeMap
import androidx.core.graphics.BitmapCompat
import com.hippo.unifile.UniFile
import com.jakewharton.rxrelay.BehaviorRelay
import com.jakewharton.rxrelay.PublishRelay
@ -27,6 +30,8 @@ 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
@ -38,6 +43,8 @@ import rx.subscriptions.CompositeSubscription
import uy.kohesive.injekt.injectLazy
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStream
import java.util.zip.CRC32
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
@ -345,7 +352,12 @@ class Downloader(
.flatMap({ page -> getOrDownloadImage(page, download, tmpDir) }, 5)
.onBackpressureLatest()
// Do when page is downloaded.
.doOnNext { notifier.onProgressChange(download) }
.doOnNext { page ->
if (preferences.splitTallImages().get()) {
splitTallImage(page, download, tmpDir)
}
notifier.onProgressChange(download)
}
.toList()
.map { download }
// Do after download completes
@ -379,7 +391,7 @@ class Downloader(
tmpFile?.delete()
// Try to find the image file.
val imageFile = tmpDir.listFiles()!!.find { it.name!!.startsWith("$filename.") }
val imageFile = tmpDir.listFiles()!!.find { it.name!!.startsWith("$filename.") || it.name!!.contains("${filename}__001") }
// If the image is already downloaded, do nothing. Otherwise download from network
val pageObservable = when {
@ -490,7 +502,7 @@ class Downloader(
dirname: String,
) {
// Ensure that the chapter folder has all the images.
val downloadedImages = tmpDir.listFiles().orEmpty().filterNot { it.name!!.endsWith(".tmp") }
val downloadedImages = tmpDir.listFiles().orEmpty().filterNot { it.name!!.endsWith(".tmp") || (it.name!!.contains("__") && !it.name!!.contains("__001.jpg")) }
download.status = if (downloadedImages.size == download.pages!!.size) {
Download.State.DOWNLOADED
@ -545,6 +557,57 @@ class Downloader(
tmpDir.delete()
}
/**
* Splits tall images to improve performance of reader
*/
private fun splitTallImage(page: Page, download: Download, tmpDir: UniFile) {
val filename = String.format("%03d", page.number)
val imageFile = tmpDir.listFiles()!!.find { it.name!!.startsWith("$filename.") }
if (imageFile == null) {
notifier.onError("Error: imageFile was not found", download.chapter.name, download.manga.title)
return
}
if (!isAnimatedAndSupported(imageFile.openInputStream()) && isTallImage(imageFile.openInputStream())) {
// Getting the scaled bitmap of the source image
val bitmap = BitmapFactory.decodeFile(imageFile.filePath)
val scaledBitmap: Bitmap =
BitmapCompat.createScaledBitmap(bitmap, bitmap.width, bitmap.height, null, true)
val splitsCount: Int = bitmap.height / context.resources.displayMetrics.heightPixels + 1
val splitHeight = bitmap.height / splitsCount
// xCoord and yCoord are the pixel positions of the image splits
val xCoord = 0
var yCoord = 0
try {
for (i in 0 until splitsCount) {
val splitPath = imageFile.filePath!!.substringBeforeLast(".") + "__${"%03d".format(i + 1)}.jpg"
// Compress the bitmap and save in jpg format
val stream: OutputStream = FileOutputStream(splitPath)
stream.use {
Bitmap.createBitmap(
scaledBitmap,
xCoord,
yCoord,
bitmap.width,
splitHeight,
).compress(Bitmap.CompressFormat.JPEG, 100, stream)
}
yCoord += splitHeight
}
imageFile.delete()
} catch (e: Exception) {
// Image splits were not successfully saved so delete them and keep the original image
for (i in 0 until splitsCount) {
val splitPath = imageFile.filePath!!.substringBeforeLast(".") + "__${"%03d".format(i + 1)}.jpg"
File(splitPath).delete()
}
throw e
}
}
}
/**
* Completes a download. This method is called in the main thread.
*/

View File

@ -200,6 +200,8 @@ class PreferencesHelper(val context: Context) {
fun saveChaptersAsCBZ() = flowPrefs.getBoolean("save_chapter_as_cbz", true)
fun splitTallImages() = flowPrefs.getBoolean("split_tall_images", false)
fun folderPerManga() = prefs.getBoolean(Keys.folderPerManga, false)
fun numberOfBackups() = flowPrefs.getInt("backup_slots", 2)

View File

@ -24,6 +24,7 @@ import eu.kanade.tachiyomi.util.preference.multiSelectListPreference
import eu.kanade.tachiyomi.util.preference.onClick
import eu.kanade.tachiyomi.util.preference.preference
import eu.kanade.tachiyomi.util.preference.preferenceCategory
import eu.kanade.tachiyomi.util.preference.summaryRes
import eu.kanade.tachiyomi.util.preference.switchPreference
import eu.kanade.tachiyomi.util.preference.titleRes
import eu.kanade.tachiyomi.util.system.toast
@ -72,6 +73,12 @@ class SettingsDownloadController : SettingsController() {
bindTo(preferences.saveChaptersAsCBZ())
titleRes = R.string.save_chapter_as_cbz
}
switchPreference {
bindTo(preferences.splitTallImages())
titleRes = R.string.split_tall_images
summaryRes = R.string.split_tall_images_summary
}
preferenceCategory {
titleRes = R.string.pref_category_delete_chapters

View File

@ -115,6 +115,24 @@ object ImageUtil {
return options.outWidth > options.outHeight
}
/**
* Check whether the image is considered a tall image
* @return true if the height:width ratio is greater than the 3:!
*/
fun isTallImage(imageStream: InputStream): Boolean {
imageStream.mark(imageStream.available() + 1)
val imageBytes = imageStream.readBytes()
// Checking the image dimensions without loading it in the memory.
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, options)
val width = options.outWidth
val height = options.outHeight
val ratio = height / width
return ratio > 3
}
/**
* Extract the 'side' part from imageStream and return it as InputStream.
*/

View File

@ -407,6 +407,8 @@
<string name="pref_download_new">Download new chapters</string>
<string name="pref_download_new_categories_details">Manga in excluded categories will not be downloaded even if they are also in included categories.</string>
<string name="save_chapter_as_cbz">Save as CBZ archive</string>
<string name="split_tall_images">Auto split tall images</string>
<string name="split_tall_images_summary">Improves reader performance by splitting tall downloaded images.</string>
<!-- Tracking section -->
<string name="tracking_guide">Tracking guide</string>