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

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 Mirror
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 ইন্টার্নালি এর 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-এ লেখা যেতে পারে এবং কম্পোজে ব্রাশের সাথে ব্যবহার করা যেতে পারে।

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

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

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

Compose-এ ব্রাশ ব্যবহার করার আরও উদাহরণ দেখতে, নিম্নলিখিত রিসোর্স চেক করুন: