본문 바로가기

Android/Tech

Bitmap, Drawable, BitmapDrawable, ImageBitmap, AndroidBitmap

Unsplash, Steve Johnson.

일단, 헷갈립니다.

해당 포스트를 작성하는 것에는 여러 가지 이유가 있겠지만, 가장 우선적인 이유로는 제가 헷갈렸기 때문입니다. 안드로이드에는 이미지 처리를 위한 수많은 클래스와 인터페이스가 존재하는데, 이 모든 것이 통폐합되어 이미지의 오리지널 파일인 Bitmap 과 안드로이드에서의 이미지 표시를 위한 Drawable 만 남았으면 좋겠다 싶을 정도로 이들이 헷갈리곤 합니다.

 

나를 바꿀 수 없다면 환경을 바꾸면 되고, 환경을 바꿀 수 없다면 나를 바꾸면 됩니다. 하나씩 알아봅니다.

 


Bitmap

Bitmap 의 기본적인 정보(feat.Webp) 에 대해서는 다음 포스트에 정리해두었습니다.

 

비트맵과 WebP

인간의 눈으로 디지털 디바이스에서 표시되는 이미지 (그림, 사진) 를 보는 것은 픽셀이라는 작은 점들이 빼곡히 모여있는 것을 보는 것과 같습니다. 학창 시절 미술 시간에 한 번 쯤은 해보았던

blothhundr.tistory.com

안드로이드에서의 Bitmap.class

안드로이드에서 Bitmap 클래스는 이미지 데이터를 처리하기 위해 사용됩니다. 이미지 데이터를 메모리에 저장하고, 픽셀 데이터에 대한 액세스 편의를 제공하며, 이미지 데이터의 크기나 형식을 설정하고 제어할 수 있도록 구현되어 있습니다.

 

안드로이드에서 Bitmap 객체를 생성하는 방법은 다양한데요, 가장 일반적인 방법은 이미지 파일이나 리소스를 로드하여 BitmapFactory 클래스를 사용, Bitmap 객체로 변환하는 방법입니다. (안드로이드 내에서 이미지 파일이나 리소스를 로드한다고해서, 곧바로 Bitmap 파일로 사용할 수 있는 것은 아닙니다.)

 

리소스 로드 방식
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.image);

파일 로드 방식
val file = File("path/to/image/file");
val bitmap = BitmapFactory.decodeFile(file.absolutePath);

 

이 외에도, Bitmap 파일을 코드로 생성하고, 이미지 데이터를 직접 설정하는 방식도 가능합니다. 코드로 직접 비트맵을 그려내는 것인데, 사실 사용할 일은 잘 없을 것 같습니다.

 

Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);

 

또한, Drawable 로 변환이 가능합니다.

 

bitmap.toDrawable(resources)

 

반환 타입은 BitmapDrawable 입니다. BitmapDrawable 에 관해서는 후술하겠습니다.

파라미터로는 Resources 타입을 받는데, 앱 리소스에 접근하기 위한 클래스입니다. AppCompatActivity 의 필드인 mResources 를 getResources() 로 받아와도 되고, 그냥 null 을 입력해줘도 됩니다.

 

 

Bitmap 객체는 다음과 같은 기능을 제공합니다.

  • 이미지 데이터의 크기 및 형식, 색상 등을 설정할 수 있습니다.
  • 이미지 데이터를 비트맵 파일로 저장할 수 있습니다.
  • 이미지 데이터를 Canvas 에 그리거나, ImageView (Image Composable) 에 표시할 수 있습니다.
  • 이미지 데이터의 일부 영역을 자를 수 있습니다.
  • 이미지 데이터의 픽셀에 직접 액세스할 수 있습니다.

 


Drawable

Drawable 클래스는 화면에 그려지는 모든 그래픽 요소를 처리하기 위한 클래스입니다. 이미지, 색상, 그라데이션, 모양, 상태 등 다양한 그래픽 요소를 처리할 수 있으며, ImageView 에 사용됩니다. Image Composable 과는 자주 사용되지 않는 것이, Drawable Class 를 통해 이미지를 표시한다기 보다 직접적으로 이미지 리소스에 접근하는 방식으로 이미지를 표시하기 때문입니다. 

 

XML
imageView.setImageDrawable(ContextCompat.getDrawable(this@MainActivity, R.drawable.ic_add_image))

Jetpack Compose
Image(
      painter = painterResource(id = R.drawable.bg_splash),
      contentDescription = "splashBackground"
)

 

Drawable 역시, Bitmap 으로 변환할 수 있습니다.

 

