111

i have an app with a gallery of images and i want that the user can save it into his own gallery. I've created an option menu with a single voice "save" to allow that but the problem is...how can i save the image into the gallery?

this is my code:

@Override
        public boolean onOptionsItemSelected(MenuItem item) {
            // Handle item selection
            switch (item.getItemId()) {
            case R.id.menuFinale:

                imgView.setDrawingCacheEnabled(true);
                Bitmap bitmap = imgView.getDrawingCache();
                File root = Environment.getExternalStorageDirectory();
                File file = new File(root.getAbsolutePath()+"/DCIM/Camera/img.jpg");
                try 
                {
                    file.createNewFile();
                    FileOutputStream ostream = new FileOutputStream(file);
                    bitmap.compress(CompressFormat.JPEG, 100, ostream);
                    ostream.close();
                } 
                catch (Exception e) 
                {
                    e.printStackTrace();
                }



                return true;
            default:
                return super.onOptionsItemSelected(item);
            }
        }

i'm not sure of this part of code:

File root = Environment.getExternalStorageDirectory();
                File file = new File(root.getAbsolutePath()+"/DCIM/Camera/img.jpg");

is it correct to save into the gallery? unfortunately the code doesn't work :(

Evorlor
  • 7,263
  • 17
  • 70
  • 141
Christian Giupponi
  • 7,408
  • 11
  • 68
  • 113
  • have you resolved this issue ? can u please share with me – user3233280 Feb 22 '14 at 09:53
  • i am also having same problem http://stackoverflow.com/questions/21951558/failed-to-save-image-from-app-assets-folder-to-gallery-folder-in-android/21951643?noredirect=1#21951643 – user3233280 Feb 22 '14 at 09:54
  • For those of you who are still having issues saving the file, it might be because your url contains illegal characters such as "?", ":", and "-" Remove those and it should work. This is a common error in foreign devices and the android emulators. See more about it here: http://stackoverflow.com/questions/11394616/java-io-ioexception-open-failed-einval-invalid-argument-when-saving-a-image – ChallengeAccepted Nov 25 '14 at 21:58
  • The accepted answer is a little outdated in 2019. I have written an updated answer here: https://stackoverflow.com/questions/36624756/how-to-save-bitmap-to-android-gallery/57265702#57265702 – Bao Lei Jul 30 '19 at 06:45

12 Answers12

188
MediaStore.Images.Media.insertImage(getContentResolver(), yourBitmap, yourTitle , yourDescription);

The former code will add the image at the end of the gallery. If you want to modify the date so it appears at the beginning or any other metadata, see the code below (Cortesy of S-K, samkirton):

https://gist.github.com/samkirton/0242ba81d7ca00b475b9

/**
 * Android internals have been modified to store images in the media folder with 
 * the correct date meta data
 * @author samuelkirton
 */
public class CapturePhotoUtils {

    /**
     * A copy of the Android internals  insertImage method, this method populates the 
     * meta data with DATE_ADDED and DATE_TAKEN. This fixes a common problem where media 
     * that is inserted manually gets saved at the end of the gallery (because date is not populated).
     * @see android.provider.MediaStore.Images.Media#insertImage(ContentResolver, Bitmap, String, String)
     */
    public static final String insertImage(ContentResolver cr, 
            Bitmap source, 
            String title, 
            String description) {

        ContentValues values = new ContentValues();
        values.put(Images.Media.TITLE, title);
        values.put(Images.Media.DISPLAY_NAME, title);
        values.put(Images.Media.DESCRIPTION, description);
        values.put(Images.Media.MIME_TYPE, "image/jpeg");
        // Add the date meta data to ensure the image is added at the front of the gallery
        values.put(Images.Media.DATE_ADDED, System.currentTimeMillis());
        values.put(Images.Media.DATE_TAKEN, System.currentTimeMillis());

        Uri url = null;
        String stringUrl = null;    /* value to be returned */

        try {
            url = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

            if (source != null) {
                OutputStream imageOut = cr.openOutputStream(url);
                try {
                    source.compress(Bitmap.CompressFormat.JPEG, 50, imageOut);
                } finally {
                    imageOut.close();
                }

                long id = ContentUris.parseId(url);
                // Wait until MINI_KIND thumbnail is generated.
                Bitmap miniThumb = Images.Thumbnails.getThumbnail(cr, id, Images.Thumbnails.MINI_KIND, null);
                // This is for backward compatibility.
                storeThumbnail(cr, miniThumb, id, 50F, 50F,Images.Thumbnails.MICRO_KIND);
            } else {
                cr.delete(url, null, null);
                url = null;
            }
        } catch (Exception e) {
            if (url != null) {
                cr.delete(url, null, null);
                url = null;
            }
        }

        if (url != null) {
            stringUrl = url.toString();
        }

        return stringUrl;
    }

