リファクタ

This commit is contained in:
tateisu 2018-02-01 18:30:46 +09:00
parent 84383661d8
commit 8c46295631
20 changed files with 414 additions and 435 deletions

View File

@ -4,7 +4,6 @@ package jp.juggler.apng
import jp.juggler.apng.util.ByteSequence
class ApngAnimationControl internal constructor(src : ByteSequence) {
companion object {

View File

@ -20,12 +20,14 @@ class ApngBackground internal constructor(colorType: ColorType, src: ByteSequenc
blue = v
index = - 1
}
ColorType.RGB, ColorType.RGBA -> {
red = src.readUInt16()
green = src.readUInt16()
blue = src.readUInt16()
index = - 1
}
ColorType.INDEX -> {
red = - 1
green = - 1

View File

@ -11,7 +11,7 @@ class ApngBitmap(var width : Int, var height : Int) {
fun reset(width : Int, height : Int) {
val newSize = width * height
if(newSize > colors.size)
throw ParseError("can't resize to $width x $height , it's greater than initial size")
throw ApngParseError("can't resize to $width x $height , it's greater than initial size")
this.width = width
this.height = height
// 透明な黒で初期化する
@ -24,11 +24,7 @@ class ApngBitmap(var width : Int, var height : Int) {
private var pos : Int = 0
var step : Int = 1
fun setPixel(argb : Int) : Pointer {
// if( pos == width) println("setPixel 0x%x".format(argb))
colors[pos] = argb
return this
}
fun setPixel(argb : Int) = apply { colors[pos] = argb }
fun setPixel(a : Int, r : Int, g : Int, b : Int) = setPixel(
((a and 255) shl 24) or
@ -37,18 +33,14 @@ class ApngBitmap(var width : Int, var height : Int) {
(b and 255)
)
fun setOffset(pos : Int = 0, step : Int = 1) : Pointer {
fun setOffset(pos : Int = 0, step : Int = 1) = apply {
this.pos = pos
this.step = step
return this
}
fun setXY(x : Int, y : Int, step : Int = 1) = setOffset(x + y * width, step)
fun plus(x : Int) : Pointer {
pos += x
return this
}
fun plus(x : Int) = apply { pos += x }
fun next() = plus(step)

View File

@ -23,17 +23,17 @@ internal class ApngChunk(crc32:CRC32,tokenizer: StreamTokenizer) {
crc32.update(bytes, 0, size)
val crcActual = crc32.value
if (crcActual != crcExpect) throw ParseError("CRC not match.")
if(crcActual != crcExpect) throw ApngParseError("CRC not match.")
return bytes
}
fun skipBody(tokenizer: StreamTokenizer) {
fun skipBody(tokenizer : StreamTokenizer) =
tokenizer.skipBytes((size + 4).toLong())
}
fun checkCRC(tokenizer : StreamTokenizer, crcActual : Long) {
val crcExpect = tokenizer.readUInt32()
if (crcActual != crcExpect) throw ParseError("CRC not match.")
if(crcActual != crcExpect) throw ApngParseError("CRC not match.")
}
}

View File

@ -11,22 +11,22 @@ object ApngDecoder {
private val PNG_SIGNATURE = byteArrayOf(0x89.toByte(), 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0a)
fun parseStream(
_inStream : InputStream,
inStream : InputStream,
callback : ApngDecoderCallback
) {
val apng = Apng()
val tokenizer = StreamTokenizer(_inStream)
val tokenizer = StreamTokenizer(inStream)
val pngHeader = tokenizer.readBytes(8)
if(! pngHeader.contentEquals(PNG_SIGNATURE)) {
throw ParseError("header not match")
throw ApngParseError("header not match")
}
var lastSequenceNumber : Int? = null
fun checkSequenceNumber(n : Int) {
val last = lastSequenceNumber
if(last != null && n <= last) {
throw ParseError("incorrect sequenceNumber. last=$lastSequenceNumber,current=$n")
throw ApngParseError("incorrect sequenceNumber. last=$lastSequenceNumber,current=$n")
}
lastSequenceNumber = n
}
@ -56,7 +56,7 @@ object ApngDecoder {
"PLTE" -> apng.palette = ApngPalette(chunk.readBody(crc32, tokenizer))
"bKGD" -> {
val header = apng.header ?: throw ParseError("missing IHDR")
val header = apng.header ?: throw ApngParseError("missing IHDR")
apng.background = ApngBackground(
header.colorType,
ByteSequence(chunk.readBody(crc32, tokenizer))
@ -64,7 +64,7 @@ object ApngDecoder {
}
"tRNS" -> {
val header = apng.header ?: throw ParseError("missing IHDR")
val header = apng.header ?: throw ApngParseError("missing IHDR")
val body = chunk.readBody(crc32, tokenizer)
when(header.colorType) {
ColorType.GREY -> apng.transparentColor =
@ -72,15 +72,15 @@ object ApngDecoder {
ColorType.RGB -> apng.transparentColor =
ApngTransparentColor(false, ByteSequence(body))
ColorType.INDEX -> apng.palette?.parseTRNS(body)
?: throw ParseError("missing palette")
?: throw ApngParseError("missing palette")
else -> callback.onApngWarning("tRNS ignored. colorType =${header.colorType}")
}
}
"IDAT" -> {
val header = apng.header ?: throw ParseError("missing IHDR")
val header = apng.header ?: throw ApngParseError("missing IHDR")
if(idatDecoder == null) {
bitmap ?: throw ParseError("missing bitmap")
bitmap ?: throw ApngParseError("missing bitmap")
bitmap.reset(header.width, header.height)
idatDecoder = IdatDecoder(
apng,
@ -106,7 +106,7 @@ object ApngDecoder {
}
"acTL" -> {
val header = apng.header ?: throw ParseError("missing IHDR")
val header = apng.header ?: throw ApngParseError("missing IHDR")
val animationControl =
ApngAnimationControl(ByteSequence(chunk.readBody(crc32, tokenizer)))
apng.animationControl = animationControl
@ -121,9 +121,9 @@ object ApngDecoder {
}
"fdAT" -> {
val fctl = lastFctl ?: throw ParseError("missing fCTL before fdAT")
val fctl = lastFctl ?: throw ApngParseError("missing fCTL before fdAT")
if(fdatDecoder == null) {
bitmap ?: throw ParseError("missing bitmap")
bitmap ?: throw ApngParseError("missing bitmap")
bitmap.reset(fctl.width, fctl.height)
fdatDecoder = IdatDecoder(
apng,

View File

@ -4,7 +4,6 @@ package jp.juggler.apng
import jp.juggler.apng.util.ByteSequence
class ApngFrameControl internal constructor(src : ByteSequence) {
val width : Int
@ -33,7 +32,8 @@ class ApngFrameControl internal constructor(src: ByteSequence) {
blendOp = BlendOp.values().first { it.num == num }
}
override fun toString() ="ApngFrameControl(width=$width,height=$height,x=$xOffset,y=$yOffset,delayNum=$delayNum,delayDen=$delayDen,disposeOp=$disposeOp,blendOp=$blendOp)"
override fun toString() =
"ApngFrameControl(width=$width,height=$height,x=$xOffset,y=$yOffset,delayNum=$delayNum,delayDen=$delayDen,disposeOp=$disposeOp,blendOp=$blendOp)"
val delayMilliseconds : Long
get() = when(delayDen) {

View File

@ -6,6 +6,7 @@ import jp.juggler.apng.util.ByteSequence
// information from IHDR chunk.
class ApngImageHeader internal constructor(src : ByteSequence) {
val width : Int
val height : Int
val bitDepth : Int
@ -18,7 +19,7 @@ class ApngImageHeader internal constructor(src: ByteSequence) {
width = src.readInt32()
height = src.readInt32()
if(width <=0 || height <=0 ) throw ParseError("w=$width,h=$height is too small")
if(width <= 0 || height <= 0) throw ApngParseError("w=$width,h=$height is too small")
bitDepth = src.readUInt8()
@ -37,5 +38,6 @@ class ApngImageHeader internal constructor(src: ByteSequence) {
interlaceMethod = InterlaceMethod.values().first { it.num == num }
}
override fun toString() = "ApngImageHeader(w=$width,h=$height,bits=$bitDepth,color=$colorType,interlace=$interlaceMethod)"
override fun toString() =
"ApngImageHeader(w=$width,h=$height,bits=$bitDepth,color=$colorType,interlace=$interlaceMethod)"
}

View File

@ -7,6 +7,7 @@ import jp.juggler.apng.util.getUInt8
class ApngPalette(
src : ByteArray // repeat of R,G,B
) {
companion object {
// full opaque black
const val OPAQUE = 255 shl 24

View File

@ -0,0 +1,3 @@
package jp.juggler.apng
class ApngParseError(message: String) : IllegalArgumentException(message)

View File

@ -8,6 +8,7 @@ class ApngTransparentColor internal constructor(isGreyScale:Boolean, src: ByteSe
val red : Int
val green : Int
val blue : Int
init {
if(isGreyScale) {
val v = src.readUInt16()

View File

@ -4,7 +4,6 @@ package jp.juggler.apng
import jp.juggler.apng.util.*
import java.io.InputStream
import java.util.*
import java.util.zip.CRC32
import java.util.zip.Inflater
@ -33,7 +32,6 @@ internal class IdatDecoder(
private val dummyPaletteData = IntArray(0)
private fun abs(v : Int) = if(v >= 0) v else - v
// a = left, b = above, c = upper left
@ -51,8 +49,9 @@ internal class IdatDecoder(
private inline fun scanLine1(baLine : ByteArray, pass_w : Int, block : (v : Int) -> Unit) {
var pos = 1
var x = 0
while(pass_w - x >= 8) {
var remain = pass_w
while(remain >= 8) {
remain -= 8
val v = baLine[pos ++].toInt()
block((v shr 7) and 1)
block((v shr 6) and 1)
@ -62,9 +61,7 @@ internal class IdatDecoder(
block((v shr 2) and 1)
block((v shr 1) and 1)
block(v and 1)
x += 8
}
val remain = pass_w - x
if(remain > 0) {
val v = baLine[pos].toInt()
block((v shr 7) and 1)
@ -79,16 +76,15 @@ internal class IdatDecoder(
private inline fun scanLine2(baLine : ByteArray, pass_w : Int, block : (v : Int) -> Unit) {
var pos = 1
var x = 0
while(pass_w - x >= 4) {
var remain = pass_w
while(remain >= 4) {
remain -= 4
val v = baLine[pos ++].toInt()
block((v shr 6) and 3)
block((v shr 4) and 3)
block((v shr 2) and 3)
block(v and 3)
x += 4
}
val remain = pass_w - x
if(remain > 0) {
val v = baLine[pos].toInt()
block((v shr 6) and 3)
@ -99,14 +95,13 @@ internal class IdatDecoder(
private inline fun scanLine4(baLine : ByteArray, pass_w : Int, block : (v : Int) -> Unit) {
var pos = 1
var x = 0
while(pass_w - x >= 2) {
var remain = pass_w
while(remain >= 2) {
remain -= 2
val v = baLine[pos ++].toInt()
block((v shr 4) and 15)
block(v and 15)
x += 2
}
val remain = pass_w - x
if(remain > 0) {
val v = baLine[pos].toInt()
block((v shr 4) and 15)
@ -115,14 +110,16 @@ internal class IdatDecoder(
private inline fun scanLine8(baLine : ByteArray, pass_w : Int, block : (v : Int) -> Unit) {
var pos = 1
for(x in 0 until pass_w) {
var remain = pass_w
while(remain -- > 0) {
block(baLine.getUInt8(pos ++))
}
}
private inline fun scanLine16(baLine : ByteArray, pass_w : Int, block : (v : Int) -> Unit) {
var pos = 1
for(x in 0 until pass_w) {
var remain = pass_w
while(remain -- > 0) {
block(baLine.getUInt16(pos))
pos += 2
}
@ -134,7 +131,8 @@ internal class IdatDecoder(
block : (r : Int, g : Int, b : Int) -> Unit
) {
var pos = 1
for(x in 0 until pass_w) {
var remain = pass_w
while(remain -- > 0) {
block(
baLine.getUInt8(pos),
baLine.getUInt8(pos + 1),
@ -150,7 +148,8 @@ internal class IdatDecoder(
block : (r : Int, g : Int, b : Int) -> Unit
) {
var pos = 1
for(x in 0 until pass_w) {
var remain = pass_w
while(remain -- > 0) {
block(
baLine.getUInt16(pos),
baLine.getUInt16(pos + 2),
@ -166,7 +165,8 @@ internal class IdatDecoder(
block : (r : Int, g : Int, b : Int, a : Int) -> Unit
) {
var pos = 1
for(x in 0 until pass_w) {
var remain = pass_w
while(remain -- > 0) {
block(
baLine.getUInt8(pos),
baLine.getUInt8(pos + 1),
@ -183,7 +183,8 @@ internal class IdatDecoder(
block : (r : Int, g : Int, b : Int, a : Int) -> Unit
) {
var pos = 1
for(x in 0 until pass_w) {
var remain = pass_w
while(remain -- > 0) {
block(
baLine.getUInt16(pos),
baLine.getUInt16(pos + 2),
@ -200,7 +201,8 @@ internal class IdatDecoder(
block : (g : Int, a : Int) -> Unit
) {
var pos = 1
for(x in 0 until pass_w) {
var remain = pass_w
while(remain -- > 0) {
block(
baLine.getUInt8(pos),
baLine.getUInt8(pos + 1)
@ -215,7 +217,8 @@ internal class IdatDecoder(
block : (g : Int, a : Int) -> Unit
) {
var pos = 1
for(x in 0 until pass_w) {
var remain = pass_w
while(remain -- > 0) {
block(
baLine.getUInt16(pos),
baLine.getUInt16(pos + 2)
@ -233,7 +236,7 @@ internal class IdatDecoder(
private val sampleBits : Int
private val sampleBytes : Int
private val scanLineBytesMax : Int
private val linePool = LinkedList<ByteArray>()
private val scanLinePool : BufferPool
private val transparentCheckerGrey : (v : Int) -> Int
private val transparentCheckerRGB : (r : Int, g : Int, b : Int) -> Int
private val renderScanLineFunc : (baLine : ByteArray) -> Unit
@ -250,15 +253,8 @@ internal class IdatDecoder(
init {
val header = requireNotNull(apng.header)
this.colorType = header.colorType
this.bitDepth = header.bitDepth
this.paletteData = if(colorType == ColorType.INDEX) {
apng.palette?.list
?: throw ParseError("missing ApngPalette for index color")
} else {
dummyPaletteData
}
colorType = header.colorType
bitDepth = header.bitDepth
sampleBits = when(colorType) {
ColorType.GREY, ColorType.INDEX -> bitDepth
@ -269,9 +265,14 @@ internal class IdatDecoder(
sampleBytes = (sampleBits + 7) / 8
scanLineBytesMax = 1 + (bitmap.width * sampleBits + 7) / 8
scanLinePool = BufferPool(scanLineBytesMax)
linePool.add(ByteArray(scanLineBytesMax))
linePool.add(ByteArray(scanLineBytesMax))
paletteData = if(colorType == ColorType.INDEX) {
apng.palette?.list
?: throw ApngParseError("missing ApngPalette for index color")
} else {
dummyPaletteData
}
val transparentColor = apng.transparentColor
@ -400,7 +401,7 @@ internal class IdatDecoder(
}
private fun colorBitsNotSupported() : Nothing {
throw ParseError("bitDepth $bitDepth is not supported for $colorType")
throw ApngParseError("bitDepth $bitDepth is not supported for $colorType")
}
private fun selectRenderFunc() = when(colorType) {
@ -443,7 +444,7 @@ internal class IdatDecoder(
passY = 0
scanLineBytes = 1 + (passWidth * sampleBits + 7) / 8
baPreviousLine?.let { linePool.add(it) }
scanLinePool.recycle(baPreviousLine)
baPreviousLine = null
if(passWidth <= 0 || passHeight <= 0) {
@ -470,7 +471,7 @@ internal class IdatDecoder(
return false
}
val baLine = linePool.removeFirst()
val baLine = scanLinePool.obtain()
inflateBufferQueue.readBytes(baLine, 0, scanLineBytes)
val filterNum = baLine.getUInt8(0)
@ -488,7 +489,6 @@ internal class IdatDecoder(
val leftPos = pos - sampleBytes
val vLeft = if(leftPos <= 0) 0 else baLine.getUInt8(leftPos)
baLine[pos] = (vCur + vLeft).toByte()
// if( callback.canApngDebug() ){
@ -550,7 +550,7 @@ internal class IdatDecoder(
renderScanLineFunc(baLine)
// save previous line
baPreviousLine?.let { linePool.add(it) }
scanLinePool.recycle(baPreviousLine)
baPreviousLine = baLine
if(++ passY >= passHeight) {

View File

@ -1,3 +0,0 @@
package jp.juggler.apng
class ParseError(message: String) : IllegalArgumentException(message)

View File

@ -4,12 +4,6 @@ import java.util.*
internal class BufferPool(private val arraySize : Int) {
private val list = LinkedList<ByteArray>()
fun recycle(array: ByteArray) {
list.add( array)
}
fun obtain(): ByteArray {
return if( list.isEmpty() ) ByteArray(arraySize) else list.removeFirst()
}
fun obtain() : ByteArray = if(list.isEmpty()) ByteArray(arraySize) else list.removeFirst()
fun recycle(array : ByteArray?) = array?.let { list.add(it) }
}

View File

@ -1,6 +1,6 @@
package jp.juggler.apng.util
import jp.juggler.apng.ParseError
import jp.juggler.apng.ApngParseError
internal fun ByteArray.getUInt8(pos : Int) = get(pos).toInt() and 255
@ -16,10 +16,11 @@ internal class ByteSequence(
var offset : Int,
var length : Int
) {
constructor(ba : ByteArray) : this(ba, 0, ba.size)
private inline fun <T> readX(dataSize : Int, block : () -> T) : T {
if(length < dataSize) throw ParseError("readX: unexpected end")
if(length < dataSize) throw ApngParseError("readX: unexpected end")
val v = block()
offset += dataSize
length -= dataSize

View File

@ -9,16 +9,9 @@ internal class ByteSequenceQueue(private val bufferRecycler :(ByteSequence)->Uni
val remain : Int
get() = list.sumBy { it.length }
fun add(range: ByteSequence) {
list.add(range)
}
fun add(range : ByteSequence) =list.add(range)
fun clear() {
for( item in list ){
bufferRecycler(item)
}
list.clear()
}
fun clear() = list.also{ it.forEach(bufferRecycler) }.clear()
fun readBytes(dst : ByteArray, offset : Int, length : Int) : Int {
var dstOffset = offset

View File

@ -1,6 +1,6 @@
package jp.juggler.apng.util
import jp.juggler.apng.ParseError
import jp.juggler.apng.ApngParseError
import java.io.InputStream
import java.util.zip.CRC32
@ -12,7 +12,7 @@ internal class StreamTokenizer(val inStream: InputStream) {
val remain = size - nRead
if(remain <= 0) break
val delta = inStream.skip(size - nRead)
if (delta <= 0) throw ParseError("skipBytes: unexpected EoS")
if(delta <= 0) throw ApngParseError("skipBytes: unexpected EoS")
nRead += delta
}
}
@ -24,7 +24,7 @@ internal class StreamTokenizer(val inStream: InputStream) {
val remain = size - nRead
if(remain <= 0) break
val delta = inStream.read(dst, nRead, size - nRead)
if (delta < 0) throw ParseError("readBytes: unexpected EoS")
if(delta < 0) throw ApngParseError("readBytes: unexpected EoS")
nRead += delta
}
return dst
@ -32,7 +32,7 @@ internal class StreamTokenizer(val inStream: InputStream) {
private fun readByte() : Int {
val b = inStream.read()
if( b == -1 ) throw ParseError("readBytes: unexpected EoS")
if(b == - 1) throw ApngParseError("readByte: unexpected EoS")
return b and 0xff
}

View File

@ -24,11 +24,9 @@ class ApngFrames private constructor(
private const val DELAY_AFTER_END = 3000L
// アニメーションフレームの描画に使う
private val sPaintDontBlend : Paint by lazy {
val paint = Paint()
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC)
paint.isFilterBitmap = true
paint
private val sPaintDontBlend = Paint().apply {
xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC)
isFilterBitmap = true
}
private fun createBlankBitmap(w : Int, h : Int) =
@ -45,7 +43,7 @@ class ApngFrames private constructor(
val wSrc = src.width
val hSrc = src.height
if(size_max <= 0 || wSrc <= size_max && hSrc <= size_max) {
if(size_max <= 0 || (wSrc <= size_max && hSrc <= size_max)) {
return if(recycleSrc) {
src
} else {
@ -75,8 +73,8 @@ class ApngFrames private constructor(
return b2
}
private fun toAndroidBitmap(src : ApngBitmap) : Bitmap {
return Bitmap.createBitmap(
private fun toAndroidBitmap(src : ApngBitmap) =
Bitmap.createBitmap(
src.colors, // int[] 配列
0, // offset
src.width, //stride
@ -84,7 +82,6 @@ class ApngFrames private constructor(
src.height, //height
Bitmap.Config.ARGB_8888
)
}
private fun toAndroidBitmap(src : ApngBitmap, size_max : Int) =
scaleBitmap(size_max, toAndroidBitmap(src))
@ -99,11 +96,8 @@ class ApngFrames private constructor(
try {
ApngDecoder.parseStream(inStream, result)
result.onParseComplete()
return if(result.defaultImage != null || result.frames?.isNotEmpty() == true) {
result
} else {
throw RuntimeException("APNG has no image")
}
return result.takeIf { result.defaultImage != null || result.frames?.isNotEmpty() == true }
?: throw RuntimeException("APNG has no image")
} catch(ex : Throwable) {
result.dispose()
throw ex