SubwayTooter-Android-App/app/src/main/java/jp/juggler/subwaytooter/span/NetworkEmojiSpan.kt

150 lines
4.1 KiB
Kotlin
Raw Normal View History

package jp.juggler.subwaytooter.span
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.RectF
2019-02-15 02:51:22 +01:00
import androidx.annotation.IntRange
import android.text.style.ReplacementSpan
2018-01-28 20:03:04 +01:00
import jp.juggler.apng.ApngFrames
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.Pref
2018-12-01 00:02:18 +01:00
import jp.juggler.util.LogCategory
import java.lang.ref.WeakReference
class NetworkEmojiSpan internal constructor(
private val url : String,
private val scale : Float = 1f
) : ReplacementSpan(),AnimatableSpan {
companion object {
internal val log = LogCategory("NetworkEmojiSpan")
private const val scale_ratio = 1.14f
private const val descent_ratio = 0.211f
}
private val mPaint = Paint()
private val rect_src = Rect()
private val rect_dst = RectF()
2018-08-22 03:05:54 +02:00
// フレーム探索結果を格納する構造体を確保しておく
private val mFrameFindResult = ApngFrames.FindFrameResult()
init {
mPaint.isFilterBitmap = true
}
2018-08-22 03:05:54 +02:00
private var invalidate_callback : AnimatableSpanInvalidator? = null
private var refDrawTarget : WeakReference<Any>? = null
2018-08-22 03:05:54 +02:00
override fun setInvalidateCallback(
draw_target_tag : Any,
2018-08-22 03:05:54 +02:00
invalidate_callback : AnimatableSpanInvalidator
) {
this.refDrawTarget = WeakReference(draw_target_tag)
this.invalidate_callback = invalidate_callback
}
2018-08-22 03:05:54 +02:00
override fun getSize(
paint : Paint,
text : CharSequence,
@IntRange(from = 0) start : Int,
@IntRange(from = 0) end : Int,
fm : Paint.FontMetricsInt?
) : Int {
val size = (paint.textSize * scale_ratio * scale + 0.5f).toInt()
if(fm != null) {
val c_descent = (0.5f + size * descent_ratio).toInt()
val c_ascent = c_descent - size
if(fm.ascent > c_ascent) fm.ascent = c_ascent
if(fm.top > c_ascent) fm.top = c_ascent
if(fm.descent < c_descent) fm.descent = c_descent
if(fm.bottom < c_descent) fm.bottom = c_descent
}
return size
}
override fun draw(
canvas : Canvas,
text : CharSequence,
start : Int,
end : Int,
x : Float,
top : Int,
baseline : Int,
bottom : Int,
textPaint : Paint
) {
val invalidate_callback = this.invalidate_callback
if(invalidate_callback == null) {
log.e("draw: invalidate_callback is null.")
return
}
// APNGデータの取得
val frames = App1.custom_emoji_cache.getFrames(refDrawTarget, url) {
invalidate_callback.delayInvalidate(0)
} ?: return
val t = if(Pref.bpDisableEmojiAnimation(App1.pref))
0L
else
invalidate_callback.timeFromStart
// アニメーション開始時刻からの経過時間に応じたフレームを探索
frames.findFrame(mFrameFindResult, t)
val b = mFrameFindResult.bitmap
if(b == null || b.isRecycled) {
log.e("draw: bitmap is null or recycled.")
return
}
val srcWidth = b.width
val srcHeight = b.height
if(srcWidth < 1 || srcHeight <1){
log.e("draw: bitmap size is too small.")
return
}
rect_src.set(0, 0, srcWidth, srcHeight )
// 絵文字の正方形のサイズ
val dstSize = textPaint.textSize * scale_ratio * scale
// ベースラインから上下方向にずらすオフセット
val c_descent = dstSize * descent_ratio
val transY = baseline - dstSize + c_descent
// 絵文字のアスペクト比から描画範囲の幅と高さを決める
val dstWidth:Float
val dstHeight:Float
val aspectSrc = srcWidth.toFloat() / srcHeight.toFloat()
if( aspectSrc >= 1f){
dstWidth = dstSize
dstHeight = dstSize /aspectSrc
}else{
dstHeight = dstSize
dstWidth = dstSize * aspectSrc
}
val dstX = (dstSize-dstWidth)/2f
val dstY = (dstSize-dstHeight)/2f
rect_dst.set(dstX,dstY,dstX+dstWidth,dstY+dstHeight)
canvas.save()
canvas.translate(x, transY)
canvas.drawBitmap(b, rect_src, rect_dst, mPaint)
canvas.restore()
// 少し後に描画しなおす
val delay = mFrameFindResult.delay
if(delay != Long.MAX_VALUE && ! Pref.bpDisableEmojiAnimation(App1.pref)) {
invalidate_callback.delayInvalidate(delay)
}
}
}