    /**
     * A copy of the Android internals StoreThumbnail method, it used with the insertImage to
     * populate the android.provider.MediaStore.Images.Media#insertImage with all the correct
     * meta data. The StoreThumbnail method is private so it must be duplicated here.
     * @see android.provider.MediaStore.Images.Media (StoreThumbnail private method)
     */
    private static final Bitmap storeThumbnail(
            ContentResolver cr,
            Bitmap source,
            long id,
            float width, 
            float height,
            int kind) {

        // create the matrix to scale it
        Matrix matrix = new Matrix();

        float scaleX = width / source.getWidth();
        float scaleY = height / source.getHeight();

        matrix.setScale(scaleX, scaleY);

        Bitmap thumb = Bitmap.createBitmap(source, 0, 0,
            source.getWidth(),
            source.getHeight(), matrix,
            true
        );

        ContentValues values = new ContentValues(4);
        values.put(Images.Thumbnails.KIND,kind);
        values.put(Images.Thumbnails.IMAGE_ID,(int)id);
        values.put(Images.Thumbnails.HEIGHT,thumb.getHeight());
        values.put(Images.Thumbnails.WIDTH,thumb.getWidth());

        Uri url = cr.insert(Images.Thumbnails.EXTERNAL_CONTENT_URI, values);

        try {
            OutputStream thumbOut = cr.openOutputStream(url);
            thumb.compress(Bitmap.CompressFormat.JPEG, 100, thumbOut);
            thumbOut.close();
            return thumb;
        } catch (FileNotFoundException ex) {
            return null;
        } catch (IOException ex) {
            return null;
        }
    }
}
EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
sebastianf182
  • 9,844
  • 3
  • 34
  • 66
  • 26
    This saves the image, but to the end of the gallery though when you take a picture with camera it's saves on top. How can i save the image to top of gallery? – eric.itzhak Jun 12 '12 at 16:23
  • 21
    Note that your must also add to your manifext.xml. – Kyle Clegg Dec 28 '12 at 22:43
  • 3
    Images are not saved at the top of the gallery because internally insertImage does not add any date meta data. Please see this GIST: https://gist.github.com/0242ba81d7ca00b475b9.git it is an exact copy of the insertImage method but it adds the date meta date to ensure the image is added at the front of the gallery. – S-K' Jun 18 '14 at 15:47
  • 1
    @S-K'I can't access that URL. Please update it and I will update my answer so it has both options. Cheers – sebastianf182 Jun 21 '14 at 02:28
  • 6
    [Here is the correct GIST link mentioned above](https://gist.github.com/samkirton/0242ba81d7ca00b475b9) (needed to remove the `.git` at the end) – minipif Jul 20 '14 at 16:59
  • 1
    You just need to remember to add the DATE_TAKEN to the values in order for it to show up on top – ShadowGod Apr 26 '15 at 07:35
  • I had to add `values.put(MediaStore.Images.Media.DATE_MODIFIED, System.currentTimeMillis()/1000);` line, some Samsung devices and/or TouchWizz versions requires that... even if [DOC](https://developer.android.com/reference/android/provider/MediaStore.MediaColumns.html#DATE_MODIFIED) says to NOT set this field (also informs that value is in seconds, so /1000) – snachmsm Dec 12 '16 at 11:59
  • 2
    How to change the file folder name? – Kopi Bryant Mar 25 '19 at 06:26
  • 5
    ```MediaStore.Images.Media.insertImage(...)``` is already deprecated. – Michael Abyzov Nov 01 '20 at 10:41
  • Nice, am able to save the image into the android gallery and when I check the image in the gallery its actually in that location. But how do I actually read the image / retrieve the image from the gallery to show it to an imageview ? For anybody wondering you can pass in the content resolver by calling the system function getContentResolver(). – MosesK Jun 29 '22 at 08:41
  • This is the only working answer among all. thanks @sebastianf182 – Michael Jul 08 '23 at 16:09
53

Actually, you can save you picture at any place. If you want to save in a public space, so any other application can access, use this code:

storageDir = new File(
    Environment.getExternalStoragePublicDirectory(
        Environment.DIRECTORY_PICTURES
    ), 
    getAlbumName()
);

The picture doesn't go to the album. To do this, you need to call a scan:

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

You can found more info at https://developer.android.com/training/camera/photobasics.html#TaskGallery

Sigrist
  • 1,471
  • 2
  • 14
  • 18
26

I've tried a lot of things to let this work on Marshmallow and Lollipop. Finally i ended up moving the saved picture to the DCIM folder (new Google Photo app scan images only if they are inside this folder apparently)

public static File createImageFile() throws IOException {
    // Create an image file name
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss")
         .format(System.currentTimeInMillis());
    File storageDir = new File(Environment
         .getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM) + "/Camera/");
    if (!storageDir.exists())
        storageDir.mkdirs();
    File image = File.createTempFile(
            timeStamp,                   /* prefix */
            ".jpeg",                     /* suffix */
            storageDir                   /* directory */
    );
    return image;
}

And then the standard code for scanning files which you can find in the Google Developers site too.

public static void addPicToGallery(Context context, String photoPath) {
    Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
    File f = new File(photoPath);
    Uri contentUri = Uri.fromFile(f);
    mediaScanIntent.setData(contentUri);
    context.sendBroadcast(mediaScanIntent);
}

Please remember that this folder could not be present in every device in the world and that starting from Marshmallow (API 23), you need to request the permission to WRITE_EXTERNAL_STORAGE to the user.

MatPag
  • 41,742
  • 14
  • 105
  • 114
14

According to this course, the correct way to do this is:

Environment.getExternalStoragePublicDirectory(
        Environment.DIRECTORY_PICTURES
    )

This will give you the root path for the gallery directory.

JorgeAmVF
  • 1,660
  • 3
  • 21
  • 32
Cédric Julien
  • 78,516
  • 15
  • 127
  • 132
12
private void galleryAddPic() {
    Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
    File f = new File(mCurrentPhotoPath);
    Uri contentUri = Uri.fromFile(f);
    mediaScanIntent.setData(contentUri);
    this.sendBroadcast(mediaScanIntent);
}
Ben
  • 51,770
  • 36
  • 127
  • 149
nitin Sol
  • 121
  • 1
  • 3
10

You can create a directory inside the camera folder and save the image. After that, you can simply perform your scan. It will instantly show your image in the gallery.

String root = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString()+ "/Camera/Your_Directory_Name";
File myDir = new File(root);
myDir.mkdirs();
String fname = "Image-" + image_name + ".png";
File file = new File(myDir, fname);
System.out.println(file.getAbsolutePath());
if (file.exists()) file.delete();
    Log.i("LOAD", root + fname);
    try {
        FileOutputStream out = new FileOutputStream(file);
        finalBitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
        out.flush();
        out.close();
    } catch (Exception e) {
       e.printStackTrace();
    }

MediaScannerConnection.scanFile(context, new String[]{file.getPath()}, new String[]{"image/jpeg"}, null);
Rohan Singh
  • 20,497
  • 1
  • 41
  • 48
javatar
  • 1,332
  • 1
  • 17
  • 24
2

Here's what worked for me:

 private fun saveBitmapAsImageToDevice(bitmap: Bitmap?) {
    // Add a specific media item.
    val resolver = this.contentResolver

    val imageStorageAddress = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
    } else {
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI
    }

    val imageDetails = ContentValues().apply {
        put(MediaStore.Images.Media.DISPLAY_NAME, "my_app_${System.currentTimeMillis()}.jpg")
        put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
        put(MediaStore.MediaColumns.DATE_ADDED, System.currentTimeMillis())
    }

    try {
        // Save the image.
        val contentUri: Uri? = resolver.insert(imageStorageAddress, imageDetails)
        contentUri?.let { uri ->
            // Don't leave an orphan entry in the MediaStore
            if (bitmap == null) resolver.delete(contentUri, null, null)
            val outputStream: OutputStream? = resolver.openOutputStream(uri)
            outputStream?.let { outStream ->
                val isBitmapCompressed =
                    bitmap?.compress(Bitmap.CompressFormat.JPEG, 95, outStream)
                if (isBitmapCompressed == true) {
                    outStream.flush()
                    outStream.close()
                }
            } ?: throw IOException("Failed to get output stream.")
        } ?: throw IOException("Failed to create new MediaStore record.")
    } catch (e: IOException) {
        throw e
    }
}
kevincodes_
  • 219
  • 4
  • 4
