অ্যাপ ফাউন্ডেশন

Jetpack Compose ও Android-এ লেটেস্ট গ্রাফিক্স ফিচারের মাধ্যমে আপনার অ্যাপকে আরও উন্নত করুন

স্পিকার

  • Rebecca Franks

    ডেভেলপার রিলেশনস ইঞ্জিনিয়ার

  • Aayush Chaudhary

    সফ্টওয়্যার ইঞ্জিনিয়ার

  • রাহুল রায়

    ডেভেলপার রিলেশনস ইঞ্জিনিয়ার

সম্পর্কে

টক সম্পর্কে

আপনার Android UI-কে "এটি কাজ করে" থেকে "এটি একটি শিল্পকর্ম" হিসেবে দেখতে চান?

Jetpack Compose-এ লেটেস্ট গ্রাফিক্সের ক্ষমতা সম্পর্কে জানতে আমাদের সাথে যোগ দিন। আমরা আপনাকে দেখাব কীভাবে নতুন ভিজ্যুয়াল এফেক্ট তৈরি করতে হয়, আনন্দদায়ক মেশ গ্রেডিয়েন্ট থেকে শুরু করে প্রগ্রেসিভ ব্লার এবং Android-এ শক্তিশালী নতুন গ্রাফিক্স ফিচার ব্যবহার করা। ব্যাটারির আয়ু বা ফ্রেম রেট না কমিয়ে কীভাবে আপনার গ্রাফিক্স ফিচারের পারফর্ম্যান্স উন্নত করবেন তাও আমরা আপনাকে দেখাব।

সারাংশ

  • Compose 1.12-এ ভার্টিসেসের 2D গ্রিড জুড়ে রিচ মাল্টিকালার ব্লেন্ডের জন্য মেশ গ্রেডিয়েন্ট নিয়ে আসা হয়েছে। Android 14 ও তার পরবর্তী ভার্সনে, আপনি কাস্টম জ্যামিতি-চালিত ভিজ্যুয়াল এফেক্ট তৈরি করতে নেটিভ drawMesh API ব্যবহার করতে পারবেন।
  • Compose-এ অনেক বেশি কালার গ্যামাট কাজ করে, যাতে কাজ করে এমন হার্ডওয়্যারে আরও মসৃণ গ্রেডিয়েন্ট ব্লেন্ডিং প্রদান করা যায়। রিচ মিডিয়া দেখানোর সময় আপনার অ্যাক্টিভিটি বা উইন্ডোতে ওয়াইড কালার গ্যামাট মোড চালু করুন।
  • Compose 1.13 কাস্টম জিওমেট্রির জন্য নেটিভ PolygonShape এবং MorphPolygonShape API নিয়ে এসেছে। MorphPolygonShape আউটলাইন রেজোলিউশন ফেজ চলাকালীন প্রগ্রেস পড়ে, রিকম্পোজিশন ট্রিগার না করেই ফ্রেম বাই ফ্রেম শেপের আপডেট করে।
  • Compose 1.13-এ প্রোগ্রেসিভ ব্লার যোগ করা হয়েছে, অন্যদিকে API লেভেল 37.2-এ ব্যাকগ্রাউন্ড কন্টেন্ট স্যাম্পেল করার জন্য RenderNode.setBackdropRenderEffect নিয়ে আসা হয়েছে। ডেভেলপাররা ফ্রস্টেড-গ্লাস বা ম্যাগনিফাইং এফেক্ট তৈরি করতে ব্যাকড্রপ এফেক্ট একসাথে চেইন করতে পারেন।
  • ফ্রেম জুড়ে অপ্রয়োজনীয় কাজ এড়িয়ে গ্রাফিক্স পারফর্ম্যান্স অপ্টিমাইজ করুন। প্রধান থ্রেড থেকে শুধু প্রয়োজনীয় ইমেজ পিক্সেল ডিকোড করুন এবং অ্যানিমেটিং এফেক্ট graphicsLayer-এ র‍্যাপ করুন।

রিসোর্স

Gemini Notebook

বিষয়

  • jetpack-compose
  • android-graphics
  • mesh-gradients
  • শেডার
  • অস্পষ্টতা
  • কালার-গ্যামাট
  • পারফর্ম্যান্স

ট্রান্সস্ক্রিপ্ট করা

Jetpack Compose-এ গ্রাফিক্স ফিচারের ভূমিকা

  • হ্যালো, সবাইকে আমাদের সেশনে স্বাগত, Compose এবং Android-এ লেটেস্ট গ্রাফিক্স ফিচারের সাহায্যে আপনার অ্যাপকে আরও উন্নত করা।

  • আমার নাম রেবেকা ফ্র্যাঙ্কস এবং আজ আমার সাথে স্টেজে যোগ দিচ্ছেন--

  • আমি আয়ুষ চৌধুরী।

  • হ্যালো, আমি রাহুল রায়।

  • আমরা একটি নতুন ট্রেন্ড লক্ষ্য করছি যে, আপনারা সবাই Android এবং Compose-এর মাধ্যমে কী কী করা সম্ভব তার সীমা বাড়িয়ে দিচ্ছেন এবং AI ব্যবহার করে আপনাদের অবিশ্বাস্য আইডিয়াগুলিকে বাস্তবে রূপ দিচ্ছেন।

  • আমরা এমন অ্যাপ দেখতে পছন্দ করি যা প্রিমিয়াম, নেটিভ ও আনন্দদায়ক অনুভূতি দেয়।

  • আমরা জানি যে যেসব অ্যাপ ব্যবহার করে ভাল লাগে এবং ব্যবহারকারীদের থেকে ক্রমাগত দুর্দান্ত রেটিং পায়, সেগুলি সাধারণত লেটেস্ট সিস্টেম-লেভেল API পরিবর্তনের সুবিধা নেয়, শেয়ার করা এলিমেন্ট ও ব্লারিং সহ সারফেসের মধ্যে ধারাবাহিকতা বজায় রাখে, ফর্ম ফ্যাক্টর জুড়ে সুন্দরভাবে মানিয়ে নেয় এবং পারফর্ম্যান্স ভালো হয়, ব্যবহারকারীদের অনুমান করতে দেয় না এবং আপনার ডিভাইসের মাধ্যমে যা সম্ভব তার সীমা বাড়িয়ে দেয়।

  • আজকের এই আলোচনায়, আমরা আপনাকে এমন কিছু লেটেস্ট ফিচার দেখাব যা আপনাকে Android-এর সুবিধা নিতে সাহায্য করবে, যেমন মেশ গ্রেডিয়েন্ট দিয়ে ব্যবহারকারীদের আনন্দিত করা, ওয়াইড কালার গ্যামাট সাপোর্ট চালু করা, পলিগন শেপের সাথে আনন্দদায়ক ট্রানজিশন, ব্লারিং-এ নতুন কী আছে এবং সবশেষে, আপনার অ্যাপে নতুন গ্রাফিক্স ফিচার যোগ করার সময় আপনি কীভাবে পারফর্ম্যান্সের কথা ভাবতে পারেন তা আমরা কভার করব।

  • তবে আজকের আলোচনা জুড়ে, আমরা স্ট্যাকের বিভিন্ন লেভেলে কিছু নতুন API কভার করব।

  • যদিও লিগ্যাসি ভিউ UI টুলকিট এখন রক্ষণাবেক্ষণ মোডে রয়েছে, আমরা অন্তর্নিহিত Android গ্রাফিক্স হার্ডওয়্যার রেন্ডারিং ইঞ্জিনে বিনিয়োগ চালিয়ে যাচ্ছি যা Jetpack Compose-কে শক্তিশালী করে।

  • আপনি Android-এ গ্রাফিক্সকে তিনটি ভিন্ন লেয়ারে ভাবতে পারেন: প্রথম লেয়ারে Compose API, এবং এখানেই সমস্ত হাই-লেভেল, নতুন UI গ্রাফিক্স API, যেমন মেশ গ্রেডিয়েন্ট পেইন্টার, পলিগন শেপ এবং প্রগ্রেসিভ ব্লার API থাকে।

  • এবং তারপরে আমাদের কাছে ইন্টারমিডিয়েট লেয়ার আছে, যেখানে Compose-এর DrawScope, ড্র মডিফায়ার নোড এবং AGSL আপনাকে কাস্টম এফেক্ট তৈরি করতে দেয়।

  • তবে, অবশেষে, আমাদের কাছে Android প্ল্যাটফর্ম আছে এবং Compose সবসময় নেটিভ Canvas অ্যাক্সেস করতে পারে এবং আপনার Compose মডিফায়ারের ঠিক ভিতরে ব্র্যান্ড-নিউ OS রেন্ডারিং প্রিমিটিভ ব্যবহার করতে পারে।

  • আজ আমরা আপনাকে এটি করার কিছু উদাহরণ দেখাব।

  • তাহলে, এই বিষয়ে আরও বিস্তারিত জানা যাক।

