diff --git a/apng/src/main/java/jp/juggler/apng/GifDecoder.kt b/apng/src/main/java/jp/juggler/apng/GifDecoder.kt
index 9426765a..a12e01c9 100644
--- a/apng/src/main/java/jp/juggler/apng/GifDecoder.kt
+++ b/apng/src/main/java/jp/juggler/apng/GifDecoder.kt
@@ -3,104 +3,70 @@ package jp.juggler.apng
import java.io.InputStream
import kotlin.math.min
-/**
- * Class GifDecoder - Decodes a GIF file into one or more frames.
- *
- * Example:
- *
- *
- * {@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
- * }
- * }
- *
- * 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)
-
-class GifFrame(val image : ApngBitmap, val delay : Int)
-
-private class Reader(val bis : InputStream) {
+class GifDecoder {
- var block = ByteArray(256) // current data block
- var blockSize = 0 // block size
-
- // Reads a single byte from the input stream.
- fun read() : Int = bis.read()
-
- fun readArray(ba:ByteArray,offset:Int=0,length:Int=ba.size-offset) =
- bis.read(ba,offset,length)
-
- /**
- * Reads next 16-bit value, LSB first
- */
- // read 16-bit value, LSB first
- fun readShort() = read() or (read() shl 8)
-
- /**
- * 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
+ 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) {
+ this.x = x
+ this.y = y
+ this.w = w
+ this.h = h
}
- return block
}
- /**
- * Skips variable length blocks up to and including
- * next zero length block.
- */
- fun skip() {
- do {
- readBlock()
- } 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())
- }
+ private class Reader(val bis : InputStream) {
+
+ var block = ByteArray(256) // current data block
+ var blockSize = 0 // block size
+
+ // Reads a single byte from the input stream.
+ fun byte() : Int = bis.read()
+
+ // Reads next 16-bit value, LSB first
+ fun UInt16() = byte() or (byte() shl 8)
+
+ fun array(ba : ByteArray, offset : Int = 0, length : Int = ba.size - offset) {
+ var nRead = 0
+ while(nRead < length) {
+ val delta = bis.read(ba, offset + nRead, length - nRead)
+ if(delta == - 1) throw RuntimeException("unexpected End of Stream")
+ nRead += delta
}
- .toString()
-
-}
-
-@Suppress("MemberVisibilityCanBePrivate", "unused")
-class GifDecoder(val callback: GifDecoderCallback) {
+ }
+
+ // 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
+ }
+
+ // Skips variable length blocks up to and including next zero length block.
+ fun skipBlock() {
+ do {
+ block()
+ } while(blockSize > 0)
+ }
+ }
companion object {
// 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 OPAQUE = 0xff shl 24
+
}
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 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 bgColor = 0 // background color
@@ -127,8 +91,7 @@ class GifDecoder(val callback: GifDecoderCallback) {
private var iw = 0 // current image rectangle
private var ih = 0 // current image rectangle
- private var lastRect : Rectangle? = null // last image rect
- private var image : ApngBitmap? = null // current frame
+ private val lastRect : Rectangle = Rectangle() // last image rect
// last graphic control extension info
private var dispose = 0
@@ -144,37 +107,109 @@ class GifDecoder(val callback: GifDecoderCallback) {
private var pixelStack : ByteArray? = null
private var pixels : ByteArray? = null
- private var frames = ArrayList() // frames read from current file
- private var frameCount = 0
+ private val frames = ArrayList>()
- /////////////////////////////////////////////////////////////
- // get decode result.
-
- /**
- * Gets the image contents of frame n.
- *
- * @return BufferedImage representation of frame, or null if n is invalid.
- */
- @Suppress("MemberVisibilityCanBePrivate", "unused")
- fun getFrame(n : Int) : ApngBitmap? {
- frames?.let {
- if(n in 0 until it.size) return it[n].image
- }
- return null
- }
-
- /////////////////////////////////////////////////////////////
- // private functions.
+ private var lastImage : ApngBitmap? = null
+ // render to ApngBitmap
+ // may use some previous frame.
+ private fun render(destImage : ApngBitmap,act:IntArray) {
+ // 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
+ 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
+ }
+ } else {
+ // lastDisposeが3以外でもlastImageはnullではない場合がある
+ }
+
+ lastImage?.let{ lastImage ->
+
+ // 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.
* Adapted from John Cristy's ImageMagick.
*/
- private fun decodeImageData(reader:Reader) {
+ private fun decodeImageData(reader : Reader) {
- // Reallocate pizel array if need
+ // allocate pixel array if need
val npix = iw * ih
if((pixels?.size ?: 0) < npix) pixels = ByteArray(npix)
val pixels = this.pixels !!
@@ -187,9 +222,15 @@ class GifDecoder(val callback: GifDecoderCallback) {
val pixelStack = this.pixelStack !!
// Initialize GIF data stream decoder.
- val data_size = reader.read()
+ val data_size = reader.byte()
val clear = 1 shl data_size
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) {
prefix[code] = 0
suffix[code] = code.toByte()
@@ -203,10 +244,6 @@ class GifDecoder(val callback: GifDecoderCallback) {
var top = 0
var bi = 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
while(i < npix) {
@@ -215,9 +252,9 @@ class GifDecoder(val callback: GifDecoderCallback) {
// Load bytes until there are enough bits for a code.
if(count == 0) {
// Read a new data block.
- reader.readBlock()
+ reader.block()
count = reader.blockSize
- if( count <= 0) break
+ if(count <= 0) break
bi = 0
}
datum += (reader.block[bi].toInt() and 0xff) shl bits
@@ -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
*
* @param nColors int number of colors to read
* @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 c = ByteArray(nBytes)
- val n = reader.readArray(c)
- if(n < nBytes) throw RuntimeException("unexpected EOS")
+ reader.array(c)
// max size to avoid bounds checks
val tab = IntArray(256)
var i = 0
var j = 0
- val opaque = 0xff shl 24
while(i < nColors) {
val r = c[j].toInt() and 255
val g = c[j + 1].toInt() and 255
val b = c[j + 2].toInt() and 255
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
}
@@ -416,48 +357,43 @@ class GifDecoder(val callback: GifDecoderCallback) {
/**
* Reads Graphics Control Extension values
*/
- private fun readGraphicControlExt(reader:Reader) {
- reader.read() // block size
-
- val packed = reader.read() // packed fields
-
+ private fun parseGraphicControlExt(reader : Reader) {
+ reader.byte() // block size
+ val packed = reader.byte() // packed fields
dispose = (packed and 0x1c) shr 2 // disposal method
-
// elect to keep old image if discretionary
if(dispose == 0) dispose = 1
transparency = (packed and 1) != 0
-
// delay in milliseconds
- delay = reader.readShort() * 10
+ delay = reader.UInt16() * 10
// transparent color index
- transIndex = reader.read()
-
+ transIndex = reader.byte()
// block terminator
- reader.read()
+ reader.byte()
}
// Reads Netscape extension to obtain iteration count
- private fun readNetscapeExt(reader:Reader) {
+ private fun readNetscapeExt(reader : Reader) {
do {
- val block = reader.readBlock()
+ val block = reader.block()
if(block[0].toInt() == 1) {
// loop count sub-block
val b1 = block[1].toInt() and 255
val b2 = block[2].toInt() and 255
loopCount = ((b2 shl 8) and b1)
}
- } while( reader.blockSize > 0 )
+ } while(reader.blockSize > 0)
}
// Reads next frame image
- private fun readImage(reader:Reader) {
- ix = reader.readShort() // (sub)image position & size
- iy = reader.readShort()
- iw = reader.readShort()
- ih = reader.readShort()
+ private fun parseFrame(reader : Reader) {
+ ix = reader.UInt16() // (sub)image position & size
+ iy = reader.UInt16()
+ iw = reader.UInt16()
+ ih = reader.UInt16()
- val packed = reader.read()
+ val packed = reader.byte()
lctFlag = (packed and 0x80) != 0 // 1 - local color table flag
interlace = (packed and 0x40) != 0 // 2 - interlace flag
// 3 - sort flag
@@ -465,63 +401,72 @@ class GifDecoder(val callback: GifDecoderCallback) {
lctSize = 2 shl (packed and 7) // 6-8 - local color table size
val act = if(lctFlag) {
- lct = readColorTable(reader,lctSize) // read table
- lct !! // make local table active
+ // make local table active
+ parseColorTable(reader, lctSize)
} else {
if(bgIndex == transIndex) bgColor = 0
gct !! // make global table active
}
- this.act = act
var save = 0
if(transparency) {
save = act[transIndex]
act[transIndex] = 0 // set transparent color if specified
}
-
decodeImageData(reader) // decode pixel data
- reader.skip()
+ reader.skipBlock()
- ++ frameCount
-
- // create new image to receive frame data
- val image = ApngBitmap(width, height).apply {
- setPixels(this) // transfer pixel data to image
+ val image = ApngBitmap(width, height).also {
+ render(it,act) // 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) {
act[transIndex] = save
}
-
+
/**
* Resets frame state for reading next image.
*/
lastDispose = dispose
- lastRect = Rectangle(ix, iy, iw, ih)
+ lastRect.set(ix, iy, iw, ih)
lastBgColor = bgColor
dispose = 0
transparency = false
delay = 0
- lct = null
+ lastImage = image
}
- private fun readContents(reader : Reader) {
- // read GIF file content blocks
+ // read GIF content blocks
+ private fun readContents(reader : Reader) : ApngAnimationControl {
loopBlocks@ while(true) {
- when(val blockCode = reader.read()) {
+ when(val blockCode = reader.byte()) {
// image separator
- 0x2C -> readImage(reader)
+ 0x2C -> parseFrame(reader)
// extension
- 0x21 -> when(reader.read()) {
+ 0x21 -> when(reader.byte()) {
// graphics control extension
- 0xf9 -> readGraphicControlExt(reader)
+ 0xf9 -> parseGraphicControlExt(reader)
// application extension
0xff -> {
- val block = reader.readBlock()
+ val block = reader.block()
var app = ""
for(i in 0 until 11) {
app += block[i].toChar()
@@ -529,13 +474,13 @@ class GifDecoder(val callback: GifDecoderCallback) {
if(app == "NETSCAPE2.0") {
readNetscapeExt(reader)
} else {
- reader.skip() // don't care
+ reader.skipBlock() // don't care
}
}
else -> {
// uninteresting extension
- reader.skip()
+ reader.skipBlock()
}
}
@@ -549,23 +494,25 @@ class GifDecoder(val callback: GifDecoderCallback) {
else -> error("unknown block code $blockCode")
}
}
+
+ return ApngAnimationControl(numFrames = frames.size, numPlays = loopCount)
}
- var header : ApngImageHeader? = null
- var animationControl : ApngAnimationControl? = null
+ /**
+ * Initializes or re-initializes reader
+ */
+ private fun reset(){
+ frames.clear()
+ lastImage = null
+ loopCount = ApngAnimationControl.PLAY_INDEFINITELY
+ }
/**
* Reads GIF file header information.
*/
- private fun readHeader(reader : Reader) {
+ private fun parseImageHeader(reader : Reader) : ApngImageHeader {
- /**
- * Initializes or re-initializes reader
- */
- frameCount = 0
- frames.clear()
- lct = null
- loopCount = ApngAnimationControl.PLAY_INDEFINITELY
+ reset()
val id = reader.string(6)
if(! id.startsWith("GIF"))
@@ -574,14 +521,14 @@ class GifDecoder(val callback: GifDecoderCallback) {
/**
* Reads Logical Screen Descriptor
*/
-
+
// logical screen size
- width = reader.readShort()
- height = reader.readShort()
- if( width < 1 || height < 1) error("too small size. ${width}*${height}")
+ width = reader.UInt16()
+ height = reader.UInt16()
+ if(width < 1 || height < 1) error("too small size. ${width}*${height}")
// packed fields
- val packed = reader.read()
+ val packed = reader.byte()
// global color table used
val gctFlag = (packed and 0x80) != 0 // 1 : global color table flag
@@ -589,19 +536,19 @@ class GifDecoder(val callback: GifDecoderCallback) {
// 5 : gct sort flag
gctSize = 2 shl (packed and 7) // 6-8 : gct size
- bgIndex = reader.read() // background color index
- pixelAspect = reader.read() // pixel aspect ratio
+ bgIndex = reader.byte() // background color index
+ pixelAspect = reader.byte() // pixel aspect ratio
gct = if(gctFlag) {
- val table = readColorTable(reader,gctSize)
+ val table = parseColorTable(reader, gctSize)
bgColor = table[bgIndex]
table
- }else{
+ } else {
bgColor = 0
null
}
- val header = ApngImageHeader(
+ return ApngImageHeader(
width = this.width,
height = this.height,
bitDepth = 8,
@@ -610,43 +557,22 @@ class GifDecoder(val callback: GifDecoderCallback) {
filterMethod = FilterMethod.Standard,
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)
- }
-
-
- @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)
-
- // 各フレームを送る
- var i=0
- for(frame in frames){
- val frameControl = ApngFrameControl(
- 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)
+ callback.onGifAnimationInfo(header, animationControl)
+ for(frame in frames) {
+ callback.onGifAnimationFrame(frame.first, frame.second)
}
- frames.clear()
+
+ reset()
}
-
-}
\ No newline at end of file
+}
diff --git a/apng_android/src/main/java/jp/juggler/apng/ApngFrames.kt b/apng_android/src/main/java/jp/juggler/apng/ApngFrames.kt
index 35f4d211..9f1ddcf5 100644
--- a/apng_android/src/main/java/jp/juggler/apng/ApngFrames.kt
+++ b/apng_android/src/main/java/jp/juggler/apng/ApngFrames.kt
@@ -108,6 +108,7 @@ class ApngFrames private constructor(
}
}
+
private fun parseGif(
inStream : InputStream,
pixelSizeMax : Int,
@@ -115,7 +116,7 @@ class ApngFrames private constructor(
) : ApngFrames {
val result = ApngFrames(pixelSizeMax, debug)
try {
- GifDecoder(result).read(inStream)
+ GifDecoder().parse(inStream, result)
result.onParseComplete()
return result.takeIf { result.defaultImage != null || result.frames?.isNotEmpty() == true }
?: throw RuntimeException("GIF has no image")
@@ -125,31 +126,31 @@ class ApngFrames private constructor(
}
}
-
- // PNGヘッダを確認
-
-
@Suppress("unused")
fun parse(
pixelSizeMax : Int,
debug : Boolean = false,
- opener: ()->InputStream?
+ opener : () -> InputStream?
) : ApngFrames? {
- val buf = ByteArray(8){ 0.toByte() }
- opener()?.use{ it.read(buf,0,buf.size) }
+
+ val buf = ByteArray(8) { 0.toByte() }
+ opener()?.use { it.read(buf, 0, buf.size) }
+
if(buf.size >= 8
&& (buf[0].toInt() and 0xff) == 0x89
&& (buf[1].toInt() and 0xff) == 0x50
) {
- return opener()?.use{ parseApng(it,pixelSizeMax,debug) }
+ return opener()?.use { parseApng(it, pixelSizeMax, debug) }
}
+
if(buf.size >= 6
&& buf[0].toChar() == 'G'
&& buf[1].toChar() == 'I'
&& buf[2].toChar() == 'F'
) {
- return opener()?.use{ parseGif(it,pixelSizeMax,debug) }
+ return opener()?.use { parseGif(it, pixelSizeMax, debug) }
}
+
return null
}
}
@@ -460,7 +461,6 @@ class ApngFrames private constructor(
}
override fun onGifAnimationInfo(
-
header : ApngImageHeader,
animationControl : ApngAnimationControl
) {
@@ -469,14 +469,12 @@ class ApngFrames private constructor(
}
this.animationControl = animationControl
this.frames = ArrayList(animationControl.numFrames)
-
val canvasBitmap = createBlankBitmap(header.width, header.height)
this.canvasBitmap = canvasBitmap
this.canvas = Canvas(canvasBitmap)
}
override fun onGifAnimationFrame(
-
frameControl : ApngFrameControl,
frameBitmap : ApngBitmap
) {
@@ -488,9 +486,14 @@ class ApngFrames private constructor(
}
val frames = this.frames ?: return
+ if(frames.isEmpty()) {
+ defaultImage?.recycle()
+ defaultImage = toAndroidBitmap(frameBitmap, pixelSizeMax)
+ // ここでwidth,heightがセットされる
+ }
+
val frameBitmapAndroid = toAndroidBitmap(frameBitmap)
try {
-
val frame = Frame(
bitmap = scaleBitmap(pixelSizeMax, frameBitmapAndroid, recycleSrc = false),
timeStart = timeTotal,
diff --git a/sample_apng/src/main/res/raw/gif_fire.gif b/sample_apng/src/main/res/raw/gif_fire.gif
new file mode 100644
index 00000000..c9265fa3
Binary files /dev/null and b/sample_apng/src/main/res/raw/gif_fire.gif differ
diff --git a/sample_apng/src/main/res/raw/gif_gifgrid.gif b/sample_apng/src/main/res/raw/gif_gifgrid.gif
new file mode 100644
index 00000000..777c267b
Binary files /dev/null and b/sample_apng/src/main/res/raw/gif_gifgrid.gif differ
diff --git a/sample_apng/src/main/res/raw/gif_hourglass_64.gif b/sample_apng/src/main/res/raw/gif_hourglass_64.gif
new file mode 100644
index 00000000..6e42eb2b
Binary files /dev/null and b/sample_apng/src/main/res/raw/gif_hourglass_64.gif differ
diff --git a/sample_apng/src/main/res/raw/gif_porsche.gif b/sample_apng/src/main/res/raw/gif_porsche.gif
new file mode 100644
index 00000000..916014e2
Binary files /dev/null and b/sample_apng/src/main/res/raw/gif_porsche.gif differ
diff --git a/sample_apng/src/main/res/raw/gif_solid2.gif b/sample_apng/src/main/res/raw/gif_solid2.gif
new file mode 100644
index 00000000..4e9524b0
Binary files /dev/null and b/sample_apng/src/main/res/raw/gif_solid2.gif differ
diff --git a/sample_apng/src/main/res/raw/gif_treescap.gif b/sample_apng/src/main/res/raw/gif_treescap.gif
new file mode 100644
index 00000000..f9fb5453
Binary files /dev/null and b/sample_apng/src/main/res/raw/gif_treescap.gif differ
diff --git a/sample_apng/src/main/res/raw/gif_treescap_interlaced.gif b/sample_apng/src/main/res/raw/gif_treescap_interlaced.gif
new file mode 100644
index 00000000..15cb77ba
Binary files /dev/null and b/sample_apng/src/main/res/raw/gif_treescap_interlaced.gif differ
diff --git a/sample_apng/src/main/res/raw/gif_welcome2.gif b/sample_apng/src/main/res/raw/gif_welcome2.gif
new file mode 100644
index 00000000..0068b3f3
Binary files /dev/null and b/sample_apng/src/main/res/raw/gif_welcome2.gif differ
diff --git a/sample_apng/src/main/res/raw/gif_x_trans.gif b/sample_apng/src/main/res/raw/gif_x_trans.gif
new file mode 100644
index 00000000..6dab62da
Binary files /dev/null and b/sample_apng/src/main/res/raw/gif_x_trans.gif differ