Working on better ApngEncoder
This commit is contained in:
parent
acd55f6d5a
commit
90d2f33efd
|
@ -18,7 +18,7 @@
|
|||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="url" value="jar:file:/C:/Program%20Files/Android/Android%20Studio/plugins/android/lib/android.jar!/images/material_design_icons/navigation/ic_menu_black_24dp.xml" />
|
||||
<entry key="url" value="jar:file:/C:/Program%20Files/Android/Android%20Studio/plugins/android/lib/android.jar!/images/material_design_icons/social/ic_share_black_24dp.xml" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
|
@ -29,7 +29,7 @@
|
|||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="ffffff" />
|
||||
<entry key="outputName" value="ic_menu_white_24dp" />
|
||||
<entry key="outputName" value="ic_share_share_24dp" />
|
||||
<entry key="sourceFile" value="C:\Users\oupso\Downloads\java-seeklogo.com.svg" />
|
||||
</map>
|
||||
</option>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package oupson.apng
|
||||
package oupson.apng.decoder
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.*
|
||||
|
@ -11,6 +11,7 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import oupson.apng.*
|
||||
import oupson.apng.chunks.IHDR
|
||||
import oupson.apng.chunks.fcTL
|
||||
import oupson.apng.exceptions.BadApng
|
||||
|
@ -125,7 +126,12 @@ class ApngDecoder {
|
|||
}
|
||||
|
||||
png.addAll(Utils.pngSignature.asList())
|
||||
png.addAll(generateIhdr(ihdr, width, height).asList())
|
||||
png.addAll(
|
||||
generateIhdr(
|
||||
ihdr,
|
||||
width,
|
||||
height
|
||||
).asList())
|
||||
plte?.let {
|
||||
png?.addAll(it.asList())
|
||||
}
|
||||
|
@ -149,11 +155,16 @@ class ApngDecoder {
|
|||
canvas.drawBitmap(buffer!!, 0f, 0f, null)
|
||||
|
||||
if (blendOp == Utils.Companion.BlendOp.APNG_BLEND_OP_SOURCE) {
|
||||
canvas.drawRect(xOffset.toFloat(), yOffset.toFloat(), xOffset+ decoded.width.toFloat(), yOffset + decoded.height.toFloat(), clearPaint)
|
||||
canvas.drawRect(xOffset.toFloat(), yOffset.toFloat(), xOffset+ decoded.width.toFloat(), yOffset + decoded.height.toFloat(),
|
||||
clearPaint
|
||||
)
|
||||
}
|
||||
|
||||
canvas.drawBitmap(decoded, xOffset.toFloat(), yOffset.toFloat(), null)
|
||||
drawable.addFrame(BitmapDrawable(btm), (delay / speed).toInt())
|
||||
drawable.addFrame(
|
||||
BitmapDrawable(
|
||||
btm
|
||||
), (delay / speed).toInt())
|
||||
|
||||
when(disposeOp) {
|
||||
Utils.Companion.DisposeOp.APNG_DISPOSE_OP_PREVIOUS -> {
|
||||
|
@ -165,7 +176,9 @@ class ApngDecoder {
|
|||
val res = Bitmap.createBitmap(maxWidth, maxHeight, Bitmap.Config.ARGB_8888)
|
||||
val can = Canvas(res)
|
||||
can.drawBitmap(btm, 0f, 0f, null)
|
||||
can.drawRect(xOffset.toFloat(), yOffset.toFloat(), xOffset + decoded.width.toFloat(), yOffset + decoded.height.toFloat(), clearPaint)
|
||||
can.drawRect(xOffset.toFloat(), yOffset.toFloat(), xOffset + decoded.width.toFloat(), yOffset + decoded.height.toFloat(),
|
||||
clearPaint
|
||||
)
|
||||
buffer = res
|
||||
}
|
||||
else -> buffer = btm
|
||||
|
@ -183,7 +196,12 @@ class ApngDecoder {
|
|||
val width = fcTL.pngWidth
|
||||
val height = fcTL.pngHeight
|
||||
png.addAll(Utils.pngSignature.asList())
|
||||
png.addAll(generateIhdr(ihdr, width, height).asList())
|
||||
png.addAll(
|
||||
generateIhdr(
|
||||
ihdr,
|
||||
width,
|
||||
height
|
||||
).asList())
|
||||
plte?.let {
|
||||
png.addAll(it.asList())
|
||||
}
|
||||
|
@ -210,11 +228,16 @@ class ApngDecoder {
|
|||
canvas.drawBitmap(buffer!!, 0f, 0f, null)
|
||||
|
||||
if (blendOp == Utils.Companion.BlendOp.APNG_BLEND_OP_SOURCE) {
|
||||
canvas.drawRect(xOffset.toFloat(), yOffset.toFloat(), xOffset+ decoded.width.toFloat(), yOffset + decoded.height.toFloat(), clearPaint)
|
||||
canvas.drawRect(xOffset.toFloat(), yOffset.toFloat(), xOffset+ decoded.width.toFloat(), yOffset + decoded.height.toFloat(),
|
||||
clearPaint
|
||||
)
|
||||
}
|
||||
|
||||
canvas.drawBitmap(decoded, xOffset.toFloat(), yOffset.toFloat(), null)
|
||||
drawable.addFrame(BitmapDrawable(btm), (delay / speed).toInt())
|
||||
drawable.addFrame(
|
||||
BitmapDrawable(
|
||||
btm
|
||||
), (delay / speed).toInt())
|
||||
|
||||
when(disposeOp) {
|
||||
Utils.Companion.DisposeOp.APNG_DISPOSE_OP_PREVIOUS -> {
|
||||
|
@ -226,7 +249,9 @@ class ApngDecoder {
|
|||
val res = Bitmap.createBitmap(maxWidth, maxHeight, Bitmap.Config.ARGB_8888)
|
||||
val can = Canvas(res)
|
||||
can.drawBitmap(btm, 0f, 0f, null)
|
||||
can.drawRect(xOffset.toFloat(), yOffset.toFloat(), xOffset + decoded.width.toFloat(), yOffset + decoded.height.toFloat(), clearPaint)
|
||||
can.drawRect(xOffset.toFloat(), yOffset.toFloat(), xOffset + decoded.width.toFloat(), yOffset + decoded.height.toFloat(),
|
||||
clearPaint
|
||||
)
|
||||
buffer = res
|
||||
}
|
||||
else -> buffer = btm
|
||||
|
@ -243,7 +268,13 @@ class ApngDecoder {
|
|||
it.addAll(iend.asList())
|
||||
it.addAll(Utils.to4Bytes(crC32.value.toInt()).asList())
|
||||
inputStream.close()
|
||||
return BitmapDrawable(BitmapFactory.decodeByteArray(it.toByteArray(), 0, it.size))
|
||||
return BitmapDrawable(
|
||||
BitmapFactory.decodeByteArray(
|
||||
it.toByteArray(),
|
||||
0,
|
||||
it.size
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -252,11 +283,12 @@ class ApngDecoder {
|
|||
if (cover == null) {
|
||||
cover = ArrayList()
|
||||
cover.addAll(Utils.pngSignature.asList())
|
||||
cover.addAll(generateIhdr(
|
||||
ihdr,
|
||||
maxWidth,
|
||||
maxHeight
|
||||
).asList())
|
||||
cover.addAll(
|
||||
generateIhdr(
|
||||
ihdr,
|
||||
maxWidth,
|
||||
maxHeight
|
||||
).asList())
|
||||
}
|
||||
// Find the chunk length
|
||||
val bodySize = Utils.parseLength(byteArray.copyOfRange(i - 4, i))
|
||||
|
@ -334,7 +366,10 @@ class ApngDecoder {
|
|||
*/
|
||||
@Suppress("unused")
|
||||
@JvmStatic
|
||||
fun decodeApng(file : File, speed: Float = 1f) : Drawable = decodeApng(FileInputStream(file), speed)
|
||||
fun decodeApng(file : File, speed: Float = 1f) : Drawable =
|
||||
decodeApng(
|
||||
FileInputStream(file), speed
|
||||
)
|
||||
|
||||
/**
|
||||
* Decode Apng and return a Drawable who can be an [CustomAnimationDrawable] if it end successfully.
|
||||
|
@ -346,7 +381,10 @@ class ApngDecoder {
|
|||
fun decodeApng(context : Context, uri : Uri, speed: Float = 1f) : Drawable {
|
||||
val inputStream = context.contentResolver.openInputStream(uri)
|
||||
?: throw Exception("Failed to open InputStream, InputStream is null")
|
||||
return decodeApng(inputStream, speed)
|
||||
return decodeApng(
|
||||
inputStream,
|
||||
speed
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -357,7 +395,10 @@ class ApngDecoder {
|
|||
*/
|
||||
@Suppress("unused")
|
||||
@JvmStatic
|
||||
fun decodeApng(context : Context, @RawRes res : Int, speed: Float = 1f) : Drawable = decodeApng(context.resources.openRawResource(res), speed)
|
||||
fun decodeApng(context : Context, @RawRes res : Int, speed: Float = 1f) : Drawable =
|
||||
decodeApng(
|
||||
context.resources.openRawResource(res), speed
|
||||
)
|
||||
|
||||
/**
|
||||
* Decode Apng and return a Drawable who can be an [CustomAnimationDrawable] if it end successfully.
|
||||
|
@ -368,7 +409,11 @@ class ApngDecoder {
|
|||
@Suppress("unused")
|
||||
@JvmStatic
|
||||
suspend fun decodeApng(context : Context, url : URL, speed: Float = 1f) = withContext(Dispatchers.IO) {
|
||||
decodeApng(FileInputStream(Loader.load(context, url)), speed)
|
||||
decodeApng(
|
||||
FileInputStream(
|
||||
Loader.load(context, url)
|
||||
), speed
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -384,7 +429,11 @@ class ApngDecoder {
|
|||
fun decodeApngAsyncInto(file : File, imageView : ImageView, speed: Float = 1f, callback: Callback? = null) {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val drawable = decodeApng(FileInputStream(file), speed)
|
||||
val drawable =
|
||||
decodeApng(
|
||||
FileInputStream(file),
|
||||
speed
|
||||
)
|
||||
withContext(Dispatchers.Main) {
|
||||
imageView.setImageDrawable(drawable)
|
||||
(drawable as? CustomAnimationDrawable)?.start()
|
||||
|
@ -413,7 +462,11 @@ class ApngDecoder {
|
|||
val inputStream = context.contentResolver.openInputStream(uri) ?: throw Exception("Failed to open InputStream, InputStream is null")
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val drawable = decodeApng(inputStream, speed)
|
||||
val drawable =
|
||||
decodeApng(
|
||||
inputStream,
|
||||
speed
|
||||
)
|
||||
withContext(Dispatchers.Main) {
|
||||
imageView.setImageDrawable(drawable)
|
||||
(drawable as? CustomAnimationDrawable)?.start()
|
||||
|
@ -441,7 +494,11 @@ class ApngDecoder {
|
|||
fun decodeApngAsyncInto(context : Context, @RawRes res : Int, imageView: ImageView, speed: Float = 1f, callback: Callback? = null) {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val drawable = decodeApng(context.resources.openRawResource(res), speed)
|
||||
val drawable =
|
||||
decodeApng(
|
||||
context.resources.openRawResource(res),
|
||||
speed
|
||||
)
|
||||
withContext(Dispatchers.Main) {
|
||||
imageView.setImageDrawable(drawable)
|
||||
(drawable as? CustomAnimationDrawable)?.start()
|
||||
|
@ -470,7 +527,16 @@ class ApngDecoder {
|
|||
fun decodeApngAsyncInto(context : Context, url : URL, imageView: ImageView, speed: Float = 1f, callback: Callback? = null) {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val drawable = decodeApng(FileInputStream(Loader.load(context, url)), speed)
|
||||
val drawable =
|
||||
decodeApng(
|
||||
FileInputStream(
|
||||
Loader.load(
|
||||
context,
|
||||
url
|
||||
)
|
||||
),
|
||||
speed
|
||||
)
|
||||
withContext(Dispatchers.Main) {
|
||||
imageView.setImageDrawable(drawable)
|
||||
(drawable as? CustomAnimationDrawable)?.start()
|
||||
|
@ -499,13 +565,29 @@ class ApngDecoder {
|
|||
GlobalScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
if (string.startsWith("http://") || string.startsWith("https://")) {
|
||||
decodeApngAsyncInto(context, URL(string), imageView, speed, callback)
|
||||
decodeApngAsyncInto(
|
||||
context,
|
||||
URL(string),
|
||||
imageView,
|
||||
speed,
|
||||
callback
|
||||
)
|
||||
} else if(File(string).exists()) {
|
||||
var pathToLoad = if (string.startsWith("content://")) string else "file://$string"
|
||||
pathToLoad = pathToLoad.replace("%", "%25").replace("#", "%23")
|
||||
decodeApngAsyncInto(context, Uri.parse(pathToLoad), imageView, speed, callback)
|
||||
decodeApngAsyncInto(
|
||||
context,
|
||||
Uri.parse(pathToLoad),
|
||||
imageView,
|
||||
speed,
|
||||
callback
|
||||
)
|
||||
} else if (string.startsWith("file://android_asset/")) {
|
||||
val drawable = decodeApng(context.assets.open(string.replace("file:///android_asset/", "")), speed)
|
||||
val drawable =
|
||||
decodeApng(
|
||||
context.assets.open(string.replace("file:///android_asset/", "")),
|
||||
speed
|
||||
)
|
||||
withContext(Dispatchers.Main) {
|
||||
imageView.setImageDrawable(drawable)
|
||||
(drawable as? CustomAnimationDrawable)?.start()
|
|
@ -0,0 +1,206 @@
|
|||
package oupson.apng.encoder
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import oupson.apng.chunks.IDAT
|
||||
import oupson.apng.imageUtils.PngEncoder
|
||||
import oupson.apng.utils.Utils
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.util.zip.CRC32
|
||||
|
||||
class ApngEncoder(
|
||||
private val outputStream: OutputStream,
|
||||
private val width : Int,
|
||||
private val height : Int,
|
||||
numberOfFrames : Int) {
|
||||
private var frameIndex = 0
|
||||
private var seq = 0
|
||||
|
||||
private val idatName : List<Byte> by lazy {
|
||||
listOf(0x49.toByte(), 0x44.toByte(), 0x41.toByte(), 0x54.toByte())
|
||||
}
|
||||
|
||||
init {
|
||||
outputStream.write(Utils.pngSignature)
|
||||
outputStream.write(generateIhdr())
|
||||
outputStream.write(generateACTL(numberOfFrames))
|
||||
}
|
||||
|
||||
fun writeFrame(
|
||||
inputStream: InputStream,
|
||||
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
|
||||
) {
|
||||
val btm = BitmapFactory.decodeStream(inputStream)
|
||||
inputStream.close()
|
||||
|
||||
generateFCTL(btm, delay, disposeOp, blendOp, xOffsets, yOffsets)
|
||||
|
||||
val idat = IDAT().apply {
|
||||
val byteArray = PngEncoder.encode(btm, true)
|
||||
var cursor = 8
|
||||
while (cursor < byteArray.size) {
|
||||
val chunk = byteArray.copyOfRange(cursor, cursor + Utils.parseLength(byteArray.copyOfRange(cursor, cursor + 4)) + 12)
|
||||
parse(chunk)
|
||||
|
||||
cursor += Utils.parseLength(byteArray.copyOfRange(cursor, cursor + 4)) + 12
|
||||
}
|
||||
}
|
||||
|
||||
idat.IDATBody.forEach { idatBody ->
|
||||
if (frameIndex == 0) {
|
||||
val idatChunk = ArrayList<Byte>().let { i ->
|
||||
// Add IDAT
|
||||
i.addAll(idatName)
|
||||
// Add chunk body
|
||||
i.addAll(idatBody.asList())
|
||||
i.toByteArray()
|
||||
}
|
||||
// Add the chunk body length
|
||||
outputStream.write(Utils.to4Bytes(idatBody.size))
|
||||
|
||||
// Generate CRC
|
||||
val crc1 = CRC32()
|
||||
crc1.update(idatChunk, 0, idatChunk.size)
|
||||
outputStream.write(idatChunk)
|
||||
outputStream.write(Utils.to4Bytes(crc1.value.toInt()))
|
||||
} else {
|
||||
val fdat = ArrayList<Byte>().let { fdat ->
|
||||
fdat.addAll(byteArrayOf(0x66, 0x64, 0x41, 0x54).asList())
|
||||
// Add fdat
|
||||
fdat.addAll(Utils.to4Bytes(seq++).asList())
|
||||
// Add chunk body
|
||||
fdat.addAll(idatBody.asList())
|
||||
fdat.toByteArray()
|
||||
}
|
||||
// Add the chunk body length
|
||||
outputStream.write(Utils.to4Bytes(idatBody.size + 4))
|
||||
|
||||
// Generate CRC
|
||||
val crc1 = CRC32()
|
||||
crc1.update(fdat, 0, fdat.size)
|
||||
outputStream.write(fdat)
|
||||
outputStream.write(Utils.to4Bytes(crc1.value.toInt()))
|
||||
}
|
||||
}
|
||||
frameIndex++
|
||||
}
|
||||
|
||||
fun writeEnd() {
|
||||
// Add IEND body length : 0
|
||||
outputStream.write(Utils.to4Bytes(0))
|
||||
// Add IEND
|
||||
val iend = byteArrayOf(0x49, 0x45, 0x4E, 0x44)
|
||||
// Generate crc for IEND
|
||||
val crC32 = CRC32()
|
||||
crC32.update(iend, 0, iend.size)
|
||||
outputStream.write(iend)
|
||||
outputStream.write(Utils.to4Bytes(crC32.value.toInt()))
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the IHDR chunks.
|
||||
* @return [ByteArray] The byteArray generated
|
||||
*/
|
||||
private fun generateIhdr(): ByteArray {
|
||||
val ihdr = ArrayList<Byte>()
|
||||
|
||||
// We need a body var to know body length and generate crc
|
||||
val ihdrBody = ArrayList<Byte>()
|
||||
|
||||
/**
|
||||
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(arrayListOf(0x00, 0x00, 0x00, 0x0d))
|
||||
ihdrBody.addAll(arrayListOf(0x49, 0x48, 0x44, 0x52))
|
||||
|
||||
// Add the max width and height
|
||||
ihdrBody.addAll(Utils.to4Bytes(width).asList())
|
||||
ihdrBody.addAll(Utils.to4Bytes(height).asList())
|
||||
|
||||
ihdrBody.add(8.toByte())
|
||||
ihdrBody.add(6.toByte())
|
||||
ihdrBody.add(0.toByte())
|
||||
ihdrBody.add(0.toByte())
|
||||
ihdrBody.add(0.toByte())
|
||||
|
||||
|
||||
// Generate CRC
|
||||
val crC32 = CRC32()
|
||||
crC32.update(ihdrBody.toByteArray(), 0, ihdrBody.size)
|
||||
ihdr.addAll(ihdrBody)
|
||||
ihdr.addAll(Utils.to4Bytes(crC32.value.toInt()).asList())
|
||||
return ihdr.toByteArray()
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the animation control chunk
|
||||
* @return [ArrayList] The byteArray generated
|
||||
*/
|
||||
private fun generateACTL(num: Int): ByteArray {
|
||||
val res = ArrayList<Byte>()
|
||||
val actl = ArrayList<Byte>()
|
||||
|
||||
// Add length bytes
|
||||
res.addAll(arrayListOf(0, 0, 0, 0x08))
|
||||
|
||||
// Add acTL
|
||||
actl.addAll(byteArrayOf(0x61, 0x63, 0x54, 0x4c).asList())
|
||||
|
||||
// Add number of frames
|
||||
actl.addAll(Utils.to4Bytes(num).asList())
|
||||
|
||||
// Number of repeat, 0 to infinite
|
||||
actl.addAll(Utils.to4Bytes(0).asList())
|
||||
res.addAll(actl)
|
||||
|
||||
// generate crc
|
||||
val crc = CRC32()
|
||||
crc.update(actl.toByteArray(), 0, actl.size)
|
||||
res.addAll(Utils.to4Bytes(crc.value.toInt()).asList())
|
||||
return res.toByteArray()
|
||||
}
|
||||
|
||||
private fun generateFCTL(btm : Bitmap, delay: Float, disposeOp: Utils.Companion.DisposeOp, blendOp: Utils.Companion.BlendOp, xOffsets: Int, yOffsets: Int) {
|
||||
val fcTL = ArrayList<Byte>()
|
||||
|
||||
// Add the length of the chunk body
|
||||
outputStream.write(byteArrayOf(0x00, 0x00, 0x00, 0x1A))
|
||||
|
||||
// Add fcTL
|
||||
fcTL.addAll(byteArrayOf(0x66, 0x63, 0x54, 0x4c).asList())
|
||||
|
||||
// Add the frame number
|
||||
fcTL.addAll(Utils.to4Bytes(seq++).asList())
|
||||
|
||||
// Add width and height
|
||||
fcTL.addAll(Utils.to4Bytes(btm.width).asList())
|
||||
fcTL.addAll(Utils.to4Bytes(btm.height).asList())
|
||||
|
||||
// Add offsets
|
||||
fcTL.addAll(Utils.to4Bytes(xOffsets ).asList())
|
||||
fcTL.addAll(Utils.to4Bytes(yOffsets ).asList())
|
||||
|
||||
// Set frame delay
|
||||
fcTL.addAll(Utils.to2Bytes(delay.toInt()).asList())
|
||||
fcTL.addAll(Utils.to2Bytes(1000).asList())
|
||||
|
||||
// Add DisposeOp and BlendOp
|
||||
fcTL.add(Utils.getDisposeOp(disposeOp).toByte())
|
||||
fcTL.add(Utils.getBlendOp(blendOp).toByte())
|
||||
|
||||
// Create CRC
|
||||
val crc = CRC32()
|
||||
crc.update(fcTL.toByteArray(), 0, fcTL.size)
|
||||
outputStream.write(fcTL.toByteArray())
|
||||
outputStream.write(Utils.to4Bytes(crc.value.toInt()))
|
||||
}
|
||||
}
|
|
@ -13,11 +13,12 @@ import androidx.recyclerview.widget.ItemTouchHelper
|
|||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.android.synthetic.main.activity_creator.*
|
||||
import oupson.apng.Apng
|
||||
import oupson.apng.encoder.ApngEncoder
|
||||
import oupson.apngcreator.BuildConfig
|
||||
import oupson.apngcreator.R
|
||||
import oupson.apngcreator.adapter.ImageAdapter
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
|
||||
class CreatorActivity : AppCompatActivity() {
|
||||
companion object {
|
||||
|
@ -35,13 +36,7 @@ class CreatorActivity : AppCompatActivity() {
|
|||
val getIntent = Intent(Intent.ACTION_GET_CONTENT)
|
||||
getIntent.type = "image/*"
|
||||
|
||||
val pickIntent = Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
|
||||
pickIntent.type = "image/*"
|
||||
|
||||
val chooserIntent = Intent.createChooser(getIntent, "Select Image")
|
||||
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf(pickIntent))
|
||||
|
||||
startActivityForResult(chooserIntent,
|
||||
startActivityForResult(getIntent,
|
||||
PICK_IMAGE
|
||||
)
|
||||
}
|
||||
|
@ -68,38 +63,94 @@ class CreatorActivity : AppCompatActivity() {
|
|||
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
|
||||
return when (item?.itemId) {
|
||||
R.id.menu_create_apng -> {
|
||||
// TODO
|
||||
val apngCreated = Apng()
|
||||
|
||||
items.forEachIndexed { i, uri ->
|
||||
println("delay : ${adapter?.delay?.get(i)?.toFloat() ?: 1000f}ms")
|
||||
val str = contentResolver.openInputStream(uri)
|
||||
apngCreated.addFrames(BitmapFactory.decodeStream(str), delay = adapter?.delay?.get(i)?.toFloat() ?: 1000f)
|
||||
str?.close()
|
||||
}
|
||||
|
||||
File(cacheDir, "apn0.png").writeBytes(apngCreated.toByteArray())
|
||||
|
||||
apngCreated.apply {
|
||||
// TODO
|
||||
//if (view.optimiseCheckBox.isChecked)
|
||||
// apngCreated.optimiseFrame()
|
||||
}
|
||||
|
||||
// TODO Open
|
||||
val f = File(filesDir, "images/apng.png").apply {
|
||||
if (!exists()) {
|
||||
parentFile.mkdirs()
|
||||
println(createNewFile())
|
||||
if (items.size > 0) {
|
||||
val f = File(filesDir, "images/apng.png").apply {
|
||||
if (!exists()) {
|
||||
parentFile.mkdirs()
|
||||
println(createNewFile())
|
||||
}
|
||||
}
|
||||
writeBytes(apngCreated.toByteArray())
|
||||
val out = FileOutputStream(f)
|
||||
var maxWidth = 0
|
||||
var maxHeight = 0
|
||||
items.forEach {
|
||||
val str = contentResolver.openInputStream(it)
|
||||
val btm = BitmapFactory.decodeStream(str)
|
||||
if (btm.width > maxWidth)
|
||||
maxWidth = btm.width
|
||||
else if(btm.height > maxHeight)
|
||||
maxHeight = btm.height
|
||||
str?.close()
|
||||
}
|
||||
|
||||
val encoder = ApngEncoder(out, maxWidth, maxHeight, items.size)
|
||||
items.forEachIndexed { i, uri ->
|
||||
println("delay : ${adapter?.delay?.get(i)?.toFloat() ?: 1000f}ms")
|
||||
val str = contentResolver.openInputStream(uri) ?: return@forEachIndexed
|
||||
encoder.writeFrame(
|
||||
str,
|
||||
delay = adapter?.delay?.get(i)?.toFloat() ?: 1000f
|
||||
)
|
||||
}
|
||||
|
||||
encoder.writeEnd()
|
||||
out.close()
|
||||
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = FileProvider.getUriForFile(
|
||||
this,
|
||||
"${BuildConfig.APPLICATION_ID}.provider",
|
||||
f
|
||||
)
|
||||
startActivity(intent)
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.menu_share_apng -> {
|
||||
if (items.size > 0) {
|
||||
val f = File(filesDir, "images/apng.png").apply {
|
||||
if (!exists()) {
|
||||
parentFile.mkdirs()
|
||||
println(createNewFile())
|
||||
}
|
||||
}
|
||||
val out = FileOutputStream(f)
|
||||
var maxWidth = 0
|
||||
var maxHeight = 0
|
||||
items.forEach {
|
||||
val str = contentResolver.openInputStream(it)
|
||||
val btm = BitmapFactory.decodeStream(str)
|
||||
if (btm.width > maxWidth)
|
||||
maxWidth = btm.width
|
||||
else if(btm.height > maxHeight)
|
||||
maxHeight = btm.height
|
||||
str?.close()
|
||||
}
|
||||
|
||||
val encoder = ApngEncoder(out, maxWidth, maxHeight, items.size)
|
||||
items.forEachIndexed { i, uri ->
|
||||
println("delay : ${adapter?.delay?.get(i)?.toFloat() ?: 1000f}ms")
|
||||
val str = contentResolver.openInputStream(uri) ?: return@forEachIndexed
|
||||
encoder.writeFrame(
|
||||
str,
|
||||
delay = adapter?.delay?.get(i)?.toFloat() ?: 1000f
|
||||
)
|
||||
}
|
||||
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = FileProvider.getUriForFile(this, "${BuildConfig.APPLICATION_ID}.provider", f)
|
||||
startActivity(intent)
|
||||
encoder.writeEnd()
|
||||
out.close()
|
||||
|
||||
val intent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(
|
||||
this@CreatorActivity,
|
||||
"${BuildConfig.APPLICATION_ID}.provider",
|
||||
f
|
||||
))
|
||||
type = "image/png"
|
||||
}
|
||||
startActivity(Intent.createChooser(intent, resources.getText(R.string.share)))
|
||||
}
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
|
|
|
@ -2,17 +2,16 @@ package oupson.apngcreator.activities
|
|||
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import kotlinx.android.synthetic.main.activity_viewer.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import oupson.apng.ApngDecoder
|
||||
import oupson.apng.CustomAnimationDrawable
|
||||
import oupson.apng.decoder.ApngDecoder
|
||||
import oupson.apngcreator.BuildConfig
|
||||
import oupson.apngcreator.R
|
||||
|
||||
class ViewerActivity : AppCompatActivity() {
|
||||
|
@ -42,15 +41,13 @@ class ViewerActivity : AppCompatActivity() {
|
|||
|
||||
private fun load() {
|
||||
val uri = intent.data ?: return
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
//val animator = imageView.loadApng(uri, null)
|
||||
val drawable = ApngDecoder.decodeApng(this@ViewerActivity, uri)
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
viewerImageView.setImageDrawable(drawable)
|
||||
if (drawable is CustomAnimationDrawable)
|
||||
drawable.start()
|
||||
ApngDecoder.decodeApngAsyncInto(this, uri, viewerImageView, callback = object : ApngDecoder.Callback {
|
||||
override fun onSuccess(drawable: Drawable) {}
|
||||
override fun onError(error: Exception) {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.e("ViewerActivity", "Error when loading file", error)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int,
|
||||
|
|
|
@ -9,7 +9,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.fragment.app.Fragment
|
||||
import oupson.apng.ApngDecoder
|
||||
import oupson.apng.decoder.ApngDecoder
|
||||
import oupson.apngcreator.BuildConfig
|
||||
import oupson.apngcreator.R
|
||||
import java.net.URL
|
||||
|
|
|
@ -14,7 +14,7 @@ import androidx.fragment.app.Fragment;
|
|||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import oupson.apng.ApngDecoder;
|
||||
import oupson.apng.decoder.ApngDecoder;
|
||||
import oupson.apngcreator.R;
|
||||
|
||||
|
||||
|
|
|
@ -8,4 +8,11 @@
|
|||
android:title="@string/create"
|
||||
app:showAsAction="ifRoom"
|
||||
app:iconTint="@color/control"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_share_apng"
|
||||
android:icon="@drawable/ic_share_share_24dp"
|
||||
android:title="@string/share"
|
||||
app:showAsAction="ifRoom"
|
||||
app:iconTint="@color/control"/>
|
||||
</menu>
|
|
@ -11,6 +11,8 @@
|
|||
<string name="menu_title_apng_decoder_fragment">ApngDecoder</string>
|
||||
|
||||
<string name="create">Create</string>
|
||||
<string name="share">Share</string>
|
||||
<string name="select_image">Select an image</string>
|
||||
|
||||
<string name="open">open</string>
|
||||
<string name="close">close</string>
|
||||
|
|
Loading…
Reference in New Issue