リファクタ
This commit is contained in:
parent
84383661d8
commit
8c46295631
|
@ -1,9 +1,9 @@
|
||||||
package jp.juggler.apng
|
package jp.juggler.apng
|
||||||
|
|
||||||
class Apng {
|
class Apng {
|
||||||
var header: ApngImageHeader? = null
|
var header : ApngImageHeader? = null
|
||||||
var background: ApngBackground? = null
|
var background : ApngBackground? = null
|
||||||
var animationControl: ApngAnimationControl? = null
|
var animationControl : ApngAnimationControl? = null
|
||||||
internal var palette: ApngPalette? = null
|
internal var palette : ApngPalette? = null
|
||||||
internal var transparentColor: ApngTransparentColor? = null
|
internal var transparentColor : ApngTransparentColor? = null
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,29 +4,28 @@ package jp.juggler.apng
|
||||||
|
|
||||||
import jp.juggler.apng.util.ByteSequence
|
import jp.juggler.apng.util.ByteSequence
|
||||||
|
|
||||||
|
class ApngAnimationControl internal constructor(src : ByteSequence) {
|
||||||
class ApngAnimationControl internal constructor(src: ByteSequence) {
|
|
||||||
|
companion object {
|
||||||
companion object {
|
const val PLAY_INDEFINITELY = 0
|
||||||
const val PLAY_INDEFINITELY =0
|
}
|
||||||
}
|
|
||||||
|
// This must equal the number of `fcTL` chunks.
|
||||||
// This must equal the number of `fcTL` chunks.
|
// 0 is not a valid value.
|
||||||
// 0 is not a valid value.
|
// 1 is a valid value for a single-frame APNG.
|
||||||
// 1 is a valid value for a single-frame APNG.
|
val numFrames : Int
|
||||||
val numFrames: Int
|
|
||||||
|
// if it is 0, the animation should play indefinitely.
|
||||||
// if it is 0, the animation should play indefinitely.
|
// If nonzero, the animation should come to rest on the final frame at the end of the last play.
|
||||||
// If nonzero, the animation should come to rest on the final frame at the end of the last play.
|
val numPlays : Int
|
||||||
val numPlays: Int
|
|
||||||
|
init {
|
||||||
init {
|
numFrames = src.readInt32()
|
||||||
numFrames = src.readInt32()
|
numPlays = src.readInt32()
|
||||||
numPlays = src.readInt32()
|
}
|
||||||
}
|
|
||||||
|
override fun toString() = "ApngAnimationControl(numFrames=$numFrames,numPlays=$numPlays)"
|
||||||
override fun toString() ="ApngAnimationControl(numFrames=$numFrames,numPlays=$numPlays)"
|
|
||||||
|
val isFinite : Boolean
|
||||||
val isFinite :Boolean
|
get() = numPlays > PLAY_INDEFINITELY
|
||||||
get() = numPlays > PLAY_INDEFINITELY
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,34 +4,36 @@ package jp.juggler.apng
|
||||||
|
|
||||||
import jp.juggler.apng.util.ByteSequence
|
import jp.juggler.apng.util.ByteSequence
|
||||||
|
|
||||||
class ApngBackground internal constructor(colorType: ColorType, src: ByteSequence) {
|
class ApngBackground internal constructor(colorType : ColorType, src : ByteSequence) {
|
||||||
|
|
||||||
val red: Int
|
val red : Int
|
||||||
val green: Int
|
val green : Int
|
||||||
val blue: Int
|
val blue : Int
|
||||||
val index: Int
|
val index : Int
|
||||||
|
|
||||||
init {
|
init {
|
||||||
when (colorType) {
|
when(colorType) {
|
||||||
ColorType.GREY, ColorType.GREY_ALPHA -> {
|
ColorType.GREY, ColorType.GREY_ALPHA -> {
|
||||||
val v = src.readUInt16()
|
val v = src.readUInt16()
|
||||||
red = v
|
red = v
|
||||||
green = v
|
green = v
|
||||||
blue = v
|
blue = v
|
||||||
index = -1
|
index = - 1
|
||||||
}
|
}
|
||||||
ColorType.RGB, ColorType.RGBA -> {
|
|
||||||
red = src.readUInt16()
|
ColorType.RGB, ColorType.RGBA -> {
|
||||||
green = src.readUInt16()
|
red = src.readUInt16()
|
||||||
blue = src.readUInt16()
|
green = src.readUInt16()
|
||||||
index = -1
|
blue = src.readUInt16()
|
||||||
}
|
index = - 1
|
||||||
ColorType.INDEX -> {
|
}
|
||||||
red = -1
|
|
||||||
green = -1
|
ColorType.INDEX -> {
|
||||||
blue = -1
|
red = - 1
|
||||||
index = src.readUInt8()
|
green = - 1
|
||||||
}
|
blue = - 1
|
||||||
}
|
index = src.readUInt8()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -11,7 +11,7 @@ class ApngBitmap(var width : Int, var height : Int) {
|
||||||
fun reset(width : Int, height : Int) {
|
fun reset(width : Int, height : Int) {
|
||||||
val newSize = width * height
|
val newSize = width * height
|
||||||
if(newSize > colors.size)
|
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.width = width
|
||||||
this.height = height
|
this.height = height
|
||||||
// 透明な黒で初期化する
|
// 透明な黒で初期化する
|
||||||
|
@ -24,11 +24,7 @@ class ApngBitmap(var width : Int, var height : Int) {
|
||||||
private var pos : Int = 0
|
private var pos : Int = 0
|
||||||
var step : Int = 1
|
var step : Int = 1
|
||||||
|
|
||||||
fun setPixel(argb : Int) : Pointer {
|
fun setPixel(argb : Int) = apply { colors[pos] = argb }
|
||||||
// if( pos == width) println("setPixel 0x%x".format(argb))
|
|
||||||
colors[pos] = argb
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setPixel(a : Int, r : Int, g : Int, b : Int) = setPixel(
|
fun setPixel(a : Int, r : Int, g : Int, b : Int) = setPixel(
|
||||||
((a and 255) shl 24) or
|
((a and 255) shl 24) or
|
||||||
|
@ -37,18 +33,14 @@ class ApngBitmap(var width : Int, var height : Int) {
|
||||||
(b and 255)
|
(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.pos = pos
|
||||||
this.step = step
|
this.step = step
|
||||||
return this
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setXY(x : Int, y : Int, step : Int = 1) = setOffset(x + y * width, step)
|
fun setXY(x : Int, y : Int, step : Int = 1) = setOffset(x + y * width, step)
|
||||||
|
|
||||||
fun plus(x : Int) : Pointer {
|
fun plus(x : Int) = apply { pos += x }
|
||||||
pos += x
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun next() = plus(step)
|
fun next() = plus(step)
|
||||||
|
|
||||||
|
|
|
@ -5,35 +5,35 @@ package jp.juggler.apng
|
||||||
import jp.juggler.apng.util.StreamTokenizer
|
import jp.juggler.apng.util.StreamTokenizer
|
||||||
import java.util.zip.CRC32
|
import java.util.zip.CRC32
|
||||||
|
|
||||||
internal class ApngChunk(crc32:CRC32,tokenizer: StreamTokenizer) {
|
internal class ApngChunk(crc32 : CRC32, tokenizer : StreamTokenizer) {
|
||||||
val size: Int
|
val size : Int
|
||||||
val type: String
|
val type : String
|
||||||
|
|
||||||
init {
|
init {
|
||||||
size = tokenizer.readInt32()
|
size = tokenizer.readInt32()
|
||||||
val typeBytes = tokenizer.readBytes(4)
|
val typeBytes = tokenizer.readBytes(4)
|
||||||
type = typeBytes.toString(Charsets.UTF_8)
|
type = typeBytes.toString(Charsets.UTF_8)
|
||||||
|
|
||||||
crc32.update(typeBytes)
|
crc32.update(typeBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun readBody(crc32:CRC32,tokenizer: StreamTokenizer): ByteArray {
|
fun readBody(crc32 : CRC32, tokenizer : StreamTokenizer) : ByteArray {
|
||||||
val bytes = tokenizer.readBytes(size)
|
val bytes = tokenizer.readBytes(size)
|
||||||
val crcExpect = tokenizer.readUInt32()
|
val crcExpect = tokenizer.readUInt32()
|
||||||
|
|
||||||
crc32.update(bytes, 0, size)
|
crc32.update(bytes, 0, size)
|
||||||
val crcActual = crc32.value
|
val crcActual = crc32.value
|
||||||
if (crcActual != crcExpect) throw ParseError("CRC not match.")
|
if(crcActual != crcExpect) throw ApngParseError("CRC not match.")
|
||||||
|
|
||||||
return bytes
|
return bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
fun skipBody(tokenizer: StreamTokenizer) {
|
fun skipBody(tokenizer : StreamTokenizer) =
|
||||||
tokenizer.skipBytes((size + 4).toLong())
|
tokenizer.skipBytes((size + 4).toLong())
|
||||||
}
|
|
||||||
|
|
||||||
fun checkCRC(tokenizer: StreamTokenizer, crcActual: Long) {
|
fun checkCRC(tokenizer : StreamTokenizer, crcActual : Long) {
|
||||||
val crcExpect = tokenizer.readUInt32()
|
val crcExpect = tokenizer.readUInt32()
|
||||||
if (crcActual != crcExpect) throw ParseError("CRC not match.")
|
if(crcActual != crcExpect) throw ApngParseError("CRC not match.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,22 +11,22 @@ object ApngDecoder {
|
||||||
private val PNG_SIGNATURE = byteArrayOf(0x89.toByte(), 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0a)
|
private val PNG_SIGNATURE = byteArrayOf(0x89.toByte(), 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0a)
|
||||||
|
|
||||||
fun parseStream(
|
fun parseStream(
|
||||||
_inStream : InputStream,
|
inStream : InputStream,
|
||||||
callback : ApngDecoderCallback
|
callback : ApngDecoderCallback
|
||||||
) {
|
) {
|
||||||
val apng = Apng()
|
val apng = Apng()
|
||||||
val tokenizer = StreamTokenizer(_inStream)
|
val tokenizer = StreamTokenizer(inStream)
|
||||||
|
|
||||||
val pngHeader = tokenizer.readBytes(8)
|
val pngHeader = tokenizer.readBytes(8)
|
||||||
if(! pngHeader.contentEquals(PNG_SIGNATURE)) {
|
if(! pngHeader.contentEquals(PNG_SIGNATURE)) {
|
||||||
throw ParseError("header not match")
|
throw ApngParseError("header not match")
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastSequenceNumber : Int? = null
|
var lastSequenceNumber : Int? = null
|
||||||
fun checkSequenceNumber(n : Int) {
|
fun checkSequenceNumber(n : Int) {
|
||||||
val last = lastSequenceNumber
|
val last = lastSequenceNumber
|
||||||
if(last != null && n <= last) {
|
if(last != null && n <= last) {
|
||||||
throw ParseError("incorrect sequenceNumber. last=$lastSequenceNumber,current=$n")
|
throw ApngParseError("incorrect sequenceNumber. last=$lastSequenceNumber,current=$n")
|
||||||
}
|
}
|
||||||
lastSequenceNumber = n
|
lastSequenceNumber = n
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ object ApngDecoder {
|
||||||
"PLTE" -> apng.palette = ApngPalette(chunk.readBody(crc32, tokenizer))
|
"PLTE" -> apng.palette = ApngPalette(chunk.readBody(crc32, tokenizer))
|
||||||
|
|
||||||
"bKGD" -> {
|
"bKGD" -> {
|
||||||
val header = apng.header ?: throw ParseError("missing IHDR")
|
val header = apng.header ?: throw ApngParseError("missing IHDR")
|
||||||
apng.background = ApngBackground(
|
apng.background = ApngBackground(
|
||||||
header.colorType,
|
header.colorType,
|
||||||
ByteSequence(chunk.readBody(crc32, tokenizer))
|
ByteSequence(chunk.readBody(crc32, tokenizer))
|
||||||
|
@ -64,7 +64,7 @@ object ApngDecoder {
|
||||||
}
|
}
|
||||||
|
|
||||||
"tRNS" -> {
|
"tRNS" -> {
|
||||||
val header = apng.header ?: throw ParseError("missing IHDR")
|
val header = apng.header ?: throw ApngParseError("missing IHDR")
|
||||||
val body = chunk.readBody(crc32, tokenizer)
|
val body = chunk.readBody(crc32, tokenizer)
|
||||||
when(header.colorType) {
|
when(header.colorType) {
|
||||||
ColorType.GREY -> apng.transparentColor =
|
ColorType.GREY -> apng.transparentColor =
|
||||||
|
@ -72,15 +72,15 @@ object ApngDecoder {
|
||||||
ColorType.RGB -> apng.transparentColor =
|
ColorType.RGB -> apng.transparentColor =
|
||||||
ApngTransparentColor(false, ByteSequence(body))
|
ApngTransparentColor(false, ByteSequence(body))
|
||||||
ColorType.INDEX -> apng.palette?.parseTRNS(body)
|
ColorType.INDEX -> apng.palette?.parseTRNS(body)
|
||||||
?: throw ParseError("missing palette")
|
?: throw ApngParseError("missing palette")
|
||||||
else -> callback.onApngWarning("tRNS ignored. colorType =${header.colorType}")
|
else -> callback.onApngWarning("tRNS ignored. colorType =${header.colorType}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"IDAT" -> {
|
"IDAT" -> {
|
||||||
val header = apng.header ?: throw ParseError("missing IHDR")
|
val header = apng.header ?: throw ApngParseError("missing IHDR")
|
||||||
if(idatDecoder == null) {
|
if(idatDecoder == null) {
|
||||||
bitmap ?: throw ParseError("missing bitmap")
|
bitmap ?: throw ApngParseError("missing bitmap")
|
||||||
bitmap.reset(header.width, header.height)
|
bitmap.reset(header.width, header.height)
|
||||||
idatDecoder = IdatDecoder(
|
idatDecoder = IdatDecoder(
|
||||||
apng,
|
apng,
|
||||||
|
@ -106,11 +106,11 @@ object ApngDecoder {
|
||||||
}
|
}
|
||||||
|
|
||||||
"acTL" -> {
|
"acTL" -> {
|
||||||
val header = apng.header ?: throw ParseError("missing IHDR")
|
val header = apng.header ?: throw ApngParseError("missing IHDR")
|
||||||
val animationControl =
|
val animationControl =
|
||||||
ApngAnimationControl(ByteSequence(chunk.readBody(crc32, tokenizer)))
|
ApngAnimationControl(ByteSequence(chunk.readBody(crc32, tokenizer)))
|
||||||
apng.animationControl = animationControl
|
apng.animationControl = animationControl
|
||||||
callback.onAnimationInfo(apng, header,animationControl)
|
callback.onAnimationInfo(apng, header, animationControl)
|
||||||
}
|
}
|
||||||
|
|
||||||
"fcTL" -> {
|
"fcTL" -> {
|
||||||
|
@ -121,9 +121,9 @@ object ApngDecoder {
|
||||||
}
|
}
|
||||||
|
|
||||||
"fdAT" -> {
|
"fdAT" -> {
|
||||||
val fctl = lastFctl ?: throw ParseError("missing fCTL before fdAT")
|
val fctl = lastFctl ?: throw ApngParseError("missing fCTL before fdAT")
|
||||||
if(fdatDecoder == null) {
|
if(fdatDecoder == null) {
|
||||||
bitmap ?: throw ParseError("missing bitmap")
|
bitmap ?: throw ApngParseError("missing bitmap")
|
||||||
bitmap.reset(fctl.width, fctl.height)
|
bitmap.reset(fctl.width, fctl.height)
|
||||||
fdatDecoder = IdatDecoder(
|
fdatDecoder = IdatDecoder(
|
||||||
apng,
|
apng,
|
||||||
|
|
|
@ -8,7 +8,7 @@ interface ApngDecoderCallback {
|
||||||
// called for debug message
|
// called for debug message
|
||||||
fun onApngDebug(message : String) {}
|
fun onApngDebug(message : String) {}
|
||||||
|
|
||||||
fun canApngDebug():Boolean = false
|
fun canApngDebug() : Boolean = false
|
||||||
|
|
||||||
// called when PNG image header is detected.
|
// called when PNG image header is detected.
|
||||||
fun onHeader(apng : Apng, header : ApngImageHeader)
|
fun onHeader(apng : Apng, header : ApngImageHeader)
|
||||||
|
|
|
@ -1,41 +1,41 @@
|
||||||
package jp.juggler.apng
|
package jp.juggler.apng
|
||||||
|
|
||||||
enum class ColorType(val num:Int ){
|
enum class ColorType(val num : Int) {
|
||||||
GREY(0),
|
GREY(0),
|
||||||
RGB ( 2),
|
RGB(2),
|
||||||
INDEX( 3),
|
INDEX(3),
|
||||||
GREY_ALPHA ( 4),
|
GREY_ALPHA(4),
|
||||||
RGBA ( 6),
|
RGBA(6),
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class CompressionMethod(val num:Int ){
|
enum class CompressionMethod(val num : Int) {
|
||||||
Standard(0)
|
Standard(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class FilterMethod(val num:Int ){
|
enum class FilterMethod(val num : Int) {
|
||||||
Standard(0)
|
Standard(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class InterlaceMethod(val num:Int ){
|
enum class InterlaceMethod(val num : Int) {
|
||||||
None(0),
|
None(0),
|
||||||
Standard(1)
|
Standard(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class FilterType(val num:Int ){
|
enum class FilterType(val num : Int) {
|
||||||
None(0),
|
None(0),
|
||||||
Sub(1),
|
Sub(1),
|
||||||
Up(2),
|
Up(2),
|
||||||
Average(3),
|
Average(3),
|
||||||
Paeth(4)
|
Paeth(4)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class DisposeOp(val num :Int){
|
enum class DisposeOp(val num : Int) {
|
||||||
None(0),
|
None(0),
|
||||||
Background(1),
|
Background(1),
|
||||||
Previous(2)
|
Previous(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class BlendOp(val num :Int){
|
enum class BlendOp(val num : Int) {
|
||||||
Source(0),
|
Source(0),
|
||||||
Over(1)
|
Over(1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,40 +4,40 @@ package jp.juggler.apng
|
||||||
|
|
||||||
import jp.juggler.apng.util.ByteSequence
|
import jp.juggler.apng.util.ByteSequence
|
||||||
|
|
||||||
|
class ApngFrameControl internal constructor(src : ByteSequence) {
|
||||||
class ApngFrameControl internal constructor(src: ByteSequence) {
|
|
||||||
|
val width : Int
|
||||||
val width: Int
|
val height : Int
|
||||||
val height: Int
|
val xOffset : Int
|
||||||
val xOffset: Int
|
val yOffset : Int
|
||||||
val yOffset: Int
|
val delayNum : Int
|
||||||
val delayNum: Int
|
val delayDen : Int
|
||||||
val delayDen: Int
|
val disposeOp : DisposeOp
|
||||||
val disposeOp: DisposeOp
|
val blendOp : BlendOp
|
||||||
val blendOp: BlendOp
|
|
||||||
|
init {
|
||||||
init {
|
width = src.readInt32()
|
||||||
width = src.readInt32()
|
height = src.readInt32()
|
||||||
height = src.readInt32()
|
xOffset = src.readInt32()
|
||||||
xOffset = src.readInt32()
|
yOffset = src.readInt32()
|
||||||
yOffset = src.readInt32()
|
delayNum = src.readUInt16()
|
||||||
delayNum = src.readUInt16()
|
delayDen = src.readUInt16().let { if(it == 0) 100 else it }
|
||||||
delayDen = src.readUInt16().let{ if(it==0) 100 else it}
|
|
||||||
|
var num : Int
|
||||||
var num:Int
|
|
||||||
|
num = src.readUInt8()
|
||||||
num = src.readUInt8()
|
disposeOp = DisposeOp.values().first { it.num == num }
|
||||||
disposeOp = DisposeOp.values().first{it.num==num}
|
|
||||||
|
num = src.readUInt8()
|
||||||
num = src.readUInt8()
|
blendOp = BlendOp.values().first { it.num == num }
|
||||||
blendOp = BlendOp.values().first{it.num==num}
|
}
|
||||||
}
|
|
||||||
|
override fun toString() =
|
||||||
override fun toString() ="ApngFrameControl(width=$width,height=$height,x=$xOffset,y=$yOffset,delayNum=$delayNum,delayDen=$delayDen,disposeOp=$disposeOp,blendOp=$blendOp)"
|
"ApngFrameControl(width=$width,height=$height,x=$xOffset,y=$yOffset,delayNum=$delayNum,delayDen=$delayDen,disposeOp=$disposeOp,blendOp=$blendOp)"
|
||||||
|
|
||||||
val delayMilliseconds : Long
|
val delayMilliseconds : Long
|
||||||
get() = when(delayDen) {
|
get() = when(delayDen) {
|
||||||
1000 -> delayNum.toLong()
|
1000 -> delayNum.toLong()
|
||||||
else -> (1000f * delayNum.toFloat() / delayDen.toFloat() + 0.5f).toLong()
|
else -> (1000f * delayNum.toFloat() / delayDen.toFloat() + 0.5f).toLong()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -5,37 +5,39 @@ package jp.juggler.apng
|
||||||
import jp.juggler.apng.util.ByteSequence
|
import jp.juggler.apng.util.ByteSequence
|
||||||
|
|
||||||
// information from IHDR chunk.
|
// information from IHDR chunk.
|
||||||
class ApngImageHeader internal constructor(src: ByteSequence) {
|
class ApngImageHeader internal constructor(src : ByteSequence) {
|
||||||
val width: Int
|
|
||||||
val height: Int
|
val width : Int
|
||||||
val bitDepth: Int
|
val height : Int
|
||||||
val colorType: ColorType
|
val bitDepth : Int
|
||||||
val compressionMethod: CompressionMethod
|
val colorType : ColorType
|
||||||
val filterMethod: FilterMethod
|
val compressionMethod : CompressionMethod
|
||||||
val interlaceMethod: InterlaceMethod
|
val filterMethod : FilterMethod
|
||||||
|
val interlaceMethod : InterlaceMethod
|
||||||
init {
|
|
||||||
|
init {
|
||||||
width = src.readInt32()
|
|
||||||
height = src.readInt32()
|
width = src.readInt32()
|
||||||
if(width <=0 || height <=0 ) throw ParseError("w=$width,h=$height is too small")
|
height = src.readInt32()
|
||||||
|
if(width <= 0 || height <= 0) throw ApngParseError("w=$width,h=$height is too small")
|
||||||
bitDepth = src.readUInt8()
|
|
||||||
|
bitDepth = src.readUInt8()
|
||||||
var num:Int
|
|
||||||
//
|
var num : Int
|
||||||
num =src.readUInt8()
|
//
|
||||||
colorType = ColorType.values().first { it.num==num }
|
num = src.readUInt8()
|
||||||
//
|
colorType = ColorType.values().first { it.num == num }
|
||||||
num =src.readUInt8()
|
//
|
||||||
compressionMethod = CompressionMethod.values().first { it.num==num }
|
num = src.readUInt8()
|
||||||
//
|
compressionMethod = CompressionMethod.values().first { it.num == num }
|
||||||
num =src.readUInt8()
|
//
|
||||||
filterMethod = FilterMethod.values().first { it.num==num }
|
num = src.readUInt8()
|
||||||
//
|
filterMethod = FilterMethod.values().first { it.num == num }
|
||||||
num =src.readUInt8()
|
//
|
||||||
interlaceMethod = InterlaceMethod.values().first { it.num==num }
|
num = src.readUInt8()
|
||||||
}
|
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)"
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import jp.juggler.apng.util.getUInt8
|
||||||
class ApngPalette(
|
class ApngPalette(
|
||||||
src : ByteArray // repeat of R,G,B
|
src : ByteArray // repeat of R,G,B
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
// full opaque black
|
// full opaque black
|
||||||
const val OPAQUE = 255 shl 24
|
const val OPAQUE = 255 shl 24
|
||||||
|
@ -18,14 +19,14 @@ class ApngPalette(
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val entryCount = src.size / 3
|
val entryCount = src.size / 3
|
||||||
list = IntArray( entryCount)
|
list = IntArray(entryCount)
|
||||||
var pos = 0
|
var pos = 0
|
||||||
for(i in 0 until entryCount) {
|
for(i in 0 until entryCount) {
|
||||||
list[i] = OPAQUE or
|
list[i] = OPAQUE or
|
||||||
(src.getUInt8(pos) shl 16) or
|
(src.getUInt8(pos) shl 16) or
|
||||||
(src.getUInt8(pos+1) shl 8) or
|
(src.getUInt8(pos + 1) shl 8) or
|
||||||
src.getUInt8(pos+2)
|
src.getUInt8(pos + 2)
|
||||||
pos+=3
|
pos += 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
package jp.juggler.apng
|
||||||
|
|
||||||
|
class ApngParseError(message: String) : IllegalArgumentException(message)
|
|
@ -4,23 +4,24 @@ package jp.juggler.apng
|
||||||
|
|
||||||
import jp.juggler.apng.util.ByteSequence
|
import jp.juggler.apng.util.ByteSequence
|
||||||
|
|
||||||
class ApngTransparentColor internal constructor(isGreyScale:Boolean, src: ByteSequence) {
|
class ApngTransparentColor internal constructor(isGreyScale : Boolean, src : ByteSequence) {
|
||||||
val red:Int
|
val red : Int
|
||||||
val green:Int
|
val green : Int
|
||||||
val blue:Int
|
val blue : Int
|
||||||
init{
|
|
||||||
if( isGreyScale){
|
init {
|
||||||
val v = src.readUInt16()
|
if(isGreyScale) {
|
||||||
red =v
|
val v = src.readUInt16()
|
||||||
green =v
|
red = v
|
||||||
blue =v
|
green = v
|
||||||
}else{
|
blue = v
|
||||||
red =src.readUInt16()
|
} else {
|
||||||
green =src.readUInt16()
|
red = src.readUInt16()
|
||||||
blue =src.readUInt16()
|
green = src.readUInt16()
|
||||||
}
|
blue = src.readUInt16()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
fun match(grey:Int) = red == grey
|
|
||||||
fun match(r:Int,g:Int,b:Int) = (r==red && g == green && b == blue)
|
fun match(grey : Int) = red == grey
|
||||||
|
fun match(r : Int, g : Int, b : Int) = (r == red && g == green && b == blue)
|
||||||
}
|
}
|
|
@ -4,7 +4,6 @@ package jp.juggler.apng
|
||||||
|
|
||||||
import jp.juggler.apng.util.*
|
import jp.juggler.apng.util.*
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.util.*
|
|
||||||
import java.util.zip.CRC32
|
import java.util.zip.CRC32
|
||||||
import java.util.zip.Inflater
|
import java.util.zip.Inflater
|
||||||
|
|
||||||
|
@ -33,7 +32,6 @@ internal class IdatDecoder(
|
||||||
|
|
||||||
private val dummyPaletteData = IntArray(0)
|
private val dummyPaletteData = IntArray(0)
|
||||||
|
|
||||||
|
|
||||||
private fun abs(v : Int) = if(v >= 0) v else - v
|
private fun abs(v : Int) = if(v >= 0) v else - v
|
||||||
|
|
||||||
// a = left, b = above, c = upper left
|
// 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) {
|
private inline fun scanLine1(baLine : ByteArray, pass_w : Int, block : (v : Int) -> Unit) {
|
||||||
var pos = 1
|
var pos = 1
|
||||||
var x = 0
|
var remain = pass_w
|
||||||
while(pass_w - x >= 8) {
|
while(remain >= 8) {
|
||||||
|
remain -= 8
|
||||||
val v = baLine[pos ++].toInt()
|
val v = baLine[pos ++].toInt()
|
||||||
block((v shr 7) and 1)
|
block((v shr 7) and 1)
|
||||||
block((v shr 6) and 1)
|
block((v shr 6) and 1)
|
||||||
|
@ -62,9 +61,7 @@ internal class IdatDecoder(
|
||||||
block((v shr 2) and 1)
|
block((v shr 2) and 1)
|
||||||
block((v shr 1) and 1)
|
block((v shr 1) and 1)
|
||||||
block(v and 1)
|
block(v and 1)
|
||||||
x += 8
|
|
||||||
}
|
}
|
||||||
val remain = pass_w - x
|
|
||||||
if(remain > 0) {
|
if(remain > 0) {
|
||||||
val v = baLine[pos].toInt()
|
val v = baLine[pos].toInt()
|
||||||
block((v shr 7) and 1)
|
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) {
|
private inline fun scanLine2(baLine : ByteArray, pass_w : Int, block : (v : Int) -> Unit) {
|
||||||
var pos = 1
|
var pos = 1
|
||||||
var x = 0
|
var remain = pass_w
|
||||||
while(pass_w - x >= 4) {
|
while(remain >= 4) {
|
||||||
|
remain -= 4
|
||||||
val v = baLine[pos ++].toInt()
|
val v = baLine[pos ++].toInt()
|
||||||
block((v shr 6) and 3)
|
block((v shr 6) and 3)
|
||||||
block((v shr 4) and 3)
|
block((v shr 4) and 3)
|
||||||
block((v shr 2) and 3)
|
block((v shr 2) and 3)
|
||||||
block(v and 3)
|
block(v and 3)
|
||||||
x += 4
|
|
||||||
}
|
}
|
||||||
val remain = pass_w - x
|
|
||||||
if(remain > 0) {
|
if(remain > 0) {
|
||||||
val v = baLine[pos].toInt()
|
val v = baLine[pos].toInt()
|
||||||
block((v shr 6) and 3)
|
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) {
|
private inline fun scanLine4(baLine : ByteArray, pass_w : Int, block : (v : Int) -> Unit) {
|
||||||
var pos = 1
|
var pos = 1
|
||||||
var x = 0
|
var remain = pass_w
|
||||||
while(pass_w - x >= 2) {
|
while(remain >= 2) {
|
||||||
|
remain -= 2
|
||||||
val v = baLine[pos ++].toInt()
|
val v = baLine[pos ++].toInt()
|
||||||
block((v shr 4) and 15)
|
block((v shr 4) and 15)
|
||||||
block(v and 15)
|
block(v and 15)
|
||||||
x += 2
|
|
||||||
}
|
}
|
||||||
val remain = pass_w - x
|
|
||||||
if(remain > 0) {
|
if(remain > 0) {
|
||||||
val v = baLine[pos].toInt()
|
val v = baLine[pos].toInt()
|
||||||
block((v shr 4) and 15)
|
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) {
|
private inline fun scanLine8(baLine : ByteArray, pass_w : Int, block : (v : Int) -> Unit) {
|
||||||
var pos = 1
|
var pos = 1
|
||||||
for(x in 0 until pass_w) {
|
var remain = pass_w
|
||||||
|
while(remain -- > 0) {
|
||||||
block(baLine.getUInt8(pos ++))
|
block(baLine.getUInt8(pos ++))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline fun scanLine16(baLine : ByteArray, pass_w : Int, block : (v : Int) -> Unit) {
|
private inline fun scanLine16(baLine : ByteArray, pass_w : Int, block : (v : Int) -> Unit) {
|
||||||
var pos = 1
|
var pos = 1
|
||||||
for(x in 0 until pass_w) {
|
var remain = pass_w
|
||||||
|
while(remain -- > 0) {
|
||||||
block(baLine.getUInt16(pos))
|
block(baLine.getUInt16(pos))
|
||||||
pos += 2
|
pos += 2
|
||||||
}
|
}
|
||||||
|
@ -134,7 +131,8 @@ internal class IdatDecoder(
|
||||||
block : (r : Int, g : Int, b : Int) -> Unit
|
block : (r : Int, g : Int, b : Int) -> Unit
|
||||||
) {
|
) {
|
||||||
var pos = 1
|
var pos = 1
|
||||||
for(x in 0 until pass_w) {
|
var remain = pass_w
|
||||||
|
while(remain -- > 0) {
|
||||||
block(
|
block(
|
||||||
baLine.getUInt8(pos),
|
baLine.getUInt8(pos),
|
||||||
baLine.getUInt8(pos + 1),
|
baLine.getUInt8(pos + 1),
|
||||||
|
@ -150,7 +148,8 @@ internal class IdatDecoder(
|
||||||
block : (r : Int, g : Int, b : Int) -> Unit
|
block : (r : Int, g : Int, b : Int) -> Unit
|
||||||
) {
|
) {
|
||||||
var pos = 1
|
var pos = 1
|
||||||
for(x in 0 until pass_w) {
|
var remain = pass_w
|
||||||
|
while(remain -- > 0) {
|
||||||
block(
|
block(
|
||||||
baLine.getUInt16(pos),
|
baLine.getUInt16(pos),
|
||||||
baLine.getUInt16(pos + 2),
|
baLine.getUInt16(pos + 2),
|
||||||
|
@ -166,7 +165,8 @@ internal class IdatDecoder(
|
||||||
block : (r : Int, g : Int, b : Int, a : Int) -> Unit
|
block : (r : Int, g : Int, b : Int, a : Int) -> Unit
|
||||||
) {
|
) {
|
||||||
var pos = 1
|
var pos = 1
|
||||||
for(x in 0 until pass_w) {
|
var remain = pass_w
|
||||||
|
while(remain -- > 0) {
|
||||||
block(
|
block(
|
||||||
baLine.getUInt8(pos),
|
baLine.getUInt8(pos),
|
||||||
baLine.getUInt8(pos + 1),
|
baLine.getUInt8(pos + 1),
|
||||||
|
@ -183,7 +183,8 @@ internal class IdatDecoder(
|
||||||
block : (r : Int, g : Int, b : Int, a : Int) -> Unit
|
block : (r : Int, g : Int, b : Int, a : Int) -> Unit
|
||||||
) {
|
) {
|
||||||
var pos = 1
|
var pos = 1
|
||||||
for(x in 0 until pass_w) {
|
var remain = pass_w
|
||||||
|
while(remain -- > 0) {
|
||||||
block(
|
block(
|
||||||
baLine.getUInt16(pos),
|
baLine.getUInt16(pos),
|
||||||
baLine.getUInt16(pos + 2),
|
baLine.getUInt16(pos + 2),
|
||||||
|
@ -200,7 +201,8 @@ internal class IdatDecoder(
|
||||||
block : (g : Int, a : Int) -> Unit
|
block : (g : Int, a : Int) -> Unit
|
||||||
) {
|
) {
|
||||||
var pos = 1
|
var pos = 1
|
||||||
for(x in 0 until pass_w) {
|
var remain = pass_w
|
||||||
|
while(remain -- > 0) {
|
||||||
block(
|
block(
|
||||||
baLine.getUInt8(pos),
|
baLine.getUInt8(pos),
|
||||||
baLine.getUInt8(pos + 1)
|
baLine.getUInt8(pos + 1)
|
||||||
|
@ -215,7 +217,8 @@ internal class IdatDecoder(
|
||||||
block : (g : Int, a : Int) -> Unit
|
block : (g : Int, a : Int) -> Unit
|
||||||
) {
|
) {
|
||||||
var pos = 1
|
var pos = 1
|
||||||
for(x in 0 until pass_w) {
|
var remain = pass_w
|
||||||
|
while(remain -- > 0) {
|
||||||
block(
|
block(
|
||||||
baLine.getUInt16(pos),
|
baLine.getUInt16(pos),
|
||||||
baLine.getUInt16(pos + 2)
|
baLine.getUInt16(pos + 2)
|
||||||
|
@ -233,7 +236,7 @@ internal class IdatDecoder(
|
||||||
private val sampleBits : Int
|
private val sampleBits : Int
|
||||||
private val sampleBytes : Int
|
private val sampleBytes : Int
|
||||||
private val scanLineBytesMax : Int
|
private val scanLineBytesMax : Int
|
||||||
private val linePool = LinkedList<ByteArray>()
|
private val scanLinePool : BufferPool
|
||||||
private val transparentCheckerGrey : (v : Int) -> Int
|
private val transparentCheckerGrey : (v : Int) -> Int
|
||||||
private val transparentCheckerRGB : (r : Int, g : Int, b : Int) -> Int
|
private val transparentCheckerRGB : (r : Int, g : Int, b : Int) -> Int
|
||||||
private val renderScanLineFunc : (baLine : ByteArray) -> Unit
|
private val renderScanLineFunc : (baLine : ByteArray) -> Unit
|
||||||
|
@ -250,15 +253,8 @@ internal class IdatDecoder(
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val header = requireNotNull(apng.header)
|
val header = requireNotNull(apng.header)
|
||||||
this.colorType = header.colorType
|
colorType = header.colorType
|
||||||
this.bitDepth = header.bitDepth
|
bitDepth = header.bitDepth
|
||||||
|
|
||||||
this.paletteData = if(colorType == ColorType.INDEX) {
|
|
||||||
apng.palette?.list
|
|
||||||
?: throw ParseError("missing ApngPalette for index color")
|
|
||||||
} else {
|
|
||||||
dummyPaletteData
|
|
||||||
}
|
|
||||||
|
|
||||||
sampleBits = when(colorType) {
|
sampleBits = when(colorType) {
|
||||||
ColorType.GREY, ColorType.INDEX -> bitDepth
|
ColorType.GREY, ColorType.INDEX -> bitDepth
|
||||||
|
@ -269,9 +265,14 @@ internal class IdatDecoder(
|
||||||
|
|
||||||
sampleBytes = (sampleBits + 7) / 8
|
sampleBytes = (sampleBits + 7) / 8
|
||||||
scanLineBytesMax = 1 + (bitmap.width * sampleBits + 7) / 8
|
scanLineBytesMax = 1 + (bitmap.width * sampleBits + 7) / 8
|
||||||
|
scanLinePool = BufferPool(scanLineBytesMax)
|
||||||
|
|
||||||
linePool.add(ByteArray(scanLineBytesMax))
|
paletteData = if(colorType == ColorType.INDEX) {
|
||||||
linePool.add(ByteArray(scanLineBytesMax))
|
apng.palette?.list
|
||||||
|
?: throw ApngParseError("missing ApngPalette for index color")
|
||||||
|
} else {
|
||||||
|
dummyPaletteData
|
||||||
|
}
|
||||||
|
|
||||||
val transparentColor = apng.transparentColor
|
val transparentColor = apng.transparentColor
|
||||||
|
|
||||||
|
@ -358,7 +359,7 @@ internal class IdatDecoder(
|
||||||
|
|
||||||
private fun renderIndex2(baLine : ByteArray) {
|
private fun renderIndex2(baLine : ByteArray) {
|
||||||
scanLine2(baLine, passWidth) { v ->
|
scanLine2(baLine, passWidth) { v ->
|
||||||
bitmapPointer.setPixel( paletteData[v] ).next()
|
bitmapPointer.setPixel(paletteData[v]).next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -400,7 +401,7 @@ internal class IdatDecoder(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun colorBitsNotSupported() : Nothing {
|
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) {
|
private fun selectRenderFunc() = when(colorType) {
|
||||||
|
@ -443,11 +444,11 @@ internal class IdatDecoder(
|
||||||
passY = 0
|
passY = 0
|
||||||
scanLineBytes = 1 + (passWidth * sampleBits + 7) / 8
|
scanLineBytes = 1 + (passWidth * sampleBits + 7) / 8
|
||||||
|
|
||||||
baPreviousLine?.let { linePool.add(it) }
|
scanLinePool.recycle(baPreviousLine)
|
||||||
baPreviousLine = null
|
baPreviousLine = null
|
||||||
|
|
||||||
if(passWidth <= 0 || passHeight <= 0) {
|
if(passWidth <= 0 || passHeight <= 0) {
|
||||||
if( callback.canApngDebug() ) callback.onApngDebug("pass $pass is empty. size=${passWidth}x${passHeight} ")
|
if(callback.canApngDebug()) callback.onApngDebug("pass $pass is empty. size=${passWidth}x${passHeight} ")
|
||||||
incrementPassOrComplete()
|
incrementPassOrComplete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -464,20 +465,20 @@ internal class IdatDecoder(
|
||||||
|
|
||||||
// スキャンラインを読む。行を処理したらtrueを返す
|
// スキャンラインを読む。行を処理したらtrueを返す
|
||||||
private fun readScanLine() : Boolean {
|
private fun readScanLine() : Boolean {
|
||||||
|
|
||||||
if(inflateBufferQueue.remain < scanLineBytes){
|
if(inflateBufferQueue.remain < scanLineBytes) {
|
||||||
// not yet enough data to process scanline
|
// not yet enough data to process scanline
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val baLine = linePool.removeFirst()
|
val baLine = scanLinePool.obtain()
|
||||||
inflateBufferQueue.readBytes(baLine, 0, scanLineBytes)
|
inflateBufferQueue.readBytes(baLine, 0, scanLineBytes)
|
||||||
|
|
||||||
val filterNum = baLine.getUInt8(0)
|
val filterNum = baLine.getUInt8(0)
|
||||||
val filterType = FilterType.values().first { it.num == filterNum }
|
val filterType = FilterType.values().first { it.num == filterNum }
|
||||||
|
|
||||||
// if( callback.canApngDebug() ) callback.onApngDebug("y=$passY/${passHeight},filterType=$filterType")
|
// if( callback.canApngDebug() ) callback.onApngDebug("y=$passY/${passHeight},filterType=$filterType")
|
||||||
|
|
||||||
when(filterType) {
|
when(filterType) {
|
||||||
FilterType.None -> {
|
FilterType.None -> {
|
||||||
}
|
}
|
||||||
|
@ -485,23 +486,22 @@ internal class IdatDecoder(
|
||||||
FilterType.Sub -> {
|
FilterType.Sub -> {
|
||||||
for(pos in 1 until scanLineBytes) {
|
for(pos in 1 until scanLineBytes) {
|
||||||
val vCur = baLine.getUInt8(pos)
|
val vCur = baLine.getUInt8(pos)
|
||||||
val leftPos = pos -sampleBytes
|
val leftPos = pos - sampleBytes
|
||||||
val vLeft = if(leftPos <=0 ) 0 else baLine.getUInt8(leftPos)
|
val vLeft = if(leftPos <= 0) 0 else baLine.getUInt8(leftPos)
|
||||||
|
|
||||||
|
|
||||||
baLine[pos] = (vCur + vLeft).toByte()
|
baLine[pos] = (vCur + vLeft).toByte()
|
||||||
|
|
||||||
// if( callback.canApngDebug() ){
|
// if( callback.canApngDebug() ){
|
||||||
// val x = passInfo.xStart + passInfo.xStep * ((pos-1)/sampleBytes)
|
// val x = passInfo.xStart + passInfo.xStep * ((pos-1)/sampleBytes)
|
||||||
// val y = passInfo.yStart + passInfo.yStep * passY
|
// val y = passInfo.yStart + passInfo.yStep * passY
|
||||||
// callback.onApngDebug("sub pos=$pos,x=$x,y=$y,left=$vLeft,cur=$vCur,after=${baLine[pos].toInt() and 255}")
|
// callback.onApngDebug("sub pos=$pos,x=$x,y=$y,left=$vLeft,cur=$vCur,after=${baLine[pos].toInt() and 255}")
|
||||||
// }
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FilterType.Up -> {
|
FilterType.Up -> {
|
||||||
val baPreviousLine=this.baPreviousLine
|
val baPreviousLine = this.baPreviousLine
|
||||||
for(pos in 1 until scanLineBytes) {
|
for(pos in 1 until scanLineBytes) {
|
||||||
val vCur = baLine.getUInt8(pos)
|
val vCur = baLine.getUInt8(pos)
|
||||||
val vUp = baPreviousLine?.getUInt8(pos) ?: 0
|
val vUp = baPreviousLine?.getUInt8(pos) ?: 0
|
||||||
|
@ -510,47 +510,47 @@ internal class IdatDecoder(
|
||||||
}
|
}
|
||||||
|
|
||||||
FilterType.Average -> {
|
FilterType.Average -> {
|
||||||
val baPreviousLine=this.baPreviousLine
|
val baPreviousLine = this.baPreviousLine
|
||||||
for(pos in 1 until scanLineBytes) {
|
for(pos in 1 until scanLineBytes) {
|
||||||
val vCur = baLine.getUInt8(pos)
|
val vCur = baLine.getUInt8(pos)
|
||||||
val leftPos = pos -sampleBytes
|
val leftPos = pos - sampleBytes
|
||||||
val vLeft = if(leftPos <=0 ) 0 else baLine.getUInt8(leftPos)
|
val vLeft = if(leftPos <= 0) 0 else baLine.getUInt8(leftPos)
|
||||||
val vUp = baPreviousLine?.getUInt8(pos) ?: 0
|
val vUp = baPreviousLine?.getUInt8(pos) ?: 0
|
||||||
baLine[pos] = (vCur + ((vLeft + vUp) shr 1)).toByte()
|
baLine[pos] = (vCur + ((vLeft + vUp) shr 1)).toByte()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FilterType.Paeth -> {
|
FilterType.Paeth -> {
|
||||||
val baPreviousLine=this.baPreviousLine
|
val baPreviousLine = this.baPreviousLine
|
||||||
for(pos in 1 until scanLineBytes) {
|
for(pos in 1 until scanLineBytes) {
|
||||||
val vCur = baLine.getUInt8(pos)
|
val vCur = baLine.getUInt8(pos)
|
||||||
val leftPos = pos -sampleBytes
|
val leftPos = pos - sampleBytes
|
||||||
val vLeft = if(leftPos <=0 ) 0 else baLine.getUInt8(leftPos)
|
val vLeft = if(leftPos <= 0) 0 else baLine.getUInt8(leftPos)
|
||||||
val vUp = baPreviousLine?.getUInt8(pos) ?: 0
|
val vUp = baPreviousLine?.getUInt8(pos) ?: 0
|
||||||
val vUpperLeft = if(leftPos <=0 ) 0 else baPreviousLine?.getUInt8(leftPos) ?: 0
|
val vUpperLeft = if(leftPos <= 0) 0 else baPreviousLine?.getUInt8(leftPos) ?: 0
|
||||||
|
|
||||||
baLine[pos] = (vCur + paeth(vLeft, vUp, vUpperLeft)).toByte()
|
baLine[pos] = (vCur + paeth(vLeft, vUp, vUpperLeft)).toByte()
|
||||||
|
|
||||||
// if( callback.canApngDebug() ){
|
// if( callback.canApngDebug() ){
|
||||||
// val x = passInfo.xStart + passInfo.xStep * ((pos-1)/sampleBytes)
|
// val x = passInfo.xStart + passInfo.xStep * ((pos-1)/sampleBytes)
|
||||||
// val y = passInfo.yStart + passInfo.yStep * passY
|
// val y = passInfo.yStart + passInfo.yStep * passY
|
||||||
// callback.onApngDebug("paeth pos=$pos,x=$x,y=$y,left=$vLeft,up=$vUp,ul=$vUpperLeft,cur=$vCur,paeth=${paeth(vLeft, vUp, vUpperLeft)}")
|
// callback.onApngDebug("paeth pos=$pos,x=$x,y=$y,left=$vLeft,up=$vUp,ul=$vUpperLeft,cur=$vCur,paeth=${paeth(vLeft, vUp, vUpperLeft)}")
|
||||||
// }
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// render scanline
|
// render scanline
|
||||||
bitmapPointer.setXY(
|
bitmapPointer.setXY(
|
||||||
x=passInfo.xStart,
|
x = passInfo.xStart,
|
||||||
y=passInfo.yStart + passInfo.yStep * passY,
|
y = passInfo.yStart + passInfo.yStep * passY,
|
||||||
step=passInfo.xStep
|
step = passInfo.xStep
|
||||||
)
|
)
|
||||||
renderScanLineFunc(baLine)
|
renderScanLineFunc(baLine)
|
||||||
|
|
||||||
// save previous line
|
// save previous line
|
||||||
baPreviousLine?.let { linePool.add(it) }
|
scanLinePool.recycle(baPreviousLine)
|
||||||
baPreviousLine = baLine
|
baPreviousLine = baLine
|
||||||
|
|
||||||
if(++ passY >= passHeight) {
|
if(++ passY >= passHeight) {
|
||||||
|
@ -607,14 +607,14 @@ internal class IdatDecoder(
|
||||||
while(! inflater.needsInput()) {
|
while(! inflater.needsInput()) {
|
||||||
val buffer = inflateBufferPool.obtain()
|
val buffer = inflateBufferPool.obtain()
|
||||||
val nInflated = inflater.inflate(buffer)
|
val nInflated = inflater.inflate(buffer)
|
||||||
if( nInflated <= 0 ){
|
if(nInflated <= 0) {
|
||||||
inflateBufferPool.recycle(buffer)
|
inflateBufferPool.recycle(buffer)
|
||||||
}else{
|
} else {
|
||||||
inflateBufferQueue.add(ByteSequence(buffer, 0, nInflated))
|
inflateBufferQueue.add(ByteSequence(buffer, 0, nInflated))
|
||||||
// キューに追加したデータをScanLine単位で消費する
|
// キューに追加したデータをScanLine単位で消費する
|
||||||
while(! isCompleted && readScanLine()) {
|
while(! isCompleted && readScanLine()) {
|
||||||
}
|
}
|
||||||
if(isCompleted){
|
if(isCompleted) {
|
||||||
inflateBufferQueue.clear()
|
inflateBufferQueue.clear()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
package jp.juggler.apng
|
|
||||||
|
|
||||||
class ParseError(message: String) : IllegalArgumentException(message)
|
|
|
@ -2,14 +2,8 @@ package jp.juggler.apng.util
|
||||||
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
internal class BufferPool(private val arraySize:Int){
|
internal class BufferPool(private val arraySize : Int) {
|
||||||
private val list =LinkedList<ByteArray>()
|
private val list = LinkedList<ByteArray>()
|
||||||
|
fun obtain() : ByteArray = if(list.isEmpty()) ByteArray(arraySize) else list.removeFirst()
|
||||||
fun recycle(array: ByteArray) {
|
fun recycle(array : ByteArray?) = array?.let { list.add(it) }
|
||||||
list.add( array)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun obtain(): ByteArray {
|
|
||||||
return if( list.isEmpty() ) ByteArray(arraySize) else list.removeFirst()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package jp.juggler.apng.util
|
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
|
internal fun ByteArray.getUInt8(pos : Int) = get(pos).toInt() and 255
|
||||||
|
|
||||||
|
@ -16,10 +16,11 @@ internal class ByteSequence(
|
||||||
var offset : Int,
|
var offset : Int,
|
||||||
var length : Int
|
var length : Int
|
||||||
) {
|
) {
|
||||||
|
|
||||||
constructor(ba : ByteArray) : this(ba, 0, ba.size)
|
constructor(ba : ByteArray) : this(ba, 0, ba.size)
|
||||||
|
|
||||||
private inline fun <T> readX(dataSize : Int, block : () -> T) : T {
|
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()
|
val v = block()
|
||||||
offset += dataSize
|
offset += dataSize
|
||||||
length -= dataSize
|
length -= dataSize
|
||||||
|
|
|
@ -2,41 +2,34 @@ package jp.juggler.apng.util
|
||||||
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
internal class ByteSequenceQueue(private val bufferRecycler :(ByteSequence)->Unit) {
|
internal class ByteSequenceQueue(private val bufferRecycler : (ByteSequence) -> Unit) {
|
||||||
|
|
||||||
private val list = LinkedList<ByteSequence>()
|
private val list = LinkedList<ByteSequence>()
|
||||||
|
|
||||||
val remain: Int
|
val remain : Int
|
||||||
get() = list.sumBy { it.length }
|
get() = list.sumBy { it.length }
|
||||||
|
|
||||||
fun add(range: ByteSequence) {
|
fun add(range : ByteSequence) =list.add(range)
|
||||||
list.add(range)
|
|
||||||
}
|
fun clear() = list.also{ it.forEach(bufferRecycler) }.clear()
|
||||||
|
|
||||||
fun clear() {
|
fun readBytes(dst : ByteArray, offset : Int, length : Int) : Int {
|
||||||
for( item in list ){
|
var dstOffset = offset
|
||||||
bufferRecycler(item)
|
var dstRemain = length
|
||||||
}
|
while(dstRemain > 0 && list.isNotEmpty()) {
|
||||||
list.clear()
|
val item = list.first()
|
||||||
}
|
if(item.length <= 0) {
|
||||||
|
bufferRecycler(item)
|
||||||
fun readBytes(dst: ByteArray, offset: Int, length: Int): Int {
|
list.removeFirst()
|
||||||
var dstOffset = offset
|
} else {
|
||||||
var dstRemain = length
|
val delta = Math.min(item.length, dstRemain)
|
||||||
while (dstRemain > 0 && list.isNotEmpty()) {
|
System.arraycopy(item.array, item.offset, dst, dstOffset, delta)
|
||||||
val item = list.first()
|
dstOffset += delta
|
||||||
if (item.length <= 0) {
|
dstRemain -= delta
|
||||||
bufferRecycler(item)
|
item.offset += delta
|
||||||
list.removeFirst()
|
item.length -= delta
|
||||||
}else {
|
}
|
||||||
val delta = Math.min(item.length, dstRemain)
|
}
|
||||||
System.arraycopy(item.array, item.offset, dst, dstOffset, delta)
|
return length - dstRemain
|
||||||
dstOffset += delta
|
}
|
||||||
dstRemain -= delta
|
|
||||||
item.offset += delta
|
|
||||||
item.length -= delta
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return length - dstRemain
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,66 +1,66 @@
|
||||||
package jp.juggler.apng.util
|
package jp.juggler.apng.util
|
||||||
|
|
||||||
import jp.juggler.apng.ParseError
|
import jp.juggler.apng.ApngParseError
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.util.zip.CRC32
|
import java.util.zip.CRC32
|
||||||
|
|
||||||
internal class StreamTokenizer(val inStream: InputStream) {
|
internal class StreamTokenizer(val inStream : InputStream) {
|
||||||
|
|
||||||
fun skipBytes(size: Long) {
|
fun skipBytes(size : Long) {
|
||||||
var nRead = 0L
|
var nRead = 0L
|
||||||
while (true) {
|
while(true) {
|
||||||
val remain = size - nRead
|
val remain = size - nRead
|
||||||
if (remain <= 0) break
|
if(remain <= 0) break
|
||||||
val delta = inStream.skip(size - nRead)
|
val delta = inStream.skip(size - nRead)
|
||||||
if (delta <= 0) throw ParseError("skipBytes: unexpected EoS")
|
if(delta <= 0) throw ApngParseError("skipBytes: unexpected EoS")
|
||||||
nRead += delta
|
nRead += delta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun readBytes(size: Int): ByteArray {
|
fun readBytes(size : Int) : ByteArray {
|
||||||
val dst = ByteArray(size)
|
val dst = ByteArray(size)
|
||||||
var nRead = 0
|
var nRead = 0
|
||||||
while (true) {
|
while(true) {
|
||||||
val remain = size - nRead
|
val remain = size - nRead
|
||||||
if (remain <= 0) break
|
if(remain <= 0) break
|
||||||
val delta = inStream.read(dst, nRead, size - nRead)
|
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
|
nRead += delta
|
||||||
}
|
}
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readByte(): Int {
|
private fun readByte() : Int {
|
||||||
val b = inStream.read()
|
val b = inStream.read()
|
||||||
if( b == -1 ) throw ParseError("readBytes: unexpected EoS")
|
if(b == - 1) throw ApngParseError("readByte: unexpected EoS")
|
||||||
return b and 0xff
|
return b and 0xff
|
||||||
}
|
}
|
||||||
|
|
||||||
fun readInt32(): Int {
|
fun readInt32() : Int {
|
||||||
val b0 = readByte()
|
val b0 = readByte()
|
||||||
val b1 = readByte()
|
val b1 = readByte()
|
||||||
val b2 = readByte()
|
val b2 = readByte()
|
||||||
val b3 = readByte()
|
val b3 = readByte()
|
||||||
|
|
||||||
return (b0 shl 24) or (b1 shl 16) or (b2 shl 8) or b3
|
return (b0 shl 24) or (b1 shl 16) or (b2 shl 8) or b3
|
||||||
}
|
}
|
||||||
|
|
||||||
fun readInt32(crc32: CRC32): Int {
|
fun readInt32(crc32 : CRC32) : Int {
|
||||||
val ba = readBytes(4)
|
val ba = readBytes(4)
|
||||||
crc32.update(ba)
|
crc32.update(ba)
|
||||||
val b0 = ba[0].toInt() and 255
|
val b0 = ba[0].toInt() and 255
|
||||||
val b1 = ba[1].toInt() and 255
|
val b1 = ba[1].toInt() and 255
|
||||||
val b2 = ba[2].toInt() and 255
|
val b2 = ba[2].toInt() and 255
|
||||||
val b3 = ba[3].toInt() and 255
|
val b3 = ba[3].toInt() and 255
|
||||||
|
|
||||||
return (b0 shl 24) or (b1 shl 16) or (b2 shl 8) or b3
|
return (b0 shl 24) or (b1 shl 16) or (b2 shl 8) or b3
|
||||||
}
|
}
|
||||||
|
|
||||||
fun readUInt32(): Long {
|
fun readUInt32() : Long {
|
||||||
val b0 = readByte()
|
val b0 = readByte()
|
||||||
val b1 = readByte()
|
val b1 = readByte()
|
||||||
val b2 = readByte()
|
val b2 = readByte()
|
||||||
val b3 = readByte()
|
val b3 = readByte()
|
||||||
return (b0.toLong() shl 24) or ((b1 shl 16) or (b2 shl 8) or b3).toLong()
|
return (b0.toLong() shl 24) or ((b1 shl 16) or (b2 shl 8) or b3).toLong()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -24,11 +24,9 @@ class ApngFrames private constructor(
|
||||||
private const val DELAY_AFTER_END = 3000L
|
private const val DELAY_AFTER_END = 3000L
|
||||||
|
|
||||||
// アニメーションフレームの描画に使う
|
// アニメーションフレームの描画に使う
|
||||||
private val sPaintDontBlend : Paint by lazy {
|
private val sPaintDontBlend = Paint().apply {
|
||||||
val paint = Paint()
|
xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC)
|
||||||
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC)
|
isFilterBitmap = true
|
||||||
paint.isFilterBitmap = true
|
|
||||||
paint
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createBlankBitmap(w : Int, h : Int) =
|
private fun createBlankBitmap(w : Int, h : Int) =
|
||||||
|
@ -45,7 +43,7 @@ class ApngFrames private constructor(
|
||||||
|
|
||||||
val wSrc = src.width
|
val wSrc = src.width
|
||||||
val hSrc = src.height
|
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) {
|
return if(recycleSrc) {
|
||||||
src
|
src
|
||||||
} else {
|
} else {
|
||||||
|
@ -75,8 +73,8 @@ class ApngFrames private constructor(
|
||||||
return b2
|
return b2
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun toAndroidBitmap(src : ApngBitmap) : Bitmap {
|
private fun toAndroidBitmap(src : ApngBitmap) =
|
||||||
return Bitmap.createBitmap(
|
Bitmap.createBitmap(
|
||||||
src.colors, // int[] 配列
|
src.colors, // int[] 配列
|
||||||
0, // offset
|
0, // offset
|
||||||
src.width, //stride
|
src.width, //stride
|
||||||
|
@ -84,7 +82,6 @@ class ApngFrames private constructor(
|
||||||
src.height, //height
|
src.height, //height
|
||||||
Bitmap.Config.ARGB_8888
|
Bitmap.Config.ARGB_8888
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
private fun toAndroidBitmap(src : ApngBitmap, size_max : Int) =
|
private fun toAndroidBitmap(src : ApngBitmap, size_max : Int) =
|
||||||
scaleBitmap(size_max, toAndroidBitmap(src))
|
scaleBitmap(size_max, toAndroidBitmap(src))
|
||||||
|
@ -99,11 +96,8 @@ class ApngFrames private constructor(
|
||||||
try {
|
try {
|
||||||
ApngDecoder.parseStream(inStream, result)
|
ApngDecoder.parseStream(inStream, result)
|
||||||
result.onParseComplete()
|
result.onParseComplete()
|
||||||
return if(result.defaultImage != null || result.frames?.isNotEmpty() == true) {
|
return result.takeIf { result.defaultImage != null || result.frames?.isNotEmpty() == true }
|
||||||
result
|
?: throw RuntimeException("APNG has no image")
|
||||||
} else {
|
|
||||||
throw RuntimeException("APNG has no image")
|
|
||||||
}
|
|
||||||
} catch(ex : Throwable) {
|
} catch(ex : Throwable) {
|
||||||
result.dispose()
|
result.dispose()
|
||||||
throw ex
|
throw ex
|
||||||
|
|
Loading…
Reference in New Issue