ব্রাশ: গ্রেডিয়েন্ট ও শেডার

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-কে ভগ্নাংশ হিসেবে উল্লেখ করতে হবে, ০ থেকে ১-এর মধ্যে। ১-এর থেকে বেশি ভ্যালু থাকলে, সেইসব রঙ গ্রেডিয়েন্টের অংশ হিসেবে রেন্ডার করা হবে না ।

আপনি বিভিন্ন পরিমাণে কালার স্টপ কনফিগার করতে পারবেন, যেমন একটি রঙের কম বা বেশি পরিমাণ:

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 জোড়ায় যেভাবে সংজ্ঞায়িত করা হয়েছে, সেই অনুযায়ী প্রদত্ত অফসেটে রঙগুলি ডিসপার্স করা হয়েছে, লাল ও নীলের তুলনায় হলুদ কম।

বিভিন্ন কালার স্টপ সহ কনফিগার করা ব্রাশ
ছবি ২: আলাদা আলাদা কালার স্টপ দিয়ে কনফিগার করা ব্রাশ

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 মিরর
TileMode.Clamp: এজ চূড়ান্ত রঙের সাথে ক্ল্যাম্প করা হয়েছে। তারপর, এটি বাকি অঞ্চলের জন্য সবচেয়ে কাছাকাছি রঙটি পেইন্ট করবে। টাইল মোড ক্ল্যাম্প
TileMode.Decal: শুধুমাত্র বাউন্ডের সাইজ পর্যন্ত রেন্ডার করো। TileMode.Decal মূল সীমার বাইরে কন্টেন্টের স্যাম্পেল নেওয়ার জন্য স্বচ্ছ কালো রঙ ব্যবহার করে, যেখানে TileMode.Clamp প্রান্তের রঙ স্যাম্পেল করে। টাইল মোড ডেকাল

TileMode অন্যান্য দিকনির্দেশক গ্রেডিয়েন্টের জন্য একই রকমভাবে কাজ করে, পার্থক্য হল যে দিকটিতে পুনরাবৃত্তি ঘটে।

ব্রাশের সাইজ পরিবর্তন করা

আপনার ব্রাশ যে অংশে আঁকা হবে সেটির সাইজ জানা থাকলে, আপনি TileMode বিভাগে উপরে যেভাবে দেখেছি সেইভাবে endX টাইল সেট করতে পারেন। আপনি যদি 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-এর সাইজ যদি শেষবার Brush তৈরি করার সময়কার সাইজের থেকে আলাদা হয় অথবা শেডার তৈরি করার সময় ব্যবহৃত স্টেট অবজেক্টে পরিবর্তন করা হলে, ডিফল্ট হিসেবে Brush ইন্টার্নালি নিজের Shader রিঅ্যালোকেট করবে।

নিচের কোডটি তিনবার আলাদা আলাদা সাইজের শেডার তৈরি করে, কারণ ড্রয়িং এরিয়ার সাইজ পরিবর্তন হয়:

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

ব্রাশটি কয়েক ধরনের ড্রয়িংয়ের ক্ষেত্রে প্রয়োগ করা হয়: ব্যাকগ্রাউন্ড, টেক্সট ও ক্যানভাস। এর ফলে নিম্নলিখিত আউটপুট পাওয়া যায়:

ImageShader ব্রাশ বিভিন্ন উপায়ে ব্যবহার করা হয়েছে
ছবি ৬: ব্যাকগ্রাউন্ড আঁকতে, টেক্সট আঁকতে ও বৃত্ত আঁকতে ImageShader ব্রাশ ব্যবহার করা

লক্ষ্য করুন যে টেক্সট এখন ImageBitmap ব্যবহার করেও রেন্ডার করা হচ্ছে যাতে টেক্সটের পিক্সেলগুলিকে পেইন্ট করা যায়।

উন্নত উদাহরণ: কাস্টম ব্রাশ

AGSL RuntimeShader ব্রাশ

AGSL, GLSL শেডার সুবিধার একটি সাবসেট অফার করে। শেডারকে AGSL-এ লেখা যায় এবং Compose-এ ব্রাশের সাথে ব্যবহার করা যায়।

Shader ব্রাশ তৈরি করতে, প্রথমে AGSL Shader স্ট্রিং হিসেবে Shader-কে সংজ্ঞায়িত করুন:

@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 Shader চলছে
ছবি ৭: Compose-এ কাস্টম AGSL শেডার চলছে

এটি মনে রাখা গুরুত্বপূর্ণ যে আপনি শেডার দিয়ে শুধু গ্রেডিয়েন্ট তৈরি করার চেয়ে অনেক বেশি কিছু করতে পারেন, কারণ এটি সম্পূর্ণভাবে গণিত-ভিত্তিক গণনা। AGSL সম্পর্কে আরও জানতে, AGSL ডকুমেন্টেশন দেখুন।

অতিরিক্ত রিসোর্স

কম্পোজ উইন্ডোতে ব্রাশ ব্যবহার করার আরও উদাহরণ দেখতে, নিম্নলিখিত রিসোর্স চেক করে দেখুন: