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