হ্যালো! CameraX এবং Jetpack Compose-এর উপর আমাদের সিরিজে আপনাকে আবার স্বাগত জানাই। আগের পোস্টগুলিতে, আমরা ক্যামেরা প্রিভিউ সেট আপ করার মূল বিষয়গুলি নিয়ে আলোচনা করেছি এবং ট্যাপ-টু-ফোকাস কার্যকারিতা যুক্ত করেছি।
🧱 পর্ব ১ : নতুন ক্যামেরা-কম্পোজ আর্টিফ্যাক্ট ব্যবহার করে একটি বেসিক ক্যামেরা প্রিভিউ তৈরি করা। আমরা পারমিশন হ্যান্ডলিং এবং বেসিক ইন্টিগ্রেশন নিয়ে আলোচনা করেছি।
👆 পর্ব ২ : কম্পোজ জেসচার সিস্টেম, গ্রাফিক্স এবং কো-রুটিন ব্যবহার করে ভিজ্যুয়াল ট্যাপ-টু-ফোকাস বাস্তবায়ন।
🔦 পর্ব ৩ (এই পোস্ট): আরও সমৃদ্ধ ইউজার এক্সপেরিয়েন্সের জন্য আপনার ক্যামেরা প্রিভিউয়ের উপরে কীভাবে কম্পোজ UI এলিমেন্ট ওভারলে করবেন, তা নিয়ে আলোচনা।
📂 পর্ব ৪ : অ্যাডাপ্টিভ এপিআই এবং কম্পোজ অ্যানিমেশন ফ্রেমওয়ার্ক ব্যবহার করে ফোল্ডেবল ফোনে টেবিলটপ মোডে সাবলীলভাবে যাওয়া-আসা করা।
এই পোস্টে, আমরা আরও দৃষ্টিনন্দন একটি বিষয় নিয়ে আলোচনা করব — ফেস ডিটেকশনকে ভিত্তি করে আমাদের ক্যামেরা প্রিভিউয়ের উপরে একটি স্পটলাইট এফেক্ট প্রয়োগ করা। কেন, আপনি হয়তো ভাবছেন? আমি ঠিক নিশ্চিত নই। তবে এটা দেখতে সত্যিই দারুণ লাগে 🙂। এবং, আরও গুরুত্বপূর্ণ বিষয় হলো, এটি দেখায় যে কীভাবে আমরা সহজেই সেন্সর কোঅর্ডিনেটকে UI কোঅর্ডিনেটে রূপান্তর করতে পারি, যা আমাদের কম্পোজে সেগুলো ব্যবহার করার সুযোগ করে দেয়!

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

কীভাবে করবেন
এই আর্টিকেলে আপনি শিখবেন, নির্দিষ্ট শর্ত পূরণ হওয়া পর্যন্ত অপেক্ষা করার জন্য Compose-এ কীভাবে waitUntil test API ব্যবহার করতে হয়।
Jose Alcérreca • 3 মিনিট পড়া

কীভাবে করবেন
আপনি অ্যান্ড্রয়েড স্টুডিওতে জেমিনি, জেমিনি সিএলআই, অ্যান্টিগ্র্যাভিটি, অথবা ক্লড কোড বা কোডেক্সের মতো থার্ড-পার্টি এজেন্ট ব্যবহার করুন না কেন, আমাদের লক্ষ্য হলো সর্বত্র উচ্চমানের অ্যান্ড্রয়েড ডেভেলপমেন্ট নিশ্চিত করা।
Adarsh Fernando , Esteban de la Canal • 4 মিনিট পড়া

কীভাবে করবেন
অ্যান্ড্রয়েড ব্যবহারকারীদের জন্য অতিরিক্ত ব্যাটারি খরচ একটি প্রধান উদ্বেগের বিষয়, এটা উপলব্ধি করে গুগল ডেভেলপারদের আরও শক্তি-সাশ্রয়ী অ্যাপ তৈরিতে সাহায্য করার জন্য গুরুত্বপূর্ণ পদক্ষেপ নিচ্ছে।
Alice Yuan • পড়তে ৮ মিনিট
আপ-টু-ডেট থাকুন
অ্যান্ড্রয়েড ডেভেলপমেন্টের সর্বশেষ তথ্য প্রতি সপ্তাহে আপনার ইনবক্সে পান।






