From c4e38bf54f1497b475038fc6f6b8e3ce4562965f Mon Sep 17 00:00:00 2001 From: Oupson Date: Mon, 22 Feb 2021 12:03:10 +0100 Subject: [PATCH] Remove deprecated classes --- .../main/java/oupson/apng/APNGDisassembler.kt | 338 ---------- .../src/main/java/oupson/apng/Apng.kt | 435 ------------- .../src/main/java/oupson/apng/ApngAnimator.kt | 567 ----------------- .../main/java/oupson/apng/BitmapDrawable.kt | 39 -- .../oupson/apng/CustomAnimationDrawable.kt | 22 - .../src/main/java/oupson/apng/Frame.kt | 97 --- .../src/main/java/oupson/apng/chunks/Chunk.kt | 15 - .../src/main/java/oupson/apng/chunks/IDAT.kt | 24 - .../src/main/java/oupson/apng/chunks/IHDR.kt | 30 - .../src/main/java/oupson/apng/chunks/fcTL.kt | 62 -- .../java/oupson/apng/decoder/ApngDecoder.kt | 2 +- .../apng/imageUtils/BitmapDiffCalculator.kt | 69 --- .../java/oupson/apng/imageUtils/PngEncoder.kt | 419 ------------- .../oupson/apng/imageUtils/PnnQuantizer.java | 578 ------------------ .../oupson/apng/utils/ApngAnimatorOptions.kt | 6 - .../java/oupson/apng/{ => utils}/Loader.kt | 2 +- .../apngcreator/fragments/KotlinFragment.kt | 84 ++- 17 files changed, 68 insertions(+), 2721 deletions(-) delete mode 100644 apng_library/src/main/java/oupson/apng/APNGDisassembler.kt delete mode 100644 apng_library/src/main/java/oupson/apng/Apng.kt delete mode 100644 apng_library/src/main/java/oupson/apng/ApngAnimator.kt delete mode 100644 apng_library/src/main/java/oupson/apng/BitmapDrawable.kt delete mode 100644 apng_library/src/main/java/oupson/apng/CustomAnimationDrawable.kt delete mode 100644 apng_library/src/main/java/oupson/apng/Frame.kt delete mode 100644 apng_library/src/main/java/oupson/apng/chunks/Chunk.kt delete mode 100644 apng_library/src/main/java/oupson/apng/chunks/IDAT.kt delete mode 100644 apng_library/src/main/java/oupson/apng/chunks/IHDR.kt delete mode 100644 apng_library/src/main/java/oupson/apng/chunks/fcTL.kt delete mode 100644 apng_library/src/main/java/oupson/apng/imageUtils/BitmapDiffCalculator.kt delete mode 100644 apng_library/src/main/java/oupson/apng/imageUtils/PngEncoder.kt delete mode 100644 apng_library/src/main/java/oupson/apng/imageUtils/PnnQuantizer.java delete mode 100644 apng_library/src/main/java/oupson/apng/utils/ApngAnimatorOptions.kt rename apng_library/src/main/java/oupson/apng/{ => utils}/Loader.kt (98%) diff --git a/apng_library/src/main/java/oupson/apng/APNGDisassembler.kt b/apng_library/src/main/java/oupson/apng/APNGDisassembler.kt deleted file mode 100644 index bb614f7..0000000 --- a/apng_library/src/main/java/oupson/apng/APNGDisassembler.kt +++ /dev/null @@ -1,338 +0,0 @@ -package oupson.apng - -import android.graphics.BitmapFactory -import oupson.apng.chunks.IHDR -import oupson.apng.chunks.fcTL -import oupson.apng.exceptions.BadApngException -import oupson.apng.exceptions.BadCRCException -import oupson.apng.exceptions.NotApngException -import oupson.apng.exceptions.NotPngException -import oupson.apng.utils.Utils -import oupson.apng.utils.Utils.Companion.isApng -import oupson.apng.utils.Utils.Companion.isPng -import oupson.apng.utils.Utils.Companion.pngSignature -import oupson.apng.utils.Utils.Companion.uIntFromBytesBigEndian -import oupson.apng.utils.Utils.Companion.uIntToByteArray -import java.io.InputStream -import java.util.* -import java.util.zip.CRC32 - -@Deprecated("Deprecated, Use ApngEncoder and ApngDecoder instead", level = DeprecationLevel.WARNING) -class APNGDisassembler { - private var png: ArrayList? = null - private var cover: ArrayList? = null - private var delay = -1f - private var yOffset = -1 - private var xOffset = -1 - private var plte: ByteArray? = null - private var tnrs: ByteArray? = null - private var maxWidth = 0 - private var maxHeight = 0 - private var blendOp: Utils.Companion.BlendOp = Utils.Companion.BlendOp.APNG_BLEND_OP_SOURCE - private var disposeOp: Utils.Companion.DisposeOp = - Utils.Companion.DisposeOp.APNG_DISPOSE_OP_NONE - private var ihdr = IHDR() - private var isApng = false - - var apng: Apng = Apng() - - /** - * Disassemble an Apng file - * @param byteArray The Byte Array of the file - * @return [Apng] The apng decoded - */ - fun disassemble(byteArray: ByteArray): Apng { - reset() - if (isApng(byteArray)) { - var cursor = 8 - while (cursor < byteArray.size) { - val length = uIntFromBytesBigEndian(byteArray.copyOfRange(cursor, cursor + 4).map { it.toInt() }) - val chunk = byteArray.copyOfRange(cursor, cursor + length + 12) - parseChunk(chunk) - cursor += length + 12 - } - return apng - } else { - throw NotApngException() - } - } - - /** - * Disassemble an Apng file - * @param input Input Stream - * @return [Apng] The apng decoded - */ - fun disassemble(input: InputStream): Apng { - reset() - val buffer = ByteArray(8) - - input.read(buffer) - - if (!isPng(buffer)) - throw NotPngException() - - var byteRead: Int - - val lengthChunk = ByteArray(4) - do { - byteRead = input.read(lengthChunk) - - if (byteRead == -1) - break - val length = uIntFromBytesBigEndian(lengthChunk.map(Byte::toInt)) - - val chunk = ByteArray(length + 8) - byteRead = input.read(chunk) - - parseChunk(lengthChunk.plus(chunk)) - } while (byteRead != -1) - - return apng - } - - /** - * Generate a correct IHDR from the IHDR chunk of the APNG - * @param ihdrOfApng The IHDR of the APNG - * @param width The width of the frame - * @param height The height of the frame - * @return [ByteArray] The generated IHDR - */ - private fun generateIhdr(ihdrOfApng: IHDR, width: Int, height: Int): ByteArray { - val ihdr = ArrayList() - // We need a body var to know body length and generate crc - val ihdrBody = ArrayList() - // Add chunk body length - ihdr.addAll(uIntToByteArray(ihdrOfApng.body.size).asList()) - // Add IHDR - ihdrBody.addAll( - byteArrayOf( - 0x49.toByte(), - 0x48.toByte(), - 0x44.toByte(), - 0x52.toByte() - ).asList() - ) - // Add the max width and height - ihdrBody.addAll(uIntToByteArray(width).asList()) - ihdrBody.addAll(uIntToByteArray(height).asList()) - // Add complicated stuff like depth color ... - // If you want correct png you need same parameters. Good solution is to create new png. - ihdrBody.addAll(ihdrOfApng.body.copyOfRange(8, 13).asList()) - // Generate CRC - val crC32 = CRC32() - crC32.update(ihdrBody.toByteArray(), 0, ihdrBody.size) - ihdr.addAll(ihdrBody) - ihdr.addAll(uIntToByteArray(crC32.value.toInt()).asList()) - return ihdr.toByteArray() - } - - /** - * Parse the chunk - * @param byteArray The chunk with length and crc - */ - private fun parseChunk(byteArray: ByteArray) { - val i = 4 - val chunkCRC = uIntFromBytesBigEndian(byteArray.copyOfRange(byteArray.size - 4, byteArray.size).map(Byte::toInt)) - val crc = CRC32() - crc.update(byteArray.copyOfRange(i, byteArray.size - 4)) - if (chunkCRC == crc.value.toInt()) { - val name = byteArray.copyOfRange(i, i + 4) - when { - name.contentEquals(Utils.fcTL) -> { - if (png == null) { - cover?.let { - it.addAll(uIntToByteArray(0).asList()) - // Add IEND - val iend = byteArrayOf(0x49, 0x45, 0x4E, 0x44) - // Generate crc for IEND - val crC32 = CRC32() - crC32.update(iend, 0, iend.size) - it.addAll(iend.asList()) - it.addAll(uIntToByteArray(crC32.value.toInt()).asList()) - apng.cover = BitmapFactory.decodeByteArray(it.toByteArray(), 0, it.size) - } - png = ArrayList() - val fcTL = fcTL() - fcTL.parse(byteArray) - delay = fcTL.delay - yOffset = fcTL.yOffset - xOffset = fcTL.xOffset - blendOp = fcTL.blendOp - disposeOp = fcTL.disposeOp - val width = fcTL.pngWidth - val height = fcTL.pngHeight - - if (xOffset + width > maxWidth) { - throw BadApngException("`yOffset` + `height` must be <= `IHDR` height") - } else if (yOffset + height > maxHeight) { - throw BadApngException("`yOffset` + `height` must be <= `IHDR` height") - } - - png?.addAll(pngSignature.asList()) - png?.addAll(generateIhdr(ihdr, width, height).asList()) - plte?.let { - png?.addAll(it.asList()) - } - tnrs?.let { - png?.addAll(it.asList()) - } - } else { - // Add IEND body length : 0 - png?.addAll(uIntToByteArray(0).asList()) - // Add IEND - val iend = byteArrayOf(0x49, 0x45, 0x4E, 0x44) - // Generate crc for IEND - val crC32 = CRC32() - crC32.update(iend, 0, iend.size) - png?.addAll(iend.asList()) - png?.addAll(uIntToByteArray(crC32.value.toInt()).asList()) - apng.frames.add( - Frame( - png!!.toByteArray(), - delay, - xOffset, - yOffset, - blendOp, - disposeOp, - maxWidth, - maxHeight - ) - ) - png = ArrayList() - val fcTL = fcTL() - fcTL.parse(byteArray) - delay = fcTL.delay - yOffset = fcTL.yOffset - xOffset = fcTL.xOffset - blendOp = fcTL.blendOp - disposeOp = fcTL.disposeOp - val width = fcTL.pngWidth - val height = fcTL.pngHeight - png?.addAll(pngSignature.asList()) - png?.addAll(generateIhdr(ihdr, width, height).asList()) - plte?.let { - png?.addAll(it.asList()) - } - tnrs?.let { - png?.addAll(it.asList()) - } - } - } - name.contentEquals(Utils.IEND) -> { - if (isApng) { - png?.addAll(uIntToByteArray(0).asList()) - // Add IEND - val iend = byteArrayOf(0x49, 0x45, 0x4E, 0x44) - // Generate crc for IEND - val crC32 = CRC32() - crC32.update(iend, 0, iend.size) - png?.addAll(iend.asList()) - png?.addAll(uIntToByteArray(crC32.value.toInt()).asList()) - apng.frames.add( - Frame( - png!!.toByteArray(), - delay, - xOffset, - yOffset, - blendOp, - disposeOp, - maxWidth, - maxHeight - ) - ) - } else { - cover?.let { - it.addAll(uIntToByteArray(0).asList()) - // Add IEND - val iend = byteArrayOf(0x49, 0x45, 0x4E, 0x44) - // Generate crc for IEND - val crC32 = CRC32() - crC32.update(iend, 0, iend.size) - it.addAll(iend.asList()) - it.addAll(uIntToByteArray(crC32.value.toInt()).asList()) - apng.cover = BitmapFactory.decodeByteArray(it.toByteArray(), 0, it.size) - } - apng.isApng = false - } - } - name.contentEquals(Utils.IDAT) -> { - if (png == null) { - if (cover == null) { - cover = ArrayList() - cover?.addAll(pngSignature.asList()) - cover?.addAll(generateIhdr(ihdr, maxWidth, maxHeight).asList()) - } - // Find the chunk length - val bodySize = uIntFromBytesBigEndian(byteArray.copyOfRange(i - 4, i).map(Byte::toInt)) - cover?.addAll(byteArray.copyOfRange(i - 4, i).asList()) - val body = ArrayList() - body.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).asList()) - // Get image bytes - body.addAll(byteArray.copyOfRange(i + 4, i + 4 + bodySize).asList()) - val crC32 = CRC32() - crC32.update(body.toByteArray(), 0, body.size) - cover?.addAll(body) - cover?.addAll(uIntToByteArray(crC32.value.toInt()).asList()) - } else { - // Find the chunk length - val bodySize = uIntFromBytesBigEndian(byteArray.copyOfRange(i - 4, i).map(Byte::toInt)) - png?.addAll(byteArray.copyOfRange(i - 4, i).asList()) - val body = ArrayList() - body.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).asList()) - // Get image bytes - body.addAll(byteArray.copyOfRange(i + 4, i + 4 + bodySize).asList()) - val crC32 = CRC32() - crC32.update(body.toByteArray(), 0, body.size) - png?.addAll(body) - png?.addAll(uIntToByteArray(crC32.value.toInt()).asList()) - } - } - name.contentEquals(Utils.fdAT) -> { - // Find the chunk length - val bodySize = uIntFromBytesBigEndian(byteArray.copyOfRange(i - 4, i).map(Byte::toInt)) - png?.addAll(uIntToByteArray(bodySize - 4).asList()) - val body = ArrayList() - body.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).asList()) - // Get image bytes - body.addAll(byteArray.copyOfRange(i + 8, i + 4 + bodySize).asList()) - val crC32 = CRC32() - crC32.update(body.toByteArray(), 0, body.size) - png?.addAll(body) - png?.addAll(uIntToByteArray(crC32.value.toInt()).asList()) - } - name.contentEquals(Utils.plte) -> { - plte = byteArray - } - name.contentEquals(Utils.tnrs) -> { - tnrs = byteArray - } - name.contentEquals(Utils.IHDR) -> { - ihdr.parse(byteArray) - maxWidth = ihdr.pngWidth - maxHeight = ihdr.pngHeight - } - name.contentEquals(Utils.acTL) -> { - isApng = true - } - } - } else throw BadCRCException() - } - - /** - * Reset all var before parsing APNG - */ - private fun reset() { - png = null - cover = null - delay = -1f - yOffset = -1 - xOffset = -1 - plte = null - tnrs = null - maxWidth = 0 - maxHeight = 0 - ihdr = IHDR() - apng = Apng() - isApng = false - } -} \ No newline at end of file diff --git a/apng_library/src/main/java/oupson/apng/Apng.kt b/apng_library/src/main/java/oupson/apng/Apng.kt deleted file mode 100644 index 6f6620a..0000000 --- a/apng_library/src/main/java/oupson/apng/Apng.kt +++ /dev/null @@ -1,435 +0,0 @@ -package oupson.apng - -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.os.Environment -import oupson.apng.chunks.IDAT -import oupson.apng.exceptions.NoFrameException -import oupson.apng.imageUtils.BitmapDiffCalculator -import oupson.apng.imageUtils.PngEncoder -import oupson.apng.imageUtils.PnnQuantizer -import oupson.apng.utils.Utils -import oupson.apng.utils.Utils.Companion.encodeBlendOp -import oupson.apng.utils.Utils.Companion.encodeDisposeOp -import oupson.apng.utils.Utils.Companion.pngSignature -import java.io.File -import java.util.zip.CRC32 - - -// TODO REMOVE -/** - * Create an APNG file - * If you want to create an APNG, use ApngEncoder instead - */ -@Suppress("unused") -@Deprecated("Deprecated, Use ApngEncoder and ApngDecoder instead", level = DeprecationLevel.WARNING) -class Apng { - @Suppress("MemberVisibilityCanBePrivate") - var maxWidth : Int? = null - @Suppress("MemberVisibilityCanBePrivate") - var maxHeight : Int? = null - - /** - * Image that will display in non compatible reader - * It's not necessary if the first frame is the biggest image. - * If it's null the library generate a cover with the first frame - */ - var cover : Bitmap? = null - - var frames : ArrayList = ArrayList() - - var isApng = true - - // region addFrames - /** - * Add a frame to the APNG - * @param bitmap The bitmap to add - * @param index Index of the frame in the animation - * @param delay Delay of the frame - * @param xOffset The X offset where the frame should be rendered - * @param yOffset The Y offset where the frame should be rendered - * @param disposeOp `DisposeOp` specifies how the output buffer should be changed at the end of the delay (before rendering the next frame). - * @param blendOp `BlendOp` specifies whether the frame is to be alpha blended into the current output buffer content, or whether it should completely replace its region in the output buffer. - */ - @Suppress("MemberVisibilityCanBePrivate") - fun addFrames(bitmap : Bitmap, index : Int? = null, delay : Float = 1000f, xOffset : Int = 0, yOffset : Int = 0, disposeOp: Utils.Companion.DisposeOp = Utils.Companion.DisposeOp.APNG_DISPOSE_OP_NONE, blendOp: Utils.Companion.BlendOp = Utils.Companion.BlendOp.APNG_BLEND_OP_SOURCE) { - if (index == null) - frames.add(Frame(PngEncoder().encode(bitmap, true), delay, xOffset, yOffset, blendOp, disposeOp)) - else - frames.add(index, Frame(PngEncoder().encode(bitmap, true), delay, xOffset, yOffset, blendOp, disposeOp)) - } - - /** - * Add a frame to the APNG - * @param frame The frame to add - * @param index Index of the frame in the animation - */ - fun addFrames(frame : Frame, index: Int? = null) { - if (index == null) - frames.add(frame) - else - frames.add(index, frame) - } - //endregion - - /** - * Generate a Bytes Array of the APNG - * @return [ByteArray] The Bytes Array of the APNG - */ - fun toByteArray() : ByteArray { - var seq = 0 - val res = ArrayList() - // Add PNG signature - res.addAll(pngSignature.asList()) - // Add Image Header - res.addAll(generateIhdr().asList()) - - // Add Animation Controller - res.addAll(generateACTL()) - - // Get max height and max width - maxHeight = frames.sortedByDescending { it.height }[0].height - maxWidth = frames.sortedByDescending { it.width }[0].width - - if (cover == null) { - val framesByte = ArrayList() - // region fcTL - // Create the fcTL - val fcTL = ArrayList() - - // Add the length of the chunk body - framesByte.addAll(byteArrayOf(0x00, 0x00, 0x00, 0x1A).asList()) - - // Add fcTL - fcTL.addAll(byteArrayOf(0x66, 0x63, 0x54, 0x4c).asList()) - - // Add the frame number - fcTL.addAll(Utils.uIntToByteArray(seq).asList()) - - // foreach fcTL or fdAT we must increment seq - seq++ - - // Add width and height - fcTL.addAll(Utils.uIntToByteArray(frames[0].width).asList()) - fcTL.addAll(Utils.uIntToByteArray(frames[0].height).asList()) - - // Add offsets - fcTL.addAll(Utils.uIntToByteArray(frames[0].xOffsets).asList()) - fcTL.addAll(Utils.uIntToByteArray(frames[0].yOffsets).asList()) - - // Set frame delay - fcTL.addAll(Utils.uShortToArray(frames[0].delay.toInt()).asList()) - fcTL.addAll(Utils.uShortToArray(1000).asList()) - - // Add DisposeOp and BlendOp - fcTL.add(encodeDisposeOp(frames[0].disposeOp).toByte()) - fcTL.add(encodeBlendOp(frames[0].blendOp).toByte()) - - // Create CRC - val crc = CRC32() - crc.update(fcTL.toByteArray(), 0, fcTL.size) - framesByte.addAll(fcTL) - framesByte.addAll(Utils.uIntToByteArray(crc.value.toInt()).asList()) - // endregion - - // region idat - frames[0].idat.IDATBody.forEach { - val idat = ArrayList() - // Add the chunk body length - framesByte.addAll(Utils.uIntToByteArray(it.size).asList()) - // Add IDAT - idat.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).asList()) - // Add chunk body - idat.addAll(it.asList()) - // Generate CRC - val crc1 = CRC32() - crc1.update(idat.toByteArray(), 0, idat.size) - framesByte.addAll(idat) - framesByte.addAll(Utils.uIntToByteArray(crc1.value.toInt()).asList()) - } - // endregion - res.addAll(framesByte) - } else { - val framesByte = ArrayList() - // Add cover image : Not part of animation - // region IDAT - val idat = IDAT() - idat.parse(PngEncoder().encode(cover!!, true, 1)) - idat.IDATBody.forEach { - val idatByteArray = ArrayList() - framesByte.addAll(Utils.uIntToByteArray(it.size).asList()) - idatByteArray.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).asList()) - idatByteArray.addAll(it.asList()) - val crc1 = CRC32() - crc1.update(idatByteArray.toByteArray(), 0, idatByteArray.size) - framesByte.addAll(idatByteArray) - framesByte.addAll(Utils.uIntToByteArray(crc1.value.toInt()).asList()) - } - // endregion - - //region fcTL - val fcTL = ArrayList() - // Add the length of the chunk body - framesByte.addAll(byteArrayOf(0x00, 0x00, 0x00, 0x1A).asList()) - // Add fcTL - fcTL.addAll(byteArrayOf(0x66, 0x63, 0x54, 0x4c).asList()) - - // Add the frame number - fcTL.addAll(Utils.uIntToByteArray(seq).asList()) - seq++ - - // Add width and height - fcTL.addAll(Utils.uIntToByteArray(frames[0].width).asList()) - fcTL.addAll(Utils.uIntToByteArray(frames[0].height).asList()) - - - fcTL.addAll(Utils.uIntToByteArray(frames[0].xOffsets).asList()) - fcTL.addAll(Utils.uIntToByteArray(frames[0].yOffsets).asList()) - - // Set frame delay - fcTL.addAll(Utils.uShortToArray(frames[0].delay.toInt()).asList()) - fcTL.addAll(Utils.uShortToArray(1000).asList()) - - // Add DisposeOp and BlendOp - fcTL.add(encodeDisposeOp(frames[0].disposeOp).toByte()) - fcTL.add(encodeBlendOp(frames[0].blendOp).toByte()) - - // Generate CRC - val crc = CRC32() - crc.update(fcTL.toByteArray(), 0, fcTL.size) - framesByte.addAll(fcTL) - framesByte.addAll(Utils.uIntToByteArray(crc.value.toInt()).asList()) - // endregion - - // region fdat - frames[0].idat.IDATBody.forEach { - val fdat = ArrayList() - // Add the chunk body length - framesByte.addAll(Utils.uIntToByteArray(it.size + 4).asList()) - // Add fdat - fdat.addAll(byteArrayOf(0x66, 0x64, 0x41, 0x54).asList()) - fdat.addAll(Utils.uIntToByteArray(seq).asList()) - seq++ - // Add chunk body - fdat.addAll(it.asList()) - // Generate CRC - val crc1 = CRC32() - crc1.update(fdat.toByteArray(), 0, fdat.size) - framesByte.addAll(fdat) - framesByte.addAll(Utils.uIntToByteArray(crc1.value.toInt()).asList()) - } - // endregion - res.addAll(framesByte) - } - - for (i in 1 until frames.size) { - // If it's the first frame - val framesByte = ArrayList() - val fcTL = ArrayList() - // region fcTL - framesByte.addAll(byteArrayOf(0x00, 0x00, 0x00, 0x1A).asList()) - - fcTL.addAll(byteArrayOf(0x66, 0x63, 0x54, 0x4c).asList()) - - // Frame number - fcTL.addAll(Utils.uIntToByteArray(seq).asList()) - seq++ - // width and height - fcTL.addAll(Utils.uIntToByteArray(frames[i].width).asList()) - fcTL.addAll(Utils.uIntToByteArray(frames[i].height).asList()) - - fcTL.addAll(Utils.uIntToByteArray(frames[i].xOffsets).asList()) - fcTL.addAll(Utils.uIntToByteArray(frames[i].yOffsets).asList()) - - // Set frame delay - fcTL.addAll(Utils.uShortToArray(frames[i].delay.toInt()).asList()) - fcTL.addAll(Utils.uShortToArray(1000).asList()) - - fcTL.add(encodeDisposeOp(frames[i].disposeOp).toByte()) - fcTL.add(encodeBlendOp(frames[i].blendOp).toByte()) - - val crc = CRC32() - crc.update(fcTL.toByteArray(), 0, fcTL.size) - framesByte.addAll(fcTL) - framesByte.addAll(Utils.uIntToByteArray(crc.value.toInt()).asList()) - // endregion - - // region fdAT - // Write fdAT - frames[i].idat.IDATBody.forEach { - val fdat = ArrayList() - // Add IDAT size of frame + 4 byte of the seq - framesByte.addAll(Utils.uIntToByteArray(it.size + 4).asList()) - // Add fdAT - fdat.addAll(byteArrayOf(0x66, 0x64, 0x41, 0x54).asList()) - // Add Sequence number - // ! THIS IS NOT FRAME NUMBER - fdat.addAll(Utils.uIntToByteArray(seq).asList()) - // Increase seq - seq++ - fdat.addAll(it.asList()) - // Generate CRC - val crc1 = CRC32() - crc1.update(fdat.toByteArray(), 0, fdat.size) - framesByte.addAll(fdat) - framesByte.addAll(Utils.uIntToByteArray(crc1.value.toInt()).asList()) - } - // endregion - res.addAll(framesByte) - } - if (frames.isNotEmpty()) { - - // Add IEND body length : 0 - res.addAll(Utils.uIntToByteArray(0).asList()) - // Add IEND - val iend = byteArrayOf(0x49, 0x45, 0x4E, 0x44) - // Generate crc for IEND - val crC32 = CRC32() - crC32.update(iend, 0, iend.size) - res.addAll(iend.asList()) - res.addAll(Utils.uIntToByteArray(crC32.value.toInt()).asList()) - return res.toByteArray() - } else { - throw NoFrameException() - } - } - - /** - * Generate a cover image that have the max width and height. - * You could also set yours - * @param bitmap The bitmap of the cover - * @param maxWidth Max width of APNG - * @param maxHeight Max height of the APNG - * @return [Bitmap] An image cover - */ - @Suppress("MemberVisibilityCanBePrivate") - fun generateCover(bitmap: Bitmap, maxWidth : Int, maxHeight : Int) : Bitmap { - return Bitmap.createScaledBitmap(bitmap, maxWidth, maxHeight, false) - } - - /** - * Generate the IHDR chunks. - * @return [ByteArray] The byteArray generated - */ - private fun generateIhdr(): ByteArray { - val ihdr = ArrayList() - - // We need a body var to know body length and generate crc - val ihdrBody = ArrayList() - - // Get max height and max width of all the frames - maxHeight = frames.sortedByDescending { it.height }[0].height - maxWidth = frames.sortedByDescending { it.width }[0].width - - if (((maxWidth != frames[0].width) && (maxHeight != frames[0].height)) && cover == null) { - cover = generateCover(BitmapFactory.decodeByteArray(frames[0].byteArray, 0, frames[0].byteArray.size), maxWidth!!, maxHeight!!) - } - - // Add chunk body length - ihdr.addAll(Utils.uIntToByteArray(frames[0].ihdr.body.size).asList()) - // Add IHDR - ihdrBody.addAll(byteArrayOf(0x49.toByte(), 0x48.toByte(), 0x44.toByte(), 0x52.toByte()).asList()) - - // Add the max width and height - ihdrBody.addAll(Utils.uIntToByteArray(maxWidth!!).asList()) - ihdrBody.addAll(Utils.uIntToByteArray(maxHeight!!).asList()) - - // Add complicated stuff like depth color ... - // If you want correct png you need same parameters. Good solution is to create new png. - ihdrBody.addAll(frames[0].ihdr.body.copyOfRange(8, 13).asList()) - - // Generate CRC - val crC32 = CRC32() - crC32.update(ihdrBody.toByteArray(), 0, ihdrBody.size) - ihdr.addAll(ihdrBody) - ihdr.addAll(Utils.uIntToByteArray(crC32.value.toInt()).asList()) - return ihdr.toByteArray() - } - - /** - * Generate the animation control chunk - * @return [ArrayList] The byteArray generated - */ - private fun generateACTL(): ArrayList { - val res = ArrayList() - val actl = ArrayList() - - // Add length bytes - res.addAll(Utils.uIntToByteArray(8).asList()) - - // Add acTL - actl.addAll(byteArrayOf(0x61, 0x63, 0x54, 0x4c).asList()) - - // Add number of frames - actl.addAll(Utils.uIntToByteArray(frames.size).asList()) - - // Number of repeat, 0 to infinite - actl.addAll(Utils.uIntToByteArray(0).asList()) - res.addAll(actl) - - // generate crc - val crc = CRC32() - crc.update(actl.toByteArray(), 0, actl.size) - res.addAll(Utils.uIntToByteArray(crc.value.toInt()).asList()) - return res - } - - - /** - * Reduce the apng size - * @param maxColor Max color you want in the image - * @param keepCover Keep the cover - * @param sizePercent Reduce image width/height by percents. - */ - fun reduceSize(maxColor : Int, keepCover : Boolean? = null, sizePercent : Int? = null) { - val apng = Apng() - if (keepCover != false) { - if (cover != null) { - if (sizePercent != null) { - cover = Bitmap.createScaledBitmap(cover!!, (cover!!.width.toFloat() * sizePercent.toFloat() / 100f).toInt(), (cover!!.height.toFloat() * sizePercent.toFloat() / 100f).toInt(), false) - val pnn = PnnQuantizer(cover) - cover = pnn.convert(maxColor, false) - } - } - } else { - cover = null - } - frames.forEach { - var btm = BitmapFactory.decodeByteArray(it.byteArray, 0, it.byteArray.size) - if (sizePercent != null) { - btm = Bitmap.createScaledBitmap(btm, (btm!!.width.toFloat() * sizePercent.toFloat() / 100f).toInt(), (btm.height.toFloat() * sizePercent.toFloat() / 100f).toInt(), false) - } - val pnn = PnnQuantizer(btm) - val btmOptimised = pnn.convert(maxColor, false) - if (sizePercent != null) { - apng.addFrames(btmOptimised, 0, it.delay, (it.xOffsets.toFloat() * sizePercent.toFloat() / 100f).toInt(), (it.yOffsets.toFloat() * sizePercent.toFloat() / 100f).toInt(), it.disposeOp, it.blendOp) - } else { - apng.addFrames(btmOptimised, 0, it.delay, it.xOffsets, it.yOffsets, it.disposeOp, it.blendOp) - } - } - frames = apng.frames - } - - /** - * A function to optimise Frame - * WIP ! - */ - fun optimiseFrame() { - maxHeight = frames.sortedByDescending { it.height }[0].height - maxWidth = frames.sortedByDescending { it.width }[0].width - frames.forEach { - it.maxWidth = maxWidth - it.maxHeight = maxHeight - } - val drawedFrame = ApngAnimator(null).draw(frames) - File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), "frame0.png").writeBytes(PngEncoder().encode(drawedFrame[0])) - for (i in 1 until frames.size) { - val diffCalculator = BitmapDiffCalculator(drawedFrame[i - 1], drawedFrame[i]) - File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), "frame$i.png").writeBytes(PngEncoder().encode(diffCalculator.res, true)) - frames[i].byteArray = PngEncoder().encode(diffCalculator.res, true) - frames[i].xOffsets = diffCalculator.xOffset - frames[i].yOffsets = diffCalculator.yOffset - frames[i].blendOp = Utils.Companion.BlendOp.APNG_BLEND_OP_OVER - } - } -} \ No newline at end of file diff --git a/apng_library/src/main/java/oupson/apng/ApngAnimator.kt b/apng_library/src/main/java/oupson/apng/ApngAnimator.kt deleted file mode 100644 index 1a98b61..0000000 --- a/apng_library/src/main/java/oupson/apng/ApngAnimator.kt +++ /dev/null @@ -1,567 +0,0 @@ -package oupson.apng - -import android.content.Context -import android.content.SharedPreferences -import android.graphics.* -import android.net.Uri -import android.util.Log -import android.widget.ImageView -import androidx.annotation.RawRes -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import oupson.apng.exceptions.NotApngException -import oupson.apng.exceptions.NotPngException -import oupson.apng.utils.ApngAnimatorOptions -import oupson.apng.utils.Utils -import oupson.apng.utils.Utils.Companion.isApng -import oupson.apng.utils.Utils.Companion.isPng -import java.io.File -import java.net.URL - -// TODO REMOVE - -/** - * Class to play APNG - * For better performance but lesser features using [oupson.apng.decoder.ApngDecoder] is strongly recommended. - */ -@Deprecated("Deprecated, Use ApngEncoder and ApngDecoder instead", level = DeprecationLevel.WARNING) -class ApngAnimator(private val context: Context?) { - companion object { - /** - * @param file The APNG to load - * @param speed The speed of the APNG - * @param apngAnimatorOptions Options of the animator - * @return [ApngAnimator] The animator - */ - @Suppress("unused") - @JvmOverloads - fun ImageView.loadApng(file: File, speed: Float? = null, apngAnimatorOptions: ApngAnimatorOptions? = null) = ApngAnimator(this.context).loadInto(this).apply { - load(file, speed, apngAnimatorOptions) - } - - /** - * @param uri The APNG to load - * @param speed The speed of the APNG - * @param apngAnimatorOptions Options of the animator - * @return [ApngAnimator] The animator - */ - @Suppress("unused") - @JvmOverloads - fun ImageView.loadApng(uri : Uri, speed: Float? = null, apngAnimatorOptions: ApngAnimatorOptions? = null) = ApngAnimator(this.context).loadInto(this).apply { - load(uri, speed, apngAnimatorOptions) - } - - /** - * @param url The url of the APNG to load - * @param speed The speed of the APNG - * @param apngAnimatorOptions Options of the animator - * @return [ApngAnimator] The animator - */ - @Suppress("unused") - @JvmOverloads - fun ImageView.loadApng(url: URL, speed: Float? = null, apngAnimatorOptions: ApngAnimatorOptions? = null) = ApngAnimator(this.context).loadInto(this).apply { - loadUrl(url, speed, apngAnimatorOptions) - } - - /** - * @param byteArray The APNG to load - * @param speed The speed of the APNG - * @param apngAnimatorOptions Options of the animator - * @return [ApngAnimator] The animator - */ - @Suppress("unused") - @JvmOverloads - fun ImageView.loadApng(byteArray: ByteArray, speed: Float? = null, apngAnimatorOptions: ApngAnimatorOptions? = null) = ApngAnimator(this.context).loadInto(this).apply { - load(byteArray, speed, apngAnimatorOptions) - } - - /** - * @param string The path APNG to load - * @param speed The speed of the APNG - * @param apngAnimatorOptions Options of the animator - * @return [ApngAnimator] The animator - */ - @Suppress("unused") - @JvmOverloads - fun ImageView.loadApng(string: String, speed : Float? = null, apngAnimatorOptions: ApngAnimatorOptions? = null) = ApngAnimator(this.context).loadInto(this).apply { - load(string, speed, apngAnimatorOptions) - } - - /** - * @param res The Resource Int of the APNG to load, must be in the raw folder - * @param speed The speed of the APNG - * @param apngAnimatorOptions Options of the animator - * @return [ApngAnimator] The animator - */ - @Suppress("unused") - @JvmOverloads - fun ImageView.loadApng(@RawRes res : Int, speed : Float? = null, apngAnimatorOptions: ApngAnimatorOptions? = null) = ApngAnimator(this.context).loadInto(this).apply { - load(res, speed, apngAnimatorOptions) - } - } - - @Suppress("MemberVisibilityCanBePrivate") - var isPlaying = true - private set - - var speed: Float? = null - set(value) { - if (isApng) { - field = value - try { - pause() - play() - } catch (e: Exception) { - } - } - } - private var imageView: ImageView? = null - - @Suppress("MemberVisibilityCanBePrivate") - var anim: CustomAnimationDrawable? = null - private var activeAnimation: CustomAnimationDrawable? = null - - private var doOnLoaded : (ApngAnimator) -> Unit = {} - private var frameChangeLister: (index : Int) -> Unit? = {} - - private var duration : ArrayList? = null - - private var scaleType : ImageView.ScaleType? = null - - @Suppress("MemberVisibilityCanBePrivate") - var isApng = false - - @Suppress("MemberVisibilityCanBePrivate") - var loadNotApng = true - - private val sharedPreferences : SharedPreferences? by lazy { - context?.getSharedPreferences("apngAnimator", Context.MODE_PRIVATE) - } - - init { - loadNotApng = sharedPreferences?.getBoolean("loadNotApng", true) ?: true - } - - /** - * Specify if the library could load non apng file - * @param boolean If true the file will be loaded even if it is not an APNG - */ - @Suppress("unused") - @SuppressWarnings("WeakerAccess") - fun loadNotApng(boolean: Boolean) { - val editor = sharedPreferences?.edit() - editor?.putBoolean("loadNotApng", boolean) - editor?.apply() - } - - /** - * Load into an ImageView - * @param imageView Image view selected. - * @return [ApngAnimator] The Animator - */ - fun loadInto(imageView: ImageView): ApngAnimator { - this.imageView = imageView - return this - } - - /** - * Load an APNG file and starts playing the animation. - * @param file The file to load - * @param speed The speed - * @return [ApngAnimator] The Animator - * @throws NotApngException - */ - @JvmOverloads - fun load(file: File, speed: Float? = null, apngAnimatorOptions: ApngAnimatorOptions? = null) : ApngAnimator { - GlobalScope.launch(Dispatchers.IO) { - val input = file.inputStream() - val bytes = ByteArray(8) - input.read(bytes) - input.close() - if (isPng(bytes)) { - isApng = true - this@ApngAnimator.speed = speed - scaleType = apngAnimatorOptions?.scaleType - // Download PNG - - val inputStream = file.inputStream() - APNGDisassembler().disassemble(inputStream).also { - inputStream.close() - if (it.isApng) { - it.frames.also {frames -> - draw(frames).apply { - setupAnimationDrawableAndStart(this) - } - } - } else { - GlobalScope.launch { - imageView?.setImageBitmap(it.cover) - } - } - } - } else { - if (loadNotApng) { - GlobalScope.launch(Dispatchers.Main) { - imageView?.scaleType = this@ApngAnimator.scaleType ?: ImageView.ScaleType.FIT_CENTER - imageView?.setImageBitmap(BitmapFactory.decodeByteArray(bytes, 0, bytes.size)) - } - } else { - throw NotPngException() - } - } - } - return this - } - - /** - * Load an APNG file and starts playing the animation. - * @param uri The uri to load - * @param speed The speed - * @return [ApngAnimator] The Animator - * @throws NotApngException - */ - @JvmOverloads - fun load(uri : Uri, speed: Float? = null, apngAnimatorOptions: ApngAnimatorOptions? = null) : ApngAnimator { - GlobalScope.launch(Dispatchers.IO) { - val input = context!!.contentResolver.openInputStream(uri)!! - val bytes = ByteArray(8) - input.read(bytes) - input.close() - if (isPng(bytes)) { - this@ApngAnimator.speed = speed - scaleType = apngAnimatorOptions?.scaleType - // Download PNG - - val inputStream = context.contentResolver.openInputStream(uri)!! - APNGDisassembler().disassemble(inputStream).also { - inputStream.close() - if (it.isApng) { - isApng = true - it.frames.also {frames -> - draw(frames).apply { - setupAnimationDrawableAndStart(this) - } - } - } else { - isApng = false - GlobalScope.launch(Dispatchers.Main) { - imageView?.setImageBitmap(it.cover) - } - } - } - } else { - if (loadNotApng) { - GlobalScope.launch(Dispatchers.Main) { - imageView?.scaleType = this@ApngAnimator.scaleType ?: ImageView.ScaleType.FIT_CENTER - imageView?.setImageBitmap(BitmapFactory.decodeByteArray(bytes, 0, bytes.size)) - } - } else { - throw NotPngException() - } - } - } - return this - } - - /** - * Load an APNG file and starts playing the animation. - * @param url URL to load. - * @param speed The speed - * @return [ApngAnimator] The Animator - * @throws NotApngException - */ - @JvmOverloads - fun loadUrl(url: URL, speed: Float? = null, apngAnimatorOptions: ApngAnimatorOptions? = null) : ApngAnimator { - GlobalScope.launch(Dispatchers.IO) { - this@ApngAnimator.speed = speed - // Download PNG - try { - Loader.load(url).apply { - try { - this@ApngAnimator.load(this, speed, apngAnimatorOptions) - } catch (e: NotPngException) { - if (loadNotApng) { - GlobalScope.launch(Dispatchers.Main) { - imageView?.scaleType = - this@ApngAnimator.scaleType ?: ImageView.ScaleType.FIT_CENTER - imageView?.setImageBitmap( - BitmapFactory.decodeByteArray( - this@apply, - 0, - this@apply.size - ) - ) - } - } else { - throw NotApngException() - } - } - } - } catch (e : java.lang.Exception) { - if (BuildConfig.DEBUG) - Log.e("ApngAnimator", "Error : $e") - } - } - return this - } - - /** - * Load an APNG file and starts playing the animation. - * @param byteArray ByteArray of the file - * @param speed The speed - * @return [ApngAnimator] The Animator - * @throws NotApngException - */ - @JvmOverloads - fun load(byteArray: ByteArray, speed: Float? = null, apngAnimatorOptions: ApngAnimatorOptions? = null) : ApngAnimator { - GlobalScope.launch { - this@ApngAnimator.speed = speed - if (isApng(byteArray)) { - isApng = true - this@ApngAnimator.speed = speed - scaleType = apngAnimatorOptions?.scaleType - // Download PNG - APNGDisassembler().disassemble(byteArray).frames.also { frames -> - draw(frames).apply { - setupAnimationDrawableAndStart(this) - } - } - } else { - if (loadNotApng) { - GlobalScope.launch(Dispatchers.Main) { - imageView?.scaleType = this@ApngAnimator.scaleType ?: ImageView.ScaleType.FIT_CENTER - imageView?.setImageBitmap(BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)) - } - } else { - throw NotApngException() - } - } - } - return this - } - - /** - * Load an APNG file - * @param string Path of the file. - * @param speed The speed - * @return [ApngAnimator] The Animator - * @throws NotApngException - */ - @JvmOverloads - fun load(string: String, speed : Float? = null, apngAnimatorOptions: ApngAnimatorOptions? = null) : ApngAnimator { - GlobalScope.launch(Dispatchers.IO) { - this@ApngAnimator.speed = speed - if (string.contains("http") || string.contains("https")) { - val url = URL(string) - loadUrl(url, speed, apngAnimatorOptions) - } else if (File(string).exists()) { - var pathToLoad = if (string.startsWith("content://")) string else "file://$string" - pathToLoad = pathToLoad.replace("%", "%25").replace("#", "%23") - this@ApngAnimator.load(Uri.parse(pathToLoad), speed, apngAnimatorOptions) - } else if (string.startsWith("file:///android_asset/")) { - val bytes = this@ApngAnimator.context?.assets?.open(string.replace("file:///android_asset/", ""))?.readBytes() - bytes ?: throw Exception("File are empty") - if (isApng(bytes)) { - load(bytes, speed, apngAnimatorOptions) - } else { - if (loadNotApng) { - GlobalScope.launch(Dispatchers.Main) { - imageView?.setImageBitmap(BitmapFactory.decodeByteArray(bytes, 0, bytes.size)) - } - } else { - throw NotApngException() - } - } - } - } - return this - } - /** - * Load an APNG file - * @param res The res of the file - * @param speed The speed - * @return [ApngAnimator] The Animator - * @throws NotApngException - */ - @JvmOverloads - fun load(@RawRes res : Int, speed : Float? = null, apngAnimatorOptions: ApngAnimatorOptions? = null) : ApngAnimator { - GlobalScope.launch { - val byteArray = context?.resources?.openRawResource(res)?.readBytes() ?: byteArrayOf() - this@ApngAnimator.speed = speed - if (isApng(byteArray)) { - isApng = true - this@ApngAnimator.speed = speed - scaleType = apngAnimatorOptions?.scaleType - // Download PNG - APNGDisassembler().disassemble(byteArray).frames.also { frames -> - draw(frames).apply { - setupAnimationDrawableAndStart(this) - } - } - } else { - if (loadNotApng) { - GlobalScope.launch(Dispatchers.Main) { - imageView?.scaleType = this@ApngAnimator.scaleType ?: ImageView.ScaleType.FIT_CENTER - imageView?.setImageBitmap(BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)) - } - } else { - throw NotApngException() - } - } - } - return this - } - - /** - * Sets up the animation drawable and any required listeners. The animation will automatically start. - * @param generatedFrame The frames generated by draw function - */ - private fun setupAnimationDrawableAndStart(generatedFrame: ArrayList) { - GlobalScope.launch { - anim = toAnimationDrawable(generatedFrame) - activeAnimation = anim - GlobalScope.launch(Dispatchers.Main) { - imageView?.apply { - scaleType = this@ApngAnimator.scaleType ?: ImageView.ScaleType.FIT_CENTER - clearAnimation() - setImageDrawable(activeAnimation) - } - activeAnimation?.start() - isPlaying = true - doOnLoaded(this@ApngAnimator) - } - } - } - - /** - * Draw frames - * @param extractedFrame The frames extracted by the disassembler - * @return [ArrayList] The drawed frames - */ - fun draw(extractedFrame: ArrayList) : ArrayList { - val generatedFrame = ArrayList() - // Set last frame - duration = ArrayList() - var bitmapBuffer = Bitmap.createBitmap(extractedFrame[0].maxWidth!!, extractedFrame[0].maxHeight!!, Bitmap.Config.ARGB_8888) - for (i in 0 until extractedFrame.size) { - // Iterator - val it = extractedFrame[i] - // Current bitmap for the frame - val btm = Bitmap.createBitmap(extractedFrame[0].maxWidth!!, extractedFrame[0].maxHeight!!, Bitmap.Config.ARGB_8888) - val canvas = Canvas(btm) - val current = BitmapFactory.decodeByteArray(it.byteArray, 0, it.byteArray.size).copy(Bitmap.Config.ARGB_8888, true) - // Write buffer to canvas - canvas.drawBitmap(bitmapBuffer, 0f, 0f, null) - // Clear current frame rect - // If `BlendOp` is APNG_BLEND_OP_SOURCE all color components of the frame, including alpha, overwrite the current contents of the frame's output buffer region. - if (it.blendOp == Utils.Companion.BlendOp.APNG_BLEND_OP_SOURCE) { - canvas.drawRect(it.xOffsets.toFloat(), it.yOffsets.toFloat(), it.xOffsets + current.width.toFloat(), it.yOffsets + current.height.toFloat(), { val paint = Paint(); paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR); paint }()) - } - // Draw the bitmap - canvas.drawBitmap(current, it.xOffsets.toFloat(), it.yOffsets.toFloat(), null) - generatedFrame.add(btm) - // Don't add current frame to bitmap buffer - when { - extractedFrame[i].disposeOp == Utils.Companion.DisposeOp.APNG_DISPOSE_OP_PREVIOUS -> { - //Do nothings - } - // Add current frame to bitmap buffer - // APNG_DISPOSE_OP_BACKGROUND: the frame's region of the output buffer is to be cleared to fully transparent black before rendering the next frame. - it.disposeOp == Utils.Companion.DisposeOp.APNG_DISPOSE_OP_BACKGROUND -> { - val res = Bitmap.createBitmap(extractedFrame[0].maxWidth!!, extractedFrame[0].maxHeight!!, Bitmap.Config.ARGB_8888) - val can = Canvas(res) - can.drawBitmap(btm, 0f, 0f, null) - can.drawRect(it.xOffsets.toFloat(), it.yOffsets.toFloat(), it.xOffsets + it.width.toFloat(), it.yOffsets + it.height.toFloat(), { val paint = Paint(); paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR); paint }()) - bitmapBuffer = res - } - else -> bitmapBuffer = btm - } - duration?.add(it.delay / (speed ?: 1f)) - } - return generatedFrame - } - - /** - * Pause the animation - */ - fun pause() { - if (isApng) { - isPlaying = false - val animResume = CustomAnimationDrawable() - activeAnimation?.stop() - val currentFrame = activeAnimation!!.current - val durations = ArrayList() - frameLoop@ for (i in 0 until anim?.numberOfFrames!!) { - val checkFrame = activeAnimation!!.getFrame(i) - if (checkFrame === currentFrame) { - for (k in i until activeAnimation!!.numberOfFrames) { - val frame = activeAnimation!!.getFrame(k) - animResume.addFrame(frame, (duration!![k] / (speed ?: 1f)).toInt()) - durations.add(duration!![k]) - } - for (k in 0 until i) { - val frame = activeAnimation!!.getFrame(k) - animResume.addFrame(frame, (duration!![k] / (speed ?: 1f)).toInt()) - durations.add(duration!![k]) - } - activeAnimation = animResume - imageView?.setImageDrawable(activeAnimation) - activeAnimation?.setOnFrameChangeListener(frameChangeLister) - imageView?.invalidate() - duration = durations - break@frameLoop - } - } - } - } - - /** - * Play the animation - */ - fun play() { - if (isApng) { - isPlaying = true - activeAnimation?.start() - } - } - - /** - * Set animation loop listener - * @param frameChangeListener The listener. - */ - @Suppress("unused") - fun setOnFrameChangeLister(frameChangeListener : (index : Int) -> Unit?) { - if (isApng) { - this.frameChangeLister = frameChangeListener - anim?.setOnFrameChangeListener(frameChangeListener) - } - } - - /** - * Execute on loaded - */ - @Suppress("unused") - fun onLoaded(f : (ApngAnimator) -> Unit) { - doOnLoaded = f - } - - /** - * Converts the generated frames into an animation drawable ([CustomAnimationDrawable]) - * in the APNG will be used instead. - * @param generatedFrame The frames - * @return [CustomAnimationDrawable] The animation drawable - */ - private fun toAnimationDrawable( generatedFrame : ArrayList ): CustomAnimationDrawable { - if (isApng) { - return CustomAnimationDrawable().apply { - isOneShot = false - for (i in 0 until generatedFrame.size) { - addFrame(BitmapDrawable(generatedFrame[i]), ((duration!![i]) / (speed ?: 1f)).toInt()) - } - } - } else { - throw NotApngException() - } - } -} \ No newline at end of file diff --git a/apng_library/src/main/java/oupson/apng/BitmapDrawable.kt b/apng_library/src/main/java/oupson/apng/BitmapDrawable.kt deleted file mode 100644 index 51891bd..0000000 --- a/apng_library/src/main/java/oupson/apng/BitmapDrawable.kt +++ /dev/null @@ -1,39 +0,0 @@ -package oupson.apng - -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.ColorFilter -import android.graphics.PixelFormat -import android.graphics.drawable.Drawable - -@Deprecated("Deprecated, Use ApngEncoder and ApngDecoder instead", level = DeprecationLevel.WARNING) -internal class BitmapDrawable(private val bitmap: Bitmap) : Drawable() { - - override fun draw(canvas: Canvas) { - canvas.drawBitmap(bitmap, 0.0f, 0.0f, null) - } - - override fun getOpacity(): Int { - return PixelFormat.TRANSLUCENT - } - - override fun setAlpha(alpha: Int) {} - - override fun setColorFilter(cf: ColorFilter?) {} - - override fun getIntrinsicWidth(): Int { - return bitmap.width - } - - override fun getIntrinsicHeight(): Int { - return bitmap.height - } - - override fun getMinimumWidth(): Int { - return bitmap.width - } - - override fun getMinimumHeight(): Int { - return bitmap.height - } -} \ No newline at end of file diff --git a/apng_library/src/main/java/oupson/apng/CustomAnimationDrawable.kt b/apng_library/src/main/java/oupson/apng/CustomAnimationDrawable.kt deleted file mode 100644 index 349673c..0000000 --- a/apng_library/src/main/java/oupson/apng/CustomAnimationDrawable.kt +++ /dev/null @@ -1,22 +0,0 @@ -package oupson.apng - -import android.graphics.drawable.AnimationDrawable - -/** - * Extension of the [AnimationDrawable] that provides an animationListener This will allow - * for the caller to listen for specific animation related events. - */ -@Deprecated("Deprecated, Use ApngEncoder and ApngDecoder instead", level = DeprecationLevel.WARNING) -class CustomAnimationDrawable : AnimationDrawable() { - private var onFrameChangeListener : (index : Int) -> Unit? = {} - - fun setOnFrameChangeListener( f : (index : Int) -> Unit?) { - onFrameChangeListener = f - } - - override fun selectDrawable(index: Int): Boolean { - val drawableChanged = super.selectDrawable(index) - onFrameChangeListener(index) - return drawableChanged - } -} \ No newline at end of file diff --git a/apng_library/src/main/java/oupson/apng/Frame.kt b/apng_library/src/main/java/oupson/apng/Frame.kt deleted file mode 100644 index d85bfee..0000000 --- a/apng_library/src/main/java/oupson/apng/Frame.kt +++ /dev/null @@ -1,97 +0,0 @@ -package oupson.apng - -import oupson.apng.chunks.IDAT -import oupson.apng.chunks.IHDR -import oupson.apng.exceptions.NotPngException -import oupson.apng.utils.Utils -import oupson.apng.utils.Utils.Companion.IDAT -import oupson.apng.utils.Utils.Companion.IHDR -import oupson.apng.utils.Utils.Companion.isPng - -/** - * A frame of the APNG - * @param byteArray The bitmap to add - * @param delay Delay of the frame - * @param xOffsets The X offset where the frame should be rendered - * @param yOffsets The Y offset where the frame should be rendered - * @param disposeOp `DisposeOp` specifies how the output buffer should be changed at the end of the delay (before rendering the next frame). - * @param blendOp `BlendOp` specifies whether the frame is to be alpha blended into the current output buffer content, or whether it should completely replace its region in the output buffer. - * @param maxWidth The max width of the APNG - * @param maxHeight The max height of the APNG - */ -@Deprecated("Deprecated, Use ApngEncoder and ApngDecoder instead", level = DeprecationLevel.WARNING) -class Frame // Get width and height for image - ( - byteArray: ByteArray, - delay: Float = 1000f, - xOffsets: Int = 0, - yOffsets: Int = 0, - blendOp: Utils.Companion.BlendOp = Utils.Companion.BlendOp.APNG_BLEND_OP_SOURCE, - disposeOp: Utils.Companion.DisposeOp = Utils.Companion.DisposeOp.APNG_DISPOSE_OP_NONE, - maxWidth: Int? = null, - maxHeight: Int? = null -) { - - var byteArray : ByteArray - - var width : Int = -1 - var height : Int = -1 - - lateinit var ihdr : IHDR - - lateinit var idat : IDAT - - val delay : Float - - var xOffsets : Int = 0 - var yOffsets : Int = 0 - - var maxWidth : Int? = null - var maxHeight : Int? = null - - var blendOp: Utils.Companion.BlendOp - var disposeOp : Utils.Companion.DisposeOp - - init { - if (isPng(byteArray)) { - this.byteArray = byteArray - // Get width and height for image - var cursor = 8 - while (cursor < byteArray.size) { - val chunk = byteArray.copyOfRange(cursor, cursor + Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(cursor, cursor + 4).map(Byte::toInt)) + 12) - parseChunk(chunk) - cursor += Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(cursor, cursor + 4).map(Byte::toInt)) + 12 - } - - this.delay = delay - - this.xOffsets = xOffsets - this.yOffsets = yOffsets - - this.maxWidth = maxWidth - this.maxHeight = maxHeight - this.blendOp = blendOp - this.disposeOp = disposeOp - } else { - throw NotPngException() - } - } - - /** - * Parse the Frame - * @param byteArray The frame - */ - private fun parseChunk(byteArray: ByteArray) { - val name = byteArray.copyOfRange(4, 8) - if (name.contentEquals(IHDR)) { - ihdr = IHDR() - ihdr.parse(byteArray) - width = ihdr.pngWidth - height = ihdr.pngHeight - } else if (name.contentEquals(IDAT)){ - // Get IDAT Bytes - idat = IDAT() - idat.parse(byteArray) - } - } -} \ No newline at end of file diff --git a/apng_library/src/main/java/oupson/apng/chunks/Chunk.kt b/apng_library/src/main/java/oupson/apng/chunks/Chunk.kt deleted file mode 100644 index e5dacc0..0000000 --- a/apng_library/src/main/java/oupson/apng/chunks/Chunk.kt +++ /dev/null @@ -1,15 +0,0 @@ -package oupson.apng.chunks - -/** - * An interface for the png chunks - */ -@Deprecated("Deprecated", level = DeprecationLevel.WARNING) -interface Chunk { - var body : ByteArray - - /** - * Parse the chunk - * @param byteArray The chunk with the length and the crc - */ - fun parse(byteArray: ByteArray) -} \ No newline at end of file diff --git a/apng_library/src/main/java/oupson/apng/chunks/IDAT.kt b/apng_library/src/main/java/oupson/apng/chunks/IDAT.kt deleted file mode 100644 index b51f283..0000000 --- a/apng_library/src/main/java/oupson/apng/chunks/IDAT.kt +++ /dev/null @@ -1,24 +0,0 @@ -package oupson.apng.chunks - -import oupson.apng.utils.Utils -@Deprecated("Deprecated", level = DeprecationLevel.WARNING) -class IDAT : Chunk { - @Suppress("PropertyName") - var IDATBody: ArrayList = ArrayList() - override var body = byteArrayOf() - - /** - * Parse the chunk - * @param byteArray The chunk with the length and the crc - */ - override fun parse(byteArray: ByteArray) { - val i = 4 - // Find IDAT chunk - if (byteArray[i] == 0x49.toByte() && byteArray[i + 1] == 0x44.toByte() && byteArray[i + 2] == 0x41.toByte() && byteArray[i + 3] == 0x54.toByte()) { - // Find the chunk length - val bodySize = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(i - 4, i).map(Byte::toInt)) - // Get image bytes - IDATBody.add(byteArray.copyOfRange(i + 4, i + 4 + bodySize)) - } - } -} \ No newline at end of file diff --git a/apng_library/src/main/java/oupson/apng/chunks/IHDR.kt b/apng_library/src/main/java/oupson/apng/chunks/IHDR.kt deleted file mode 100644 index a8261f4..0000000 --- a/apng_library/src/main/java/oupson/apng/chunks/IHDR.kt +++ /dev/null @@ -1,30 +0,0 @@ -package oupson.apng.chunks - -import oupson.apng.utils.Utils - -// TODO REMOVE -@Deprecated("Deprecated", level = DeprecationLevel.WARNING) -class IHDR : Chunk { - override var body = byteArrayOf() - var pngWidth = -1 - var pngHeight = -1 - - /** - * Parse the chunk - * @param byteArray The chunk with the length and the crc - */ - override fun parse(byteArray: ByteArray) { - for (i in byteArray.indices) { - // Find IHDR chunk - if (byteArray[i] == 0x49.toByte() && byteArray[i + 1] == 0x48.toByte() && byteArray[ i + 2 ] == 0x44.toByte() && byteArray[ i + 3 ] == 0x52.toByte()) { - // Get length of the body of the chunk - val bodySize = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(i - 4, i).map(Byte::toInt)) - // Get the width of the png - pngWidth = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(i +4, i + 8).map(Byte::toInt)) - // Get the height of the png - pngHeight = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(i +8, i +12).map(Byte::toInt)) - body = byteArray.copyOfRange(i + 4, i + bodySize + 4) - } - } - } -} \ No newline at end of file diff --git a/apng_library/src/main/java/oupson/apng/chunks/fcTL.kt b/apng_library/src/main/java/oupson/apng/chunks/fcTL.kt deleted file mode 100644 index 9aabce6..0000000 --- a/apng_library/src/main/java/oupson/apng/chunks/fcTL.kt +++ /dev/null @@ -1,62 +0,0 @@ -package oupson.apng.chunks - -import oupson.apng.utils.Utils -import oupson.apng.utils.Utils.Companion.decodeBlendOp -import oupson.apng.utils.Utils.Companion.decodeDisposeOp - -@Suppress("ClassName") -@Deprecated("Deprecated, Use ApngEncoder and ApngDecoder instead", level = DeprecationLevel.WARNING) -class fcTL : Chunk { - override var body : ByteArray = byteArrayOf() - - // Height and width of frame - var pngWidth = -1 - var pngHeight = -1 - - // Delay to wait after the frame - var delay : Float = -1f - - // x and y offsets - var xOffset : Int = 0 - var yOffset : Int = 0 - - var blendOp : Utils.Companion.BlendOp = Utils.Companion.BlendOp.APNG_BLEND_OP_SOURCE - var disposeOp : Utils.Companion.DisposeOp = Utils.Companion.DisposeOp.APNG_DISPOSE_OP_NONE - - /** - * Parse the chunk - * @param byteArray The chunk with the length and the crc - */ - override fun parse(byteArray: ByteArray) { - val i = 4 - // Find fcTL chunk - if (byteArray[i] == 0x66.toByte() && byteArray[i + 1] == 0x63.toByte() && byteArray[i + 2] == 0x54.toByte() && byteArray[i + 3] == 0x4C.toByte()) { - // Get length of the body of the chunk - val bodySize = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(i - 4, i+1).map{it .toInt()}) - // Get the width of the png - pngWidth = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(i + 8, i + 12).map(Byte::toInt)) - // Get the height of the png - pngHeight = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(i + 12, i + 16).map(Byte::toInt)) - /* - * The `delay_num` and `delay_den` parameters together specify a fraction indicating the time to display the current frame, in seconds. - * If the the value of the numerator is 0 the decoder should render the next frame as quickly as possible, though viewers may impose a reasonable lower bound. - */ - // Get delay numerator - val delayNum = Utils.uShortFromBytesBigEndian(byteArray.copyOfRange(i + 24, i + 26).map(Byte::toInt)).toFloat() - // Get delay denominator - var delayDen = Utils.uShortFromBytesBigEndian(byteArray.copyOfRange(i + 26, i + 28).map(Byte::toInt)).toFloat() - - // If the denominator is 0, it is to be treated as if it were 100 (that is, `delay_num` then specifies 1/100ths of a second). - if (delayDen == 0f) { - delayDen = 100f - } - delay = (delayNum / delayDen * 1000) - // Get x and y offsets - xOffset = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(i + 16, i + 20).map(Byte::toInt)) - yOffset = Utils.uIntFromBytesBigEndian(byteArray.copyOfRange(i + 20, i + 24).map(Byte::toInt)) - body = byteArray.copyOfRange(i + 4, i + bodySize + 4) - blendOp = decodeBlendOp(byteArray[33].toInt()) - disposeOp = decodeDisposeOp(byteArray[32].toInt()) - } - } -} \ No newline at end of file diff --git a/apng_library/src/main/java/oupson/apng/decoder/ApngDecoder.kt b/apng_library/src/main/java/oupson/apng/decoder/ApngDecoder.kt index bd7b8fd..8ec881e 100644 --- a/apng_library/src/main/java/oupson/apng/decoder/ApngDecoder.kt +++ b/apng_library/src/main/java/oupson/apng/decoder/ApngDecoder.kt @@ -16,10 +16,10 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import oupson.apng.BuildConfig -import oupson.apng.Loader import oupson.apng.decoder.ApngDecoder.Companion.decodeApng import oupson.apng.exceptions.BadApngException import oupson.apng.exceptions.BadCRCException +import oupson.apng.utils.Loader import oupson.apng.utils.Utils import oupson.apng.utils.Utils.Companion.isPng import java.io.* diff --git a/apng_library/src/main/java/oupson/apng/imageUtils/BitmapDiffCalculator.kt b/apng_library/src/main/java/oupson/apng/imageUtils/BitmapDiffCalculator.kt deleted file mode 100644 index 5ed643b..0000000 --- a/apng_library/src/main/java/oupson/apng/imageUtils/BitmapDiffCalculator.kt +++ /dev/null @@ -1,69 +0,0 @@ -package oupson.apng.imageUtils - -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Paint -import oupson.apng.utils.Utils - -@Deprecated("") -class BitmapDiffCalculator(firstBitmap: Bitmap, secondBitmap : Bitmap) { - val res : Bitmap - var xOffset : Int = 0 - var yOffset : Int = 0 - @Suppress("unused") - var blendOp = Utils.Companion.BlendOp.APNG_BLEND_OP_OVER - init { - val difBitmap = Bitmap.createBitmap(firstBitmap.width, firstBitmap.height, Bitmap.Config.ARGB_8888) - val difCanvas = Canvas(difBitmap) - for (y in 0 until firstBitmap.height) { - for (x in 0 until firstBitmap.width) { - if (firstBitmap.getPixel(x, y) != secondBitmap.getPixel(x, y)) { - val colour = secondBitmap.getPixel(x, y) - val paint = Paint().apply { - this.color = colour - } - difCanvas.drawPoint(x.toFloat(), y.toFloat(), paint) - } - } - } - var width = difBitmap.width - var height = difBitmap.height - topLoop@for (y in 0 until difBitmap.height){ - for (x in 0 until difBitmap.width) { - if (difBitmap.getPixel(x, y) != Color.TRANSPARENT) { - break@topLoop - } - } - yOffset += 1 - } - bottomLoop@ while (true) { - for (x in 0 until difBitmap.width) { - if (height < 0) { - break@bottomLoop - } else if (difBitmap.getPixel(x, height - 1) != Color.TRANSPARENT) { - break@bottomLoop - } - } - height -= 1 - } - leftLoop@for (x in 0 until difBitmap.width) { - for (y in 0 until difBitmap.height) { - if (difBitmap.getPixel(x, y) != Color.TRANSPARENT) { - break@leftLoop - } - } - xOffset += 1 - } - rightLoop@ while (true) { - for (y in 0 until difBitmap.height) { - if (difBitmap.getPixel(width - 1, y) != Color.TRANSPARENT) { - break@rightLoop - } - } - width -= 1 - } - val btm = Bitmap.createBitmap(difBitmap, xOffset, yOffset, width - xOffset, height - yOffset) - res = btm - } -} \ No newline at end of file diff --git a/apng_library/src/main/java/oupson/apng/imageUtils/PngEncoder.kt b/apng_library/src/main/java/oupson/apng/imageUtils/PngEncoder.kt deleted file mode 100644 index 4f256dc..0000000 --- a/apng_library/src/main/java/oupson/apng/imageUtils/PngEncoder.kt +++ /dev/null @@ -1,419 +0,0 @@ -package oupson.apng.imageUtils - -import android.graphics.Bitmap -import java.io.ByteArrayOutputStream -import java.io.IOException -import java.util.zip.CRC32 -import java.util.zip.Deflater -import java.util.zip.DeflaterOutputStream -import kotlin.math.max -import kotlin.math.min - -/** - * Taken from http://catcode.com/pngencoder/com/keypoint/PngEncoder.java - */ -@Deprecated("It now integrated in ApngEncoder and will be removed after the 1.10 release") -class PngEncoder { - companion object { - /** Constants for filter (NONE) */ - private const val FILTER_NONE = 0 - - /** Constants for filter (SUB) */ - private const val FILTER_SUB = 1 - - /** Constants for filter (UP) */ - private const val FILTER_UP = 2 - - /** Constants for filter (LAST) */ - private const val FILTER_LAST = 2 - } - /** Encode alpha ? */ - private var encodeAlpha = true - - /** IHDR tag. */ - private val ihdr = byteArrayOf(73, 72, 68, 82) - - /** IDAT tag. */ - private val idat = byteArrayOf(73, 68, 65, 84) - - /** IEND tag. */ - private val iend = byteArrayOf(73, 69, 78, 68) - - /** The image. */ - private var image: Bitmap? = null - - /** The png bytes. */ - private var pngBytes: ByteArray? = null - - /** The prior row. */ - private var priorRow: ByteArray? = null - - /** The left bytes. */ - private var leftBytes: ByteArray? = null - - /** The width. */ - private var width: Int = 0 - private var height: Int = 0 - - /** The byte position. */ - private var bytePos: Int = 0 - private var maxPos: Int = 0 - - /** CRC. */ - private var crc = CRC32() - - /** The CRC value. */ - private var crcValue: Long = 0 - - /** The filter type. */ - private var filter: Int = 0 - - /** The bytes-per-pixel. */ - private var bytesPerPixel: Int = 0 - - /** The compression level. */ - private var compressionLevel: Int = 0 - - /** - * Encode a [Bitmap] into a png - * - * @param image Bitmap to encode - * @param encodeAlpha Specify if the alpha should be encoded or not - * @param filter 0=none, 1=sub, 2=up - * @param compressionLevel ! Don't use it : It's buggy - */ - fun encode(image: Bitmap, encodeAlpha: Boolean = false, filter: Int = 0, compressionLevel: Int = 0): ByteArray { - this.filter = FILTER_NONE - if (filter <= FILTER_LAST) { - this.filter = filter - } - - if (compressionLevel in 0..9) { - this.compressionLevel = compressionLevel - } - - this.encodeAlpha = encodeAlpha - - - val pngIdBytes = byteArrayOf(-119, 80, 78, 71, 13, 10, 26, 10) - width = image.width - height = image.height - this.image = image - /* - * start with an array that is big enough to hold all the pixels - * (plus filter bytes), and an extra 200 bytes for header info - */ - pngBytes = ByteArray((width + 1) * height * 3 + 200) - /* - * keep track of largest byte written to the array - */ - maxPos = 0 - - bytePos = writeBytes(pngIdBytes, 0) - //hdrPos = bytePos; - writeHeader() - //dataPos = bytePos; - if (writeImageData()) { - writeEnd() - pngBytes = resizeByteArray(pngBytes!!, maxPos) - } else { - throw Exception() - } - return pngBytes!! - } - - /** - * Increase or decrease the length of a byte array. - * - * @param array ByteArray to resize - * @param newLength The length you wish the new array to have. - * @return Array of newly desired length. If shorter than the - * original, the trailing elements are truncated. - */ - private fun resizeByteArray(array: ByteArray, newLength: Int): ByteArray { - val newArray = ByteArray(newLength) - val oldLength = array.size - System.arraycopy(array, 0, newArray, 0, min(oldLength, newLength)) - return newArray - } - - @Suppress("unused") - fun release() { - image?.recycle() - image = null - pngBytes = null - } - - /** - * Write an array of bytes into the pngBytes array. - * Note: This routine has the side effect of updating - * maxPos, the largest element written in the array. - * The array is resized by 1000 bytes or the length - * of the data to be written, whichever is larger. - * - * @param data The data to be written into pngBytes. - * @param offset The starting point to write to. - * @return The next place to be written to in the pngBytes array. - */ - private fun writeBytes(data: ByteArray, offset: Int): Int { - maxPos = max(maxPos, offset + data.size) - if (data.size + offset > pngBytes!!.size) { - pngBytes = resizeByteArray(pngBytes!!, pngBytes!!.size + max(1000, data.size)) - } - System.arraycopy(data, 0, pngBytes!!, offset, data.size) - return offset + data.size - } - - /** - * Write an array of bytes into the pngBytes array, specifying number of bytes to write. - * Note: This routine has the side effect of updating - * maxPos, the largest element written in the array. - * The array is resized by 1000 bytes or the length - * of the data to be written, whichever is larger. - * - * @param data The data to be written into pngBytes. - * @param nBytes The number of bytes to be written. - * @param offset The starting point to write to. - * @return The next place to be written to in the pngBytes array. - */ - private fun writeBytes(data: ByteArray, nBytes: Int, offset: Int): Int { - maxPos = max(maxPos, offset + nBytes) - if (nBytes + offset > pngBytes!!.size) { - pngBytes = resizeByteArray(pngBytes!!, pngBytes!!.size + max(1000, nBytes)) - } - System.arraycopy(data, 0, pngBytes!!, offset, nBytes) - return offset + nBytes - } - - /** - * Write a two-byte integer into the pngBytes array at a given position. - * - * @param n The integer to be written into pngBytes. - * @param offset The starting point to write to. - * @return The next place to be written to in the pngBytes array. - */ - @Suppress("unused") - private fun writeInt2(n: Int, offset: Int): Int { - val temp = byteArrayOf((n shr 8 and 0xff).toByte(), (n and 0xff).toByte()) - return writeBytes(temp, offset) - } - - /** - * Write a four-byte integer into the pngBytes array at a given position. - * - * @param n The integer to be written into pngBytes. - * @param offset The starting point to write to. - * @return The next place to be written to in the pngBytes array. - */ - private fun writeInt4(n: Int, offset: Int): Int { - val temp = byteArrayOf( - (n shr 24 and 0xff).toByte(), - (n shr 16 and 0xff).toByte(), - (n shr 8 and 0xff).toByte(), - (n and 0xff).toByte() - ) - return writeBytes(temp, offset) - } - - /** - * Write a single byte into the pngBytes array at a given position. - * - * @param b The integer to be written into pngBytes. - * @param offset The starting point to write to. - * @return The next place to be written to in the pngBytes array. - */ - private fun writeByte(b: Int, offset: Int): Int { - val temp = byteArrayOf(b.toByte()) - - return writeBytes(temp, offset) - } - - /** - * Write a PNG "IHDR" chunk into the pngBytes array. - */ - private fun writeHeader() { - bytePos = writeInt4(13, bytePos) - val startPos: Int = bytePos - bytePos = writeBytes(ihdr, bytePos) - width = image!!.width - height = image!!.height - bytePos = writeInt4(width, bytePos) - bytePos = writeInt4(height, bytePos) - bytePos = writeByte(8, bytePos) // bit depth - bytePos = writeByte(if (encodeAlpha) 6 else 2, bytePos) // direct model - bytePos = writeByte(0, bytePos) // compression method - bytePos = writeByte(0, bytePos) // filter method - bytePos = writeByte(0, bytePos) // no interlace - crc.reset() - crc.update(pngBytes!!, startPos, bytePos - startPos) - crcValue = crc.value - bytePos = writeInt4(crcValue.toInt(), bytePos) - } - - /** - * Perform "sub" filtering on the given row. - * Uses temporary array leftBytes to store the original values - * of the previous pixels. The array is 16 bytes long, which - * will easily hold two-byte samples plus two-byte alpha. - * - * @param pixels The array holding the scan lines being built - * @param startPos Starting position within pixels of bytes to be filtered. - * @param width Width of a scanline in pixels. - */ - private fun filterSub(pixels: ByteArray, startPos: Int, width: Int) { - val offset = bytesPerPixel - val actualStart = startPos + offset - val nBytes = width * bytesPerPixel - var leftInsert = offset - var leftExtract = 0 - var i: Int = actualStart - while (i < startPos + nBytes) { - leftBytes!![leftInsert] = pixels[i] - pixels[i] = ((pixels[i] - leftBytes!![leftExtract]) % 256).toByte() - leftInsert = (leftInsert + 1) % 0x0f - leftExtract = (leftExtract + 1) % 0x0f - i++ - } - } - - /** - * Perform "up" filtering on the given row. - * Side effect: refills the prior row with current row - * - * @param pixels The array holding the scan lines being built - * @param startPos Starting position within pixels of bytes to be filtered. - * @param width Width of a scanline in pixels. - */ - private fun filterUp(pixels: ByteArray, startPos: Int, width: Int) { - var i = 0 - val nBytes: Int = width * bytesPerPixel - var currentByte: Byte - while (i < nBytes) { - currentByte = pixels[startPos + i] - pixels[startPos + i] = ((pixels[startPos + i] - priorRow!![i]) % 256).toByte() - priorRow!![i] = currentByte - i++ - } - } - - /** - * Write the image data into the pngBytes array. - * This will write one or more PNG "IDAT" chunks. In order - * to conserve memory, this method grabs as many rows as will - * fit into 32K bytes, or the whole image; whichever is less. - * - * - * @return true if no errors; false if error grabbing pixels - */ - private fun writeImageData(): Boolean { - var rowsLeft = height // number of rows remaining to write - var startRow = 0 // starting row to process this time through - var nRows: Int // how many rows to grab at a time - - var scanLines: ByteArray // the scan lines to be compressed - var scanPos: Int // where we are in the scan lines - var startPos: Int // where this line's actual pixels start (used for filtering) - - val compressedLines: ByteArray // the resultant compressed lines - val nCompressed: Int // how big is the compressed area? - - //int depth; // color depth ( handle only 8 or 32 ) - - bytesPerPixel = if (encodeAlpha) 4 else 3 - - val scrunch = Deflater(compressionLevel) - val outBytes = ByteArrayOutputStream(1024) - - val compBytes = DeflaterOutputStream(outBytes, scrunch) - try { - while (rowsLeft > 0) { - nRows = min(32767 / (width * (bytesPerPixel + 1)), rowsLeft) - nRows = max(nRows, 1) - - val pixels = IntArray(width * nRows) - - //pg = new PixelGrabber(image, 0, startRow, width, nRows, pixels, 0, width); - image!!.getPixels(pixels, 0, width, 0, startRow, width, nRows) - - /* - * Create a data chunk. scanLines adds "nRows" for - * the filter bytes. - */ - scanLines = ByteArray(width * nRows * bytesPerPixel + nRows) - - if (filter == FILTER_SUB) { - leftBytes = ByteArray(16) - } - if (filter == FILTER_UP) { - priorRow = ByteArray(width * bytesPerPixel) - } - - scanPos = 0 - startPos = 1 - for (i in 0 until width * nRows) { - if (i % width == 0) { - scanLines[scanPos++] = filter.toByte() - startPos = scanPos - } - scanLines[scanPos++] = (pixels[i] shr 16 and 0xff).toByte() - scanLines[scanPos++] = (pixels[i] shr 8 and 0xff).toByte() - scanLines[scanPos++] = (pixels[i] and 0xff).toByte() - if (encodeAlpha) { - scanLines[scanPos++] = (pixels[i] shr 24 and 0xff).toByte() - } - if (i % width == width - 1 && filter != FILTER_NONE) { - if (filter == FILTER_SUB) { - filterSub(scanLines, startPos, width) - } - if (filter == FILTER_UP) { - filterUp(scanLines, startPos, width) - } - } - } - - /* - * Write these lines to the output area - */ - compBytes.write(scanLines, 0, scanPos) - - startRow += nRows - rowsLeft -= nRows - } - compBytes.close() - - /* - * Write the compressed bytes - */ - compressedLines = outBytes.toByteArray() - nCompressed = compressedLines.size - - crc.reset() - bytePos = writeInt4(nCompressed, bytePos) - bytePos = writeBytes(idat, bytePos) - crc.update(idat) - bytePos = writeBytes(compressedLines, nCompressed, bytePos) - crc.update(compressedLines, 0, nCompressed) - - crcValue = crc.value - bytePos = writeInt4(crcValue.toInt(), bytePos) - scrunch.finish() - scrunch.end() - return true - } catch (e: IOException) { - System.err.println(e.toString()) - return false - } - } - - /** - * Write a PNG "IEND" chunk into the pngBytes array. - */ - private fun writeEnd() { - bytePos = writeInt4(0, bytePos) - bytePos = writeBytes(iend, bytePos) - crc.reset() - crc.update(iend) - crcValue = crc.value - bytePos = writeInt4(crcValue.toInt(), bytePos) - } -} \ No newline at end of file diff --git a/apng_library/src/main/java/oupson/apng/imageUtils/PnnQuantizer.java b/apng_library/src/main/java/oupson/apng/imageUtils/PnnQuantizer.java deleted file mode 100644 index 8078f20..0000000 --- a/apng_library/src/main/java/oupson/apng/imageUtils/PnnQuantizer.java +++ /dev/null @@ -1,578 +0,0 @@ -package oupson.apng.imageUtils; - -/* Fast pairwise nearest neighbor based algorithm for multilevel thresholding -Copyright (C) 2004-2016 Mark Tyler and Dmitry Groshev -Copyright (c) 2018 Miller Cy Chan -* error measure; time used is proportional to number of bins squared - WJ */ - -/* - https://github.com/mcychan/nQuant.android/blob/master/nQuant.master/src/main/java/com/android/nQuant/PnnQuantizer.java - */ - -import android.annotation.SuppressLint; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Color; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Random; - -@SuppressWarnings("WeakerAccess") -@Deprecated() -public class PnnQuantizer { - private final short SHORT_MAX = Short.MAX_VALUE; - private final char BYTE_MAX = -Byte.MIN_VALUE + Byte.MAX_VALUE; - private boolean hasTransparency = false, hasSemiTransparency = false; - protected int width, height; - protected int[] pixels = null; - private Integer m_transparentColor; - @SuppressWarnings("unchecked") - @SuppressLint("UseSparseArrays") - private final HashMap closestMap = new HashMap(); - - public PnnQuantizer(String fname) { - fromBitmap(fname); - } - - public PnnQuantizer(Bitmap bitmap) { - fromBitmap(bitmap); - } - - private void fromBitmap(Bitmap bitmap) { - width = bitmap.getWidth(); - height = bitmap.getHeight(); - pixels = new int [width * height]; - bitmap.getPixels(pixels, 0, width, 0, 0, width, height); - } - - private void fromBitmap(String fname) { - Bitmap bitmap = BitmapFactory.decodeFile(fname); - fromBitmap(bitmap); - } - - private static final class Pnnbin { - double ac = 0, rc = 0, gc = 0, bc = 0, err = 0; - int cnt = 0; - int nn, fw, bk, tm, mtm; - } - - private int getColorIndex(final int c) - { - if(hasSemiTransparency) - return (Color.alpha(c) & 0xF0) << 8 | (Color.red(c) & 0xF0) << 4 | (Color.green(c) & 0xF0) | (Color.blue(c) >> 4); - if (hasTransparency) - return (Color.alpha(c) & 0x80) << 8 | (Color.red(c) & 0xF8) << 7 | (Color.green(c) & 0xF8) << 2 | (Color.blue(c) >> 3); - return (Color.red(c) & 0xF8) << 8 | (Color.green(c) & 0xFC) << 3 | (Color.blue(c) >> 3); - } - - private double sqr(double value) - { - return value * value; - } - - private void find_nn(Pnnbin[] bins, int idx) - { - int nn = 0; - double err = 1e100; - - Pnnbin bin1 = bins[idx]; - int n1 = bin1.cnt; - double wa = bin1.ac; - double wr = bin1.rc; - double wg = bin1.gc; - double wb = bin1.bc; - for (int i = bin1.fw; i != 0; i = bins[i].fw) { - double nerr = sqr(bins[i].ac - wa) + sqr(bins[i].rc - wr) + sqr(bins[i].gc - wg) + sqr(bins[i].bc - wb); - double n2 = bins[i].cnt; - nerr *= (n1 * n2) / (n1 + n2); - if (nerr >= err) - continue; - err = nerr; - nn = i; - } - bin1.err = err; - bin1.nn = nn; - } - - private Integer[] pnnquan(final int[] pixels, int nMaxColors, boolean quan_sqrt) - { - Pnnbin[] bins = new Pnnbin[65536]; - int[] heap = new int[65537]; - double err, n1, n2; - - /* Build histogram */ - for (final int pixel : pixels) { - // !!! Can throw gamma correction in here, but what to do about perceptual - // !!! nonuniformity then? - int index = getColorIndex(pixel); - if(bins[index] == null) - bins[index] = new Pnnbin(); - Pnnbin tb = bins[index]; - tb.ac += Color.alpha(pixel); - tb.rc += Color.red(pixel); - tb.gc += Color.green(pixel); - tb.bc += Color.blue(pixel); - tb.cnt++; - } - - /* Cluster nonempty bins at one end of array */ - int maxbins = 0; - - for (int i = 0; i < bins.length; ++i) { - if (bins[i] == null) - continue; - - double d = 1.0 / (double)bins[i].cnt; - bins[i].ac *= d; - bins[i].rc *= d; - bins[i].gc *= d; - bins[i].bc *= d; - if (quan_sqrt) - bins[i].cnt = (int) Math.sqrt(bins[i].cnt); - bins[maxbins++] = bins[i]; - } - - for (int i = 0; i < maxbins - 1; i++) { - bins[i].fw = (i + 1); - bins[i + 1].bk = i; - } - // !!! Already zeroed out by calloc() - // bins[0].bk = bins[i].fw = 0; - - int h, l, l2; - /* Initialize nearest neighbors and build heap of them */ - for (int i = 0; i < maxbins; i++) { - find_nn(bins, i); - /* Push slot on heap */ - err = bins[i].err; - for (l = ++heap[0]; l > 1; l = l2) { - l2 = l >> 1; - if (bins[h = heap[l2]].err <= err) - break; - heap[l] = h; - } - heap[l] = i; - } - - /* Merge bins which increase error the least */ - int extbins = maxbins - nMaxColors; - for (int i = 0; i < extbins; ) { - Pnnbin tb; - /* Use heap to find which bins to merge */ - for (;;) { - int b1 = heap[1]; - tb = bins[b1]; /* One with least error */ - /* Is stored error up to date? */ - if ((tb.tm >= tb.mtm) && (bins[tb.nn].mtm <= tb.tm)) - break; - if (tb.mtm == 0xFFFF) /* Deleted node */ - b1 = heap[1] = heap[heap[0]--]; - else /* Too old error value */ - { - find_nn(bins, b1); - tb.tm = i; - } - /* Push slot down */ - err = bins[b1].err; - for (l = 1; (l2 = l + l) <= heap[0]; l = l2) { - if ((l2 < heap[0]) && (bins[heap[l2]].err > bins[heap[l2 + 1]].err)) - l2++; - if (err <= bins[h = heap[l2]].err) - break; - heap[l] = h; - } - heap[l] = b1; - } - - /* Do a merge */ - Pnnbin nb = bins[tb.nn]; - n1 = tb.cnt; - n2 = nb.cnt; - double d = 1.0 / (n1 + n2); - tb.ac = d * (n1 * tb.ac + n2 * nb.ac); - tb.rc = d * (n1 * tb.rc + n2 * nb.rc); - tb.gc = d * (n1 * tb.gc + n2 * nb.gc); - tb.bc = d * (n1 * tb.bc + n2 * nb.bc); - tb.cnt += nb.cnt; - tb.mtm = ++i; - - /* Unchain deleted bin */ - bins[nb.bk].fw = nb.fw; - bins[nb.fw].bk = nb.bk; - nb.mtm = 0xFFFF; - } - - /* Fill palette */ - List palette = new ArrayList<>(); - short k = 0; - for (int i = 0;; ++k) { - assert bins[i] != null; - int alpha = (int) Math.rint(bins[i].ac); - palette.add(Color.argb(alpha, (int) Math.rint(bins[i].rc), (int) Math.rint(bins[i].gc), (int) Math.rint(bins[i].bc))); - if (hasTransparency && palette.get(k).equals(m_transparentColor)) { - Integer temp = palette.get(0); - palette.set(0, palette.get(k)); - palette.set(k, temp); - } - - if ((i = bins[i].fw) == 0) - break; - } - - return palette.toArray(new Integer[0]); - } - - private short nearestColorIndex(final Integer[] palette, final int[] squares3, final int c) - { - short k = 0; - int curdist, mindist = SHORT_MAX; - for (short i=0; i mindist) - continue; - - int rdist = Math.abs(Color.red(c2) - Color.red(c)); - curdist += squares3[rdist]; - if (curdist > mindist) - continue; - - int gdist = Math.abs(Color.green(c2) - Color.green(c)); - curdist += squares3[gdist]; - if (curdist > mindist) - continue; - - int bdist = Math.abs(Color.blue(c2) - Color.blue(c)); - curdist += squares3[bdist]; - if (curdist > mindist) - continue; - - mindist = curdist; - k = i; - } - return k; - } - - private short closestColorIndex(final Integer[] palette, final int c) - { - short k = 0; - short[] closest = new short[5]; - short[] got = closestMap.get(c); - if (got == null) { - closest[2] = closest[3] = SHORT_MAX; - - for (; k < palette.length; k++) { - int c2 = palette[k]; - - closest[4] = (short) (Math.abs(Color.alpha(c) - Color.alpha(c2)) + Math.abs(Color.red(c) - Color.red(c2)) + Math.abs(Color.green(c) - Color.green(c2)) + Math.abs(Color.blue(c) - Color.blue(c2))); - if (closest[4] < closest[2]) { - closest[1] = closest[0]; - closest[3] = closest[2]; - closest[0] = k; - closest[2] = closest[4]; - } - else if (closest[4] < closest[3]) { - closest[1] = k; - closest[3] = closest[4]; - } - } - - if (closest[3] == SHORT_MAX) - closest[2] = 0; - } - else - closest = got; - - Random rand = new Random(); - if (closest[2] == 0 || (rand.nextInt(SHORT_MAX) % (closest[3] + closest[2])) <= closest[3]) - k = closest[0]; - else - k = closest[1]; - - closestMap.put(c, closest); - return k; - } - - @SuppressWarnings({"WeakerAccess", "UnusedReturnValue", "UnusedAssignment", "SameReturnValue"}) - boolean quantize_image(final int[] pixels, final Integer[] palette, int[] qPixels, final boolean dither) - { - int nMaxColors = palette.length; - int[] sqr_tbl = new int[BYTE_MAX + BYTE_MAX + 1]; - - for (int i = (-BYTE_MAX); i <= BYTE_MAX; i++) - sqr_tbl[i + BYTE_MAX] = i * i; - - int[] squares3 = new int[sqr_tbl.length - BYTE_MAX]; - //noinspection ManualArrayCopy - for (int i = 0; i < squares3.length; i++) - squares3[i] = sqr_tbl[i + BYTE_MAX]; - - int pixelIndex = 0; - if (dither) { - boolean odd_scanline = false; - short[] row0, row1; - int a_pix, r_pix, g_pix, b_pix, dir, k; - final int DJ = 4; - final int DITHER_MAX = 20; - final int err_len = (width + 2) * DJ; - int[] clamp = new int[DJ * 256]; - int[] limtb = new int[512]; - short[] erowerr = new short[err_len]; - short[] orowerr = new short[err_len]; - int[] lookup = new int[65536]; - - for (int i = 0; i < 256; i++) { - clamp[i] = 0; - clamp[i + 256] = (short) i; - clamp[i + 512] = BYTE_MAX; - clamp[i + 768] = BYTE_MAX; - - limtb[i] = -DITHER_MAX; - limtb[i + 256] = DITHER_MAX; - } - for (int i = -DITHER_MAX; i <= DITHER_MAX; i++) - limtb[i + 256] = i; - - for (short i = 0; i < height; i++) { - if (odd_scanline) { - dir = -1; - pixelIndex += (width - 1); - row0 = orowerr; - row1 = erowerr; - } - else { - dir = 1; - row0 = erowerr; - row1 = orowerr; - } - - int cursor0 = DJ, cursor1 = width * DJ; - row1[cursor1] = row1[cursor1 + 1] = row1[cursor1 + 2] = row1[cursor1 + 3] = 0; - for (short j = 0; j < width; j++) { - int c = pixels[pixelIndex]; - r_pix = clamp[((row0[cursor0] + 0x1008) >> 4) + Color.red(c)]; - g_pix = clamp[((row0[cursor0 + 1] + 0x1008) >> 4) + Color.green(c)]; - b_pix = clamp[((row0[cursor0 + 2] + 0x1008) >> 4) + Color.blue(c)]; - a_pix = clamp[((row0[cursor0 + 3] + 0x1008) >> 4) + Color.alpha(c)]; - - int c1 = Color.argb(a_pix, r_pix, g_pix, b_pix); - int offset = getColorIndex(c1); - if (lookup[offset] == 0) - lookup[offset] = nearestColorIndex(palette, squares3, c1) + 1; - - int c2 = qPixels[pixelIndex] = palette[lookup[offset] - 1]; - - r_pix = limtb[r_pix - Color.red(c2) + 256]; - g_pix = limtb[g_pix - Color.green(c2) + 256]; - b_pix = limtb[b_pix - Color.blue(c2) + 256]; - a_pix = limtb[a_pix - Color.alpha(c2) + 256]; - - k = r_pix * 2; - row1[cursor1 - DJ] = (short) r_pix; - row1[cursor1 + DJ] += (r_pix += k); - row1[cursor1] += (r_pix += k); - row0[cursor0 + DJ] += (r_pix += k); - - k = g_pix * 2; - row1[cursor1 + 1 - DJ] = (short) g_pix; - row1[cursor1 + 1 + DJ] += (g_pix += k); - row1[cursor1 + 1] += (g_pix += k); - row0[cursor0 + 1 + DJ] += (g_pix += k); - - k = b_pix * 2; - row1[cursor1 + 2 - DJ] = (short) b_pix; - row1[cursor1 + 2 + DJ] += (b_pix += k); - row1[cursor1 + 2] += (b_pix += k); - row0[cursor0 + 2 + DJ] += (b_pix += k); - - k = a_pix * 2; - row1[cursor1 + 3 - DJ] = (short) a_pix; - row1[cursor1 + 3 + DJ] += (a_pix += k); - row1[cursor1 + 3] += (a_pix += k); - row0[cursor0 + 3 + DJ] += (a_pix += k); - - cursor0 += DJ; - cursor1 -= DJ; - pixelIndex += dir; - } - if ((i % 2) == 1) - pixelIndex += (width + 1); - - odd_scanline = !odd_scanline; - } - return true; - } - - if(hasSemiTransparency || nMaxColors < 256) { - for (int i = 0; i < qPixels.length; i++) - qPixels[i] = palette[nearestColorIndex(palette, squares3, pixels[i])]; - } - else { - for (int i = 0; i < qPixels.length; i++) - qPixels[i] = palette[closestColorIndex(palette, pixels[i])]; - } - - return true; - } - - @SuppressWarnings("UnusedAssignment") - private Bitmap quantize_image(final int[] pixels, int[] qPixels) - { - int pixelIndex = 0; - boolean odd_scanline = false; - short[] row0, row1; - int a_pix, r_pix, g_pix, b_pix, dir, k; - final int DJ = 4; - final int DITHER_MAX = 20; - final int err_len = (width + 2) * DJ; - short[] clamp = new short[DJ * 256]; - int[] limtb = new int[512]; - short[] erowerr = new short[err_len]; - short[] orowerr = new short[err_len]; - int[] lookup = new int[65536]; - - for (int i = 0; i < 256; i++) { - clamp[i] = 0; - clamp[i + 256] = (short) i; - clamp[i + 512] = BYTE_MAX; - clamp[i + 768] = BYTE_MAX; - - limtb[i] = -DITHER_MAX; - limtb[i + 256] = DITHER_MAX; - } - for (int i = -DITHER_MAX; i <= DITHER_MAX; i++) - limtb[i + 256] = i; - - for (int i = 0; i < height; i++) { - if (odd_scanline) { - dir = -1; - pixelIndex += (width - 1); - row0 = orowerr; - row1 = erowerr; - } - else { - dir = 1; - row0 = erowerr; - row1 = orowerr; - } - - int cursor0 = DJ, cursor1 = width * DJ; - row1[cursor1] = row1[cursor1 + 1] = row1[cursor1 + 2] = row1[cursor1 + 3] = 0; - for (short j = 0; j < width; j++) { - int c = pixels[pixelIndex]; - - r_pix = clamp[((row0[cursor0] + 0x1008) >> 4) + Color.red(c)]; - g_pix = clamp[((row0[cursor0 + 1] + 0x1008) >> 4) + Color.green(c)]; - b_pix = clamp[((row0[cursor0 + 2] + 0x1008) >> 4) + Color.blue(c)]; - a_pix = clamp[((row0[cursor0 + 3] + 0x1008) >> 4) + Color.alpha(c)]; - - int c1 = Color.argb(a_pix, r_pix, g_pix, b_pix); - int offset = getColorIndex(c1); - if (lookup[offset] == 0) { - int argb1 = Color.argb(BYTE_MAX, (Color.red(c1) & 0xF8), (Color.green(c1) & 0xFC), (Color.blue(c1) & 0xF8)); - if (hasSemiTransparency) - argb1 = Color.argb((Color.alpha(c1) & 0xF0), (Color.red(c1) & 0xF0), (Color.green(c1) & 0xF0), (Color.blue(c1) & 0xF0)); - else if (hasTransparency) - argb1 = Color.argb((Color.alpha(c1) < BYTE_MAX) ? 0 : BYTE_MAX, (Color.red(c1) & 0xF8), (Color.green(c1) & 0xF8), (Color.blue(c1) & 0xF8)); - lookup[offset] = argb1; - } - - int c2 = qPixels[pixelIndex] = lookup[offset]; - - r_pix = limtb[r_pix - Color.red(c2) + 256]; - g_pix = limtb[g_pix - Color.green(c2) + 256]; - b_pix = limtb[b_pix - Color.blue(c2) + 256]; - a_pix = limtb[a_pix - Color.alpha(c2) + 256]; - - k = r_pix * 2; - row1[cursor1 - DJ] = (short) r_pix; - row1[cursor1 + DJ] += (r_pix += k); - row1[cursor1] += (r_pix += k); - row0[cursor0 + DJ] += (r_pix += k); - - k = g_pix * 2; - row1[cursor1 + 1 - DJ] = (short) g_pix; - row1[cursor1 + 1 + DJ] += (g_pix += k); - row1[cursor1 + 1] += (g_pix += k); - row0[cursor0 + 1 + DJ] += (g_pix += k); - - k = b_pix * 2; - row1[cursor1 + 2 - DJ] = (short) b_pix; - row1[cursor1 + 2 + DJ] += (b_pix += k); - row1[cursor1 + 2] += (b_pix += k); - row0[cursor0 + 2 + DJ] += (b_pix += k); - - k = a_pix * 2; - row1[cursor1 + 3 - DJ] = (short) a_pix; - row1[cursor1 + 3 + DJ] += (a_pix += k); - row1[cursor1 + 3] += (a_pix += k); - row0[cursor0 + 3 + DJ] += (a_pix += k); - - cursor0 += DJ; - cursor1 -= DJ; - pixelIndex += dir; - } - if ((i % 2) == 1) - pixelIndex += (width + 1); - - odd_scanline = !odd_scanline; - } - - if (hasTransparency) - return Bitmap.createBitmap(qPixels, width, height, Bitmap.Config.ARGB_8888); - return Bitmap.createBitmap(qPixels, width, height, Bitmap.Config.RGB_565); - } - - @SuppressWarnings("UnusedReturnValue") - public Bitmap convert(int nMaxColors, boolean dither) { - final int[] cPixels = new int[pixels.length]; - for (int i =0; i> 24) & 0xff; - int r = (pixel >> 16) & 0xff; - int g = (pixel >> 8) & 0xff; - int b = (pixel ) & 0xff; - cPixels[i] = Color.argb(alfa, r, g, b); - if (alfa < BYTE_MAX) { - hasSemiTransparency = true; - if (alfa == 0) { - hasTransparency = true; - m_transparentColor = cPixels[i]; - } - } - } - - if (nMaxColors > 256) { - int[] qPixels = new int[cPixels.length]; - return quantize_image(cPixels, qPixels); - } - - boolean quan_sqrt = nMaxColors > BYTE_MAX; - Integer[] palette = new Integer[nMaxColors]; - if (nMaxColors > 2) - palette = pnnquan(cPixels, nMaxColors, quan_sqrt); - else { - if (hasSemiTransparency) { - palette[0] = Color.argb(0, 0, 0, 0); - palette[1] = Color.BLACK; - } - else { - palette[0] = Color.BLACK; - palette[1] = Color.WHITE; - } - } - - int[] qPixels = new int[cPixels.length]; - quantize_image(cPixels, palette, qPixels, dither); - closestMap.clear(); - - if (hasTransparency) - return Bitmap.createBitmap(qPixels, width, height, Bitmap.Config.ARGB_8888); - return Bitmap.createBitmap(qPixels, width, height, Bitmap.Config.RGB_565); - } - -} \ No newline at end of file diff --git a/apng_library/src/main/java/oupson/apng/utils/ApngAnimatorOptions.kt b/apng_library/src/main/java/oupson/apng/utils/ApngAnimatorOptions.kt deleted file mode 100644 index 0071f72..0000000 --- a/apng_library/src/main/java/oupson/apng/utils/ApngAnimatorOptions.kt +++ /dev/null @@ -1,6 +0,0 @@ -package oupson.apng.utils - -import android.widget.ImageView - -@Deprecated("Deprecated, Use ApngEncoder and ApngDecoder instead", level = DeprecationLevel.WARNING) -class ApngAnimatorOptions(val scaleType: ImageView.ScaleType? = ImageView.ScaleType.FIT_CENTER) \ No newline at end of file diff --git a/apng_library/src/main/java/oupson/apng/Loader.kt b/apng_library/src/main/java/oupson/apng/utils/Loader.kt similarity index 98% rename from apng_library/src/main/java/oupson/apng/Loader.kt rename to apng_library/src/main/java/oupson/apng/utils/Loader.kt index 2a45fa6..775008b 100644 --- a/apng_library/src/main/java/oupson/apng/Loader.kt +++ b/apng_library/src/main/java/oupson/apng/utils/Loader.kt @@ -1,4 +1,4 @@ -package oupson.apng +package oupson.apng.utils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext diff --git a/app-test/src/main/java/oupson/apngcreator/fragments/KotlinFragment.kt b/app-test/src/main/java/oupson/apngcreator/fragments/KotlinFragment.kt index 6b33181..e43e686 100644 --- a/app-test/src/main/java/oupson/apngcreator/fragments/KotlinFragment.kt +++ b/app-test/src/main/java/oupson/apngcreator/fragments/KotlinFragment.kt @@ -1,6 +1,8 @@ package oupson.apngcreator.fragments +import android.graphics.drawable.AnimationDrawable +import android.graphics.drawable.Drawable import android.os.Bundle import android.util.Log import android.view.LayoutInflater @@ -11,8 +13,8 @@ import android.widget.ImageView import android.widget.SeekBar import androidx.fragment.app.Fragment import com.squareup.picasso.Picasso -import oupson.apng.ApngAnimator -import oupson.apng.ApngAnimator.Companion.loadApng +import kotlinx.android.synthetic.main.activity_creator.* +import oupson.apng.decoder.ApngDecoder import oupson.apngcreator.BuildConfig import oupson.apngcreator.R @@ -33,7 +35,11 @@ class KotlinFragment : Fragment() { private var speedSeekBar : SeekBar? = null - private var animator : ApngAnimator? = null + //private var animator : ApngAnimator? = null + private var animation : AnimationDrawable? = null + private var durations : IntArray? = null + + private var frameIndex = 0 private val imageUrls = arrayListOf( "http://oupson.oupsman.fr/apng/bigApng.png", @@ -71,11 +77,34 @@ class KotlinFragment : Fragment() { Log.v(TAG, "onResume()") playButton?.setOnClickListener { - animator?.play() + animation?.start() } pauseButton?.setOnClickListener { - animator?.pause() + animation = animation?.let { animation -> + val res = AnimationDrawable() + animation.stop() + val currentFrame = animation.current + + frameLoop@ for (i in 0 until animation.numberOfFrames) { + val checkFrame = animation.getFrame(i) + if (checkFrame === currentFrame) { + frameIndex = i + for (k in frameIndex until animation.numberOfFrames) { + val frame: Drawable = animation.getFrame(k) + res.addFrame(frame, animation.getDuration(i)) + } + for (k in 0 until frameIndex) { + val frame: Drawable = animation.getFrame(k) + res.addFrame(frame, animation.getDuration(i)) + } + apngImageView?.setImageDrawable(res) + animation.invalidateSelf() + break@frameLoop + } + } + res + } } speedSeekBar?.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { @@ -88,23 +117,42 @@ class KotlinFragment : Fragment() { } override fun onStopTrackingTouch(seekBar: SeekBar?) { - if (seekBar != null) - animator?.speed = seekBar.progress.toFloat() / 100f + if (seekBar != null && durations != null) { + val speed = seekBar.progress.toFloat() / 100f + animation = animation?.let { animation -> + val res = AnimationDrawable() + animation.stop() + + for (i in 0 until animation.numberOfFrames) { + res.addFrame(animation.getFrame(i), (durations!![i].toFloat() / speed).toInt()) + } + + apngImageView?.setImageDrawable(res) + animation.invalidateSelf() + res.start() + res + } + } } }) - if (animator == null) { - try { - animator = apngImageView?.loadApng(imageUrls[selected])?.apply { - onLoaded { - setOnFrameChangeLister { - // Log.v("app-test", "onLoop") + if (animation == null) { + ApngDecoder.decodeApngAsyncInto( + this.context!!, + imageUrls[selected], + apngImageView!!, + callback = object : ApngDecoder.Callback { + override fun onSuccess(drawable: Drawable) { + animation = (drawable as? AnimationDrawable) + durations = IntArray(animation?.numberOfFrames ?: 0) { i -> + animation?.getDuration(i) ?: 0 } } - } - } catch (e : Exception) { - Log.e(TAG, "Error : $e") - } + + override fun onError(error: Exception) { + Log.e(TAG, "Error : $error") + } + }) } Picasso.get().load(imageUrls[selected]).into(normalImageView) @@ -115,7 +163,7 @@ class KotlinFragment : Fragment() { if (BuildConfig.DEBUG) Log.v(TAG, "onPause()") - animator = null + animation = null normalImageView?.setImageDrawable(null) apngImageView?.setImageDrawable(null)