ब्रश: ग्रेडिएंट और शेडर

Compose में मौजूद Brush, यह बताता है कि स्क्रीन पर कोई चीज़ कैसे बनाई जाती है: यह ड्राइंग एरिया (जैसे, सर्कल, स्क्वेयर, पाथ) में बनाए गए रंग तय करता है. ड्रॉइंग के लिए, कुछ बिल्ट-इन ब्रश उपलब्ध हैं. जैसे, LinearGradient, RadialGradient या सामान्य SolidColor ब्रश.

ब्रश का इस्तेमाल Modifier.background(), TextStyle या DrawScope ड्रॉ कॉल के साथ किया जा सकता है. इससे, ड्रॉ किए जा रहे कॉन्टेंट पर पेंटिंग स्टाइल लागू की जा सकती है.

उदाहरण के लिए, हॉरिज़ॉन्टल ग्रेडिएंट ब्रश को DrawScope में सर्कल बनाने के लिए इस्तेमाल किया जा सकता है:

val brush = Brush.horizontalGradient(listOf(Color.Red, Color.Blue))
Canvas(
    modifier = Modifier.size(200.dp),
    onDraw = {
        drawCircle(brush)
    }
)
हॉरिज़ॉन्टल ग्रेडिएंट से बनाया गया सर्कल
पहली इमेज: हॉरिज़ॉन्टल ग्रेडिएंट की मदद से बनाया गया सर्कल

ग्रेडिएंट ब्रश

इसमें कई बिल्ट-इन ग्रेडिएंट ब्रश होते हैं. इनका इस्तेमाल अलग-अलग ग्रेडिएंट इफ़ेक्ट पाने के लिए किया जा सकता है. इन ब्रशिंग की मदद से, उन रंगों की सूची तय की जा सकती है जिनसे आपको ग्रेडिएंट बनाना है.

उपलब्ध ग्रेडिएंट ब्रश और उनके आउटपुट की सूची:

ग्रेडिएंट ब्रश का टाइप आउटपुट
Brush.horizontalGradient(colorList) हॉरिज़ॉन्टल ग्रेडिएंट
Brush.linearGradient(colorList) लीनियर ग्रेडिएंट
Brush.verticalGradient(colorList) वर्टिकल ग्रेडिएंट
Brush.sweepGradient(colorList)
ध्यान दें: रंगों के बीच आसानी से ट्रांज़िशन पाने के लिए - आखिरी रंग को शुरुआती रंग पर सेट करें.
स्वीप ग्रेडिएंट
Brush.radialGradient(colorList) रेडियल ग्रेडिएंट

colorStops की मदद से, रंगों के डिस्ट्रिब्यूशन में बदलाव करना

ग्रेडिएंट में रंगों के दिखने के तरीके को पसंद के मुताबिक बनाने के लिए, हर रंग की colorStops वैल्यू में बदलाव किया जा सकता है. colorStops को 0 और 1 के बीच के किसी भिन्न के तौर पर तय किया जाना चाहिए. वैल्यू 1 से ज़्यादा होने पर, वे रंग ग्रेडिएंट के हिस्से के तौर पर रेंडर नहीं होंगे.

अलग-अलग रंगों के लिए, कलर स्टॉप को अलग-अलग वैल्यू पर कॉन्फ़िगर किया जा सकता है. जैसे, किसी रंग की कम या ज़्यादा वैल्यू:

val colorStops = arrayOf(
    0.0f to Color.Yellow,
    0.2f to Color.Red,
    1f to Color.Blue
)
Box(
    modifier = Modifier
        .requiredSize(200.dp)
        .background(Brush.horizontalGradient(colorStops = colorStops))
)

रंगों को दिए गए ऑफ़सेट पर फैलाया जाता है. यह ऑफ़सेट, colorStop pair में तय किया जाता है. यह लाल और नीले रंग की तुलना में कम पीला होता है.

अलग-अलग रंग के स्टॉप के साथ कॉन्फ़िगर किया गया ब्रश
दूसरी इमेज: अलग-अलग कलर स्टॉप के साथ कॉन्फ़िगर किया गया ब्रश

TileMode की मदद से किसी पैटर्न को दोहराना