2

Note: For Build.VERSION.SDK_INT < 29, the image has to be saved locally on disk first, which will increase the app size as the user saves more images. The user can delete the image later, in the Files app, but the local image has to sync with Google Photos or Amazon Photos in the cloud.

Saving the image to the cloud is accomplished by having the user open their Google Photos or Amazon Photos app after exporting and before deleting your app APK. If the user of < 29 deletes your APK before opening Google Photos or Amazon Photos, the photo will be lost.

This is a bug with Android Builds before Q (Level 29). Level 29 and later save directly to the Photo Library.

Android Manifest XML

<!-- Adding Read External Storage Permission -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Save Function

// - Save Image -

@Throws(FileNotFoundException::class)
private fun saveImage(
    bitmap: Bitmap,
    context: Context,
    folderName: String
) {

    if (Build.VERSION.SDK_INT >= 29) {

        val values = ContentValues()
        values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/$folderName")
        values.put(MediaStore.Images.Media.IS_PENDING, true)

        // RELATIVE_PATH and IS_PENDING are introduced in API 29.

        val uri: Uri? = context.contentResolver
            .insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)

        if (uri != null) {
            saveImageToStream(bitmap, context.contentResolver.openOutputStream(uri))
            values.put(MediaStore.Images.Media.IS_PENDING, false)
            context.contentResolver.update(uri, values, null, null)
        }

    } else {

        var dir = File(
            applicationContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES),
            ""
        )

        // getExternalStorageDirectory is deprecated in API 29

        if (!dir.exists()) {

            dir.mkdirs()

        }

        val date = Date()

        val fullFileName = "myFileName.jpeg"

        val fileName = fullFileName?.substring(0, fullFileName.lastIndexOf("."))
        val extension = fullFileName?.substring(fullFileName.lastIndexOf("."))

        var imageFile = File(
            dir.absolutePath
                .toString() + File.separator
                    + fileName + "_" + Timestamp(date.time).toString()
                    + ".jpg"
        )

        println("imageFile: $imageFile")

        saveImageToStream(bitmap, FileOutputStream(imageFile))

        if (imageFile.getAbsolutePath() != null) {

            val values = ContentValues()

            values.put(MediaStore.Images.Media.DATA, imageFile.absolutePath)

            // .DATA is deprecated in API 29

            context.contentResolver
                .insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)

        }

    }

}

