반응형
RenderScript -> RenderNode
기존 이미지 블러 처리에 사용되던 RenderScript가 Android12 부터 deprecated 되었습니다.
RenderScript 대신 RenderNode를 사용하는 코드를 공유하도록 하겠습니다.
private const val BITMAP_SCALE = 0.6f
private const val BLUR_RADIUS = 5f
private fun blurImageAboveOs12(image: Bitmap): Bitmap? {
val width = Math.round(image.width * BITMAP_SCALE)
val height = Math.round(image.height * BITMAP_SCALE)
var hardwareBuffer: HardwareBuffer? = null
var newImage: Image? = null
var imageReader: ImageReader? = null
var renderNode: RenderNode? = null
var hardwareRenderer: HardwareRenderer? = null
imageReader = ImageReader.newInstance(
width, height,
PixelFormat.RGBA_8888, 1,
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT
)
// 이미지 흐리게 처리하기 위한 RenderNode, HardwareRenederer, RenderEffect 생성
renderNode = RenderNode("BlurEffect")
hardwareRenderer = HardwareRenderer()
hardwareRenderer.setSurface(imageReader.surface)
hardwareRenderer.setContentRoot(renderNode)
renderNode.setPosition(0, 0, imageReader.width, imageReader.height)
val blurRenderEffect = RenderEffect.createBlurEffect(
BLUR_RADIUS, BLUR_RADIUS,
Shader.TileMode.MIRROR
)
renderNode.setRenderEffect(blurRenderEffect)
// 그림을 기록, 렌더링 요청을 만든 후 완료될 때까지 기다림
val renderCanvas = renderNode.beginRecording()
renderCanvas.drawBitmap(image, 0f, 0f, null)
renderNode.endRecording()
hardwareRenderer.createRenderRequest()
.setWaitForPresent(true)
.syncAndDraw()
// 이미지를 획득하고, HardwareBuffer를 래핑하는 Bitmap 반환
val image = imageReader.acquireNextImage() ?: throw RuntimeException("No Image")
val hardwareBuffer = image.hardwareBuffer ?: throw RuntimeException("No HardwareBuffer")
val bitmap = Bitmap.wrapHardwareBuffer(hardwareBuffer, null) ?: throw RuntimeException("Create Bitmap Failed")
return bitmap
}
BITMAP_SCALE : 이미지를 얼마나 자를 건지의 값 (0~1)
BLUR_RADIUS : 블러 처리를 얼마나 세게 할지의 값 (0 < radius <= 25)
이미지 렌더링 완료 후 해제 처리를 해주어야 합니다.
위에서 리턴된 비트맵도 사용 이후 recycle 처리 해주어야 하겠죠.
hardwareBuffer.close()
image.close()
imageReader.close()
renderNode.discardDisplayList()
hardwareRenderer.destroy()
OS11 이하는?
위 코드는 OS12 이상부터 사용할 수 있는데요, OS11 이하 코드도 필요하신 분들을 위해 남깁니다.
저는 기존 RenderScript를 사용했을 때, 저사양 기기에서 깨지는 현상이 있었습니다.
그래서 RenderScript를 사용하지 않는 코드를 StackOverflow에서 발견하여 공유합니다.
비트맵을 가져와 픽셀 단위로 변환하는 코드입니다.
private fun blurImageBelowOs11(sentBitmap: Bitmap): Bitmap? {
var originBitmap = sentBitmap
val radius = ImageUtils.BLUR_RADIUS.toInt()
val width = Math.round(originBitmap.width * ImageUtils.BITMAP_SCALE)
val height = Math.round(originBitmap.height * ImageUtils.BITMAP_SCALE)
originBitmap = Bitmap.createScaledBitmap(originBitmap, width, height, false)
val bitmap = originBitmap.copy(originBitmap.config, true)
val w = bitmap.width
val h = bitmap.height
val pix = IntArray(w * h)
bitmap.getPixels(pix, 0, w, 0, 0, w, h)
val wm = w - 1
val hm = h - 1
val wh = w * h
val div = radius + radius + 1
val r = IntArray(wh)
val g = IntArray(wh)
val b = IntArray(wh)
var rSum: Int
var gSum: Int
var bSum: Int
var x: Int
var y: Int
var i: Int
var p: Int
var yp: Int
var yi: Int
val vMin = IntArray(max(w.toDouble(), h.toDouble()).toInt())
var divSum = (div + 1) shr 1
divSum *= divSum
val dv = IntArray(256 * divSum)
i = 0
while (i < 256 * divSum) {
dv[i] = (i / divSum)
i++
}
yi = 0
var yw = 0
val stack = Array(div) { IntArray(3) }
var stackPointer: Int
var stackStart: Int
var sir: IntArray
var rbs: Int
val r1 = radius + 1
var rOutSum: Int
var gOutSum: Int
var bOutSum: Int
var rInSum: Int
var gInSum: Int
var bInSum: Int
y = 0
while (y < h) {
bSum = 0
gSum = 0
rSum = 0
bOutSum = 0
gOutSum = 0
rOutSum = 0
bInSum = 0
gInSum = 0
rInSum = 0
i = -radius
while (i <= radius) {
p = pix[(yi + min(wm.toDouble(), max(i.toDouble(), 0.0))).toInt()]
sir = stack[i + radius]
sir[0] = (p and 0xff0000) shr 16
sir[1] = (p and 0x00ff00) shr 8
sir[2] = (p and 0x0000ff)
rbs = (r1 - abs(i.toDouble())).toInt()
rSum += sir[0] * rbs
gSum += sir[1] * rbs
bSum += sir[2] * rbs
if (i > 0) {
rInSum += sir[0]
gInSum += sir[1]
bInSum += sir[2]
} else {
rOutSum += sir[0]
gOutSum += sir[1]
bOutSum += sir[2]
}
i++
}
stackPointer = radius
x = 0
while (x < w) {
r[yi] = dv[rSum]
g[yi] = dv[gSum]
b[yi] = dv[bSum]
rSum -= rOutSum
gSum -= gOutSum
bSum -= bOutSum
stackStart = stackPointer - radius + div
sir = stack[stackStart % div]
rOutSum -= sir[0]
gOutSum -= sir[1]
bOutSum -= sir[2]
if (y == 0) {
vMin[x] = min((x + radius + 1).toDouble(), wm.toDouble()).toInt()
}
p = pix[yw + vMin[x]]
sir[0] = (p and 0xff0000) shr 16
sir[1] = (p and 0x00ff00) shr 8
sir[2] = (p and 0x0000ff)
rInSum += sir[0]
gInSum += sir[1]
bInSum += sir[2]
rSum += rInSum
gSum += gInSum
bSum += bInSum
stackPointer = (stackPointer + 1) % div
sir = stack[stackPointer % div]
rOutSum += sir[0]
gOutSum += sir[1]
bOutSum += sir[2]
rInSum -= sir[0]
gInSum -= sir[1]
bInSum -= sir[2]
yi++
x++
}
yw += w
y++
}
x = 0
while (x < w) {
bSum = 0
gSum = 0
rSum = 0
bOutSum = 0
gOutSum = 0
rOutSum = 0
bInSum = 0
gInSum = 0
rInSum = 0
yp = -radius * w
i = -radius
while (i <= radius) {
yi = (max(0.0, yp.toDouble()) + x).toInt()
sir = stack[i + radius]
sir[0] = r[yi]
sir[1] = g[yi]
sir[2] = b[yi]
rbs = (r1 - abs(i.toDouble())).toInt()
rSum += r[yi] * rbs
gSum += g[yi] * rbs
bSum += b[yi] * rbs
if (i > 0) {
rInSum += sir[0]
gInSum += sir[1]
bInSum += sir[2]
} else {
rOutSum += sir[0]
gOutSum += sir[1]
bOutSum += sir[2]
}
if (i < hm) {
yp += w
}
i++
}
yi = x
stackPointer = radius
y = 0
while (y < h) {
// Preserve alpha channel: ( 0xff000000 & pix[yi] )
pix[yi] =
(-0x1000000 and pix[yi]) or (dv[rSum] shl 16) or (dv[gSum] shl 8) or dv[bSum]
rSum -= rOutSum
gSum -= gOutSum
bSum -= bOutSum
stackStart = stackPointer - radius + div
sir = stack[stackStart % div]
rOutSum -= sir[0]
gOutSum -= sir[1]
bOutSum -= sir[2]
if (x == 0) {
vMin[y] = (min((y + r1).toDouble(), hm.toDouble()) * w).toInt()
}
p = x + vMin[y]
sir[0] = r[p]
sir[1] = g[p]
sir[2] = b[p]
rInSum += sir[0]
gInSum += sir[1]
bInSum += sir[2]
rSum += rInSum
gSum += gInSum
bSum += bInSum
stackPointer = (stackPointer + 1) % div
sir = stack[stackPointer]
rOutSum += sir[0]
gOutSum += sir[1]
bOutSum += sir[2]
rInSum -= sir[0]
gInSum -= sir[1]
bInSum -= sir[2]
yi += w
y++
}
x++
}
bitmap.setPixels(pix, 0, w, 0, 0, w, h)
originBitmap.recycle()
return bitmap
}
StackOverflow 출처 : https://stackoverflow.com/questions/39280123/issues-with-android-blur-using-renderscript
반응형
'Android' 카테고리의 다른 글
[Android] Compose의 remember 그리고 MutableState (TextField 값 바꾸기) (2) | 2024.12.20 |
---|---|
[Android] Compose 사용 이유, 맛보기 (0) | 2022.07.08 |
[Android] Context란? (0) | 2021.10.22 |
[Android] 레이아웃 Background 둥글게 만들기 (shape, radius, border stroke) - XML (1) | 2021.03.30 |
[Android] Retrofit 2.0 사용방법 (Kotlin) - 3. RxJava + Retrofit (0) | 2020.12.03 |