हर ग्रेडिएंट ब्रश में, TileMode सेट करने का विकल्प होता है. अगर आपने ग्रेडिएंट के लिए शुरू और खत्म होने की तारीख सेट नहीं की है, तो आपको TileMode नहीं दिखेगा. ऐसा इसलिए, क्योंकि यह डिफ़ॉल्ट रूप से पूरे एरिया को भर देगा. अगर एरिया का साइज़, ब्रश के साइज़ से बड़ा है, तो TileMode सिर्फ़ ग्रेडिएंट को टाइल करेगा.

नीचे दिए गए कोड में, ग्रेडिएंट पैटर्न को चार बार दोहराया जाएगा. ऐसा इसलिए होगा, क्योंकि endX को 50.dp पर सेट किया गया है और साइज़ को 200.dp पर सेट किया गया है:

val listColors = listOf(Color.Yellow, Color.Red, Color.Blue)
val tileSize = with(LocalDensity.current) {
    50.dp.toPx()
}
Box(
    modifier = Modifier
        .requiredSize(200.dp)
        .background(
            Brush.horizontalGradient(
                listColors,
                endX = tileSize,
                tileMode = TileMode.Repeated
            )
        )
)

यहां दी गई टेबल में बताया गया है कि ऊपर दिए गए HorizontalGradient उदाहरण के लिए, अलग-अलग टाइल मोड क्या करते हैं:

TileMode आउटपुट
TileMode.Repeated: इसमें, किनारे का रंग आखिरी रंग से पहले रंग तक दोहराया जाता है. TileMode.repeated
TileMode.Mirror: किनारे पर, आखिरी रंग से पहले रंग तक को मिरर किया जाता है. TileMode Mirror
TileMode.Clamp: किनारों को फ़ाइनल रंग के हिसाब से सेट किया जाता है. इसके बाद, यह बाकी के हिस्से में सबसे मिलता-जुलता रंग भर देगा. टाइल मोड क्लैंप
TileMode.Decal: सिर्फ़ सीमाओं के साइज़ तक रेंडर करें. TileMode.Decal, ओरिजनल इमेज के बाहर के कॉन्टेंट को सैंपल करने के लिए, पारदर्शी काले रंग का इस्तेमाल करता है. वहीं, TileMode.Clamp, किनारे के रंग को सैंपल करता है. टाइल्ड मोड डेकल

TileMode अन्य दिशात्मक ग्रेडिएंट के लिए भी इसी तरह काम करता है. हालांकि, इसमें अंतर यह होता है कि दोहराव किस दिशा में होता है.

ब्रश का साइज़ बदलना

अगर आपको उस जगह का साइज़ पता है जहां आपका ब्रश चलेगा, तो endX सेक्शन में बताए गए तरीके से, टाइल endX सेट की जा सकती है.TileMode अगर आप DrawScope में हैं, तो इलाके का साइज़ पाने के लिए, इसकी size प्रॉपर्टी का इस्तेमाल किया जा सकता है.

अगर आपको अपने ड्रॉइंग एरिया का साइज़ नहीं पता है (उदाहरण के लिए, अगर Brush को टेक्स्ट असाइन किया गया है), तो Shader को बड़ा किया जा सकता है. साथ ही, createShader फ़ंक्शन में ड्रॉइंग एरिया के साइज़ का इस्तेमाल किया जा सकता है.

इस उदाहरण में, पैटर्न को चार बार दोहराने के लिए, साइज़ को 4 से भाग दें:

val listColors = listOf(Color.Yellow, Color.Red, Color.Blue)
val customBrush = remember {
    object : ShaderBrush() {
        override fun createShader(size: Size): Shader {
            return LinearGradientShader(
                colors = listColors,
                from = Offset.Zero,
                to = Offset(size.width / 4f, 0f),
                tileMode = TileMode.Mirror
            )
        }
    }
}
Box(
    modifier = Modifier
        .requiredSize(200.dp)
        .background(customBrush)
)

शेयर किए गए साइज़ को 4 से भाग दिया गया है
तीसरी इमेज: शेडर के साइज़ को 4 से भाग दिया गया है

रेडियल ग्रेडिएंट जैसे किसी अन्य ग्रेडिएंट के ब्रश का साइज़ भी बदला जा सकता है. अगर आपने साइज़ और सेंटर की जानकारी नहीं दी है, तो ग्रैडिएंट, DrawScope के पूरे बाउंड्री बॉक्स पर दिखेगा. साथ ही, रेडियल ग्रैडिएंट का सेंटर, डिफ़ॉल्ट रूप से DrawScope के बाउंड्री बॉक्स के सेंटर पर सेट हो जाएगा. इससे रेडियल ग्रेडिएंट का सेंटर, छोटे डाइमेंशन (चौड़ाई या ऊंचाई) के सेंटर के तौर पर दिखता है:

