リファクタ
This commit is contained in:
parent
84383661d8
commit
8c46295631
|
@ -1,9 +1,9 @@
|
|||
package jp.juggler.apng
|
||||
|
||||
class Apng {
|
||||
var header: ApngImageHeader? = null
|
||||
var background: ApngBackground? = null
|
||||
var animationControl: ApngAnimationControl? = null
|
||||
internal var palette: ApngPalette? = null
|
||||
internal var transparentColor: ApngTransparentColor? = null
|
||||
var header : ApngImageHeader? = null
|
||||
var background : ApngBackground? = null
|
||||
var animationControl : ApngAnimationControl? = null
|
||||
internal var palette : ApngPalette? = null
|
||||
internal var transparentColor : ApngTransparentColor? = null
|
||||
}
|
||||
|
|
|
@ -4,29 +4,28 @@ package jp.juggler.apng
|
|||
|
||||
import jp.juggler.apng.util.ByteSequence
|
||||
|
||||
|
||||
class ApngAnimationControl internal constructor(src: ByteSequence) {
|
||||
|
||||
companion object {
|
||||
const val PLAY_INDEFINITELY =0
|
||||
}
|
||||
|
||||
// This must equal the number of `fcTL` chunks.
|
||||
// 0 is not a valid value.
|
||||
// 1 is a valid value for a single-frame APNG.
|
||||
val numFrames: Int
|
||||
|
||||
// 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.
|
||||
val numPlays: Int
|
||||
|
||||
init {
|
||||
numFrames = src.readInt32()
|
||||
numPlays = src.readInt32()
|
||||
}
|
||||
|
||||
override fun toString() ="ApngAnimationControl(numFrames=$numFrames,numPlays=$numPlays)"
|
||||
|
||||
val isFinite :Boolean
|
||||
get() = numPlays > PLAY_INDEFINITELY
|
||||
class ApngAnimationControl internal constructor(src : ByteSequence) {
|
||||
|
||||
companion object {
|
||||
const val PLAY_INDEFINITELY = 0
|
||||
}
|
||||
|
||||
// This must equal the number of `fcTL` chunks.
|
||||
// 0 is not a valid value.
|
||||
// 1 is a valid value for a single-frame APNG.
|
||||
val numFrames : Int
|
||||
|
||||
// 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.
|
||||
val numPlays : Int
|
||||
|
||||
init {
|
||||
numFrames = src.readInt32()
|
||||
numPlays = src.readInt32()
|
||||
}
|
||||
|
||||
override fun toString() = "ApngAnimationControl(numFrames=$numFrames,numPlays=$numPlays)"
|
||||
|
||||
val isFinite : Boolean
|
||||
get() = numPlays > PLAY_INDEFINITELY
|
||||
}
|
||||
|
|
|
@ -4,34 +4,36 @@ package jp.juggler.apng
|
|||
|
||||
import jp.juggler.apng.util.ByteSequence
|
||||
|
||||
class ApngBackground internal constructor(colorType: ColorType, src: ByteSequence) {
|
||||
|
||||
val red: Int
|
||||
val green: Int
|
||||
val blue: Int
|
||||
val index: Int
|
||||
|
||||
init {
|
||||
when (colorType) {
|
||||
ColorType.GREY, ColorType.GREY_ALPHA -> {
|
||||
val v = src.readUInt16()
|
||||
red = v
|
||||
green = v
|
||||
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
|
||||
blue = -1
|
||||
index = src.readUInt8()
|
||||
}
|
||||
}
|
||||
}
|
||||
class ApngBackground internal constructor(colorType : ColorType, src : ByteSequence) {
|
||||
|
||||
val red : Int
|
||||
val green : Int
|
||||
val blue : Int
|
||||
val index : Int
|
||||
|
||||
init {
|
||||
when(colorType) {
|
||||
ColorType.GREY, ColorType.GREY_ALPHA -> {
|
||||
val v = src.readUInt16()
|
||||
red = v
|
||||
green = v
|
||||
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
|
||||
blue = - 1
|
||||
index = src.readUInt8()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -5,35 +5,35 @@ package jp.juggler.apng
|
|||
import jp.juggler.apng.util.StreamTokenizer
|
||||
import java.util.zip.CRC32
|
||||
|
||||
internal class ApngChunk(crc32:CRC32,tokenizer: StreamTokenizer) {
|
||||
val size: Int
|
||||
val type: String
|
||||
|
||||
init {
|
||||
size = tokenizer.readInt32()
|
||||
val typeBytes = tokenizer.readBytes(4)
|
||||
type = typeBytes.toString(Charsets.UTF_8)
|
||||
|
||||
crc32.update(typeBytes)
|
||||
}
|
||||
|
||||
fun readBody(crc32:CRC32,tokenizer: StreamTokenizer): ByteArray {
|
||||
val bytes = tokenizer.readBytes(size)
|
||||
val crcExpect = tokenizer.readUInt32()
|
||||
|
||||
crc32.update(bytes, 0, size)
|
||||
val crcActual = crc32.value
|
||||
if (crcActual != crcExpect) throw ParseError("CRC not match.")
|
||||
|
||||
return bytes
|
||||
}
|
||||
|
||||
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.")
|
||||
}
|
||||
internal class ApngChunk(crc32 : CRC32, tokenizer : StreamTokenizer) {
|
||||
val size : Int
|
||||
val type : String
|
||||
|
||||
init {
|
||||
size = tokenizer.readInt32()
|
||||
val typeBytes = tokenizer.readBytes(4)
|
||||
type = typeBytes.toString(Charsets.UTF_8)
|
||||
|
||||
crc32.update(typeBytes)
|
||||
}
|
||||
|
||||
fun readBody(crc32 : CRC32, tokenizer : StreamTokenizer) : ByteArray {
|
||||
val bytes = tokenizer.readBytes(size)
|
||||
val crcExpect = tokenizer.readUInt32()
|
||||
|
||||
crc32.update(bytes, 0, size)
|
||||
val crcActual = crc32.value
|
||||
if(crcActual != crcExpect) throw ApngParseError("CRC not match.")
|
||||
|
||||
return bytes
|
||||
}
|
||||
|
||||
fun skipBody(tokenizer : StreamTokenizer) =
|
||||
tokenizer.skipBytes((size + 4).toLong())
|
||||
|
||||
|
||||
fun checkCRC(tokenizer : StreamTokenizer, crcActual : Long) {
|
||||
val crcExpect = tokenizer.readUInt32()
|
||||
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)
|
||||
|
||||
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,11 +106,11 @@ 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
|
||||
callback.onAnimationInfo(apng, header,animationControl)
|
||||
callback.onAnimationInfo(apng, header, animationControl)
|
||||
}
|
||||
|
||||
"fcTL" -> {
|
||||
|
@ -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,
|
||||
|
|
|
@ -8,7 +8,7 @@ interface ApngDecoderCallback {
|
|||
// called for debug message
|
||||
fun onApngDebug(message : String) {}
|
||||
|
||||
fun canApngDebug():Boolean = false
|
||||
fun canApngDebug() : Boolean = false
|
||||
|
||||
// called when PNG image header is detected.
|
||||
fun onHeader(apng : Apng, header : ApngImageHeader)
|
||||
|
|
|
@ -1,41 +1,41 @@
|
|||
package jp.juggler.apng
|
||||
|
||||
enum class ColorType(val num:Int ){
|
||||
GREY(0),
|
||||
RGB ( 2),
|
||||
INDEX( 3),
|
||||
GREY_ALPHA ( 4),
|
||||
RGBA ( 6),
|
||||
enum class ColorType(val num : Int) {
|
||||
GREY(0),
|
||||
RGB(2),
|
||||
INDEX(3),
|
||||
GREY_ALPHA(4),
|
||||
RGBA(6),
|
||||
}
|
||||
|
||||
enum class CompressionMethod(val num:Int ){
|
||||
Standard(0)
|
||||
enum class CompressionMethod(val num : Int) {
|
||||
Standard(0)
|
||||
}
|
||||
|
||||
enum class FilterMethod(val num:Int ){
|
||||
Standard(0)
|
||||
enum class FilterMethod(val num : Int) {
|
||||
Standard(0)
|
||||
}
|
||||
|
||||
enum class InterlaceMethod(val num:Int ){
|
||||
None(0),
|
||||
Standard(1)
|
||||
enum class InterlaceMethod(val num : Int) {
|
||||
None(0),
|
||||
Standard(1)
|
||||
}
|
||||
|
||||
enum class FilterType(val num:Int ){
|
||||
None(0),
|
||||
Sub(1),
|
||||
Up(2),
|
||||
Average(3),
|
||||
Paeth(4)
|
||||
enum class FilterType(val num : Int) {
|
||||
None(0),
|
||||
Sub(1),
|
||||
Up(2),
|
||||
Average(3),
|
||||
Paeth(4)
|
||||
}
|
||||
|
||||
enum class DisposeOp(val num :Int){
|
||||
None(0),
|
||||
Background(1),
|
||||
Previous(2)
|
||||
enum class DisposeOp(val num : Int) {
|
||||
None(0),
|
||||
Background(1),
|
||||
Previous(2)
|
||||
}
|
||||
|
||||
enum class BlendOp(val num :Int){
|
||||
Source(0),
|
||||
Over(1)
|
||||
enum class BlendOp(val num : Int) {
|
||||
Source(0),
|
||||
Over(1)
|
||||
}
|
||||
|
|
|
@ -4,40 +4,40 @@ package jp.juggler.apng
|
|||
|
||||
import jp.juggler.apng.util.ByteSequence
|
||||
|
||||
|
||||
class ApngFrameControl internal constructor(src: ByteSequence) {
|
||||
|
||||
val width: Int
|
||||
val height: Int
|
||||
val xOffset: Int
|
||||
val yOffset: Int
|
||||
val delayNum: Int
|
||||
val delayDen: Int
|
||||
val disposeOp: DisposeOp
|
||||
val blendOp: BlendOp
|
||||
|
||||
init {
|
||||
width = src.readInt32()
|
||||
height = src.readInt32()
|
||||
xOffset = src.readInt32()
|
||||
yOffset = src.readInt32()
|
||||
delayNum = src.readUInt16()
|
||||
delayDen = src.readUInt16().let{ if(it==0) 100 else it}
|
||||
|
||||
var num:Int
|
||||
|
||||
num = src.readUInt8()
|
||||
disposeOp = DisposeOp.values().first{it.num==num}
|
||||
|
||||
num = src.readUInt8()
|
||||
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)"
|
||||
|
||||
val delayMilliseconds : Long
|
||||
get() = when(delayDen) {
|
||||
1000 -> delayNum.toLong()
|
||||
else -> (1000f * delayNum.toFloat() / delayDen.toFloat() + 0.5f).toLong()
|
||||
}
|
||||
class ApngFrameControl internal constructor(src : ByteSequence) {
|
||||
|
||||
val width : Int
|
||||
val height : Int
|
||||
val xOffset : Int
|
||||
val yOffset : Int
|
||||
val delayNum : Int
|
||||
val delayDen : Int
|
||||
val disposeOp : DisposeOp
|
||||
val blendOp : BlendOp
|
||||
|
||||
init {
|
||||
width = src.readInt32()
|
||||
height = src.readInt32()
|
||||
xOffset = src.readInt32()
|
||||
yOffset = src.readInt32()
|
||||
delayNum = src.readUInt16()
|
||||
delayDen = src.readUInt16().let { if(it == 0) 100 else it }
|
||||
|
||||
var num : Int
|
||||
|
||||
num = src.readUInt8()
|
||||
disposeOp = DisposeOp.values().first { it.num == num }
|
||||
|
||||
num = src.readUInt8()
|
||||
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)"
|
||||
|
||||
val delayMilliseconds : Long
|
||||
get() = when(delayDen) {
|
||||
1000 -> delayNum.toLong()
|
||||
else -> (1000f * delayNum.toFloat() / delayDen.toFloat() + 0.5f).toLong()
|
||||
}
|
||||
}
|
|
@ -5,37 +5,39 @@ package jp.juggler.apng
|
|||
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
|
||||
val colorType: ColorType
|
||||
val compressionMethod: CompressionMethod
|
||||
val filterMethod: FilterMethod
|
||||
val interlaceMethod: InterlaceMethod
|
||||
|
||||
init {
|
||||
|
||||
width = src.readInt32()
|
||||
height = src.readInt32()
|
||||
if(width <=0 || height <=0 ) throw ParseError("w=$width,h=$height is too small")
|
||||
|
||||
bitDepth = src.readUInt8()
|
||||
|
||||
var num:Int
|
||||
//
|
||||
num =src.readUInt8()
|
||||
colorType = ColorType.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()
|
||||
interlaceMethod = InterlaceMethod.values().first { it.num==num }
|
||||
}
|
||||
|
||||
override fun toString() = "ApngImageHeader(w=$width,h=$height,bits=$bitDepth,color=$colorType,interlace=$interlaceMethod)"
|
||||
class ApngImageHeader internal constructor(src : ByteSequence) {
|
||||
|
||||
val width : Int
|
||||
val height : Int
|
||||
val bitDepth : Int
|
||||
val colorType : ColorType
|
||||
val compressionMethod : CompressionMethod
|
||||
val filterMethod : FilterMethod
|
||||
val interlaceMethod : InterlaceMethod
|
||||
|
||||
init {
|
||||
|
||||
width = src.readInt32()
|
||||
height = src.readInt32()
|
||||
if(width <= 0 || height <= 0) throw ApngParseError("w=$width,h=$height is too small")
|
||||
|
||||
bitDepth = src.readUInt8()
|
||||
|
||||
var num : Int
|
||||
//
|
||||
num = src.readUInt8()
|
||||
colorType = ColorType.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()
|
||||
interlaceMethod = InterlaceMethod.values().first { it.num == num }
|
||||
}
|
||||
|
||||
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(
|
||||
src : ByteArray // repeat of R,G,B
|
||||
) {
|
||||
|
||||
companion object {
|
||||
// full opaque black
|
||||
const val OPAQUE = 255 shl 24
|
||||
|
@ -18,14 +19,14 @@ class ApngPalette(
|
|||
|
||||
init {
|
||||
val entryCount = src.size / 3
|
||||
list = IntArray( entryCount)
|
||||
list = IntArray(entryCount)
|
||||
var pos = 0
|
||||
for(i in 0 until entryCount) {
|
||||
list[i] = OPAQUE or
|
||||
(src.getUInt8(pos) shl 16) or
|
||||
(src.getUInt8(pos+1) shl 8) or
|
||||
src.getUInt8(pos+2)
|
||||
pos+=3
|
||||
(src.getUInt8(pos + 1) shl 8) or
|
||||
src.getUInt8(pos + 2)
|
||||
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
|
||||
|
||||
class ApngTransparentColor internal constructor(isGreyScale:Boolean, src: ByteSequence) {
|
||||
val red:Int
|
||||
val green:Int
|
||||
val blue:Int
|
||||
init{
|
||||
if( isGreyScale){
|
||||
val v = src.readUInt16()
|
||||
red =v
|
||||
green =v
|
||||
blue =v
|
||||
}else{
|
||||
red =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)
|
||||
class ApngTransparentColor internal constructor(isGreyScale : Boolean, src : ByteSequence) {
|
||||
val red : Int
|
||||
val green : Int
|
||||
val blue : Int
|
||||
|
||||
init {
|
||||
if(isGreyScale) {
|
||||
val v = src.readUInt16()
|
||||
red = v
|
||||
green = v
|
||||
blue = v
|
||||
} else {
|
||||
red = 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)
|
||||
}
|
|
@ -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
|
||||
|
||||
|
@ -358,7 +359,7 @@ internal class IdatDecoder(
|
|||
|
||||
private fun renderIndex2(baLine : ByteArray) {
|
||||
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 {
|
||||
throw ParseError("bitDepth $bitDepth is not supported for $colorType")
|
||||
throw ApngParseError("bitDepth $bitDepth is not supported for $colorType")
|
||||
}
|
||||
|
||||
private fun selectRenderFunc() = when(colorType) {
|
||||
|
@ -443,11 +444,11 @@ 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) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -464,20 +465,20 @@ internal class IdatDecoder(
|
|||
|
||||
// スキャンラインを読む。行を処理したらtrueを返す
|
||||
private fun readScanLine() : Boolean {
|
||||
|
||||
if(inflateBufferQueue.remain < scanLineBytes){
|
||||
|
||||
if(inflateBufferQueue.remain < scanLineBytes) {
|
||||
// not yet enough data to process scanline
|
||||
return false
|
||||
}
|
||||
|
||||
val baLine = linePool.removeFirst()
|
||||
val baLine = scanLinePool.obtain()
|
||||
inflateBufferQueue.readBytes(baLine, 0, scanLineBytes)
|
||||
|
||||
val filterNum = baLine.getUInt8(0)
|
||||
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) {
|
||||
FilterType.None -> {
|
||||
}
|
||||
|
@ -485,23 +486,22 @@ internal class IdatDecoder(
|
|||
FilterType.Sub -> {
|
||||
for(pos in 1 until scanLineBytes) {
|
||||
val vCur = baLine.getUInt8(pos)
|
||||
val leftPos = pos -sampleBytes
|
||||
val vLeft = if(leftPos <=0 ) 0 else baLine.getUInt8(leftPos)
|
||||
|
||||
val leftPos = pos - sampleBytes
|
||||
val vLeft = if(leftPos <= 0) 0 else baLine.getUInt8(leftPos)
|
||||
|
||||
baLine[pos] = (vCur + vLeft).toByte()
|
||||
|
||||
// if( callback.canApngDebug() ){
|
||||
// val x = passInfo.xStart + passInfo.xStep * ((pos-1)/sampleBytes)
|
||||
// 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}")
|
||||
// }
|
||||
|
||||
// if( callback.canApngDebug() ){
|
||||
// val x = passInfo.xStart + passInfo.xStep * ((pos-1)/sampleBytes)
|
||||
// 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}")
|
||||
// }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
FilterType.Up -> {
|
||||
val baPreviousLine=this.baPreviousLine
|
||||
val baPreviousLine = this.baPreviousLine
|
||||
for(pos in 1 until scanLineBytes) {
|
||||
val vCur = baLine.getUInt8(pos)
|
||||
val vUp = baPreviousLine?.getUInt8(pos) ?: 0
|
||||
|
@ -510,47 +510,47 @@ internal class IdatDecoder(
|
|||
}
|
||||
|
||||
FilterType.Average -> {
|
||||
val baPreviousLine=this.baPreviousLine
|
||||
val baPreviousLine = this.baPreviousLine
|
||||
for(pos in 1 until scanLineBytes) {
|
||||
val vCur = baLine.getUInt8(pos)
|
||||
val leftPos = pos -sampleBytes
|
||||
val vLeft = if(leftPos <=0 ) 0 else baLine.getUInt8(leftPos)
|
||||
val leftPos = pos - sampleBytes
|
||||
val vLeft = if(leftPos <= 0) 0 else baLine.getUInt8(leftPos)
|
||||
val vUp = baPreviousLine?.getUInt8(pos) ?: 0
|
||||
baLine[pos] = (vCur + ((vLeft + vUp) shr 1)).toByte()
|
||||
}
|
||||
}
|
||||
|
||||
FilterType.Paeth -> {
|
||||
val baPreviousLine=this.baPreviousLine
|
||||
val baPreviousLine = this.baPreviousLine
|
||||
for(pos in 1 until scanLineBytes) {
|
||||
val vCur = baLine.getUInt8(pos)
|
||||
val leftPos = pos -sampleBytes
|
||||
val vLeft = if(leftPos <=0 ) 0 else baLine.getUInt8(leftPos)
|
||||
val leftPos = pos - sampleBytes
|
||||
val vLeft = if(leftPos <= 0) 0 else baLine.getUInt8(leftPos)
|
||||
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()
|
||||
|
||||
// if( callback.canApngDebug() ){
|
||||
// val x = passInfo.xStart + passInfo.xStep * ((pos-1)/sampleBytes)
|
||||
// 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)}")
|
||||
// }
|
||||
|
||||
// if( callback.canApngDebug() ){
|
||||
// val x = passInfo.xStart + passInfo.xStep * ((pos-1)/sampleBytes)
|
||||
// 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)}")
|
||||
// }
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// render scanline
|
||||
bitmapPointer.setXY(
|
||||
x=passInfo.xStart,
|
||||
y=passInfo.yStart + passInfo.yStep * passY,
|
||||
step=passInfo.xStep
|
||||
x = passInfo.xStart,
|
||||
y = passInfo.yStart + passInfo.yStep * passY,
|
||||
step = passInfo.xStep
|
||||
)
|
||||
renderScanLineFunc(baLine)
|
||||
|
||||
// save previous line
|
||||
baPreviousLine?.let { linePool.add(it) }
|
||||
scanLinePool.recycle(baPreviousLine)
|
||||
baPreviousLine = baLine
|
||||
|
||||
if(++ passY >= passHeight) {
|
||||
|
@ -607,14 +607,14 @@ internal class IdatDecoder(
|
|||
while(! inflater.needsInput()) {
|
||||
val buffer = inflateBufferPool.obtain()
|
||||
val nInflated = inflater.inflate(buffer)
|
||||
if( nInflated <= 0 ){
|
||||
if(nInflated <= 0) {
|
||||
inflateBufferPool.recycle(buffer)
|
||||
}else{
|
||||
} else {
|
||||
inflateBufferQueue.add(ByteSequence(buffer, 0, nInflated))
|
||||
// キューに追加したデータをScanLine単位で消費する
|
||||
while(! isCompleted && readScanLine()) {
|
||||
}
|
||||
if(isCompleted){
|
||||
if(isCompleted) {
|
||||
inflateBufferQueue.clear()
|
||||
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.*
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
internal class BufferPool(private val arraySize : Int) {
|
||||
private val list = LinkedList<ByteArray>()
|
||||
fun obtain() : ByteArray = if(list.isEmpty()) ByteArray(arraySize) else list.removeFirst()
|
||||
fun recycle(array : ByteArray?) = array?.let { list.add(it) }
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -2,41 +2,34 @@ package jp.juggler.apng.util
|
|||
|
||||
import java.util.*
|
||||
|
||||
internal class ByteSequenceQueue(private val bufferRecycler :(ByteSequence)->Unit) {
|
||||
|
||||
private val list = LinkedList<ByteSequence>()
|
||||
|
||||
val remain: Int
|
||||
get() = list.sumBy { it.length }
|
||||
|
||||
fun add(range: ByteSequence) {
|
||||
list.add(range)
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
for( item in list ){
|
||||
bufferRecycler(item)
|
||||
}
|
||||
list.clear()
|
||||
}
|
||||
|
||||
fun readBytes(dst: ByteArray, offset: Int, length: Int): Int {
|
||||
var dstOffset = offset
|
||||
var dstRemain = length
|
||||
while (dstRemain > 0 && list.isNotEmpty()) {
|
||||
val item = list.first()
|
||||
if (item.length <= 0) {
|
||||
bufferRecycler(item)
|
||||
list.removeFirst()
|
||||
}else {
|
||||
val delta = Math.min(item.length, dstRemain)
|
||||
System.arraycopy(item.array, item.offset, dst, dstOffset, delta)
|
||||
dstOffset += delta
|
||||
dstRemain -= delta
|
||||
item.offset += delta
|
||||
item.length -= delta
|
||||
}
|
||||
}
|
||||
return length - dstRemain
|
||||
}
|
||||
internal class ByteSequenceQueue(private val bufferRecycler : (ByteSequence) -> Unit) {
|
||||
|
||||
private val list = LinkedList<ByteSequence>()
|
||||
|
||||
val remain : Int
|
||||
get() = list.sumBy { it.length }
|
||||
|
||||
fun add(range : ByteSequence) =list.add(range)
|
||||
|
||||
fun clear() = list.also{ it.forEach(bufferRecycler) }.clear()
|
||||
|
||||
fun readBytes(dst : ByteArray, offset : Int, length : Int) : Int {
|
||||
var dstOffset = offset
|
||||
var dstRemain = length
|
||||
while(dstRemain > 0 && list.isNotEmpty()) {
|
||||
val item = list.first()
|
||||
if(item.length <= 0) {
|
||||
bufferRecycler(item)
|
||||
list.removeFirst()
|
||||
} else {
|
||||
val delta = Math.min(item.length, dstRemain)
|
||||
System.arraycopy(item.array, item.offset, dst, dstOffset, delta)
|
||||
dstOffset += delta
|
||||
dstRemain -= delta
|
||||
item.offset += delta
|
||||
item.length -= delta
|
||||
}
|
||||
}
|
||||
return length - dstRemain
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,66 +1,66 @@
|
|||
package jp.juggler.apng.util
|
||||
|
||||
import jp.juggler.apng.ParseError
|
||||
import jp.juggler.apng.ApngParseError
|
||||
import java.io.InputStream
|
||||
import java.util.zip.CRC32
|
||||
|
||||
internal class StreamTokenizer(val inStream: InputStream) {
|
||||
|
||||
fun skipBytes(size: Long) {
|
||||
var nRead = 0L
|
||||
while (true) {
|
||||
val remain = size - nRead
|
||||
if (remain <= 0) break
|
||||
val delta = inStream.skip(size - nRead)
|
||||
if (delta <= 0) throw ParseError("skipBytes: unexpected EoS")
|
||||
nRead += delta
|
||||
}
|
||||
}
|
||||
|
||||
fun readBytes(size: Int): ByteArray {
|
||||
val dst = ByteArray(size)
|
||||
var nRead = 0
|
||||
while (true) {
|
||||
val remain = size - nRead
|
||||
if (remain <= 0) break
|
||||
val delta = inStream.read(dst, nRead, size - nRead)
|
||||
if (delta < 0) throw ParseError("readBytes: unexpected EoS")
|
||||
nRead += delta
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
private fun readByte(): Int {
|
||||
val b = inStream.read()
|
||||
if( b == -1 ) throw ParseError("readBytes: unexpected EoS")
|
||||
return b and 0xff
|
||||
}
|
||||
|
||||
fun readInt32(): Int {
|
||||
val b0 = readByte()
|
||||
val b1 = readByte()
|
||||
val b2 = readByte()
|
||||
val b3 = readByte()
|
||||
|
||||
return (b0 shl 24) or (b1 shl 16) or (b2 shl 8) or b3
|
||||
}
|
||||
|
||||
fun readInt32(crc32: CRC32): Int {
|
||||
val ba = readBytes(4)
|
||||
crc32.update(ba)
|
||||
val b0 = ba[0].toInt() and 255
|
||||
val b1 = ba[1].toInt() and 255
|
||||
val b2 = ba[2].toInt() and 255
|
||||
val b3 = ba[3].toInt() and 255
|
||||
|
||||
return (b0 shl 24) or (b1 shl 16) or (b2 shl 8) or b3
|
||||
}
|
||||
|
||||
fun readUInt32(): Long {
|
||||
val b0 = readByte()
|
||||
val b1 = readByte()
|
||||
val b2 = readByte()
|
||||
val b3 = readByte()
|
||||
return (b0.toLong() shl 24) or ((b1 shl 16) or (b2 shl 8) or b3).toLong()
|
||||
}
|
||||
internal class StreamTokenizer(val inStream : InputStream) {
|
||||
|
||||
fun skipBytes(size : Long) {
|
||||
var nRead = 0L
|
||||
while(true) {
|
||||
val remain = size - nRead
|
||||
if(remain <= 0) break
|
||||
val delta = inStream.skip(size - nRead)
|
||||
if(delta <= 0) throw ApngParseError("skipBytes: unexpected EoS")
|
||||
nRead += delta
|
||||
}
|
||||
}
|
||||
|
||||
fun readBytes(size : Int) : ByteArray {
|
||||
val dst = ByteArray(size)
|
||||
var nRead = 0
|
||||
while(true) {
|
||||
val remain = size - nRead
|
||||
if(remain <= 0) break
|
||||
val delta = inStream.read(dst, nRead, size - nRead)
|
||||
if(delta < 0) throw ApngParseError("readBytes: unexpected EoS")
|
||||
nRead += delta
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
private fun readByte() : Int {
|
||||
val b = inStream.read()
|
||||
if(b == - 1) throw ApngParseError("readByte: unexpected EoS")
|
||||
return b and 0xff
|
||||
}
|
||||
|
||||
fun readInt32() : Int {
|
||||
val b0 = readByte()
|
||||
val b1 = readByte()
|
||||
val b2 = readByte()
|
||||
val b3 = readByte()
|
||||
|
||||
return (b0 shl 24) or (b1 shl 16) or (b2 shl 8) or b3
|
||||
}
|
||||
|
||||
fun readInt32(crc32 : CRC32) : Int {
|
||||
val ba = readBytes(4)
|
||||
crc32.update(ba)
|
||||
val b0 = ba[0].toInt() and 255
|
||||
val b1 = ba[1].toInt() and 255
|
||||
val b2 = ba[2].toInt() and 255
|
||||
val b3 = ba[3].toInt() and 255
|
||||
|
||||
return (b0 shl 24) or (b1 shl 16) or (b2 shl 8) or b3
|
||||
}
|
||||
|
||||
fun readUInt32() : Long {
|
||||
val b0 = readByte()
|
||||
val b1 = readByte()
|
||||
val b2 = readByte()
|
||||
val b3 = readByte()
|
||||
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 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
|
||||
|
|
Loading…
Reference in New Issue