Android/Compose

[Compose][Image]맞춤 페인터 (Custom painter), 이미지 성능 최적화

네모메모 2024. 6. 2. 18:07
반응형

 

 

 

 

 

 

 

 

 

 

 

 

 

맞춤 페인터 (Custom painter)

 

Painter 객체

  • 그릴 수 있는 항목을 나타내고(Android에서 정의된 Drawable API의 대체 API) 객체를 사용하고 있는 컴포저블의 measure 또는 layout 과정에 영향을 미치는 데 사용됩니다.
    • Painter는 지정된 경계 내에서 엄격하게 그려진다.
    • cf) 컴포저블의 measure 또는 layout 과정에 영향을 주지 않는DrawModifier와 다릅니다.
  • painterResource()를 사용하면 애셋에 맞는 올바른 페인터(예: BitmapPainter 또는 VectorPainter)가 반환됩니다.
    (둘의 차이점에 관한 자세한 내용은 ImageBitmap과 ImageVector 섹션을 참고)
    • BitmapPainter는 ImageBitmap을 사용하여 화면에 Bitmap을 그릴 수 있습니다.

 

CustomPainter 만들기

  • CustomPainter를 만들려면, Painter 클래스를 확장하고 onDraw 메서드를 구현합니다.
    이렇게 하면 DrawScope에 액세스하여 맞춤 그래픽을 그릴 수 있습니다.
    또한, CustomPainter가 포함된 컴포저블에 영향을 미치는 데 사용되는 intrinsicSize를 재정의할 수 있습니다.
 
  • (1) CustomPainter 구현하기
    • (1-1). measure 또는 layout 과정에 영향을 주어야 하는 경우, Painter를 사용해 CustomPainter 구현해야 합니다.
    • (1-2). 지정된 경계에서만 렌더링해야 한다면 DrawModifier를 사용해 CustomPainter 구현해야 합니다.
class OverlayImagePainter constructor(
    private val image: ImageBitmap,
    private val imageOverlay: ImageBitmap,
    private val srcOffset: IntOffset = IntOffset.Zero,
    private val srcSize: IntSize = IntSize(image.width, image.height),
    private val overlaySize: IntSize = IntSize(imageOverlay.width, imageOverlay.height)
) : Painter() {

    private val size: IntSize = validateSize(srcOffset, srcSize)
    override fun DrawScope.onDraw() {
        // draw the first image without any blend mode
        drawImage(
            image,
            srcOffset,
            srcSize,
            dstSize = IntSize(
                this@onDraw.size.width.roundToInt(),
                this@onDraw.size.height.roundToInt()
            )
        )
        // draw the second image with an Overlay blend mode to blend the two together
        drawImage(
            imageOverlay,
            srcOffset,
            overlaySize,
            dstSize = IntSize(
                this@onDraw.size.width.roundToInt(),
                this@onDraw.size.height.roundToInt()
            ),
            blendMode = BlendMode.Overlay
        )
    }

    /**
     * Return the dimension of the underlying [ImageBitmap] as it's intrinsic width and height
     */
    override val intrinsicSize: Size get() = size.toSize()

    private fun validateSize(srcOffset: IntOffset, srcSize: IntSize): IntSize {
        require(
            srcOffset.x >= 0 &&
                srcOffset.y >= 0 &&
                srcSize.width >= 0 &&
                srcSize.height >= 0 &&
                srcSize.width <= image.width &&
                srcSize.height <= image.height
        )
        return srcSize
    }
}

 

 
  • (2-1) CustomPainter를 Image컴포저블에 적용하기 : 소스 이미지 위에 이미지를 오버레이
val rainbowImage = ImageBitmap.imageResource(id = R.drawable.rainbow)
val dogImage = ImageBitmap.imageResource(id = R.drawable.dog)
val customPainter = remember {
    OverlayImagePainter(dogImage, rainbowImage)
}
Image(
    painter = customPainter,
    contentDescription = stringResource(id = R.string.dog_content_description),
    contentScale = ContentScale.Crop,
    modifier = Modifier.wrapContentSize()
)
 
그림 1 : 두 이미지를 서로 오버레이하는 맞춤 페인터

 

 

 

  • (2-2) CustomPainter를 다른 컴포저블에 적용하기 : Box컴포저블  위에 이미지를 오버레이
val rainbowImage = ImageBitmap.imageResource(id = R.drawable.rainbow)
val dogImage = ImageBitmap.imageResource(id = R.drawable.dog)
val customPainter = remember {
    OverlayImagePainter(dogImage, rainbowImage)
}
Box(
    modifier =
    Modifier.background(color = Color.Gray)
        .padding(30.dp)
        .background(color = Color.Yellow)
        .paint(customPainter)
) { /** intentionally empty **/ }

 

 

 

 


이미지 성능 최적화를 위한 권장사항

이미지 작업을 할 때는 주의하지 않으면 금방 성능 문제가 발생할 수 있습니다.

Ex) 큰 비트맵으로 작업할 때 OutOfMemoryError가 발생하기가 매우 쉽습니다.

 

1. 필요한 크기의 비트맵만 로드하기

