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

View File

@ -38,6 +38,7 @@ import eu.kanade.domain.ui.UiPreferences
import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode
import eu.kanade.tachiyomi.crash.CrashActivity
import eu.kanade.tachiyomi.crash.GlobalExceptionHandler
import eu.kanade.tachiyomi.data.coil.BufferedSourceFetcher
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
import eu.kanade.tachiyomi.data.coil.MangaCoverKeyer
import eu.kanade.tachiyomi.data.coil.MangaKeyer
@ -208,6 +209,7 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
add(MangaCoverFetcher.MangaCoverFactory(callFactoryLazy))
add(MangaKeyer())
add(MangaCoverKeyer())
add(BufferedSourceFetcher.Factory())
// SY -->
add(PagePreviewKeyer())
add(PagePreviewFetcher.Factory(callFactoryLazy))

View File

@ -0,0 +1,38 @@
package eu.kanade.tachiyomi.data.coil
import coil3.ImageLoader
import coil3.decode.DataSource
import coil3.decode.ImageSource
import coil3.fetch.FetchResult
import coil3.fetch.Fetcher
import coil3.fetch.SourceFetchResult
import coil3.request.Options
import okio.BufferedSource
class BufferedSourceFetcher(
private val data: BufferedSource,
private val options: Options,
) : Fetcher {
override suspend fun fetch(): FetchResult {
return SourceFetchResult(
source = ImageSource(
source = data,
fileSystem = options.fileSystem,
),
mimeType = null,
dataSource = DataSource.MEMORY,
)
}
class Factory : Fetcher.Factory<BufferedSource> {
override fun create(
data: BufferedSource,
options: Options,
imageLoader: ImageLoader,
): Fetcher {
return BufferedSourceFetcher(data, options)
}
}
}

View File