মেশ গ্রেডিয়েন্ট ও কাস্টম মেশ রেন্ডারিং

  • তাই প্রথমে, Compose 1.12-এ চালু করা নতুন API, মেশ গ্রেডিয়েন্ট দেখা যাক।

  • আগে, আমাদের কাছে লিনিয়ার গ্রেডিয়েন্ট ছিল, আগে, আমাদের কাছে লিনিয়ার, সুইপ এবং রেডিয়াল গ্রেডিয়েন্ট ছিল, যা সাধারণ ট্রানজিশনের জন্য দুর্দান্ত, তবে আপনি যদি অ্যাম্বিয়েন্ট ব্যাকগ্রাউন্ড বা ফ্লাডলাইটিং এফেক্টের মতো রিচ মাল্টিকালার ব্লেন্ড চান, তাহলে আপনার সিস্টেমের 2D গ্রিড প্রয়োজন যেখানে, পজিশন এবং রঙ স্পেস জুড়ে ব্লেন্ড হতে পারে।

  • এখন, এই ধরনের মেশ গ্রেডিয়েন্ট তৈরি করতে, প্রথমে, আপনি কতগুলি সারি এবং কলামের প্যাচ চান তা নির্দিষ্ট করুন।

    val gradientPainter = remember {
    MeshGradientPainter(rows = 3, columns = 3) {
    }
    }
  • তারপর, ট্রেলিং ল্যাম্বডার মধ্যে, setVertex কল করুন এবং প্রতিটি গ্রিড ইন্টারসেকশনের জন্য একটি রঙের সাথে 0F থেকে 1F পর্যন্ত একটি নরমালাইজড অফসেট অ্যাসাইন করুন।

    val gradientPainter = remember {
    MeshGradientPainter(rows = 3, columns = 3) {
       setVertex(0, 0, Offset(0.0f, 0.0f), indigo)
    
    }
    }
  • এবং তারপর সেই ভার্টিসেস অ্যানিমেট করতে, আটটি ভার্টিসেসের জন্য অ্যানিমেটেড অফসেট যোগ করুন।

    val gradientPainter = remember {
    MeshGradientPainter(rows = 3, columns = 3) {
       setVertex(0, 0, Offset(0.0f, 0.0f), indigo)
         // ....
       setVertex(1, 1, Offset(0.2f, 0.4f) + Offset(animatedOffset, animatedOffset), coral)
       // ....
       setVertex(3, 0, Offset(0.0f, 1.0f), sunshine)
       // ....
    }
    }
    
    Box(
       modifier = modifier.padding(32.dp)
           .safeContentPadding()
           .aspectRatio(16 / 9f)
           .fillMaxWidth()
           .paint(gradientPainter)
    )
  • আপনার পেইন্টার ডিফাইন করা হয়ে গেলে, পেইন্ট মডিফায়ারের মাধ্যমে যেকোনও কম্পোজেবল দিয়ে এটিকে কল করুন।

  • এখন, JetChat অ্যাপে, আমরা একটি মেসেজে ডবল-ট্যাপ করলে সেটি লাইক করার ফিচার যোগ করেছি।

  • এটি তৈরি করতে, আমরা ২ বাই ৩ মেশ সেট-আপ করি এবং কম্পোনেন্ট জুড়ে আমাদের কালার স্টপ রাখি।

    MeshGradientPainter(
       rows = 2,
       columns = 3,
       hasBicubicColor = true,
    ) {
       // Row 0 (top edge, y = 0.0f)
       setVertex(0, 0, Offset(0.0000f, 0.0000f), Color(0xFFFF6B6B))
       setVertex(0, 1, Offset(0.3300f, 0.0000f), Color(0xFFFFD6D6))
       setVertex(0, 2, Offset(0.6700f, 0.0000f), Color(0xFFE8C3FF))
       setVertex(0, 3, Offset(1.0000f, 0.0000f), Color(0xFFE396FF))
    
       // ... remaining vertices for Rows 0, 1, and 2
    }
  • লক্ষ্য করুন যে আমরা এখানে hasBicubicColor-এর মান true সেট করেছি।

  • ডিফল্ট হিসেবে, ত্রিভুজ জুড়ে রঙগুলি লিনিয়ারলি ইন্টারপোলেট করে, যা কখনও কখনও দৃশ্যমান প্যাচ ছেড়ে যেতে পারে।

  • এটি চালু করলে শেডারটি মসৃণ বাইকিউবিক ইন্টারপোলেশনে পাল্টে যায় যাতে রঙগুলি একে অপরের সাথে নির্বিঘ্নে মিশে যেতে পারে।

  • এখন, আপনি যদি ইনপুট বক্সের পিছনে এইরকম একটি অ্যানিমেটেড অ্যাম্বিয়েন্ট গ্লো তৈরি করতে চান তাহলে কী করবেন?

  • মেশ গ্রেডিয়েন্ট ব্যবহার করে এই ধরনের এফেক্ট তৈরি করতে আপনার দুটি জিনিস প্রয়োজন।

  • প্রথমে, ভিতরের ভার্টেক্সের জন্য একটি মসৃণ অর্গানিক মোশন এবং বাইরের প্রান্তগুলিকে নরম করার একটি উপায় যাতে আপনি কার্ডের চারপাশে একটি শক্ত আয়তক্ষেত্র না পান।

  • তাই আমরা দুটি ফেজ অ্যাঙ্গেল সহ একটি rememberInfiniteTransition সেট-আপ করে শুরু করি যা কিছুটা ভিন্ন সময়কালের জন্য চলে যাতে মোশনটি মেকানিক্যাল না হয়ে অর্গানিক মনে হয়।

    val transition = rememberInfiniteTransition(label = "userInputGlowMesh")
    
    val phase1 by transition.animateFloat(
       initialValue = 0f,
       targetValue = (2 * PI).toFloat(),
       animationSpec = infiniteRepeatable(
           animation = tween(durationMillis = 2800, easing = LinearEasing),
           repeatMode = RepeatMode.Restart,
       ),
       label = "glowPhase1",
    )
    
    val phase2 by transition.animateFloat(
       initialValue = 0f,
       targetValue = (2 * PI).toFloat(),
       animationSpec = infiniteRepeatable(
           animation = tween(durationMillis = 4200, easing = LinearEasing),
           repeatMode = RepeatMode.Restart,
       ),
       label = "glowPhase2",
    )
  • এখানে, আমরা ভিতরের ভার্টিসেস অ্যানিমেট করি এবং বাইরের রিং, ভার্টিসেসের বাইরের রিং, 0 আলফা সহ এজ পিন করি।

  • বাইরের সীমানা স্থির এবং স্বচ্ছ হওয়ার কারণে, গ্লো স্বাভাবিকভাবেই ফেদার আউট হয়ে যায়।

  • এবং, অবশেষে, আমরা সেই অ্যানিমেটেড অফসেটগুলি আমাদের MeshGradientPainter-এ ফিড করি।

    MeshGradientPainter(
       rows = 6,
       columns = 5,
       hasBicubicColor = true,
    ) {
       for (r in 0..6) {
           for (c in 0..5) {
                   val offset = if (r in 1..5 && c in 1..4) {
                   val ampX = when (r) {
                       1, 2 -> 0.048f
                       3 -> 0.042f
                       4 -> 0.028f
                       else -> 0.036f
                   }
                   val ampY = when (r) {
                       1, 2 -> 0.028f
                       3 -> 0.020f
                       4 -> 0.022f
                       else -> 0.016f
                   }
                   val d = orbit(c, r, ampX, ampY)
                   Offset(
                       x = (baseU + d.x).coerceIn(0.04f, 0.96f),
                       y = (baseV + d.y).coerceIn(0.05f, 0.95f),
                   )
               } else {
                   Offset(baseU, baseV)
               }
               setVertex(r, c, offset, gridColors[r][c])
           }
       }
    }
  • আপনি এইমাত্র যে মেশ গ্রেডিয়েন্ট দেখেছেন সেটি Android 14 ও তার পরবর্তী যেকোনও ভার্সনে drawMesh API ব্যবহার করে আঁকা হয়েছে।

  • তাই, একেবারে শুরু থেকে একটি এফেক্ট তৈরি করে এই API-এর ব্যাপারে আরও ভালোভাবে জেনে নেওয়া যাক।

  • JetChat-এর স্বাগত স্ক্রিনের জন্য, আমরা এমন একটি পাথ আঁকতে চাই যা দেখতে রিবনের মতো।

  • এখানে দুটি জিনিস লক্ষ্য করুন: স্ট্রোকের প্রস্থ ধ্রুবক নয় এবং রঙটি পথের প্রতিটি স্ট্রোক বরাবর চলে।

  • আসুন এটি ছোট ছোট অংশে ভাগ করে বুঝি যে এই এফেক্ট তৈরি করতে আমাদের আসলে কী প্রয়োজন।

  • প্রতিটি পিক্সেলের জন্য, আমাদের সিদ্ধান্ত নিতে হবে যে এটি আঁকা হবে কিনা এবং কোন রঙ ব্যবহার করা হবে।

  • সেটা বোঝার জন্য আমাদের দুটি দূরত্ব জানতে হবে।

  • প্রথম মানটি হল স্ট্রোকের কেন্দ্র থেকে একটি পিক্সেল কত দূরে আছে।

  • দ্বিতীয় দূরত্ব হল, শুরু থেকে শেষ পর্যন্ত, পাথ বরাবর একটি পিক্সেল কত দূরে আছে।

  • এটি আমাদের বলে যে গ্রেডিয়েন্ট থেকে কোন রঙটি বেছে নিতে হবে এবং প্রথম ভ্যালুর কিছুটা মিশিয়ে দিলে প্রস্থ জুড়ে রঙের কার্ভ তৈরি হয়।

  • তাহলে কি আমরা শুধু পাথ এবং পেইন্ট ব্যবহার করে একটি পাথ বরাবর গ্রেডিয়েন্ট আঁকতে পারি?

  • হ্যাঁ, আমরা করতে পারি।

  • এর জন্য একটি পাথকে ছোট সরলরেখার তালিকায় ফ্ল্যাট করতে হবে, যা পাথ বরাবর দূরত্বও প্রদান করে।

    val lines = remember {
            path.asAndroidPath().flatten(0.5f)
        }
    //... inside draw scope
    lines.forEach { line ->
    //...
        drawLine(
            brush = Brush.linearGradient(...),
            start = Offset(line.start.x,
    line.start.y),
            end = Offset(line.end.x, line.end.y),
            strokeWidth = 30f,
        )
    //...
    }
  • তারপর আমরা প্রতিটি লাইনকে তার নিজস্ব গ্রেডিয়েন্ট দিয়ে আঁকি।

  • এটি কাজ করে, তবে প্রতিটি লাইনের জন্য আলাদা ড্র কল এবং একটি পেইন্ট অবজেক্ট প্রয়োজন এবং পুরো পাথের এখনও একটি প্রস্থ রয়েছে।

  • অন্য বিকল্পটি হল রানটাইম শেডার, তবে এটি শুধুমাত্র কন্টেনার বাউন্ডের মধ্যে পিক্সেলের পজিশন পায়।

  • এই রিবন আঁকার জন্য কী কী লাগবে তা এখানে বলা নেই।

  • আর, আমরা পিক্সেলের পজিশন থেকেও সহজে এটি হিসেব করতে পারি না।

  • Draw mesh এর জন্য আমাদের ভার্টেক্স শেডার দেয়।

  • তাহলে, মেশ কী?

  • একটি মেশ ত্রিভুজ দিয়ে তৈরি হয় যার প্রতিটি কোণে ডেটা থাকে এবং প্রতিটি কোণকে ভার্টেক্স বলা হয়।

  • প্রতিটি ভার্টেক্সে ডেটার একটি ছোট রেকর্ড থাকে।

  • সাধারণত, সেই রেকর্ডে একটি পজিশন থাকে, তবে এটি বাধ্যতামূলক নয়।

  • ভার্টেক্স শেডার সেই রেকর্ড পড়ে এবং সেখান থেকে পজিশন গণনা করে।

  • এখানে, একটি ত্রিভুজ যে পথটি নেয় তা এখানে রয়েছে: ভার্টেক্স শেডার প্রতিটি কোণের জন্য চলে, এটি কোণের অবস্থান গণনা করে এবং প্রতিটি পিক্সেলের জন্য আমরা যে কোনও মান পাস করতে চাই।

  • তারপর রাস্টারাইজার সেই ত্রিভুজের মধ্যে থাকা প্রতিটি পিক্সেল খুঁজে বের করে এবং প্রতিটি পিক্সেলের জন্য সেই তিনটি কোণ থেকে মান ইন্টারপোলেট করে।

  • শেষে, ফ্র্যাগমেন্ট শেডার সেইসব পিক্সেলের জন্য রান করে, সেইসব ইন্টারপোলেট করা ভ্যালু ব্যবহার করে এবং পিক্সেলের রঙ গণনা করে।

  • আসুন দেখা যাক, কীভাবে আমরা একটি সহজ উদাহরণের মাধ্যমে ভার্টেক্স ও ফ্র্যাগমেন্ট শেডার লিখতে পারি, একটি সোজা লাইন যার উপর দিয়ে একটি পালস যাচ্ছে।

    LineShaders.VERTEX
    
    uniform float2 uStart;
    uniform float2 uEnd;
    uniform float uHalfWidth;
    uniform float uTime;
    
    Varyings main(const Attributes a) {
        Varyings v;
        float2 dir = uEnd - uStart;
        float2 normal = normalize(float2(-dir.y, dir.x));
        float along = a.corner.x * length(dir);
        float pulse = (a.corner.x - uTime) / 0.18;
        float radius = uHalfWidth * (1.0 + 0.6 * exp(-pulse * pulse));
        float across = a.corner.y * (radius + 1.0);
        v.position = uStart + dir * a.corner.x + normal * across;
        v.stroke = float3(along, across, radius);
        return v;
    }
  • ভার্টেক্স শেডার ইনপুট হিসেবে অ্যাট্রিবিউট নেয় এবং আউটপুট হিসেবে ভেরিয়িং রিটার্ন করে।

  • অ্যাট্রিবিউট হল একটি কোণের ডেটা।

  • এখানে মাত্র দুটি সংখ্যা আছে: লাইন বরাবর এর অগ্রগতি এবং এটি লাইনের কোন দিকে আছে।

  • তারপর ভেরিয়িং ইন্টারপোলেট করে ফ্র্যাগমেন্ট শেডারে পাস করা হয়।

  • আমরা শুরু থেকে পিক্সেল দূরত্ব গণনা করি এবং স্ট্রোকের প্রস্থের জন্য, আমরা ব্যাসার্ধে একটি পালস পালস তৈরি করতে সময় ব্যবহার করি।

  • তারপর আমরা সেই ব্যাসার্ধকে কোণের দিক দিয়ে গুণ করি, যা আমাদের স্ট্রোক জুড়ে সাইন দূরত্ব দেয়।

  • GPU যখন সেইসব চিহ্নের মধ্যে ইন্টারপোলেট করে, তখন সেন্টার লাইন শূন্য হয়ে যায়।

  • শেষে, আমরা কোণের পজিশন গণনা করি এবং এই সব ভ্যালু স্ট্রোক ভেরিয়িংয়ে স্টোর করে ফ্র্যাগমেন্ট শেডারে পাস করি।

  • রানটাইম শেডারের মতো নয়, একটি মেশ ফ্র্যাগমেন্ট শেডার প্যারামিটারের মাধ্যমে একটি রঙ আউটপুট করে এবং সেট করা থাকলে পেইন্ট স্যাম্পেল করার জন্য একটি পজিশন রিটার্ন করে।

    LineShaders.FRAGMENT
    
    uniform float uLength;
    layout(color) uniform float4 uStartColor;
    layout(color) uniform float4 uEndColor;
    
    float2 main(const Varyings v, out float4 color) {
        float distance = abs(v.stroke.y);
        float coverage = 1.0 - smoothstep(
        v.stroke.z - 0.5, v.stroke.z + 0.5, distance);
        float side = distance / v.stroke.z;
        float4 ink = mix(uStartColor, uEndColor,
            clamp(v.stroke.x / uLength + 0.4 * side * side, 0.0, 1.0));
        color = ink * coverage;
        return v.position;
    }
  • আমাদের আড়াআড়ি কোঅর্ডিনেটের পরম মান নিলে, আমরা কেন্দ্র রেখা থেকে পিক্সেল থেকে পিক্সেলের দূরত্ব পাই।

  • কভারেজ পেতে আমরা সেই দূরত্বকে ব্যাসার্ধের সাথে তুলনা করি এবং রঙের জন্য, আমরা লাইনের পথ বরাবর ব্লেন্ড করি এবং গ্রেডিয়েন্টটিকে প্রান্তের দিকে সামান্য বাঁকিয়ে দিই।

  • সেই কভারেজকে কভারেজ দিয়ে গুণ করলে আমরা চূড়ান্ত রঙ পাব।

  • Compose-এর ক্ষেত্রে, আমরা MeshSpecification অবজেক্টের সাথে আমাদের শেডার এবং ডেটা লেআউট একসাথে যুক্ত করি।

    val spec = remember {
        MeshSpecification.make(
            arrayOf(Attribute(TYPE_FLOAT2, 0, "corner")),
            vertexStride = 8,
            arrayOf(Varying(TYPE_FLOAT3, "stroke")),
            LineShaders.VERTEX, LineShaders.FRAGMENT)
    }
  • প্রথমে, আমরা বাইট ০ থেকে শুরু করে দুটি ফ্লোট হিসেবে আমাদের কর্নার অ্যাট্রিবিউট ঘোষণা করি।

  • দুটি ফ্লোট ৮ বাইট নেয়, তাই বাফারে প্রতিটি কর্নার স্ট্রাইড হল ৮।

  • এরপর, আমরা স্ট্রোক স্ট্রোক ভেরিয়িংকে তিনটি ফ্লোট হিসেবে ঘোষণা করি এবং আমরা এইমাত্র যে ভার্টেক্স ও ফ্র্যাগমেন্ট শেডার লিখেছি তা পাস করি।

  • আমরা ভার- ভার্টেক্স বাফার তৈরি করি।

    fun lineCorners(segments: Int): ByteBuffer {
        val buffer = ByteBuffer.allocateDirect((segments + 1) * 2 * 8)
            .order(ByteOrder.nativeOrder())
        for (i in 0..segments) {
            val t = i / segments.toFloat()
            buffer.putFloat(t).putFloat(-1f) // one side
            buffer.putFloat(t).putFloat(1f)  // the other side
        }
        return buffer.rewind() as ByteBuffer
    }
  • লাইনের প্রতিটি ধাপে, আমরা প্রগ্রেস সম্পূর্ণ করি, ০ থেকে ১ পর্যন্ত প্রগ্রেস গণনা করি এবং বাফারে দুটি কোণ লিখি।

  • শেষে, আমরা স্পেক ও বাফারকে একটি মেশ অবজেক্টে একত্রিত করি।

    val mesh = Mesh(
        spec,
        mode = Mesh.TRIANGLE_STRIP,
        vertexBuffer = corners,
        vertexCount = (segments + 1) * 2,
        bounds = RectF(0f, 0f, size.width, size.height)
    )
  • Compose ক্যানভাস এখনও drawMesh এক্সপোজ করে না, তাই আমরা nativeCanvas-এ ড্রপ ডাউন করে মেশ আঁকি।

    Box(Modifier.fillMaxSize().drawWithCache {
       val mesh = Mesh(...)
       onDrawBehind {
           mesh.setFloatUniform("uTime", time.value)
           drawIntoCanvas {
              it.nativeCanvas.drawMesh(mesh,android.graphics.BlendMode.DST, paint)
           }
       }
    })
  • আপনি জিওমেট্রি ও পিক্সেল কন্ট্রোল করেন বলে, ড্র মেশ ব্যবহার করে জিওমেট্রি দ্বারা চালিত যেকোনও ভিজ্যুয়াল এফেক্ট আঁকতে পারবেন।

  • এই API সম্পর্কে আরও জানতে do- ডকুমেন্টেশন দেখুন।

