16bit APNGのバグ修正

This commit is contained in:
tateisu 2018-01-29 04:03:04 +09:00
parent ec8527eae0
commit af5cddf051
55 changed files with 1566 additions and 1790 deletions

View File

@ -29,7 +29,9 @@
<w>hashtag</w> <w>hashtag</w>
<w>hashtags</w> <w>hashtags</w>
<w>hohoemi</w> <w>hohoemi</w>
<w>idat</w>
<w>idempotency</w> <w>idempotency</w>
<w>ihdr</w>
<w>kenglxn</w> <w>kenglxn</w>
<w>mailto</w> <w>mailto</w>
<w>mimumedon</w> <w>mimumedon</w>
@ -37,6 +39,7 @@
<w>noto</w> <w>noto</w>
<w>nsfw</w> <w>nsfw</w>
<w>openclose</w> <w>openclose</w>
<w>paeth</w>
<w>pleroma</w> <w>pleroma</w>
<w>poller</w> <w>poller</w>
<w>proc</w> <w>proc</w>

View File

@ -9,6 +9,7 @@
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/apng" /> <option value="$PROJECT_DIR$/apng" />
<option value="$PROJECT_DIR$/apng_android" />
<option value="$PROJECT_DIR$/app" /> <option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/colorpicker" /> <option value="$PROJECT_DIR$/colorpicker" />
<option value="$PROJECT_DIR$/emoji" /> <option value="$PROJECT_DIR$/emoji" />

View File

@ -4,6 +4,7 @@
<modules> <modules>
<module fileurl="file://$PROJECT_DIR$/SubwayTooter.iml" filepath="$PROJECT_DIR$/SubwayTooter.iml" /> <module fileurl="file://$PROJECT_DIR$/SubwayTooter.iml" filepath="$PROJECT_DIR$/SubwayTooter.iml" />
<module fileurl="file://$PROJECT_DIR$/apng/apng.iml" filepath="$PROJECT_DIR$/apng/apng.iml" /> <module fileurl="file://$PROJECT_DIR$/apng/apng.iml" filepath="$PROJECT_DIR$/apng/apng.iml" />
<module fileurl="file://$PROJECT_DIR$/apng_android/apng_android.iml" filepath="$PROJECT_DIR$/apng_android/apng_android.iml" />
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" /> <module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
<module fileurl="file://$PROJECT_DIR$/colorpicker/colorpicker.iml" filepath="$PROJECT_DIR$/colorpicker/colorpicker.iml" /> <module fileurl="file://$PROJECT_DIR$/colorpicker/colorpicker.iml" filepath="$PROJECT_DIR$/colorpicker/colorpicker.iml" />
<module fileurl="file://$PROJECT_DIR$/emoji/emoji.iml" filepath="$PROJECT_DIR$/emoji/emoji.iml" /> <module fileurl="file://$PROJECT_DIR$/emoji/emoji.iml" filepath="$PROJECT_DIR$/emoji/emoji.iml" />

View File

