refactor
This commit is contained in:
parent
afe8f39fe3
commit
c8a22febf2
|
@ -7,58 +7,51 @@ import android.util.SparseIntArray
|
||||||
import androidx.appcompat.widget.AppCompatTextView
|
import androidx.appcompat.widget.AppCompatTextView
|
||||||
import jp.juggler.util.LogCategory
|
import jp.juggler.util.LogCategory
|
||||||
import java.lang.Math.pow
|
import java.lang.Math.pow
|
||||||
import kotlin.math.abs
|
|
||||||
import kotlin.math.sign
|
import kotlin.math.sign
|
||||||
|
|
||||||
class Blurhash(blurhash : String, punch : Float = 1f) {
|
class Blurhash(blurhash : String, punch : Float = 1f) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private fun String.sub1(idx : Int) = substring(idx, idx + 1)
|
|
||||||
private fun String.sub2(idx : Int) = substring(idx, idx + 2)
|
|
||||||
private fun String.sub4(idx : Int) = substring(idx, idx + 4)
|
|
||||||
|
|
||||||
// map from base83 character to index
|
// map from base83 character to index
|
||||||
private val base83Map = SparseIntArray().apply {
|
private val base83Map = SparseIntArray().apply {
|
||||||
val base83Chars =
|
|
||||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~"
|
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~"
|
||||||
for(i in 0 until base83Chars.length) {
|
.forEachIndexed { index, c ->
|
||||||
val c = base83Chars[i]
|
put(c.toInt(), index)
|
||||||
put(c.toInt(), i)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert from base83 chars(1..4) to integer
|
// convert from base83 chars(1..4 length) to integer
|
||||||
private fun String.decodeBase83() : Int {
|
private fun String.decodeBase83(start : Int, length : Int) : Int {
|
||||||
var v = 0
|
var v = 0
|
||||||
for(c in this) {
|
for(i in start until start + length) {
|
||||||
val ci = c.toInt()
|
val ci = this[i].toInt()
|
||||||
val i = base83Map.get(ci, - 1)
|
val idx = base83Map.get(ci, - 1)
|
||||||
if(i == - 1) error("decodeBase83: incorrect char code $ci")
|
if(idx == - 1) {
|
||||||
v = v * 83 + i
|
error("decodeBase83: incorrect char code $ci")
|
||||||
|
}
|
||||||
|
v = v * 83 + idx
|
||||||
}
|
}
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
// array to convert gamma curve from sRGB(0..255) to linear(0..1f)
|
// array to convert gamma curve from sRGB(0..255) to linear(0..1f)
|
||||||
private val arraySRGB2Linear = FloatArray(256).apply {
|
private val arraySRGB2Linear = FloatArray(256) { i ->
|
||||||
for(i in 0 until 256) {
|
|
||||||
val v = i.toDouble() / 255.0
|
val v = i.toDouble() / 255.0
|
||||||
this[i] =
|
|
||||||
if(v <= 0.04045) {
|
if(v <= 0.04045) {
|
||||||
v / 12.92
|
v / 12.92
|
||||||
} else {
|
} else {
|
||||||
pow(((v + 0.055) / 1.055), 2.4)
|
pow(((v + 0.055) / 1.055), 2.4)
|
||||||
}
|
}
|
||||||
.toFloat()
|
.toFloat()
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sRGBToLinear(value : Int) =
|
private fun clip(min:Int,max:Int,value:Int) = if(value < min) min else if(value > max) max else value
|
||||||
arraySRGB2Linear[if(value < 0) 0 else if(value > 255) 255 else value]
|
|
||||||
|
private fun sRGBToLinear(value : Int) = arraySRGB2Linear[clip(0,255,value)]
|
||||||
|
|
||||||
private fun linearTosRGB(value : Float) : Int {
|
private fun linearTosRGB(value : Float) : Int {
|
||||||
// binary search in arraySRGB2Linear
|
// binary search in arraySRGB2Linear to avoid using pow()
|
||||||
var start = 0
|
var start = 0
|
||||||
var end = 256
|
var end = 256
|
||||||
while(end - start > 1) {
|
while(end - start > 1) {
|
||||||
|
@ -70,61 +63,54 @@ class Blurhash(blurhash : String, punch : Float = 1f) {
|
||||||
else -> return mid
|
else -> return mid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return when {
|
return clip(0,255,start)
|
||||||
start < 0 -> 0
|
|
||||||
start >= 256 -> 255
|
|
||||||
else -> start
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun signPow(v : Float, exp : Double) : Float =
|
private fun signPow2(v : Float) : Float = sign(v) * (v * v)
|
||||||
sign(v) * pow(abs(v).toDouble(), exp).toFloat()
|
|
||||||
|
|
||||||
private fun decodeDC(value : Int) = floatArrayOf(
|
|
||||||
sRGBToLinear((value ushr 16) and 255),
|
|
||||||
sRGBToLinear((value ushr 8) and 255),
|
|
||||||
sRGBToLinear((value) and 255)
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun decodeACSub(maximumValue : Float, v : Int) : Float =
|
private fun decodeACSub(maximumValue : Float, v : Int) : Float =
|
||||||
signPow((v - 9).toFloat() / 9f, 2.0) * maximumValue
|
signPow2((v - 9).toFloat() / 9f) * maximumValue
|
||||||
|
|
||||||
private fun decodeAC(value : Int, maximumValue : Float) = floatArrayOf(
|
private fun decodeAC(value : Int, maximumValue : Float) = floatArrayOf(
|
||||||
decodeACSub(maximumValue, value / 361), // 19*19
|
decodeACSub(maximumValue, value / 361), // 19*19
|
||||||
decodeACSub(maximumValue, ((value / 19) % 19)),
|
decodeACSub(maximumValue, ((value / 19) % 19)),
|
||||||
decodeACSub(maximumValue, value % 19)
|
decodeACSub(maximumValue, value % 19)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
private val numY : Int
|
private fun decodeDC(value : Int) = floatArrayOf(
|
||||||
private val numX : Int
|
sRGBToLinear((value ushr 16) and 255),
|
||||||
private val colors : ArrayList<FloatArray>
|
sRGBToLinear((value ushr 8) and 255),
|
||||||
|
sRGBToLinear((value) and 255)
|
||||||
init {
|
|
||||||
if(blurhash.length < 6) error("blurhash: too short $blurhash")
|
|
||||||
|
|
||||||
val sizeFlag = blurhash.sub1(0).decodeBase83()
|
|
||||||
this.numY = (sizeFlag / 9) + 1
|
|
||||||
this.numX = (sizeFlag % 9) + 1
|
|
||||||
val quantisedMaximumValue : Int = blurhash.sub1(1).decodeBase83()
|
|
||||||
val maximumValue : Float = ((quantisedMaximumValue + 1).toFloat() / 166f) * punch
|
|
||||||
|
|
||||||
if(blurhash.length != 4 + 2 * numX * numY) {
|
|
||||||
error("'blurhash length mismatch. actual=${blurhash.length},expect=${4 + 2 * numX * numY}")
|
|
||||||
}
|
|
||||||
|
|
||||||
this.colors = ArrayList()
|
|
||||||
for(i in 0 until numX * numY) {
|
|
||||||
colors.add(
|
|
||||||
if(i == 0) {
|
|
||||||
decodeDC(blurhash.sub4(2).decodeBase83()) // 2..5
|
|
||||||
} else {
|
|
||||||
// 6..8,...
|
|
||||||
decodeAC(blurhash.sub2(4 + i * 2).decodeBase83(), maximumValue)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val height : Int
|
||||||
|
private val width : Int
|
||||||
|
private val colors : Array<FloatArray>
|
||||||
|
|
||||||
|
init {
|
||||||
|
if(blurhash.length < 6) {
|
||||||
|
error("blurhash: too short $blurhash")
|
||||||
|
}
|
||||||
|
|
||||||
|
val sizeFlag = blurhash.decodeBase83(0, 1)
|
||||||
|
this.height = (sizeFlag / 9) + 1
|
||||||
|
this.width = (sizeFlag % 9) + 1
|
||||||
|
|
||||||
|
val lengthExpect = 4 + 2 * width * height
|
||||||
|
if(blurhash.length != lengthExpect) {
|
||||||
|
error("'blurhash length mismatch. expect=$lengthExpect,actual=${blurhash.length}")
|
||||||
|
}
|
||||||
|
|
||||||
|
val quantisedMaximumValue = blurhash.decodeBase83(1, 1)
|
||||||
|
val maximumValue = ((quantisedMaximumValue + 1).toFloat() / 166f) * punch
|
||||||
|
|
||||||
|
this.colors = Array(width * height) { i ->
|
||||||
|
when(i) {
|
||||||
|
0 -> decodeDC(blurhash.decodeBase83(2, 4))
|
||||||
|
else -> decodeAC(blurhash.decodeBase83(4 + i * 2, 2), maximumValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// render to IntArray that can be used to Bitmap.setPixels()
|
// render to IntArray that can be used to Bitmap.setPixels()
|
||||||
|
@ -137,10 +123,10 @@ class Blurhash(blurhash : String, punch : Float = 1f) {
|
||||||
var r = 0f
|
var r = 0f
|
||||||
var g = 0f
|
var g = 0f
|
||||||
var b = 0f
|
var b = 0f
|
||||||
for(j in 0 until numY) {
|
for(j in 0 until height) {
|
||||||
for(i in 0 until numX) {
|
for(i in 0 until width) {
|
||||||
val basis = (Math.cos(kx * i) * Math.cos(ky * j)).toFloat()
|
val basis = (Math.cos(kx * i) * Math.cos(ky * j)).toFloat()
|
||||||
val color = colors[i + j * numX]
|
val color = colors[i + j * width]
|
||||||
r += color[0] * basis
|
r += color[0] * basis
|
||||||
g += color[1] * basis
|
g += color[1] * basis
|
||||||
b += color[2] * basis
|
b += color[2] * basis
|
||||||
|
@ -162,20 +148,20 @@ class BlurhashView : AppCompatTextView {
|
||||||
companion object {
|
companion object {
|
||||||
val log = LogCategory("BlurhashView")
|
val log = LogCategory("BlurhashView")
|
||||||
|
|
||||||
// blurhashビットマップのサイズ
|
|
||||||
const val bitmapWidth = 16
|
const val bitmapWidth = 16
|
||||||
const val bitmapHeight = 16
|
const val bitmapHeight = 16
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(context : Context) : super(context)
|
constructor(context : Context) :
|
||||||
constructor(context : Context, attrs : AttributeSet) : super(context, attrs)
|
super(context)
|
||||||
constructor(context : Context, attrs : AttributeSet, defStyleAttr : Int) : super(
|
|
||||||
context,
|
|
||||||
attrs,
|
|
||||||
defStyleAttr
|
|
||||||
)
|
|
||||||
|
|
||||||
// bitmapとIntArrayはViewに保持して再利用する
|
constructor(context : Context, attrs : AttributeSet) :
|
||||||
|
super(context, attrs)
|
||||||
|
|
||||||
|
constructor(context : Context, attrs : AttributeSet, defStyleAttr : Int) :
|
||||||
|
super(context, attrs, defStyleAttr)
|
||||||
|
|
||||||
|
// keep bitmap and IntArray to reuse it.
|
||||||
private val pixels = IntArray(bitmapWidth * bitmapHeight)
|
private val pixels = IntArray(bitmapWidth * bitmapHeight)
|
||||||
private val blurhashBitmap = Bitmap.createBitmap(
|
private val blurhashBitmap = Bitmap.createBitmap(
|
||||||
bitmapWidth,
|
bitmapWidth,
|
||||||
|
@ -200,7 +186,6 @@ class BlurhashView : AppCompatTextView {
|
||||||
var blurhash : String? = null
|
var blurhash : String? = null
|
||||||
set(v) {
|
set(v) {
|
||||||
if(v == field) return
|
if(v == field) return
|
||||||
field = v
|
|
||||||
|
|
||||||
blurhashDecodeOk = if(v?.isEmpty() != false) {
|
blurhashDecodeOk = if(v?.isEmpty() != false) {
|
||||||
false
|
false
|
||||||
|
@ -220,6 +205,9 @@ class BlurhashView : AppCompatTextView {
|
||||||
log.e(ex, "blurhash decode failed.")
|
log.e(ex, "blurhash decode failed.")
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
field = v
|
||||||
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDraw(canvas : Canvas) {
|
override fun onDraw(canvas : Canvas) {
|
||||||
|
|
Loading…
Reference in New Issue