ফটো তোলা

এই পৃষ্ঠাটি ক্যামেরা ক্লাসকে রেফার করে, যা বন্ধ করা হয়েছে। আমরা CameraX অথবা নির্দিষ্ট ব্যবহারের ক্ষেত্রে Camera2 ব্যবহার করার সাজেশন দিই। CameraX এবং Camera2, Android 5.0 (API লেভেল ২১) এবং তার পরবর্তী যেকোনও ভার্সনে কাজ করে।

Camera1 থেকে CameraX-এ কীভাবে মাইগ্রেট করবেন তা জানতে, Camera1 থেকে CameraX-এ মাইগ্রেট করুন দেখুন। বিকল্প হিসেবে, আপনি Camera1 থেকে CameraX-এ মাইগ্রেট করার জন্য এজেন্টিক স্কিল ব্যবহার করতে পারেন।

এই টিউটোরিয়াল থেকে আপনি শিখতে পারবেন কীভাবে ডিভাইসের অন্য ক্যামেরা অ্যাপে কাজ ডেলিগেট করে ফটো ক্যাপচার করতে হয়। (আপনি যদি নিজের ক্যামেরা ফাংশনালিটি তৈরি করতে চান, তাহলে ক্যামেরা কন্ট্রোল করা দেখুন।)

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

ক্যামেরা ফিচারের জন্য অনুরোধ করুন

আপনার অ্যাপ্লিকেশনের একটি অপরিহার্য ফাংশন যদি ছবি তোলা হয়, তাহলে Google Play-তে এর দৃশ্যমানতা ক্যামেরা আছে এমন ডিভাইসে সীমাবদ্ধ করুন। আপনার অ্যাপ্লিকেশন ক্যামেরা থাকার উপর নির্ভর করে তা বিজ্ঞাপন দিতে, আপনার ম্যানিফেস্ট ফাইলে <uses-feature> ট্যাগ যোগ করুন:

<manifest ... >
    <uses-feature android:name="android.hardware.camera"
                  android:required="true" />
    ...
</manifest>

আপনার অ্যাপ্লিকেশন যদি ক্যামেরা ব্যবহার করে, কিন্তু কাজ করার জন্য ক্যামেরার প্রয়োজন না হয়, তাহলে android:required-এর পরিবর্তে false সেট করুন। এটি করলে, Google Play ক্যামেরা নেই এমন ডিভাইসকেও আপনার অ্যাপ্লিকেশন ডাউনলোড করার অনুমতি দেবে। তারপর, রানটাইমে ক্যামেরা উপলভ্য আছে কিনা তা চেক করার দায়িত্ব আপনার। hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)-এ কল করে এটি চেক করতে পারেন। ক্যামেরা উপলভ্য না থাকলে, আপনার ক্যামেরা ফিচার বন্ধ করে দেওয়া উচিত।

থাম্বনেল পান

আপনার অ্যাপের উদ্দেশ্য যদি শুধু ফটো তোলা না হয়, তাহলে আপনি সম্ভবত ক্যামেরা অ্যাপ্লিকেশন থেকে ছবিটি পেতে এবং সেটি নিয়ে কিছু করতে চান।

Android ক্যামেরা অ্যাপ্লিকেশন রিটার্নে ফটো এনকোড করে Intent যা onActivityResult()-এ ডেলিভার করা হয় এক্সট্রাতে ছোট Bitmap হিসেবে, "data" কী-এর অধীনে। নিম্নলিখিত কোড এই ছবি ফিরিয়ে আনে এবং এটি একটি ImageView-এ দেখায়।

Kotlin

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
        val imageBitmap = data.extras.get("data") as Bitmap
        imageView.setImageBitmap(imageBitmap)
    }
}

জাভা

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
        Bundle extras = data.getExtras();
        Bitmap imageBitmap = (Bitmap) extras.get("data");
        imageView.setImageBitmap(imageBitmap);
    }
}