Box(
    modifier = Modifier
        .fillMaxSize()
        .background(
            Brush.radialGradient(
                listOf(Color(0xFF2be4dc), Color(0xFF243484))
            )
        )
)

साइज़ में बदलाव किए बिना सेट किया गया रेडियल ग्रेडिएंट
चौथी इमेज: साइज़ में बदलाव किए बिना सेट किया गया रेडियल ग्रेडिएंट

रेडियल ग्रेडिएंट को बदलकर, रेडियस के साइज़ को ज़्यादा से ज़्यादा डाइमेंशन पर सेट करने पर, आपको दिखेगा कि इससे बेहतर रेडियल ग्रेडिएंट इफ़ेक्ट मिलता है:

val largeRadialGradient = object : ShaderBrush() {
    override fun createShader(size: Size): Shader {
        val biggerDimension = maxOf(size.height, size.width)
        return RadialGradientShader(
            colors = listOf(Color(0xFF2be4dc), Color(0xFF243484)),
            center = size.center,
            radius = biggerDimension / 2f,
            colorStops = listOf(0f, 0.95f)
        )
    }
}

Box(
    modifier = Modifier
        .fillMaxSize()
        .background(largeRadialGradient)
)

रेडियल ग्रेडिएंट का बड़ा रेडियस, जो क्षेत्र के साइज़ पर आधारित होता है
पांचवीं इमेज: रेडियल ग्रेडिएंट पर बड़ा रेडियस, जगह के साइज़ के आधार पर

यह ध्यान देने लायक बात है कि शेडर बनाने के लिए पास किया गया असल साइज़, उस जगह से तय होता है जहां इसे शुरू किया गया है. डिफ़ॉल्ट रूप से, Brush अपने Shader को अंदरूनी तौर पर फिर से असाइन करेगा. ऐसा तब होगा, जब Brush का साइज़ पिछली बार बनाए गए Brush के साइज़ से अलग होगा या जब शेडर बनाने में इस्तेमाल किया गया कोई स्टेट ऑब्जेक्ट बदल गया होगा.

नीचे दिए गए कोड में, शेडर को तीन अलग-अलग साइज़ में बनाया गया है. ऐसा इसलिए किया गया है, क्योंकि ड्राइंग एरिया का साइज़ बदलता रहता है:

val colorStops = arrayOf(
    0.0f to Color.Yellow,
    0.2f to Color.Red,
    1f to Color.Blue
)
val brush = Brush.horizontalGradient(colorStops = colorStops)
Box(
    modifier = Modifier
        .requiredSize(200.dp)
        .drawBehind {
            drawRect(brush = brush) // will allocate a shader to occupy the 200 x 200 dp drawing area
            inset(10f) {
      /* Will allocate a shader to occupy the 180 x 180 dp drawing area as the
       inset scope reduces the drawing  area by 10 pixels on the left, top, right,
      bottom sides */
                drawRect(brush = brush)
                inset(5f) {
        /* will allocate a shader to occupy the 170 x 170 dp drawing area as the
         inset scope reduces the  drawing area by 5 pixels on the left, top,
         right, bottom sides */
                    drawRect(brush = brush)
                }
            }
        }
)

ब्रश के तौर पर इमेज का इस्तेमाल करना

Brush के तौर पर ImageBitmap का इस्तेमाल करने के लिए, इमेज को ImageBitmap के तौर पर लोड करें. इसके बाद, ImageShader ब्रश बनाएं:

val imageBrush =
    ShaderBrush(ImageShader(ImageBitmap.imageResource(id = R.drawable.dog)))

// Use ImageShader Brush with background
Box(
    modifier = Modifier
        .requiredSize(200.dp)
        .background(imageBrush)
)

// Use ImageShader Brush with TextStyle
Text(
    text = "Hello Android!",
    style = TextStyle(
        brush = imageBrush,
        fontWeight = FontWeight.ExtraBold,
        fontSize = 36.sp
    )
)

