본문 바로가기

Android

[Android] Bitmap 이미지 블러 처리하기 (RenderNode)

반응형

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

 

Issues with android blur using Renderscript

I am using following code for building blurred background for my imageview. public class BlurBuilder { private static final float BITMAP_SCALE = 0.2f; private static final float BLUR_RADI...

stackoverflow.com

 

 

반응형