본문 바로가기
Android/Compose

[Android] Compose에서 이미지를 자유자재로 커스터마이징 해보기

by 태크민 2025. 9. 17.

Compose로 이미지 UI를 작업하다 보면, 단순히 이미지를 보여주는 것 이상으로 다양한 커스터마이징이 필요할 때가 있습니다.


실제로  Compose의 Image 컴포저블에서는 contentScale, colorFilter와 같은 속성을 통해 이미지를 원하는 형태로 제어가 가능한데요.

이를 Modifier와 함께 활용하면, 모양을 자르거나(border/clip), 색상을 입히거나, 효과를 적용하는 등 표현 범위를 더 넓힐 수 있습니다.

 

이번 글에서는 이러한 기능들을 정리해보며 하나씩 알아보겠습니다.

 


ContentScale

contentScale 옵션은 이미지가 컴포저블 영역에 맞게 어떤 방식으로 표시될지를 결정합니다.

예를 들어, 이미지를 크롭하거나, 영역에 맞게 비율 조정하여 표시하도록 설정할 수 있습니다. 별도로 지정하지 않으면 기본값으로 ContentScale.Fit이 적용됩니다.

 

아래 예제에서는 Image 컴포저블의 크기를 150dp로 제한하고 테두리를 추가했으며, 서로 다른 ContentScale 옵션 적용 시 어떻게 보여지는지 확인할 수 있도록 배경색을 노란색으로 설정했습니다.

val imageModifier = Modifier
    .size(150.dp)
    .border(BorderStroke(1.dp, Color.Black))
    .background(Color.Yellow)
Image(
    painter = painterResource(id = R.drawable.dog),
    contentDescription = stringResource(id = R.string.dog_content_description),
    contentScale = ContentScale.Fit,
    modifier = imageModifier
)

 

ContentScale 옵션을 적용하면 이미지가 서로 다르게 표시됩니다. 이제 부터 위 코드를 바탕으로 ContentScale 별로 어떻게 표시가 되는지 확인해 보겠습니다.

 

원본 이미지

아래와 같은 원본 이미지가 있다고 가정해보겠습니다.

세로형 이미지(좌) / 가로형 이미지(우)

 

ContentScale.Fit

이미지의 가로세로 비율을 유지한 채 영역에 맞게 균일하게 확대 또는 축소합니다. (기본값)
만약 원본 이미지가 컴포저블 영역보다 작다면, 영역에 맞도록 이미지가 확대되어 표시됩니다.

 

ContentScale.Crop

이미지를 가운데를 기준으로 확대하여, 주어진 영역을 빈 공간 없이 꽉 채워서 보여주는 방식입니다. 이 과정에서 이미지의 비율은 유지되지만, 화면에 다 들어오지 않는 바깥 부분은 잘려나갑니다.

쉽게 말해, “이미지를 영역에 꽉 차도록 크게 보여주되, 필요한 만큼 잘라내는 방식”입니다.

예를 들어, 가로로 긴 이미지를 정사각형 영역에 넣을 경우, 세로 길이에 맞춰 확대되기 때문에 양옆이 잘려 보이게 됩니다.

 

ContentScale.FillHeight

이미지의 가로세로 비율을 유지한 상태에서 , 주어진 영역의 높이에 맞도록 이미지를 확대 또는 축소합니다.
이 경우 높이에 맞춰지기 때문에, 가로 폭이 남거나 잘려 보일 수 있습니다.

위의 예시는 레이아웃의 너비와 높이가 모두 고정된 경우입니다.

만약, 레이아웃의 너비가 고정되어 있고 높이가 가변적이라면, 이미지는 비율을 유지하면서 높이를 기준으로 확장됩니다.

 

예시(왼쪽)를 들면 아래 사진과 같이 너비는 고정이지만, 높이가 늘어나게 됩니다. 이게 가능한 이유는 FillHeight는 가로세로 비율을 유지하는 특성이 있기 때문입니다.

 

ContentScale.FillWidth

이미지의 가로세로 비율을 유지한 상태에서, 주어진 영역의 너비에 맞도록 이미지를 확대 또는 축소합니다.
너비에 맞춰지기 때문에, 세로(height)는 세로 폭이 남거나 잘려 보일 수 있습니다.

위의 예시는 레이아웃의 너비와 높이가 모두 고정된 경우입니다.

만약, 레이아웃의 높이가 고정되어 있고 너비가 가변적이라면, 이미지는 비율을 유지하면서 너비를 기준으로 확장됩니다.

예시(오른쪽)로 들면 아래와 같이 높이가 고정이지만, 너비가 늘어나게 됩니다. 이게 가능한 이유는 FillWidth는 가로세로 비율을 유지하는 특성이 있기 때문입니다.

 

ContentScale.FillBounds

FillBounds는 앞서 얘기한 FillWidth와 FillHeight랑은 성격이 좀 다릅니다.

이미지를 가로와 세로 방향으로 비율을 유지하지 않고 각각 늘리거나 줄여, 주어진 영역을 빈 공간 없이 완전히 채우도록 맞춥니다.
즉, 영역에 딱 맞게 들어가긴 하지만, 원본 비율이 유지되지 않기 때문에 이미지가 찌그러지거나 왜곡될 수 있습니다.

특히 이미지가 배치되는 레이아웃의 비율이 원본 이미지와 다를 경우, 화면에 꽉 차게 표시되지만, 가로나 세로로 늘어나 보이는 왜곡이 발생하게 됩니다.

 

ContentScale.Inside

이미지의 가로세로 비율을 유지하면서, 주어진 영역(bounds) 안에 이미지가 완전히 들어오도록 표시합니다.
즉, 이미지가 영역 밖으로 나가지 않도록 “안쪽에 맞춰” 배치되는 방식입니다.

원본 이미지의 가로/세로 크기가 대상 영역보다 작거나 같다면, 이미지를 확대하지 않고 있는 그대로 표시합니다. 이 경우 동작 방식은 None과 거의 동일하게 보입니다.
언제나 이미지가 영역 안에 완전히 포함되며, 영역보다 이미지가 작은 경우에는 스케일링이 적용되지 않습니다.

 

ContentScale.None

이미지에 어떠한 스케일링도 적용하지 않습니다.
원본 크기를 그대로 유지하며, 영역보다 이미지가 작더라도 빈 공간을 채우기 위해 확대되지 않습니다.

 


Image 컴포저블을 특정 모양으로 자르기 (Clip)

이미지를 특정 모양에 맞게 잘라내고 싶다면, Jetpack Compose에서 제공하는 clip Modifier를 사용할 수 있습니다.

 

예를 들어, 이미지를 원형으로 잘라내고 싶다면 Modifier.clip(CircleShape)를 적용하면 됩니다:

Image(
    painter = painterResource(id = R.drawable.dog),
    contentDescription = stringResource(id = R.string.dog_content_description),
    contentScale = ContentScale.Crop,
    modifier = Modifier
        .size(200.dp)
        .clip(CircleShape)
)

그림 1. CircleShape을 사용한 이미지 클리핑

 

 

모서리가 둥근 사각형 형태로 자르고 싶다면, RoundedCornerShape(16.dp)처럼 원하는 코너 값을 지정해줄 수 있습니다:

Image(
    painter = painterResource(id = R.drawable.dog),
    contentDescription = stringResource(id = R.string.dog_content_description),
    contentScale = ContentScale.Crop,
    modifier = Modifier
        .size(200.dp)
        .clip(RoundedCornerShape(16.dp))
)

그림 2. RoundedCornerShape을 사용한 이미지 클리핑

 

기본 제공되는 도형 외에도, Shape를 확장해 직접 원하는 형태의 클리핑 모양을 만들 수도 있습니다.


아래 예시는 가로 폭을 ¼ ~ ¾만 사용하는 납작한 타원(Squashed Oval) 모양으로 이미지를 잘라내는 Shape입니다:

class SquashedOval : Shape {
    override fun createOutline(
        size: Size,
        layoutDirection: LayoutDirection,
        density: Density
    ): Outline {
        val path = Path().apply {
            // 컨테이너 너비의 1/4 지점부터 3/4 지점까지 타원을 생성
            addOval(
                Rect(
                    left = size.width / 4f,
                    top = 0f,
                    right = size.width * 3 / 4f,
                    bottom = size.height
                )
            )
        }
        return Outline.Generic(path = path)
    }
}

Image(
    painter = painterResource(id = R.drawable.dog),
    contentDescription = stringResource(id = R.string.dog_content_description),
    contentScale = ContentScale.Crop,
    modifier = Modifier
        .size(200.dp)
        .clip(SquashedOval())
)

그림 3. 커스텀 Path Shape을 사용한 이미지 클리핑 예시

 


Image 컴포저블에 Border 추가하기

Modifier.border()와 Modifier.clip()을 조합하면 이미지에 테두리를 쉽게 적용할 수 있습니다.


아래 예시는 이미지를 원형으로 자르고, 그 외곽에 노란색 테두리를 추가한 코드입니다:

val borderWidth = 4.dp

Image(
    painter = painterResource(id = R.drawable.dog),
    contentDescription = stringResource(id = R.string.dog_content_description),
    contentScale = ContentScale.Crop,
    modifier = Modifier
        .size(150.dp)
        .border(
            BorderStroke(borderWidth, Color.Yellow),
            CircleShape
        )
        .padding(borderWidth)
        .clip(CircleShape)
)

그림 4. border와 clip을 활용한 이미지 테두리 적용 예시

 

단색 테두리 대신 그라데이션 테두리를 만들고 싶다면, Brush API를 활용할 수 있습니다.


아래 예시는 이미지 외곽에 레인보우 그라데이션 테두리를 적용한 코드입니다:

val rainbowColorsBrush = remember {
    Brush.sweepGradient(
        listOf(
            Color(0xFF9575CD),
            Color(0xFFBA68C8),
            Color(0xFFE57373),
            Color(0xFFFFB74D),
            Color(0xFFFFF176),
            Color(0xFFAED581),
            Color(0xFF4DD0E1),
            Color(0xFF9575CD)
        )
    )
}

val borderWidth = 4.dp

Image(
    painter = painterResource(id = R.drawable.dog),
    contentDescription = stringResource(id = R.string.dog_content_description),
    contentScale = ContentScale.Crop,
    modifier = Modifier
        .size(150.dp)
        .border(
            BorderStroke(borderWidth, rainbowColorsBrush),
            CircleShape
        )
        .padding(borderWidth)
        .clip(CircleShape)
)

그림 5. Rainbow Gradient Circle Border

 


커스텀 비율로 이미지 표시하기

이미지를 특정 비율(Aspect Ratio)에 맞게 보여주고 싶다면, Modifier.aspectRatio()를 사용하면 됩니다.


예를 들어, 16:9 비율로 표시하려면 다음과 같이 설정합니다:

Image(
    painter = painterResource(id = R.drawable.dog),
    contentDescription = stringResource(id = R.string.dog_content_description),
    modifier = Modifier.aspectRatio(16f / 9f)
)

그림 6. Modifier.aspectRatio(16f/9f)를 적용한 이미지 예시

 


이미지의 픽셀 색상을 변환하기

Image 컴포저블은 colorFilter 파라미터를 통해 이미지의 픽셀 색상을 변경할 수 있습니다. 이를 활용하면 이미지에 다양한 색상 효과나 필터를 적용할 수 있습니다.

Tint로 이미지 색상 변경하기

ColorFilter.tint(color, blendMode)를 사용하면 지정한 색상과 블렌드 모드를 활용해 이미지에 색상을 입힐 수 있습니다.
tint는 기본적으로 BlendMode.SrcIn을 사용하여, 이미지가 표시되는 영역에만 지정 색상이 입혀진 형태로 렌더링됩니다.
이는 테마에 따라 색상이 변경되어야 하는 아이콘이나 벡터 이미지에 매우 유용합니다.

그림 7. BlendMode.SrcIn과 함께 적용된 ColorFilter.tint

 

BlendMode를 변경하면 다른 색상 효과를 낼 수도 있습니다.


예를 들어, BlendMode.Darken과 초록색(Color.Green)을 함께 적용하면 다음과 같은 결과가 나옵니다:

Image(
    painter = painterResource(id = R.drawable.dog),
    contentDescription = stringResource(id = R.string.dog_content_description),
    colorFilter = ColorFilter.tint(Color.Green, blendMode = BlendMode.Darken)
)

그림 8. Color.Green + BlendMode.Darken 적용 예시

 

ColorMatrix로 이미지 필터 적용하기

ColorFilter.colorMatrix()를 사용하면 ColorMatrix를 기반으로 다양한 색상 필터를 적용할 수 있습니다.


예를 들어, 채도를 제거해 흑백 이미지(Black & White) 효과를 주고 싶다면 setToSaturation(0f)를 적용하면 됩니다.

Image(
    painter = painterResource(id = R.drawable.dog),
    contentDescription = stringResource(id = R.string.dog_content_description),
    colorFilter = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0f) })
)

그림 9. Saturation 0 적용 (흑백 필터)

 

이미지의 대비(Contrast) & 밝기(Brightness) 조절하기

ColorMatrix 값을 직접 변경하면 이미지의 대비와 밝기를 조정할 수 있습니다.


아래 예시는 대비를 높이고 밝기를 낮춘 ColorMatrix 설정 예시입니다:

val contrast = 2f // 0f..10f (기본값은 1)
val brightness = -180f // -255f..255f (기본값은 0)
val colorMatrix = floatArrayOf(
    contrast, 0f, 0f, 0f, brightness,
    0f, contrast, 0f, 0f, brightness,
    0f, 0f, contrast, 0f, brightness,
    0f, 0f, 0f, 1f, 0f
)

Image(
    painter = painterResource(id = R.drawable.dog),
    contentDescription = stringResource(id = R.string.dog_content_description),
    colorFilter = ColorFilter.colorMatrix(ColorMatrix(colorMatrix))
)

그림 10. ColorMatrix로 밝기 & 대비 조절 예시

 

이미지 색상 반전(Invert)하기

이미지의 색상을 반전하고 싶다면, ColorMatrix 값을 반전용 매트릭스로 설정하면 됩니다.
이는 사진 네거티브 필름과 같은 효과를 만들 때 사용됩니다.

val colorMatrix = floatArrayOf(
    -1f, 0f, 0f, 0f, 255f,
    0f, -1f, 0f, 0f, 255f,
    0f, 0f, -1f, 0f, 255f,
    0f, 0f, 0f, 1f, 0f
)

Image(
    painter = painterResource(id = R.drawable.dog),
    contentDescription = stringResource(id = R.string.dog_content_description),
    colorFilter = ColorFilter.colorMatrix(ColorMatrix(colorMatrix))
)
그림 11. ColorMatrix를 이용한 색상 반전(Invert)

 

이미지에 Blur(블러) 효과 적용하기

이미지를 흐릿하게 처리하고 싶다면, Modifier.blur()를 사용하면 됩니다.
radiusX와 radiusY 값은 각각 가로/세로 방향의 블러 강도를 의미합니다.

Image(
    painter = painterResource(id = R.drawable.dog),
    contentDescription = stringResource(id = R.string.dog_content_description),
    contentScale = ContentScale.Crop,
    modifier = Modifier
        .size(150.dp)
        .blur(
            radiusX = 10.dp,
            radiusY = 10.dp,
            edgeTreatment = BlurredEdgeTreatment(RoundedCornerShape(8.dp))
        )
)

그림 12. BlurEffect 적용 이미지

 

이미지에 블러를 적용할 때는 BlurredEdgeTreatment.Unbounded보다는 BlurredEdgeTreatment(shape)를 사용하는 것이 권장됩니다.

  • Unbounded는 원본 경계를 넘어 블러가 퍼지는 효과를 적용할 때 사용되며,
  • 이미지처럼 대부분 원본 영역 안에서만 렌더링되는 콘텐츠에는 적합하지 않을 수 있습니다.

특히 둥근 모서리 형태로 블러를 적용하고 싶을 때는 BlurredEdgeTreatment(RoundedCornerShape(...))가 훨씬 자연스러운 결과를 보여줍니다.

 

아래 예시는 Unbounded를 사용했을 때의 결과로, 이미지의 외곽까지 블러가 퍼져 모서리가 흐릿해 보이는 예시입니다:

Image(
    painter = painterResource(id = R.drawable.dog),
    contentDescription = stringResource(id = R.string.dog_content_description),
    contentScale = ContentScale.Crop,
    modifier = Modifier
        .size(150.dp)
        .blur(
            radiusX = 10.dp,
            radiusY = 10.dp,
            edgeTreatment = BlurredEdgeTreatment.Unbounded
        )
        .clip(RoundedCornerShape(8.dp))
)

그림 13. BlurredEdgeTreatment.Unbounded 적용 예시

 


마무리

이번 글은 Jetpack Compose에서 Image를 다룰 때 꼭 알아두면 좋은 기능들을 정리해봤습니다.
저도 Compose로 이미지 UI 작업을 하면서 한 번씩 헷갈리거나 찾아봤던 내용들이라, 이렇게 정리해두면 나중에 다시 참고하기 좋을 것 같아요.
필요하신 분들께도 도움이 되었으면 합니다.

 

 

끝.


참고자료

https://developer.android.com/develop/ui/compose/graphics/images/customize

 

이미지 맞춤설정  |  Jetpack Compose  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 이미지 맞춤설정 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Image 컴포저블의 속성(contentScale, colorF

developer.android.com