// Use ImageShader Brush with DrawScope#drawCircle()
Canvas(onDraw = {
    drawCircle(imageBrush)
}, modifier = Modifier.size(200.dp))

ब्रश को अलग-अलग तरह की ड्रॉइंग पर लागू किया जाता है: बैकग्राउंड, टेक्स्ट, और कैनवस. इससे यह आउटपुट मिलता है:

इमेजशेडर ब्रश का अलग-अलग तरीकों से इस्तेमाल
इमेज 6: बैकग्राउंड बनाने, टेक्स्ट लिखने, और सर्कल बनाने के लिए, ImageShader ब्रश का इस्तेमाल करना

ध्यान दें कि अब टेक्स्ट को रेंडर करने के लिए भी ImageBitmap का इस्तेमाल किया जाता है, ताकि टेक्स्ट के पिक्सल को पेंट किया जा सके.

ऐडवांस उदाहरण: कस्टम ब्रश

AGSL RuntimeShader ब्रश

AGSL, GLSL शेडर की सुविधाओं का एक सबसेट उपलब्ध कराता है. शेडर को AGSL में लिखा जा सकता है और Compose में ब्रश के साथ इस्तेमाल किया जा सकता है.

शेडर ब्रश बनाने के लिए, सबसे पहले शेडर को AGSL शेडर स्ट्रिंग के तौर पर तय करें:

@Language("AGSL")
val CUSTOM_SHADER = """
    uniform float2 resolution;
    layout(color) uniform half4 color;
    layout(color) uniform half4 color2;

    half4 main(in float2 fragCoord) {
        float2 uv = fragCoord/resolution.xy;

        float mixValue = distance(uv, vec2(0, 1));
        return mix(color, color2, mixValue);
    }
""".trimIndent()

ऊपर दिए गए शेडर में, इनपुट के तौर पर दो रंग लिए जाते हैं. इसके बाद, यह ड्राइंग एरिया के नीचे बाईं ओर (vec2(0, 1)) से दूरी का हिसाब लगाता है. इसके बाद, दूरी के आधार पर दोनों रंगों के बीच mix करता है. इससे ग्रेडिएंट इफ़ेक्ट मिलता है.

इसके बाद, Shader Brush बनाएं. साथ ही, resolution के लिए यूनिफ़ॉर्म सेट करें. यह यूनिफ़ॉर्म, ड्रॉइंग एरिया का साइज़ होता है. इसके अलावा, color और color2 के लिए यूनिफ़ॉर्म सेट करें. इनका इस्तेमाल, कस्टम ग्रेडिएंट के इनपुट के तौर पर किया जाता है:

val Coral = Color(0xFFF3A397)
val LightYellow = Color(0xFFF8EE94)

@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@Composable
@Preview
fun ShaderBrushExample() {
    Box(
        modifier = Modifier
            .drawWithCache {
                val shader = RuntimeShader(CUSTOM_SHADER)
                val shaderBrush = ShaderBrush(shader)
                shader.setFloatUniform("resolution", size.width, size.height)
                onDrawBehind {
                    shader.setColorUniform(
                        "color",
                        android.graphics.Color.valueOf(
                            LightYellow.red, LightYellow.green,
                            LightYellow
                                .blue,
                            LightYellow.alpha
                        )
                    )
                    shader.setColorUniform(
                        "color2",
                        android.graphics.Color.valueOf(
                            Coral.red,
                            Coral.green,
                            Coral.blue,
                            Coral.alpha
                        )
                    )
                    drawRect(shaderBrush)
                }
            }
            .fillMaxWidth()
            .height(200.dp)
    )
}

इसे चलाने पर, आपको स्क्रीन पर यह दिखेगा:

Compose में कस्टम AGSL शेडर चल रहा है
सातवीं इमेज: Compose में कस्टम AGSL शेडर चल रहा है

ध्यान दें कि शेडर की मदद से, ग्रेडिएंट के अलावा और भी बहुत कुछ किया जा सकता है. ऐसा इसलिए, क्योंकि ये सभी गणित पर आधारित कैलकुलेशन हैं. AGSL के बारे में ज़्यादा जानने के लिए, AGSL का दस्तावेज़ देखें.

अन्य संसाधन

Compose में Brush का इस्तेमाल करने के ज़्यादा उदाहरण देखने के लिए, यहां दिए गए संसाधन देखें: