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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user