@ -2,10 +2,10 @@
package jp.juggler.apng package jp.juggler.apng
import jp.juggler.apng.util.ByteArrayTokenizer import jp.juggler.apng.util.ByteSequence
class ApngAnimationControl internal constructor(bat: ByteArrayTokenizer) { class ApngAnimationControl internal constructor(src: ByteSequence) {
companion object { companion object {
const val PLAY_INDEFINITELY =0 const val PLAY_INDEFINITELY =0
@ -21,8 +21,8 @@ class ApngAnimationControl internal constructor(bat: ByteArrayTokenizer) {
val numPlays: Int val numPlays: Int
init { init {
numFrames = bat.readInt32() numFrames = src.readInt32()
numPlays = bat.readInt32() numPlays = src.readInt32()
} }
override fun toString() ="ApngAnimationControl(numFrames=$numFrames,numPlays=$numPlays)" override fun toString() ="ApngAnimationControl(numFrames=$numFrames,numPlays=$numPlays)"

View File

@ -2,9 +2,9 @@
package jp.juggler.apng package jp.juggler.apng
import jp.juggler.apng.util.ByteArrayTokenizer import jp.juggler.apng.util.ByteSequence
class ApngBackground internal constructor(colorType: ColorType, bat: ByteArrayTokenizer) { class ApngBackground internal constructor(colorType: ColorType, src: ByteSequence) {
val red: Int val red: Int
val green: Int val green: Int
@ -14,23 +14,23 @@ class ApngBackground internal constructor(colorType: ColorType, bat: ByteArrayTo
init { init {
when (colorType) { when (colorType) {
ColorType.GREY, ColorType.GREY_ALPHA -> { ColorType.GREY, ColorType.GREY_ALPHA -> {
val v = bat.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 -> { ColorType.RGB, ColorType.RGBA -> {
red = bat.readUInt16() red = src.readUInt16()
green = bat.readUInt16() green = src.readUInt16()
blue = bat.readUInt16() blue = src.readUInt16()
index = -1 index = -1
} }
ColorType.INDEX -> { ColorType.INDEX -> {
red = -1 red = -1
green = -1 green = -1
blue = -1 blue = -1
index = bat.readUInt8() index = src.readUInt8()
} }
} }
} }

View File

@ -1,35 +1,72 @@
@file:Suppress("MemberVisibilityCanBePrivate", "unused")
package jp.juggler.apng package jp.juggler.apng
class ApngBitmap(var width: Int, var height: Int) { class ApngBitmap(var width : Int, var height : Int) {
val colors = IntArray( width * height) // each int value contains 0xAARRGGBB
val colors = IntArray(width * height)
fun reset(width: Int, height: Int) {
val newSize = width * height // widthとheightを再指定する。ビットマップはそのまま再利用する
if( newSize > colors.size ) fun reset(width : Int, height : Int) {
throw ParseError("can't resize to ${width}x${height} , it's greater than initial size") val newSize = width * height
this.width = width if(newSize > colors.size)
this.height = height throw ParseError("can't resize to $width x $height , it's greater than initial size")
colors.fill( 0,fromIndex = 0,toIndex = newSize) this.width = width
} this.height = height
// 透明な黒で初期化する
inner class Pointer(private var pos: Int) { colors.fill(0, fromIndex = 0, toIndex = newSize)
}
fun plusX(x: Int): Pointer {
pos += x // ビットマップ中の位置を保持して、ピクセルへの書き込みと位置の更新を行う
return this inner class Pointer {
}
private var pos : Int = 0
fun setPixel(a: Int, r: Int, g: Int, b: Int): Pointer { var step : Int = 1
colors[pos] = ((a and 255) shl 24) or ((r and 255) shl 16) or ((g and 255) shl 8) or (b and 255)
return this fun setPixel(argb : Int) : Pointer {
} // if( pos == width) println("setPixel 0x%x".format(argb))
colors[pos] = argb
fun setPixel(a: Byte, r: Byte, g: Byte, b: Byte): Pointer { return this
colors[pos] = ((a.toInt() and 255) shl 24) or ((r.toInt() and 255) shl 16) or ((g.toInt() and 255) shl 8) or (b.toInt() and 255) }
return this
} fun setPixel(a : Int, r : Int, g : Int, b : Int) = setPixel(
} ((a and 255) shl 24) or
((r and 255) shl 16) or
fun pointer(x: Int, y: Int) = Pointer( x + y * width ) ((g and 255) shl 8) or
(b and 255)
)
fun setOffset(pos : Int = 0, step : Int = 1) : Pointer {
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 next() = plus(step)
val color : Int
get() = colors[pos]
val alpha : Int
get() = (colors[pos] shr 24) and 255
val red : Int
get() = (colors[pos] shr 16) and 255
val green : Int
get() = (colors[pos] shr 8) and 255
val blue : Int
get() = (colors[pos]) and 255
}
fun pointer() = Pointer()
} }

View File

@ -2,146 +2,167 @@
package jp.juggler.apng package jp.juggler.apng
import jp.juggler.apng.util.BufferPool import jp.juggler.apng.util.*
import jp.juggler.apng.util.ByteArrayTokenizer
import jp.juggler.apng.util.StreamTokenizer
import java.io.InputStream import java.io.InputStream
import java.util.zip.CRC32 import java.util.zip.CRC32
object ApngDecoder { 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 ParseError("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 ParseError("incorrect sequenceNumber. last=$lastSequenceNumber,current=$n")
} }
lastSequenceNumber = n lastSequenceNumber = n
} }
val inBuffer = ByteArray(4096) val inBuffer = ByteArray(4096)
val inflateBufferPool = BufferPool(8192) val inflateBufferPool = BufferPool(8192)
var idatDecoder: IdatDecoder? = null var idatDecoder : IdatDecoder? = null
var fdatDecoder: IdatDecoder? = null var fdatDecoder : IdatDecoder? = null
val crc32 = CRC32() val crc32 = CRC32()
var lastFctl: ApngFrameControl? = null var lastFctl : ApngFrameControl? = null
var bitmap: ApngBitmap? = null var bitmap : ApngBitmap? = null
loop@ while (true) { loop@ while(true) {
crc32.reset() crc32.reset()
val chunk = ApngChunk(crc32, tokenizer) val chunk = ApngChunk(crc32, tokenizer)
when (chunk.type) { when(chunk.type) {
"IEND" -> break@loop "IEND" -> break@loop
"IHDR" -> { "IHDR" -> {
val header = ApngImageHeader(ByteArrayTokenizer(chunk.readBody(crc32, tokenizer))) val header = ApngImageHeader(ByteSequence(chunk.readBody(crc32, tokenizer)))
bitmap = ApngBitmap(header.width, header.height) bitmap = ApngBitmap(header.width, header.height)
apng.header = header apng.header = header
callback.onHeader(apng, header) callback.onHeader(apng, header)
} }
"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 ParseError("missing IHDR")
apng.background = ApngBackground(header.colorType, ByteArrayTokenizer(chunk.readBody(crc32, tokenizer))) apng.background = ApngBackground(
} header.colorType,
ByteSequence(chunk.readBody(crc32, tokenizer))
"tRNS" -> { )
val header = apng.header ?: throw ParseError("missing IHDR") }
val body = chunk.readBody(crc32, tokenizer)
when (header.colorType) { "tRNS" -> {
ColorType.GREY -> apng.transparentColor = ApngTransparentColor(true, ByteArrayTokenizer(body)) val header = apng.header ?: throw ParseError("missing IHDR")
ColorType.RGB -> apng.transparentColor = ApngTransparentColor(false, ByteArrayTokenizer(body)) val body = chunk.readBody(crc32, tokenizer)
ColorType.INDEX -> apng.palette?.parseTRNS(body) ?: throw ParseError("missing palette") when(header.colorType) {
else -> callback.log("tRNS ignored. colorType =${header.colorType}") ColorType.GREY -> apng.transparentColor =
} ApngTransparentColor(true, ByteSequence(body))
} ColorType.RGB -> apng.transparentColor =
ApngTransparentColor(false, ByteSequence(body))
"IDAT" -> { ColorType.INDEX -> apng.palette?.parseTRNS(body)
val header = apng.header ?: throw ParseError("missing IHDR") ?: throw ParseError("missing palette")
if (idatDecoder == null) { else -> callback.onApngWarning("tRNS ignored. colorType =${header.colorType}")
bitmap ?: throw ParseError("missing bitmap") }
bitmap.reset(header.width, header.height) }
idatDecoder = IdatDecoder(apng, bitmap, inflateBufferPool) {
callback.onDefaultImage(apng, bitmap) "IDAT" -> {
val fctl = lastFctl val header = apng.header ?: throw ParseError("missing IHDR")
if (fctl != null) { if(idatDecoder == null) {
// IDATより前にfcTLが登場しているなら、そのfcTLの画像はIDATと同じ bitmap ?: throw ParseError("missing bitmap")
callback.onAnimationFrame(apng, fctl, bitmap) bitmap.reset(header.width, header.height)
} idatDecoder = IdatDecoder(
} apng,
} bitmap,
idatDecoder.addData( inflateBufferPool,
tokenizer.inStream, callback
chunk.size, ) {
inBuffer, callback.onDefaultImage(apng, bitmap)
crc32 val fctl = lastFctl
) if(fctl != null) {
chunk.checkCRC(tokenizer, crc32.value) // IDATより前にfcTLが登場しているなら、そのfcTLの画像はIDATと同じ
} callback.onAnimationFrame(apng, fctl, bitmap)
}
"acTL" -> { }
val animationControl = ApngAnimationControl(ByteArrayTokenizer(chunk.readBody(crc32, tokenizer))) }
apng.animationControl = animationControl idatDecoder.addData(
callback.onAnimationInfo(apng, animationControl) tokenizer.inStream,
} chunk.size,
inBuffer,
"fcTL" -> { crc32
val bat = ByteArrayTokenizer(chunk.readBody(crc32, tokenizer)) )
checkSequenceNumber(bat.readInt32()) chunk.checkCRC(tokenizer, crc32.value)
lastFctl = ApngFrameControl(bat) }
fdatDecoder = null
} "acTL" -> {
val header = apng.header ?: throw ParseError("missing IHDR")
"fdAT" -> { val animationControl =
val fctl = lastFctl ?: throw ParseError("missing fCTL before fdAT") ApngAnimationControl(ByteSequence(chunk.readBody(crc32, tokenizer)))
if (fdatDecoder == null) { apng.animationControl = animationControl
bitmap ?: throw ParseError("missing bitmap") callback.onAnimationInfo(apng, header,animationControl)
bitmap.reset(fctl.width, fctl.height) }
fdatDecoder = IdatDecoder(apng, bitmap, inflateBufferPool) {
callback.onAnimationFrame(apng, fctl, bitmap) "fcTL" -> {
} val bat = ByteSequence(chunk.readBody(crc32, tokenizer))
} checkSequenceNumber(bat.readInt32())
checkSequenceNumber(tokenizer.readInt32(crc32)) lastFctl = ApngFrameControl(bat)
fdatDecoder.addData( fdatDecoder = null
tokenizer.inStream, }
chunk.size - 4,
inBuffer, "fdAT" -> {
crc32 val fctl = lastFctl ?: throw ParseError("missing fCTL before fdAT")
) if(fdatDecoder == null) {
chunk.checkCRC(tokenizer, crc32.value) bitmap ?: throw ParseError("missing bitmap")
} bitmap.reset(fctl.width, fctl.height)
fdatDecoder = IdatDecoder(
// 無視するチャンク apng,
"cHRM", "gAMA", "iCCP", "sBIT", "sRGB", // color space information bitmap,
"tEXt", "zTXt", "iTXt", // text information inflateBufferPool,
"tIME", // timestamp callback
"hIST", // histogram ) {
"pHYs", // Physical pixel dimensions callback.onAnimationFrame(apng, fctl, bitmap)
"sPLT" // Suggested palette (おそらく減色用?) }
-> chunk.skipBody(tokenizer) }
checkSequenceNumber(tokenizer.readInt32(crc32))
else -> { fdatDecoder.addData(
callback.log("unknown chunk: type=%s,size=0x%x".format(chunk.type, chunk.size)) tokenizer.inStream,
chunk.skipBody(tokenizer) chunk.size - 4,
} inBuffer,
} crc32
} )
} chunk.checkCRC(tokenizer, crc32.value)
}
// 無視するチャンク
"cHRM", "gAMA", "iCCP", "sBIT", "sRGB", // color space information
"tEXt", "zTXt", "iTXt", // text information
"tIME", // timestamp
"hIST", // histogram
"pHYs", // Physical pixel dimensions
"sPLT" // Suggested palette (おそらく減色用?)
-> chunk.skipBody(tokenizer)
else -> {
callback.onApngWarning(
"unknown chunk: type=%s,size=0x%x".format(
chunk.type,
chunk.size
)
)
chunk.skipBody(tokenizer)
}
}
}
}
} }

View File

@ -1,9 +1,31 @@
package jp.juggler.apng package jp.juggler.apng
interface ApngDecoderCallback{ interface ApngDecoderCallback {
fun onHeader(apng: Apng, header: ApngImageHeader)
fun onAnimationInfo(apng: Apng, animationControl: ApngAnimationControl) // called for non-fatal warning
fun onDefaultImage(apng: Apng, bitmap: ApngBitmap) fun onApngWarning(message : String)
fun onAnimationFrame(apng: Apng, frameControl: ApngFrameControl, bitmap: ApngBitmap)
fun log(message:String) // called for debug message
fun onApngDebug(message : String) {}
fun canApngDebug():Boolean = false
// called when PNG image header is detected.
fun onHeader(apng : Apng, header : ApngImageHeader)
// called when APNG Animation Control is detected.
fun onAnimationInfo(
apng : Apng,
header : ApngImageHeader,
animationControl : ApngAnimationControl
)
// called when default image bitmap was rendered.
fun onDefaultImage(apng : Apng, bitmap : ApngBitmap)
// called when APNG Frame Control is detected and its bitmap was rendered.
// its bitmap may same to default image for first frame.
// ( in this case, both of onDefaultImage and onAnimationFrame are called for same bitmap)
fun onAnimationFrame(apng : Apng, frameControl : ApngFrameControl, bitmap : ApngBitmap)
} }

View File

@ -2,10 +2,10 @@
package jp.juggler.apng package jp.juggler.apng
import jp.juggler.apng.util.ByteArrayTokenizer import jp.juggler.apng.util.ByteSequence
class ApngFrameControl internal constructor(bat: ByteArrayTokenizer) { class ApngFrameControl internal constructor(src: ByteSequence) {
val width: Int val width: Int
val height: Int val height: Int
@ -17,19 +17,19 @@ class ApngFrameControl internal constructor(bat: ByteArrayTokenizer) {
val blendOp: BlendOp val blendOp: BlendOp
init { init {
width = bat.readInt32() width = src.readInt32()
height = bat.readInt32() height = src.readInt32()
xOffset = bat.readInt32() xOffset = src.readInt32()
yOffset = bat.readInt32() yOffset = src.readInt32()
delayNum = bat.readUInt16() delayNum = src.readUInt16()
delayDen = bat.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 = bat.readUInt8() num = src.readUInt8()
disposeOp = DisposeOp.values().first{it.num==num} disposeOp = DisposeOp.values().first{it.num==num}
num = bat.readUInt8() num = src.readUInt8()
blendOp = BlendOp.values().first{it.num==num} blendOp = BlendOp.values().first{it.num==num}
} }

View File

@ -2,10 +2,10 @@
package jp.juggler.apng package jp.juggler.apng
import jp.juggler.apng.util.ByteArrayTokenizer import jp.juggler.apng.util.ByteSequence
// information from IHDR chunk.
class ApngImageHeader internal constructor(bat: ByteArrayTokenizer) { class ApngImageHeader internal constructor(src: ByteSequence) {
val width: Int val width: Int
val height: Int val height: Int
val bitDepth: Int val bitDepth: Int
@ -16,22 +16,24 @@ class ApngImageHeader internal constructor(bat: ByteArrayTokenizer) {
init { init {
width = bat.readInt32() width = src.readInt32()
height = bat.readInt32() height = src.readInt32()
bitDepth = bat.readUInt8() if(width <=0 || height <=0 ) throw ParseError("w=$width,h=$height is too small")
bitDepth = src.readUInt8()
var num:Int var num:Int
// //
num =bat.readUInt8() num =src.readUInt8()
colorType = ColorType.values().first { it.num==num } colorType = ColorType.values().first { it.num==num }
// //
num =bat.readUInt8() num =src.readUInt8()
compressionMethod = CompressionMethod.values().first { it.num==num } compressionMethod = CompressionMethod.values().first { it.num==num }
// //
num =bat.readUInt8() num =src.readUInt8()
filterMethod = FilterMethod.values().first { it.num==num } filterMethod = FilterMethod.values().first { it.num==num }
// //
num =bat.readUInt8() num =src.readUInt8()
interlaceMethod = InterlaceMethod.values().first { it.num==num } interlaceMethod = InterlaceMethod.values().first { it.num==num }
} }

View File

@ -2,28 +2,40 @@
package jp.juggler.apng package jp.juggler.apng
import jp.juggler.apng.util.getUInt8
class ApngPalette(rgb: ByteArray) { class ApngPalette(
val list: ByteArray src : ByteArray // repeat of R,G,B
var hasAlpha: Boolean = false ) {
companion object {
init { // full opaque black
val entryCount = rgb.size / 3 const val OPAQUE = 255 shl 24
list = ByteArray(4 * entryCount) }
for (i in 0 until entryCount) {
list[i * 4] = 255.toByte() val list : IntArray // repeat of 0xAARRGGBB
list[i * 4 + 1] = rgb[i * 3 + 0]
list[i * 4 + 2] = rgb[i * 3 + 1] var hasAlpha : Boolean = false
list[i * 4 + 3] = rgb[i * 3 + 2]
} init {
} val entryCount = src.size / 3
list = IntArray( entryCount)
override fun toString() = "palette(${list.size} entries,hasAlpha=$hasAlpha)" var pos = 0
for(i in 0 until entryCount) {
fun parseTRNS(ba: ByteArray) { list[i] = OPAQUE or
hasAlpha = true (src.getUInt8(pos) shl 16) or
for (i in 0 until Math.min(list.size, ba.size)) { (src.getUInt8(pos+1) shl 8) or
list[i * 4] = ba[i] src.getUInt8(pos+2)
} pos+=3
} }
}
override fun toString() = "palette(${list.size} entries,hasAlpha=$hasAlpha)"
// update alpha value from tRNS chunk data
fun parseTRNS(ba : ByteArray) {
hasAlpha = true
for(i in 0 until Math.min(list.size, ba.size)) {
list[i] = (list[i] and 0xffffff) or (ba.getUInt8(i) shl 24)
}
}
} }

View File

@ -1,21 +1,23 @@
@file:Suppress("MemberVisibilityCanBePrivate")
package jp.juggler.apng package jp.juggler.apng
import jp.juggler.apng.util.ByteArrayTokenizer import jp.juggler.apng.util.ByteSequence
class ApngTransparentColor internal constructor(isGreyScale:Boolean, bat: ByteArrayTokenizer) { 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{ init{
if( isGreyScale){ if( isGreyScale){
val v = bat.readUInt16() val v = src.readUInt16()
red =v red =v
green =v green =v
blue =v blue =v
}else{ }else{
red =bat.readUInt16() red =src.readUInt16()
green =bat.readUInt16() green =src.readUInt16()
blue =bat.readUInt16() blue =src.readUInt16()
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,40 +0,0 @@
package jp.juggler.apng.util
import java.util.*
internal class ByteArrayQueue(private val bufferRecycler :(ByteArrayRange)->Unit) {
private val list = LinkedList<ByteArrayRange>()
val remain: Int
get() = list.sumBy { it.remain }
fun add(range: ByteArrayRange) {
list.add(range)
}
fun clear() {
for( item in list ){
bufferRecycler(item)
}
list.clear()
}
fun readBytes(dst: ByteArray, offset: Int, length: Int): Int {
var nRead = 0
while (nRead < length && list.isNotEmpty()) {
val item = list.first()
if (item.remain <= 0) {
bufferRecycler(item)
list.removeFirst()
} else {
val delta = Math.min(item.remain, length - nRead)
System.arraycopy(item.array, item.start, dst, offset + nRead, delta)
item.start += delta
item.remain -= delta
nRead += delta
}
}
return nRead
}
}

View File

@ -1,7 +0,0 @@
package jp.juggler.apng.util
internal class ByteArrayRange(
val array: ByteArray,
var start: Int,
var remain: Int
)

View File

@ -1,55 +0,0 @@
package jp.juggler.apng.util
import jp.juggler.apng.ParseError
internal class ByteArrayTokenizer(ba: ByteArray) {
private val array: ByteArray = ba
private val arraySize: Int = ba.size
private var pos = 0
val size: Int
get()= arraySize
val remain: Int
get()= arraySize -pos
fun skipBytes(size: Int) {
pos += size
}
fun readBytes(size: Int): ByteArrayRange {
if (pos + size > arraySize) {
throw ParseError("readBytes: unexpected EoS")
}
val result = ByteArrayRange(array, pos, size)
pos+=size
return result
}
private fun readByte(): Int {
if (pos >= arraySize) {
throw ParseError("readBytes: unexpected EoS")
}
return array[pos++].toInt() 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 readUInt16(): Int {
val b0 = readByte()
val b1 = readByte()
return (b0 shl 8) or b1
}
fun readUInt8() = readByte()
}

View File

@ -0,0 +1,33 @@
package jp.juggler.apng.util
import jp.juggler.apng.ParseError
internal fun ByteArray.getUInt8(pos : Int) = this[pos].toInt() and 255
internal fun ByteArray.getUInt16(pos : Int) = (this.getUInt8(pos) shl 8) or this.getUInt8(pos + 1)
internal fun ByteArray.getInt32(pos : Int) = (this.getUInt8(pos) shl 24) or
(this.getUInt8(pos + 1) shl 16) or
(this.getUInt8(pos + 2) shl 8) or
this.getUInt8(pos + 3)
internal class ByteSequence(
val array : ByteArray,
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("readIntX: unexpected end")
val v = block()
offset += dataSize
length -= dataSize
return v
}
fun readUInt8() = readX(1) { array.getUInt8(offset) }
fun readUInt16() = readX(2) { array.getUInt16(offset) }
fun readInt32() = readX(4) { array.getInt32(offset) }
}

View File

@ -0,0 +1,40 @@
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 nRead = 0
while (nRead < length && list.isNotEmpty()) {
val item = list.first()
if (item.length <= 0) {
bufferRecycler(item)
list.removeFirst()
continue
}
val delta = Math.min(item.length, length - nRead)
System.arraycopy(item.array, item.offset, dst, offset + nRead, delta)
item.offset += delta
item.length -= delta
nRead += delta
}
return nRead
}
}

1
apng_android/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

42
apng_android/build.gradle Normal file
View File

@ -0,0 +1,42 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 27
buildToolsVersion '27.0.3'
defaultConfig {
targetSdkVersion 27
minSdkVersion 21
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
repositories {
mavenCentral()
}
dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
api project(':apng')
// 'api'
// 'implementation'
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

21
apng_android/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,26 @@
package jp.juggler.apng;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception{
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals( "jp.juggler.apng.test", appContext.getPackageName() );
}
}

View File

@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="jp.juggler.apng"
/>

View File

@ -0,0 +1,388 @@
package jp.juggler.apng
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.graphics.Rect
import android.util.Log
import java.io.InputStream
import java.util.ArrayList
class ApngFrames private constructor(
private val pixelSizeMax : Int = 0,
private val debug:Boolean =false
) : ApngDecoderCallback {
companion object {
private const val TAG = "ApngFrames"
// ループしない画像の場合は3秒でまたループさせる
private const val DELAY_AFTER_END = 3000L
// アニメーションフレームの描画に使う
private val sSrcModePaint : Paint by lazy {
val paint = Paint()
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC)
paint.isFilterBitmap = true
paint
}
private fun createBlankBitmap(w : Int, h : Int) : Bitmap {
return Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
}
// WARNING: ownership of "src" will be moved or recycled.
private fun scaleBitmap(src : Bitmap?, size_max : Int) : Bitmap? {
if(src == null) return null
val wSrc = src.width
val hSrc = src.height
if(size_max <= 0 || wSrc <= size_max && hSrc <= size_max) return src
val wDst : Int
val hDst : Int
if(wSrc >= hSrc) {
wDst = size_max
hDst = Math.max(
1,
(size_max.toFloat() * hSrc.toFloat() / wSrc.toFloat() + 0.5f).toInt()
)
} else {
hDst = size_max
wDst = Math.max(
1,
(size_max.toFloat() * wSrc.toFloat() / hSrc.toFloat() + 0.5f).toInt()
)
}
Log.v(TAG,"scaleBitmap: $wSrc,$hSrc => $wDst,$hDst")
val b2 = createBlankBitmap(wDst, hDst)
val canvas = Canvas(b2)
val rectSrc = Rect(0, 0, wSrc, hSrc)
val rectDst = Rect(0, 0, wDst, hDst)
canvas.drawBitmap(src, rectSrc, rectDst,
sSrcModePaint
)
src.recycle()
return b2
}
private fun toBitmap(src : ApngBitmap) : Bitmap {
return Bitmap.createBitmap(
src.colors, // int[] 配列
0, // offset
src.width, //stride
src.width, // width
src.height, //height
Bitmap.Config.ARGB_8888
)
}
private fun toBitmap(src : ApngBitmap, size_max : Int) : Bitmap? {
return scaleBitmap(
toBitmap(
src
), size_max
)
}
@Suppress("unused")
fun parseApng(inStream : InputStream, pixelSizeMax : Int,debug:Boolean=false) : ApngFrames {
val result = ApngFrames(pixelSizeMax,debug)
try {
ApngDecoder.parseStream(inStream, result)
result.onParseComplete()
return if( result.defaultImage != null || result.frames?.isNotEmpty() == true ){
result
}else{
throw RuntimeException("APNG has no image")
}
} catch(ex : Throwable) {
result.dispose()
throw ex
}
}
}
private var header : ApngImageHeader? = null
private var animationControl : ApngAnimationControl? = null
val width : Int
get() = Math.min( pixelSizeMax, header?.width ?: 1)
val height : Int
get() = Math.min( pixelSizeMax, header?.height ?: 1)
@Suppress("MemberVisibilityCanBePrivate")
val numFrames : Int
get() = animationControl?.numFrames ?: 1
@Suppress("unused")
val hasMultipleFrame : Boolean
get() = numFrames > 1
private var timeTotal = 0L
private lateinit var canvas : Canvas
private var canvasBitmap : Bitmap? = null
// 再生速度の調整
private var durationScale = 1f
// APNGじゃなかった場合に使われる
private var defaultImage : Bitmap? = null
private class Frame(
internal val bitmap : Bitmap,
internal val time_start : Long,
internal val time_width : Long
)
private var frames : ArrayList<Frame>? = null
@Suppress("unused")
constructor(bitmap : Bitmap) : this() {
this.defaultImage = bitmap
}
private fun onParseComplete() {
canvasBitmap?.recycle()
canvasBitmap = null
val frames = this.frames
if( frames != null ){
if( frames.size > 1){
defaultImage?.recycle()
defaultImage = null
}else if( frames.size == 1){
defaultImage?.recycle()
defaultImage = frames.first().bitmap
frames.clear()
}
}
}
fun dispose() {
defaultImage?.recycle()
canvasBitmap?.recycle()
val frames = this.frames
if(frames != null) {
for(f in frames) {
f.bitmap.recycle()
}
}
}
class FindFrameResult {
var bitmap : Bitmap? = null // may null
var delay : Long = 0 // 再描画が必要ない場合は Long.MAX_VALUE
}
// シーク位置に応じたコマ画像と次のコマまでの残り時間をresultに格納する
@Suppress("unused")
fun findFrame(result : FindFrameResult, t : Long) {
if(defaultImage != null) {
result.bitmap = defaultImage
result.delay = Long.MAX_VALUE
return
}
val animationControl = this.animationControl
val frames = this.frames
if(animationControl == null || frames == null) {
// この場合は既に mBitmapNonAnimation が用意されてるはずだ
result.bitmap = null
result.delay = Long.MAX_VALUE
return
}
val frameCount = frames.size
val isFinite = ! animationControl.isPlayIndefinitely
val repeatSequenceCount = if(isFinite) animationControl.numPlays else 1
val endWait = if(isFinite) DELAY_AFTER_END else 0L
val timeTotalLoop = Math.max(1,timeTotal * repeatSequenceCount + endWait)
val tf = (if(0.5f + t < 0f) 0f else t / durationScale).toLong()
// 全体の繰り返し時刻で余りを計算
val tl = tf % timeTotalLoop
if(tl >= timeTotalLoop - endWait) {
// 終端で待機状態
result.bitmap = frames[frameCount - 1].bitmap
result.delay = (0.5f + (timeTotalLoop - tl) * durationScale).toLong()
return
}
// 1ループの繰り返し時刻で余りを計算
val tt = tl % timeTotal
// フレームリストを時刻で二分探索
var s = 0
var e = frameCount
while(e - s > 1) {
val mid = s + e shr 1
val frame = frames[mid]
// log.d("s=%d,m=%d,e=%d tt=%d,fs=%s,fe=%d",s,mid,e,tt,frame.time_start,frame.time_start+frame.time_width );
if(tt < frame.time_start) {
e = mid
} else if(tt >= frame.time_start + frame.time_width) {
s = mid + 1
} else {
s = mid
break
}
}
s = if(s < 0) 0 else if(s >= frameCount - 1) frameCount - 1 else s
val frame = frames[s]
val delay = frame.time_start + frame.time_width - tt
result.bitmap = frames[s].bitmap
result.delay = (0.5f + durationScale * Math.max(0f, delay.toFloat())).toLong()
// log.d("findFrame tf=%d,tl=%d/%d,tt=%d/%d,s=%d,w=%d,delay=%d",tf,tl,loop_total,tt,timeTotal,s,frame.time_width,result.delay);
}
/////////////////////////////////////////////////////
// implements ApngDecoderCallback
override fun onApngWarning(message : String) {
Log.w(TAG, message)
}
override fun onApngDebug(message : String) {
Log.d(TAG, message)
}
override fun canApngDebug():Boolean = debug
override fun onHeader(apng : Apng, header : ApngImageHeader) {
this.header = header
}
override fun onAnimationInfo(
apng : Apng,
header: ApngImageHeader,
animationControl : ApngAnimationControl
) {
this.animationControl = animationControl
val canvasBitmap =
createBlankBitmap(header.width, header.height)
this.canvasBitmap = canvasBitmap
this.canvas = Canvas(canvasBitmap)
this.frames = ArrayList(animationControl.numFrames)
}
override fun onDefaultImage(apng : Apng, bitmap : ApngBitmap) {
defaultImage?.recycle()
defaultImage = toBitmap(bitmap, pixelSizeMax)
}
override fun onAnimationFrame(
apng : Apng,
frameControl : ApngFrameControl,
bitmap : ApngBitmap
) {
val frames = this.frames ?: return
val canvasBitmap = this.canvasBitmap ?: return
// APNGのフレーム画像をAndroidの形式に変換する。この段階ではリサイズしない
val bitmapNative = toBitmap(bitmap)
val previous : Bitmap? = if(frameControl.disposeOp == DisposeOp.Previous) {
// Capture the current bitmap region IF it needs to be reverted after rendering
Bitmap.createBitmap(
canvasBitmap,
frameControl.xOffset,
frameControl.yOffset,
frameControl.width,
frameControl.height
)
} else {
null
}
val paint = if(frameControl.blendOp == BlendOp.Source) {
sSrcModePaint // SRC_OVER, not blend
} else {
null // (for blend, leave paint null)
}
// Draw the new frame into place
canvas.drawBitmap(
bitmapNative,
frameControl.xOffset.toFloat(),
frameControl.yOffset.toFloat(),
paint
)
// Extract a drawable from the canvas. Have to copy the current bitmap.
// Store the drawable in the sequence of frames
val timeStart = timeTotal
val timeWidth = Math.max(1L, frameControl.delayMilliseconds)
timeTotal += timeWidth
val scaledBitmap =
scaleBitmap(
canvasBitmap.copy(
Bitmap.Config.ARGB_8888,
false
), pixelSizeMax
)
if(scaledBitmap != null) {
frames.add(Frame(scaledBitmap, timeStart, timeWidth))
}
// Now "dispose" of the frame in preparation for the next.
// https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk
when(frameControl.disposeOp) {
DisposeOp.None -> {
}
DisposeOp.Background ->
// APNG_DISPOSE_OP_BACKGROUND: the frame's region of the output buffer is to be cleared to fully transparent black before rendering the next frame.
//System.out.println(String.format("Frame %d clear background (full=%s, x=%d y=%d w=%d h=%d) previous=%s", currentFrame.sequenceNumber,
// isFull, currentFrame.xOffset, currentFrame.yOffset, currentFrame.width, currentFrame.height, previous));
//if (true || isFull) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR) // Clear to fully transparent black
DisposeOp.Previous ->
// APNG_DISPOSE_OP_PREVIOUS: the frame's region of the output buffer is to be reverted to the previous contents before rendering the next frame.
//System.out.println(String.format("Frame %d restore previous (full=%s, x=%d y=%d w=%d h=%d) previous=%s", currentFrame.sequenceNumber,
// isFull, currentFrame.xOffset, currentFrame.yOffset, currentFrame.width, currentFrame.height, previous));
// Put the original section back
if(previous != null) {
canvas.drawBitmap(
previous, frameControl.xOffset.toFloat(), frameControl.yOffset.toFloat(),
sSrcModePaint
)
previous.recycle()
}
else -> {
// 0: Default should never happen
// APNG_DISPOSE_OP_NONE: no disposal is done on this frame before rendering the next; the contents of the output buffer are left as is.
//System.out.println("Frame "+currentFrame.sequenceNumber+" do nothing dispose");
// do nothing
// } else {
// Rect rt = new Rect(currentFrame.xOffset, currentFrame.yOffset, currentFrame.width+currentFrame.xOffset, currentFrame.height+currentFrame.yOffset);
// paint = new Paint();
// paint.setColor(0);
// paint.setStyle(Paint.Style.FILL);
// canvas.drawRect(rt, paint);
// }
}
}
}
}

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">apng_android</string>
</resources>

View File

@ -0,0 +1,17 @@
package jp.juggler.apng;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception{
assertEquals( 4, 2 + 2 );
}
}

View File

@ -64,10 +64,10 @@ dependencies {
exclude group: 'com.android.support', module: 'support-annotations' exclude group: 'com.android.support', module: 'support-annotations'
}) })
compile project(':exif') implementation project(':exif')
compile project(':colorpicker') implementation project(':colorpicker')
compile project(':emoji') implementation project(':emoji')
compile project(':apng') implementation project(':apng_android')
compile 'com.android.support:support-v4:27.0.2' compile 'com.android.support:support-v4:27.0.2'
compile 'com.android.support:appcompat-v7:27.0.2' compile 'com.android.support:appcompat-v7:27.0.2'

View File

@ -6,7 +6,7 @@ import android.util.AttributeSet
class ListRecyclerView : RecyclerView { class ListRecyclerView : RecyclerView {
companion object { companion object {
// private val log = LogCategory("ListRecyclerView") // private val warning = LogCategory("ListRecyclerView")
} }
constructor(context : Context) : super(context) constructor(context : Context) : super(context)

View File

@ -1936,7 +1936,7 @@ class ActMain : AppCompatActivity()
App1.openCustomTab(this, opener.url) App1.openCustomTab(this, opener.url)
} catch(ex : Throwable) { } catch(ex : Throwable) {
// log.trace( ex ); // warning.trace( ex );
log.e(ex, "openChromeTab failed. url=%s", opener.url) log.e(ex, "openChromeTab failed. url=%s", opener.url)
} }

View File

@ -107,12 +107,12 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
override fun onLoadingChanged(isLoading : Boolean) { override fun onLoadingChanged(isLoading : Boolean) {
// かなり頻繁に呼ばれる // かなり頻繁に呼ばれる
// log.d( "exoPlayer onLoadingChanged %s" ,isLoading ); // warning.d( "exoPlayer onLoadingChanged %s" ,isLoading );
} }
override fun onPlayerStateChanged(playWhenReady : Boolean, playbackState : Int) { override fun onPlayerStateChanged(playWhenReady : Boolean, playbackState : Int) {
// かなり頻繁に呼ばれる // かなり頻繁に呼ばれる
// log.d( "exoPlayer onPlayerStateChanged %s %s", playWhenReady, playbackState ); // warning.d( "exoPlayer onPlayerStateChanged %s %s", playWhenReady, playbackState );
if(playWhenReady && playbackState == Player.STATE_BUFFERING) { if(playWhenReady && playbackState == Player.STATE_BUFFERING) {
val now = SystemClock.elapsedRealtime() val now = SystemClock.elapsedRealtime()
if(now - buffering_last_shown >= short_limit && exoPlayer.duration >= short_limit) { if(now - buffering_last_shown >= short_limit && exoPlayer.duration >= short_limit) {

View File

@ -124,7 +124,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
// Intent takeVideoIntent = new Intent( MediaStore.ACTION_VIDEO_CAPTURE ); // Intent takeVideoIntent = new Intent( MediaStore.ACTION_VIDEO_CAPTURE );
// startActivityForResult( takeVideoIntent, REQUEST_CODE_VIDEO ); // startActivityForResult( takeVideoIntent, REQUEST_CODE_VIDEO );
// }catch( Throwable ex ){ // }catch( Throwable ex ){
// log.trace( ex ); // warning.trace( ex );
// Utils.showToast( this, ex, "opening video app failed." ); // Utils.showToast( this, ex, "opening video app failed." );
// } // }
// } // }

View File

@ -182,7 +182,7 @@ class App1 : Application() {
// int memory = am.getMemoryClass(); // int memory = am.getMemoryClass();
// int largeMemory = am.getLargeMemoryClass(); // int largeMemory = am.getLargeMemoryClass();
// // どちらも単位はMB // // どちらも単位はMB
// log.d("MemoryClass=%d, LargeMemoryClass = %d",memory,largeMemory); // warning.d("MemoryClass=%d, LargeMemoryClass = %d",memory,largeMemory);
// //
// int maxSize; // int maxSize;
// if( am.isLowRamDevice() ){ // if( am.isLowRamDevice() ){

View File

@ -391,16 +391,16 @@ class AppState(internal val context : Context, internal val pref : SharedPrefere
// tts.setOnUtteranceProgressListener( new UtteranceProgressListener() { // tts.setOnUtteranceProgressListener( new UtteranceProgressListener() {
// @Override public void onStart( String utteranceId ){ // @Override public void onStart( String utteranceId ){
// log.d( "UtteranceProgressListener.onStart id=%s", utteranceId ); // warning.d( "UtteranceProgressListener.onStart id=%s", utteranceId );
// } // }
// //
// @Override public void onDone( String utteranceId ){ // @Override public void onDone( String utteranceId ){
// log.d( "UtteranceProgressListener.onDone id=%s", utteranceId ); // warning.d( "UtteranceProgressListener.onDone id=%s", utteranceId );
// handler.post( proc_flushSpeechQueue ); // handler.post( proc_flushSpeechQueue );
// } // }
// //
// @Override public void onError( String utteranceId ){ // @Override public void onError( String utteranceId ){
// log.d( "UtteranceProgressListener.onError id=%s", utteranceId ); // warning.d( "UtteranceProgressListener.onError id=%s", utteranceId );
// handler.post( proc_flushSpeechQueue ); // handler.post( proc_flushSpeechQueue );
// } // }
// } ); // } );

View File

@ -3461,7 +3461,7 @@ class Column(
} else if(holder_sp.adapterIndex == 0 && holder_sp.offset == 0) { } else if(holder_sp.adapterIndex == 0 && holder_sp.offset == 0) {
// スクロール位置が先頭なら先頭にする // スクロール位置が先頭なら先頭にする
log.d( log.d(
"mergeStreamingMessage: has VH. keep head. pos=%s,offset=%s" "mergeStreamingMessage: has VH. keep head. offset=%s,offset=%s"
, holder_sp.adapterIndex , holder_sp.adapterIndex
, holder_sp.offset , holder_sp.offset
) )

View File

@ -341,7 +341,7 @@ class ColumnViewHolder(
} }
val sp = column.scroll_save ?: //復元後にもここを通るがこれは正常である val sp = column.scroll_save ?: //復元後にもここを通るがこれは正常である
// log.d( "restoreScrollPosition [%d] %s , column has no saved scroll position.", page_idx, column.getColumnName( true ) ); // warning.d( "restoreScrollPosition [%d] %s , column has no saved scroll position.", page_idx, column.getColumnName( true ) );
return return
column.scroll_save = null column.scroll_save = null

View File

@ -172,7 +172,7 @@ internal class ItemListAdapter(
// 変更リストを順番に通知する // 変更リストを順番に通知する
for(c in changeList) { for(c in changeList) {
val adapterIndex = column.toAdapterIndex(c.listIndex) val adapterIndex = column.toAdapterIndex(c.listIndex)
log.d("notifyChange: ChangeType=${c.type} pos=$adapterIndex,count=${c.count}") log.d("notifyChange: ChangeType=${c.type} offset=$adapterIndex,count=${c.count}")
when(c.type) { when(c.type) {
AdapterChangeType.RangeInsert -> notifyItemRangeInserted(adapterIndex, c.count) AdapterChangeType.RangeInsert -> notifyItemRangeInserted(adapterIndex, c.count)
AdapterChangeType.RangeRemove -> notifyItemRangeRemoved(adapterIndex, c.count) AdapterChangeType.RangeRemove -> notifyItemRangeRemoved(adapterIndex, c.count)
@ -204,17 +204,17 @@ internal class ItemListAdapter(
diffResult.dispatchUpdatesTo(object : ListUpdateCallback { diffResult.dispatchUpdatesTo(object : ListUpdateCallback {
override fun onInserted(position : Int, count : Int) { override fun onInserted(position : Int, count : Int) {
log.d("notifyChange: notifyItemRangeInserted pos=$position,count=$count") log.d("notifyChange: notifyItemRangeInserted offset=$position,count=$count")
notifyItemRangeInserted(position, count) notifyItemRangeInserted(position, count)
} }
override fun onRemoved(position : Int, count : Int) { override fun onRemoved(position : Int, count : Int) {
log.d("notifyChange: notifyItemRangeRemoved pos=$position,count=$count") log.d("notifyChange: notifyItemRangeRemoved offset=$position,count=$count")
notifyItemRangeRemoved(position, count) notifyItemRangeRemoved(position, count)
} }
override fun onChanged(position : Int, count : Int, payload : Any?) { override fun onChanged(position : Int, count : Int, payload : Any?) {
log.d("notifyChange: notifyItemRangeChanged pos=$position,count=$count") log.d("notifyChange: notifyItemRangeChanged offset=$position,count=$count")
notifyItemRangeChanged(position, count, payload) notifyItemRangeChanged(position, count, payload)
} }

View File

@ -1373,7 +1373,7 @@ class PollingWorker private constructor(c : Context) {
val type = src.parseString("type") val type = src.parseString("type")
if(id <= nr.nid_read) { if(id <= nr.nid_read) {
// log.d("update_sub: ignore data that id=%s, <= read id %s ",id,nr.nid_read); // warning.d("update_sub: ignore data that id=%s, <= read id %s ",id,nr.nid_read);
return return
} else { } else {
log.d("update_sub: found data that id=%s, > read id %s ", id, nr.nid_read) log.d("update_sub: found data that id=%s, > read id %s ", id, nr.nid_read)

View File

@ -100,7 +100,7 @@ internal class StreamReader(
* Invoked when a text (type `0x1`) message has been received. * Invoked when a text (type `0x1`) message has been received.
*/ */
override fun onMessage(webSocket : WebSocket, text : String) { override fun onMessage(webSocket : WebSocket, text : String) {
// log.d( "WebSocket onMessage. url=%s, message=%s", webSocket.request().url(), text ); // warning.d( "WebSocket onMessage. url=%s, message=%s", webSocket.request().url(), text );
try { try {
val obj = text.toJsonObject() val obj = text.toJsonObject()

View File

@ -93,7 +93,7 @@ open class TootApiResult(
while(m.find()) { while(m.find()) {
val url = m.group(1) val url = m.group(1)
val rel = m.group(2) val rel = m.group(2)
// log.d("Link %s,%s",rel,url); // warning.d("Link %s,%s",rel,url);
if("next" == rel) link_older = url if("next" == rel) link_older = url
if("prev" == rel) link_newer = url if("prev" == rel) link_newer = url
} }

View File

@ -15,7 +15,7 @@ class EmojiImageSpan(context : Context, private val res_id : Int) : ReplacementS
companion object { companion object {
// private static final LogCategory log = new LogCategory( "EmojiImageSpan" ); // private static final LogCategory warning = new LogCategory( "EmojiImageSpan" );
// static DynamicDrawableSpan x = null; // static DynamicDrawableSpan x = null;

View File

@ -6,10 +6,10 @@ import android.graphics.Rect
import android.graphics.RectF import android.graphics.RectF
import android.support.annotation.IntRange import android.support.annotation.IntRange
import android.text.style.ReplacementSpan import android.text.style.ReplacementSpan
import jp.juggler.apng.ApngFrames
import jp.juggler.subwaytooter.App1 import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.Pref import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.util.ApngFrames
import jp.juggler.subwaytooter.util.LogCategory import jp.juggler.subwaytooter.util.LogCategory
import java.lang.ref.WeakReference import java.lang.ref.WeakReference

View File

@ -8,7 +8,7 @@ import android.util.Log
object LogData { object LogData {
private const val TAG = "SubwayTooter" private const val TAG = "SubwayTooter"
internal const val table = "log" internal const val table = "warning"
private const val COL_TIME = "t" private const val COL_TIME = "t"
private const val COL_LEVEL = "l" private const val COL_LEVEL = "l"

View File

@ -88,7 +88,7 @@ object MutedApp {
// cursor.close(); // cursor.close();
// } // }
// }catch( Throwable ex ){ // }catch( Throwable ex ){
// log.e( ex, "load failed." ); // warning.e( ex, "load failed." );
// } // }
// return false; // return false;
// } // }

View File

@ -88,7 +88,7 @@ object MutedWord {
// cursor.close(); // cursor.close();
// } // }
// }catch( Throwable ex ){ // }catch( Throwable ex ){
// log.e( ex, "load failed." ); // warning.e( ex, "load failed." );
// } // }
// return false; // return false;
// } // }

View File

@ -55,7 +55,7 @@ object TagSet {
// App1.database.delete(table, COL_TIME_SAVE + "<?", arrayOf(expire.toString())) // App1.database.delete(table, COL_TIME_SAVE + "<?", arrayOf(expire.toString()))
// //
// } catch(ex : Throwable) { // } catch(ex : Throwable) {
// log.e(ex, "deleteOld failed.") // warning.e(ex, "deleteOld failed.")
// } // }
// } // }
@ -67,7 +67,7 @@ object TagSet {
// cv.put( COL_ACCT, acct ); // cv.put( COL_ACCT, acct );
// App1.getDB().replace( table, null, cv ); // App1.getDB().replace( table, null, cv );
// }catch( Throwable ex ){ // }catch( Throwable ex ){
// log.e( ex, "save failed." ); // warning.e( ex, "save failed." );
// } // }
// } // }

View File

@ -221,7 +221,7 @@ class UserRelation private constructor() {
// try{ // try{
// App1.getDB().delete( table, COL_NAME + "=?", new String[]{ name } ); // App1.getDB().delete( table, COL_NAME + "=?", new String[]{ name } );
// }catch( Throwable ex ){ // }catch( Throwable ex ){
// log.e( ex, "delete failed." ); // warning.e( ex, "delete failed." );
// } // }
// } // }
// //
@ -241,7 +241,7 @@ class UserRelation private constructor() {
// } // }
// } // }
// }catch( Throwable ex ){ // }catch( Throwable ex ){
// log.e(ex,"getNameSet() failed.") // warning.e(ex,"getNameSet() failed.")
// } // }
// return dst; // return dst;
// } // }
@ -267,7 +267,7 @@ class UserRelation private constructor() {
// cursor.close(); // cursor.close();
// } // }
// }catch( Throwable ex ){ // }catch( Throwable ex ){
// log.e( ex, "load failed." ); // warning.e( ex, "load failed." );
// } // }
// return false; // return false;
// } // }

View File

@ -1,810 +0,0 @@
package jp.juggler.subwaytooter.util
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.graphics.Rect
import android.util.Log
import jp.juggler.apng.*
import java.io.InputStream
import java.util.ArrayList
class ApngFrames(private val pixelSizeMax : Int = 0) : ApngDecoderCallback {
companion object {
private const val TAG = "ApngFrames"
// ループしない画像の場合は3秒でまたループさせる
private const val DELAY_AFTER_END = 3000L
// アニメーションフレームの描画に使う
private val sSrcModePaint : Paint by lazy {
val paint = Paint()
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC)
paint.isFilterBitmap = true
paint
}
private fun createBlankBitmap(w : Int, h : Int) : Bitmap {
return Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
}
// WARNING: ownership of "src" will be moved or recycled.
private fun scaleBitmap(src : Bitmap?, size_max : Int) : Bitmap? {
if(src == null) return null
if(size_max <= 0) return src
val wSrc = src.width
val hSrc = src.height
if(wSrc <= size_max && hSrc <= size_max) return src
val wDst : Int
val hDst : Int
if(wSrc >= hSrc) {
wDst = size_max
hDst = Math.max(
1,
(size_max.toFloat() * hSrc.toFloat() / wSrc.toFloat() + 0.5f).toInt()
)
} else {
hDst = size_max
wDst = Math.max(
1,
(size_max.toFloat() * wSrc.toFloat() / hSrc.toFloat() + 0.5f).toInt()
)
}
val b2 = createBlankBitmap(wDst, hDst)
val canvas = Canvas(b2)
val rectSrc = Rect(0, 0, wSrc, hSrc)
val rectDst = Rect(0, 0, wDst, hDst)
canvas.drawBitmap(src, rectSrc, rectDst, sSrcModePaint)
src.recycle()
return b2
}
private fun toBitmap(src : ApngBitmap) : Bitmap {
return Bitmap.createBitmap(
src.colors, // int[] 配列
0, // offset
src.width, //stride
src.width, // width
src.height, //height
Bitmap.Config.ARGB_8888
)
}
private fun toBitmap(src : ApngBitmap, size_max : Int) : Bitmap? {
return scaleBitmap(toBitmap(src), size_max)
}
@Suppress("unused")
fun parseApng(inStream : InputStream, size_max : Int) : ApngFrames {
val result = ApngFrames(size_max)
try {
ApngDecoder.parseStream(inStream, result)
result.onParseComplete()
return if( result.defaultImage != null || result.frames?.isNotEmpty() == true ){
result
}else{
throw RuntimeException("APNG has no image")
}
} catch(ex : Throwable) {
result.dispose()
throw ex
}
}
}
private var header : ApngImageHeader? = null
private var animationControl : ApngAnimationControl? = null
val width : Int
get() = header?.width ?: 0
val height : Int
get() = header?.height ?: 0
@Suppress("MemberVisibilityCanBePrivate")
val numFrames : Int
get() = animationControl?.numFrames ?: 1
@Suppress("unused")
val hasMultipleFrame : Boolean
get() = numFrames > 1
private var timeTotal = 0L
private lateinit var canvas : Canvas
private var canvasBitmap : Bitmap? = null
// 再生速度の調整
private var durationScale = 1f
// APNGじゃなかった場合に使われる
private var defaultImage : Bitmap? = null
private class Frame(
internal val bitmap : Bitmap,
internal val time_start : Long,
internal val time_width : Long
)
private var frames : ArrayList<Frame>? = null
@Suppress("unused")
constructor(bitmap : Bitmap) : this() {
this.defaultImage = bitmap
}
private fun onParseComplete() {
canvasBitmap?.recycle()
canvasBitmap = null
val frames = this.frames
if( frames != null ){
if( frames.size > 1){
defaultImage?.recycle()
defaultImage = null
}else if( frames.size == 1){
defaultImage?.recycle()
defaultImage = frames.first().bitmap
frames.clear()
}
}
}
fun dispose() {
defaultImage?.recycle()
canvasBitmap?.recycle()
val frames = this.frames
if(frames != null) {
for(f in frames) {
f.bitmap.recycle()
}
}
}
class FindFrameResult {
var bitmap : Bitmap? = null // may null
var delay : Long = 0 // 再描画が必要ない場合は Long.MAX_VALUE
}
// シーク位置に応じたコマ画像と次のコマまでの残り時間をresultに格納する
@Suppress("unused")
fun findFrame(result : FindFrameResult, t : Long) {
if(defaultImage != null) {
result.bitmap = defaultImage
result.delay = Long.MAX_VALUE
return
}
val animationControl = this.animationControl
val frames = this.frames
if(animationControl == null || frames == null) {
// この場合は既に mBitmapNonAnimation が用意されてるはずだ
result.bitmap = null
result.delay = Long.MAX_VALUE
return
}
val frameCount = frames.size
val isFinite = ! animationControl.isPlayIndefinitely
val repeatSequenceCount = if(isFinite) animationControl.numPlays else 1
val endWait = if(isFinite) DELAY_AFTER_END else 0L
val timeTotalLoop = Math.max(1,timeTotal * repeatSequenceCount + endWait)
val tf = (if(0.5f + t < 0f) 0f else t / durationScale).toLong()
// 全体の繰り返し時刻で余りを計算
val tl = tf % timeTotalLoop
if(tl >= timeTotalLoop - endWait) {
// 終端で待機状態
result.bitmap = frames[frameCount - 1].bitmap
result.delay = (0.5f + (timeTotalLoop - tl) * durationScale).toLong()
return
}
// 1ループの繰り返し時刻で余りを計算
val tt = tl % timeTotal
// フレームリストを時刻で二分探索
var s = 0
var e = frameCount
while(e - s > 1) {
val mid = s + e shr 1
val frame = frames[mid]
// log.d("s=%d,m=%d,e=%d tt=%d,fs=%s,fe=%d",s,mid,e,tt,frame.time_start,frame.time_start+frame.time_width );
if(tt < frame.time_start) {
e = mid
} else if(tt >= frame.time_start + frame.time_width) {
s = mid + 1
} else {
s = mid
break
}
}
s = if(s < 0) 0 else if(s >= frameCount - 1) frameCount - 1 else s
val frame = frames[s]
val delay = frame.time_start + frame.time_width - tt
result.bitmap = frames[s].bitmap
result.delay = (0.5f + durationScale * Math.max(0f, delay.toFloat())).toLong()
// log.d("findFrame tf=%d,tl=%d/%d,tt=%d/%d,s=%d,w=%d,delay=%d",tf,tl,loop_total,tt,timeTotal,s,frame.time_width,result.delay);
}
/////////////////////////////////////////////////////
// implements ApngDecoderCallback
override fun log(message : String) {
Log.d(ApngFrames.TAG, message)
}
override fun onHeader(apng : Apng, header : ApngImageHeader) {
this.header = header
}
override fun onAnimationInfo(
apng : Apng,
animationControl : ApngAnimationControl
) {
this.animationControl = animationControl
val canvasBitmap = ApngFrames.createBlankBitmap(width, height)
this.canvasBitmap = canvasBitmap
this.canvas = Canvas(canvasBitmap)
this.frames = ArrayList(animationControl.numFrames)
}
override fun onDefaultImage(apng : Apng, bitmap : ApngBitmap) {
defaultImage?.recycle()
defaultImage = ApngFrames.toBitmap(bitmap, pixelSizeMax)
}
override fun onAnimationFrame(
apng : Apng,
frameControl : ApngFrameControl,
bitmap : ApngBitmap
) {
val frames = this.frames ?: return
val canvasBitmap = this.canvasBitmap ?: return
// APNGのフレーム画像をAndroidの形式に変換する。この段階ではリサイズしない
val bitmapNative = ApngFrames.toBitmap(bitmap)
val previous : Bitmap? = if(frameControl.disposeOp == DisposeOp.Previous) {
// Capture the current bitmap region IF it needs to be reverted after rendering
Bitmap.createBitmap(
canvasBitmap,
frameControl.xOffset,
frameControl.yOffset,
frameControl.width,
frameControl.height
)
} else {
null
}
val paint = if(frameControl.blendOp == BlendOp.Source) {
ApngFrames.sSrcModePaint // SRC_OVER, not blend
} else {
null // (for blend, leave paint null)
}
// Draw the new frame into place
canvas.drawBitmap(
bitmapNative,
frameControl.xOffset.toFloat(),
frameControl.yOffset.toFloat(),
paint
)
// Extract a drawable from the canvas. Have to copy the current bitmap.
// Store the drawable in the sequence of frames
val timeStart = timeTotal
val timeWidth = Math.max(1L, frameControl.delayMilliseconds)
timeTotal += timeWidth
val scaledBitmap =
scaleBitmap(canvasBitmap.copy(Bitmap.Config.ARGB_8888, false), pixelSizeMax)
if(scaledBitmap != null) {
frames.add(Frame(scaledBitmap, timeStart, timeWidth))
}
// Now "dispose" of the frame in preparation for the next.
// https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk
when(frameControl.disposeOp) {
DisposeOp.None -> {
}
DisposeOp.Background ->
// APNG_DISPOSE_OP_BACKGROUND: the frame's region of the output buffer is to be cleared to fully transparent black before rendering the next frame.
//System.out.println(String.format("Frame %d clear background (full=%s, x=%d y=%d w=%d h=%d) previous=%s", currentFrame.sequenceNumber,
// isFull, currentFrame.xOffset, currentFrame.yOffset, currentFrame.width, currentFrame.height, previous));
//if (true || isFull) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR) // Clear to fully transparent black
DisposeOp.Previous ->
// APNG_DISPOSE_OP_PREVIOUS: the frame's region of the output buffer is to be reverted to the previous contents before rendering the next frame.
//System.out.println(String.format("Frame %d restore previous (full=%s, x=%d y=%d w=%d h=%d) previous=%s", currentFrame.sequenceNumber,
// isFull, currentFrame.xOffset, currentFrame.yOffset, currentFrame.width, currentFrame.height, previous));
// Put the original section back
if(previous != null) {
canvas.drawBitmap(
previous, frameControl.xOffset.toFloat(), frameControl.yOffset.toFloat(),
ApngFrames.sSrcModePaint
)
previous.recycle()
}
else -> {
// 0: Default should never happen
// APNG_DISPOSE_OP_NONE: no disposal is done on this frame before rendering the next; the contents of the output buffer are left as is.
//System.out.println("Frame "+currentFrame.sequenceNumber+" do nothing dispose");
// do nothing
// } else {
// Rect rt = new Rect(currentFrame.xOffset, currentFrame.yOffset, currentFrame.width+currentFrame.xOffset, currentFrame.height+currentFrame.yOffset);
// paint = new Paint();
// paint.setColor(0);
// paint.setStyle(Paint.Style.FILL);
// canvas.drawRect(rt, paint);
// }
}
}
}
}
//package jp.juggler.subwaytooter.util
//
//import android.graphics.Bitmap
//import android.graphics.Canvas
//import android.graphics.Paint
//import android.graphics.PorterDuff
//import android.graphics.PorterDuffXfermode
//import android.graphics.Rect
//
//import net.ellerton.japng.PngScanlineBuffer
//import net.ellerton.japng.argb8888.Argb8888Bitmap
//import net.ellerton.japng.argb8888.Argb8888Processor
//import net.ellerton.japng.argb8888.Argb8888Processors
//import net.ellerton.japng.argb8888.Argb8888ScanlineProcessor
//import net.ellerton.japng.argb8888.BasicArgb8888Director
//import net.ellerton.japng.chunks.PngAnimationControl
//import net.ellerton.japng.chunks.PngFrameControl
//import net.ellerton.japng.chunks.PngHeader
//import net.ellerton.japng.error.PngException
//import net.ellerton.japng.reader.DefaultPngChunkReader
//import net.ellerton.japng.reader.PngReadHelper
//
//import java.io.InputStream
//import java.util.ArrayList
//
//// APNGを解釈した結果を保持する
//// (フレーム数分のbitmapと時間情報)
//
//class APNGFrames {
//
// companion object {
//
// internal val log = LogCategory("APNGFrames")
//
// // ループしない画像の場合は3秒でまたループさせる
// private const val DELAY_AFTER_END = 3000L
//
// /**
// * Keep a 1x1 transparent image around as reference for creating a scaled starting bitmap.
// * Considering this because of some reported OutOfMemory errors, and this post:
// *
// *
// * http://stackoverflow.com/a/8527745/963195
// *
// *
// * Specifically: "NEVER use Bitmap.createBitmap(width, height, Config.ARGB_8888). I mean NEVER!"
// *
// *
// * Instead the 1x1 image (68 bytes of resources) is scaled up to the needed size.
// * Whether or not this fixes the OOM problems is TBD...
// */
// //static Bitmap sOnePxTransparent;
// internal val sSrcModePaint : Paint by lazy{
// val paint = Paint()
// paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC)
// paint.isFilterBitmap = true
// paint
// }
//
// internal fun createBlankBitmap(w : Int, h : Int) : Bitmap {
// return Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
// }
//
// // WARNING: ownership of "src" will be moved or recycled.
// internal fun scaleBitmap(src : Bitmap?, size_max : Int) : Bitmap? {
// if(src == null) return null
//
// val src_w = src.width
// val src_h = src.height
// if(src_w <= size_max && src_h <= size_max) return src
//
// var dst_w : Int
// var dst_h : Int
// if(src_w >= src_h) {
// dst_w = size_max
// dst_h = (0.5f + src_h * size_max / src_w.toFloat()).toInt()
// if(dst_h < 1) dst_h = 1
// } else {
// dst_h = size_max
// dst_w = (0.5f + src_w * size_max / src_h.toFloat()).toInt()
// if(dst_w < 1) dst_w = 1
// }
//
// // この方法だとリークがあるらしい???
// // http://stackoverflow.com/a/8527745/963195
// // return Bitmap.createScaledBitmap( src, dst_w , dst_h , true );
//
// val b2 = createBlankBitmap(dst_w, dst_h)
// val canvas = Canvas(b2)
// val rect_src = Rect(0, 0, src_w, src_h)
// val rect_dst = Rect(0, 0, dst_w, dst_h)
// canvas.drawBitmap(src, rect_src, rect_dst, sSrcModePaint)
// src.recycle()
// return b2
// }
//
// internal fun toBitmap(src : Argb8888Bitmap) : Bitmap {
// val offset = 0
// val stride = src.width
// return Bitmap.createBitmap(src.pixelArray, offset, stride, src.width, src.height, Bitmap.Config.ARGB_8888)
// }
//
// internal fun toBitmap(src : Argb8888Bitmap, size_max : Int) : Bitmap? {
// return scaleBitmap(toBitmap(src), size_max)
// }
//
// /////////////////////////////////////////////////////////////////////
//
// // entry point is here
// @Throws(PngException::class)
// internal fun parseAPNG( inStream : InputStream, size_max : Int) : APNGFrames? {
// val handler = APNGParseEventHandler(size_max)
// try {
// val processor = Argb8888Processor(handler)
// val reader = DefaultPngChunkReader(processor)
// val result = PngReadHelper.read(inStream, reader)
// result?.onParseComplete()
// return result
// } catch(ex : Throwable) {
// handler.dispose()
// throw ex
// }
//
// }
// }
//
// // ピクセルサイズ制限
// private var mPixelSizeMax : Int = 0
//
// // APNGじゃなかった場合に使われる
// private var mBitmapNonAnimation : Bitmap? = null
//
// private lateinit var header : PngHeader
// private lateinit var scanlineProcessor : Argb8888ScanlineProcessor
// private lateinit var canvas : Canvas
//
// private var canvasBitmap : Bitmap? = null
// private var currentFrame : PngFrameControl? = null
// private var animationControl : PngAnimationControl? = null
//
// private var time_total = 0L
//
// private var frames : ArrayList<Frame>? = null
//
// ///////////////////////////////////////////////////////////////
//
// // 再生速度の調整
// private var durationScale = 1f
//
// private val numFrames : Int
// get() = animationControl?.numFrames ?: 1
//
// val hasMultipleFrame : Boolean
// get() = numFrames > 1
//
// private class Frame(
// internal val bitmap : Bitmap,
// internal val time_start : Long,
// internal val time_width : Long
// )
//
// ///////////////////////////////////////////////////////////////
//
// internal constructor(bitmap : Bitmap) {
// this.mBitmapNonAnimation = bitmap
// }
//
// internal constructor(
// header : PngHeader, scanlineProcessor : Argb8888ScanlineProcessor, animationControl : PngAnimationControl, size_max : Int
// ) {
// this.header = header
// this.scanlineProcessor = scanlineProcessor
// this.animationControl = animationControl
// this.mPixelSizeMax = size_max
//
// this.canvasBitmap = createBlankBitmap(header.width, header.height)
// this.canvas = Canvas(this.canvasBitmap)
// this.frames = ArrayList(animationControl.numFrames)
//
// }
//
// internal fun onParseComplete() {
// val frames = this.frames
//
// if(frames != null && frames.size <= 1) {
// mBitmapNonAnimation = toBitmap(scanlineProcessor.bitmap, mPixelSizeMax)
// }
//
// canvasBitmap?.recycle()
// canvasBitmap = null
// }
//
// internal fun dispose() {
// mBitmapNonAnimation?.recycle()
// canvasBitmap?.recycle()
//
// val frames = this.frames
// if(frames != null) {
// for(f in frames) {
// f.bitmap.recycle()
// }
// }
// }
//
// // フレームが追加される
// internal fun beginFrame(frameControl : PngFrameControl) : Argb8888ScanlineProcessor {
// currentFrame = frameControl
// return scanlineProcessor.cloneWithSharedBitmap(header.adjustFor(currentFrame))
// }
//
// // フレームが追加される
// internal fun completeFrame(frameImage : Argb8888Bitmap) {
//
// val frames = this.frames ?: return
//
// val canvasBitmap = this.canvasBitmap ?: return
//
// val currentFrame = this.currentFrame ?: return
// this.currentFrame = null
//
// // APNGのフレーム画像をAndroidの形式に変換する
// val frame = toBitmap(frameImage)
//
// var previous : Bitmap? = null
// // Capture the current bitmap region IF it needs to be reverted after rendering
// if(2 == currentFrame.disposeOp.toInt()) {
// previous = Bitmap.createBitmap(canvasBitmap, currentFrame.xOffset, currentFrame.yOffset, currentFrame.width, currentFrame.height) // or could use from frames?
// //System.out.println(String.format("Captured previous %d x %d", previous.getWidth(), previous.getHeight()));
// }
//
// var paint : Paint? = null // (for blend, leave paint null)
// if(0 == currentFrame.blendOp.toInt()) { // SRC_OVER, not blend
// paint = sSrcModePaint
// }
//
// // boolean isFull = currentFrame.height == header.height && currentFrame.width == header.width;
//
// // Draw the new frame into place
// canvas.drawBitmap(frame, currentFrame.xOffset.toFloat(), currentFrame.yOffset.toFloat(), paint)
//
// // Extract a drawable from the canvas. Have to copy the current bitmap.
// // Store the drawable in the sequence of frames
// val time_start = time_total
// var time_width = currentFrame.delayMilliseconds.toLong()
// if(time_width <= 0L) time_width = 1L
// time_total = time_start + time_width
//
// val scaledBitmap = scaleBitmap(canvasBitmap.copy(Bitmap.Config.ARGB_8888, false), mPixelSizeMax)
// if(scaledBitmap != null) {
// frames.add(Frame(scaledBitmap, time_start, time_width))
// }
//
// // Now "dispose" of the frame in preparation for the next.
// // https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk
//
// when(currentFrame.disposeOp.toInt()) {
//
// 1 ->
// // APNG_DISPOSE_OP_BACKGROUND: the frame's region of the output buffer is to be cleared to fully transparent black before rendering the next frame.
// //System.out.println(String.format("Frame %d clear background (full=%s, x=%d y=%d w=%d h=%d) previous=%s", currentFrame.sequenceNumber,
// // isFull, currentFrame.xOffset, currentFrame.yOffset, currentFrame.width, currentFrame.height, previous));
// //if (true || isFull) {
// canvas.drawColor(0, PorterDuff.Mode.CLEAR) // Clear to fully transparent black
//
// 2 ->
// // APNG_DISPOSE_OP_PREVIOUS: the frame's region of the output buffer is to be reverted to the previous contents before rendering the next frame.
// //System.out.println(String.format("Frame %d restore previous (full=%s, x=%d y=%d w=%d h=%d) previous=%s", currentFrame.sequenceNumber,
// // isFull, currentFrame.xOffset, currentFrame.yOffset, currentFrame.width, currentFrame.height, previous));
// // Put the original section back
// if(previous != null) {
// canvas.drawBitmap(previous, currentFrame.xOffset.toFloat(), currentFrame.yOffset.toFloat(), sSrcModePaint)
// previous.recycle()
// }
//
// else -> {
// // 0: Default should never happen
//
// // APNG_DISPOSE_OP_NONE: no disposal is done on this frame before rendering the next; the contents of the output buffer are left as is.
// //System.out.println("Frame "+currentFrame.sequenceNumber+" do nothing dispose");
// // do nothing
// // } else {
// // Rect rt = new Rect(currentFrame.xOffset, currentFrame.yOffset, currentFrame.width+currentFrame.xOffset, currentFrame.height+currentFrame.yOffset);
// // paint = new Paint();
// // paint.setColor(0);
// // paint.setStyle(Paint.Style.FILL);
// // canvas.drawRect(rt, paint);
// // }
//
// }
// }
// }
//
// class FindFrameResult {
// var bitmap : Bitmap? = null // may null
// var delay : Long = 0 // 再描画が必要ない場合は Long.MAX_VALUE
// }
//
// // シーク位置に応じたコマ画像と次のコマまでの残り時間をresultに格納する
// fun findFrame(result : FindFrameResult, t : Long) {
//
// if(mBitmapNonAnimation != null) {
// result.bitmap = mBitmapNonAnimation
// result.delay = Long.MAX_VALUE
// return
// }
//
// val animationControl = this.animationControl
// val frames = this.frames
// if(animationControl == null || frames == null) {
// // この場合は既に mBitmapNonAnimation が用意されてるはずだ
// result.bitmap = null
// result.delay = Long.MAX_VALUE
// return
// }
//
// val frame_count = frames.size
//
// val isFinite = ! animationControl.loopForever()
// val repeatSequenceCount = if(isFinite) animationControl.numPlays else 1
// val end_wait = if(isFinite) DELAY_AFTER_END else 0L
// var loop_total = time_total * repeatSequenceCount + end_wait
// if(loop_total <= 0) loop_total = 1
//
// val tf = (if(0.5f + t < 0f) 0f else t / durationScale).toLong()
//
// // 全体の繰り返し時刻で余りを計算
// val tl = tf % loop_total
// if(tl >= loop_total - end_wait) {
// // 終端で待機状態
// result.bitmap = frames[frame_count - 1].bitmap
// result.delay = (0.5f + (loop_total - tl) * durationScale).toLong()
// return
// }
// // 1ループの繰り返し時刻で余りを計算
// val tt = tl % time_total
//
// // フレームリストを時刻で二分探索
// var s = 0
// var e = frame_count
// while(e - s > 1) {
// val mid = s + e shr 1
// val frame = frames[mid]
// // log.d("s=%d,m=%d,e=%d tt=%d,fs=%s,fe=%d",s,mid,e,tt,frame.time_start,frame.time_start+frame.time_width );
// if(tt < frame.time_start) {
// e = mid
// } else if(tt >= frame.time_start + frame.time_width) {
// s = mid + 1
// } else {
// s = mid
// break
// }
// }
// s = if(s < 0) 0 else if(s >= frame_count - 1) frame_count - 1 else s
// val frame = frames[s]
// val delay = frame.time_start + frame.time_width - tt
// result.bitmap = frames[s].bitmap
// result.delay = (0.5f + durationScale * Math.max(0f, delay.toFloat())).toLong()
//
// // log.d("findFrame tf=%d,tl=%d/%d,tt=%d/%d,s=%d,w=%d,delay=%d",tf,tl,loop_total,tt,time_total,s,frame.time_width,result.delay);
// }
//
// /////////////////////////////////////////////////////////////////////
//
// // APNGのパース中に随時呼び出される
// internal class APNGParseEventHandler(
// private val size_max : Int
// ) : BasicArgb8888Director<APNGFrames>() {
//
// private lateinit var header : PngHeader
//
// private var frames : APNGFrames? = null
//
// private val isAnimated : Boolean
// get() = frames != null
//
// // ヘッダが分かった
// @Throws(PngException::class)
// override fun receiveHeader(header : PngHeader, buffer : PngScanlineBuffer) {
// this.header = header
//
// // 親クラスのprotectedフィールドを更新する
// val pngBitmap = Argb8888Bitmap(header.width, header.height)
// this.scanlineProcessor = Argb8888Processors.from(header, buffer, pngBitmap)
// }
//
// // デフォルト画像の手前で呼ばれる
// override fun beforeDefaultImage() : Argb8888ScanlineProcessor {
// return scanlineProcessor
// }
//
// // デフォルト画像が分かった
// // おそらく receiveAnimationControl より先に呼ばれる
// override fun receiveDefaultImage(defaultImage : Argb8888Bitmap) {
// // japng ライブラリの返すデフォルトイメージはあまり信用できないので使わない
// }
//
// // アニメーション制御情報が分かった
// override fun receiveAnimationControl(animationControl : PngAnimationControl) {
// this.frames = APNGFrames(header , scanlineProcessor, animationControl, size_max)
// }
//
// override fun wantDefaultImage() : Boolean {
// return ! isAnimated
// }
//
// override fun wantAnimationFrames() : Boolean {
// return true // isAnimated;
// }
//
// // フレーム制御情報が分かった
// override fun receiveFrameControl(frameControl : PngFrameControl) : Argb8888ScanlineProcessor {
// val frames = this.frames ?: throw RuntimeException("not animation image")
// return frames.beginFrame(frameControl)
// }
//
// // フレーム画像が分かった
// override fun receiveFrameImage(frameImage : Argb8888Bitmap) {
// val frames = this.frames ?: throw RuntimeException("not animation image")
// frames.completeFrame(frameImage)
// }
//
// // 結果を取得する
// override fun getResult() : APNGFrames? {
// val frames = this.frames
// return if( frames?.hasMultipleFrame == true ){
// frames
// }else {
// dispose()
// return null
// }
// }
//
// // 処理中に例外が起きた場合、Bitmapリソースを解放する
// fun dispose() {
// frames?.dispose()
// frames = null
// }
// }
//
//}

View File

@ -14,7 +14,7 @@ import java.util.concurrent.ConcurrentHashMap
import jp.juggler.subwaytooter.App1 import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.span.NetworkEmojiSpan import jp.juggler.subwaytooter.span.NetworkEmojiSpan
import jp.juggler.apng.ApngFrames
class CustomEmojiCache(internal val context : Context) { class CustomEmojiCache(internal val context : Context) {

View File

@ -446,7 +446,7 @@ class PostHelper(
val part = src.substring(last_sharp + 1, end) val part = src.substring(last_sharp + 1, end)
if(reCharsNotTag.matcher(part).find()) { if(reCharsNotTag.matcher(part).find()) {
// log.d( "checkTag: character not tag in string %s", part ); // warning.d( "checkTag: character not tag in string %s", part );
checkEmoji() checkEmoji()
return return
} }

View File

@ -54,7 +54,7 @@ object Utils {
// if( iv != 0 ) return iv; // if( iv != 0 ) return iv;
// }catch(Throwable ex){ // }catch(Throwable ex){
// } // }
// log.e("missing resid for %s",name); // warning.e("missing resid for %s",name);
// return R.string.Dialog_Cancel; // return R.string.Dialog_Cancel;
// } // }
@ -547,7 +547,7 @@ fun String.digestSHA256() : String {
// sb.append( String.format( Locale.JAPAN, "%dk", n ) ); // sb.append( String.format( Locale.JAPAN, "%dk", n ) );
// t -= n * 1000L; // t -= n * 1000L;
// } // }
// // remain // // length
// if( sb.length() > 0 ){ // if( sb.length() > 0 ){
// sb.append( String.format( Locale.JAPAN, "%03d", t ) ); // sb.append( String.format( Locale.JAPAN, "%03d", t ) );
// }else if( n > 0 ){ // }else if( n > 0 ){

View File

@ -51,7 +51,7 @@ class VersionString(src : String?) {
companion object { companion object {
// private val log = new LogCategory( "VersionString" ) // private val warning = new LogCategory( "VersionString" )
private fun isDelimiter(c : Char) : Boolean { private fun isDelimiter(c : Char) : Boolean {
return c == '.' || c == ' ' return c == '.' || c == ' '

View File

@ -116,7 +116,7 @@ class MyNetworkImageView : AppCompatImageView {
val d = drawable val d = drawable
if(d is Animatable) { if(d is Animatable) {
if(d.isRunning) { if(d.isRunning) {
//log.d("cancelLoading: Animatable.stop()") //warning.d("cancelLoading: Animatable.stop()")
d.stop() d.stop()
} }
} }

View File

@ -9,10 +9,11 @@ import android.os.SystemClock
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.util.ApngFrames
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import jp.juggler.apng.ApngFrames
import jp.juggler.subwaytooter.App1
class NetworkEmojiView : View { class NetworkEmojiView : View {
constructor(context : Context) : super(context) constructor(context : Context) : super(context)

View File

@ -179,7 +179,7 @@ class TestKotlinFeature {
/* /*
蛇足だがクラスファイルを読むのは 蛇足だがクラスファイルを読むのは
app/build/tmp/kotlin-classes/*UnitTest\**/TestKotlinFeature.class app/build/tmp/kotlin-classes/*UnitTest\**/TestKotlinFeature.class
javap.exe -c TestKotlinFeature.class > javap.log とすると逆アセンブルできる javap.exe -c TestKotlinFeature.class > javap.warning とすると逆アセンブルできる
*/ */
} }

View File

@ -1 +1 @@
include ':app', ':exif', ':colorpicker', ':emoji', ':apng' include ':app', ':exif', ':colorpicker', ':emoji', ':apng', ':apng_android'