Mihail Kudrin
Mihail Kudrin

Reputation: 321

how to save bitmap to android gallery

unfortunately the solutions I've found didn't work on android 5.1.1. I have a bitmap called source. I need to save it directly to my phone's gallery. My manifest contains <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> Can you give me a working method to do this?

Upvotes: 21

Views: 41952

Answers (10)

Liong
Liong

Reputation: 1584

Since it 2024, I'll try update the answer with Kotlin. Right now I also use this code in my Jetpack Compose app.

Since all the answer have not 100% similarity with mine, and my answer is no need to use any file providers or generate any File object, I'll show it here.

Environment I used on my app is Pictures (Environment.DIRECTORY_PICTURES).

First of all, because my app min SDK is 25, I need to add Manifest uses-permission for read and write.

<manifest ...

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

    <application
        ...

</manifest>

Second, let's say you already have your own bitmap, so I'll just skip to the MediaStore, ContentValues, and ContentResolver configuration. I'm using Kotlin Coroutines with Dispatchers.IO as the dispatcher. This second stage is occur in my ViewModel file.

To create ContentValues with MediaStore efficiently, I put a function to create it.

// This code, is in my ViewModel file

private fun createContentValues(title: String, subfolder: String): ContentValues =
    ContentValues().apply {
        put(MediaStore.Images.Media.TITLE, "$title.png")
        put(MediaStore.Images.Media.DISPLAY_NAME, "$title.png")
        put(MediaStore.Images.Media.MIME_TYPE, "image/png")
        put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis())
        put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis())
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            put(
                MediaStore.MediaColumns.RELATIVE_PATH,
                "${Environment.DIRECTORY_PICTURES}/$subfolder"
            )
        }
    }

Then we back on configure the ContentResolver

// This code, is in my ViewModel file

suspend fun saveShareBitmap(applicationContext: Context, bitmap: Bitmap, subfolder: String, filename: String): List<Any> =
    withContext(Dispatchers.IO) {
        val contentValues = createContentValues(filename, subfolder)

        var uri: Uri? = null

        try {
            uri = applicationContext.contentResolver.insert(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues
            ) ?: throw IOException("Failed to create new MediaStore record")

            val stream = applicationContext.contentResolver.openOutputStream(uri)
                ?: throw IOException("Failed to open output stream")

            bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)
            stream.flush()
            stream.close()
        } catch (e: IOException) {
            if (uri != null) {
                applicationContext.contentResolver.delete(uri, null, null)
            }
        }

        return@withContext listOf(uri ?: Uri.parse(""), filename)
    }

Third, we back to our layout code file. We need to check if build version code is <= SDK 28, we need to grant permission for read-write file. Inside the CoroutineScope that I already initialized before, it is show how to share the image using current created URI from my previous ViewModel function.

if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
    if (!(writePermissionCheckResult && readPermissionCheckResult)) {
        launcherPermission.launch(
            arrayOf(
                Manifest.permission.READ_EXTERNAL_STORAGE,
                Manifest.permission.WRITE_EXTERNAL_STORAGE
            )
        )
    } else {
        isWriteReadPermission = true
    }
} else {  // SDK after 28 no need check
    isWriteReadPermission = true
}

if (!isWriteReadPermission && !isRationaleWriteReadPermission) {
    context.showToast(context.getString(R.string.accept_on_settings))

    Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
        data = Uri.fromParts("package", applicationContext.packageName, null)
        context.startActivity(this)
    }
} else if (isRationaleWriteReadPermission) {
    context.showToast(
        "${context.getString(R.string.accept_permissions)}\n" +
                context.getString(R.string.please_press_again)
    )
} else {
    // code if permission accepted
    // Coroutine Scope already initialized as scope
    // ViewModel already initialized as 
    scope.launch{
        withContext(Dispatchers.Main){
            // in this scope I make share bitmap by URI
            val (uri, filename) = viewModel.saveShareBitmap(applicationContext, bitmap, subfolder, filename)

            if (uri is Uri && filename is String) {
                val chooser = Intent.createChooser(
                    Intent(Intent.ACTION_SEND).apply {
                        clipData =
                            ClipData.newRawUri(filename, uri)  // to fix Security Exception
                        putExtra(Intent.EXTRA_STREAM, uri)
                        addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
                        type = "image/png"
                    }, applicationContext.getString(R.string.share_where)
                )
                context.startActivity(chooser)
            }
        }
    }
}

That's all from me, thank you and feel free to correct my answer by comment.

