mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-31 06:17:57 +01:00 
			
		
		
		
	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:
		| @@ -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> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user