@ -1141,7 +1141,7 @@ class ReaderViewModel @JvmOverloads constructor(
return imageSaver.save(
image = Image.Page(
inputStream = { ImageUtil.mergeBitmaps(imageBitmap, imageBitmap2, isLTR, 0, bg) },
inputStream = { ImageUtil.mergeBitmaps(imageBitmap, imageBitmap2, isLTR, 0, bg).inputStream() },
name = filename,
location = location,
),

View File

@ -33,8 +33,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonSubsamplingImageView
import eu.kanade.tachiyomi.util.system.GLUtil
import eu.kanade.tachiyomi.util.system.animatorDurationScale
import eu.kanade.tachiyomi.util.view.isVisibleOnScreen
import java.io.InputStream
import java.nio.ByteBuffer
import okio.BufferedSource
/**
* A wrapper view for showing page image.
@ -140,14 +139,14 @@ open class ReaderPageImageView @JvmOverloads constructor(
}
}
fun setImage(inputStream: InputStream, isAnimated: Boolean, config: Config) {
fun setImage(source: BufferedSource, isAnimated: Boolean, config: Config) {
this.config = config
if (isAnimated) {
prepareAnimatedImageView()
setAnimatedImage(inputStream, config)
setAnimatedImage(source, config)
} else {
prepareNonAnimatedImageView()
setNonAnimatedImage(inputStream, config)
setNonAnimatedImage(source, config)
}
}
@ -256,7 +255,7 @@ open class ReaderPageImageView @JvmOverloads constructor(
}
private fun setNonAnimatedImage(
image: Any,
data: Any,
config: Config,
) = (pageView as? SubsamplingScaleImageView)?.apply {
setDoubleTapZoomDuration(config.zoomDuration.getSystemScaledDuration())
@ -277,10 +276,10 @@ open class ReaderPageImageView @JvmOverloads constructor(
},
)
when (image) {
is BitmapDrawable -> setImage(ImageSource.bitmap(image.bitmap))
is InputStream -> setImage(ImageSource.inputStream(image))
else -> throw IllegalArgumentException("Not implemented for class ${image::class.simpleName}")
when (data) {
is BitmapDrawable -> setImage(ImageSource.bitmap(data.bitmap))
is BufferedSource -> setImage(ImageSource.inputStream(data.inputStream()))
else -> throw IllegalArgumentException("Not implemented for class ${data::class.simpleName}")
}
isVisible = true
}
@ -325,18 +324,13 @@ open class ReaderPageImageView @JvmOverloads constructor(
}
private fun setAnimatedImage(
image: Any,
data: Any,
config: Config,
) = (pageView as? AppCompatImageView)?.apply {
if (this is PhotoView) {
setZoomTransitionDuration(config.zoomDuration.getSystemScaledDuration())
}
val data = when (image) {
is Drawable -> image
is InputStream -> ByteBuffer.wrap(image.readBytes())
else -> throw IllegalArgumentException("Not implemented for class ${image::class.simpleName}")
}
val request = ImageRequest.Builder(context)
.data(data)
.memoryCachePolicy(CachePolicy.DISABLED)

View File

@ -19,15 +19,14 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
import logcat.LogPriority
import okio.Buffer
import okio.BufferedSource
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.core.common.util.system.ImageUtil
import tachiyomi.core.common.util.system.logcat
import tachiyomi.decoder.ImageDecoder
import java.io.BufferedInputStream
import java.io.ByteArrayInputStream
import java.io.InputStream
import kotlin.math.max
/**
@ -159,41 +158,34 @@ class PagerPageHolder(
val streamFn2 = extraPage?.stream
try {
val (bais, isAnimated, background) = withIOContext {
streamFn().buffered(16).use { stream ->
val (source, isAnimated, background) = withIOContext {
streamFn().buffered(16).use { source ->
// SY -->
(
if (extraPage != null) {
streamFn2?.invoke()
?.buffered(16)
} else {
null
}
).use { stream2 ->
if (viewer.config.dualPageSplit) {
process(item.first, stream)
}.use { source2 ->
val itemSource = if (viewer.config.dualPageSplit) {
process(item.first, Buffer().readFrom(source))
} else {
mergePages(stream, stream2)
}.use { itemStream ->
mergePages(Buffer().readFrom(source), source2?.let { Buffer().readFrom(it) })
}
// SY <--
val bais = ByteArrayInputStream(itemStream.readBytes())
val isAnimated = ImageUtil.isAnimatedAndSupported(bais)
bais.reset()
val isAnimated = ImageUtil.isAnimatedAndSupported(itemSource)
val background = if (!isAnimated && viewer.config.automaticBackground) {
ImageUtil.chooseBackground(context, bais)
ImageUtil.chooseBackground(context, itemSource.peek())
} else {
null
}
bais.reset()
Triple(bais, isAnimated, background)
}
Triple(itemSource, isAnimated, background)
}
}
}
withUIContext {
bais.use {
setImage(
it,
source,
isAnimated,
Config(
zoomDuration = viewer.config.doubleTapAnimDuration,
@ -206,7 +198,6 @@ class PagerPageHolder(
if (!isAnimated) {
pageBackground = background
}
}
removeErrorLayout()
}
} catch (e: Throwable) {
@ -217,124 +208,119 @@ class PagerPageHolder(
}
}
private fun process(page: ReaderPage, imageStream: BufferedInputStream): InputStream {
private fun process(page: ReaderPage, imageSource: BufferedSource): BufferedSource {
if (viewer.config.dualPageRotateToFit) {
return rotateDualPage(imageStream)
return rotateDualPage(imageSource)
}
if (!viewer.config.dualPageSplit) {
return imageStream
return imageSource
}
if (page is InsertPage) {
return splitInHalf(imageStream)
return splitInHalf(imageSource)
}
val isDoublePage = ImageUtil.isWideImage(imageStream)
val isDoublePage = ImageUtil.isWideImage(imageSource)
if (!isDoublePage) {
return imageStream
return imageSource
}
onPageSplit(page)
return splitInHalf(imageStream)
return splitInHalf(imageSource)
}
private fun rotateDualPage(imageStream: BufferedInputStream): InputStream {
val isDoublePage = ImageUtil.isWideImage(imageStream)
private fun rotateDualPage(imageSource: BufferedSource): BufferedSource {
val isDoublePage = ImageUtil.isWideImage(imageSource)
return if (isDoublePage) {
val rotation = if (viewer.config.dualPageRotateToFitInvert) -90f else 90f
ImageUtil.rotateImage(imageStream, rotation)
ImageUtil.rotateImage(imageSource, rotation)
} else {
imageStream
imageSource
}
}
private fun mergePages(imageStream: InputStream, imageStream2: InputStream?): InputStream {
private fun mergePages(imageSource: BufferedSource, imageSource2: BufferedSource?): BufferedSource {
// Handle adding a center margin to wide images if requested
if (imageStream2 == null) {
return if (imageStream is BufferedInputStream &&
!ImageUtil.isAnimatedAndSupported(imageStream) &&
ImageUtil.isWideImage(imageStream) &&
if (imageSource2 == null) {
return if (
!ImageUtil.isAnimatedAndSupported(imageSource) &&
ImageUtil.isWideImage(imageSource) &&
viewer.config.centerMarginType and PagerConfig.CenterMarginType.WIDE_PAGE_CENTER_MARGIN > 0 &&
!viewer.config.imageCropBorders
) {
ImageUtil.addHorizontalCenterMargin(imageStream, height, context)
ImageUtil.addHorizontalCenterMargin(imageSource, height, context)
} else {
imageStream
imageSource
}
}
if (page.fullPage) return imageStream
if (ImageUtil.isAnimatedAndSupported(imageStream)) {
if (page.fullPage) return imageSource
if (ImageUtil.isAnimatedAndSupported(imageSource)) {
page.fullPage = true
splitDoublePages()
return imageStream
} else if (ImageUtil.isAnimatedAndSupported(imageStream2)) {
return imageSource
} else if (ImageUtil.isAnimatedAndSupported(imageSource2)) {
page.isolatedPage = true
extraPage?.fullPage = true
splitDoublePages()
return imageStream
return imageSource
}
val imageBytes = imageStream.readBytes()
val imageBitmap = try {
ImageDecoder.newInstance(imageBytes.inputStream())?.decode()
ImageDecoder.newInstance(imageSource.inputStream())?.decode()
} catch (e: Exception) {
logcat(LogPriority.ERROR, e) { "Cannot combine pages" }
null
}
if (imageBitmap == null) {
imageStream2.close()
imageStream.close()
imageSource2.close()
page.fullPage = true
splitDoublePages()
logcat(LogPriority.ERROR) { "Cannot combine pages" }
return imageBytes.inputStream()
return imageSource
}
scope.launch { progressIndicator.setProgress(96) }
val height = imageBitmap.height
val width = imageBitmap.width
if (height < width) {
imageStream2.close()
imageStream.close()
imageSource2.close()
page.fullPage = true
splitDoublePages()
return imageBytes.inputStream()
return imageSource
}
val imageBytes2 = imageStream2.readBytes()
val imageBitmap2 = try {
ImageDecoder.newInstance(imageBytes2.inputStream())?.decode()
ImageDecoder.newInstance(imageSource2.inputStream())?.decode()
} catch (e: Exception) {
logcat(LogPriority.ERROR, e) { "Cannot combine pages" }
null
}
if (imageBitmap2 == null) {
imageStream2.close()
imageStream.close()
imageSource2.close()
extraPage?.fullPage = true
page.isolatedPage = true
splitDoublePages()
logcat(LogPriority.ERROR) { "Cannot combine pages" }
return imageBytes.inputStream()
return imageSource
}
scope.launch { progressIndicator.setProgress(97) }
val height2 = imageBitmap2.height
val width2 = imageBitmap2.width
if (height2 < width2) {
imageStream2.close()
imageStream.close()
imageSource2.close()
extraPage?.fullPage = true
page.isolatedPage = true
splitDoublePages()
return imageBytes.inputStream()
return imageSource
}
val isLTR = (viewer !is R2LPagerViewer) xor viewer.config.invertDoublePages
imageStream.close()
imageStream2.close()
imageSource.close()
imageSource2.close()
val centerMargin = if (viewer.config.centerMarginType and PagerConfig.CenterMarginType.DOUBLE_PAGE_CENTER_MARGIN > 0 && !viewer.config.imageCropBorders) {
96 / (this.height.coerceAtLeast(1) / max(height, height2).coerceAtLeast(1)).coerceAtLeast(1)
@ -363,7 +349,7 @@ class PagerPageHolder(
}
}
private fun splitInHalf(imageStream: InputStream): InputStream {
private fun splitInHalf(imageSource: BufferedSource): BufferedSource {
var side = when {
viewer is L2RPagerViewer && page is InsertPage -> ImageUtil.Side.RIGHT
viewer !is L2RPagerViewer && page is InsertPage -> ImageUtil.Side.LEFT
@ -387,7 +373,7 @@ class PagerPageHolder(
0
}
return ImageUtil.splitInHalf(imageStream, side, sideMargin)
return ImageUtil.splitInHalf(imageSource, side, sideMargin)
}
private fun onPageSplit(page: ReaderPage) {

View File

@ -22,15 +22,14 @@ import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.suspendCancellableCoroutine
import logcat.LogPriority
import okio.Buffer
import okio.BufferedSource
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.core.common.util.system.ImageUtil
import tachiyomi.core.common.util.system.logcat
import java.io.BufferedInputStream
import java.io.InputStream
/**
* Holder of the webtoon reader for a single page of a chapter.
@ -188,16 +187,14 @@ class WebtoonPageHolder(
val streamFn = page?.stream ?: return
try {
val (openStream, isAnimated) = withIOContext {
val stream = streamFn().buffered(16)
val openStream = process(stream)
val isAnimated = ImageUtil.isAnimatedAndSupported(stream)
Pair(openStream, isAnimated)
val (source, isAnimated) = withIOContext {
val source = streamFn().use { process(Buffer().readFrom(it)) }
val isAnimated = ImageUtil.isAnimatedAndSupported(source)
Pair(source, isAnimated)
}
withUIContext {
frame.setImage(
openStream,
source,
isAnimated,
ReaderPageImageView.Config(
zoomDuration = viewer.config.doubleTapAnimDuration,
@ -207,10 +204,6 @@ class WebtoonPageHolder(
)
removeErrorLayout()
}
// Suspend the coroutine to close the input stream only when the WebtoonPageHolder is recycled
suspendCancellableCoroutine<Nothing> { continuation ->
continuation.invokeOnCancellation { openStream.close() }
}
} catch (e: Throwable) {
logcat(LogPriority.ERROR, e)
withUIContext {
@ -219,29 +212,29 @@ class WebtoonPageHolder(
}
}
private fun process(imageStream: BufferedInputStream): InputStream {
private fun process(imageSource: BufferedSource): BufferedSource {
if (viewer.config.dualPageRotateToFit) {
return rotateDualPage(imageStream)
return rotateDualPage(imageSource)
}
if (viewer.config.dualPageSplit) {
val isDoublePage = ImageUtil.isWideImage(imageStream)
val isDoublePage = ImageUtil.isWideImage(imageSource)
if (isDoublePage) {
val upperSide = if (viewer.config.dualPageInvert) ImageUtil.Side.LEFT else ImageUtil.Side.RIGHT
return ImageUtil.splitAndMerge(imageStream, upperSide)
return ImageUtil.splitAndMerge(imageSource, upperSide)
}
}
return imageStream
return imageSource
}
private fun rotateDualPage(imageStream: BufferedInputStream): InputStream {
val isDoublePage = ImageUtil.isWideImage(imageStream)
private fun rotateDualPage(imageSource: BufferedSource): BufferedSource {
val isDoublePage = ImageUtil.isWideImage(imageSource)
return if (isDoublePage) {
val rotation = if (viewer.config.dualPageRotateToFitInvert) -90f else 90f
ImageUtil.rotateImage(imageStream, rotation)
ImageUtil.rotateImage(imageSource, rotation)
} else {
imageStream
imageSource
}
}

View File

@ -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