মনে রাখবেন: "data" থেকে নেওয়া এই থাম্বনেল ছবিটি আইকনের জন্য ভালো হতে পারে, তবে এর থেকে বেশি কিছু নয়। ফুল-সাইজ ছবি নিয়ে কাজ করতে একটু বেশি সময় লাগে।

ফুল-সাইজ ফটো সেভ করুন

আপনি Android ক্যামেরা অ্যাপ্লিকেশনকে সেভ করার জন্য কোনও ফাইল দিলে, এটি সম্পূর্ণ সাইজের ফটো সেভ করে। ক্যামেরা অ্যাপ যেখানে ফটো সেভ করবে সেখানে আপনাকে অবশ্যই ফাইলের সম্পূর্ণ নাম দিতে হবে।

সাধারণত, ব্যবহারকারী ডিভাইসের ক্যামেরা দিয়ে যেসব ফটো তোলেন সেগুলি ডিভাইসের পাবলিক এক্সটার্নাল স্টোরেজে সেভ করা উচিত যাতে সব অ্যাপ সেগুলি অ্যাক্সেস করতে পারে। শেয়ার করা ফটোর জন্য সঠিক ডিরেক্টরি getExternalStoragePublicDirectory() প্রদান করে, DIRECTORY_PICTURES আর্গুমেন্টের সাথে। এই পদ্ধতির মাধ্যমে প্রদান করা ডিরেক্টরি সব অ্যাপের মধ্যে শেয়ার করা হয়। Android 9 (API লেভেল ২৮) এবং তার আগের ভার্সনে, এই ডিরেক্টরিতে রিড ও রাইট করার জন্য যথাক্রমে READ_EXTERNAL_STORAGE এবং WRITE_EXTERNAL_STORAGE অনুমতি প্রয়োজন:

<manifest ...>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    ...
</manifest>

Android 10 (API লেভেল ২৯) এবং তার পরবর্তী ভার্সনে, ফটো শেয়ার করার জন্য সঠিক ডিরেক্টরি হল MediaStore.Images টেবিল। আপনার অ্যাপের যদি শুধুমাত্র ব্যবহারকারীর তোলা ফটো অ্যাক্সেস করার প্রয়োজন হয়, তাহলে আপনাকে কোনও স্টোরেজ অনুমতি ঘোষণা করতে হবে না।

তবে, আপনি যদি চান যে ফটো শুধুমাত্র আপনার অ্যাপের জন্য ব্যক্তিগত থাকুক, তাহলে আপনি এর পরিবর্তে Context.getExternalFilesDir()-এর প্রদান করা ডিরেক্টরি ব্যবহার করতে পারেন। Android 4.3 এবং তার আগের ভার্সনে, এই ডিরেক্টরিতে লেখার জন্য WRITE_EXTERNAL_STORAGE অনুমতিও প্রয়োজন। Android 4.4 থেকে শুরু করে, এই অনুমতির আর প্রয়োজন নেই কারণ ডিরেক্টরি অন্য অ্যাপ অ্যাক্সেস করতে পারে না, তাই আপনি ঘোষণা করতে পারেন যে অনুমতির জন্য শুধু Android-এর পুরনো ভার্সনে অনুরোধ করা উচিত, এর জন্য maxSdkVersion অ্যাট্রিবিউট যোগ করতে হবে:

<manifest ...>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                     android:maxSdkVersion="28" />
    ...
</manifest>

মনে রাখবেন: ব্যবহারকারী আপনার অ্যাপ আনইনস্টল করলে, getExternalFilesDir() বা getFilesDir()-এর দেওয়া ডিরেক্টরিতে সেভ করা ফাইল মুছে যায়।

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

Kotlin

lateinit var currentPhotoPath: String

@Throws(IOException::class)
private fun createImageFile(): File {
    // Create an image file name
    val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
    val storageDir: File = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
    return File.createTempFile(
            "JPEG_${timeStamp}_", /* prefix */
            ".jpg", /* suffix */
            storageDir /* directory */
    ).apply {
        // Save a file: path for use with ACTION_VIEW intents
        currentPhotoPath = absolutePath
    }
}