ওয়াইড কালার গ্যামাট সাপোর্ট

  • Compose 1.12-এ, আমরা ওয়াইড কালার গ্যামাটের জন্য সহায়তা চালু করেছি।

  • তাহলে, এর আসল অর্থ কী?

  • আপনি কি জানেন যে স্ক্রিন, প্রচলিত sRGB কালার স্পেসের থেকে আলাদা রেঞ্জের কালার রিপ্রোডিউস করতে পারে?

  • যেমন, এই ছবি দেখলে বুঝতে পারবেন যে সাদা ত্রিভুজটি হল স্ট্যান্ডার্ড sRGB কালারের রেঞ্জ যা বেশিরভাগ স্ক্রিনে দেখা যায়।

  • তবে, নতুন স্ক্রিনে, কিছু স্ক্রিন আসলে আরও বেশি রঙের অ্যারে সাপোর্ট করে, যেমন হলুদ ত্রিভুজ দ্বারা যা দেখানো হয়েছে, কিছু আরও স্যাচুরেটেড এবং প্রাণবন্ত।

  • কিন্তু যখন আমরা কোনও রঙকে উপস্থাপন করি, তখন আমরা আসলে সেই ত্রিভুজের প্রতিটি শীর্ষবিন্দুর বিবরণ দিয়ে এটিকে উপস্থাপন করি।

  • আপনি যখন এই গ্রেডিয়েন্ট তুলনা দেখেন, তখন ডিসপ্লে গ্যামাট এবং গ্রেডিয়েন্ট ইন্টারপোলেশনের মধ্যে পার্থক্য করা গুরুত্বপূর্ণ।

  • স্ট্যান্ডার্ড sRGB ডিসপ্লে বা সম্ভবত এই প্রোজেক্টরে, স্ক্রিন আসলে এই sRGB গ্যামাটের বাইরে রঙ ফিজিক্যালি রেন্ডার করতে পারে না।

  • তাই, এই মুহূর্তে স্টেজে এটি ডেমো করে দেখানো সত্যিই কঠিন।

  • তাই, আপনাকে আমার কথা বিশ্বাস করতে হবে যে ওয়াইড কালার গ্যামাট সাপোর্ট সহ Android-এ রঙ আরও ভালো দেখায়।

  • এই উদাহরণে, আপনি আসলে যা দেখছেন তা হল আরও বড় কালার স্পেসে কালার স্টপ ইন্টারপোলেট করার প্রভাব।

  • একটি প্রশস্ত বা লিনিয়ার কালার স্পেসে রঙ ব্লেন্ড করলে এই গ্রেডিয়েন্টের প্রকৃত গাণিতিক পথ পরিবর্তন হয়, যা এই মিড-গ্রেয়িং এবং হার্শ ব্যান্ডিং প্রতিরোধ করে যা আপনি প্রশস্ত কালার গ্যামাট ছাড়া করলে হতে পারে।

  • Compose 1.12-এর মাধ্যমে, আপনি যেকোনও ডিসপ্লেতে আরও মসৃণ গ্রেডিয়েন্ট ব্লেন্ডিংয়ের সুবিধা পাবেন, সেইসাথে আপনি যখন কোনও কাজ করা যায় এমন হার্ডওয়্যারে চালাচ্ছেন তখন সত্যিকারের ওয়াইড কালার গ্যামাট সাপোর্ট পাবেন।

  • ডিভাইসে সঠিক সাপোর্ট চালু করতে, আপনার অ্যাক্টিভিটি বা উইন্ডো যাতে ওয়াইড কালার গ্যামাট মোড চালু করার জন্য সেট করা থাকে তা নিশ্চিত করতে হবে।

  • এবং এটি আপনার AndroidManifest-এ করা যেতে পারে।

    <!-- AndroidManifest.xml -->
    <activity
    android:name=".NavActivity"
    android:colorMode="wideColorGamut"
    android:exported="true">
    </activity>
  • অথবা আপনার কম্পোজেবলে প্রোগ্রাম্যাটিকভাবে।

    // Dynamic toggle in code
    DisposableEffect(window) {
       window?.colorMode = ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT
       onDispose {
           window?.colorMode = ActivityInfo.COLOR_MODE_DEFAULT
       }
    }
  • colorMode সেট না করলে, উইন্ডোটি স্ট্যান্ডার্ড ৮-বিট sRGB-তে থেকে যায় এবং GPU দ্বারা রেন্ডার করা যেকোনও ওয়াইড কালার গ্যামাট কালার ক্লিপ করা হবে।

  • তাই, ডিসপ্লেতে যদি ওয়াইড কালার গ্যামাট কালার সাপোর্ট না করে, তাহলে সেটি ব্যবহার না করাই ভালো।

  • ব্যাটারি ও পারফর্ম্যান্স সাশ্রয় করতে, আপনি শুধুমাত্র সেইসব অ্যাক্টিভিটি বা কোড পাথে ওয়াইড কালার গ্যামাট চালু করবেন যেগুলি আসলে মিডিয়া, ফটো বা রিচ গ্রাফিক্স দেখায়।

  • এর অর্থ হল, আপনি Compose-এ বিভিন্ন কালার স্পেসে রঙ তৈরি করতে পারবেন এবং এগুলি এখন মান্য করা হয়।

    // 1. Display P3 Color (Wider gamut: greens & reds are more saturated) colorMode=wideColorGamut
    val VividP3Red = Color(
       red = 1.0f,
       green = 0.05f,
       blue = 0.05f,
       alpha = 1.0f,
       colorSpace = ColorSpaces.DisplayP3
    )
    // 2. Extended sRGB (Enables HDR values > 1.0f) use colorMode=hdr
    val HdrNeonCyan = Color(
       red = 0.0f,
       green = 1.8f,  // Peak brightness > 1.0 in extended gamut
       blue = 2.0f,
       alpha = 1.0f,
       colorSpace = ColorSpaces.ExtendedSrgb
    )
    // 3. BT.2020 HLG (Broadcast HDR standard, supported API 34+) - use colorMode=hdr
    val HdrSunsetOrange = Color(
       red = 1.0f,
       green = 0.45f,
       blue = 0.1f,
       alpha = 1.0f,
       colorSpace = ColorSpaces.Bt2020Hlg
    )
  • আপনি যে ধরনের colorSpace-এ আপনার রঙ তৈরি করতে চান তা পাস করতে পারেন এবং আপনি কোনও colorSpace পাস না করলে, স্ট্যান্ডার্ড sRGB ব্যবহার করা হবে।

  • বিটম্যাপের ক্ষেত্রে, আপনি getColorSpace ব্যবহার করে বিটম্যাপের সোর্স কী তা চেক করতে পারবেন এবং জনপ্রিয় ইমেজ লোডিং লাইব্রেরি আপনার জন্য সঠিক বিটম্যাপ কালার স্পেস অটোমেটিক ম্যানেজ করবে।

  • রঙের তথ্য সংরক্ষণ করতে আপনার Bitmap.Config.HARDWARE ব্যবহার করা উচিত, কারণ 565-এর মতো কনফিগারেশন আসলে নির্ভুলতা হারাবে এবং ব্যবহারকারীদের কাছে দৃশ্যমান ব্যান্ডিং থাকবে।

  • রঙ সম্পর্কে আরও জানতে, আপনি ২০১৭ সালে ফিরে গিয়ে রোমেন গাইয়ের কম্পোজে রঙ সম্পর্কে বা Android-এ রঙ সম্পর্কে আলোচনা দেখতে পারেন।