Upvotes: 1

Burger
Burger

Reputation: 413

I'd like to add Java code based on @Bao Lei 's answer that I used in my app.

    private void saveImage(Bitmap bitmap, Context context, String folderName) throws FileNotFoundException {
        if (android.os.Build.VERSION.SDK_INT >= 29) {
            ContentValues values = new 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.

            Uri uri = context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
            if (uri != null) {
                saveImageToStream(bitmap, context.getContentResolver().openOutputStream(uri));
                values.put(MediaStore.Images.Media.IS_PENDING, false);
                context.getContentResolver().update(uri, values, null, null);
            }
        } else {
            dir = new File(getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES),"");
            // getExternalStorageDirectory is deprecated in API 29

            if (!dir.exists()) {
                dir.mkdirs();
            }

            java.util.Date date = new java.util.Date();
            imageFile = new File(dir.getAbsolutePath()
                    + File.separator
                    + new Timestamp(date.getTime()).toString()
                    + "Image.jpg");

            imageFile = new File(dir.getAbsolutePath()
                    + File.separator
                    + new Timestamp(date.getTime()).toString()
                    + "Image.jpg");
            saveImageToStream(bitmap, new FileOutputStream(imageFile));
            if (imageFile.getAbsolutePath() != null) {
                ContentValues values = new ContentValues();
                values.put(MediaStore.Images.Media.DATA, imageFile.getAbsolutePath());
                // .DATA is deprecated in API 29
                context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
            }
        }
    }

    private ContentValues contentValues() {
        ContentValues values = new ContentValues();
        values.put(MediaStore.Images.Media.MIME_TYPE, "image/png");
        values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
        values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
        return values;
    }

    private void saveImageToStream(Bitmap bitmap, OutputStream outputStream) {
        if (outputStream != null) {
            try {
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
                outputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

This one worked fine in my app.

Upvotes: 2

Maulik Dhameliya
Maulik Dhameliya

Reputation: 1558

From Android Q there are changes in saving image to gallery.Thanks to @BaoLei, here is my answer in java if anybody needs it.

    private void saveImage(Bitmap bitmap) {
        if (android.os.Build.VERSION.SDK_INT >= 29) {
            ContentValues values = contentValues();
            values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/" + getString(R.string.app_name));
            values.put(MediaStore.Images.Media.IS_PENDING, true);

            Uri uri = this.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
            if (uri != null) {
                try {
                    saveImageToStream(bitmap, this.getContentResolver().openOutputStream(uri));
                    values.put(MediaStore.Images.Media.IS_PENDING, false);
                    this.getContentResolver().update(uri, values, null, null);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }

            }
        } else {
            File directory = new File(Environment.getExternalStorageDirectory().toString() + '/' + getString(R.string.app_name));

            if (!directory.exists()) {
                directory.mkdirs();
            }
            String fileName = System.currentTimeMillis() + ".png";
            File file = new File(directory, fileName);
            try {
                saveImageToStream(bitmap, new FileOutputStream(file));
                ContentValues values = new ContentValues();
                values.put(MediaStore.Images.Media.DATA, file.getAbsolutePath());
                this.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }

        }
    }

    private ContentValues contentValues() {
        ContentValues values = new ContentValues();
        values.put(MediaStore.Images.Media.MIME_TYPE, "image/png");
        values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
        }
        return values;
    }

    private void saveImageToStream(Bitmap bitmap, OutputStream outputStream) {
        if (outputStream != null) {
            try {
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
                outputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

Upvotes: 10

Kashfa Khan
Kashfa Khan

Reputation: 2493

Now we have android10 and android11, so here is an updated version, that will work in all android devices.

Make sure you have the WRITE_EXTERNAL_STORAGE permission before calling this function.

    private fun saveMediaToStorage(bitmap: Bitmap) {
        val filename = "${System.currentTimeMillis()}.jpg"
        var fos: OutputStream? = null
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            contentResolver?.also { resolver ->
                val contentValues = ContentValues().apply {
                    put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
                    put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg")
                    put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
                }
                val imageUri: Uri? =
                    resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
                fos = imageUri?.let { resolver.openOutputStream(it) }
            }
        } else {
            val imagesDir =
                Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
            val image = File(imagesDir, filename)
            fos = FileOutputStream(image)
        }
        fos?.use {
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it)
            toast("Saved to Photos")
        }
    }

Its kotlin, and I think very straight forward and self explaining code. But still if you have a problem, comment below and I will explain.

Reference: Android Save Bitmap to Gallery Tutorial.

Upvotes: 4

Omar Aflak
Omar Aflak

Reputation: 2962

Here is a fully working solution in Kotlin:

fun saveToGallery(context: Context, bitmap: Bitmap, albumName: String) {
    val filename = "${System.currentTimeMillis()}.png"
    val write: (OutputStream) -> Boolean = {
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
    }

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        val contentValues = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
            put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
            put(MediaStore.MediaColumns.RELATIVE_PATH, "${Environment.DIRECTORY_DCIM}/$albumName")
        }

        context.contentResolver.let {
            it.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)?.let { uri ->
                it.openOutputStream(uri)?.let(write)
            }
        }
    } else {
        val imagesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString() + File.separator + albumName
        val file = File(imagesDir)
        if (!file.exists()) {
            file.mkdir()
        }
        val image = File(imagesDir, filename)
        write(FileOutputStream(image))
    }
}

