diff --git a/app/src/main/java/coil/decode/DecodeUtils.kt b/app/src/main/java/coil/decode/DecodeUtils.kt deleted file mode 100644 index 0d530af66..000000000 --- a/app/src/main/java/coil/decode/DecodeUtils.kt +++ /dev/null @@ -1,66 +0,0 @@ -@file:JvmName("GifDecodeUtils") - -package coil.decode - -import okio.BufferedSource -import okio.ByteString.Companion.encodeUtf8 -import kotlin.experimental.and - -// https://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp -private val GIF_HEADER_87A = "GIF87a".encodeUtf8() -private val GIF_HEADER_89A = "GIF89a".encodeUtf8() - -// https://developers.google.com/speed/webp/docs/riff_container -private val WEBP_HEADER_RIFF = "RIFF".encodeUtf8() -private val WEBP_HEADER_WEBP = "WEBP".encodeUtf8() -private val WEBP_HEADER_VPX8 = "VP8X".encodeUtf8() - -// https://nokiatech.github.io/heif/technical.html -private val HEIF_HEADER_FTYP = "ftyp".encodeUtf8() -private val HEIF_HEADER_MSF1 = "msf1".encodeUtf8() -private val HEIF_HEADER_HEVC = "hevc".encodeUtf8() -private val HEIF_HEADER_HEVX = "hevx".encodeUtf8() - -/** - * Return 'true' if the [source] contains a GIF image. The [source] is not consumed. - */ -fun DecodeUtils.isGif(source: BufferedSource): Boolean { - return source.rangeEquals(0, GIF_HEADER_89A) || - source.rangeEquals(0, GIF_HEADER_87A) -} - -/** - * Return 'true' if the [source] contains a WebP image. The [source] is not consumed. - */ -fun DecodeUtils.isWebP(source: BufferedSource): Boolean { - return source.rangeEquals(0, WEBP_HEADER_RIFF) && - source.rangeEquals(8, WEBP_HEADER_WEBP) -} - -/** - * Return 'true' if the [source] contains an animated WebP image. The [source] is not consumed. - */ -fun DecodeUtils.isAnimatedWebP(source: BufferedSource): Boolean { - return isWebP(source) && - source.rangeEquals(12, WEBP_HEADER_VPX8) && - source.request(17) && - (source.buffer[16] and 0b00000010) > 0 -} - -/** - * Return 'true' if the [source] contains an HEIF image. The [source] is not consumed. - */ -fun DecodeUtils.isHeif(source: BufferedSource): Boolean { - return source.rangeEquals(4, HEIF_HEADER_FTYP) -} - -/** - * Return 'true' if the [source] contains an animated HEIF image sequence. The [source] is not - * consumed. - */ -fun DecodeUtils.isAnimatedHeif(source: BufferedSource): Boolean { - return isHeif(source) && - (source.rangeEquals(8, HEIF_HEADER_MSF1) || - source.rangeEquals(8, HEIF_HEADER_HEVC) || - source.rangeEquals(8, HEIF_HEADER_HEVX)) -} diff --git a/app/src/main/java/coil/decode/FrameDelayRewritingSource.kt b/app/src/main/java/coil/decode/FrameDelayRewritingSource.kt deleted file mode 100644 index e2d75cd80..000000000 --- a/app/src/main/java/coil/decode/FrameDelayRewritingSource.kt +++ /dev/null @@ -1,88 +0,0 @@ -@file:Suppress("SameParameterValue") - -package coil.decode - -import okio.Buffer -import okio.ByteString -import okio.ByteString.Companion.decodeHex -import okio.ForwardingSource -import okio.Source - -/** - * A [ForwardingSource] that rewrites the GIF frame delay in every graphics control block if it's - * below a threshold. - */ -internal class FrameDelayRewritingSource(delegate: Source) : ForwardingSource(delegate) { - - // An intermediary buffer so we can read and alter the data before it's written to the destination. - private val buffer = Buffer() - - override fun read(sink: Buffer, byteCount: Long): Long { - // Ensure our buffer has enough bytes to satisfy this read. - request(byteCount) - - // Short circuit if there are no bytes in the buffer. - if (buffer.size == 0L) { - return if (byteCount == 0L) 0L else -1L - } - - // Search through the buffer and rewrite any frame delays below the threshold. - var bytesWritten = 0L - while (true) { - val index = indexOf(FRAME_DELAY_START_MARKER) - if (index == -1L) break - - // Write up until the end of the frame delay start marker. - bytesWritten += write(sink, index + FRAME_DELAY_START_MARKER_SIZE_BYTES) - - // Check for the end of the graphics control extension block. - if (!request(5) || buffer[4] != 0.toByte()) continue - - // Rewrite the frame delay if it is below the threshold. - // The frame delay is stored as two unsigned bytes in reverse order - // (i.e. the most significant bits are in the byte that's read second). - val frameDelay = (buffer[2].toUByte().toInt() shl 8) or buffer[1].toUByte().toInt() - if (frameDelay < MINIMUM_FRAME_DELAY) { - sink.writeByte(buffer[0].toInt()) - sink.writeByte(DEFAULT_FRAME_DELAY) - sink.writeByte(0) - buffer.skip(3) - } - } - - // Write anything left in the source. - if (bytesWritten < byteCount) { - bytesWritten += write(sink, byteCount - bytesWritten) - } - return if (bytesWritten == 0L) -1 else bytesWritten - } - - private fun indexOf(bytes: ByteString): Long { - var index = -1L - while (true) { - index = buffer.indexOf(bytes[0], index + 1) - if (index == -1L) break - if (request(bytes.size.toLong()) && buffer.rangeEquals(index, bytes)) break - } - return index - } - - private fun write(sink: Buffer, byteCount: Long): Long { - return buffer.read(sink, byteCount).coerceAtLeast(0) - } - - private fun request(byteCount: Long): Boolean { - if (buffer.size >= byteCount) return true - val toRead = byteCount - buffer.size - return super.read(buffer, toRead) == toRead - } - - private companion object { - // https://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp - // See: "Graphics Control Extension" - private val FRAME_DELAY_START_MARKER = "0021F904".decodeHex() - private const val FRAME_DELAY_START_MARKER_SIZE_BYTES = 4 - private const val MINIMUM_FRAME_DELAY = 2 - private const val DEFAULT_FRAME_DELAY = 10 - } -} diff --git a/app/src/main/java/com/hippo/image/Image.kt b/app/src/main/java/com/hippo/image/Image.kt index f2fb26eff..42247dd79 100644 --- a/app/src/main/java/com/hippo/image/Image.kt +++ b/app/src/main/java/com/hippo/image/Image.kt @@ -25,15 +25,8 @@ import android.graphics.ImageDecoder.Source import android.graphics.drawable.Animatable import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable -import coil.decode.DecodeUtils -import coil.decode.FrameDelayRewritingSource -import coil.decode.isGif import com.hippo.UriArchiveAccessor import com.hippo.ehviewer.EhApplication -import okio.Buffer -import okio.BufferedSource -import okio.buffer -import okio.source import java.io.FileInputStream import java.nio.ByteBuffer import java.nio.channels.FileChannel @@ -87,35 +80,20 @@ class Image private constructor(source: Source, private val byteBuffer: ByteBuff @Throws(DecodeException::class) @JvmStatic fun decode(stream: FileInputStream): Image { - val source = stream.source().buffer() - val buffer = if (DecodeUtils.isGif(source)) { - rewriteSource(source) - } else { + val src = ImageDecoder.createSource( stream.channel.map( FileChannel.MapMode.READ_ONLY, 0, stream.available().toLong() ) - } - val src = ImageDecoder.createSource(buffer) + ) return Image(src) } @Throws(DecodeException::class) @JvmStatic fun decode(buffer: ByteBuffer): Image { - val source = Buffer().apply { write(buffer) } - val byteBuffer = if (DecodeUtils.isGif(source)) { - rewriteSource(source) - } else { - buffer.position(0) as ByteBuffer - } - val src = ImageDecoder.createSource(byteBuffer) + val src = ImageDecoder.createSource(buffer) return Image(src, buffer) } - - private fun rewriteSource(source: BufferedSource): ByteBuffer { - val bufferedSource = FrameDelayRewritingSource(source).buffer() - return ByteBuffer.wrap(bufferedSource.use { it.readByteArray() }) - } } } \ No newline at end of file