Load ZIP file contents to cache (#9381)
* Extract downloaded archives to tmp folder when loading for viewing * Generate sequence of entries from ZipInputStream instead of loading entire ZipFile (cherry picked from commit 44619febd333f4e662cdbf149ae0741a43ebd27b) # Conflicts: # app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ZipPageLoader.kt
This commit is contained in:
parent
cf752a0d88
commit
20d35268b1
@ -121,7 +121,6 @@ class ChapterLoader(
|
|||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
isDownloaded -> DownloadPageLoader(chapter, manga, source, downloadManager, downloadProvider)
|
isDownloaded -> DownloadPageLoader(chapter, manga, source, downloadManager, downloadProvider)
|
||||||
source is HttpSource -> HttpPageLoader(chapter, source)
|
|
||||||
source is LocalSource -> source.getFormat(chapter.chapter).let { format ->
|
source is LocalSource -> source.getFormat(chapter.chapter).let { format ->
|
||||||
when (format) {
|
when (format) {
|
||||||
is Format.Directory -> DirectoryPageLoader(format.file)
|
is Format.Directory -> DirectoryPageLoader(format.file)
|
||||||
@ -140,6 +139,7 @@ class ChapterLoader(
|
|||||||
is Format.Epub -> EpubPageLoader(format.file)
|
is Format.Epub -> EpubPageLoader(format.file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
source is HttpSource -> HttpPageLoader(chapter, source)
|
||||||
source is StubSource -> error(context.getString(R.string.source_not_installed, source.toString()))
|
source is StubSource -> error(context.getString(R.string.source_not_installed, source.toString()))
|
||||||
else -> error(context.getString(R.string.loader_not_implemented_error))
|
else -> error(context.getString(R.string.loader_not_implemented_error))
|
||||||
}
|
}
|
||||||
|
@ -1,61 +1,57 @@
|
|||||||
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.ui.reader.model.ReaderPage
|
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||||
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
|
import uy.kohesive.injekt.injectLazy
|
||||||
import tachiyomi.core.util.system.ImageUtil
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.PipedInputStream
|
import java.io.PipedInputStream
|
||||||
import java.io.PipedOutputStream
|
import java.io.PipedOutputStream
|
||||||
import java.util.concurrent.Executors
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loader used to load a chapter from a .rar or .cbr file.
|
* Loader used to load a chapter from a .rar or .cbr file.
|
||||||
*/
|
*/
|
||||||
internal class RarPageLoader(file: File) : PageLoader() {
|
internal class RarPageLoader(file: File) : PageLoader() {
|
||||||
|
|
||||||
private val rar = Archive(file)
|
private val context: Application by injectLazy()
|
||||||
|
private val tmpDir = File(context.externalCacheDir, "reader_${file.hashCode()}").also {
|
||||||
|
it.deleteRecursively()
|
||||||
|
it.mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
init {
|
||||||
* Pool for copying compressed files to an input stream.
|
Archive(file).use { rar ->
|
||||||
*/
|
rar.fileHeaders.asSequence()
|
||||||
private val pool = Executors.newFixedThreadPool(1)
|
.filterNot { it.isDirectory }
|
||||||
|
.forEach { header ->
|
||||||
|
val pageFile = File(tmpDir, header.fileName).also { it.createNewFile() }
|
||||||
|
getStream(rar, header).use {
|
||||||
|
it.copyTo(pageFile.outputStream())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override var isLocal: Boolean = true
|
override var isLocal: Boolean = true
|
||||||
|
|
||||||
override suspend fun getPages(): List<ReaderPage> {
|
override suspend fun getPages(): List<ReaderPage> {
|
||||||
return rar.fileHeaders.asSequence()
|
return DirectoryPageLoader(tmpDir).getPages()
|
||||||
.filter { !it.isDirectory && ImageUtil.isImage(it.fileName) { rar.getInputStream(it) } }
|
|
||||||
.sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) }
|
|
||||||
.mapIndexed { i, header ->
|
|
||||||
ReaderPage(i).apply {
|
|
||||||
stream = { getStream(header) }
|
|
||||||
status = Page.State.READY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun loadPage(page: ReaderPage) {
|
|
||||||
check(!isRecycled)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun recycle() {
|
override fun recycle() {
|
||||||
super.recycle()
|
super.recycle()
|
||||||
rar.close()
|
tmpDir.deleteRecursively()
|
||||||
pool.shutdown()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an input stream for the given [header].
|
* Returns an input stream for the given [header].
|
||||||
*/
|
*/
|
||||||
private fun getStream(header: FileHeader): InputStream {
|
private fun getStream(rar: Archive, header: FileHeader): InputStream {
|
||||||
val pipeIn = PipedInputStream()
|
val pipeIn = PipedInputStream()
|
||||||
val pipeOut = PipedOutputStream(pipeIn)
|
val pipeOut = PipedOutputStream(pipeIn)
|
||||||
pool.execute {
|
synchronized(this) {
|
||||||
try {
|
try {
|
||||||
pipeOut.use {
|
pipeOut.use {
|
||||||
rar.extractFile(header, it)
|
rar.extractFile(header, it)
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.loader
|
package eu.kanade.tachiyomi.ui.reader.loader
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
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.util.lang.compareToCaseInsensitiveNaturalOrder
|
|
||||||
import eu.kanade.tachiyomi.util.storage.CbzCrypto
|
import eu.kanade.tachiyomi.util.storage.CbzCrypto
|
||||||
import net.lingala.zip4j.ZipFile
|
import net.lingala.zip4j.ZipFile
|
||||||
import tachiyomi.core.util.system.ImageUtil
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.util.zip.ZipInputStream
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loader used to load a chapter from a .zip or .cbz file.
|
* Loader used to load a chapter from a .zip or .cbz file.
|
||||||
@ -22,85 +23,75 @@ internal class ZipPageLoader(
|
|||||||
// SY <--
|
// SY <--
|
||||||
) : PageLoader() {
|
) : PageLoader() {
|
||||||
|
|
||||||
/**
|
private val context: Application by injectLazy()
|
||||||
* The zip file to load pages from.
|
private val tmpDir = File(context.externalCacheDir, "reader_${file.hashCode()}").also {
|
||||||
*/
|
it.deleteRecursively()
|
||||||
// SY -->
|
it.mkdirs()
|
||||||
private var zip4j = ZipFile(file)
|
}
|
||||||
|
|
||||||
|
// SY -->
|
||||||
init {
|
init {
|
||||||
if (zip4j.isEncrypted) {
|
val zip = ZipFile(file)
|
||||||
if (!CbzCrypto.checkCbzPassword(zip4j, CbzCrypto.getDecryptedPasswordCbz())) {
|
if (zip.isEncrypted) {
|
||||||
|
if (!CbzCrypto.checkCbzPassword(zip, CbzCrypto.getDecryptedPasswordCbz())) {
|
||||||
this.recycle()
|
this.recycle()
|
||||||
throw Exception(context.getString(R.string.wrong_cbz_archive_password))
|
throw Exception(context.getString(R.string.wrong_cbz_archive_password))
|
||||||
}
|
}
|
||||||
|
unzipEncrypted(zip)
|
||||||
|
} else {
|
||||||
|
unzip(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private fun unzip(file: File) {
|
||||||
|
// SY <--
|
||||||
|
ZipInputStream(FileInputStream(file)).use { zipInputStream ->
|
||||||
|
generateSequence { zipInputStream.nextEntry }
|
||||||
|
.filterNot { it.isDirectory }
|
||||||
|
.forEach { entry ->
|
||||||
|
File(tmpDir, entry.name).also { it.createNewFile() }
|
||||||
|
.outputStream().use { pageOutputStream ->
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
pageOutputStream.write(zipInputStream.readNBytes(entry.size.toInt()))
|
||||||
|
} else {
|
||||||
|
val buffer = ByteArray(2048)
|
||||||
|
var len: Int
|
||||||
|
while (
|
||||||
|
zipInputStream.read(buffer, 0, buffer.size)
|
||||||
|
.also { len = it } >= 0
|
||||||
|
) {
|
||||||
|
pageOutputStream.write(buffer, 0, len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pageOutputStream.flush()
|
||||||
|
}
|
||||||
|
zipInputStream.closeEntry()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val zip: java.util.zip.ZipFile? =
|
// SY -->
|
||||||
|
private fun unzipEncrypted(zip: ZipFile) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
if (!zip4j.isEncrypted) java.util.zip.ZipFile(file, StandardCharsets.ISO_8859_1) else null
|
zip.charset = StandardCharsets.ISO_8859_1
|
||||||
} else {
|
|
||||||
if (!zip4j.isEncrypted) java.util.zip.ZipFile(file) else null
|
|
||||||
}
|
}
|
||||||
|
zip.setPassword(CbzCrypto.getDecryptedPasswordCbz())
|
||||||
|
|
||||||
|
zip.fileHeaders.asSequence()
|
||||||
|
.filterNot { !it.isDirectory }
|
||||||
|
.forEach { entry ->
|
||||||
|
zip.extractFile(entry, tmpDir.absolutePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
/**
|
override var isLocal: Boolean = true
|
||||||
* Recycles this loader and the open zip.
|
|
||||||
*/
|
|
||||||
override fun recycle() {
|
|
||||||
super.recycle()
|
|
||||||
// SY -->
|
|
||||||
zip4j.close()
|
|
||||||
zip?.close()
|
|
||||||
// SY <--
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the pages found on this zip archive ordered with a natural comparator.
|
|
||||||
|
|
||||||
override suspend fun getPages(): List<ReaderPage> {
|
override suspend fun getPages(): List<ReaderPage> {
|
||||||
// SY -->
|
return DirectoryPageLoader(tmpDir).getPages()
|
||||||
// Part can be removed after testing that there are no bugs with zip4j on some users devices
|
|
||||||
if (zip != null) {
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
// SY -->
|
|
||||||
}.toList()
|
|
||||||
} else {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
||||||
zip4j.charset = StandardCharsets.ISO_8859_1
|
|
||||||
}
|
|
||||||
zip4j.setPassword(CbzCrypto.getDecryptedPasswordCbz())
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
// SY <--
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun loadPage(page: ReaderPage) {
|
|
||||||
check(!isRecycled)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun recycle() {
|
override fun recycle() {
|
||||||
super.recycle()
|
super.recycle()
|
||||||
zip.close()
|
tmpDir.deleteRecursively()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
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(
|
||||||
@ -10,9 +8,6 @@ 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 */
|
||||||
|
@ -229,13 +229,7 @@ class PagerPageHolder(
|
|||||||
return splitInHalf(imageStream)
|
return splitInHalf(imageStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
val isDoublePage = ImageUtil.isWideImage(
|
val isDoublePage = ImageUtil.isWideImage(imageStream)
|
||||||
imageStream,
|
|
||||||
// SY -->
|
|
||||||
page.zip4jFile,
|
|
||||||
page.zip4jEntry,
|
|
||||||
// SY <--
|
|
||||||
)
|
|
||||||
if (!isDoublePage) {
|
if (!isDoublePage) {
|
||||||
return imageStream
|
return imageStream
|
||||||
}
|
}
|
||||||
@ -260,13 +254,7 @@ 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(
|
ImageUtil.isWideImage(imageStream) &&
|
||||||
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,13 +213,7 @@ 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(
|
val isDoublePage = ImageUtil.isWideImage(imageStream)
|
||||||
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)
|
||||||
@ -230,13 +224,7 @@ class WebtoonPageHolder(
|
|||||||
if (page is StencilPage) {
|
if (page is StencilPage) {
|
||||||
return imageStream
|
return imageStream
|
||||||
}
|
}
|
||||||
val isStripSplitNeeded = ImageUtil.isStripSplitNeeded(
|
val isStripSplitNeeded = ImageUtil.isStripSplitNeeded(imageStream)
|
||||||
imageStream,
|
|
||||||
// SY -->
|
|
||||||
page?.zip4jFile,
|
|
||||||
page?.zip4jEntry,
|
|
||||||
// SY <--
|
|
||||||
)
|
|
||||||
if (isStripSplitNeeded) {
|
if (isStripSplitNeeded) {
|
||||||
return onStripSplit(imageStream)
|
return onStripSplit(imageStream)
|
||||||
}
|
}
|
||||||
@ -249,14 +237,7 @@ 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(
|
val splitData = ImageUtil.getSplitDataForStream(imageStream).toMutableList()
|
||||||
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,8 +26,6 @@ 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
|
||||||
@ -127,17 +125,9 @@ object ImageUtil {
|
|||||||
*
|
*
|
||||||
* @return true if the width is greater than the height
|
* @return true if the width is greater than the height
|
||||||
*/
|
*/
|
||||||
fun isWideImage(
|
fun isWideImage(imageStream: BufferedInputStream): Boolean {
|
||||||
imageStream: BufferedInputStream,
|
|
||||||
zip4jFile: ZipFile? = null,
|
|
||||||
zip4jEntry: FileHeader? = null,
|
|
||||||
): Boolean {
|
|
||||||
val options = extractImageOptions(
|
val options = extractImageOptions(
|
||||||
imageStream,
|
imageStream,
|
||||||
// SY -->
|
|
||||||
zip4jFile,
|
|
||||||
zip4jEntry,
|
|
||||||
// SY <--
|
|
||||||
)
|
)
|
||||||
return options.outWidth > options.outHeight
|
return options.outWidth > options.outHeight
|
||||||
}
|
}
|
||||||
@ -263,19 +253,9 @@ 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(
|
private fun isTallImage(imageStream: InputStream): Boolean {
|
||||||
imageStream: InputStream,
|
|
||||||
// SY -->
|
|
||||||
zip4jFile: ZipFile? = null,
|
|
||||||
zip4jEntry: FileHeader? = null,
|
|
||||||
// SY <--
|
|
||||||
): Boolean {
|
|
||||||
val options = extractImageOptions(
|
val options = extractImageOptions(
|
||||||
imageStream,
|
imageStream,
|
||||||
// SY -->
|
|
||||||
zip4jFile,
|
|
||||||
zip4jEntry,
|
|
||||||
// SY <--
|
|
||||||
resetAfterExtraction = false,
|
resetAfterExtraction = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -285,23 +265,8 @@ object ImageUtil {
|
|||||||
/**
|
/**
|
||||||
* Splits tall images to improve performance of reader
|
* Splits tall images to improve performance of reader
|
||||||
*/
|
*/
|
||||||
fun splitTallImage(
|
fun splitTallImage(tmpDir: UniFile, imageFile: UniFile, filenamePrefix: String, ): Boolean {
|
||||||
tmpDir: UniFile,
|
if (isAnimatedAndSupported(imageFile.openInputStream()) || !isTallImage(imageFile.openInputStream())) {
|
||||||
imageFile: UniFile,
|
|
||||||
filenamePrefix: String,
|
|
||||||
// SY -->
|
|
||||||
zip4jFile: ZipFile? = null,
|
|
||||||
zip4jEntry: FileHeader? = null,
|
|
||||||
// SY <--
|
|
||||||
): Boolean {
|
|
||||||
if (isAnimatedAndSupported(imageFile.openInputStream()) || !isTallImage(
|
|
||||||
imageFile.openInputStream(),
|
|
||||||
// SY -->
|
|
||||||
zip4jFile,
|
|
||||||
zip4jEntry,
|
|
||||||
// SY <--
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -311,14 +276,7 @@ object ImageUtil {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val options = extractImageOptions(
|
val options = extractImageOptions(imageFile.openInputStream(), resetAfterExtraction = false).apply {
|
||||||
imageFile.openInputStream(),
|
|
||||||
// SY -->
|
|
||||||
zip4jFile,
|
|
||||||
zip4jEntry,
|
|
||||||
// SY <--
|
|
||||||
resetAfterExtraction = false,
|
|
||||||
).apply {
|
|
||||||
inJustDecodeBounds = false
|
inJustDecodeBounds = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,22 +322,10 @@ 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(
|
fun isStripSplitNeeded(imageStream: BufferedInputStream): Boolean {
|
||||||
imageStream: BufferedInputStream,
|
|
||||||
// SY -->
|
|
||||||
zip4jFile: ZipFile? = null,
|
|
||||||
zip4jEntry: FileHeader? = null,
|
|
||||||
// SY <--
|
|
||||||
): Boolean {
|
|
||||||
if (isAnimatedAndSupported(imageStream)) return false
|
if (isAnimatedAndSupported(imageStream)) return false
|
||||||
|
|
||||||
val options = extractImageOptions(
|
val options = extractImageOptions(imageStream)
|
||||||
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
|
||||||
@ -411,21 +357,8 @@ object ImageUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSplitDataForStream(
|
fun getSplitDataForStream(imageStream: InputStream): List<SplitData> {
|
||||||
imageStream: InputStream,
|
return extractImageOptions(imageStream).splitData
|
||||||
// SY -->
|
|
||||||
zip4jFile: ZipFile? = null,
|
|
||||||
zip4jEntry: FileHeader? = null,
|
|
||||||
// SY <--
|
|
||||||
|
|
||||||
): List<SplitData> {
|
|
||||||
// SY -->
|
|
||||||
return extractImageOptions(
|
|
||||||
imageStream,
|
|
||||||
zip4jFile,
|
|
||||||
zip4jEntry,
|
|
||||||
).splitData
|
|
||||||
// <--
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val BitmapFactory.Options.splitData
|
private val BitmapFactory.Options.splitData
|
||||||
@ -690,17 +623,8 @@ object ImageUtil {
|
|||||||
*/
|
*/
|
||||||
private fun extractImageOptions(
|
private fun extractImageOptions(
|
||||||
imageStream: InputStream,
|
imageStream: InputStream,
|
||||||
// SY -->
|
|
||||||
zip4jFile: ZipFile? = null,
|
|
||||||
zip4jEntry: FileHeader? = null,
|
|
||||||
// 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()
|
||||||
@ -711,15 +635,6 @@ 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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user