private fun contentValues(): ContentValues? {

    val values = ContentValues()

    values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
    values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000)
    values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis())

    return values

}

private fun saveImageToStream(bitmap: Bitmap, outputStream: OutputStream?) {

    println("saveImageToStream")

    if (outputStream != null) {

        try {

            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
            outputStream.close()

            // success dialog

            runOnUiThread {

                val successDialog = SuccessDialog.getInstance(null)
                successDialog.show(supportFragmentManager, SuccessDialog.TAG)

            }

        } catch (e: Exception) {

            e.printStackTrace()

            // warning dialog

            runOnUiThread {

                val warningDialog = WarningDialog.getInstance(null)
                warningDialog.show(supportFragmentManager, WarningDialog.TAG)

            }

        }

    }

}
Michael N
  • 436
  • 5
  • 6
1

I come here with the same doubt but for Xamarin for Android, I have used the Sigrist answer to do this method after save my file:

private void UpdateGallery()
{
    Intent mediaScanIntent = new Intent(Intent.ActionMediaScannerScanFile);
    Java.IO.File file = new Java.IO.File(_path);
    Android.Net.Uri contentUri = Android.Net.Uri.FromFile(file);
    mediaScanIntent.SetData(contentUri);
    Application.Context.SendBroadcast(mediaScanIntent);
} 

