কীভাবে করবেন

CameraX এবং Jetpack Compose ব্যবহার করে একটি স্পটলাইট এফেক্ট তৈরি করুন

৮ মিনিটের পাঠ
Jolanda Verhoef
ডেভেলপার সম্পর্ক প্রকৌশলী

হ্যালো! CameraX এবং Jetpack Compose-এর উপর আমাদের সিরিজে আপনাকে আবার স্বাগত জানাই। আগের পোস্টগুলিতে, আমরা ক্যামেরা প্রিভিউ সেট আপ করার মূল বিষয়গুলি নিয়ে আলোচনা করেছি এবং ট্যাপ-টু-ফোকাস কার্যকারিতা যুক্ত করেছি।

🧱 পর্ব ১ : নতুন ক্যামেরা-কম্পোজ আর্টিফ্যাক্ট ব্যবহার করে একটি বেসিক ক্যামেরা প্রিভিউ তৈরি করা। আমরা পারমিশন হ্যান্ডলিং এবং বেসিক ইন্টিগ্রেশন নিয়ে আলোচনা করেছি।

👆 পর্ব ২ : কম্পোজ জেসচার সিস্টেম, গ্রাফিক্স এবং কো-রুটিন ব্যবহার করে ভিজ্যুয়াল ট্যাপ-টু-ফোকাস বাস্তবায়ন।

🔦 পর্ব ৩ (এই পোস্ট): আরও সমৃদ্ধ ইউজার এক্সপেরিয়েন্সের জন্য আপনার ক্যামেরা প্রিভিউয়ের উপরে কীভাবে কম্পোজ UI এলিমেন্ট ওভারলে করবেন, তা নিয়ে আলোচনা।

📂 পর্ব ৪ : অ্যাডাপ্টিভ এপিআই এবং কম্পোজ অ্যানিমেশন ফ্রেমওয়ার্ক ব্যবহার করে ফোল্ডেবল ফোনে টেবিলটপ মোডে সাবলীলভাবে যাওয়া-আসা করা।

এই পোস্টে, আমরা আরও দৃষ্টিনন্দন একটি বিষয় নিয়ে আলোচনা করব — ফেস ডিটেকশনকে ভিত্তি করে আমাদের ক্যামেরা প্রিভিউয়ের উপরে একটি স্পটলাইট এফেক্ট প্রয়োগ করা। কেন, আপনি হয়তো ভাবছেন? আমি ঠিক নিশ্চিত নই। তবে এটা দেখতে সত্যিই দারুণ লাগে 🙂। এবং, আরও গুরুত্বপূর্ণ বিষয় হলো, এটি দেখায় যে কীভাবে আমরা সহজেই সেন্সর কোঅর্ডিনেটকে UI কোঅর্ডিনেটে রূপান্তর করতে পারি, যা আমাদের কম্পোজে সেগুলো ব্যবহার করার সুযোগ করে দেয়!

মুখ-শনাক্তকরণ.gif

মুখ শনাক্তকরণ সক্রিয় করুন

প্রথমে, ফেস ডিটেকশন চালু করার জন্য CameraPreviewViewModel-কে পরিবর্তন করতে হবে। আমরা Camera2Interop API ব্যবহার করব, যা আমাদেরকে CameraX থেকে অন্তর্নিহিত Camera2 API-এর সাথে ইন্টারঅ্যাক্ট করার সুযোগ দেয়। এর ফলে আমরা ক্যামেরার এমন সব ফিচার ব্যবহার করতে পারব যা CameraX সরাসরি প্রদান করে না। আমাদের নিম্নলিখিত পরিবর্তনগুলো করতে হবে:

  • একটি স্টেটফ্লো তৈরি করুন যাতে ফেস বাউন্ডগুলি Rect এর একটি তালিকা হিসাবে থাকে।
  • STATISTICS_FACE_DETECT_MODE ক্যাপচার রিকোয়েস্ট অপশনটি FULL-এ সেট করুন, যা ফেস ডিটেকশন সক্রিয় করে।
  • ক্যাপচার ফলাফল থেকে মুখের তথ্য পেতে একটি CaptureCallback সেট করুন।
class CameraPreviewViewModel : ViewModel() {
    ...
    private val _sensorFaceRects = MutableStateFlow(listOf<Rect>())
    val sensorFaceRects: StateFlow<List<Rect>> = _sensorFaceRects.asStateFlow()

