More "thread-safe" Png Encoder
Now PngEncoder and his variables should be freed after using PngEncoder().encode(...), as wanted in issue #7
This commit is contained in:
parent
d32f5f3ebf
commit
9777569c4b
|
@ -21,6 +21,7 @@ import java.util.zip.CRC32
|
|||
// TODO CREATE A BETTER CLASS
|
||||
/**
|
||||
* Create an APNG file
|
||||
* If you want to create an APNG, use ApngEncoder instead
|
||||
*/
|
||||
@Suppress("unused")
|
||||
class Apng {
|
||||
|
@ -54,9 +55,9 @@ class Apng {
|
|||
@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))
|
||||
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))
|
||||
frames.add(index, Frame(PngEncoder().encode(bitmap, true), delay, xOffset, yOffset, blendOp, disposeOp))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -154,7 +155,7 @@ class Apng {
|
|||
// Add cover image : Not part of animation
|
||||
// region IDAT
|
||||
val idat = IDAT()
|
||||
idat.parse(PngEncoder.encode(cover!!, true, 1))
|
||||
idat.parse(PngEncoder().encode(cover!!, true, 1))
|
||||
idat.IDATBody.forEach {
|
||||
val idatByteArray = ArrayList<Byte>()
|
||||
framesByte.addAll(to4Bytes(it.size).asList())
|
||||
|
@ -422,11 +423,11 @@ class Apng {
|
|||
it.maxHeight = maxHeight
|
||||
}
|
||||
val drawedFrame = ApngAnimator(null).draw(frames)
|
||||
File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), "frame0.png").writeBytes(PngEncoder.encode(drawedFrame[0]))
|
||||
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)
|
||||
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
|
||||
|
|
|
@ -62,7 +62,7 @@ class ApngEncoder(
|
|||
|
||||
val idat = IDAT().apply {
|
||||
val byteArray = if (usePngEncoder) {
|
||||
PngEncoder.encode(btm, true)
|
||||
PngEncoder().encode(btm, true)
|
||||
} else {
|
||||
val outputStream = ByteArrayOutputStream()
|
||||
btm.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
|
||||
|
@ -114,9 +114,9 @@ class ApngEncoder(
|
|||
}
|
||||
}
|
||||
frameIndex++
|
||||
if (usePngEncoder) {
|
||||
/**if (usePngEncoder) {
|
||||
PngEncoder.release()
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
fun writeEnd() {
|
||||
|
|
|
@ -65,7 +65,7 @@ class ExperimentalApngEncoder(
|
|||
|
||||
val idat = IDAT().apply {
|
||||
val byteArray = if (usePngEncoder) {
|
||||
PngEncoder.encode(btm, true)
|
||||
PngEncoder().encode(btm, true)
|
||||
} else {
|
||||
val outputStream = ByteArrayOutputStream()
|
||||
btm.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
|
||||
|
@ -117,9 +117,9 @@ class ExperimentalApngEncoder(
|
|||
}
|
||||
}
|
||||
frameIndex++
|
||||
if (usePngEncoder) {
|
||||
/**if (usePngEncoder) {
|
||||
PngEncoder.release()
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
fun writeEnd() {
|
||||
|
|
|
@ -10,15 +10,11 @@ import kotlin.math.max
|
|||
import kotlin.math.min
|
||||
|
||||
// TODO FIND A BETTER SOLUTION
|
||||
// TODO ABSOLUTELY NOT THREAD SAFE, FIX THAT
|
||||
/**
|
||||
* Taken from http://catcode.com/pngencoder/com/keypoint/PngEncoder.java
|
||||
*/
|
||||
class PngEncoder {
|
||||
companion object {
|
||||
/** Encode alpha ? */
|
||||
private var encodeAlpha = true
|
||||
|
||||
/** Constants for filter (NONE) */
|
||||
private const val FILTER_NONE = 0
|
||||
|
||||
|
@ -30,391 +26,393 @@ class PngEncoder {
|
|||
|
||||
/** 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)
|
||||
/** IHDR tag. */
|
||||
private val ihdr = byteArrayOf(73, 72, 68, 82)
|
||||
|
||||
/** IDAT tag. */
|
||||
private val IDAT = byteArrayOf(73, 68, 65, 84)
|
||||
/** IDAT tag. */
|
||||
private val idat = byteArrayOf(73, 68, 65, 84)
|
||||
|
||||
/** IEND tag. */
|
||||
private val IEND = byteArrayOf(73, 69, 78, 68)
|
||||
/** IEND tag. */
|
||||
private val iend = byteArrayOf(73, 69, 78, 68)
|
||||
|
||||
/** The image. */
|
||||
private var image: Bitmap? = null
|
||||
/** The image. */
|
||||
private var image: Bitmap? = null
|
||||
|
||||
/** The png bytes. */
|
||||
private var pngBytes: ByteArray? = null
|
||||
/** The png bytes. */
|
||||
private var pngBytes: ByteArray? = null
|
||||
|
||||
/** The prior row. */
|
||||
private var priorRow: ByteArray? = null
|
||||
/** The prior row. */
|
||||
private var priorRow: ByteArray? = null
|
||||
|
||||
/** The left bytes. */
|
||||
private var leftBytes: ByteArray? = null
|
||||
/** The left bytes. */
|
||||
private var leftBytes: ByteArray? = null
|
||||
|
||||
/** The width. */
|
||||
private var width: Int = 0
|
||||
private var height: Int = 0
|
||||
/** 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
|
||||
/** The byte position. */
|
||||
private var bytePos: Int = 0
|
||||
private var maxPos: Int = 0
|
||||
|
||||
/** CRC. */
|
||||
private var crc = CRC32()
|
||||
/** CRC. */
|
||||
private var crc = CRC32()
|
||||
|
||||
/** The CRC value. */
|
||||
private var crcValue: Long = 0
|
||||
/** The CRC value. */
|
||||
private var crcValue: Long = 0
|
||||
|
||||
/** The filter type. */
|
||||
private var filter: Int = 0
|
||||
/** The filter type. */
|
||||
private var filter: Int = 0
|
||||
|
||||
/** The bytes-per-pixel. */
|
||||
private var bytesPerPixel: Int = 0
|
||||
/** The bytes-per-pixel. */
|
||||
private var bytesPerPixel: Int = 0
|
||||
|
||||
/** The compression level. */
|
||||
private var compressionLevel: 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!!
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
if (compressionLevel in 0..9) {
|
||||
this.compressionLevel = compressionLevel
|
||||
}
|
||||
|
||||
fun release() {
|
||||
image?.recycle()
|
||||
image = null
|
||||
pngBytes = null
|
||||
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!!
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
|
||||
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 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 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 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())
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
/**
|
||||
* 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)
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
}
|
|
@ -5,7 +5,6 @@ import android.content.Intent
|
|||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
|
@ -319,11 +318,7 @@ class CreatorActivity : AppCompatActivity() {
|
|||
if (BuildConfig.DEBUG)
|
||||
Log.i(TAG, "MaxWidth : $maxWidth; MaxHeight : $maxHeight")
|
||||
|
||||
val encoder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
ExperimentalApngEncoder(out, maxWidth, maxHeight, items.size, Bitmap.Config.RGBA_F16)
|
||||
} else {
|
||||
TODO("VERSION.SDK_INT < O")
|
||||
}
|
||||
val encoder = ExperimentalApngEncoder(out, maxWidth, maxHeight, items.size, Bitmap.Config.ARGB_8888)
|
||||
items.forEach { uri ->
|
||||
// println("delay : ${adapter?.delay?.get(i)?.toFloat() ?: 1000f}ms")
|
||||
val str =
|
||||
|
|
Loading…
Reference in New Issue