বহুভুজ আকার এবং মর্ফিং

  • Compose 1.13-এর সাথে, আমরা বহুভুজ আকারের জন্য নেটিভ সাপোর্ট চালু করছি।

  • এর ফলে কাস্টম শেপ ও অর্গ্যানিক দেখতে UI এলিমেন্ট তৈরি করা অনেক সহজ হয়ে যাবে।

  • আগে, আপনি যদি এই ধরনের একটি জটিল জ্যামিতি আঁকতে চান, তাহলে আপনাকে একটি জ্যামিতি বর্ণনা করতে RoundedPolygon ব্যবহার করতে হবে যা একটি পাথ আউটপুট করবে এবং তারপরে সেই পাথটিকে একটি কম্পোজ শেপ API-তে ম্যাপ করবে।

    fun cookiePolygon(): RoundedPolygon {
        val vertices = FloatArray(32)
        for (i in 0 until 16) {
            val radius = if (i % 2 == 0) 1f else 0.84f
    // …
        }
        return RoundedPolygon(
            vertices,
            rounding = CornerRounding(0.12f)
        )
    }
  • যেমন, প্রথমে আপনি RoundedPolygon ব্যবহার করে অনুপাত নির্ধারণ করেন এবং আমরা সেট করি। এই স্নিপেটে, আমরা আমাদের ভেতরের পয়েন্টগুলি বাইরের পয়েন্টের তুলনায় কেন্দ্র থেকে ৮৪% দূরত্বে সেট করি।

  • এছাড়াও, আপনি এখানে কোণ রাউন্ডিং প্রয়োগ করতে পারেন, তবে এটি শুধুমাত্র গাণিতিক কার্ভ বর্ণনা করে।

  • Compose-এ ক্লিপিং বা ব্যাকগ্রাউন্ডের জন্য এই পলিগন ব্যবহার করতে, আপনাকে এই ধরনের কাস্টম Shape র‍্যাপার লিখতে হবে।

    class RoundedPolygonShape(
     polygon: RoundedPolygonShape
    ) : Shape {
        // …
        override fun createOutline(size: Size, layoutDirection: LayoutDirection,
        density: Density,
        ): Outline {
            path = polygon.toPath().asComposePath()
            // …
            matrix.scale(size.width / maxDimension, size.height / maxDimension)
            matrix.translate(-bounds.left, -bounds.top)
            // …
            return Outline.Generic(path)
        }
    }
  • এবং এই createOutline স্নিপেটের মধ্যে, আপনি দেখতে পাবেন যে আপনাকে পাথ তৈরি করতে হবে এবং ম্যানুয়ালি স্কেল করতে হবে এবং সেগুলি নিজেই অনুবাদ করতে হবে।

  • এখন, নতুন PolygonShape API-এর মাধ্যমে, আপনার আর সেই কাস্টম শেপ র‍্যাপারের প্রয়োজন নেই।

    private val AvatarShape = PolygonShape {
        val vertices = List(16) { i ->
            val radius = size.minDimension / 2f * if (i % 2 == 0) 1f else 0.86f
            val angle = i * PI.toFloat() / 8f
            size.center + Offset(cos(angle), sin(angle)) * radius
        }
        polygon(vertices, rounding = CornerRounding(radius = 8.dp))
    }
    
        // …
    Modifier.clip(AvatarShape)
  • এটি ডেডিকেটেড Compose Shape বিল্ডার হওয়ার কারণে, এটি সরাসরি আউট-অফ-দ্য-বক্স থেকে ঘনত্ব এবং সাইজ পায়।

  • এর অর্থ হল, আপনি ডায়নামিক উপায়ে ভার্টিসেস গণনা করতে পারবেন এবং ডিপি বা শতাংশ হিসেবে সরাসরি কোণ রাউন্ডিং নির্দিষ্ট করতে পারবেন।

  • একবার সংজ্ঞায়িত করা হয়ে গেলে, আপনি এটি যেকোনও API-তে পাস করতে পারবেন যা Shape নেয়।

  • এছাড়াও, আমরা MorphPolygonShape API নিয়ে আসছি, যা আপনাকে বিভিন্ন পলিগন শেপের মধ্যে নির্বিঘ্নে ট্রানজিশন করতে দেয়।

    val recordShape = remember {
        MorphPolygonShape(
            start = PolygonShape.circle(),
            end = PolygonShape.star(
                numPoints = 9,
                innerRadiusRatio = 0.8f,
                outerRounding = CornerRounding(percent = 50),
            ),
        ) { shapeProgress.value }
    }
  • এই উদাহরণে, আমাদের কাছে একটি রেকর্ড বোতাম আছে যা আপনি এটির সাথে ইন্টার‍্যাক্ট করলে একটি বৃত্ত থেকে একটি তারায় রূপান্তরিত হয়।

  • আপনাকে শুধু শুরুর আকার, শেষের আকার এবং প্রোগ্রেস ভ্যালু দিতে হবে।

  • MorphPolygonShape API আউটলাইন রেজোলিউশন ফেজ চলাকালীন প্রগ্রেস ভ্যালু পড়ে, এর অর্থ হল, রিকম্পোজিশন চলাকালীন নয়- কম্পোজিশন চলাকালীন নয়।

  • এর অর্থ হল, আপনার শেপ ফ্রেম বাই ফ্রেম মর্ফ করার সময়, এটি রিকম্পোজিশন ট্রিগার না করেই নতুন আউটলাইনে পরিণত হয়।