জাভা

String currentPhotoPath;

private File createImageFile() throws IOException {
    // Create an image file name
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    String imageFileName = "JPEG_" + timeStamp + "_";
    File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
    File image = File.createTempFile(
        imageFileName,  /* prefix */
        ".jpg",         /* suffix */
        storageDir      /* directory */
    );

    // Save a file: path for use with ACTION_VIEW intents
    currentPhotoPath = image.getAbsolutePath();
    return image;
}

ফটোটির জন্য ফাইল তৈরি করার জন্য এই পদ্ধতিটি উপলভ্য থাকায়, আপনি এখন এইভাবে Intent তৈরি এবং কল করতে পারেন:

Kotlin

private fun dispatchTakePictureIntent() {
    Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
        // Ensure that there's a camera activity to handle the intent
        takePictureIntent.resolveActivity(packageManager)?.also {
            // Create the File where the photo should go
            val photoFile: File? = try {
                createImageFile()
            } catch (ex: IOException) {
                // Error occurred while creating the File
                ...
                null
            }
            // Continue only if the File was successfully created
            photoFile?.also {
                val photoURI: Uri = FileProvider.getUriForFile(
                        this,
                        "com.example.android.fileprovider",
                        it
                )
                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
                startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
            }
        }
    }
}

জাভা

private void dispatchTakePictureIntent() {
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    // Ensure that there's a camera activity to handle the intent
    if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
        // Create the File where the photo should go
        File photoFile = null;
        try {
            photoFile = createImageFile();
        } catch (IOException ex) {
            // Error occurred while creating the File
            ...
        }
        // Continue only if the File was successfully created
        if (photoFile != null) {
            Uri photoURI = FileProvider.getUriForFile(this,
                                                  "com.example.android.fileprovider",
                                                  photoFile);
            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
            startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
        }
    }
}

মনে রাখবেন: আমরা getUriForFile(Context, String, File) ব্যবহার করছি যা content:// URI রিটার্ন করে। Android 7.0 (API লেভেল 24) এবং তার পরবর্তী ভার্সনকে টার্গেট করা আরও সাম্প্রতিক অ্যাপের জন্য, প্যাকেজ বাউন্ডারি জুড়ে file:// URI পাস করলে FileUriExposedException হয়। তাই, আমরা এখন FileProvider ব্যবহার করে ছবি সেভ করার আরও সাধারণ উপায় উপস্থাপন করছি।

এখন, আপনাকে FileProvider কনফিগার করতে হবে। আপনার অ্যাপের ম্যানিফেস্টে, আপনার অ্যাপ্লিকেশনে একটি প্রদানকারী যোগ করুন:

<application>
   ...
   <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="com.example.android.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths"></meta-data>
    </provider>
    ...
</application>

নিশ্চিত করুন যে কর্তৃপক্ষের স্ট্রিং getUriForFile(Context, String, File)-এর দ্বিতীয় আর্গুমেন্টের সাথে মিলছে। প্রোভাইডার ডেফিনিশনের মেটাডেটা বিভাগে, আপনি দেখতে পাবেন যে প্রোভাইডার আশা করে যে উপযুক্ত পাথ একটি নির্দিষ্ট রিসোর্স ফাইল, res/xml/file_paths.xml-এ কনফিগার করা হবে। এই নির্দিষ্ট উদাহরণের জন্য প্রয়োজনীয় কন্টেন্ট এখানে দেওয়া হল:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-files-path name="my_images" path="Pictures" />
</paths>

পাথ কম্পোনেন্টটি সেই পাথের সাথে সম্পর্কিত যা getExternalFilesDir() দ্বারা রিটার্ন করা হয় যখন Environment.DIRECTORY_PICTURES-এর সাথে কল করা হয়। আপনার অ্যাপের আসল প্যাকেজের নাম দিয়ে com.example.package.name পরিবর্তন করতে ভুলবেন না। এছাড়াও, external-path ছাড়াও আপনি ব্যবহার করতে পারেন এমন পাথ স্পেসিফায়ারের বিস্তারিত বিবরণের জন্য FileProvider-এর ডকুমেন্টেশন চেক করুন।

