फ़ोटो लें

ध्यान दें: इस पेज पर, Camera क्लास के बारे में बताया गया है. हालांकि, अब इस क्लास का इस्तेमाल नहीं किया जा सकता. हमारा सुझाव है कि आप CameraX का इस्तेमाल करें. इसके अलावा, इस्तेमाल के कुछ खास उदाहरणों के लिए, Camera2 का इस्तेमाल करें. CameraX और Camera2, दोनों ही Android 5.0 (एपीआई लेवल 21) और इसके बाद के वर्शन पर काम करते हैं.

इस लेसन में, डिवाइस पर मौजूद किसी दूसरे कैमरा ऐप्लिकेशन को फ़ोटो खींचने का काम सौंपकर, फ़ोटो खींचने का तरीका बताया गया है. (अगर आपको कैमरे की सुविधाएं खुद बनानी हैं, तो कैमरे को कंट्रोल करना लेख पढ़ें.)

मान लें कि आपने ऐसी वेदर सेवा लागू की है जो आपके क्लाइंट ऐप्लिकेशन पर चलने वाले डिवाइसों से ली गई आसमान की फ़ोटो को ब्लेंड करके, दुनिया भर का वेदर मैप बनाती है. फ़ोटो को इंटिग्रेट करना, आपके ऐप्लिकेशन का सिर्फ़ एक छोटा हिस्सा है. आपको कैमरे से आसानी से फ़ोटो लेनी है, न कि कैमरे को फिर से डिज़ाइन करना है. अच्छी बात यह है कि 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 Camera ऐप्लिकेशन, फ़ोटो को Intent में कोड में बदलकर, onActivityResult() को भेजता है. यह फ़ोटो, "data" कुंजी के तहत एक्सट्रा में एक छोटे Bitmap के तौर पर दिखती है. नीचे दिया गया कोड, इस इमेज को ढूंढता है और इसे किसी 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)
    }
}

Java

@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 Camera ऐप्लिकेशन को फ़ोटो सेव करने के लिए कोई फ़ाइल दी है, तो वह फ़ोटो को फ़ुल साइज़ में सेव करेगा. आपको पूरी तरह से क्वालिफ़ाइड फ़ाइल का नाम देना होगा, जहां कैमरा ऐप्लिकेशन को फ़ोटो सेव करनी चाहिए.

आम तौर पर, उपयोगकर्ता के डिवाइस के कैमरे से ली गई फ़ोटो, डिवाइस के सार्वजनिक बाहरी स्टोरेज में सेव होनी चाहिए, ताकि सभी ऐप्लिकेशन उन्हें ऐक्सेस कर सकें. शेयर की गई फ़ोटो के लिए सही डायरेक्ट्री, getExternalStoragePublicDirectory() के साथ DIRECTORY_PICTURES आर्ग्युमेंट की मदद से दी जाती है. इस तरीके से दी गई डायरेक्ट्री, सभी ऐप्लिकेशन के साथ शेयर की जाती है. Android 9 (एपीआई लेवल 28) और इससे पहले के वर्शन पर, इस डायरेक्ट्री को पढ़ने और उसमें लिखने के लिए, 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 (एपीआई लेवल 29) और उसके बाद के वर्शन में, फ़ोटो शेयर करने के लिए सही डायरेक्ट्री, MediaStore.Images टेबल है. अगर आपके ऐप्लिकेशन को सिर्फ़ उन फ़ोटो को ऐक्सेस करना है जो उपयोगकर्ता ने आपके ऐप्लिकेशन का इस्तेमाल करके ली हैं, तो आपको स्टोरेज से जुड़ी अनुमतियों का एलान करने की ज़रूरत नहीं है.

हालांकि, अगर आपको फ़ोटो सिर्फ़ अपने ऐप्लिकेशन के लिए निजी रखनी हैं, तो Context.getExternalFilesDir() की दी गई डायरेक्ट्री का इस्तेमाल करें. Android 4.3 और इससे पहले के वर्शन पर, इस डायरेक्ट्री में लिखने के लिए भी WRITE_EXTERNAL_STORAGE अनुमति की ज़रूरत होती है. Android 4.4 के बाद, अनुमति की ज़रूरत नहीं है, क्योंकि डायरेक्ट्री को अन्य ऐप्लिकेशन ऐक्सेस नहीं कर सकते. इसलिए, maxSdkVersion एट्रिब्यूट जोड़कर, यह एलान किया जा सकता है कि अनुमति का अनुरोध सिर्फ़ Android के पुराने वर्शन के लिए किया जाना चाहिए:

<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
    }
}

Java

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)
            }
        }
    }
}

Java

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:// यूआरआई दिखाता है. Android 7.0 (एपीआई लेवल 24) और उसके बाद के वर्शन को टारगेट करने वाले नए ऐप्लिकेशन के लिए, पैकेज की सीमा के पार file:// यूआरआई पास करने पर, 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>

पाथ कॉम्पोनेंट, उस पाथ से मेल खाता है जो Environment.DIRECTORY_PICTURES के साथ कॉल करने पर, getExternalFilesDir() से दिखाया जाता है. पक्का करें कि आपने com.example.package.name को अपने ऐप्लिकेशन के पैकेज के असली नाम से बदल दिया हो. साथ ही, external-path के अलावा, पाथ के बारे में ज़्यादा जानकारी पाने के लिए, FileProvider के दस्तावेज़ देखें.

फ़ोटो को गैलरी में जोड़ना

इंटेंट की मदद से फ़ोटो बनाने पर, आपको पता होना चाहिए कि आपकी इमेज कहां सेव है, क्योंकि आपने पहले ही यह तय कर लिया था कि उसे कहां सेव करना है. अगर आपको अपनी फ़ोटो को सभी के लिए ऐक्सेस करना है, तो सबसे आसान तरीका यह है कि आप उसे सिस्टम के मीडिया प्रोवाइडर से ऐक्सेस करने की अनुमति दें.

ध्यान दें: अगर आपने अपनी फ़ोटो को getExternalFilesDir() की दी गई डायरेक्ट्री में सेव किया है, तो मीडिया स्कैनर उन फ़ाइलों को ऐक्सेस नहीं कर सकता, क्योंकि वे आपके ऐप्लिकेशन के लिए निजी हैं.

यहां दिए गए उदाहरण में, सिस्टम के मीडिया स्कैनर को चालू करने का तरीका बताया गया है. इससे, अपनी फ़ोटो को मीडिया प्रोवाइडर के डेटाबेस में जोड़ा जा सकता है. साथ ही, इसे Android Gallery ऐप्लिकेशन और अन्य ऐप्लिकेशन में उपलब्ध कराया जा सकता है.

Kotlin

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

Java

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)
    }
}

Java

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);
}