ব্লার করা ও ব্যাকড্রপ রেন্ডার এফেক্ট

  • এরপর, অস্পষ্ট করা সম্পর্কে আলোচনা করা যাক।

  • গত কয়েকটি রিলিজের মাধ্যমে, আমরা ব্লার প্রয়োগ করার পদ্ধতিকে আরও সহজ ও অর্থপূর্ণ করে তোলার চেষ্টা করছি।

  • আমরা যখনই অস্পষ্ট করার কথা বলি, তখনই এই মডিফায়ারটি সাধারণত ব্যবহার করা হয়।

    Modifier
       .blur(
           radiusX = 10.dp,
           radiusY = 10.dp,
           edgeTreatment = BlurredEdgeTreatment(
               shape = RoundedCornerShape(8.dp),
           ),
       )
  • Android 12 থেকে Compose-এ এই মডিফায়ার আছে।

  • আপনি যখন ইউনিফর্ম ব্লার চান, যেখানে কম্পোজেবল জুড়ে একই ব্লার রেডিয়াস প্রয়োগ করা হয়, তখন এটি দারুণ কাজ করে।

  • তবে আধুনিক UI ডিজাইনে প্রায়ই শার্প থেকে ব্লার করা অঞ্চলের মধ্যে সাবলীল ট্রানজিশন প্রয়োজন হয়, হার্ড কাটঅফ নয়।

  • আর এর ফলে আমরা প্রোগ্রেসিভ ব্লার ব্যবহার করতে পারি, যা এখন Compose 1.13 থেকে শুরু করে একটি অফিসিয়াল API।

  • পুরো কম্পোজেবল জুড়ে একটি একক ধ্রুবক ব্যাসার্ধের পরিবর্তে, এখন আপনি স্পেস জুড়ে অস্পষ্টতার তীব্রতা পরিবর্তন করতে পারেন।

  • ব্যবহারকারীর প্রোফাইল স্ক্রিন থেকে আমাদের JetChat অ্যাপের উদাহরণ এখানে দেওয়া হল।

  • নতুন ব্লার মডিফায়ার ব্লার রেডিয়াস স্পেক নেয়।

    Modifier.blur {
    blurRadiusSpec = BlurRadiusSpec.verticalGradient(
    listOf(
    BlurStop(0.5f, 0.dp),
    BlurStop(1.0f, 32.dp),
    )
    )
    edgeTreatment = BlurredEdgeTreatment.Unbounded
    }
  • এখানে আমরা দুটি ব্লার স্টপ সহ উল্লম্ব গ্রেডিয়েন্ট ব্যবহার করি, ফটোর উপরের অর্ধেকটি ০ ডিপি-তে সম্পূর্ণ শার্প রাখি এবং নিচের প্রান্তে ৩২ ডিপি ব্লার পর্যন্ত বাড়িয়ে দিই।

  • লক্ষ্য করুন, আমরা edgeTreatment-কে আনবাউন্ডেড হিসেবে সেট করেছি, যা মূলত ব্লারকে কম্পোজেবল বাউন্ডের বাইরেও ট্রান্সপারেন্ট পিক্সেল স্যাম্পেল করতে বলে।

  • ভার্টিক্যাল ফেড-আউট ছাড়াও, এই ব্লার রেডিয়াস স্পেক আপনাকে প্রোগ্রেসিভ ব্লার করার জন্য একাধিক বিল্ট-ইন উপায় প্রদান করে।

  • যেমন, বাঁদিকে আপনি অনুভূমিক গ্রেডিয়েন্ট দেখতে পাবেন, যেখানে শুরুর ব্যাসার্ধ হল ০ ডিপি এবং শেষের ব্যাসার্ধ স্লাইডার ভ্যালু দ্বারা পরিচালিত হয়।

    BlurRadiusSpec.horizontalGradient(
    startRadius = 0.dp,
    endRadius = sliderValue.dp,
    )
  • এবং ডানদিকে, আমাদের একটি রেডিয়াল গ্রেডিয়েন্ট রয়েছে, যেখানে কেন্দ্রে, ব্লার রেডিয়াস হল ০ ডিপি এবং আপনি fallOffRadius-এর দিকে এগিয়ে যাওয়ার সাথে সাথে, যা এই ক্ষেত্রে ৩০০ ডিপি, ব্লার মসৃণভাবে ২৪ ডিপি পর্যন্ত র‍্যাম্প আপ করে।

    BlurRadiusSpec.radialGradient(
    center = center,
    fallOffRadius = 300.dp,
    startRadius = 0.dp,
    endRadius = 24.dp,
    )
  • আর এই উল্লম্ব, রেডিয়াল ও অনুভূমিক গ্রেডিয়েন্ট যদি আপনার জন্য যথেষ্ট না হয়, তাহলে আপনি কাস্টম শেডার ব্যবহার করে প্রোগ্রেসিভ ব্লার ড্রাইভ করতে পারবেন।

    val customShader = remember {
       RuntimeShader(
           """
           uniform float2 size;
           uniform float time;
    
           half4 main(float2 coords) {
              // Diagonal wave mask
              float normalizedX = coords.x / size.x;
              float normalizedY = coords.y / size.y;
              float wave = sin((normalizedX + normalizedY) * 6.0 + time) * 0.5 + 0.5;
              return half4(0.0, 0.0, 0.0, wave);
           }
           """.trimIndent()
       )
    }
    
    val customBlurRadius = remember(customShader) {
       BlurRadiusSpec.shader(maxRadius = 24.dp) { customShader }
    }
    
    Modifier.blur {
           val w = size.width.toPx()
           val h = size.height.toPx()
           customShader.setFloatUniform("size", w, h)
           customShader.setFloatUniform("time", animatedTime)
           blurRadiusSpec = customBlurRadius
       },
  • এখানে আমরা একটি ছোট ফ্র্যাগমেন্ট শেডার লিখি যা একটি অ্যানিমেটেড ডায়াগোনাল সাইন ওয়েভ গণনা করে এবং সেই ইনটেনসিটি একটি আলফা চ্যানেলে আউটপুট করে।

  • তারপর শেডার স্পেক সেই ০ থেকে ১ মাস্ককে সরাসরি ২৪ ডিপি মাস্ক maxRadius-এ ম্যাপ করে।

  • ব্যাকগ্রাউন্ড ব্লার করার ব্যাপারে কথা বলার আগে, আসুন দুটির মধ্যে পার্থক্য দেখে নেওয়া যাক।

  • এখন বাঁদিকে, আমাদের কাছে একটি টপ অ্যাপ বার আছে যেখানে আমরা লেয়ার ব্লার প্রয়োগ করেছি।

  • অর্থাৎ, এটি কম্পোজেবলকে নিজেই অস্পষ্ট করে।

  • আপনি দেখতে পাচ্ছেন যে শীর্ষক ও অ্যাকশন আইকন অস্পষ্ট, কিন্তু তার পিছনে চ্যাট কম্পোনেন্ট একেবারেই অস্পষ্ট নয়।

  • ডানদিকে, ব্যাকগ্রাউন্ড অস্পষ্ট করার সুবিধা রয়েছে।

  • এখানে শুধু টপ বারের পিছনে স্ক্রল করা চ্যাট মেসেজ স্যাম্পেল ও অস্পষ্ট করা হয়।

  • তবে, টেক্সট ও আইকন সম্পূর্ণ তীক্ষ্ণ ও পাঠযোগ্য হতে হবে।

  • Android 37.2 থেকে, আপনি RenderNode.setBackdropRenderEffect নামক একটি নতুন API ব্যবহার করে ব্যাকগ্রাউন্ড ব্লার করতে পারবেন।

  • তবে, ব্লার কীভাবে উন্নত হচ্ছে তা জানার আগে, ব্লার যাতে পারফর্ম্যান্ট হয় তা নিশ্চিত করতে Android কীভাবে কাজ করে তা বুঝে নেওয়া যাক।

  • Android, RenderNodes ব্যবহার করে, যা হার্ডওয়্যার-অ্যাক্সিলারেটেড ডিসপ্লে লিস্ট কন্টেনার, যার অর্থ হল এটি রেকর্ডিং শুরু এবং রেকর্ডিং শেষ করার মাধ্যমে একগুচ্ছ বিভিন্ন ড্রয়িং কমান্ড কম্প্যাক্ট এবং স্টোর করতে পারে।

  • RenderNode-এ আপনার 3D ট্রান্সলেশন, রোটেশন, আলফা, স্কেল ও পজিশন বাউন্ডের মতো তথ্য থাকে।

  • এবং Compose আসলে RenderNode ব্যবহার করে।

  • তাই, Compose-এ ব্যাকড্রপ রেন্ডার এফেক্ট ব্যবহার করতে, আমরা একটি কাস্টম Modifier.Node তৈরি করতে পারি যাতে এই মডিফায়ারটি আবার ব্যবহার করা যায়।

    fun Modifier.backdropBlur(spec: BlurRadiusSpec, shape: Shape, tint: Color): Modifier =
       this then BackdropBlurElement(spec, shape, tint)
    
    private class BackdropBlurNode(...) : Modifier.Node(), DrawModifierNode {
       private var renderNode: RenderNode? = null
    
       override fun ContentDrawScope.draw() {
           val node = getOrCreateNode()
           if (isDirty || sizeChanged) {
               node.setPosition(0, 0, widthPx, heightPx)
               node.setBackdropRenderEffect(spec.createRenderEffect(this))
           }
           drawIntoCanvas { it.nativeCanvas.drawRenderNode(node) }
           drawContent()
       }
    }
  • আমরা RenderNode-এর রেফারেন্স রাখি এবং RenderNode-কে এমনভাবে কনফিগার করা হয় যাতে অন্য ফাংশনে আমরা যে এফেক্ট চাই তা পাওয়া যায়।

  • যেমন, আমরা এখানে ব্লার এফেক্ট পাস করি।

  • এরপর, আমরা drawIntoCanvas-কে কল করি, আমাদের নেটিভ ক্যানভাসে drawRenderNode-কে কল করি।

  • এবং অবশেষে, আমরা drawContent কল করি যাতে উপরেও আসল অস্পষ্ট না করা কন্টেন্ট আঁকা যায়।

  • আমরা এইমাত্র যে মডিফায়ার তৈরি করেছি সেটি ব্যবহার করতে, আমরা Modifier.backdropBlur কল করি এবং ব্লার রেডিয়াস স্পেক পাস করি।

    JetchatAppBar(
        modifier = modifier
            .backdropBlur(
                tint = MaterialTheme.colorScheme
                    .surface.copy(alpha = 0.5f),
                shape = RectangleShape,
                spec = BlurRadiusSpec.uniform(5.dp)
            )
    )
  • মনে রাখবেন, এই পদ্ধতিতে আমাদের এখানে আন্ডারলাইং লেয়ারে কিছু রাখতে হবে না।

  • RenderNode.setBackdropRenderEffect অটোমেটিক সেই কন্টেন্ট স্যাম্পেল করছে যা JetChat অ্যাপ বারের সারফেসের নিচে থাকে।

  • আর এর ফলে ব্যাকগ্রাউন্ড ব্লার করার এই চমৎকার ফিচারটি তৈরি হয়েছে।

  • এছাড়াও, আমরা শুধু প্রগ্রেসিভ ব্লার করার পরিবর্তে প্রগ্রেসিভ ব্লার রেডিয়াস স্পেক সেট করতে পারি এবং কন্টেন্টে ভার্টিক্যাল ব্লারও পেতে পারি।

    JetchatAppBar(
        modifier = modifier
            .backdropBlur(
                tint = MaterialTheme.colorScheme
                    .surface.copy(alpha = 0.5f),
                shape = RectangleShape,
                spec = BlurRadiusSpec.verticalGradient(
                    listOf(
                        BlurStop(0.5f, 32.dp),
                        BlurStop(1f, 0.dp),
                    ),
                )
            )
    )
  • যা উল্লম্ব গ্রেডিয়েন্ট সহ কন্টেনারের বিভিন্ন ভগ্নাংশে বিভিন্ন ব্যাসার্ধ সহ অস্পষ্ট কন্টেন্ট দেখায়।

  • আমরা Compose-এ বিল্ট-ইন ব্যাকড্রপ ব্লার মডিফায়ার নিয়ে কাজ করছি।

  • এবং এটি Compose-এর মাধ্যমে Android-এ অন্তর্নিহিত প্রযুক্তির একটি উদাহরণ মাত্র।

  • আমি যে উদাহরণটি দেখিয়েছি সেটিও শুধুমাত্র API লেভেল ৩৭.২-এ কাজ করবে, তবে আমরা Compose-এর জন্য ব্যাকপোর্ট নিয়ে কাজ করছি, তাই আমাদের সাথে থাকুন।

  • RenderNode ব্যাকড্রপ এফেক্টের আরেকটি মজার উদাহরণ হল যে আমরা আসলে সেগুলিকে একসাথে চেইন করতে পারি যাতে সত্যিই সুন্দর দেখতে UI তৈরি করা যায় এবং এটি এর উপরে একাধিক বিভিন্ন এফেক্ট পারফর্ম করে।

  • তাই, এফেক্ট চেইন করতে, আমরা এই দুটি এফেক্ট তৈরি করি।

    val blur = RenderEffect.createBlurEffect(24f, 24f, Shader.TileMode.CLAMP)
    
    // Boost saturation by 30% and apply blur together
    val matrix = ColorMatrix().apply { setSaturation(1.3f) }
    val vibrancy = RenderEffect.createColorFilterEffect(ColorMatrixColorFilter(matrix))
    val vibrantBlur = RenderEffect.createChainEffect(vibrancy, blur)
  • এই উদাহরণে, আমরা একটি ব্লার এফেক্ট এবং ৩০% স্যাচুরেশন তৈরি করি।

  • আমরা প্রথমে ব্লার এফেক্ট এবং পরে স্যাচুরেশন প্রয়োগ করে createChainEffect কল করি।

  • এটি JetChat-এর একটি উদাহরণ।

    val agslShader = RuntimeShader(CHROMATIC_ABERRATION)
    val noiseEffect = RenderEffect.createRuntimeShaderEffect(agslShader, "backdropInput")
    
    val finalEffect = RenderEffect.createChainEffect(noiseEffect, vibrantBlur)
    renderNode?.setBackdropRenderEffect(finalEffect)
  • জাম্প-টু বোতাম জাম্প টু বটম বোতাম, যা আমরা কয়েকটি ভিন্ন প্রভাব একত্রিত করে একটি ছোট ম্যাগনিফাইং গ্লাসের মতো দেখাতে পারি যাতে এই কম্পোজেবলের প্রান্তগুলি ম্যাগনিফায়ারের মতো গোল হয়ে যায়।

  • তবে, আপনি শুধু ম্যাগনিফাইং এফেক্টই প্রয়োগ করতে পারবেন না, ব্যাকড্রপে প্রয়োগ করার জন্য যেকোনও ধরনের শেডার একসাথে চেইন করা যেতে পারে।

  • এখানে কিছু মজার উদাহরণ দেওয়া হল যা আমরা প্রয়োগ করেছি এবং এগুলি একসাথে করলে কী কী করা সম্ভব তা দেখানো হয়েছে।

  • ঐতিহ্যবাহী ফ্রস্টেড-গ্লাস-টাইপ এফেক্ট এখন সম্ভব।

  • তবে আমরা অন্যান্য মজাদার এফেক্টও করতে পারি, যেমন ক্যালিডোস্কোপ।

  • অথবা আমরা যদি এই Android মাসকটকে এক ধরনের মর্ফিং UI এলিমেন্টে পরিণত করতে চাই যা নিচের কন্টেন্টকে ট্রান্সফর্ম করে, তাহলে আমরা এই ব্যাকড্রপ রেন্ডার এফেক্টও প্রয়োগ করতে পারি যাতে মনে হয় যে আমরা এই ছোট ড্রয়েডের মাধ্যমে দেখছি।

  • এছাড়াও, আমরা ব্যাকড্রপ রেন্ডার এফেক্টের মাধ্যমে এটি করতে পারি।

  • তাই, এই নতুন API-এর মাধ্যমে শুধু ব্যাকড্রপ ব্লার ও ফ্রস্টেড গ্লাস আনলক করা হয়নি, এখন আপনার কল্পনার অসীম সম্ভাবনাকে খুঁজে পান।

  • প্ল্যাটফর্মে আমরা আরেকটি নতুন API নিয়ে এসেছি, সেটি হল SurfaceView.setBlurRegions।

  • বর্তমানে আপনি টেক্সচার ভিউ এবং Modifier.blur ও RenderNode ব্যাকড্রপ এফেক্ট ব্যবহার করে যেকোনও কম্পোজেবল অস্পষ্ট করতে পারবেন, তবে SurfaceView-এর অংশ অস্পষ্ট করা কঠিন এবং এটি অ্যাপের ভিউ RenderNode ট্রির অংশ নয়, কারণ এটি আসলে উইন্ডোর মাধ্যমে একটি গর্ত তৈরি করে।

  • তাই, Compose আসলে SurfaceView-এর ব্যাকিং বাফার স্যাম্পেল করতে পারে না, যা API 37.2-এর আগে পর্যন্ত অসম্ভব ছিল।

  • তাই, SurfaceView-এর setBlurRegions অ্যাপকে সরাসরি অস্পষ্ট অঞ্চল ঘোষণা করার অনুমতি দেয়।

  • যেমন, JetChat-এর এই ভিডিও প্লেয়ারে, কন্ট্রোল দেখানো এবং লুকানোর সময় আমরা সারফেস ব্লার SurfaceView ব্লার যোগ করেছি।

  • এই নতুন API ব্যবহার করতে, আপনি SurfaceView পদ্ধতিতে setBlurRegions অ্যাক্সেস করতে পারবেন।

    val region = RoundedRectBlurRegion().apply {
       bounds = RectF(left, top, right, bottom)
       setCornerRadii(spec.cornerRadiusPx.coerceAtLeast(0f))
       alpha = spec.alpha.coerceIn(0f, 1f)
       blurRadius = spec.blurRadiusPx.coerceAtLeast(0f)
    }
    
    surfaceView.setBlurRegions(listOf(region))
    
    surfaceView.setBlurRegions(emptyList()) // Clear
  • এটি বাউন্ডের ক্ষেত্র, ব্লার রেডিয়াস, আলফা এবং এতে কর্নার রেডিয়াসের তালিকা নেয়।

  • এইসব বাউন্ডে পাস করার সময়, সেগুলি স্ক্রিনে SurfaceView-এর সাথে সম্পর্কিত হতে হবে।

  • তাই, আপনি SurfaceView-এর কোঅর্ডিনেট এবং যে অংশটি অস্পষ্ট করতে চান সেটি ট্র্যাক করতে চাইবেন।

