package kr.co.vividnext.sodalive.utils import java.awt.image.BufferedImage import kotlin.math.exp import kotlin.math.max import kotlin.math.min /** * 가우시안 커널 기반 블러 유틸리티 * - 반경(radius)에 따라 커널 크기(2*radius+1) 생성 * - 시그마는 관례적으로 radius/3.0 적용 * - 수평/수직 분리 합성곱으로 품질과 성능 확보 */ object ImageBlurUtil { fun blur(src: BufferedImage, radius: Int = 10): BufferedImage { require(radius > 0) { "radius must be > 0" } val w = src.width val h = src.height val dst = BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB) // 가우시안 1D 커널 생성 및 정규화 val sigma = radius / 3.0 val kernel = gaussianKernel(radius, sigma) // 중간 버퍼 val temp = BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB) // 수평 합성곱 for (y in 0 until h) { for (x in 0 until w) { var aAcc = 0.0 var rAcc = 0.0 var gAcc = 0.0 var bAcc = 0.0 for (k in -radius..radius) { val xx = clamp(x + k, 0, w - 1) val rgb = src.getRGB(xx, y) val a = (rgb ushr 24) and 0xFF val r = (rgb ushr 16) and 0xFF val g = (rgb ushr 8) and 0xFF val b = rgb and 0xFF val wgt = kernel[k + radius] aAcc += a * wgt rAcc += r * wgt gAcc += g * wgt bAcc += b * wgt } val a = aAcc.toInt().coerceIn(0, 255) val r = rAcc.toInt().coerceIn(0, 255) val g = gAcc.toInt().coerceIn(0, 255) val b = bAcc.toInt().coerceIn(0, 255) temp.setRGB(x, y, (a shl 24) or (r shl 16) or (g shl 8) or b) } } // 수직 합성곱 for (x in 0 until w) { for (y in 0 until h) { var aAcc = 0.0 var rAcc = 0.0 var gAcc = 0.0 var bAcc = 0.0 for (k in -radius..radius) { val yy = clamp(y + k, 0, h - 1) val rgb = temp.getRGB(x, yy) val a = (rgb ushr 24) and 0xFF val r = (rgb ushr 16) and 0xFF val g = (rgb ushr 8) and 0xFF val b = rgb and 0xFF val wgt = kernel[k + radius] aAcc += a * wgt rAcc += r * wgt gAcc += g * wgt bAcc += b * wgt } val a = aAcc.toInt().coerceIn(0, 255) val r = rAcc.toInt().coerceIn(0, 255) val g = gAcc.toInt().coerceIn(0, 255) val b = bAcc.toInt().coerceIn(0, 255) dst.setRGB(x, y, (a shl 24) or (r shl 16) or (g shl 8) or b) } } return dst } private fun gaussianKernel(radius: Int, sigma: Double): DoubleArray { val size = 2 * radius + 1 val kernel = DoubleArray(size) val sigma2 = 2.0 * sigma * sigma var sum = 0.0 for (i in -radius..radius) { val v = exp(-(i * i) / sigma2) kernel[i + radius] = v sum += v } // 정규화 for (i in kernel.indices) kernel[i] /= sum return kernel } private fun clamp(v: Int, lo: Int, hi: Int): Int = max(lo, min(hi, v)) }