    private val cameraPreviewUseCase = Preview.Builder()
        .apply {
            Camera2Interop.Extender(this)
                .setCaptureRequestOption(
                    CaptureRequest.STATISTICS_FACE_DETECT_MODE,
                    CaptureRequest.STATISTICS_FACE_DETECT_MODE_FULL
                )
                .setSessionCaptureCallback(object : CameraCaptureSession.CaptureCallback() {
                    override fun onCaptureCompleted(
                        session: CameraCaptureSession,
                        request: CaptureRequest,
                        result: TotalCaptureResult
                    ) {
                        super.onCaptureCompleted(session, request, result)
                        result.get(CaptureResult.STATISTICS_FACES)
                            ?.map { face -> face.bounds.toComposeRect() }
                            ?.toList()
                            ?.let { faces -> _sensorFaceRects.update { faces } }
                    }
                })
        }
        .build().apply {
    ...
}

এই পরিবর্তনগুলো কার্যকর করার ফলে, আমাদের ভিউ মডেল এখন সেন্সর স্থানাঙ্কে শনাক্তকৃত মুখমণ্ডলগুলোর বাউন্ডিং বক্স উপস্থাপনকারী Rect অবজেক্টের একটি তালিকা নির্গত করে।

সেন্সর স্থানাঙ্ককে UI স্থানাঙ্কে রূপান্তর করুন

গত বিভাগে আমরা শনাক্ত করা মুখমণ্ডলের যে বাউন্ডিং বক্সগুলো সংরক্ষণ করেছিলাম, সেগুলো সেন্সর কোঅর্ডিনেট সিস্টেমের স্থানাঙ্ক ব্যবহার করে। আমাদের UI-তে বাউন্ডিং বক্সগুলো আঁকার জন্য, এই স্থানাঙ্কগুলোকে এমনভাবে রূপান্তর করতে হবে যাতে সেগুলো কম্পোজ কোঅর্ডিনেট সিস্টেমে সঠিক হয়। আমাদের যা করতে হবে তা হলো:

  • সেন্সর স্থানাঙ্কগুলিকে প্রিভিউ বাফার স্থানাঙ্কে রূপান্তর করুন
  • প্রিভিউ বাফার স্থানাঙ্কগুলিকে কম্পোজ UI স্থানাঙ্কে রূপান্তর করুন

এই রূপান্তরগুলো রূপান্তর ম্যাট্রিক্স ব্যবহার করে করা হয়। প্রতিটি রূপান্তরের নিজস্ব ম্যাট্রিক্স রয়েছে:

