Make comic book archive caching optional instead of rolling it back completely (#883)
* Make comic book archive caching optional and fix cbz archives not opening for some users * corrected placement of // SY <--
This commit is contained in:
parent
be6bbb8e9b
commit
6071acd3df
@ -545,6 +545,11 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
3 to stringResource(R.string.center_margin_double_and_wide_page),
|
3 to stringResource(R.string.center_margin_double_and_wide_page),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
pref = readerPreferences.cacheArchiveMangaOnDisk(),
|
||||||
|
title = stringResource(R.string.cache_archived_manga_to_disk),
|
||||||
|
subtitle = stringResource(R.string.cache_archived_manga_to_disk_subtitle),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -522,7 +522,15 @@ class Downloader(
|
|||||||
// If the original page was previously split, then skip
|
// If the original page was previously split, then skip
|
||||||
if (imageFile.name.orEmpty().startsWith("${filenamePrefix}__")) return
|
if (imageFile.name.orEmpty().startsWith("${filenamePrefix}__")) return
|
||||||
|
|
||||||
ImageUtil.splitTallImage(tmpDir, imageFile, filenamePrefix)
|
ImageUtil.splitTallImage(
|
||||||
|
tmpDir,
|
||||||
|
imageFile,
|
||||||
|
filenamePrefix,
|
||||||
|
// SY -->
|
||||||
|
zip4jFile = null,
|
||||||
|
zip4jEntry = null,
|
||||||
|
// SY <--
|
||||||
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logcat(LogPriority.ERROR, e) { "Failed to split downloaded image" }
|
logcat(LogPriority.ERROR, e) { "Failed to split downloaded image" }
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.loader
|
package eu.kanade.tachiyomi.ui.reader.loader
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import com.github.junrar.Archive
|
import com.github.junrar.Archive
|
||||||
import com.github.junrar.rarfile.FileHeader
|
import com.github.junrar.rarfile.FileHeader
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||||
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
|
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
|
||||||
import tachiyomi.core.util.system.ImageUtil
|
import tachiyomi.core.util.system.ImageUtil
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.PipedInputStream
|
import java.io.PipedInputStream
|
||||||
@ -18,9 +21,40 @@ internal class RarPageLoader(file: File) : PageLoader() {
|
|||||||
|
|
||||||
private val rar = Archive(file)
|
private val rar = Archive(file)
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
private val context: Application by injectLazy()
|
||||||
|
private val readerPreferences: ReaderPreferences by injectLazy()
|
||||||
|
private val tmpDir = File(context.externalCacheDir, "reader_${file.hashCode()}").also {
|
||||||
|
it.deleteRecursively()
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (readerPreferences.cacheArchiveMangaOnDisk().get()) {
|
||||||
|
tmpDir.mkdirs()
|
||||||
|
Archive(file).use { rar ->
|
||||||
|
rar.fileHeaders.asSequence()
|
||||||
|
.filterNot { it.isDirectory }
|
||||||
|
.forEach { header ->
|
||||||
|
val pageOutputStream = File(tmpDir, header.fileName.substringAfterLast("/"))
|
||||||
|
.also { it.createNewFile() }
|
||||||
|
.outputStream()
|
||||||
|
getStream(rar, header).use {
|
||||||
|
it.copyTo(pageOutputStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
|
|
||||||
override var isLocal: Boolean = true
|
override var isLocal: Boolean = true
|
||||||
|
|
||||||
override suspend fun getPages(): List<ReaderPage> {
|
override suspend fun getPages(): List<ReaderPage> {
|
||||||
|
// SY -->
|
||||||
|
if (readerPreferences.cacheArchiveMangaOnDisk().get()) {
|
||||||
|
return DirectoryPageLoader(tmpDir).getPages()
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
return rar.fileHeaders.asSequence()
|
return rar.fileHeaders.asSequence()
|
||||||
.filter { !it.isDirectory && ImageUtil.isImage(it.fileName) { rar.getInputStream(it) } }
|
.filter { !it.isDirectory && ImageUtil.isImage(it.fileName) { rar.getInputStream(it) } }
|
||||||
.sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) }
|
.sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) }
|
||||||
@ -40,6 +74,9 @@ internal class RarPageLoader(file: File) : PageLoader() {
|
|||||||
override fun recycle() {
|
override fun recycle() {
|
||||||
super.recycle()
|
super.recycle()
|
||||||
rar.close()
|
rar.close()
|
||||||
|
// SY -->
|
||||||
|
tmpDir.deleteRecursively()
|
||||||
|
// SY <--
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3,13 +3,17 @@ package eu.kanade.tachiyomi.ui.reader.loader
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||||
|
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
|
||||||
import eu.kanade.tachiyomi.util.storage.CbzCrypto
|
import eu.kanade.tachiyomi.util.storage.CbzCrypto
|
||||||
import net.lingala.zip4j.ZipFile
|
|
||||||
import tachiyomi.core.util.system.ImageUtil
|
import tachiyomi.core.util.system.ImageUtil
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.util.zip.ZipFile
|
||||||
|
import net.lingala.zip4j.ZipFile as Zip4jFile
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loader used to load a chapter from a .zip or .cbz file.
|
* Loader used to load a chapter from a .zip or .cbz file.
|
||||||
@ -18,44 +22,99 @@ internal class ZipPageLoader(file: File) : PageLoader() {
|
|||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
private val context: Application by injectLazy()
|
private val context: Application by injectLazy()
|
||||||
|
private val readerPreferences: ReaderPreferences by injectLazy()
|
||||||
private val tmpDir = File(context.externalCacheDir, "reader_${file.hashCode()}").also {
|
private val tmpDir = File(context.externalCacheDir, "reader_${file.hashCode()}").also {
|
||||||
it.deleteRecursively()
|
it.deleteRecursively()
|
||||||
it.mkdirs()
|
}
|
||||||
|
private val zip4j: Zip4jFile = Zip4jFile(file)
|
||||||
|
private val zip: ZipFile? =
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
if (!zip4j.isEncrypted) ZipFile(file, StandardCharsets.ISO_8859_1) else null
|
||||||
|
} else {
|
||||||
|
if (!zip4j.isEncrypted) ZipFile(file) else null
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
ZipFile(file).use { zip ->
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
zip4j.charset = StandardCharsets.ISO_8859_1
|
||||||
|
}
|
||||||
|
|
||||||
|
Zip4jFile(file).use { zip ->
|
||||||
if (zip.isEncrypted) {
|
if (zip.isEncrypted) {
|
||||||
if (!CbzCrypto.checkCbzPassword(zip, CbzCrypto.getDecryptedPasswordCbz())) {
|
if (!CbzCrypto.checkCbzPassword(zip, CbzCrypto.getDecryptedPasswordCbz())) {
|
||||||
this.recycle()
|
this.recycle()
|
||||||
throw IllegalStateException(context.getString(R.string.wrong_cbz_archive_password))
|
throw IllegalStateException(context.getString(R.string.wrong_cbz_archive_password))
|
||||||
}
|
}
|
||||||
unzip(zip, CbzCrypto.getDecryptedPasswordCbz())
|
zip4j.setPassword(CbzCrypto.getDecryptedPasswordCbz())
|
||||||
|
if (readerPreferences.cacheArchiveMangaOnDisk().get()) {
|
||||||
|
unzip()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
unzip(zip)
|
if (readerPreferences.cacheArchiveMangaOnDisk().get()) {
|
||||||
|
unzip()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private fun unzip(zip: ZipFile, password: CharArray? = null) {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
||||||
zip.charset = StandardCharsets.ISO_8859_1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (password != null) {
|
|
||||||
zip.setPassword(password)
|
|
||||||
}
|
|
||||||
|
|
||||||
zip.fileHeaders.asSequence()
|
|
||||||
.filter { !it.isDirectory && ImageUtil.isImage(it.fileName) { zip.getInputStream(it) } }
|
|
||||||
.forEach { entry ->
|
|
||||||
zip.extractFile(entry, tmpDir.absolutePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// SY <--
|
// SY <--
|
||||||
|
override fun recycle() {
|
||||||
|
super.recycle()
|
||||||
|
zip?.close()
|
||||||
|
// SY -->
|
||||||
|
zip4j.close()
|
||||||
|
tmpDir.deleteRecursively()
|
||||||
|
}
|
||||||
|
private fun unzip() {
|
||||||
|
tmpDir.mkdirs()
|
||||||
|
zip4j.fileHeaders.asSequence()
|
||||||
|
.filter { !it.isDirectory && ImageUtil.isImage(it.fileName) { zip4j.getInputStream(it) } }
|
||||||
|
.forEach { entry ->
|
||||||
|
zip4j.extractFile(entry, tmpDir.absolutePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override var isLocal: Boolean = true
|
override var isLocal: Boolean = true
|
||||||
|
|
||||||
override suspend fun getPages(): List<ReaderPage> {
|
override suspend fun getPages(): List<ReaderPage> {
|
||||||
|
if (readerPreferences.cacheArchiveMangaOnDisk().get()) {
|
||||||
return DirectoryPageLoader(tmpDir).getPages()
|
return DirectoryPageLoader(tmpDir).getPages()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (zip == null) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
zip4j.charset = StandardCharsets.ISO_8859_1
|
||||||
|
}
|
||||||
|
|
||||||
|
return zip4j.fileHeaders.asSequence()
|
||||||
|
.filter { !it.isDirectory && ImageUtil.isImage(it.fileName) { zip4j.getInputStream(it) } }
|
||||||
|
.sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) }
|
||||||
|
.mapIndexed { i, entry ->
|
||||||
|
ReaderPage(i).apply {
|
||||||
|
stream = { zip4j.getInputStream(entry) }
|
||||||
|
status = Page.State.READY
|
||||||
|
zip4jFile = zip4j
|
||||||
|
zip4jEntry = entry
|
||||||
|
}
|
||||||
|
}.toList()
|
||||||
|
} else {
|
||||||
|
// SY <--
|
||||||
|
return zip.entries().asSequence()
|
||||||
|
.filter { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
|
||||||
|
.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
|
||||||
|
.mapIndexed { i, entry ->
|
||||||
|
ReaderPage(i).apply {
|
||||||
|
stream = { zip.getInputStream(entry) }
|
||||||
|
status = Page.State.READY
|
||||||
|
}
|
||||||
|
}.toList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No additional action required to load the page
|
||||||
|
*/
|
||||||
|
override suspend fun loadPage(page: ReaderPage) {
|
||||||
|
check(!isRecycled)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.model
|
package eu.kanade.tachiyomi.ui.reader.model
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
import net.lingala.zip4j.ZipFile
|
||||||
|
import net.lingala.zip4j.model.FileHeader
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
open class ReaderPage(
|
open class ReaderPage(
|
||||||
@ -8,6 +10,9 @@ open class ReaderPage(
|
|||||||
url: String = "",
|
url: String = "",
|
||||||
imageUrl: String? = null,
|
imageUrl: String? = null,
|
||||||
// SY -->
|
// SY -->
|
||||||
|
/** zip4j inputStreams do not support mark() and release(), so they must be passed to ImageUtil */
|
||||||
|
var zip4jFile: ZipFile? = null,
|
||||||
|
var zip4jEntry: FileHeader? = null,
|
||||||
/** Value to check if this page is used to as if it was too wide */
|
/** Value to check if this page is used to as if it was too wide */
|
||||||
var shiftedPage: Boolean = false,
|
var shiftedPage: Boolean = false,
|
||||||
/** Value to check if a page is can be doubled up, but can't because the next page is too wide */
|
/** Value to check if a page is can be doubled up, but can't because the next page is too wide */
|
||||||
|
@ -166,6 +166,8 @@ class ReaderPreferences(
|
|||||||
fun invertDoublePages() = preferenceStore.getBoolean("invert_double_pages", false)
|
fun invertDoublePages() = preferenceStore.getBoolean("invert_double_pages", false)
|
||||||
|
|
||||||
fun centerMarginType() = preferenceStore.getInt("center_margin_type", PagerConfig.CenterMarginType.NONE)
|
fun centerMarginType() = preferenceStore.getInt("center_margin_type", PagerConfig.CenterMarginType.NONE)
|
||||||
|
|
||||||
|
fun cacheArchiveMangaOnDisk() = preferenceStore.getBoolean("cache_archive_manga_on_disk", false)
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
enum class TappingInvertMode(val shouldInvertHorizontal: Boolean = false, val shouldInvertVertical: Boolean = false) {
|
enum class TappingInvertMode(val shouldInvertHorizontal: Boolean = false, val shouldInvertVertical: Boolean = false) {
|
||||||
|
@ -229,7 +229,13 @@ class PagerPageHolder(
|
|||||||
return splitInHalf(imageStream)
|
return splitInHalf(imageStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
val isDoublePage = ImageUtil.isWideImage(imageStream)
|
val isDoublePage = ImageUtil.isWideImage(
|
||||||
|
imageStream,
|
||||||
|
// SY -->
|
||||||
|
page.zip4jFile,
|
||||||
|
page.zip4jEntry,
|
||||||
|
// SY <--
|
||||||
|
)
|
||||||
if (!isDoublePage) {
|
if (!isDoublePage) {
|
||||||
return imageStream
|
return imageStream
|
||||||
}
|
}
|
||||||
@ -240,7 +246,13 @@ class PagerPageHolder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun rotateDualPage(imageStream: BufferedInputStream): InputStream {
|
private fun rotateDualPage(imageStream: BufferedInputStream): InputStream {
|
||||||
val isDoublePage = ImageUtil.isWideImage(imageStream)
|
val isDoublePage = ImageUtil.isWideImage(
|
||||||
|
imageStream,
|
||||||
|
// SY -->
|
||||||
|
page.zip4jFile,
|
||||||
|
page.zip4jEntry,
|
||||||
|
// SY <--
|
||||||
|
)
|
||||||
return if (isDoublePage) {
|
return if (isDoublePage) {
|
||||||
val rotation = if (viewer.config.dualPageRotateToFitInvert) -90f else 90f
|
val rotation = if (viewer.config.dualPageRotateToFitInvert) -90f else 90f
|
||||||
ImageUtil.rotateImage(imageStream, rotation)
|
ImageUtil.rotateImage(imageStream, rotation)
|
||||||
@ -254,7 +266,13 @@ class PagerPageHolder(
|
|||||||
if (imageStream2 == null) {
|
if (imageStream2 == null) {
|
||||||
return if (imageStream is BufferedInputStream &&
|
return if (imageStream is BufferedInputStream &&
|
||||||
!ImageUtil.isAnimatedAndSupported(imageStream) &&
|
!ImageUtil.isAnimatedAndSupported(imageStream) &&
|
||||||
ImageUtil.isWideImage(imageStream) &&
|
ImageUtil.isWideImage(
|
||||||
|
imageStream,
|
||||||
|
// SY -->
|
||||||
|
page.zip4jFile,
|
||||||
|
page.zip4jEntry,
|
||||||
|
// SY <--
|
||||||
|
) &&
|
||||||
viewer.config.centerMarginType and PagerConfig.CenterMarginType.WIDE_PAGE_CENTER_MARGIN > 0 &&
|
viewer.config.centerMarginType and PagerConfig.CenterMarginType.WIDE_PAGE_CENTER_MARGIN > 0 &&
|
||||||
!viewer.config.imageCropBorders
|
!viewer.config.imageCropBorders
|
||||||
) {
|
) {
|
||||||
|
@ -213,7 +213,13 @@ class WebtoonPageHolder(
|
|||||||
|
|
||||||
private fun process(imageStream: BufferedInputStream): InputStream {
|
private fun process(imageStream: BufferedInputStream): InputStream {
|
||||||
if (viewer.config.dualPageSplit) {
|
if (viewer.config.dualPageSplit) {
|
||||||
val isDoublePage = ImageUtil.isWideImage(imageStream)
|
val isDoublePage = ImageUtil.isWideImage(
|
||||||
|
imageStream,
|
||||||
|
// SY -->
|
||||||
|
page?.zip4jFile,
|
||||||
|
page?.zip4jEntry,
|
||||||
|
// SY <--
|
||||||
|
)
|
||||||
if (isDoublePage) {
|
if (isDoublePage) {
|
||||||
val upperSide = if (viewer.config.dualPageInvert) ImageUtil.Side.LEFT else ImageUtil.Side.RIGHT
|
val upperSide = if (viewer.config.dualPageInvert) ImageUtil.Side.LEFT else ImageUtil.Side.RIGHT
|
||||||
return ImageUtil.splitAndMerge(imageStream, upperSide)
|
return ImageUtil.splitAndMerge(imageStream, upperSide)
|
||||||
@ -224,7 +230,13 @@ class WebtoonPageHolder(
|
|||||||
if (page is StencilPage) {
|
if (page is StencilPage) {
|
||||||
return imageStream
|
return imageStream
|
||||||
}
|
}
|
||||||
val isStripSplitNeeded = ImageUtil.isStripSplitNeeded(imageStream)
|
val isStripSplitNeeded = ImageUtil.isStripSplitNeeded(
|
||||||
|
imageStream,
|
||||||
|
// SY -->
|
||||||
|
page?.zip4jFile,
|
||||||
|
page?.zip4jEntry,
|
||||||
|
// SY <--
|
||||||
|
)
|
||||||
if (isStripSplitNeeded) {
|
if (isStripSplitNeeded) {
|
||||||
return onStripSplit(imageStream)
|
return onStripSplit(imageStream)
|
||||||
}
|
}
|
||||||
@ -237,7 +249,13 @@ class WebtoonPageHolder(
|
|||||||
// If we have reached this point [page] and its stream shouldn't be null
|
// If we have reached this point [page] and its stream shouldn't be null
|
||||||
val page = page!!
|
val page = page!!
|
||||||
val stream = page.stream!!
|
val stream = page.stream!!
|
||||||
val splitData = ImageUtil.getSplitDataForStream(imageStream).toMutableList()
|
val splitData = ImageUtil.getSplitDataForStream(
|
||||||
|
imageStream,
|
||||||
|
// SY -->
|
||||||
|
page.zip4jFile,
|
||||||
|
page.zip4jEntry,
|
||||||
|
// SY <--
|
||||||
|
).toMutableList()
|
||||||
val currentSplitData = splitData.removeFirst()
|
val currentSplitData = splitData.removeFirst()
|
||||||
val newPages = splitData.map {
|
val newPages = splitData.map {
|
||||||
StencilPage(page) { ImageUtil.splitStrip(it, stream) }
|
StencilPage(page) { ImageUtil.splitStrip(it, stream) }
|
||||||
|
@ -26,6 +26,8 @@ import androidx.core.graphics.red
|
|||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterface
|
||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import net.lingala.zip4j.ZipFile
|
||||||
|
import net.lingala.zip4j.model.FileHeader
|
||||||
import tachiyomi.decoder.Format
|
import tachiyomi.decoder.Format
|
||||||
import tachiyomi.decoder.ImageDecoder
|
import tachiyomi.decoder.ImageDecoder
|
||||||
import java.io.BufferedInputStream
|
import java.io.BufferedInputStream
|
||||||
@ -129,9 +131,19 @@ object ImageUtil {
|
|||||||
*
|
*
|
||||||
* @return true if the width is greater than the height
|
* @return true if the width is greater than the height
|
||||||
*/
|
*/
|
||||||
fun isWideImage(imageStream: BufferedInputStream): Boolean {
|
fun isWideImage(
|
||||||
|
imageStream: BufferedInputStream,
|
||||||
|
// SY -->
|
||||||
|
zip4jFile: ZipFile?,
|
||||||
|
zip4jEntry: FileHeader?,
|
||||||
|
// SY <--
|
||||||
|
): Boolean {
|
||||||
val options = extractImageOptions(
|
val options = extractImageOptions(
|
||||||
imageStream,
|
imageStream,
|
||||||
|
// SY -->
|
||||||
|
zip4jFile,
|
||||||
|
zip4jEntry,
|
||||||
|
// SY <--
|
||||||
)
|
)
|
||||||
return options.outWidth > options.outHeight
|
return options.outWidth > options.outHeight
|
||||||
}
|
}
|
||||||
@ -257,9 +269,19 @@ object ImageUtil {
|
|||||||
*
|
*
|
||||||
* @return true if the height:width ratio is greater than 3.
|
* @return true if the height:width ratio is greater than 3.
|
||||||
*/
|
*/
|
||||||
private fun isTallImage(imageStream: InputStream): Boolean {
|
private fun isTallImage(
|
||||||
|
imageStream: InputStream,
|
||||||
|
// SY -->
|
||||||
|
zip4jFile: ZipFile?,
|
||||||
|
zip4jEntry: FileHeader?,
|
||||||
|
// SY <--
|
||||||
|
): Boolean {
|
||||||
val options = extractImageOptions(
|
val options = extractImageOptions(
|
||||||
imageStream,
|
imageStream,
|
||||||
|
// SY -->
|
||||||
|
zip4jFile,
|
||||||
|
zip4jEntry,
|
||||||
|
// SY <--
|
||||||
resetAfterExtraction = false,
|
resetAfterExtraction = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -269,8 +291,23 @@ object ImageUtil {
|
|||||||
/**
|
/**
|
||||||
* Splits tall images to improve performance of reader
|
* Splits tall images to improve performance of reader
|
||||||
*/
|
*/
|
||||||
fun splitTallImage(tmpDir: UniFile, imageFile: UniFile, filenamePrefix: String): Boolean {
|
fun splitTallImage(
|
||||||
if (isAnimatedAndSupported(imageFile.openInputStream()) || !isTallImage(imageFile.openInputStream())) {
|
tmpDir: UniFile,
|
||||||
|
imageFile: UniFile,
|
||||||
|
filenamePrefix: String,
|
||||||
|
// SY -->
|
||||||
|
zip4jFile: ZipFile?,
|
||||||
|
zip4jEntry: FileHeader?,
|
||||||
|
// SY <--
|
||||||
|
): Boolean {
|
||||||
|
if (isAnimatedAndSupported(imageFile.openInputStream()) || !isTallImage(
|
||||||
|
imageFile.openInputStream(),
|
||||||
|
// SY -->
|
||||||
|
zip4jFile,
|
||||||
|
zip4jEntry,
|
||||||
|
// SY <--
|
||||||
|
)
|
||||||
|
) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,7 +317,14 @@ object ImageUtil {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val options = extractImageOptions(imageFile.openInputStream(), resetAfterExtraction = false).apply {
|
val options = extractImageOptions(
|
||||||
|
imageFile.openInputStream(),
|
||||||
|
// SY -->
|
||||||
|
zip4jFile,
|
||||||
|
zip4jEntry,
|
||||||
|
// SY <--
|
||||||
|
resetAfterExtraction = false,
|
||||||
|
).apply {
|
||||||
inJustDecodeBounds = false
|
inJustDecodeBounds = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,10 +370,22 @@ object ImageUtil {
|
|||||||
* Check whether the image is a long Strip that needs splitting
|
* Check whether the image is a long Strip that needs splitting
|
||||||
* @return true if the image is not animated and it's height is greater than image width and screen height
|
* @return true if the image is not animated and it's height is greater than image width and screen height
|
||||||
*/
|
*/
|
||||||
fun isStripSplitNeeded(imageStream: BufferedInputStream): Boolean {
|
fun isStripSplitNeeded(
|
||||||
|
imageStream: BufferedInputStream,
|
||||||
|
// SY -->
|
||||||
|
zip4jFile: ZipFile?,
|
||||||
|
zip4jEntry: FileHeader?,
|
||||||
|
// SY <--
|
||||||
|
): Boolean {
|
||||||
if (isAnimatedAndSupported(imageStream)) return false
|
if (isAnimatedAndSupported(imageStream)) return false
|
||||||
|
|
||||||
val options = extractImageOptions(imageStream)
|
val options = extractImageOptions(
|
||||||
|
imageStream,
|
||||||
|
// SY -->
|
||||||
|
zip4jFile,
|
||||||
|
zip4jEntry,
|
||||||
|
// SY <--
|
||||||
|
)
|
||||||
val imageHeightIsBiggerThanWidth = options.outHeight > options.outWidth
|
val imageHeightIsBiggerThanWidth = options.outHeight > options.outWidth
|
||||||
val imageHeightBiggerThanScreenHeight = options.outHeight > optimalImageHeight
|
val imageHeightBiggerThanScreenHeight = options.outHeight > optimalImageHeight
|
||||||
return imageHeightIsBiggerThanWidth && imageHeightBiggerThanScreenHeight
|
return imageHeightIsBiggerThanWidth && imageHeightBiggerThanScreenHeight
|
||||||
@ -361,8 +417,20 @@ object ImageUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSplitDataForStream(imageStream: InputStream): List<SplitData> {
|
fun getSplitDataForStream(
|
||||||
return extractImageOptions(imageStream).splitData
|
imageStream: InputStream,
|
||||||
|
// SY -->
|
||||||
|
zip4jFile: ZipFile?,
|
||||||
|
zip4jEntry: FileHeader?,
|
||||||
|
// SY <--
|
||||||
|
): List<SplitData> {
|
||||||
|
return extractImageOptions(
|
||||||
|
imageStream,
|
||||||
|
// SY -->
|
||||||
|
zip4jFile,
|
||||||
|
zip4jEntry,
|
||||||
|
// SY <--
|
||||||
|
).splitData
|
||||||
}
|
}
|
||||||
|
|
||||||
private val BitmapFactory.Options.splitData
|
private val BitmapFactory.Options.splitData
|
||||||
@ -627,8 +695,17 @@ object ImageUtil {
|
|||||||
*/
|
*/
|
||||||
private fun extractImageOptions(
|
private fun extractImageOptions(
|
||||||
imageStream: InputStream,
|
imageStream: InputStream,
|
||||||
|
// SY -->
|
||||||
|
zip4jFile: ZipFile?,
|
||||||
|
zip4jEntry: FileHeader?,
|
||||||
|
// SY <--
|
||||||
resetAfterExtraction: Boolean = true,
|
resetAfterExtraction: Boolean = true,
|
||||||
): BitmapFactory.Options {
|
): BitmapFactory.Options {
|
||||||
|
// SY -->
|
||||||
|
// zip4j does currently not support mark() and reset()
|
||||||
|
if (zip4jFile != null && zip4jEntry != null) return extractImageOptionsZip4j(zip4jFile, zip4jEntry)
|
||||||
|
// SY <--
|
||||||
|
|
||||||
imageStream.mark(imageStream.available() + 1)
|
imageStream.mark(imageStream.available() + 1)
|
||||||
|
|
||||||
val imageBytes = imageStream.readBytes()
|
val imageBytes = imageStream.readBytes()
|
||||||
@ -639,6 +716,15 @@ object ImageUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
|
private fun extractImageOptionsZip4j(zip4jFile: ZipFile?, zip4jEntry: FileHeader?): BitmapFactory.Options {
|
||||||
|
zip4jFile?.getInputStream(zip4jEntry).use { imageStream ->
|
||||||
|
val imageBytes = imageStream?.readBytes()
|
||||||
|
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
||||||
|
imageBytes?.size?.let { BitmapFactory.decodeByteArray(imageBytes, 0, it, options) }
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates random exif metadata used as padding to make
|
* Creates random exif metadata used as padding to make
|
||||||
* the size of files inside CBZ archives unique
|
* the size of files inside CBZ archives unique
|
||||||
|
@ -334,6 +334,10 @@
|
|||||||
<string name="pref_center_margin">Center margin type</string>
|
<string name="pref_center_margin">Center margin type</string>
|
||||||
<string name="pref_center_margin_summary">Insert spacer to accommodate deadspace on foldable devices.</string>
|
<string name="pref_center_margin_summary">Insert spacer to accommodate deadspace on foldable devices.</string>
|
||||||
|
|
||||||
|
<!-- Cache archived manga to disk -->
|
||||||
|
<string name="cache_archived_manga_to_disk">Cache images inside CBZ/CBR archives on disk</string>
|
||||||
|
<string name="cache_archived_manga_to_disk_subtitle">Temporarily copy images inside comic book archives to disk while reading \nMay improve reader performance</string>
|
||||||
|
|
||||||
<!-- Entry Page -->
|
<!-- Entry Page -->
|
||||||
<!-- Entry Info -->
|
<!-- Entry Info -->
|
||||||
<string name="az_recommends">See Recommendations</string>
|
<string name="az_recommends">See Recommendations</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user