val drawable = ContextCompat.getDrawable(this@MainActivity, R.drawable.ic_add_image)
drawable?.toBitmap(240, 240, Bitmap.Config.ARGB_8888)

 

Drawable 클래스가 제공하는 기능들은 다음과 같습니다.

  • 그래픽 요소의 크기, 모양, 색상 등을 설정할 수 있습니다.
  • 그래픽 요소를 비트맵 파일로 저장할 수 있습니다. 
  • 그래픽 요소를 Canvas 에 그리거나 ImageView 에 표시할 수 있습니다.
  • 그래픽 요소의 상태를 변경할 수 있습니다.
  • 그래픽 요소의 터치 이벤트를 처리할 수 있습니다.

 

Drawable 클래스는 다양한 하위 클래스를 갖고 있는데요, 가장 자주 사용되는 것은 BitmapDrawable 입니다.

 


BitmapDrawable

Bitmap 이미지를 표시하기 위해 사용되는 Drawable 의 자손 클래스입니다. Bitmap 을 Drawable 로 변환시켜주는 래퍼 클래스 정도로 보면 좋을 것 같습니다. Uri 를 통해 InputStream 을 생성하고, 해당 Stream 으로부터 Bitmap 을 받아 온 뒤, 이를 BitmapDrawable 로 변환해야만 이미지 표시를 위해 사용할 수 있습니다.

해당 객체의 프로퍼티로 Bitmap 이 있기 때문에, 직접 액세스하여 Bitmap 을 얻어 올 수 있습니다.

 

 


ImageBitmap

Jetpack Compose 에서 이미지를 다루는 인터페이스입니다. 이미지 리소스의 프로퍼티에 대한 접근이 가능하며, Jetpack Compose 의 이미지 관련 Composable 의 bitmap 파라미터에 직접 할당이 가능합니다. 

🤔 왜 Image Composable 은 그냥 Bitmap 을 받아서 처리해주지 않고 ImageBitmap 을 받나요?

Bitmap 은 이미지 원본 파일이며, ImageBitmap 은 Composable 함수에서 이미지를 렌더링하는 데 필요한 최소한의 데이터만을 포함합니다. 앱의 성능을 위해서입니다. 이미지의 너비와 높이, 픽셀 데이터만 포함하므로 앱에서 사용하지 않는 다른 이미지 데이터가 메모리에 로드되는 것을 방지할 수 있습니다.

 

또한, ImageBitmap 인터페이스는 이미지를 로드하고 캐시하는 데 사용할 수 있는 ImageAsset 객체를 반환할 수 있습니다. 해당 객체는 이미지 데이터를 비동기적으로 로드하고 디코딩하여 CPU 와 메모리를 최적화 하는데 도움이 됩니다. 즉, Jetpack Compose 에서 이미지를 사용하는 가장 효율적인 방식입니다.

asAndroidBitmap?

ImageBitmap 의 메서드로, ImageBitmap 을 AndroidBitmap 으로 바꿔주는 함수라고 예상할 수 있는데, AndroidBitmap 이라는 별도의 클래스나 타입이 존재하는 것은 아니고, 그냥 ImageBitmap 을 Bitmap 으로 변환해주는 함수입니다. ImageBitmap 에는 bitmap 프로퍼티에 접근할 수 없기 때문에, 함수로 접근하여야 합니다.

 


정리

Bitmap : 원본 이미지 Bitmap 파일. 이미지 표현을 위한 경우 곧바로 사용되지는 않음. (painterResource() 를 통해 리소스를 로드하면 내부적으로 ImageBitmap 으로 변환시켜 사용)

Drawable : Bitmap 뿐 아니라 다른 모든 그래픽 리소스를 포함. 그래픽 리소스의 표현을 위해 존재하나, 곧바로 사용되지는 않음. (리소스를 로드하면 Uri 를 사용하여 InputStream 을 만들고, 해당 Stream 으로부터 Bitmap 을 얻어 온 뒤 이를 BitmapDrawable 로 변환하여 사용)

 

즉, BitmapDrawableImageBitmap 이 비슷한 역할을 한다고 볼 수 있을 것 같습니다. 다만 추상화 수준이 달라, Compose 의 ImageBitmap 쪽이 훨씬 파악하기 수월하고 간단합니다. 

 

AndroidBitmap : Bitmap.


이미지 처리 관련 클래스 및 인터페이스에 대해 알아보았습니다. 사용할 때마다 어느 타입끼리 호환이 되고 변환이 되는지 알기 복잡했는데, 이번 기회에 정리해보니 확실하게 이해할 수 있었습니다.