and it solved my problem, Thx Sigrist. I put it here becouse i did not found an answare about this for Xamarin and i hope it can help other people.

Slaters
  • 613
  • 6
  • 9
1

In my case the solutions above did not work I had to do the following:

sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(f)));
dc10
  • 2,160
  • 6
  • 29
  • 46
  • its's really good to know about this option, but unfortunately not works on some devices with android 6, so `ContentProvider` preferable solytion – Siarhei Apr 19 '17 at 21:03
1
 String filePath="/storage/emulated/0/DCIM"+app_name;
    File dir=new File(filePath);
    if(!dir.exists()){
        dir.mkdir();
    }

This code is in onCreate method.This code is for creating a directory of app_name. Now,this directory can be accessed using default file manager app in android. Use this string filePath wherever required to set your destination folder. I am sure this method works on Android 7 too because I tested on it.Hence,it can work on other versions of android too.

0

Just you need to scan your Media After Saving finished.

 BitmapDrawable drawable = (BitmapDrawable) imageView.getDrawable();
            Bitmap bitmap = drawable.getBitmap();

            File filepath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
            File dir = new File(filepath.getAbsolutePath()+"/Pro Scanner/");
            if(!dir.exists()){
                dir.mkdir();
            }
            File file = new File(dir,System.currentTimeMillis()+"_Pro_Scanner.png");
            try {
                outputStream = new FileOutputStream(file);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                downloadQRCode.setVisibility(View.VISIBLE);
                loadingBar.setVisibility(View.INVISIBLE);
            }
            bitmap.compress(Bitmap.CompressFormat.PNG,100,outputStream);
            Toast.makeText(GenerateQRCodeActivity.this, "QR image saved successfully", Toast.LENGTH_SHORT).show();
            try {
                outputStream.flush();
                outputStream.close();
                loadingBar.setVisibility(View.INVISIBLE);
                downloadDone.setVisibility(View.VISIBLE);
                downloadDone.setAnimation(bottomAnimation);
            } catch (IOException e) {
                downloadQRCode.setVisibility(View.VISIBLE);
                loadingBar.setVisibility(View.INVISIBLE);
                e.printStackTrace();
            }

            MediaScannerConnection.scanFile(GenerateQRCodeActivity.this,new String[]{file.getPath()},new String[] {"image/jpeg"},null);

These code are same as everyone .If you try the bellow code after this it will work. You just need these one line of code:

MediaScannerConnection.scanFile(GenerateQRCodeActivity.this,new String[]{file.getPath()},new String[] {"image/jpeg"},null);

Boom!!! Yo can now get your saved image on your Gallery.

Md. Al-Amin
  • 690
  • 3
  • 13