fix(image): Coil 2 대응 BlurTransformation 구현을 추가한다
This commit is contained in:
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
docs/20260420_BlurTransformation오류수정.md
Normal file
34
docs/20260420_BlurTransformation오류수정.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# 20260420 BlurTransformation 오류 수정
|
||||||
|
|
||||||
|
## 작업 체크리스트
|
||||||
|
- [x] `BlurTransformation` 컴파일 오류의 실제 원인을 의존성 해석 기준으로 확정한다.
|
||||||
|
QA: `dependencyInsight` 결과에서 어떤 경로가 `io.coil-kt:coil`을 2.x로 올리는지 확인되어야 한다.
|
||||||
|
- [x] 최소 수정 방향을 정하고 실패 재현 경로를 확보한다.
|
||||||
|
QA: `:app:compileDebugKotlin` 또는 관련 단위 테스트가 수정 전 실패해야 한다.
|
||||||
|
- [x] Coil 2.x 환경에서 사용할 수 있는 로컬 `BlurTransformation` 호환 구현을 추가한다.
|
||||||
|
QA: 기존 호출부 시그니처 `(context, 25f, 2.5f)`를 유지한 채 컴파일 가능해야 한다.
|
||||||
|
- [x] `BlurTransformation` 사용처 import를 최소 범위로 교체한다.
|
||||||
|
QA: 기존 blur 사용 화면만 수정되고, 다른 Coil transform 사용처는 변경되지 않아야 한다.
|
||||||
|
- [x] 관련 테스트/컴파일 검증을 수행하고 결과를 기록한다.
|
||||||
|
QA: 관련 단위 테스트와 `:app:compileDebugKotlin` 결과를 문서 하단에 남겨야 한다.
|
||||||
|
|
||||||
|
## 검증 기록
|
||||||
|
- 2026-04-20
|
||||||
|
- 무엇: `BlurTransformation` unresolved 오류의 원인을 `Daro -> Moloco -> coil-compose:2.2.2` 경로로 확정하고, Coil 2.x에서 사용할 로컬 `BlurTransformation` 호환 구현을 추가했다.
|
||||||
|
- 왜: `so.daro:daro-a:1.5.3` 추가 후 Moloco SDK가 `io.coil-kt:coil-compose:2.2.2`를 끌어오면서 앱의 `io.coil-kt:coil:1.4.0`이 2.2.2로 승격됐고, Coil 2에서 제거된 `coil.transform.BlurTransformation`만 컴파일 오류가 발생했기 때문이다.
|
||||||
|
- 어떻게:
|
||||||
|
- 수정 파일: `app/src/main/java/kr/co/vividnext/sodalive/common/image/BlurTransformation.kt`
|
||||||
|
- 수정 파일: `app/src/main/java/kr/co/vividnext/sodalive/audio_content/series/detail/SeriesDetailActivity.kt`
|
||||||
|
- 수정 파일: `app/src/main/java/kr/co/vividnext/sodalive/chat/original/detail/OriginalWorkDetailActivity.kt`
|
||||||
|
- 수정 파일: `app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileActivity.kt`
|
||||||
|
- 수정 파일: `app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityAdapter.kt`
|
||||||
|
- 추가 파일: `app/src/test/java/kr/co/vividnext/sodalive/common/image/BlurTransformationTest.kt`
|
||||||
|
- 실행 명령: `./gradlew :app:dependencyInsight --configuration debugRuntimeClasspath --dependency io.coil-kt:coil`
|
||||||
|
- 결과: `so.daro:daro-a:1.5.3 -> com.google.ads.mediation:moloco -> com.moloco.sdk:moloco-sdk:4.1.1 -> io.coil-kt:coil-compose:2.2.2 -> io.coil-kt:coil:2.2.2` 경로를 확인했다.
|
||||||
|
- 실행 명령: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.common.image.BlurTransformationTest"`
|
||||||
|
- 결과: 수정 전에는 main source의 `BlurTransformation` unresolved로 실패했고, 호환 구현 추가 후 테스트 포함 `BUILD SUCCESSFUL`로 통과했다.
|
||||||
|
- 실행 명령: `./gradlew :app:compileDebugKotlin`
|
||||||
|
- 결과: `BUILD SUCCESSFUL`
|
||||||
|
- 실행 명령: `./gradlew :app:assembleDebug`
|
||||||
|
- 결과: `BUILD SUCCESSFUL`
|
||||||
|
- 진단 도구: Kotlin(`.kt`)용 LSP 서버 미구성으로 `lsp_diagnostics` 실행 불가 확인
|
||||||
Reference in New Issue
Block a user