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:
parent
c4088bad12
commit
aa11902aa1
@ -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.
|
||||
*/
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user