আপনি যদি কখনো আন্ড্রয়েড এর ক্যামেরা নিয়ে কাজ করে থাকেন তাহলে আপনি হয়তো এর পীড়াদায়ক ব্যাপারগুলো সম্পর্কে অবগত আছেন। আজকে আমাদের আলোচনার বিষয় হচ্ছে Camera2 API এর ইমেজ রোটেশন জনিত সমস্যা।

Camera2 API কী?

Camera API এর মাধ্যমে অ্যান্ড্রয়েডের ক্যামেরা হার্ডওয়্যার ব্যবহার করা যায়। Camera2 API এর আগমন ঘটেছে আন্ড্রয়েড ক্যামেরা হার্ডওয়্যার এর উপর ক্যামেরা অ্যাপ ডেভেলপারদের অধিক নিয়ন্ত্রণ দেয়ার জন্য। Android API লেভেল 21 অর্থাৎ Android 5.0 (ললিপপ) সহ পরবর্তী সকল অ্যান্ড্রয়েড ডিভাইস Camera2 API সাপোর্টেড।

Camera2 API এর image rotation সমস্যা

visual representation of a developer working with android camera

Google কর্তৃক Camera2 API এ বিভিন্ন নতুন ফিচার যোগ করা হলেও Camera2 API ব্যবহার করে প্রাপ্ত তথ্যাদি সব ক্ষেত্রে নির্ভরযোগ্য নয়। এর কারণ মূলত ভিন্ন ভিন্ন ডিভাইস প্রস্তুতকারকদের ভিন্ন ভিন্ন উপায়ে ক্যামেরা ইমপ্লিমেন্টেশন, যা সব ক্ষেত্রে Camera2 API এর স্ট্যান্ডার্ড মেনে চলে না। এর ফলে একই প্রোগ্রাম এক ডিভাইসে portrait এ এবং আরেক ডিভাইসে landscape এ ছবি তোলে, যা ডেভেলপার এর জন্য দ্বিধার কারণ হতে পারে। ছবি তোলার পূর্বে ও পরে কীরূপ দেখায় তার উদাহরণ নিচে দেয়া হল -

চিত্রঃ সমাধান প্রয়োগের পূর্বে
capture screenshotImage Capture Screenshot preview screenshotCaptured Image Preview Screenshot

এখানে দেখা যাচ্ছে যে ছবি তোলার পূর্বে ঠিকমত দেখা গেলেও ছবি তোলার পরের আচরণ যেমনটি হওয়া দরকার ছিল তা নয়। ছবিটি এক দিকে ঘুরে গিয়েছে। মূলত 90 degree clockwise rotate করেছে, যা মোটেও কাম্য নয়।

CaptureRequest.JPEG_ORIENTATION দিয়ে সমাধান

ছবি তোলার জন্য একটি CaptureRequest তৈরি করতে হয়, যা আমরা CaptureRequest.Builder দিয়ে করি।

CaptureRequest.Builder captureRequestBuilder =
        mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);

এখানে mCamera হল CameraDevice। স্থির চিত্র গ্রহণের জন্য target হিসেবে আমাদের ImageReader surface যোগ করতে হবে।

captureRequestBuilder.addTarget(mImageReader.getSurface());

captureRequestBuilder এর সাথে আমাদের প্রয়োজন মতো আরও নানাবিধ প্যারামিটার আমরা যোগ করতে পারি নিম্নরূপেঃ

captureRequestBuilder.set(
        CaptureRequest.CONTROL_AF_MODE,
        mPreviewRequestBuilder.get(CaptureRequest.CONTROL_AF_MODE)
);

এর সাহায্যে আমরা auto flash সেট করে নিলাম। এমনি ভাবে আমরা CaptureRequest এ JPEG_ORIENTATION সেট করতে পারি। সাধারণত একটি upright বা portrait ছবি পেতে আমরা JPEG_ORIENTATION হিসেবে 0 ডিগ্রি সেট করে থাকি।

captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, 0);

কিন্তু সব ডিভাইসে এই 0 ডিগ্রি সেট করাই যথেষ্ট নয়। Device rotation এবং Camera sensor হতে প্রাপ্ত তথ্যের উপর নির্ভর করে ডিভাইস ভেদে এই মান পরিবর্তন করতে হবে। ধরে নেই, আমরা এই মান jpegOrientation সেট করবো।

captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, jpegOrientation);

তাহলে প্রশ্ন হল এই jpegOrientation এর মান আমরা কীভাবে নির্ধারণ করবো?

প্রথমেই আমরা deviceRotation হিসেব করে ফেলি।

int deviceRotation = getDeviceRotation();