  • আমাদের SurfaceRequest একটি TransformationInfo ইনস্ট্যান্স ধারণ করে, যার মধ্যে একটি sensorToBufferTranform ম্যাট্রিক্স থাকে।
  • আমাদের CameraXViewfinder সাথে একটি CoordinateTransformer যুক্ত আছে। আপনাদের হয়তো মনে আছে যে, আমরা আগের ব্লগ পোস্টে ট্যাপ-টু-ফোকাস স্থানাঙ্ক রূপান্তর করার জন্য এই ট্রান্সফর্মারটি ব্যবহার করেছিলাম।

আমরা একটি সহায়ক মেথড তৈরি করতে পারি যা আমাদের জন্য রূপান্তরটি করে দেবে:

private fun List<Rect>.transformToUiCoords(
    transformationInfo: SurfaceRequest.TransformationInfo?,
    uiToBufferCoordinateTransformer: MutableCoordinateTransformer
): List<Rect> = this.map { sensorRect ->
    val bufferToUiTransformMatrix = Matrix().apply {
        setFrom(uiToBufferCoordinateTransformer.transformMatrix)
        invert()
    }

    val sensorToBufferTransformMatrix = Matrix().apply {
        transformationInfo?.let {
            setFrom(it.sensorToBufferTransform)
        }
    }

    val bufferRect = sensorToBufferTransformMatrix.map(sensorRect)
    val uiRect = bufferToUiTransformMatrix.map(bufferRect)

    uiRect
}
  • আমরা শনাক্তকৃত মুখগুলির তালিকা ধরে পুনরাবৃত্তি করি এবং প্রতিটি মুখের জন্য রূপান্তরটি সম্পাদন করি।
  • আমাদের CameraXViewfinder থেকে প্রাপ্ত CoordinateTransformer.transformMatrix ডিফল্টরূপে UI স্থানাঙ্ককে বাফার স্থানাঙ্কে রূপান্তর করে। আমাদের ক্ষেত্রে, আমরা চাই ম্যাট্রিক্সটি এর বিপরীতভাবে কাজ করুক, অর্থাৎ বাফার স্থানাঙ্ককে UI স্থানাঙ্কে রূপান্তর করুক। এজন্য, আমরা ম্যাট্রিক্সটিকে ইনভার্ট করার জন্য invert() মেথডটি ব্যবহার করি।
  • আমরা প্রথমে sensorToBufferTransformMatrix ব্যবহার করে মুখমণ্ডলকে সেন্সর স্থানাঙ্ক থেকে বাফার স্থানাঙ্কে রূপান্তর করি এবং তারপর bufferToUiTransformMatrix ব্যবহার করে সেই বাফার স্থানাঙ্কগুলিকে UI স্থানাঙ্কে রূপান্তর করি।

স্পটলাইট এফেক্ট প্রয়োগ করুন

এখন, স্পটলাইট এফেক্টটি আঁকার জন্য CameraPreviewContent কম্পোজেবলটি আপডেট করা যাক। আমরা প্রিভিউয়ের উপর একটি গ্রেডিয়েন্ট মাস্ক আঁকতে একটি Canvas কম্পোজেবল ব্যবহার করব, যা শনাক্ত করা মুখগুলোকে দৃশ্যমান করে তুলবে:

@Composable
fun CameraPreviewContent(
    viewModel: CameraPreviewViewModel,
    modifier: Modifier = Modifier,
    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current
) {
    val surfaceRequest by viewModel.surfaceRequest.collectAsStateWithLifecycle()
    val sensorFaceRects by viewModel.sensorFaceRects.collectAsStateWithLifecycle()
    val transformationInfo by
        produceState<SurfaceRequest.TransformationInfo?>(null, surfaceRequest) {
            try {
                surfaceRequest?.setTransformationInfoListener(Runnable::run) { transformationInfo ->
                    value = transformationInfo
                }
                awaitCancellation()
            } finally {
                surfaceRequest?.clearTransformationInfoListener()
            }
        }
    val shouldSpotlightFaces by remember {
        derivedStateOf { sensorFaceRects.isNotEmpty() && transformationInfo != null} 
    }
    val spotlightColor = Color(0xDDE60991)
    ..

    surfaceRequest?.let { request ->
        val coordinateTransformer = remember { MutableCoordinateTransformer() }
        CameraXViewfinder(
            surfaceRequest = request,
            coordinateTransformer = coordinateTransformer,
            modifier = ..
        )

        AnimatedVisibility(shouldSpotlightFaces, enter = fadeIn(), exit = fadeOut()) {
            Canvas(Modifier.fillMaxSize()) {
                val uiFaceRects = sensorFaceRects.transformToUiCoords(
                    transformationInfo = transformationInfo,
                    uiToBufferCoordinateTransformer = coordinateTransformer
                )

                // Fill the whole space with the color
                drawRect(spotlightColor)
                // Then extract each face and make it transparent

                uiFaceRects.forEach { faceRect ->
                    drawRect(
                        Brush.radialGradient(
                            0.4f to Color.Black, 1f to Color.Transparent,
                            center = faceRect.center,
                            radius = faceRect.minDimension * 2f,
                        ),
                        blendMode = BlendMode.DstOut
                    )
                }
            }
        }
    }
}

এটি যেভাবে কাজ করে:

  • আমরা ভিউ মডেল থেকে মুখমণ্ডলগুলোর তালিকা সংগ্রহ করি।
  • শনাক্ত হওয়া মুখমণ্ডলের তালিকা প্রতিবার পরিবর্তিত হওয়ার সাথে সাথে যাতে পুরো স্ক্রিনটি নতুন করে সাজানো না হয়, তা নিশ্চিত করতে আমরা derivedStateOf ব্যবহার করি এটা নজরে রাখার জন্য যে আদৌ কোনো মুখমণ্ডল শনাক্ত হয়েছে কি না। এরপর রঙিন ওভারলেটিকে ভেতরে ও বাইরে অ্যানিমেট করার জন্য এটিকে AnimatedVisibility সাথে ব্যবহার করা যায়।
  • SurfaceRequest.TransformationInfo তে সেন্সর স্থানাঙ্ককে বাফার স্থানাঙ্কে রূপান্তর করার জন্য প্রয়োজনীয় তথ্য surfaceRequest মধ্যে থাকে। আমরা সারফেস রিকোয়েস্টে একটি লিসেনার সেট আপ করতে produceState ফাংশনটি ব্যবহার করি, এবং যখন কম্পোজেবলটি কম্পোজিশন ট্রি থেকে বেরিয়ে যায় তখন এই লিসেনারটি ক্লিয়ার করে দিই।
  • আমরা একটি Canvas ব্যবহার করে পুরো স্ক্রিন জুড়ে একটি স্বচ্ছ গোলাপি আয়তক্ষেত্র আঁকি।
  • আমরা Canvas ড্র ব্লকের ভিতরে না আসা পর্যন্ত sensorFaceRects ভেরিয়েবলটির রিডিং স্থগিত রাখি। তারপর আমরা স্থানাঙ্কগুলিকে UI স্থানাঙ্কে রূপান্তর করি।
  • আমরা শনাক্ত করা মুখগুলোর ওপর পুনরাবৃত্তি করি এবং প্রতিটি মুখের জন্য একটি রেডিয়াল গ্রেডিয়েন্ট আঁকি, যা মুখের আয়তক্ষেত্রের ভেতরটাকে স্বচ্ছ করে তুলবে।
  • স্পটলাইট ইফেক্ট তৈরি করার জন্য, আমরা গোলাপি আয়তক্ষেত্র থেকে গ্রেডিয়েন্টটি ছেঁটে ফেলা নিশ্চিত করতে BlendMode.DstOut ব্যবহার করি।

দ্রষ্টব্য: আপনি যখন ক্যামেরাটি DEFAULT_FRONT_CAMERA তে পরিবর্তন করবেন , তখন লক্ষ্য করবেন যে স্পটলাইটটি প্রতিবিম্বিত (mirrored) হয়ে গেছে! এটি একটি জ্ঞাত সমস্যা, যা গুগল ইস্যু ট্র্যাকারে (Google Issue Tracker) ট্র্যাক করা হচ্ছে

ফলাফল

এই কোডটির সাহায্যে আমরা একটি সম্পূর্ণ কার্যকরী স্পটলাইট ইফেক্ট তৈরি করতে পারি, যা শনাক্ত করা মুখমণ্ডলগুলোকে হাইলাইট করে। সম্পূর্ণ কোড স্নিপেটটি আপনি এখানে খুঁজে পাবেন।

এই এফেক্টটি তো কেবল শুরু — কম্পোজ-এর শক্তি ব্যবহার করে আপনি অগণিত দৃষ্টিনন্দন ক্যামেরা অভিজ্ঞতা তৈরি করতে পারেন। সেন্সর এবং বাফার কোঅর্ডিনেটকে কম্পোজ UI কোঅর্ডিনেটে এবং আবার আগের অবস্থায় ফিরিয়ে আনার ক্ষমতার অর্থ হলো, আমরা কম্পোজ UI-এর সমস্ত ফিচার ব্যবহার করতে পারি এবং সেগুলোকে অন্তর্নিহিত ক্যামেরা সিস্টেমের সাথে নির্বিঘ্নে একীভূত করতে পারি। অ্যানিমেশন, উন্নত UI গ্রাফিক্স, সহজ UI স্টেট ম্যানেজমেন্ট এবং সম্পূর্ণ জেসচার কন্ট্রোলের মাধ্যমে আপনার কল্পনারই কোনো সীমা নেই!

এই সিরিজের শেষ পোস্টে, আমরা বিস্তারিতভাবে আলোচনা করব কিভাবে অ্যাডাপ্টিভ এপিআই এবং কম্পোজ অ্যানিমেশন ফ্রেমওয়ার্ক ব্যবহার করে ফোল্ডেবল ডিভাইসের বিভিন্ন ক্যামেরা ইউআই-এর মধ্যে নির্বিঘ্নে পরিবর্তন আনা যায়। সাথেই থাকুন!


এই ব্লগের কোড স্নিপেটগুলোর নিম্নলিখিত লাইসেন্স রয়েছে:

// Copyright 2024 Google LLC. SPDX-License-Identifier: Apache-2.0

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

লিখেছেন:

পড়তে থাকুন