fix(image): Coil 2 대응 BlurTransformation 구현을 추가한다

This commit is contained in:
2026-04-20 11:46:38 +09:00
parent 272cd502be
commit b589329398
3 changed files with 148 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
package kr.co.vividnext.sodalive.common.image
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Bitmap.Config.ARGB_8888
import android.graphics.Paint
import android.renderscript.Allocation
import android.renderscript.Element
import android.renderscript.RenderScript
import android.renderscript.ScriptIntrinsicBlur
import androidx.annotation.RequiresApi
import androidx.core.graphics.applyCanvas
import coil.size.Size
import coil.transform.Transformation
/**
* Coil 2.x 환경에서 기존 blur 호출부를 유지하기 위한 Android blur transformation이다.
*/
@RequiresApi(18)
class BlurTransformation @JvmOverloads constructor(
private val context: Context,
private val radius: Float = DEFAULT_RADIUS,
private val sampling: Float = DEFAULT_SAMPLING
) : Transformation {
init {
require(radius in 0.0f..25.0f) { "radius must be in [0, 25]." }
require(sampling > 0f) { "sampling must be > 0." }
}
override val cacheKey: String = "${BlurTransformation::class.java.name}-$radius-$sampling"
@Suppress("DEPRECATION")
override suspend fun transform(input: Bitmap, size: Size): Bitmap {
val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG)
val scaledWidth = (input.width / sampling).toInt().coerceAtLeast(1)
val scaledHeight = (input.height / sampling).toInt().coerceAtLeast(1)
val output = Bitmap.createBitmap(scaledWidth, scaledHeight, input.config ?: ARGB_8888)
output.applyCanvas {
scale(1f / sampling, 1f / sampling)
drawBitmap(input, 0f, 0f, paint)
}
var renderScript: RenderScript? = null
var inputAllocation: Allocation? = null
var outputAllocation: Allocation? = null
var blurScript: ScriptIntrinsicBlur? = null
try {
renderScript = RenderScript.create(context)
inputAllocation = Allocation.createFromBitmap(
renderScript,
output,
Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT
)
outputAllocation = Allocation.createTyped(renderScript, inputAllocation.type)
blurScript = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript))
blurScript.setRadius(radius)
blurScript.setInput(inputAllocation)
blurScript.forEach(outputAllocation)
outputAllocation.copyTo(output)
} finally {
blurScript?.destroy()
inputAllocation?.destroy()
outputAllocation?.destroy()
renderScript?.destroy()
}
return output
}
private companion object {
private const val DEFAULT_RADIUS = 10f
private const val DEFAULT_SAMPLING = 1f
}
}

View File

@@ -0,0 +1,35 @@
package kr.co.vividnext.sodalive.common.image
import android.content.Context
import io.mockk.mockk
import org.junit.Assert.assertEquals
import org.junit.Assert.assertThrows
import org.junit.Test
class BlurTransformationTest {
@Test
fun `cacheKey includes radius and sampling`() {
val transformation = BlurTransformation(
context = mockk<Context>(relaxed = true),
radius = 25f,
sampling = 2.5f
)
assertEquals(
"kr.co.vividnext.sodalive.common.image.BlurTransformation-25.0-2.5",
transformation.cacheKey
)
}
@Test
fun `constructor rejects radius above max`() {
assertThrows(IllegalArgumentException::class.java) {
BlurTransformation(
context = mockk<Context>(relaxed = true),
radius = 26f,
sampling = 2.5f
)
}
}
}