Как создать градиент изменения насыщенности и яркости HSL или редактор кистей с помощью Jetpack Compose?

Я создаю палитру цветов с помощью Jetpack Compose и пытаюсь реализовать ромб выбора насыщенности и яркости (прямоугольник, повернутый на 45 градусов), как это видно на изображениях, но не смог найти хороший метод для отображения цветов, как они должны выглядеть

Я могу получить позиции в ромбе и рисовать круги с изображением слева, так как это круги, они не выглядят хорошо. Также пытался рисовать небольшие пути, но это значительно замедляет работу приложения.

      /**
 * Get each point and saturation and lightness of the point. This function is for
 * creating points to draw like gradient effect for HSL color
 */
fun getPointsInRhombus(length: Float): MutableList<ColorPoint> {

    val step = length.toInt() / 50
    val colorPoints = mutableListOf<ColorPoint>()

    for (yPos in 0..length.toInt() step step) {
        val range = getIntRangeInLength(length = length, yPos.toFloat())
        for (xPos in range step step) {

            val path = rhombusPath(Size(10f, 10f))
            path.translate(Offset(xPos.toFloat() - 5, yPos.toFloat()))
            val saturation = xPos / length
            val lightness = 1 - (yPos / length)
            val colorPoint =
                ColorPoint(Offset(xPos.toFloat(), yPos.toFloat()), saturation, lightness, path)
            colorPoints.add(colorPoint)
        }
    }
    return colorPoints
}



 colorPoints.forEach { colorPoint: ColorPoint ->
        drawCircle(
            Color.hsl(hue, colorPoint.saturation, colorPoint.lightness),
            center = colorPoint.point,
            radius = 10f
        )
    }

Также пробовал создавать формы для светлоты и насыщенности и пытался смешать их, но это не работает, как видно на изображении справа.

       with(drawContext.canvas.nativeCanvas) {
        val checkPoint = saveLayer(null, null)

        // Destination lightness top to bottom
        drawPath(
            rhombusPath, Brush.verticalGradient(
                colors = listOf(
                    Color.hsl(
                        hue,
                        saturation = .5f,
                        lightness = 1f,
                        alpha = 1f
                    ),
                    Color.hsl(
                        hue,
                        saturation = .5f,
                        lightness = 0f,
                        alpha = 1f
                    )
                )
            )
        )

        // Source saturation left to right
        drawPath(
            rhombusPath,
            Brush.horizontalGradient(
                colors = listOf(
                    Color.hsl(
                        hue,
                        saturation = 0f,
                        lightness = .5f,
                        alpha = 1f
                    ),
                    Color.hsl(
                        hue,
                        saturation = 1f,
                        lightness = .5f,
                        alpha = 1f
                    )
                )
            ),
            blendMode = BlendMode.SrcIn
        )
        
        restoreToCount(checkPoint)
    }

Мне нужно, чтобы цвета с первого изображения были применены к ромбовидному изображению справа без рисования кругов или путей. Я думаю, что это можно решить с помощью одного градиента или нескольких градиентов или их смешивания, но не могу понять, как это сделать.

Проверил этот вопрос в С# для справки, но не смог понять, как применить его к Compose. Brush

1 ответ

Для градиента HSL BlendMode.Multiplyне работает, он работает для получения градиента HSV . Решение состоит в том, чтобы использовать Blendmode.Overlay с градиентами. Также угол по умолчанию для Brush.linergradient составляет 45 градусов по часовой стрелке, необходимо установить его на 0 градусов , чтобы насыщенность изменялась от начала до конца Composable.

      val lightnessGradient = remember {
    Brush.verticalGradient(
        colors = listOf(
            Color.hsl(hue = hue, saturation = .5f, lightness = 1f),
            Color.hsl(hue = hue, saturation = .5f, lightness = 0f)
        )
    )

}

val saturationHSLGradient = remember {
    val gradientOffset = GradientOffset(GradientAngle.CW0)

    Brush.linearGradient(
        colors = listOf(
            Color.hsl(hue, 0f, .5f),
            Color.hsl(hue, 1f, .5f)
        ),
        start = gradientOffset.start,
        end = gradientOffset.end
    )
}

Затем нарисуйте оба этих градиента в слое с режимом Blend(PorterDuff) Overlay.

      Canvas(modifier = canvasModifier) {


    drawIntoLayer {
        drawPath(
            path = rhombusPath,
            lightnessGradient,
        )
        drawPath(
            path = rhombusPath,
            saturationHSLGradient,
            blendMode = BlendMode.Overlay
        )
    }
}

Функция рисования для смешивания

      fun DrawScope.drawIntoLayer(
    content: DrawScope.() -> Unit
) {
    with(drawContext.canvas.nativeCanvas) {
        val checkPoint = saveLayer(null, null)
        content()
        restoreToCount(checkPoint)
    }
}

Результат для градиентов HSV и HSL: маленькие прямоугольники рисуются не градиентом, а маленькие прямоугольники в точках, чтобы убедиться, что градиент HSL соответствует реальным цветам в позициях.

Репозиторий Github для полной реализации доступен здесь .