1
0
mirror of https://github.com/tateisu/SubwayTooter synced 2025-02-04 12:47:48 +01:00

improve gif decoder

This commit is contained in:
tateisu 2019-08-12 08:39:19 +09:00
parent aed1bde1bc
commit 898521d0d1
11 changed files with 260 additions and 331 deletions

View File

@ -3,35 +3,16 @@ package jp.juggler.apng
import java.io.InputStream import java.io.InputStream
import kotlin.math.min import kotlin.math.min
/** class GifDecoder {
* Class GifDecoder - Decodes a GIF file into one or more frames.
*
* Example:
*
* <pre>
* {@code
* GifDecoder d = new GifDecoder();
* d.read("sample.gif");
* int n = d.getFrameCount();
* for (int i = 0; i < n; i++) {
* BufferedImage frame = d.getFrame(i); // frame i
* int t = d.getDelay(i); // display duration of frame in milliseconds
* // do something with frame
* }
* }
* </pre>
* No copyright asserted on the source code of this class. May be used for
* any purpose, however, refer to the Unisys LZW patent for any additional
* restrictions. Please forward any corrections to questions at fmsware.com.
*
* @author Kevin Weiner, FM Software; LZW decoder adapted from John Cristy's ImageMagick.
* @version 1.03 November 2003
*
*/
class Rectangle(val x : Int, val y : Int, val w : Int, val h : Int) private class Rectangle(var x : Int = 0, var y : Int = 0, var w : Int = 0, var h : Int = 0) {
fun set(x : Int, y : Int, w : Int, h : Int) {
class GifFrame(val image : ApngBitmap, val delay : Int) this.x = x
this.y = y
this.w = w
this.h = h
}
}
private class Reader(val bis : InputStream) { private class Reader(val bis : InputStream) {
@ -39,68 +20,53 @@ private class Reader(val bis : InputStream) {
var blockSize = 0 // block size var blockSize = 0 // block size
// Reads a single byte from the input stream. // Reads a single byte from the input stream.
fun read() : Int = bis.read() fun byte() : Int = bis.read()
fun readArray(ba:ByteArray,offset:Int=0,length:Int=ba.size-offset) = // Reads next 16-bit value, LSB first
bis.read(ba,offset,length) fun UInt16() = byte() or (byte() shl 8)
/** fun array(ba : ByteArray, offset : Int = 0, length : Int = ba.size - offset) {
* Reads next 16-bit value, LSB first var nRead = 0
*/ while(nRead < length) {
// read 16-bit value, LSB first val delta = bis.read(ba, offset + nRead, length - nRead)
fun readShort() = read() or (read() shl 8) if(delta == - 1) throw RuntimeException("unexpected End of Stream")
nRead += delta
/**
* Reads next variable length block from input.
*
* @return number of bytes stored in "buffer"
*/
fun readBlock() : ByteArray {
val blockSize = read()
this.blockSize = blockSize
var n = 0
while(n < blockSize) {
val delta = bis .read(block, n, blockSize - n)
if(delta == - 1) throw RuntimeException("unexpected EOS")
n += delta
} }
}
// Reads specified bytes and compose it to ascii string
fun string(n : Int) : String {
val ba = ByteArray(n)
array(ba)
return ba.map { it.toChar() }.joinToString(separator = "")
}
// Reads next variable length block
fun block() : ByteArray {
blockSize = byte()
array(block, 0, blockSize)
return block return block
} }
/** // Skips variable length blocks up to and including next zero length block.
* Skips variable length blocks up to and including fun skipBlock() {
* next zero length block.
*/
fun skip() {
do { do {
readBlock() block()
} while((blockSize > 0)) } while(blockSize > 0)
}
// read n byte and compose it to ascii string
fun string(n : Int) =
StringBuilder()
.apply {
for(i in 0 until n) {
append(read().toChar())
} }
} }
.toString()
}
@Suppress("MemberVisibilityCanBePrivate", "unused")
class GifDecoder(val callback: GifDecoderCallback) {
companion object { companion object {
// max decoder pixel stack size // max decoder pixel stack size
const val MaxStackSize = 4096 private const val MaxStackSize = 4096
const val NullCode = - 1 private const val NullCode = - 1
private const val b0 = 0.toByte() private const val b0 = 0.toByte()
private const val OPAQUE = 0xff shl 24
} }
private var width = 0 // full image width private var width = 0 // full image width
@ -109,8 +75,6 @@ class GifDecoder(val callback: GifDecoderCallback) {
private var loopCount = 1 // iterations; 0 = repeat forever private var loopCount = 1 // iterations; 0 = repeat forever
private var gct : IntArray? = null // global color table private var gct : IntArray? = null // global color table
private var lct : IntArray? = null // local color table
private var act : IntArray? = null // active color table
private var bgIndex = 0 // background color index private var bgIndex = 0 // background color index
private var bgColor = 0 // background color private var bgColor = 0 // background color
@ -127,8 +91,7 @@ class GifDecoder(val callback: GifDecoderCallback) {
private var iw = 0 // current image rectangle private var iw = 0 // current image rectangle
private var ih = 0 // current image rectangle private var ih = 0 // current image rectangle
private var lastRect : Rectangle? = null // last image rect private val lastRect : Rectangle = Rectangle() // last image rect
private var image : ApngBitmap? = null // current frame
// last graphic control extension info // last graphic control extension info
private var dispose = 0 private var dispose = 0
@ -144,29 +107,101 @@ class GifDecoder(val callback: GifDecoderCallback) {
private var pixelStack : ByteArray? = null private var pixelStack : ByteArray? = null
private var pixels : ByteArray? = null private var pixels : ByteArray? = null
private var frames = ArrayList<GifFrame>() // frames read from current file private val frames = ArrayList<Pair<ApngFrameControl, ApngBitmap>>()
private var frameCount = 0
///////////////////////////////////////////////////////////// private var lastImage : ApngBitmap? = null
// get decode result.
/** // render to ApngBitmap
* Gets the image contents of frame n. // may use some previous frame.
* private fun render(destImage : ApngBitmap,act:IntArray) {
* @return BufferedImage representation of frame, or null if n is invalid. // expose destination image's pixels as int array
*/ val dest = destImage.colors
@Suppress("MemberVisibilityCanBePrivate", "unused") val dest_w = destImage.width
fun getFrame(n : Int) : ApngBitmap? {
frames?.let { // fill in starting image contents based on last image's dispose code
if(n in 0 until it.size) return it[n].image if(lastDispose > 0) {
if(lastDispose == 3) {
// use image before last
val idx = frames.size - 2
lastImage = if(idx > 0) {
frames[idx-1].second
} else {
null
} }
return null } else {
// lastDisposeが3以外でもlastImageはnullではない場合がある
} }
///////////////////////////////////////////////////////////// lastImage?.let{ lastImage ->
// private functions.
// copy pixels
System.arraycopy(lastImage.colors, 0, dest, 0, dest.size)
if(lastDispose == 2) {
val fillColor = if(transparency) {
0 // assume background is transparent
} else {
lastBgColor // use given background color
}
// fill lastRect
for(y in lastRect.y until lastRect.y + lastRect.h) {
val fillStart = y * dest_w + lastRect.x
val fillWidth = lastRect.w
dest.fill(fillColor, fromIndex = fillStart, toIndex = fillStart + fillWidth)
}
}
}
}
// copy each source line to the appropriate place in the destination
var pass = 1
var inc = 8
var iline = 0
for(i in 0 until ih) {
var line = i
if(interlace) {
if(iline >= ih) {
when(++ pass) {
2 -> {
iline = 4
}
3 -> {
iline = 2
inc = 4
}
4 -> {
iline = 1
inc = 2
}
}
}
line = iline
iline += inc
}
line += iy
if(line < height) {
// start of line in source
var sx = i * iw
//
val k = line * width
// loop for dest line.
for(dx in k + ix until min(k + width, k + ix + iw)) {
// map color and insert in destination
val index = pixels !![sx ++].toInt() and 0xff
val c = act[index]
if(c != 0) dest[dx] = c
}
}
}
}
/** /**
* Decodes LZW image data into pixel array. * Decodes LZW image data into pixel array.
@ -174,7 +209,7 @@ class GifDecoder(val callback: GifDecoderCallback) {
*/ */
private fun decodeImageData(reader : Reader) { private fun decodeImageData(reader : Reader) {
// Reallocate pizel array if need // allocate pixel array if need
val npix = iw * ih val npix = iw * ih
if((pixels?.size ?: 0) < npix) pixels = ByteArray(npix) if((pixels?.size ?: 0) < npix) pixels = ByteArray(npix)
val pixels = this.pixels !! val pixels = this.pixels !!
@ -187,9 +222,15 @@ class GifDecoder(val callback: GifDecoderCallback) {
val pixelStack = this.pixelStack !! val pixelStack = this.pixelStack !!
// Initialize GIF data stream decoder. // Initialize GIF data stream decoder.
val data_size = reader.read() val data_size = reader.byte()
val clear = 1 shl data_size val clear = 1 shl data_size
val end_of_information = clear + 1 val end_of_information = clear + 1
var available = clear + 2
var old_code = NullCode
var code_size = data_size + 1
var code_mask = (1 shl code_size) - 1
for(code in 0 until clear) { for(code in 0 until clear) {
prefix[code] = 0 prefix[code] = 0
suffix[code] = code.toByte() suffix[code] = code.toByte()
@ -203,10 +244,6 @@ class GifDecoder(val callback: GifDecoderCallback) {
var top = 0 var top = 0
var bi = 0 var bi = 0
var pi = 0 var pi = 0
var available = clear + 2
var old_code = NullCode
var code_size = data_size + 1
var code_mask = (1 shl code_size) - 1
var i = 0 var i = 0
while(i < npix) { while(i < npix) {
@ -215,7 +252,7 @@ class GifDecoder(val callback: GifDecoderCallback) {
// Load bytes until there are enough bits for a code. // Load bytes until there are enough bits for a code.
if(count == 0) { if(count == 0) {
// Read a new data block. // Read a new data block.
reader.readBlock() reader.block()
count = reader.blockSize count = reader.blockSize
if(count <= 0) break if(count <= 0) break
bi = 0 bi = 0
@ -292,123 +329,27 @@ class GifDecoder(val callback: GifDecoderCallback) {
} }
} }
/**
* Creates new frame image from current data (and previous
* frames as specified by their disposition codes).
*/
private fun setPixels(destImage : ApngBitmap) {
// expose destination image's pixels as int array
val dest = destImage.colors
val dest_w = destImage.width
// fill in starting image contents based on last image's dispose code
var lastImage : ApngBitmap? = null // previous frame
if(lastDispose > 0) {
if(lastDispose == 3) {
// use image before last
val n = frameCount - 2
if(n > 0) {
lastImage = getFrame(n - 1)
} else {
lastImage = null
}
}
}
if(lastImage != null) {
// copy pixels
System.arraycopy(lastImage.colors, 0, dest, 0, dest.size)
val lastRect = this.lastRect
if(lastDispose == 2 && lastRect != null) {
// fill lastRect
val fillColor = if(transparency) {
0 // assume background is transparent
} else {
lastBgColor // use given background color
}
for(y in lastRect.y until lastRect.y + lastRect.h) {
val fillStart = y * dest_w + lastRect.x
val fillWidth = lastRect.w
dest.fill(fillColor, fromIndex = fillStart, toIndex = fillStart + fillWidth)
}
}
}
// copy each source line to the appropriate place in the destination
var pass = 1
var inc = 8
var iline = 0
for(i in 0 until ih) {
var line = i
if(interlace) {
if(iline >= ih) {
pass ++
when(++ pass) {
2 -> {
iline = 4
inc = 8
}
3 -> {
iline = 2
inc = 4
}
4 -> {
iline = 1
inc = 2
}
}
}
line = iline
iline += inc
}
line += iy
if(line < height) {
// start of line in source
var sx = i * iw
//
val k = line * width
// loop for dest line.
for(dx in k + ix until min(k + width, k + ix + iw)) {
// map color and insert in destination
val index = pixels !![sx ++].toInt() and 0xff
val c = act !![index]
if(c != 0) dest[dx] = c
}
}
}
}
/** /**
* Reads color table as 256 RGB integer values * Reads color table as 256 RGB integer values
* *
* @param nColors int number of colors to read * @param nColors int number of colors to read
* @return int array containing 256 colors (packed ARGB with full alpha) * @return int array containing 256 colors (packed ARGB with full alpha)
*/ */
private fun readColorTable(reader:Reader,nColors : Int) : IntArray { private fun parseColorTable(reader : Reader, nColors : Int) : IntArray {
val nBytes = 3 * nColors val nBytes = 3 * nColors
val c = ByteArray(nBytes) val c = ByteArray(nBytes)
val n = reader.readArray(c) reader.array(c)
if(n < nBytes) throw RuntimeException("unexpected EOS")
// max size to avoid bounds checks // max size to avoid bounds checks
val tab = IntArray(256) val tab = IntArray(256)
var i = 0 var i = 0
var j = 0 var j = 0
val opaque = 0xff shl 24
while(i < nColors) { while(i < nColors) {
val r = c[j].toInt() and 255 val r = c[j].toInt() and 255
val g = c[j + 1].toInt() and 255 val g = c[j + 1].toInt() and 255
val b = c[j + 2].toInt() and 255 val b = c[j + 2].toInt() and 255
j += 3 j += 3
tab[i ++] = ( opaque or (r shl 16) or (g shl 8) or b) tab[i ++] = (OPAQUE or (r shl 16) or (g shl 8) or b)
} }
return tab return tab
} }
@ -416,31 +357,26 @@ class GifDecoder(val callback: GifDecoderCallback) {
/** /**
* Reads Graphics Control Extension values * Reads Graphics Control Extension values
*/ */
private fun readGraphicControlExt(reader:Reader) { private fun parseGraphicControlExt(reader : Reader) {
reader.read() // block size reader.byte() // block size
val packed = reader.byte() // packed fields
val packed = reader.read() // packed fields
dispose = (packed and 0x1c) shr 2 // disposal method dispose = (packed and 0x1c) shr 2 // disposal method
// elect to keep old image if discretionary // elect to keep old image if discretionary
if(dispose == 0) dispose = 1 if(dispose == 0) dispose = 1
transparency = (packed and 1) != 0 transparency = (packed and 1) != 0
// delay in milliseconds // delay in milliseconds
delay = reader.readShort() * 10 delay = reader.UInt16() * 10
// transparent color index // transparent color index
transIndex = reader.read() transIndex = reader.byte()
// block terminator // block terminator
reader.read() reader.byte()
} }
// Reads Netscape extension to obtain iteration count // Reads Netscape extension to obtain iteration count
private fun readNetscapeExt(reader : Reader) { private fun readNetscapeExt(reader : Reader) {
do { do {
val block = reader.readBlock() val block = reader.block()
if(block[0].toInt() == 1) { if(block[0].toInt() == 1) {
// loop count sub-block // loop count sub-block
val b1 = block[1].toInt() and 255 val b1 = block[1].toInt() and 255
@ -451,13 +387,13 @@ class GifDecoder(val callback: GifDecoderCallback) {
} }
// Reads next frame image // Reads next frame image
private fun readImage(reader:Reader) { private fun parseFrame(reader : Reader) {
ix = reader.readShort() // (sub)image position & size ix = reader.UInt16() // (sub)image position & size
iy = reader.readShort() iy = reader.UInt16()
iw = reader.readShort() iw = reader.UInt16()
ih = reader.readShort() ih = reader.UInt16()
val packed = reader.read() val packed = reader.byte()
lctFlag = (packed and 0x80) != 0 // 1 - local color table flag lctFlag = (packed and 0x80) != 0 // 1 - local color table flag
interlace = (packed and 0x40) != 0 // 2 - interlace flag interlace = (packed and 0x40) != 0 // 2 - interlace flag
// 3 - sort flag // 3 - sort flag
@ -465,33 +401,42 @@ class GifDecoder(val callback: GifDecoderCallback) {
lctSize = 2 shl (packed and 7) // 6-8 - local color table size lctSize = 2 shl (packed and 7) // 6-8 - local color table size
val act = if(lctFlag) { val act = if(lctFlag) {
lct = readColorTable(reader,lctSize) // read table // make local table active
lct !! // make local table active parseColorTable(reader, lctSize)
} else { } else {
if(bgIndex == transIndex) bgColor = 0 if(bgIndex == transIndex) bgColor = 0
gct !! // make global table active gct !! // make global table active
} }
this.act = act
var save = 0 var save = 0
if(transparency) { if(transparency) {
save = act[transIndex] save = act[transIndex]
act[transIndex] = 0 // set transparent color if specified act[transIndex] = 0 // set transparent color if specified
} }
decodeImageData(reader) // decode pixel data decodeImageData(reader) // decode pixel data
reader.skip() reader.skipBlock()
++ frameCount val image = ApngBitmap(width, height).also {
render(it,act) // transfer pixel data to image
// create new image to receive frame data
val image = ApngBitmap(width, height).apply {
setPixels(this) // transfer pixel data to image
} }
this.image = image
frames.add(GifFrame(image, delay)) // add image to frame list // add image to frame list
frames.add(
Pair(
ApngFrameControl(
width = width,
height = height,
xOffset = 0,
yOffset = 0,
disposeOp = DisposeOp.None,
blendOp = BlendOp.Source,
sequenceNumber = frames.size,
delayMilliseconds = delay.toLong()
),
image
)
)
if(transparency) { if(transparency) {
act[transIndex] = save act[transIndex] = save
@ -501,27 +446,27 @@ class GifDecoder(val callback: GifDecoderCallback) {
* Resets frame state for reading next image. * Resets frame state for reading next image.
*/ */
lastDispose = dispose lastDispose = dispose
lastRect = Rectangle(ix, iy, iw, ih) lastRect.set(ix, iy, iw, ih)
lastBgColor = bgColor lastBgColor = bgColor
dispose = 0 dispose = 0
transparency = false transparency = false
delay = 0 delay = 0
lct = null lastImage = image
} }
private fun readContents(reader : Reader) { // read GIF content blocks
// read GIF file content blocks private fun readContents(reader : Reader) : ApngAnimationControl {
loopBlocks@ while(true) { loopBlocks@ while(true) {
when(val blockCode = reader.read()) { when(val blockCode = reader.byte()) {
// image separator // image separator
0x2C -> readImage(reader) 0x2C -> parseFrame(reader)
// extension // extension
0x21 -> when(reader.read()) { 0x21 -> when(reader.byte()) {
// graphics control extension // graphics control extension
0xf9 -> readGraphicControlExt(reader) 0xf9 -> parseGraphicControlExt(reader)
// application extension // application extension
0xff -> { 0xff -> {
val block = reader.readBlock() val block = reader.block()
var app = "" var app = ""
for(i in 0 until 11) { for(i in 0 until 11) {
app += block[i].toChar() app += block[i].toChar()
@ -529,13 +474,13 @@ class GifDecoder(val callback: GifDecoderCallback) {
if(app == "NETSCAPE2.0") { if(app == "NETSCAPE2.0") {
readNetscapeExt(reader) readNetscapeExt(reader)
} else { } else {
reader.skip() // don't care reader.skipBlock() // don't care
} }
} }
else -> { else -> {
// uninteresting extension // uninteresting extension
reader.skip() reader.skipBlock()
} }
} }
@ -549,23 +494,25 @@ class GifDecoder(val callback: GifDecoderCallback) {
else -> error("unknown block code $blockCode") else -> error("unknown block code $blockCode")
} }
} }
return ApngAnimationControl(numFrames = frames.size, numPlays = loopCount)
} }
var header : ApngImageHeader? = null
var animationControl : ApngAnimationControl? = null
/**
* Reads GIF file header information.
*/
private fun readHeader(reader : Reader) {
/** /**
* Initializes or re-initializes reader * Initializes or re-initializes reader
*/ */
frameCount = 0 private fun reset(){
frames.clear() frames.clear()
lct = null lastImage = null
loopCount = ApngAnimationControl.PLAY_INDEFINITELY loopCount = ApngAnimationControl.PLAY_INDEFINITELY
}
/**
* Reads GIF file header information.
*/
private fun parseImageHeader(reader : Reader) : ApngImageHeader {
reset()
val id = reader.string(6) val id = reader.string(6)
if(! id.startsWith("GIF")) if(! id.startsWith("GIF"))
@ -576,12 +523,12 @@ class GifDecoder(val callback: GifDecoderCallback) {
*/ */
// logical screen size // logical screen size
width = reader.readShort() width = reader.UInt16()
height = reader.readShort() height = reader.UInt16()
if(width < 1 || height < 1) error("too small size. ${width}*${height}") if(width < 1 || height < 1) error("too small size. ${width}*${height}")
// packed fields // packed fields
val packed = reader.read() val packed = reader.byte()
// global color table used // global color table used
val gctFlag = (packed and 0x80) != 0 // 1 : global color table flag val gctFlag = (packed and 0x80) != 0 // 1 : global color table flag
@ -589,11 +536,11 @@ class GifDecoder(val callback: GifDecoderCallback) {
// 5 : gct sort flag // 5 : gct sort flag
gctSize = 2 shl (packed and 7) // 6-8 : gct size gctSize = 2 shl (packed and 7) // 6-8 : gct size
bgIndex = reader.read() // background color index bgIndex = reader.byte() // background color index
pixelAspect = reader.read() // pixel aspect ratio pixelAspect = reader.byte() // pixel aspect ratio
gct = if(gctFlag) { gct = if(gctFlag) {
val table = readColorTable(reader,gctSize) val table = parseColorTable(reader, gctSize)
bgColor = table[bgIndex] bgColor = table[bgIndex]
table table
} else { } else {
@ -601,7 +548,7 @@ class GifDecoder(val callback: GifDecoderCallback) {
null null
} }
val header = ApngImageHeader( return ApngImageHeader(
width = this.width, width = this.width,
height = this.height, height = this.height,
bitDepth = 8, bitDepth = 8,
@ -610,43 +557,22 @@ class GifDecoder(val callback: GifDecoderCallback) {
filterMethod = FilterMethod.Standard, filterMethod = FilterMethod.Standard,
interlaceMethod = InterlaceMethod.None interlaceMethod = InterlaceMethod.None
) )
this.header = header }
fun parse(src : InputStream, callback : GifDecoderCallback) {
val reader = Reader(src)
val header = parseImageHeader(reader)
val animationControl = readContents(reader)
// GIFは最後まで読まないとフレーム数が分からない
if(frames.isEmpty()) throw error("there is no frame.")
callback.onGifHeader(header) callback.onGifHeader(header)
}
@Suppress("MemberVisibilityCanBePrivate", "unused")
fun read(src : InputStream) {
val reader = Reader(src)
readHeader(reader)
readContents(reader)
val header = this.header!!
if(frameCount < 0) throw error("frameCount < 0")
// GIFは最後まで読まないとフレーム数が分からない
// 最後まで読んでからフレーム数をコールバック
val animationControl = ApngAnimationControl(numFrames = frameCount,numPlays = loopCount)
this.animationControl = animationControl
callback.onGifAnimationInfo(header, animationControl) callback.onGifAnimationInfo(header, animationControl)
// 各フレームを送る
var i=0
for(frame in frames) { for(frame in frames) {
val frameControl = ApngFrameControl( callback.onGifAnimationFrame(frame.first, frame.second)
width = header.width,
height = header.height,
xOffset = 0,
yOffset = 0,
disposeOp = DisposeOp.None,
blendOp = BlendOp.Source,
sequenceNumber=i++,
delayMilliseconds = frame.delay.toLong()
)
callback.onGifAnimationFrame(frameControl,frame.image)
}
frames.clear()
} }
reset()
}
} }

View File

@ -108,6 +108,7 @@ class ApngFrames private constructor(
} }
} }
private fun parseGif( private fun parseGif(
inStream : InputStream, inStream : InputStream,
pixelSizeMax : Int, pixelSizeMax : Int,
@ -115,7 +116,7 @@ class ApngFrames private constructor(
) : ApngFrames { ) : ApngFrames {
val result = ApngFrames(pixelSizeMax, debug) val result = ApngFrames(pixelSizeMax, debug)
try { try {
GifDecoder(result).read(inStream) GifDecoder().parse(inStream, result)
result.onParseComplete() result.onParseComplete()
return result.takeIf { result.defaultImage != null || result.frames?.isNotEmpty() == true } return result.takeIf { result.defaultImage != null || result.frames?.isNotEmpty() == true }
?: throw RuntimeException("GIF has no image") ?: throw RuntimeException("GIF has no image")
@ -125,24 +126,23 @@ class ApngFrames private constructor(
} }
} }
// PNGヘッダを確認
@Suppress("unused") @Suppress("unused")
fun parse( fun parse(
pixelSizeMax : Int, pixelSizeMax : Int,
debug : Boolean = false, debug : Boolean = false,
opener : () -> InputStream? opener : () -> InputStream?
) : ApngFrames? { ) : ApngFrames? {
val buf = ByteArray(8) { 0.toByte() } val buf = ByteArray(8) { 0.toByte() }
opener()?.use { it.read(buf, 0, buf.size) } opener()?.use { it.read(buf, 0, buf.size) }
if(buf.size >= 8 if(buf.size >= 8
&& (buf[0].toInt() and 0xff) == 0x89 && (buf[0].toInt() and 0xff) == 0x89
&& (buf[1].toInt() and 0xff) == 0x50 && (buf[1].toInt() and 0xff) == 0x50
) { ) {
return opener()?.use { parseApng(it, pixelSizeMax, debug) } return opener()?.use { parseApng(it, pixelSizeMax, debug) }
} }
if(buf.size >= 6 if(buf.size >= 6
&& buf[0].toChar() == 'G' && buf[0].toChar() == 'G'
&& buf[1].toChar() == 'I' && buf[1].toChar() == 'I'
@ -150,6 +150,7 @@ class ApngFrames private constructor(
) { ) {
return opener()?.use { parseGif(it, pixelSizeMax, debug) } return opener()?.use { parseGif(it, pixelSizeMax, debug) }
} }
return null return null
} }
} }
@ -460,7 +461,6 @@ class ApngFrames private constructor(
} }
override fun onGifAnimationInfo( override fun onGifAnimationInfo(
header : ApngImageHeader, header : ApngImageHeader,
animationControl : ApngAnimationControl animationControl : ApngAnimationControl
) { ) {
@ -469,14 +469,12 @@ class ApngFrames private constructor(
} }
this.animationControl = animationControl this.animationControl = animationControl
this.frames = ArrayList(animationControl.numFrames) this.frames = ArrayList(animationControl.numFrames)
val canvasBitmap = createBlankBitmap(header.width, header.height) val canvasBitmap = createBlankBitmap(header.width, header.height)
this.canvasBitmap = canvasBitmap this.canvasBitmap = canvasBitmap
this.canvas = Canvas(canvasBitmap) this.canvas = Canvas(canvasBitmap)
} }
override fun onGifAnimationFrame( override fun onGifAnimationFrame(
frameControl : ApngFrameControl, frameControl : ApngFrameControl,
frameBitmap : ApngBitmap frameBitmap : ApngBitmap
) { ) {
@ -488,9 +486,14 @@ class ApngFrames private constructor(
} }
val frames = this.frames ?: return val frames = this.frames ?: return
if(frames.isEmpty()) {
defaultImage?.recycle()
defaultImage = toAndroidBitmap(frameBitmap, pixelSizeMax)
// ここでwidth,heightがセットされる
}
val frameBitmapAndroid = toAndroidBitmap(frameBitmap) val frameBitmapAndroid = toAndroidBitmap(frameBitmap)
try { try {
val frame = Frame( val frame = Frame(
bitmap = scaleBitmap(pixelSizeMax, frameBitmapAndroid, recycleSrc = false), bitmap = scaleBitmap(pixelSizeMax, frameBitmapAndroid, recycleSrc = false),
timeStart = timeTotal, timeStart = timeTotal,

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 926 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB