Use Okio instead of java.io for image processing (#691)

(cherry picked from commit b152e3881bffd9050a8a0ed4030823886e3fe04f)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/App.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt
#	core/common/src/main/kotlin/tachiyomi/core/common/util/system/ImageUtil.kt
This commit is contained in:
FooIbar
2024-04-20 12:52:40 +08:00
committed by Jobobby04
parent 5895e78b39
commit aeeff72bed
7 changed files with 181 additions and 189 deletions
@@ -26,11 +26,10 @@ import androidx.core.graphics.red
import androidx.exifinterface.media.ExifInterface
import com.hippo.unifile.UniFile
import logcat.LogPriority
import okio.Buffer
import okio.BufferedSource
import tachiyomi.decoder.Format
import tachiyomi.decoder.ImageDecoder
import java.io.BufferedInputStream
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.InputStream
import java.net.URLConnection
@@ -83,9 +82,9 @@ object ImageUtil {
?: "jpg"
}
fun isAnimatedAndSupported(stream: InputStream): Boolean {
fun isAnimatedAndSupported(source: BufferedSource): Boolean {
return try {
val type = getImageType(stream) ?: return false
val type = getImageType(source.peek().inputStream()) ?: return false
// https://coil-kt.github.io/coil/getting_started/#supported-image-formats
when (type.format) {
Format.Gif -> true
@@ -132,18 +131,16 @@ object ImageUtil {
*
* @return true if the width is greater than the height
*/
fun isWideImage(imageStream: BufferedInputStream): Boolean {
val options = extractImageOptions(imageStream)
fun isWideImage(imageSource: BufferedSource): Boolean {
val options = extractImageOptions(imageSource)
return options.outWidth > options.outHeight
}
/**
* Extract the 'side' part from imageStream and return it as InputStream.
* Extract the 'side' part from [BufferedSource] and return it as [BufferedSource].
*/
fun splitInHalf(imageStream: InputStream, side: Side, sidePadding: Int): InputStream {
val imageBytes = imageStream.readBytes()
val imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
fun splitInHalf(imageSource: BufferedSource, side: Side, sidePadding: Int): BufferedSource {
val imageBitmap = BitmapFactory.decodeStream(imageSource.inputStream())
val height = imageBitmap.height
val width = imageBitmap.width
@@ -157,22 +154,20 @@ object ImageUtil {
half.applyCanvas {
drawBitmap(imageBitmap, part, singlePage, null)
}
val output = ByteArrayOutputStream()
half.compress(Bitmap.CompressFormat.JPEG, 100, output)
val output = Buffer()
half.compress(Bitmap.CompressFormat.JPEG, 100, output.outputStream())
return ByteArrayInputStream(output.toByteArray())
return output
}
fun rotateImage(imageStream: InputStream, degrees: Float): InputStream {
val imageBytes = imageStream.readBytes()
val imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
fun rotateImage(imageSource: BufferedSource, degrees: Float): BufferedSource {
val imageBitmap = BitmapFactory.decodeStream(imageSource.inputStream())
val rotated = rotateBitMap(imageBitmap, degrees)
val output = ByteArrayOutputStream()
rotated.compress(Bitmap.CompressFormat.JPEG, 100, output)
val output = Buffer()
rotated.compress(Bitmap.CompressFormat.JPEG, 100, output.outputStream())
return ByteArrayInputStream(output.toByteArray())
return output
}
private fun rotateBitMap(bitmap: Bitmap, degrees: Float): Bitmap {
@@ -184,10 +179,8 @@ object ImageUtil {
* Split the image into left and right parts, then merge them into a
* new vertically-aligned image.
*/
fun splitAndMerge(imageStream: InputStream, upperSide: Side): InputStream {
val imageBytes = imageStream.readBytes()
val imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
fun splitAndMerge(imageSource: BufferedSource, upperSide: Side): BufferedSource {
val imageBitmap = BitmapFactory.decodeStream(imageSource.inputStream())
val height = imageBitmap.height
val width = imageBitmap.width
@@ -209,9 +202,9 @@ object ImageUtil {
drawBitmap(imageBitmap, leftPart, bottomPart, null)
}
val output = ByteArrayOutputStream()
result.compress(Bitmap.CompressFormat.JPEG, 100, output)
return ByteArrayInputStream(output.toByteArray())
val output = Buffer()
result.compress(Bitmap.CompressFormat.JPEG, 100, output.outputStream())
return output
}
enum class Side {
@@ -225,8 +218,8 @@ object ImageUtil {
* to compensate for scaling.
*/
fun addHorizontalCenterMargin(imageStream: InputStream, viewHeight: Int, backgroundContext: Context): InputStream {
val imageBitmap = ImageDecoder.newInstance(imageStream)?.decode()!!
fun addHorizontalCenterMargin(imageSource: BufferedSource, viewHeight: Int, backgroundContext: Context): BufferedSource {
val imageBitmap = ImageDecoder.newInstance(imageSource.inputStream())?.decode()!!
val height = imageBitmap.height
val width = imageBitmap.width
@@ -237,7 +230,7 @@ object ImageUtil {
val leftTargetPart = Rect(0, 0, width / 2, height)
val rightTargetPart = Rect(width / 2 + centerPadding, 0, width + centerPadding, height)
val bgColor = chooseBackground(backgroundContext, imageStream)
val bgColor = chooseBackground(backgroundContext, imageSource)
bgColor.setBounds(width / 2, 0, width / 2 + centerPadding, height)
val result = createBitmap(width + centerPadding, height)
@@ -247,9 +240,9 @@ object ImageUtil {
bgColor.draw(this)
}
val output = ByteArrayOutputStream()
result.compress(Bitmap.CompressFormat.JPEG, 100, output)
return ByteArrayInputStream(output.toByteArray())
val output = Buffer()
result.compress(Bitmap.CompressFormat.JPEG, 100, output.outputStream())
return output
}
// SY <--
@@ -258,11 +251,8 @@ object ImageUtil {
*
* @return true if the height:width ratio is greater than 3.
*/
private fun isTallImage(imageStream: InputStream): Boolean {
val options = extractImageOptions(
imageStream,
resetAfterExtraction = false,
)
private fun isTallImage(imageSource: BufferedSource): Boolean {
val options = extractImageOptions(imageSource)
return (options.outHeight / options.outWidth) > 3
}
@@ -275,22 +265,18 @@ object ImageUtil {
imageFile: UniFile,
filenamePrefix: String,
): Boolean {
if (isAnimatedAndSupported(imageFile.openInputStream()) ||
!isTallImage(imageFile.openInputStream())
) {
val imageSource = imageFile.openInputStream().use { Buffer().readFrom(it) }
if (isAnimatedAndSupported(imageSource) || !isTallImage(imageSource)) {
return true
}
val bitmapRegionDecoder = getBitmapRegionDecoder(imageFile.openInputStream())
val bitmapRegionDecoder = getBitmapRegionDecoder(imageSource.peek().inputStream())
if (bitmapRegionDecoder == null) {
logcat { "Failed to create new instance of BitmapRegionDecoder" }
return false
}
val options = extractImageOptions(
imageFile.openInputStream(),
resetAfterExtraction = false,
).apply {
val options = extractImageOptions(imageSource).apply {
inJustDecodeBounds = false
}
@@ -380,8 +366,8 @@ object ImageUtil {
/**
* Algorithm for determining what background to accompany a comic/manga page
*/
fun chooseBackground(context: Context, imageStream: InputStream): Drawable {
val decoder = ImageDecoder.newInstance(imageStream)
fun chooseBackground(context: Context, imageSource: BufferedSource): Drawable {
val decoder = ImageDecoder.newInstance(imageSource.inputStream())
val image = decoder?.decode()
decoder?.recycle()
@@ -603,16 +589,9 @@ object ImageUtil {
/**
* Used to check an image's dimensions without loading it in the memory.
*/
private fun extractImageOptions(
imageStream: InputStream,
resetAfterExtraction: Boolean = true,
): BitmapFactory.Options {
imageStream.mark(Int.MAX_VALUE)
val imageBytes = imageStream.readBytes()
private fun extractImageOptions(imageSource: BufferedSource): BitmapFactory.Options {
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, options)
if (resetAfterExtraction) imageStream.reset()
BitmapFactory.decodeStream(imageSource.peek().inputStream(), null, options)
return options
}
@@ -657,7 +636,7 @@ object ImageUtil {
centerMargin: Int,
@ColorInt background: Int = Color.WHITE,
progressCallback: ((Int) -> Unit)? = null,
): ByteArrayInputStream {
): BufferedSource {
val height = imageBitmap.height
val width = imageBitmap.width
val height2 = imageBitmap2.height
@@ -687,10 +666,10 @@ object ImageUtil {
canvas.drawBitmap(imageBitmap2, imageBitmap2.rect, bottomPart, null)
progressCallback?.invoke(99)
val output = ByteArrayOutputStream()
result.compress(Bitmap.CompressFormat.JPEG, 100, output)
val output = Buffer()
result.compress(Bitmap.CompressFormat.JPEG, 100, output.outputStream())
progressCallback?.invoke(100)
return ByteArrayInputStream(output.toByteArray())
return output
}
private val Bitmap.rect: Rect