গ্যালারিতে ফটো যোগ করুন

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

মনে রাখবেন: আপনি যদি getExternalFilesDir()-এর দেওয়া ডিরেক্টরিতে ফটো সেভ করেন, তাহলে মিডিয়া স্ক্যানার ফাইলগুলি অ্যাক্সেস করতে পারবে না কারণ সেগুলি আপনার অ্যাপের জন্য ব্যক্তিগত।

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

Kotlin

private fun galleryAddPic() {
    Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE).also { mediaScanIntent ->
        val f = File(currentPhotoPath)
        mediaScanIntent.data = Uri.fromFile(f)
        sendBroadcast(mediaScanIntent)
    }
}

জাভা

private void galleryAddPic() {
    Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
    File f = new File(currentPhotoPath);
    Uri contentUri = Uri.fromFile(f);
    mediaScanIntent.setData(contentUri);
    this.sendBroadcast(mediaScanIntent);
}

উন্নতি করার ছবি ডিকোড করুন

সীমিত মেমরির ক্ষেত্রে একাধিক পূর্ণ আকারের ছবি ম্যানেজ করা কঠিন হতে পারে। আপনি যদি দেখেন যে মাত্র কয়েকটি ছবি দেখানোর পরেই আপনার অ্যাপ্লিকেশনের মেমরি শেষ হয়ে যাচ্ছে, তাহলে আপনি ডেস্টিনেশন ভিউয়ের সাইজের সাথে ম্যাচ করার জন্য আগে থেকেই স্কেল করা মেমরি অ্যারেতে JPEG এক্সপ্যান্ড করে, ব্যবহৃত ডায়নামিক হিপের পরিমাণ নাটকীয়ভাবে কমাতে পারেন। নিম্নলিখিত উদাহরণ পদ্ধতি এই কৌশলটি প্রদর্শন করে।

Kotlin

private fun setPic() {
    // Get the dimensions of the View
    val targetW: Int = imageView.width
    val targetH: Int = imageView.height

    val bmOptions = BitmapFactory.Options().apply {
        // Get the dimensions of the bitmap
        inJustDecodeBounds = true

        BitmapFactory.decodeFile(currentPhotoPath, bmOptions)

        val photoW: Int = outWidth
        val photoH: Int = outHeight

        // Determine how much to scale down the image
        val scaleFactor: Int = Math.max(1, Math.min(photoW / targetW, photoH / targetH))

        // Decode the image file into a Bitmap sized to fill the View
        inJustDecodeBounds = false
        inSampleSize = scaleFactor
        inPurgeable = true
    }
    BitmapFactory.decodeFile(currentPhotoPath, bmOptions)?.also { bitmap ->
        imageView.setImageBitmap(bitmap)
    }
}

জাভা

private void setPic() {
    // Get the dimensions of the View
    int targetW = imageView.getWidth();
    int targetH = imageView.getHeight();

    // Get the dimensions of the bitmap
    BitmapFactory.Options bmOptions = new BitmapFactory.Options();
    bmOptions.inJustDecodeBounds = true;

    BitmapFactory.decodeFile(currentPhotoPath, bmOptions);

    int photoW = bmOptions.outWidth;
    int photoH = bmOptions.outHeight;

    // Determine how much to scale down the image
    int scaleFactor = Math.max(1, Math.min(photoW/targetW, photoH/targetH));

    // Decode the image file into a Bitmap sized to fill the View
    bmOptions.inJustDecodeBounds = false;
    bmOptions.inSampleSize = scaleFactor;
    bmOptions.inPurgeable = true;

    Bitmap bitmap = BitmapFactory.decodeFile(currentPhotoPath, bmOptions);
    imageView.setImageBitmap(bitmap);
}