support svg format in custom emoji. using http://bigbadaboom.github.io/androidsvg/

This commit is contained in:
tateisu 2019-03-20 13:35:07 +09:00
parent 7436c82819
commit 6b9bba8e3c
2 changed files with 92 additions and 26 deletions

View File

@ -141,6 +141,8 @@ dependencies {
// implementation 'com.simplecityapps:recyclerview-fastscroll:1.0.16'
implementation 'me.drakeet.support:toastcompat:1.0.2'
implementation 'com.caverock:androidsvg-aar:1.3'
}
repositories {

View File

@ -1,10 +1,10 @@
package jp.juggler.subwaytooter.util
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.*
import android.os.Handler
import android.os.SystemClock
import com.caverock.androidsvg.SVG
import java.io.ByteArrayInputStream
import java.lang.ref.WeakReference
@ -16,6 +16,7 @@ import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.span.NetworkEmojiSpan
import jp.juggler.apng.ApngFrames
import jp.juggler.util.LogCategory
import kotlin.math.ceil
class CustomEmojiCache(internal val context : Context) {
@ -38,7 +39,7 @@ class CustomEmojiCache(internal val context : Context) {
private class Request(
val refTarget : WeakReference<Any>,
val url : String,
val onLoadComplete : ()->Unit
val onLoadComplete : () -> Unit
)
////////////////////////////////
@ -71,9 +72,9 @@ class CustomEmojiCache(internal val context : Context) {
// tag_target を持つリクエストまたはtagがGCされたリクエストをキューから除去する
fun cancelRequest(refTarget : WeakReference<Any>) {
val targetTag = refTarget.get() ?: return
synchronized(queue) {
val it = queue.iterator()
while(it.hasNext()) {
@ -101,20 +102,24 @@ class CustomEmojiCache(internal val context : Context) {
return null
}
fun getFrames(refDrawTarget: WeakReference<Any>?, url : String, onLoadComplete : ()->Unit) : ApngFrames? {
fun getFrames(
refDrawTarget : WeakReference<Any>?,
url : String,
onLoadComplete : () -> Unit
) : ApngFrames? {
try {
if( refDrawTarget?.get() == null ){
if(refDrawTarget?.get() == null) {
NetworkEmojiSpan.log.e("draw: DrawTarget is null ")
return null
}
cancelRequest(refDrawTarget)
synchronized(cache) {
val item = getCached(elapsedTime, url)
if(item != null) return item.frames
}
// キャンセル操作の都合上、排他が必要
synchronized(queue) {
queue.addLast(Request(refDrawTarget, url, onLoadComplete))
@ -154,21 +159,26 @@ class CustomEmojiCache(internal val context : Context) {
var cache_size : Int = - 1
if(synchronized(cache) {
val now = elapsedTime
val item = getCached(now, request.url)
if(item != null) {
if(item.frames != null) {
fireCallback(request)
val now = elapsedTime
val item = getCached(now, request.url)
if(item != null) {
if(item.frames != null) {
fireCallback(request)
}
return@synchronized true
}
return@synchronized true
}
sweep_cache(now)
cache_size = cache.size
return@synchronized false
}) continue
sweep_cache(now)
cache_size = cache.size
return@synchronized false
}) continue
if(DEBUG)
log.d("start get image. queue_size=%d, cache_size=%d url=%s", queue_size, cache_size, request.url)
log.d(
"start get image. queue_size=%d, cache_size=%d url=%s",
queue_size,
cache_size,
request.url
)
var frames : ApngFrames? = null
try {
@ -238,21 +248,21 @@ class CustomEmojiCache(internal val context : Context) {
for(item in list) {
cache.remove(item.url)
item.frames?.dispose()
if( ++removed >= over) break
if(++ removed >= over) break
}
}
private fun decodeAPNG(data : ByteArray, url : String) : ApngFrames? {
try {
// PNGヘッダを確認
if( data.size >= 8
if(data.size >= 8
&& (data[0].toInt() and 0xff) == 0x89
&& (data[1].toInt() and 0xff) == 0x50
){
&& (data[1].toInt() and 0xff) == 0x50
) {
// APNGをデコード
return ApngFrames.parseApng(ByteArrayInputStream(data), 64)
}
// fall thru
} catch(ex : Throwable) {
if(DEBUG) log.trace(ex)
@ -268,10 +278,25 @@ class CustomEmojiCache(internal val context : Context) {
} else {
log.e("Bitmap decode returns null. %s", url)
}
// fall thru
} catch(ex : Throwable) {
log.e(ex, "Bitmap decode failed. %s", url)
}
// SVGのロードを試みる
try {
val b = decodeSVG(data, 128.toFloat())
if(b != null) {
if(DEBUG) log.d("SVG decoded.")
return ApngFrames(b)
}
// fall thru
} catch(ex : Throwable) {
log.e(ex, "SVG decode failed. %s", url)
}
return null
}
@ -299,6 +324,45 @@ class CustomEmojiCache(internal val context : Context) {
options.inSampleSize = 1 shl bits
return BitmapFactory.decodeByteArray(data, 0, data.size, options)
}
private fun decodeSVG(data : ByteArray, pixelMax : Float) : Bitmap? {
try {
val svg = SVG.getFromInputStream(ByteArrayInputStream(data))
val src_w = svg.documentWidth
val src_h = svg.documentHeight
val aspect = src_w / src_h
val dst_w : Float
val dst_h : Float
if(aspect >= 1f) {
dst_w = pixelMax
dst_h = pixelMax / aspect
} else {
dst_h = pixelMax
dst_w = pixelMax * aspect
}
val w_ceil = ceil(dst_w)
val h_ceil = ceil(dst_h)
// Create a Bitmap to render our SVG to
val b = Bitmap.createBitmap(w_ceil.toInt(), h_ceil.toInt(), Bitmap.Config.ARGB_8888)
// Create a Canvas to use for rendering
val canvas = Canvas(b)
svg.renderToCanvas(
canvas,
if(aspect >= 1f) {
RectF(0f, h_ceil-dst_h, dst_w, dst_h) // 後半はw,hを指定する
} else {
RectF(w_ceil-dst_w, 0f, dst_w , dst_h) // 後半はw,hを指定する
}
)
return b
} catch(ex : Throwable) {
log.e(ex, "decodeSVG failed.")
}
return null
}
}
}