getDeviceRotation() ফাংশনটিতে WindowManager এর সাহায্য নিয়ে Display থেকে rotation হিসাব করা যেতে পারে।

int getDeviceRotation () {
    WindowManager wm = 
        (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    Display display = wm.getDefaultDisplay();
    return display.getRotation();
}

deviceRotation থেকে আমরা surfaceRotation বের করবো। এটাকে আমরা এভাবে চিন্তা করতে পারি যে 90 degree orientation এ নিতে deviceRotation এর সাথে clockwise কত ডিগ্রি যোগ করা লাগবে। 0, 90, 180, 270 এর ক্ষেত্রে যা যথাক্রমে 90, 0, 270, 180।

private static final SparseIntArray ORIENTATIONS = new SparseIntArray(4);

static {
    ORIENTATIONS.append(Surface.ROTATION_0, 90);
    ORIENTATIONS.append(Surface.ROTATION_90, 0);
    ORIENTATIONS.append(Surface.ROTATION_180, 270);
    ORIENTATIONS.append(Surface.ROTATION_270, 180);
}

এখন deviceRotation এর দ্বারা নির্ণীত surfaceRotation এর মান নিম্নরূপ।

int surfaceRotation = ORIENTATIONS.get(deviceRotation);

এবার আমাদের প্রয়োজন ক্যামেরা সেন্সর এর ORIENTATION তথ্য যা আমরা CameraCharacteristics থেকে জানবো।

int sensorOrientation = mCameraCharacteristics.get(
        CameraCharacteristics.SENSOR_ORIENTATION);

অবশেষে আমরা আমাদের কাঙ্ক্ষিত jpegOrientation এর মান বের করতে প্রস্তুত।

int jpegOrientation = (surfaceRotation + sensorOrientation + 270) % 360;

এই jpegOrientation কে আমরা CaptureRequest.JPEG_ORIENTATION এ সেট করবো।

captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, jpegOrientation);

এভাবে jpegOrientation হিসেব করার পরের ফলাফল -

চিত্রঃ সমাধান প্রয়োগের পরে
capture screenshotImage Capture Screenshot preview screenshotCaptured Image Preview Screenshot

এই পদ্ধতি ব্যবহার করে ছবি তোলার পর Preview এর Orientation জনিত সমস্যা আর হয়নি এবং তা আশানুরূপ উপায়ে upright বা portrait অবস্থায় দেখতে পাওয়া যাচ্ছে।

অন্যান্য পদ্ধতিতে সমাধান

অ্যান্ড্রয়েড ডকুমেন্টেশনে jpegOrientation বের করার লজিক ভিন্ন ধাঁচের। সেখানে ব্যবহৃত প্রক্রিয়াটি হলঃ

private int getJpegOrientation(CameraCharacteristics c, int deviceOrientation) {
    if (deviceOrientation ==  android.view.OrientationEventListener.ORIENTATION_UNKNOWN) 
            return 0;

    int sensorOrientation = c.get(CameraCharacteristics.SENSOR_ORIENTATION);

    // Round device orientation to a multiple of 90
    deviceOrientation = (deviceOrientation + 45) / 90 * 90;

    // Reverse device orientation for front-facing cameras
    boolean facingFront = c.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT;

    if (facingFront) deviceOrientation = -deviceOrientation;

    // Calculate desired JPEG orientation relative to camera orientation to make
    // the image upright relative to the device orientation
    int jpegOrientation = (sensorOrientation + deviceOrientation + 360) % 360;

    return jpegOrientation;
 }

প্রক্রিয়াটি জটিল এবং অনেক ডিভাইস এর ক্ষেত্রেই তা কাজ করে না। কেননা, কেবল সেন্সর থেকে প্রাপ্ত তথ্যের উপর নির্ভর করাই যথেষ্ট নয়। এখানে surfaceRotation তথা deviceRotation বিবেচনা করা হয়নি।

ExifInterface.TAG_ORIENTATION ব্যবহার করা আরেকটি ভালো সমাধান। কিন্তু exif data কতিপয় ডিভাইসে Camera2 API এর মাধ্যমে বের করতে গেলে তা null রিটার্ন করতে পারে।

bad-time-meme

ইতিকথা

অ্যান্ড্রয়েড ক্যামেরা নিয়ে কাজ করতে গিয়ে পদে পদে আমার এই একই কারণে সমস্যার সম্মুখীন হতে হয়েছে। শেষমেষ Brenda Cook এর বাতলে দেয়া এই পদ্ধতিতে আমার সমস্যার সমাধান হয়েছে। আশা করি আপনাদেরও কাজে দিবে।