Revert "Rewrite the GIF frame delay if it's below a threshold (coil-kt/coil#783) (#672)"
This reverts commit 0ebb55551c.
This commit is contained in:
1 parent
b1b10298b4
commit
beb132d604
3 files changed
+3
-179
No files matched your search
@@ -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))
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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() })
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user