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

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 टाइल को वैसे ही सेट किया जा सकता है जैसा कि हमने ऊपर TileMode सेक्शन में देखा है. अगर आप किसी DrawScope में हैं, तो क्षेत्र का साइज़ पाने के लिए, उसकी size प्रॉपर्टी का इस्तेमाल किया जा सकता है.

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

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

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)
)

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

रेडियल ग्रेडिएंट जैसे किसी भी अन्य ग्रेडिएंट के ब्रश का साइज़ भी बदला जा सकता है. अगर साइज़ और सेंटर की जानकारी नहीं दी जाती है, तो ग्रैडिएंट 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 के आखिरी बार बनने के साइज़ से अलग होगा या शेडर बनाने के लिए इस्तेमाल किया गया स्टेट ऑब्जेक्ट बदल गया होगा.

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

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)
                }
            }
        }
)

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

ImageBitmap को Brush के तौर पर इस्तेमाल करने के लिए, इमेज को 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))

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

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

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

बेहतर उदाहरण: कस्टम ब्रश

AGSL RuntimeShader ब्रश

AGSL, GLSL शेडर की सुविधाओं का सबसेट उपलब्ध कराता है. शेडर को एजीएसएल में लिखा जा सकता है और 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 करता है. इससे ग्रेडिएंट इफ़ेक्ट बनता है.

इसके बाद, शेडर ब्रश बनाएं और 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 शेडर

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

अन्य संसाधन

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