Как настроить изображение

Вы можете настраивать изображения, используя свойства composable-функции Image (contentScale, colorFilter). Также можно применять существующие модификаторы, чтобы добавлять к Image различные эффекты. Модификаторы можно использовать с любой composable-функцией, а не только с Image, тогда как contentScale и colorFilter – это явные параметры функции Image.

Масштаб контента

Укажите параметр contentScale, чтобы обрезать изображение или изменить способ его масштабирования в пределах границ. Если вы не укажете параметр contentScale, по умолчанию будет использоваться ContentScale.Fit.

В следующем примере размер composable-функции Image ограничен 150 dp, у нее есть граница, а для фона composable-функции Image задан желтый цвет, чтобы продемонстрировать различные варианты 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 Результат – вертикальное изображение: Результат – горизонтальное изображение:
ContentScale.Fit – равномерно масштабировать изображение, сохраняя соотношение сторон (по умолчанию). Если контент меньше указанного размера, изображение увеличивается до границ. Портрет собаки, масштабированный равномерно. Горизонтальное изображение собаки, масштабированное равномерно.
ContentScale.Crop – обрезать изображение по центру, чтобы оно поместилось в доступное пространство. Вертикальное изображение, обрезанное до квадратного формата, на котором видна только центральная часть. Горизонтальное изображение, обрезанное до квадрата. Видна только центральная часть.
ContentScale.FillHeight: масштабировать источник с сохранением соотношения сторон, чтобы границы соответствовали высоте целевого объекта. Вертикальное изображение, масштабированное по высоте квадратной рамки. Слева и справа виден желтый фон. Горизонтальное изображение, масштабированное по высоте квадратного кадра, с обрезанными боковыми сторонами.
ContentScale.FillWidth – масштабирование источника с сохранением соотношения сторон, чтобы границы соответствовали ширине целевого объекта. Вертикальное изображение, масштабированное по ширине квадратной рамки, с обрезанными верхней и нижней частями. Горизонтальное изображение, масштабированное по ширине квадратной рамки. Оно показано полностью, а сверху и снизу виден желтый фон.
ContentScale.FillBounds – масштабировать контент по вертикали и горизонтали неравномерно, чтобы заполнить границы целевого объекта. Примечание. Если вы поместите изображение в контейнер, который не соответствует его пропорциям, оно будет искажено. Вертикальное изображение, растянутое по горизонтали, чтобы заполнить квадратную рамку. Горизонтальное изображение, растянутое по вертикали, чтобы заполнить квадратную рамку.
ContentScale.Inside – масштабирование источника с сохранением соотношения сторон в пределах целевых границ. Если исходное изображение меньше целевого или равно ему по обоим измерениям, то оно ведет себя так же, как и None. Контент всегда будет находиться в пределах границ. Если контент меньше границ, масштабирование не применяется. Исходное изображение больше границ: Вертикальное изображение, которое изначально было больше квадратных границ, уменьшено, чтобы поместиться в них, с сохранением соотношения сторон. По бокам виден желтый фон. Исходное изображение меньше границ: Вертикальное изображение, которое изначально было меньше квадратных границ, показано в исходном размере в рамке с желтым фоном. Исходное изображение больше границ: Горизонтальное изображение, которое изначально было больше квадратных границ, уменьшено, чтобы поместиться в них, с сохранением соотношения сторон. Сверху и снизу виден желтый фон. Исходное изображение меньше границ: Горизонтальное изображение, которое изначально меньше квадратной рамки, показывается в исходном размере, а вокруг него виден желтый фон.
ContentScale.None – не применять масштабирование к источнику. Если контент меньше целевых границ, он не будет масштабироваться по размеру области. Исходное изображение больше границ: Вертикальное изображение, которое изначально было больше квадратной рамки, показано в исходном размере, а его части выходят за верхнюю и нижнюю границы рамки. Исходное изображение меньше границ: Вертикальное изображение, которое изначально было меньше квадратных границ, показано в исходном размере в рамке с желтым фоном. Исходное изображение больше границ: Горизонтальное изображение, которое изначально было больше квадратной рамки, показано в исходном размере, а его части выходят за левую и правую границы рамки. Исходное изображение меньше границ: Горизонтальное изображение, которое изначально было меньше квадратных границ, отображается в исходном размере в рамке с желтым фоном вокруг него.

Как обрезать composable-функцию Image по форме

Чтобы изображение соответствовало форме, используйте встроенный модификатор clip. Чтобы обрезать изображение в форме круга, используйте 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.

Чтобы закруглить углы, используйте Modifier.clip(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 и предоставив Path для формы, которую нужно обрезать:

class SquashedOval : Shape {
    override fun createOutline(
        size: Size,
        layoutDirection: LayoutDirection,
        density: Density
    ): Outline {
        val path = Path().apply {
            // We create an Oval that starts at ¼ of the width, and ends at ¾ of the width of the container.
            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. Обрезка изображения с помощью пользовательского контура.

Добавьте границу к composable-функции Image

Часто 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. Обрезанное изображение с рамкой.

Чтобы создать градиентную рамку, можно использовать API Brush и нарисовать вокруг изображения радужную градиентную рамку:

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. Радужная градиентная рамка.

Как задать соотношение сторон

Чтобы преобразовать изображение в пользовательское соотношение сторон, используйте Modifier.aspectRatio(16f/9f), чтобы указать пользовательское соотношение для изображения (или любого составного объекта).

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

Квадратное изображение собаки, преобразованное в соотношение сторон 16:9, что делает его шире и короче.
Рисунок 6. Используется Modifier.aspectRatio(16f/9f) на устройстве Image.

Цветовой фильтр: преобразует цвета пикселей изображения.

У composable-функции Image есть параметр colorFilter, который может менять вывод отдельных пикселей изображения.

Тонировать изображения

При использовании ColorFilter.tint(color, blendMode) к composable-функции Image применяется режим смешивания с заданным цветом. ColorFilter.tint(color, blendMode) использует BlendMode.SrcIn для тонирования контента, то есть указанный цвет показывается там, где на экране отображается изображение. Это полезно для значков и векторов, которые должны быть оформлены по-разному.

Image(
    painter = painterResource(id = R.drawable.baseline_directions_bus_24),
    contentDescription = stringResource(id = R.string.bus_content_description),
    colorFilter = ColorFilter.tint(Color.Yellow)
)

Изображение автобуса с желтым оттенком.
Рисунок 7. ColorFilter.tint, примененный с помощью BlendMode.SrcIn.

Другие 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)
)

Собака с зеленым оттенком, примененным с помощью BlendMode.Darken, в результате чего получаются более темные оттенки зеленого.
Рисунок 8. Color.Green tint с BlendMode.Darken.

Дополнительную информацию о доступных режимах смешивания можно найти в справочной документации по BlendMode.

Как применить фильтр Image с матрицей цветов

Преобразуйте изображение с помощью матрицы цветов ColorFilter. Например, чтобы применить к изображениям черно-белый фильтр, можно использовать ColorMatrix и установить насыщенность на 0f.

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

Собака с черно-белым фильтром, убирающим всю насыщенность цвета.
Рисунок 9. Цветовая матрица с насыщенностью 0 (черно-белое изображение).

Как настроить контрастность или яркость composable-функции Image

Чтобы изменить контрастность и яркость изображения, используйте ColorMatrix:

val contrast = 2f // 0f..10f (1 should be default)
val brightness = -180f // -255f..255f (0 should be default)
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".

Инвертировать цвета composable-функции Image

Чтобы инвертировать цвета изображения, задайте для 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. Инвертированные цвета на изображении.

Как размыть composable-функцию Image

Чтобы размыть изображение, используйте 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, примененный к изображению.

При размытии Images рекомендуется использовать BlurredEdgeTreatment(Shape), а не BlurredEdgeTreatment.Unbounded, поскольку последний используется для размытия произвольных изображений, которые должны отображаться за пределами исходного контента. Изображения, скорее всего, не будут отображаться за пределами контента, тогда как для размытия закругленного прямоугольника может потребоваться такое различие.

Например, если для свойства BlurredEdgeTreatment задать значение 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. BlurEdgeTreatment.Unbounded.