화면에 이미지를 표시하는 경우 이미지 해상도를 낮추거나 이미지 컨테이너 크기까지만 이미지를 로드해야 합니다.
필요보다 더 큰 이미지를 지속적으로 로드하면 GPU 캐시가 소진되어 UI 렌더링 성능이 저하될 수 있습니다.

 

1-0. 이미지 크기를 관리 가이드

  • 출력 이미지에 영향을 주지 않고 이미지 파일을 가능한 한 작게 축소합니다.
  • 이미지 로드 라이브러리를 사용하여 화면의 뷰 크기에 맞게 이미지를 축소합니다.
    • 이렇게 하면 화면 로드 성능이 개선됩니다.
    • (주의) painterResource를 사용하면 이미지가 화면에 표시되는 컴포저블의 크기에 맞춰 조정되지 않습니다.
      작은 컴포저블에 큰 이미지가 있다면 경계에 맞게 이미지를 축소하는 이미지 로드 라이브러리를 사용해야 합니다.

 

1-1. 가능하면 비트맵 대신 벡터 사용하기

  • JPEG 또는 PNG 대신 WebP 형식으로 이미지를 변환합니다.
  • 벡터 이미지는 다른 크기로 조정할 때 픽셀화되지 않으므로 비트맵보다 벡터 이미지를 사용하는 것이 좋습니다.

 

1-2. 다양한 화면 크기(해상도)에 맞는 대체 리소스 제공

  • 앱과 함께 이미지를 제공하는 경우 기기 해상도에 따라 다양한 크기의 애셋을 제공하는 것이 좋습니다.
    이렇게 하면 기기에서 앱의 다운로드 크기가 줄어들고 해상도가 낮은 기기에 더 낮은 해상도의 이미지가 로드되므로 성능이 개선됩니다.
  • 다양한 기기 크기에 맞는 대체 비트맵을 제공하는 방법 :  대체 비트맵 문서를 확인

 

1-3. ImageBitmap 사용 시 그리기 전에 prepareToDraw 호출

  • ImageBitmap을 사용할 때 GPU에 텍스처를 업로드하는 프로세스를 시작하려면 실제로 그리기 전에 ImageBitmap#prepareToDraw()를 호출합니다. 그러면 GPU가 텍스처를 준비하고 화면에 시각 요소를 표시하는 성능을 향상할 수 있습니다.
  • (대부분의 이미지 로드 라이브러리는 이러한 최적화를 이미 실행하지만) ImageBitmap 클래스를 직접 사용한다면 이 사항에 유의해야 합니다.

 

1-4. Painter 대신 `IntDrawableRes` 또는 `URL`을 매개변수로 컴포저블에 전달하기

  • Painter를 매개변수로 전달하는 대신 URL 또는 드로어블 리소스 ID를 매개변수로서 컴포저블에 전달하는 것이 좋습니다.
  • 불안정한 클래스의 경우 데이터가 변경되었는지 컴파일러가 쉽게 추론할 수 없으므로 불필요한 리컴포지션으로 이어질 수 있습니다. 
    • 이미지 처리의 복잡성으로 인해(예: Bitmaps의 equals 함수를 작성하는 데 드는 높은 계산 비용) Painter API는 안정적인 클래스로 명시하지 않습니다.

 


2. 비트맵을 필요한 기간보다 오래 메모리에 저장하지 않기

  • 메모리에 로드하는 비트맵이 많을수록 기기의 메모리가 부족할 가능성이 커집니다.
  • 화면에 대량의 이미지 컴포저블 목록을 로드하는 경우, 큰 목록을 스크롤할 때 메모리가 확보되도록 LazyColumn 또는 LazyRow를 사용합니다.

 


 

3. 대용량 이미지를 AAB/APK 파일로 패키징하지 않기

  • 앱 다운로드 크기가 커지는 주요 원인 중 하나는 AAB 또는 APK 파일 내에 패키징된 그래픽 때문입니다.
    APK Analyzer 도구를 사용하여 필요한 이미지 파일보다 크게 패키징하지 않도록 합니다.
  • 크기를 줄이거나 이미지를 서버에 두고 필요할 때만 다운로드하는 것이 좋습니다.

 

 

 

 

 

 

 

 

 

 

 


출처

 

 

 

- https://developer.android.com/develop/ui/compose/graphics/images/custompainter?hl=ko&_gl=1*byh352*_up*MQ..*_ga*MTc0MzIzMDYxMS4xNzE2ODU5OTU0*_ga_6HH9YJMN9M*MTcxNzMxNTcyMS40LjAuMTcxNzMxNTcyMS4wLjAuMA.. 

 

맞춤 페인터  |  Jetpack Compose  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 맞춤 페인터 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Compose에서 Painter 객체는 그릴 수 있는 항

developer.android.com

 

- https://developer.android.com/develop/ui/compose/graphics/images/optimization?_gl=1*3yb1w0*_up*MQ..*_ga*MTIwNTIxOTMwNC4xNzE3MTQ3ODQy*_ga_6HH9YJMN9M*MTcxNzMwNTQ0OC4yLjAuMTcxNzMwNTQ0OC4wLjAuMA..

 

이미지를 위한 성능 최적화  |  Jetpack Compose  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 이미지를 위한 성능 최적화 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 이미지 작업을 할 때는 주

developer.android.com

 

 

반응형