Bug fix with disassembler.
Chunk optimisation Working on apng optimiser ! Experimental, buggy Add crc verification
This commit is contained in:
parent
6acbf227f2
commit
6d1f6af7c5
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="WizardSettings">
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="vectorWizard">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="vectorAssetStep">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="clipartAsset">
|
||||
<value>
|
||||
<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/content/ic_add_black_24dp.xml" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="ffffff" />
|
||||
<entry key="outputName" value="ic_add_white_24dp" />
|
||||
<entry key="sourceFile" value="C:\Users\oupso" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
Binary file not shown.
|
@ -3,6 +3,7 @@ package oupson.apng
|
|||
import android.graphics.BitmapFactory
|
||||
import oupson.apng.chunks.IHDR
|
||||
import oupson.apng.chunks.fcTL
|
||||
import oupson.apng.exceptions.BadCRC
|
||||
import oupson.apng.exceptions.NotApngException
|
||||
import oupson.apng.utils.Utils
|
||||
import oupson.apng.utils.Utils.Companion.isApng
|
||||
|
@ -25,19 +26,18 @@ class APNGDisassembler {
|
|||
private var maxHeight = 0
|
||||
private var blend_op: Utils.Companion.blend_op = Utils.Companion.blend_op.APNG_BLEND_OP_SOURCE
|
||||
private var dispose_op: Utils.Companion.dispose_op = Utils.Companion.dispose_op.APNG_DISPOSE_OP_NONE
|
||||
private val ihdr = IHDR()
|
||||
private var ihdr = IHDR()
|
||||
|
||||
var apng: Apng = Apng()
|
||||
|
||||
/**
|
||||
* Disassemble an Apng file
|
||||
* @param byteArray The Byte Array of the file
|
||||
* @return The apng decoded
|
||||
*/
|
||||
fun disassemble(byteArray: ByteArray) : Apng {
|
||||
reset()
|
||||
if (isApng(byteArray)) {
|
||||
ihdr.parse(byteArray)
|
||||
maxWidth = ihdr.pngWidth
|
||||
maxHeight = ihdr.pngHeight
|
||||
var cursor = 8
|
||||
while (cursor < byteArray.size) {
|
||||
val chunk = byteArray.copyOfRange(cursor, cursor + parseLength(byteArray.copyOfRange(cursor, cursor + 4)) + 12)
|
||||
|
@ -74,41 +74,75 @@ class APNGDisassembler {
|
|||
|
||||
private fun parseChunk(byteArray: ByteArray) {
|
||||
val i = 4
|
||||
val name = Arrays.toString(byteArray.copyOfRange(i, i +4))
|
||||
when (name) {
|
||||
Utils.fcTL -> {
|
||||
if (png == null) {
|
||||
cover?.let {
|
||||
it.addAll(to4Bytes(0).toList())
|
||||
val chunkCRC = parseLength(byteArray.copyOfRange(byteArray.size - 4, byteArray.size))
|
||||
val crc = CRC32();
|
||||
crc.update(byteArray.copyOfRange(i, byteArray.size - 4))
|
||||
if (chunkCRC == crc.value.toInt()) {
|
||||
val name = Arrays.toString(byteArray.copyOfRange(i, i + 4))
|
||||
when (name) {
|
||||
Utils.fcTL -> {
|
||||
if (png == null) {
|
||||
cover?.let {
|
||||
it.addAll(to4Bytes(0).toList())
|
||||
// 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.toList())
|
||||
it.addAll(to4Bytes(crC32.value.toInt()).toList())
|
||||
apng.cover = BitmapFactory.decodeByteArray(it.toByteArray(), 0, it.size)
|
||||
}
|
||||
png = ArrayList()
|
||||
val fcTL = fcTL()
|
||||
fcTL.parse(byteArray)
|
||||
delay = fcTL.delay
|
||||
yOffset = fcTL.y_offset
|
||||
xOffset = fcTL.x_offset
|
||||
blend_op = fcTL.blend_op
|
||||
dispose_op = fcTL.dispose_op
|
||||
val width = fcTL.pngWidth
|
||||
val height = fcTL.pngHeight
|
||||
png!!.addAll(pngSignature.toList())
|
||||
png!!.addAll(generateIhdr(ihdr, width, height).toList())
|
||||
plte?.let {
|
||||
png!!.addAll(it.toList())
|
||||
}
|
||||
tnrs?.let {
|
||||
png!!.addAll(it.toList())
|
||||
}
|
||||
} else {
|
||||
// Add IEND body length : 0
|
||||
png!!.addAll(to4Bytes(0).toList())
|
||||
// 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.toList())
|
||||
it.addAll(to4Bytes(crC32.value.toInt()).toList())
|
||||
apng.cover = BitmapFactory.decodeByteArray(it.toByteArray(), 0, it.size)
|
||||
png!!.addAll(iend.toList())
|
||||
png!!.addAll(to4Bytes(crC32.value.toInt()).toList())
|
||||
apng.frames.add(Frame(png!!.toByteArray(), delay, xOffset, yOffset, maxWidth, maxHeight, blend_op, dispose_op))
|
||||
png = ArrayList()
|
||||
val fcTL = fcTL()
|
||||
fcTL.parse(byteArray)
|
||||
delay = fcTL.delay
|
||||
yOffset = fcTL.y_offset
|
||||
xOffset = fcTL.x_offset
|
||||
blend_op = fcTL.blend_op
|
||||
dispose_op = fcTL.dispose_op
|
||||
val width = fcTL.pngWidth
|
||||
val height = fcTL.pngHeight
|
||||
png!!.addAll(pngSignature.toList())
|
||||
png!!.addAll(generateIhdr(ihdr, width, height).toList())
|
||||
plte?.let {
|
||||
png!!.addAll(it.toList())
|
||||
}
|
||||
tnrs?.let {
|
||||
png!!.addAll(it.toList())
|
||||
}
|
||||
}
|
||||
png = ArrayList()
|
||||
val fcTL = fcTL()
|
||||
fcTL.parse(byteArray)
|
||||
delay = fcTL.delay
|
||||
yOffset = fcTL.y_offset
|
||||
xOffset = fcTL.x_offset
|
||||
blend_op = fcTL.blend_op
|
||||
dispose_op = fcTL.dispose_op
|
||||
val width = fcTL.pngWidth
|
||||
val height = fcTL.pngHeight
|
||||
png!!.addAll(pngSignature.toList())
|
||||
png!!.addAll(generateIhdr(ihdr, width, height).toList())
|
||||
plte?.let {
|
||||
png!!.addAll(it.toList())
|
||||
}
|
||||
tnrs?.let {
|
||||
png!!.addAll(it.toList())
|
||||
}
|
||||
} else {
|
||||
// Add IEND body length : 0
|
||||
}
|
||||
Utils.IEND -> {
|
||||
png!!.addAll(to4Bytes(0).toList())
|
||||
// Add IEND
|
||||
val iend = byteArrayOf(0x49, 0x45, 0x4E, 0x44)
|
||||
|
@ -118,89 +152,79 @@ class APNGDisassembler {
|
|||
png!!.addAll(iend.toList())
|
||||
png!!.addAll(to4Bytes(crC32.value.toInt()).toList())
|
||||
apng.frames.add(Frame(png!!.toByteArray(), delay, xOffset, yOffset, maxWidth, maxHeight, blend_op, dispose_op))
|
||||
png = ArrayList()
|
||||
val fcTL = fcTL()
|
||||
fcTL.parse(byteArray)
|
||||
delay = fcTL.delay
|
||||
yOffset = fcTL.y_offset
|
||||
xOffset = fcTL.x_offset
|
||||
blend_op = fcTL.blend_op
|
||||
dispose_op = fcTL.dispose_op
|
||||
val width = fcTL.pngWidth
|
||||
val height = fcTL.pngHeight
|
||||
png!!.addAll(pngSignature.toList())
|
||||
png!!.addAll(generateIhdr(ihdr, width, height).toList())
|
||||
plte?.let {
|
||||
png!!.addAll(it.toList())
|
||||
}
|
||||
tnrs?.let {
|
||||
png!!.addAll(it.toList())
|
||||
}
|
||||
Utils.IDAT -> {
|
||||
if (png == null) {
|
||||
if (cover == null) {
|
||||
cover = ArrayList()
|
||||
cover!!.addAll(pngSignature.toList())
|
||||
cover!!.addAll(generateIhdr(ihdr, maxWidth, maxHeight).toList())
|
||||
}
|
||||
// Find the chunk length
|
||||
val bodySize = parseLength(byteArray.copyOfRange(i - 4, i))
|
||||
cover!!.addAll(byteArray.copyOfRange(i - 4, i).toList())
|
||||
val body = ArrayList<Byte>()
|
||||
body.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).toList())
|
||||
// Get image bytes
|
||||
body.addAll(byteArray.copyOfRange(i + 4, i + 4 + bodySize).toList())
|
||||
val crC32 = CRC32()
|
||||
crC32.update(body.toByteArray(), 0, body.size)
|
||||
cover!!.addAll(body)
|
||||
cover!!.addAll(to4Bytes(crC32.value.toInt()).toList())
|
||||
} else {
|
||||
// Find the chunk length
|
||||
val bodySize = parseLength(byteArray.copyOfRange(i - 4, i))
|
||||
png!!.addAll(byteArray.copyOfRange(i - 4, i).toList())
|
||||
val body = ArrayList<Byte>()
|
||||
body.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).toList())
|
||||
// Get image bytes
|
||||
body.addAll(byteArray.copyOfRange(i + 4, i + 4 + bodySize).toList())
|
||||
val crC32 = CRC32()
|
||||
crC32.update(body.toByteArray(), 0, body.size)
|
||||
png!!.addAll(body)
|
||||
png!!.addAll(to4Bytes(crC32.value.toInt()).toList())
|
||||
}
|
||||
}
|
||||
}
|
||||
Utils.IEND -> {
|
||||
png!!.addAll(to4Bytes(0).toList())
|
||||
// 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.toList())
|
||||
png!!.addAll(to4Bytes(crC32.value.toInt()).toList())
|
||||
apng.frames.add(Frame(png!!.toByteArray(), delay, xOffset, yOffset, maxWidth, maxHeight, blend_op, dispose_op))
|
||||
}
|
||||
Utils.IDAT -> {
|
||||
if (png == null) {
|
||||
if (cover == null) {
|
||||
cover = ArrayList()
|
||||
cover!!.addAll(pngSignature.toList())
|
||||
cover!!.addAll(generateIhdr(ihdr, maxWidth, maxHeight).toList())
|
||||
}
|
||||
Utils.fdAT -> {
|
||||
// Find the chunk length
|
||||
val bodySize = parseLength(byteArray.copyOfRange(i - 4, i))
|
||||
cover!!.addAll(byteArray.copyOfRange(i - 4, i).toList())
|
||||
png!!.addAll(to4Bytes(bodySize - 4).toList())
|
||||
val body = ArrayList<Byte>()
|
||||
body.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).toList())
|
||||
// Get image bytes
|
||||
body.addAll(byteArray.copyOfRange(i + 4,i + 4 + bodySize).toList())
|
||||
val crC32 = CRC32()
|
||||
crC32.update(body.toByteArray(), 0, body.size)
|
||||
cover!!.addAll(body)
|
||||
cover!!.addAll(to4Bytes(crC32.value.toInt()).toList())
|
||||
} else {
|
||||
// Find the chunk length
|
||||
val bodySize = parseLength(byteArray.copyOfRange(i - 4, i))
|
||||
png!!.addAll(byteArray.copyOfRange(i - 4, i).toList())
|
||||
val body = ArrayList<Byte>()
|
||||
body.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).toList())
|
||||
// Get image bytes
|
||||
body.addAll(byteArray.copyOfRange(i + 4,i + 4 + bodySize).toList())
|
||||
body.addAll(byteArray.copyOfRange(i + 8, i + 4 + bodySize).toList())
|
||||
val crC32 = CRC32()
|
||||
crC32.update(body.toByteArray(), 0, body.size)
|
||||
png!!.addAll(body)
|
||||
png!!.addAll(to4Bytes(crC32.value.toInt()).toList())
|
||||
}
|
||||
Utils.plte -> {
|
||||
plte = byteArray
|
||||
}
|
||||
Utils.tnrs -> {
|
||||
tnrs = byteArray
|
||||
}
|
||||
Utils.IHDR -> {
|
||||
ihdr.parse(byteArray)
|
||||
maxWidth = ihdr.pngWidth
|
||||
maxHeight = ihdr.pngHeight
|
||||
}
|
||||
}
|
||||
Utils.fdAT -> {
|
||||
// Find the chunk length
|
||||
val bodySize = parseLength(byteArray.copyOfRange(i - 4, i))
|
||||
png!!.addAll(to4Bytes(bodySize - 4).toList())
|
||||
val body = ArrayList<Byte>()
|
||||
body.addAll(byteArrayOf(0x49, 0x44, 0x41, 0x54).toList())
|
||||
// Get image bytes
|
||||
body.addAll(byteArray.copyOfRange(i + 8,i + 4 + bodySize).toList())
|
||||
val crC32 = CRC32()
|
||||
crC32.update(body.toByteArray(), 0, body.size)
|
||||
png!!.addAll(body)
|
||||
png!!.addAll(to4Bytes(crC32.value.toInt()).toList())
|
||||
}
|
||||
Utils.plte -> {
|
||||
plte = byteArray
|
||||
}
|
||||
Utils.tnrs -> {
|
||||
tnrs = byteArray
|
||||
}
|
||||
}
|
||||
} else throw BadCRC()
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ package oupson.apng
|
|||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import oupson.apng.ImageUtils.BitmapDiffCalculator
|
||||
import oupson.apng.ImageUtils.PngEncoder
|
||||
import oupson.apng.ImageUtils.PnnQuantizer
|
||||
import oupson.apng.chunks.IDAT
|
||||
|
@ -37,7 +38,7 @@ class Apng {
|
|||
* @param bitmap The bitamp to add
|
||||
*/
|
||||
fun addFrames(bitmap: Bitmap) {
|
||||
frames.add(Frame(PngEncoder.encode(bitmap, true, 1)))
|
||||
frames.add(Frame(PngEncoder.encode(bitmap, true)))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,7 +47,7 @@ class Apng {
|
|||
* @param delay Delay of the frame
|
||||
*/
|
||||
fun addFrames(bitmap: Bitmap, delay : Float) {
|
||||
frames.add(Frame(PngEncoder.encode(bitmap, true, 1), delay))
|
||||
frames.add(Frame(PngEncoder.encode(bitmap, true), delay))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,7 +58,7 @@ class Apng {
|
|||
* @param blend_op `blend_op` 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.
|
||||
*/
|
||||
fun addFrames(bitmap: Bitmap, delay: Float, dispose_op: Utils.Companion.dispose_op, blend_op: Utils.Companion.blend_op) {
|
||||
frames.add(Frame(PngEncoder.encode(bitmap, true, 1), delay, blend_op, dispose_op))
|
||||
frames.add(Frame(PngEncoder.encode(bitmap, true), delay, blend_op, dispose_op))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,7 +71,7 @@ class Apng {
|
|||
* @param blend_op `blend_op` 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.
|
||||
*/
|
||||
fun addFrames(bitmap: Bitmap, delay: Float, xOffset : Int, yOffset : Int, dispose_op: Utils.Companion.dispose_op, blend_op: Utils.Companion.blend_op) {
|
||||
frames.add(Frame(PngEncoder.encode(bitmap, true, 1), delay, xOffset, yOffset, blend_op, dispose_op))
|
||||
frames.add(Frame(PngEncoder.encode(bitmap, true), delay, xOffset, yOffset, blend_op, dispose_op))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -79,7 +80,7 @@ class Apng {
|
|||
* @param bitmap The bitamp to add
|
||||
*/
|
||||
fun addFrames(index : Int, bitmap: Bitmap) {
|
||||
frames.add(index, Frame(PngEncoder.encode(bitmap, true, 1)))
|
||||
frames.add(index, Frame(PngEncoder.encode(bitmap, true)))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -89,7 +90,7 @@ class Apng {
|
|||
* @param delay Delay of the frame
|
||||
*/
|
||||
fun addFrames(index : Int, bitmap: Bitmap, delay : Float) {
|
||||
frames.add(index, Frame(PngEncoder.encode(bitmap, true, 1), delay))
|
||||
frames.add(index, Frame(PngEncoder.encode(bitmap, true), delay))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -101,7 +102,7 @@ class Apng {
|
|||
* @param blend_op `blend_op` 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.
|
||||
*/
|
||||
fun addFrames(index: Int, bitmap: Bitmap, delay: Float, dispose_op: Utils.Companion.dispose_op, blend_op: Utils.Companion.blend_op) {
|
||||
frames.add(index, Frame(PngEncoder.encode(bitmap, true, 1), delay, blend_op, dispose_op))
|
||||
frames.add(index, Frame(PngEncoder.encode(bitmap, true), delay, blend_op, dispose_op))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -115,7 +116,7 @@ class Apng {
|
|||
* @param blend_op `blend_op` 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.
|
||||
*/
|
||||
fun addFrames(index: Int, bitmap: Bitmap, delay: Float, xOffset : Int, yOffset : Int, dispose_op: Utils.Companion.dispose_op, blend_op: Utils.Companion.blend_op) {
|
||||
frames.add(index, Frame(PngEncoder.encode(bitmap, true, 1), delay, xOffset, yOffset, blend_op, dispose_op))
|
||||
frames.add(index, Frame(PngEncoder.encode(bitmap, true), delay, xOffset, yOffset, blend_op, dispose_op))
|
||||
}
|
||||
|
||||
fun addFrames(frame : Frame) {
|
||||
|
@ -508,4 +509,20 @@ class Apng {
|
|||
}
|
||||
frames = apng.frames
|
||||
}
|
||||
|
||||
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)
|
||||
for (i in 1 until frames.size) {
|
||||
val diffCalculator = BitmapDiffCalculator(drawedFrame[i - 1], drawedFrame[i])
|
||||
frames[i].byteArray = PngEncoder.encode(diffCalculator.res)
|
||||
frames[i].x_offsets = diffCalculator.xOffset
|
||||
frames[i].y_offsets = diffCalculator.yOffset
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ import java.net.URL
|
|||
/**
|
||||
* Class to play APNG
|
||||
*/
|
||||
class ApngAnimator(private val context: Context) {
|
||||
class ApngAnimator(private val context: Context?) {
|
||||
var isPlaying = true
|
||||
private set(value) {
|
||||
field = value
|
||||
|
@ -36,7 +36,7 @@ class ApngAnimator(private val context: Context) {
|
|||
}
|
||||
}
|
||||
private var imageView: ImageView? = null
|
||||
private var anim: CustomAnimationDrawable? = null
|
||||
var anim: CustomAnimationDrawable? = null
|
||||
private var activeAnimation: CustomAnimationDrawable? = null
|
||||
private var doOnLoaded : (ApngAnimator) -> Unit = {}
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
|
@ -47,10 +47,10 @@ class ApngAnimator(private val context: Context) {
|
|||
@SuppressWarnings("WeakerAccess")
|
||||
var loadNotApng = true
|
||||
|
||||
private val sharedPreferences : SharedPreferences = context.getSharedPreferences("apngAnimator", Context.MODE_PRIVATE)
|
||||
private val sharedPreferences : SharedPreferences? = context?.getSharedPreferences("apngAnimator", Context.MODE_PRIVATE)
|
||||
|
||||
init {
|
||||
loadNotApng = sharedPreferences.getBoolean("loadNotApng", true)
|
||||
loadNotApng = sharedPreferences?.getBoolean("loadNotApng", true) ?: true
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -58,9 +58,9 @@ class ApngAnimator(private val context: Context) {
|
|||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
fun loadNotApng(boolean: Boolean) {
|
||||
val editor = sharedPreferences.edit()
|
||||
editor.putBoolean("loadNotApng", boolean)
|
||||
editor.apply()
|
||||
val editor = sharedPreferences?.edit()
|
||||
editor?.putBoolean("loadNotApng", boolean)
|
||||
editor?.apply()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -94,7 +94,7 @@ class ApngAnimator(private val context: Context) {
|
|||
}
|
||||
} else {
|
||||
if (loadNotApng) {
|
||||
context.runOnUiThread {
|
||||
context?.runOnUiThread {
|
||||
imageView?.scaleType = this@ApngAnimator.scaleType ?: ImageView.ScaleType.FIT_CENTER
|
||||
imageView?.setImageBitmap(BitmapFactory.decodeByteArray(bytes, 0, bytes.size))
|
||||
}
|
||||
|
@ -105,6 +105,8 @@ class ApngAnimator(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Load an APNG file and starts playing the animation.
|
||||
* @param uri The uri to load
|
||||
|
@ -113,25 +115,26 @@ class ApngAnimator(private val context: Context) {
|
|||
*/
|
||||
fun load(uri : Uri, speed: Float? = null, apngAnimatorOptions: ApngAnimatorOptions? = null) {
|
||||
doAsync {
|
||||
val bytes = context.contentResolver.openInputStream(uri).readBytes()
|
||||
if (isApng(bytes)) {
|
||||
isApng = true
|
||||
this@ApngAnimator.speed = speed
|
||||
scaleType = apngAnimatorOptions?.scaleType
|
||||
// Download PNG
|
||||
APNGDisassembler.disassemble(bytes).frames.apply {
|
||||
draw(this).apply {
|
||||
setupAnimationDrawableAndStart(this)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (loadNotApng) {
|
||||
context.runOnUiThread {
|
||||
imageView?.scaleType = this@ApngAnimator.scaleType ?: ImageView.ScaleType.FIT_CENTER
|
||||
imageView?.setImageBitmap(BitmapFactory.decodeByteArray(bytes, 0, bytes.size))
|
||||
context?.contentResolver?.openInputStream(uri)?.readBytes()?.let {
|
||||
if (isApng(it)) {
|
||||
isApng = true
|
||||
this@ApngAnimator.speed = speed
|
||||
scaleType = apngAnimatorOptions?.scaleType
|
||||
// Download PNG
|
||||
APNGDisassembler.disassemble(it).frames.apply {
|
||||
draw(this).apply {
|
||||
setupAnimationDrawableAndStart(this)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw NotApngException()
|
||||
if (loadNotApng) {
|
||||
context.runOnUiThread {
|
||||
imageView?.scaleType = this@ApngAnimator.scaleType ?: ImageView.ScaleType.FIT_CENTER
|
||||
imageView?.setImageBitmap(BitmapFactory.decodeByteArray(it, 0, it.size))
|
||||
}
|
||||
} else {
|
||||
throw NotApngException()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -148,7 +151,7 @@ class ApngAnimator(private val context: Context) {
|
|||
doAsync(exceptionHandler = { e -> e.printStackTrace() }) {
|
||||
this@ApngAnimator.speed = speed
|
||||
// Download PNG
|
||||
Loader.load(context, url).apply {
|
||||
Loader.load(context!!, url).apply {
|
||||
if (isApng(this)) {
|
||||
isApng = true
|
||||
this@ApngAnimator.speed = speed
|
||||
|
@ -197,7 +200,7 @@ class ApngAnimator(private val context: Context) {
|
|||
}
|
||||
} else {
|
||||
if (loadNotApng) {
|
||||
context.runOnUiThread {
|
||||
context?.runOnUiThread {
|
||||
imageView?.scaleType = this@ApngAnimator.scaleType ?: ImageView.ScaleType.FIT_CENTER
|
||||
imageView?.setImageBitmap(BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size))
|
||||
}
|
||||
|
@ -223,7 +226,7 @@ class ApngAnimator(private val context: Context) {
|
|||
} else if (File(string).exists()) {
|
||||
var pathToLoad = if (string.startsWith("content://")) string else "file://$string"
|
||||
pathToLoad = pathToLoad.replace("%", "%25").replace("#", "%23")
|
||||
val bytes = context.contentResolver.openInputStream(Uri.parse(pathToLoad)).readBytes()
|
||||
val bytes = context!!.contentResolver.openInputStream(Uri.parse(pathToLoad)).readBytes()
|
||||
if (isApng(bytes)) {
|
||||
load(bytes, speed, apngAnimatorOptions)
|
||||
} else {
|
||||
|
@ -261,7 +264,7 @@ class ApngAnimator(private val context: Context) {
|
|||
/**
|
||||
* Draw frames
|
||||
*/
|
||||
private fun draw(extractedFrame: ArrayList<Frame>) : ArrayList<Bitmap> {
|
||||
fun draw(extractedFrame: ArrayList<Frame>) : ArrayList<Bitmap> {
|
||||
val generatedFrame = ArrayList<Bitmap>()
|
||||
// Set last frame
|
||||
duration = ArrayList()
|
||||
|
|
|
@ -3,10 +3,10 @@ package oupson.apng
|
|||
import android.graphics.drawable.AnimationDrawable
|
||||
|
||||
/**
|
||||
* Extension of the [AnimationDrawable] that provides an [AnimationListener]. This will allow
|
||||
* Extension of the [AnimationDrawable] that provides an animationListener This will allow
|
||||
* for the caller to listen for specific animation related events.
|
||||
*/
|
||||
internal class CustomAnimationDrawable : AnimationDrawable() {
|
||||
class CustomAnimationDrawable : AnimationDrawable() {
|
||||
private var onAnimationLoop : () -> Unit = {}
|
||||
|
||||
fun setOnAnimationLoopListener( f : () -> Unit) {
|
||||
|
|
|
@ -4,11 +4,13 @@ import android.graphics.Bitmap
|
|||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import oupson.apng.utils.Utils
|
||||
|
||||
class BitmapDiffCalculator(val firstBitmap: Bitmap, val secondBitmap : Bitmap) {
|
||||
class BitmapDiffCalculator(firstBitmap: Bitmap, secondBitmap : Bitmap) {
|
||||
val res : Bitmap
|
||||
var xOffset : Int = 0
|
||||
var yOffset : Int = 0
|
||||
var blend_op = Utils.Companion.blend_op.APNG_BLEND_OP_OVER
|
||||
init {
|
||||
val difBitmap = Bitmap.createBitmap(firstBitmap.width, firstBitmap.height, Bitmap.Config.ARGB_8888)
|
||||
val difCanvas = Canvas(difBitmap)
|
||||
|
@ -35,7 +37,9 @@ class BitmapDiffCalculator(val firstBitmap: Bitmap, val secondBitmap : Bitmap) {
|
|||
}
|
||||
bottomLoop@ while (true) {
|
||||
for (x in 0 until difBitmap.width) {
|
||||
if (difBitmap.getPixel(x, height - 1) != Color.TRANSPARENT) {
|
||||
if (height - 1 < 0) {
|
||||
break@bottomLoop
|
||||
} else if (difBitmap.getPixel(x, height - 1) != Color.TRANSPARENT) {
|
||||
break@bottomLoop
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,14 +7,13 @@ class IDAT : Chunk {
|
|||
override var body = byteArrayOf()
|
||||
|
||||
override fun parse(byteArray: ByteArray) {
|
||||
for (i in 0 until byteArray.size) {
|
||||
// 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 = parseLength(byteArray.copyOfRange(i - 4, i))
|
||||
// Get image bytes
|
||||
IDATBody.add(byteArray.copyOfRange(i + 4, i + 4 + bodySize))
|
||||
}
|
||||
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 = parseLength(byteArray.copyOfRange(i - 4, i))
|
||||
// Get image bytes
|
||||
IDATBody.add(byteArray.copyOfRange(i + 4, i + 4 + bodySize))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,36 +23,35 @@ class fcTL : Chunk {
|
|||
var dispose_op : Utils.Companion.dispose_op = Utils.Companion.dispose_op.APNG_DISPOSE_OP_NONE
|
||||
|
||||
override fun parse(byteArray: ByteArray) {
|
||||
for (i in 0 until byteArray.size) {
|
||||
// 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
|
||||
var bodySize = parseLength(byteArray.copyOfRange(i - 4, 1))
|
||||
// Get the width of the png
|
||||
pngWidth = parseLength(byteArray.copyOfRange(i + 8, i + 12))
|
||||
// Get the height of the png
|
||||
pngHeight = parseLength(byteArray.copyOfRange(i + 12, i + 16))
|
||||
/*
|
||||
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 = parseLength(byteArray.copyOfRange(i - 4, 1))
|
||||
// Get the width of the png
|
||||
pngWidth = parseLength(byteArray.copyOfRange(i + 8, i + 12))
|
||||
// Get the height of the png
|
||||
pngHeight = parseLength(byteArray.copyOfRange(i + 12, i + 16))
|
||||
/*
|
||||
* 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 delay_num = parseLength(byteArray.copyOfRange(i + 24, i + 26)).toFloat()
|
||||
// Get delay denominator
|
||||
var delay_den = parseLength(byteArray.copyOfRange(i + 26, i + 28)).toFloat()
|
||||
// Get delay numerator
|
||||
val delay_num = parseLength(byteArray.copyOfRange(i + 24, i + 26)).toFloat()
|
||||
// Get delay denominator
|
||||
var delay_den = parseLength(byteArray.copyOfRange(i + 26, i + 28)).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 (delay_den == 0f) {
|
||||
delay_den = 100f
|
||||
}
|
||||
delay = (delay_num / delay_den * 1000)
|
||||
// Get x and y offsets
|
||||
x_offset = parseLength(byteArray.copyOfRange(i + 16, i+ 20))
|
||||
y_offset = parseLength( byteArray.copyOfRange(i + 20, i+ 24))
|
||||
body = byteArray.copyOfRange(i +4, i + bodySize + 4)
|
||||
blend_op = getBlend_op(String.format("%02x", byteArray[33]).toLong(16).toInt())
|
||||
dispose_op = getDispose_op(String.format("%02x", byteArray[32]).toLong(16).toInt())
|
||||
// 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 (delay_den == 0f) {
|
||||
delay_den = 100f
|
||||
}
|
||||
delay = (delay_num / delay_den * 1000)
|
||||
// Get x and y offsets
|
||||
x_offset = parseLength(byteArray.copyOfRange(i + 16, i + 20))
|
||||
y_offset = parseLength(byteArray.copyOfRange(i + 20, i + 24))
|
||||
body = byteArray.copyOfRange(i + 4, i + bodySize + 4)
|
||||
blend_op = getBlend_op(String.format("%02x", byteArray[33]).toLong(16).toInt())
|
||||
dispose_op = getDispose_op(String.format("%02x", byteArray[32]).toLong(16).toInt())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,4 +3,5 @@ package oupson.apng.exceptions
|
|||
class NoFrameException : Exception()
|
||||
class NotPngException : Exception()
|
||||
class NotApngException : Exception()
|
||||
class NoFcTL : Exception()
|
||||
class NoFcTL : Exception()
|
||||
class BadCRC : Exception()
|
|
@ -143,5 +143,6 @@ class Utils {
|
|||
val fdAT = Arrays.toString(byteArrayOf(0x66, 0x64, 0x41, 0x54))
|
||||
val plte = Arrays.toString(byteArrayOf(0x50, 0x4c, 0x54, 0x45))
|
||||
val tnrs = Arrays.toString(byteArrayOf(0x74, 0x52, 0x4e, 0x53))
|
||||
val IHDR = Arrays.toString(byteArrayOf(0x49, 0x48, 0x44, 0x52))
|
||||
}
|
||||
}
|
|
@ -35,7 +35,6 @@ dependencies {
|
|||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
|
||||
//noinspection GradleCompatible
|
||||
implementation 'com.android.support:appcompat-v7:27.1.1'
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||
|
@ -48,6 +47,7 @@ dependencies {
|
|||
// implementation fileTree(include: ['*.aar'], dir: 'libs')
|
||||
//implementation 'com.github.oupson:Kapng-Android:1.0.0'
|
||||
implementation 'com.android.support:design:27.1.1' // where X.X.X version
|
||||
implementation 'asia.ivity.android:drag-sort-listview:1.0'
|
||||
}
|
||||
kotlin {
|
||||
experimental {
|
||||
|
|
|
@ -8,13 +8,13 @@
|
|||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:hardwareAccelerated="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:largeHeap="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
android:hardwareAccelerated="false"
|
||||
android:largeHeap="true">
|
||||
android:theme="@style/AppTheme">
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
@ -22,17 +22,19 @@
|
|||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".Main2Activity"
|
||||
<activity
|
||||
android:name=".Main2Activity"
|
||||
android:label="APNG Viewer">
|
||||
<intent-filter tools:ignore="AppLinkUrlError">
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="image/png"/>
|
||||
<data android:mimeType="image/png" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".CreatorActivity"></activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,96 @@
|
|||
package oupson.apngcreator
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Canvas
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.util.Log
|
||||
import kotlinx.android.synthetic.main.activity_creator.*
|
||||
import org.jetbrains.anko.alert
|
||||
import org.jetbrains.anko.customView
|
||||
import org.jetbrains.anko.imageView
|
||||
import org.jetbrains.anko.sdk27.coroutines.onClick
|
||||
import oupson.apng.APNGDisassembler
|
||||
import oupson.apng.Apng
|
||||
import oupson.apng.ApngAnimator
|
||||
import oupson.apng.ImageUtils.PngEncoder
|
||||
import oupson.apngcreator.adapter.frameListViewAdapter
|
||||
import java.io.File
|
||||
|
||||
|
||||
class CreatorActivity : AppCompatActivity() {
|
||||
var items : ArrayList<Bitmap> = ArrayList()
|
||||
var bitmapAdapter : frameListViewAdapter? = null
|
||||
val PICK_IMAGE = 999
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_creator)
|
||||
fab_add_frame.onClick {
|
||||
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, PICK_IMAGE)
|
||||
}
|
||||
fab_create.onClick {
|
||||
var apngCreated = Apng()
|
||||
|
||||
items.forEach {
|
||||
apngCreated.addFrames(it)
|
||||
}
|
||||
|
||||
|
||||
Log.e("tag", apngCreated.frames.size.toString())
|
||||
apngCreated = APNGDisassembler.disassemble(apngCreated.toByteArray())
|
||||
apngCreated.optimiseFrame()
|
||||
File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), "vtm").writeBytes(apngCreated.toByteArray())
|
||||
val a = ApngAnimator(applicationContext)
|
||||
|
||||
|
||||
a.load(apngCreated.toByteArray())
|
||||
a.onLoaded {
|
||||
alert {
|
||||
customView {
|
||||
imageView {
|
||||
Log.e("tag", "${it.anim?.numberOfFrames.toString()} : ${items.size}")
|
||||
it.anim?.let {
|
||||
for (i in 0 until it.numberOfFrames) {
|
||||
val vt = Bitmap.createBitmap(it.getFrame(i).intrinsicWidth, it.getFrame(i).intrinsicHeight, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(vt)
|
||||
it.getFrame(i).draw(canvas)
|
||||
File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), "frame$i.png").writeBytes(PngEncoder.encode(vt))
|
||||
}
|
||||
}
|
||||
this.setImageDrawable(it.anim)
|
||||
}
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
bitmapAdapter = frameListViewAdapter(this, items)
|
||||
dragView.adapter = bitmapAdapter
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
when(requestCode) {
|
||||
PICK_IMAGE -> {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
contentResolver.openInputStream(data?.data).readBytes().apply {
|
||||
items.add(BitmapFactory.decodeByteArray(this, 0, this.size))
|
||||
bitmapAdapter?.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +1,24 @@
|
|||
package oupson.apngcreator
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.widget.SeekBar
|
||||
import com.squareup.picasso.Picasso
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import oupson.apng.ApngAnimator
|
||||
import java.util.*
|
||||
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
lateinit var animator: ApngAnimator
|
||||
|
||||
//val imageUrl = "https://metagif.files.wordpress.com/2015/01/bugbuckbunny.png"
|
||||
//val imageUrl = "http://orig06.deviantart.net/7812/f/2012/233/7/5/twilight_rapidash_shaded_and_animated_by_tamalesyatole-d5bz7hd.png"
|
||||
val imageUrl = "https://raw.githubusercontent.com/tinify/iMessage-Panda-sticker/master/StickerPackExtension/Stickers.xcstickers/Sticker%20Pack.stickerpack/panda.sticker/panda.png"
|
||||
// val imageUrl = "http://oupson.oupsman.fr/apng/bigApng.png"
|
||||
// val imageUrl = "https://metagif.files.wordpress.com/2015/01/bugbuckbunny.png"
|
||||
val imageUrl = "http://orig06.deviantart.net/7812/f/2012/233/7/5/twilight_rapidash_shaded_and_animated_by_tamalesyatole-d5bz7hd.png"
|
||||
// val imageUrl = "https://raw.githubusercontent.com/tinify/iMessage-Panda-sticker/master/StickerPackExtension/Stickers.xcstickers/Sticker%20Pack.stickerpack/panda.sticker/panda.png"
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
@ -53,4 +56,20 @@ class MainActivity : AppCompatActivity() {
|
|||
animator.pause()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
menuInflater.inflate(R.menu.main_menu, menu)
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
|
||||
when(item?.itemId) {
|
||||
R.id.action_open_create_activity -> {
|
||||
val intent = Intent(this, CreatorActivity::class.java)
|
||||
startActivity(intent)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package oupson.apngcreator.adapter
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.ImageView
|
||||
import oupson.apngcreator.R
|
||||
|
||||
class frameListViewAdapter (context: Context, val bitmaps: List<Bitmap>) : ArrayAdapter<Bitmap>(context, 0, bitmaps) {
|
||||
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.framelistviewadapterlayout, parent, false)
|
||||
|
||||
val imageView = view.findViewById<ImageView>(R.id.frameAdapterImageView)
|
||||
imageView.setImageBitmap(bitmaps[position])
|
||||
return view
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M8,5v14l11,-7z"/>
|
||||
</vector>
|
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".CreatorActivity">
|
||||
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
android:id="@+id/fab_create"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:clickable="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:srcCompat="@drawable/ic_play_arrow_white_24dp" />
|
||||
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
android:id="@+id/fab_add_frame"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:clickable="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:srcCompat="@drawable/ic_play_arrow_white_24dp" />
|
||||
|
||||
<ListView
|
||||
android:id="@+id/dragView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</android.support.constraint.ConstraintLayout>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<ImageView android:id="@+id/frameAdapterImageView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
</RelativeLayout>
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item android:id="@+id/action_open_create_activity"
|
||||
android:title="Create apng"
|
||||
app:showAsAction="never"/>
|
||||
</menu>
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<item type="id" name="drag_handle" />
|
||||
</resources>
|
Loading…
Reference in New Issue