Upvotes: 6

Bao Lei
Bao Lei

Reputation: 3615

There were several different ways to do it before API 29 (Android Q) but all of them involved one or a few APIs that are deprecated with Q. In 2019, here's a way to do it that is both backward and forward compatible:

(And since it is 2019 so I will write in Kotlin)

    /// @param folderName can be your app's name
    private fun saveImage(bitmap: Bitmap, context: Context, folderName: String) {
        if (android.os.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 {
            val directory = File(Environment.getExternalStorageDirectory().toString() + separator + folderName)
            // getExternalStorageDirectory is deprecated in API 29

            if (!directory.exists()) {
                directory.mkdirs()
            }
            val fileName = System.currentTimeMillis().toString() + ".png"
            val file = File(directory, fileName)
            saveImageToStream(bitmap, FileOutputStream(file))
            if (file.absolutePath != null) {
                val values = contentValues()
                values.put(MediaStore.Images.Media.DATA, file.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/png")
        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?) {
        if (outputStream != null) {
            try {
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
                outputStream.close()
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

Also, before calling this, you need to have WRITE_EXTERNAL_STORAGE first.

Upvotes: 39

Saksham Khurana
Saksham Khurana

Reputation: 1020

For Media Scanning, you can simply do


  val intent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
  intent.data = Uri.fromFile(path) // path must be of File type
  context.sendBroadcast(intent)

Upvotes: 0

Er Ekta Sahu
Er Ekta Sahu

Reputation: 363

Use this code this we help you to store images into a particular folder which is saved_images and that folder images show in gallery immediately.

private void SaveImage(Bitmap finalBitmap) {

String root = Environment.getExternalStoragePublicDirectory(
    Environment.DIRECTORY_PICTURES).toString();
File myDir = new File(root + "/saved_images");
myDir.mkdirs();
Random generator = new Random();

int n = 10000;
n = generator.nextInt(n);
String fname = "Image-"+ n +".jpg";
File file = new File (myDir, fname);
if (file.exists ()) file.delete ();
try {
    FileOutputStream out = new FileOutputStream(file);
    finalBitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
    // sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, 
    //     Uri.parse("file://"+ Environment.getExternalStorageDirectory())));
    out.flush();
    out.close();

} catch (Exception e) {
    e.printStackTrace();
}
// Tell the media scanner about the new file so that it is
// immediately available to the user.
MediaScannerConnection.scanFile(this, new String[]{file.toString()}, null,
    new MediaScannerConnection.OnScanCompletedListener() {
        public void onScanCompleted(String path, Uri uri) {
            Log.i("ExternalStorage", "Scanned " + path + ":");
            Log.i("ExternalStorage", "-> uri=" + uri);
        }
    });
}

Upvotes: 15

Sohaib Aslam
Sohaib Aslam

Reputation: 1325

Do it in One Line MediaStore.Images.Media.insertImage(applicationContext.getContentResolver(), IMAGE ,"nameofimage" , "description");

Upvotes: 5

Bui Quang Huy
Bui Quang Huy

Reputation: 1794

Use this one:

private void saveImage(Bitmap finalBitmap, String image_name) {

        String root = Environment.getExternalStorageDirectory().toString();
        File myDir = new File(root);
        myDir.mkdirs();
        String fname = "Image-" + image_name+ ".jpg";
        File file = new File(myDir, fname);
        if (file.exists()) file.delete();
        Log.i("LOAD", root + fname);
        try {
            FileOutputStream out = new FileOutputStream(file);
            finalBitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
            out.flush();
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

Upvotes: 17

Related Questions