RuntimeColorFilter

  • Android 16 ও তার পরবর্তী যেকোনও ভার্সনে আরেকটি নতুন ফিচার হল RuntimeColorFilter।

  • আগে, ছবির ক্ষেত্রে, আপনি শুধুমাত্র একটি স্ট্যাটিক ম্যাট্রিক্স কালার ফিল্টার সেট করতে এবং স্যাচুরেশনের মতো এফেক্ট দিতে পারতেন।

  • তবে, Android 16 ও তার পরবর্তী ভার্সনে, আপনি এখন এমন শেডার পাস করতে পারবেন যা ইন কালার, সেই নির্দিষ্ট কন্টেন্টের জন্য ইনপুট পিক্সেল নেয় এবং নতুন রঙ তৈরি করে।

  • যেমন, আমরা যদি কোনও ফটোতে সাইবারপাঙ্ক ফিল্টার প্রয়োগ করতে চাই, তাহলে আমরা একটি ছোট শেডার সংজ্ঞায়িত করতে পারি যা RuntimeColorFilter-এ ইনপুট হিসেবে ব্যবহার করা হয়।

    @Language("AGSL")
    private const val CYBERPUNK_SHADER = """
        vec4 main(half4 in_color) {
            float lum = dot(in_color.rgb, vec3(0.299, 0.587, 0.114));
            vec3 shadow = vec3(0.06, 0.04, 0.25);
            vec3 mid = vec3(0.92, 0.12, 0.58);
            vec3 high = vec3(0.18, 0.94, 0.98);
            vec3 col = lum < 0.5 ? mix(shadow, mid, lum * 2.0) : mix(mid, high, (lum - 0.5) * 2.0);
            return vec4(mix(in_color.rgb, col, 0.7), in_color.a);
        }
    """
  • এই শেডার সাইবারপাঙ্ক থিম তৈরি করে, তবে আপনি চাইলে অন্য যেকোনও এফেক্ট তৈরি করতে পারেন।

  • তাই, শেডার ব্যবহার করতে, আমরা RuntimeColorFilter প্রয়োগ করি এবং এটিকে Compose কালার ফিল্টারে কনভার্ট করি।

    AsyncImage(
        model = imageUri,
        contentDescription = filter.displayName,
        colorFilter = RuntimeColorFilter(CYBERPUNK_SHADER)
                       .asComposeColorFilter(),
        contentScale = ContentScale.Crop,
        modifier = Modifier.fillMaxSize(),
    )
  • এই উদাহরণে আপনি দেখতে পাচ্ছেন যে, OpenGL, SurfaceView সেট-আপ বা এই ধরনের কোনও কিছুতে গভীরভাবে না গিয়েও আমরা এখন আগের চেয়ে আরও ডাইনামিক এফেক্ট তৈরি করতে পারি।

  • এবং এটি যেকোনও কম্পোজেবলের ক্ষেত্রেও প্রযোজ্য হতে পারে, তাই শুধু ছবি নয়, আরও অনেক কিছুতেই এটি প্রযোজ্য।

গ্রাফিক্স পারফর্ম্যান্স অপ্টিমাইজেশন

  • ধন্যবাদ।

  • সুতরাং, আমরা কিছু সত্যিই চমৎকার এফেক্ট তৈরি করেছি, তবে আমাদের অ্যাপটি এখনও দ্রুত এবং মসৃণভাবে কাজ করছে কিনা তা নিশ্চিত করা যাক।

  • আমরা যখন কথোপকথন স্ক্রল করি, তখন আমাদের সব এফেক্ট একসাথে চলতে থাকে।

  • এই স্ক্রল পরিমাপ করে শুরু করা যাক।

    @Test
    fun scrollFling() = measureScroll(swipeSteps = 5)
    
    private fun measureScroll(swipeSteps: Int) = rule.measureRepeated(
        packageName = TARGET_PACKAGE,
        metrics = defaultComposeScrollingMetrics(),
        compilationMode = CompilationMode.Full(),
        iterations = 5,
        setupBlock = {
            killProcess()
        }
    //...
  • আমরা একটি ম্যাক্রো বেঞ্চমার্ক ব্যবহার করি যা একটি নতুন প্রসেসে একটি অ্যাপ চালু করে এবং কথোপকথনের মাধ্যমে স্ক্রোল রান করে।

  • তারপর, আমরা ট্রেস কোয়েরি করে সেইসব ফ্রেমের সংখ্যা গণনা করি যেগুলি ডেডলাইন মিস করেছে।

    select count(*) as late_frames
    from actual_frame_timeline_slice
    where upid = (select upid from process where name = 'com.example.compose.jetchat.perf')
      and jank_type like '%App Deadline Missed%'
  • এর ফলে আমরা ছয়টি লেট ফ্রেম পাই।

  • আসুন, লেট ফ্রেমের মধ্যে একটি খোলা যাক।

  • তাই, এই ফ্রেমের ঠিক আগে, Compose পরবর্তী মেসেজ প্রিফেচ করছে, যাতে এটি স্ক্রল করে ভিউতে আসার আগেই তা করা যায়।

  • প্রিফেচ ফ্রেমের মধ্যে ছোট ফাঁকে ফিট করার কথা, কিন্তু এই মেসেজটি প্রস্তুত করার সময় এর ছবি সরাসরি মূল থ্রেডে ডিকোড করে, যা কোনও ফলাফল ছাড়াই দীর্ঘ সময় ধরে চলে।

  • এই ছবি ডিকোড করার সময় মূল থ্রেড আটকে গেছে, তাই নতুন কোনও ফ্রেম শুরু করা যাবে না।

  • তারপর, সেই মেসেজটি যখন স্ক্রল করে ভিউতে আসে, তখন RenderThread-কে মেমরি বরাদ্দ করতে হয় এবং সেইসব পিক্সেল GPU-তে আপলোড করতে হয়, তাই একই ছবি দ্বিতীয় ফ্রেমকে বিলম্বিত করে।

  • এখন, আমরা দেখতে পাচ্ছি যে আমাদের সময় কোথায় গেছে, কিন্তু এই ফ্রেমটি আঁকার জন্য কি আমাদের এই সমস্ত কাজের প্রয়োজন আছে?

  • আর আমরা কীভাবে বলতে পারি?

  • ট্রেস আমাদের বলে।

  • যখনই স্ক্রিনে অনেক ছোট জায়গায় বড় বিটম্যাপ আঁকা হয়, Android ট্রেসে একটি মার্কার লগ করে।

  • এটি পিক্সেলে দুটি সংখ্যা দেখায়: সম্পূর্ণ বিটম্যাপ ডাইমেনশন এবং স্ক্রিনে এটি আসলে কতটা জায়গা কভার করে।

  • স্ক্রিন ডেনসিটি দিয়ে সেটি ভাগ করুন এবং আপনি DP-তে সেই মান পেয়ে যাবেন।

  • তারপর, আমরা সেই ডিপি ভ্যালু দিয়ে আমাদের কোড সার্চ করে সেই ছবি খুঁজে বের করি।

  • আমরা এটি খুঁজে পেয়েছি।

  • ছবিটি painterResource-এর মাধ্যমে লোড করা হয়েছে, যা কন্টেনারের সাইজ জানে না, তাই এটি সম্পূর্ণ রেজোলিউশনে পুরো ছবিটি ডিকোড করে।

    Image(
        painter = painterResource(it),
        contentScale = ContentScale.Fit,
        modifier = Modifier.size(160.dp),
        contentDescription = stringResource(id = R.string.attached_image),
    )
  • তাই, আমরা এটি কয়েল থেকে AsyncImage দিয়ে পরিবর্তন করি।

    AsyncImage(
        model = it,
        contentScale = ContentScale.Fit,
        modifier = Modifier.size(160.dp),
        contentDescription = stringResource(id = R.string.attached_image),
    )
  • এটি প্রথমে কন্টেনার পরিমাপ করে, তাই এটি শুধুমাত্র প্রয়োজনীয় পিক্সেল ডিকোড করে এবং এটি একটি ভিন্ন থ্রেডে সেই কাজটি করে।

  • একই বেঞ্চমার্ক আবার রান করা হচ্ছে, মূল থ্রেডে ইমেজ ডিকোডিং কমে ০ হয়েছে এবং লেট ফ্রেম কমে ২ হয়েছে।

  • আমরা এখানে এই দুটি ফ্রেম ডিবাগ করব না, তবে মূল কথাটি হল: এমন পিক্সেল গণনা করার চেষ্টা করবেন না যা কখনও ফ্রেমে পৌঁছাবে না।

  • তাই, ফ্রেম দেরিতে দেখানোর জন্য ইমেজ ডিকোডিং যথেষ্ট বড় ছিল, কিন্তু প্রতিটি খরচ ড্রপ করা ফ্রেম হিসেবে দেখানো হয় না এবং এখনও ছোট খরচ আছে যা আমরা কোড লেখার সময় এড়াতে পারি।

  • তাহলে, এর মধ্যে দুটি দেখে নেওয়া যাক।

  • আমরা যখনই RuntimeShader ব্যবহার করি, আমাদের এটি একবার তৈরি করা উচিত কারণ এটি শুধুমাত্র কম্পাইল করে এবং ক্যাশে করা হয়, তবুও আমরা যখনই একটি তৈরি করি তখন এটি তার সোর্স স্ট্রিং পার্স করে।

  • এবং যখনই কোনও এফেক্ট প্রতিটি ফ্রেমকে অ্যানিমেট করে, আমরা এটিকে একটি graphicsLayer-এ র‍্যাপ করতে পারি, যাতে শুধুমাত্র অ্যানিমেটিং এলিমেন্টটি আবার রেকর্ড করা হয় এবং এর চারপাশে UI আবার আঁকার পরিবর্তে।

  • 'নিচে যান' বোতামের ক্ষেত্রে, আমরা যখনই স্ক্রল করি তখনই এটি দেখা যায়।

  • এই এফেক্ট দেখানোর জন্য দুটি RuntimeShader ব্যবহার করা হয় এবং প্রতিবার বোতামটি দেখা গেলে, আমরা দুটি শেডারই তাদের সোর্স স্ট্রিং থেকে আবার তৈরি করি।

    val lensShader = RuntimeShader(MAGNIFIER_LENS_SHADER).apply {
        setFloatUniform("size", size.width, size.height)
        setFloatUniform("zoom", zoom)
    // …
    val lensEffect = RenderEffect.createRuntimeShaderEffect(lensShader, "content")
    // …
    val glassShader = RuntimeShader(GLASS_FINISH_SHADER).apply {
  • বোতামটি যখনই দেখানো হয়, তখনই মূল থ্রেডে এই দুটি শেডার পার্স করতে এবং এফেক্ট তৈরি করতে শত শত মাইক্রোসেকেন্ড সময় লাগে।

  • পরিবর্তে, আমরা পুরো প্রসেসের জন্য একবার শেডার তৈরি করি অথবা আমরা এটি মনে রাখতে পারি এবং তারপরে এর ইউনিফর্ম আপডেট করতে পারি।

    val lensShader = MagnifierShaders.lens.apply {
        setFloatUniform("size", size.width, size.height)
        setFloatUniform("zoom", zoom)
    // …
    private object MagnifierShaders {
        val lens: RuntimeShader by lazy(LazyThreadSafetyMode.NONE) { RuntimeShader(MAGNIFIER_LENS_SHADER) }
        val glassFinish: RuntimeShader by lazy(LazyThreadSafetyMode.NONE) { RuntimeShader(GLASS_FINISH_SHADER) }
  • এই পরিবর্তনের ফলে, সেই এফেক্ট তৈরি করা প্রায় ৪০ গুণ দ্রুত হয় এবং বোতামটি দেখা গেলে খরচ ২০ গুণ কমে যায়।

  • আমরা আগে যে মেশ গ্রেডিয়েন্ট গ্লো দেখেছি, তাতে drawBehind-এর মধ্যে প্রতিটি ফ্রেমে গ্লো অ্যানিমেট করে।

    Box(
        modifier = Modifier
            .matchParentSize()
            .drawBehind {
                // Behind the card, scaled past its bounds.
                translate(top = -58.dp.toPx()) {
                    scale(scaleX = 1.38f, scaleY = 2.85f) {
                        with(glowMeshPainter) {
    draw(size, alpha = glowAlpha)
    }
  • তাই ড্র ফেজে রিডিং স্টেট কম্পোজিশন ও লেআউট বজায় রাখে, কিন্তু ডিসপ্লে লিস্ট কোনও একটি ড্র কমান্ডকে আপডেট করতে পারে না।

  • তাই Compose গ্রাফিক্স লেয়ার সহ সবচেয়ে কাছের নোড খুঁজে পেতে ট্রি-তে উপরের দিকে যায়, যা এখানে লাল আউটলাইন, অর্থাৎ পুরো স্ক্রিন এবং প্রতিটি ফ্রেমে সেই সম্পূর্ণ লেয়ার আবার রেকর্ড করে।

  • বক্সের ঠিক উপরে graphicsLayer রেখে, ট্রাভেল সবুজ আউটলাইনে থেমে যায়, তাই, তাই শুধু গ্লো বক্স আবার রেকর্ড করা হয় এবং মূল থ্রেড ড্রয়িং স্লাইস এক মিলি সেকেন্ডের দুই-দশমাংশ কমে যায়।

    Box(
        modifier = Modifier
            .matchParentSize()
                      .graphicsLayer()
            .drawBehind {
                // Behind the card, scaled past its bounds.
                translate(top = -58.dp.toPx()) {
                    scale(scaleX = 1.38f, scaleY = 2.85f) {
                        with(glowMeshPainter) {
    draw(size, alpha = glowAlpha)
    }
  • Perfetto-র মাধ্যমে, ট্রেসে কী ভুল হয়েছে তা বিশ্লেষণ করা কঠিন হতে পারে, তাই কিছু ইনসাইট সংগ্রহ করতে আপনাকে সাহায্য করার জন্য আপনি ট্রেস বিশ্লেষণ দক্ষতা ব্যবহার করতে পারেন।

  • এছাড়াও, আপনি যদি পারফর্ম্যান্স কম্পোজ কোড লেখার ক্ষেত্রে আরও সহায়তা চান, তাহলে Android CLI ইনস্টল করতে ভুলবেন না এবং শুরু করার জন্য কম্পোজ পারফর্ম্যান্স স্কিল ব্যবহার করুন।

    android skills add perfetto-trace-analysis
    android skills add jetpack-compose-performance

সারসংক্ষেপ ও রিসোর্স

  • আজ আমরা যেসব উদাহরণ দেখিয়েছি সেগুলি দেখতে চাইলে, এই লিঙ্কে উপলভ্য JetChat অ্যাপটি দেখুন।

কোড

কোড স্নিপেট

"Mesh gradients and custom mesh rendering" ভিডিওর 02:47-এ Kotlin স্নিপেট
val gradientPainter = remember {
MeshGradientPainter(rows = 3, columns = 3) {
}
}
"Mesh gradients and custom mesh rendering" ভিডিওর 02:55-এ Kotlin স্নিপেট
val gradientPainter = remember {
MeshGradientPainter(rows = 3, columns = 3) {
   setVertex(0, 0, Offset(0.0f, 0.0f), indigo)

}
}
"Mesh gradients and custom mesh rendering" ভিডিওর 03:05-এ Kotlin স্নিপেট
val gradientPainter = remember {
MeshGradientPainter(rows = 3, columns = 3) {
   setVertex(0, 0, Offset(0.0f, 0.0f), indigo)
     // ....
   setVertex(1, 1, Offset(0.2f, 0.4f) + Offset(animatedOffset, animatedOffset), coral)
   // ....
   setVertex(3, 0, Offset(0.0f, 1.0f), sunshine)
   // ....
}
}

Box(
   modifier = modifier.padding(32.dp)
       .safeContentPadding()
       .aspectRatio(16 / 9f)
       .fillMaxWidth()
       .paint(gradientPainter)
)
"Mesh gradients and custom mesh rendering" ভিডিওর 03:24-এ Kotlin স্নিপেট
MeshGradientPainter(
   rows = 2,
   columns = 3,
   hasBicubicColor = true,
) {
   // Row 0 (top edge, y = 0.0f)
   setVertex(0, 0, Offset(0.0000f, 0.0000f), Color(0xFFFF6B6B))
   setVertex(0, 1, Offset(0.3300f, 0.0000f), Color(0xFFFFD6D6))
   setVertex(0, 2, Offset(0.6700f, 0.0000f), Color(0xFFE8C3FF))
   setVertex(0, 3, Offset(1.0000f, 0.0000f), Color(0xFFE396FF))

   // ... remaining vertices for Rows 0, 1, and 2
}
"Mesh gradients and custom mesh rendering" ভিডিওর 04:09-এ Kotlin স্নিপেট
val transition = rememberInfiniteTransition(label = "userInputGlowMesh")

val phase1 by transition.animateFloat(
   initialValue = 0f,
   targetValue = (2 * PI).toFloat(),
   animationSpec = infiniteRepeatable(
       animation = tween(durationMillis = 2800, easing = LinearEasing),
       repeatMode = RepeatMode.Restart,
   ),
   label = "glowPhase1",
)

val phase2 by transition.animateFloat(
   initialValue = 0f,
   targetValue = (2 * PI).toFloat(),
   animationSpec = infiniteRepeatable(
       animation = tween(durationMillis = 4200, easing = LinearEasing),
       repeatMode = RepeatMode.Restart,
   ),
   label = "glowPhase2",
)
"Mesh gradients and custom mesh rendering" ভিডিওর 04:33-এ Kotlin স্নিপেট
MeshGradientPainter(
   rows = 6,
   columns = 5,
   hasBicubicColor = true,
) {
   for (r in 0..6) {
       for (c in 0..5) {
               val offset = if (r in 1..5 && c in 1..4) {
               val ampX = when (r) {
                   1, 2 -> 0.048f
                   3 -> 0.042f
                   4 -> 0.028f
                   else -> 0.036f
               }
               val ampY = when (r) {
                   1, 2 -> 0.028f
                   3 -> 0.020f
                   4 -> 0.022f
                   else -> 0.016f
               }
               val d = orbit(c, r, ampX, ampY)
               Offset(
                   x = (baseU + d.x).coerceIn(0.04f, 0.96f),
                   y = (baseV + d.y).coerceIn(0.05f, 0.95f),
               )
           } else {
               Offset(baseU, baseV)
           }
           setVertex(r, c, offset, gridColors[r][c])
       }
   }
}
"Mesh gradients and custom mesh rendering" ভিডিওর 05:40-এ Kotlin স্নিপেট
val lines = remember {
        path.asAndroidPath().flatten(0.5f)
    }
//... inside draw scope
lines.forEach { line ->
//...
    drawLine(
        brush = Brush.linearGradient(...),
        start = Offset(line.start.x,
line.start.y),
        end = Offset(line.end.x, line.end.y),
        strokeWidth = 30f,
    )
//...
}
"Mesh gradients and custom mesh rendering" ভিডিওর 07:03-এ টেক্সট স্নিপেট
LineShaders.VERTEX

uniform float2 uStart;
uniform float2 uEnd;
uniform float uHalfWidth;
uniform float uTime;

Varyings main(const Attributes a) {
    Varyings v;
    float2 dir = uEnd - uStart;
    float2 normal = normalize(float2(-dir.y, dir.x));
    float along = a.corner.x * length(dir);
    float pulse = (a.corner.x - uTime) / 0.18;
    float radius = uHalfWidth * (1.0 + 0.6 * exp(-pulse * pulse));
    float across = a.corner.y * (radius + 1.0);
    v.position = uStart + dir * a.corner.x + normal * across;
    v.stroke = float3(along, across, radius);
    return v;
}
"Mesh gradients and custom mesh rendering" ভিডিওর 08:01-এ টেক্সট স্নিপেট
LineShaders.FRAGMENT

uniform float uLength;
layout(color) uniform float4 uStartColor;
layout(color) uniform float4 uEndColor;

float2 main(const Varyings v, out float4 color) {
    float distance = abs(v.stroke.y);
    float coverage = 1.0 - smoothstep(
    v.stroke.z - 0.5, v.stroke.z + 0.5, distance);
    float side = distance / v.stroke.z;
    float4 ink = mix(uStartColor, uEndColor,
        clamp(v.stroke.x / uLength + 0.4 * side * side, 0.0, 1.0));
    color = ink * coverage;
    return v.position;
}
"Mesh gradients and custom mesh rendering" ভিডিওর 08:37-এ Kotlin স্নিপেট
val spec = remember {
    MeshSpecification.make(
        arrayOf(Attribute(TYPE_FLOAT2, 0, "corner")),
        vertexStride = 8,
        arrayOf(Varying(TYPE_FLOAT3, "stroke")),
        LineShaders.VERTEX, LineShaders.FRAGMENT)
}
"Mesh gradients and custom mesh rendering" ভিডিওর 09:06-এ Kotlin স্নিপেট
fun lineCorners(segments: Int): ByteBuffer {
    val buffer = ByteBuffer.allocateDirect((segments + 1) * 2 * 8)
        .order(ByteOrder.nativeOrder())
    for (i in 0..segments) {
        val t = i / segments.toFloat()
        buffer.putFloat(t).putFloat(-1f) // one side
        buffer.putFloat(t).putFloat(1f)  // the other side
    }
    return buffer.rewind() as ByteBuffer
}
"Mesh gradients and custom mesh rendering" ভিডিওর 09:18-এ Kotlin স্নিপেট
val mesh = Mesh(
    spec,
    mode = Mesh.TRIANGLE_STRIP,
    vertexBuffer = corners,
    vertexCount = (segments + 1) * 2,
    bounds = RectF(0f, 0f, size.width, size.height)
)
"Mesh gradients and custom mesh rendering" ভিডিওর 09:24-এ Kotlin স্নিপেট
Box(Modifier.fillMaxSize().drawWithCache {
   val mesh = Mesh(...)
   onDrawBehind {
       mesh.setFloatUniform("uTime", time.value)
       drawIntoCanvas {
          it.nativeCanvas.drawMesh(mesh,android.graphics.BlendMode.DST, paint)
       }
   }
})
"ওয়াইড কালার গ্যামাট সাপোর্ট" ভিডিওর 11:22-এ XML স্নিপেট
<!-- AndroidManifest.xml -->
<activity
android:name=".NavActivity"
android:colorMode="wideColorGamut"
android:exported="true">
</activity>
"ওয়াইড কালার গ্যামাট সাপোর্ট" ভিডিওর ১১:২৪-এ Kotlin স্নিপেট
// Dynamic toggle in code
DisposableEffect(window) {
   window?.colorMode = ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT
   onDispose {
       window?.colorMode = ActivityInfo.COLOR_MODE_DEFAULT
   }
}
"ওয়াইড কালার গ্যামাট সাপোর্ট" ভিডিওর ১১:৪৯-এ Kotlin স্নিপেট
// 1. Display P3 Color (Wider gamut: greens & reds are more saturated) colorMode=wideColorGamut
val VividP3Red = Color(
   red = 1.0f,
   green = 0.05f,
   blue = 0.05f,
   alpha = 1.0f,
   colorSpace = ColorSpaces.DisplayP3
)
// 2. Extended sRGB (Enables HDR values > 1.0f) use colorMode=hdr
val HdrNeonCyan = Color(
   red = 0.0f,
   green = 1.8f,  // Peak brightness > 1.0 in extended gamut
   blue = 2.0f,
   alpha = 1.0f,
   colorSpace = ColorSpaces.ExtendedSrgb
)
// 3. BT.2020 HLG (Broadcast HDR standard, supported API 34+) - use colorMode=hdr
val HdrSunsetOrange = Color(
   red = 1.0f,
   green = 0.45f,
   blue = 0.1f,
   alpha = 1.0f,
   colorSpace = ColorSpaces.Bt2020Hlg
)
"Polygon shapes and morphing" ভিডিওর ১২:৪৫-এ Kotlin স্নিপেট
fun cookiePolygon(): RoundedPolygon {
    val vertices = FloatArray(32)
    for (i in 0 until 16) {
        val radius = if (i % 2 == 0) 1f else 0.84f
// …
    }
    return RoundedPolygon(
        vertices,
        rounding = CornerRounding(0.12f)
    )
}
"পলিগন শেপ ও মর্ফিং" ভিডিওর ১৩:১৫-এ Kotlin স্নিপেট
class RoundedPolygonShape(
 polygon: RoundedPolygonShape
) : Shape {
    // …
    override fun createOutline(size: Size, layoutDirection: LayoutDirection,
    density: Density,
    ): Outline {
        path = polygon.toPath().asComposePath()
        // …
        matrix.scale(size.width / maxDimension, size.height / maxDimension)
        matrix.translate(-bounds.left, -bounds.top)
        // …
        return Outline.Generic(path)
    }
}
"পলিগন শেপ ও মর্ফিং" ভিডিওর 13:30-এ Kotlin স্নিপেট
private val AvatarShape = PolygonShape {
    val vertices = List(16) { i ->
        val radius = size.minDimension / 2f * if (i % 2 == 0) 1f else 0.86f
        val angle = i * PI.toFloat() / 8f
        size.center + Offset(cos(angle), sin(angle)) * radius
    }
    polygon(vertices, rounding = CornerRounding(radius = 8.dp))
}

    // …
Modifier.clip(AvatarShape)
"Polygon shapes and morphing" ভিডিওর 13:53-এ Kotlin স্নিপেট
val recordShape = remember {
    MorphPolygonShape(
        start = PolygonShape.circle(),
        end = PolygonShape.star(
            numPoints = 9,
            innerRadiusRatio = 0.8f,
            outerRounding = CornerRounding(percent = 50),
        ),
    ) { shapeProgress.value }
}
"ব্লারিং এবং ব্যাকড্রপ রেন্ডার এফেক্ট" ভিডিওর 14:37-এ Kotlin স্নিপেট
Modifier
   .blur(
       radiusX = 10.dp,
       radiusY = 10.dp,
       edgeTreatment = BlurredEdgeTreatment(
           shape = RoundedCornerShape(8.dp),
       ),
   )
"ব্লারিং ও ব্যাকড্রপ রেন্ডার এফেক্ট" ভিডিওর ১৫:১৫-এ Kotlin স্নিপেট
Modifier.blur {
blurRadiusSpec = BlurRadiusSpec.verticalGradient(
listOf(
BlurStop(0.5f, 0.dp),
BlurStop(1.0f, 32.dp),
)
)
edgeTreatment = BlurredEdgeTreatment.Unbounded
}
"ব্লারিং ও ব্যাকড্রপ রেন্ডার এফেক্ট" ভিডিওর ১৫:৪২-এ Kotlin স্নিপেট
BlurRadiusSpec.horizontalGradient(
startRadius = 0.dp,
endRadius = sliderValue.dp,
)
"ব্লারিং এবং ব্যাকড্রপ রেন্ডার এফেক্ট" ভিডিওর 15:53-এ Kotlin স্নিপেট
BlurRadiusSpec.radialGradient(
center = center,
fallOffRadius = 300.dp,
startRadius = 0.dp,
endRadius = 24.dp,
)
"ব্লারিং এবং ব্যাকড্রপ রেন্ডার এফেক্ট" ভিডিওর ১৬:০৭-এ Kotlin স্নিপেট
val customShader = remember {
   RuntimeShader(
       """
       uniform float2 size;
       uniform float time;

       half4 main(float2 coords) {
          // Diagonal wave mask
          float normalizedX = coords.x / size.x;
          float normalizedY = coords.y / size.y;
          float wave = sin((normalizedX + normalizedY) * 6.0 + time) * 0.5 + 0.5;
          return half4(0.0, 0.0, 0.0, wave);
       }
       """.trimIndent()
   )
}

val customBlurRadius = remember(customShader) {
   BlurRadiusSpec.shader(maxRadius = 24.dp) { customShader }
}

Modifier.blur {
       val w = size.width.toPx()
       val h = size.height.toPx()
       customShader.setFloatUniform("size", w, h)
       customShader.setFloatUniform("time", animatedTime)
       blurRadiusSpec = customBlurRadius
   },
"ব্লারিং ও ব্যাকড্রপ রেন্ডার এফেক্ট" ভিডিওর ১৭:৪০-এ Kotlin স্নিপেট
fun Modifier.backdropBlur(spec: BlurRadiusSpec, shape: Shape, tint: Color): Modifier =
   this then BackdropBlurElement(spec, shape, tint)

private class BackdropBlurNode(...) : Modifier.Node(), DrawModifierNode {
   private var renderNode: RenderNode? = null

   override fun ContentDrawScope.draw() {
       val node = getOrCreateNode()
       if (isDirty || sizeChanged) {
           node.setPosition(0, 0, widthPx, heightPx)
           node.setBackdropRenderEffect(spec.createRenderEffect(this))
       }
       drawIntoCanvas { it.nativeCanvas.drawRenderNode(node) }
       drawContent()
   }
}
"ব্লারিং ও ব্যাকড্রপ রেন্ডার এফেক্ট" ভিডিওর ১৮:০৯-এ Kotlin স্নিপেট
JetchatAppBar(
    modifier = modifier
        .backdropBlur(
            tint = MaterialTheme.colorScheme
                .surface.copy(alpha = 0.5f),
            shape = RectangleShape,
            spec = BlurRadiusSpec.uniform(5.dp)
        )
)
"ব্লারিং ও ব্যাকড্রপ রেন্ডার এফেক্ট" ভিডিওর ১৮:৩০-এ Kotlin স্নিপেট
JetchatAppBar(
    modifier = modifier
        .backdropBlur(
            tint = MaterialTheme.colorScheme
                .surface.copy(alpha = 0.5f),
            shape = RectangleShape,
            spec = BlurRadiusSpec.verticalGradient(
                listOf(
                    BlurStop(0.5f, 32.dp),
                    BlurStop(1f, 0.dp),
                ),
            )
        )
)
"ব্লারিং ও ব্যাকড্রপ রেন্ডার এফেক্ট" ভিডিওর ১৯:১০-এ Kotlin স্নিপেট
val blur = RenderEffect.createBlurEffect(24f, 24f, Shader.TileMode.CLAMP)

// Boost saturation by 30% and apply blur together
val matrix = ColorMatrix().apply { setSaturation(1.3f) }
val vibrancy = RenderEffect.createColorFilterEffect(ColorMatrixColorFilter(matrix))
val vibrantBlur = RenderEffect.createChainEffect(vibrancy, blur)
"ব্লারিং ও ব্যাকড্রপ রেন্ডার এফেক্ট" ভিডিওর ১৯:২৩-এ Kotlin স্নিপেট
val agslShader = RuntimeShader(CHROMATIC_ABERRATION)
val noiseEffect = RenderEffect.createRuntimeShaderEffect(agslShader, "backdropInput")

val finalEffect = RenderEffect.createChainEffect(noiseEffect, vibrantBlur)
renderNode?.setBackdropRenderEffect(finalEffect)
"ব্লারিং ও ব্যাকড্রপ রেন্ডার এফেক্ট" ভিডিওর ২০:৫৩-এ Kotlin স্নিপেট
val region = RoundedRectBlurRegion().apply {
   bounds = RectF(left, top, right, bottom)
   setCornerRadii(spec.cornerRadiusPx.coerceAtLeast(0f))
   alpha = spec.alpha.coerceIn(0f, 1f)
   blurRadius = spec.blurRadiusPx.coerceAtLeast(0f)
}

surfaceView.setBlurRegions(listOf(region))

surfaceView.setBlurRegions(emptyList()) // Clear
"RuntimeColorFilter"-এ 21:33-এ Kotlin স্নিপেট
@Language("AGSL")
private const val CYBERPUNK_SHADER = """
    vec4 main(half4 in_color) {
        float lum = dot(in_color.rgb, vec3(0.299, 0.587, 0.114));
        vec3 shadow = vec3(0.06, 0.04, 0.25);
        vec3 mid = vec3(0.92, 0.12, 0.58);
        vec3 high = vec3(0.18, 0.94, 0.98);
        vec3 col = lum < 0.5 ? mix(shadow, mid, lum * 2.0) : mix(mid, high, (lum - 0.5) * 2.0);
        return vec4(mix(in_color.rgb, col, 0.7), in_color.a);
    }
"""
"RuntimeColorFilter"-এ 21:48-এ Kotlin স্নিপেট
AsyncImage(
    model = imageUri,
    contentDescription = filter.displayName,
    colorFilter = RuntimeColorFilter(CYBERPUNK_SHADER)
                   .asComposeColorFilter(),
    contentScale = ContentScale.Crop,
    modifier = Modifier.fillMaxSize(),
)
"গ্রাফিক্স পারফর্ম্যান্স অপ্টিমাইজেশন" ভিডিওর ২২:২১-এ Kotlin স্নিপেট
@Test
fun scrollFling() = measureScroll(swipeSteps = 5)

private fun measureScroll(swipeSteps: Int) = rule.measureRepeated(
    packageName = TARGET_PACKAGE,
    metrics = defaultComposeScrollingMetrics(),
    compilationMode = CompilationMode.Full(),
    iterations = 5,
    setupBlock = {
        killProcess()
    }
//...
"গ্রাফিক্স পারফর্ম্যান্স অপ্টিমাইজেশন" ভিডিওর 22:32-এ SQL স্নিপেট
select count(*) as late_frames
from actual_frame_timeline_slice
where upid = (select upid from process where name = 'com.example.compose.jetchat.perf')
  and jank_type like '%App Deadline Missed%'
"গ্রাফিক্স পারফর্ম্যান্স অপ্টিমাইজেশন" ভিডিওর ২৩:৫৪-এ Kotlin স্নিপেট
Image(
    painter = painterResource(it),
    contentScale = ContentScale.Fit,
    modifier = Modifier.size(160.dp),
    contentDescription = stringResource(id = R.string.attached_image),
)
"গ্রাফিক্স পারফর্ম্যান্স অপ্টিমাইজেশন" ভিডিওর ২৪:০২-এ Kotlin স্নিপেট
AsyncImage(
    model = it,
    contentScale = ContentScale.Fit,
    modifier = Modifier.size(160.dp),
    contentDescription = stringResource(id = R.string.attached_image),
)
"গ্রাফিক্স পারফর্ম্যান্স অপ্টিমাইজেশন" ভিডিওর ২৫:১৪-এ Kotlin স্নিপেট
val lensShader = RuntimeShader(MAGNIFIER_LENS_SHADER).apply {
    setFloatUniform("size", size.width, size.height)
    setFloatUniform("zoom", zoom)
// …
val lensEffect = RenderEffect.createRuntimeShaderEffect(lensShader, "content")
// …
val glassShader = RuntimeShader(GLASS_FINISH_SHADER).apply {
"গ্রাফিক্স পারফর্ম্যান্স অপ্টিমাইজেশন" ভিডিওর ২৫:৩২-এ Kotlin স্নিপেট
val lensShader = MagnifierShaders.lens.apply {
    setFloatUniform("size", size.width, size.height)
    setFloatUniform("zoom", zoom)
// …
private object MagnifierShaders {
    val lens: RuntimeShader by lazy(LazyThreadSafetyMode.NONE) { RuntimeShader(MAGNIFIER_LENS_SHADER) }
    val glassFinish: RuntimeShader by lazy(LazyThreadSafetyMode.NONE) { RuntimeShader(GLASS_FINISH_SHADER) }
"গ্রাফিক্স পারফর্ম্যান্স অপ্টিমাইজেশন" ভিডিওর 25:49-এ Kotlin স্নিপেট
Box(
    modifier = Modifier
        .matchParentSize()
        .drawBehind {
            // Behind the card, scaled past its bounds.
            translate(top = -58.dp.toPx()) {
                scale(scaleX = 1.38f, scaleY = 2.85f) {
                    with(glowMeshPainter) {
draw(size, alpha = glowAlpha)
}
"গ্রাফিক্স পারফর্ম্যান্স অপ্টিমাইজেশন" ভিডিওর ২৬:১৫-এ Kotlin স্নিপেট
Box(
    modifier = Modifier
        .matchParentSize()
                  .graphicsLayer()
        .drawBehind {
            // Behind the card, scaled past its bounds.
            translate(top = -58.dp.toPx()) {
                scale(scaleX = 1.38f, scaleY = 2.85f) {
                    with(glowMeshPainter) {
draw(size, alpha = glowAlpha)
}
"গ্রাফিক্স পারফর্ম্যান্স অপ্টিমাইজেশন" ভিডিওর ২৬:৩৭-এ শেল স্নিপেট
android skills add perfetto-trace-analysis
android skills add jetpack-compose-performance