将位图保存到指定位置

524
我正在编写一个函数,用于从Web服务器下载图像,将其显示在屏幕上,并且如果用户想要保留该图像,则将其保存在SD卡的某个文件夹中。是否有一种简单的方法可以将位图保存到我选择的文件夹中?
我的问题是,我可以下载图像并将其显示为位图。我找到的唯一保存图像到特定文件夹的方法是使用FileOutputStream,但这需要一个字节数组。我不确定如何进行转换(如果这是正确的方式),以便我可以使用FileOutputStream来写入数据。
我另一个选择是使用MediaStore:
MediaStore.Images.Media.insertImage(getContentResolver(), bm,
    barcodeNumber + ".jpg Card Image", barcodeNumber + ".jpg Card Image");

这个方法可以成功地将文件保存到SD卡,但不允许你自定义文件夹。


正是我在我的应用程序中所做的。我从Web服务器下载一个大图像,对其进行操作,然后通过mImage.setImageBitmap(_result.getBitmap());直接将位图加载到ImageView中,在我的onTaskComplete()回调中。现在,如果用户希望通过长按上下文菜单将文件保存到本地,我必须允许他们这样做。我应该能够使用下面的解决方案。不过我想知道,你是否发现了更好的方法? - wired00
这里有一种优雅的方法:https://dev59.com/GG855IYBdhLWcg3wlFWP - Chepech
请查看我在此处的答案:https://dev59.com/pF0a5IYBdhLWcg3wipTk#68110559 - Amr
19个回答

5
我找到了一种发送PNG和透明度的方法。
String file_path = Environment.getExternalStorageDirectory().getAbsolutePath() +
                    "/CustomDir";
File dir = new File(file_path);
if(!dir.exists())
  dir.mkdirs();

String format = new SimpleDateFormat("yyyyMMddHHmmss",
       java.util.Locale.getDefault()).format(new Date());

File file = new File(dir, format + ".png");
FileOutputStream fOut;
try {
        fOut = new FileOutputStream(file);
        yourbitmap.compress(Bitmap.CompressFormat.PNG, 85, fOut);
        fOut.flush();
        fOut.close();
     } catch (Exception e) {
        e.printStackTrace();
 }

Uri uri = Uri.fromFile(file);     
Intent intent = new Intent(android.content.Intent.ACTION_SEND);
intent.setType("image/*");
intent.putExtra(android.content.Intent.EXTRA_SUBJECT, "");
intent.putExtra(android.content.Intent.EXTRA_TEXT, "");
intent.putExtra(Intent.EXTRA_STREAM, uri);

startActivity(Intent.createChooser(intent,"Sharing something")));

这里的值85没有意义,因为PNG是无损的。文档中说:“一些格式(如PNG)是无损的,将忽略质量设置”。 - Minhaz

4

不压缩地将位图保存到您的相册。

private File saveBitMap(Context context, Bitmap Final_bitmap) {
    File pictureFileDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Your Folder Name");
    if (!pictureFileDir.exists()) {
        boolean isDirectoryCreated = pictureFileDir.mkdirs();
        if (!isDirectoryCreated)
            Log.i("TAG", "Can't create directory to save the image");
        return null;
    }
    String filename = pictureFileDir.getPath() + File.separator + System.currentTimeMillis() + ".jpg";
    File pictureFile = new File(filename);
    try {
        pictureFile.createNewFile();
        FileOutputStream oStream = new FileOutputStream(pictureFile);
        Final_bitmap.compress(Bitmap.CompressFormat.PNG, 100, oStream);
        oStream.flush();
        oStream.close();
        Toast.makeText(Full_Screen_Activity.this, "Save Image Successfully..", Toast.LENGTH_SHORT).show();
    } catch (IOException e) {
        e.printStackTrace();
        Log.i("TAG", "There was an issue saving the image.");
    }
    scanGallery(context, pictureFile.getAbsolutePath());
    return pictureFile;
}
private void scanGallery(Context cntx, String path) {
    try {
        MediaScannerConnection.scanFile(cntx, new String[]{path}, null, new MediaScannerConnection.OnScanCompletedListener() {
            public void onScanCompleted(String path, Uri uri) {
                Toast.makeText(Full_Screen_Activity.this, "Save Image Successfully..", Toast.LENGTH_SHORT).show();
            }
        });
    } catch (Exception e) {
        e.printStackTrace();
        Log.i("TAG", "There was an issue scanning gallery.");
    }
}

这两行代码需要放在花括号中!!! Log.i("TAG", "无法创建目录以保存图像"); return null; - mehrdad seyrafi

3
您想将位图保存到您选择的目录中。我制作了一个名为ImageWorker的库,使用户能够加载、保存和转换位图/可绘制对象/base64图像。

最低SDK-14

先决条件

  • 保存文件需要WRITE_EXTERNAL_STORAGE权限。
  • 检索文件需要READ_EXTERNAL_STORAGE权限。

保存位图/可绘制对象/Base64

ImageWorker.to(context).
    directory("ImageWorker").
    subDirectory("SubDirectory").
    setFileName("Image").
    withExtension(Extension.PNG).
    save(sourceBitmap,85)

加载位图

val bitmap: Bitmap? = ImageWorker.from(context).
    directory("ImageWorker").
    subDirectory("SubDirectory").
    setFileName("Image").
    withExtension(Extension.PNG).
    load()

实现

添加依赖项

在项目级别的Gradle中

allprojects {
        repositories {
            ...
            maven { url 'https://jitpack.io' }
        }
    }

在应用级别的Gradle中
dependencies {
            implementation 'com.github.ihimanshurawat:ImageWorker:0.51'
    }

您可以在https://github.com/ihimanshurawat/ImageWorker/blob/master/README.md上了解更多关于IT技术的内容。

2
创建视频缩略图,如果视频损坏或格式不支持,则可能返回空值。
private void makeVideoPreview() {
    Bitmap thumbnail = ThumbnailUtils.createVideoThumbnail(videoAbsolutePath, MediaStore.Images.Thumbnails.MINI_KIND);
    saveImage(thumbnail);
}

要将位图保存到sdcard中,请使用以下代码:

存储图像

private void storeImage(Bitmap image) {
    File pictureFile = getOutputMediaFile();
    if (pictureFile == null) {
        Log.d(TAG,
                "Error creating media file, check storage permissions: ");// e.getMessage());
        return;
    } 
    try {
        FileOutputStream fos = new FileOutputStream(pictureFile);
        image.compress(Bitmap.CompressFormat.PNG, 90, fos);
        fos.close();
    } catch (FileNotFoundException e) {
        Log.d(TAG, "File not found: " + e.getMessage());
    } catch (IOException e) {
        Log.d(TAG, "Error accessing file: " + e.getMessage());
    }  
}

获取图像存储路径
/** Create a File for saving an image or video */
private  File getOutputMediaFile(){
    // To be safe, you should check that the SDCard is mounted
    // using Environment.getExternalStorageState() before doing this. 
    File mediaStorageDir = new File(Environment.getExternalStorageDirectory()
            + "/Android/data/"
            + getApplicationContext().getPackageName()
            + "/Files"); 

    // This location works best if you want the created images to be shared
    // between applications and persist after your app has been uninstalled.

    // Create the storage directory if it does not exist
    if (! mediaStorageDir.exists()){
        if (! mediaStorageDir.mkdirs()){
            return null;
        }
    } 
    // Create a media file name
    String timeStamp = new SimpleDateFormat("ddMMyyyy_HHmm").format(new Date());
    File mediaFile;
        String mImageName="MI_"+ timeStamp +".jpg";
        mediaFile = new File(mediaStorageDir.getPath() + File.separator + mImageName);  
    return mediaFile;
} 

2

在调用bitmap.compress之前,请确保目录已创建:

new File(FileName.substring(0,FileName.lastIndexOf("/"))).mkdirs();

2
一些新设备不会保存位图,因此我稍微解释一下...请确保您已添加以下权限。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

xml文件夹下创建一个名为provider_paths.xml的xml文件。
<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path
        name="external_files"
        path="." />
</paths>

and in AndroidManifest under

<provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="${applicationId}.provider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths"/>
        </provider>

then simply call saveBitmapFile(passYourBitmapHere)

public static void saveBitmapFile(Bitmap bitmap) throws IOException {
        File mediaFile = getOutputMediaFile();
        FileOutputStream fileOutputStream = new FileOutputStream(mediaFile);
        bitmap.compress(Bitmap.CompressFormat.JPEG, getQualityNumber(bitmap), fileOutputStream);
        fileOutputStream.flush();
        fileOutputStream.close();
    }

where

File getOutputMediaFile() {
        File mediaStorageDir = new File(
                Environment.getExternalStorageDirectory(),
                "easyTouchPro");
        if (mediaStorageDir.isDirectory()) {

            // Create a media file name
            String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss")
                    .format(Calendar.getInstance().getTime());
            String mCurrentPath = mediaStorageDir.getPath() + File.separator
                            + "IMG_" + timeStamp + ".jpg";
            File mediaFile = new File(mCurrentPath);
            return mediaFile;
        } else { /// error handling for PIE devices..
            mediaStorageDir.delete();
            mediaStorageDir.mkdirs();
            galleryAddPic(mediaStorageDir);

            return (getOutputMediaFile());
        }
    }

and other methods

public static int getQualityNumber(Bitmap bitmap) {
        int size = bitmap.getByteCount();
        int percentage = 0;

        if (size > 500000 && size <= 800000) {
            percentage = 15;
        } else if (size > 800000 && size <= 1000000) {
            percentage = 20;
        } else if (size > 1000000 && size <= 1500000) {
            percentage = 25;
        } else if (size > 1500000 && size <= 2500000) {
            percentage = 27;
        } else if (size > 2500000 && size <= 3500000) {
            percentage = 30;
        } else if (size > 3500000 && size <= 4000000) {
            percentage = 40;
        } else if (size > 4000000 && size <= 5000000) {
            percentage = 50;
        } else if (size > 5000000) {
            percentage = 75;
        }

        return percentage;
    }

and

void galleryAddPic(File f) {
        Intent mediaScanIntent = new Intent(
                "android.intent.action.MEDIA_SCANNER_SCAN_FILE");
        Uri contentUri = Uri.fromFile(f);
        mediaScanIntent.setData(contentUri);

        this.sendBroadcast(mediaScanIntent);
    }


你有关于“针对PIE设备的错误处理”的更多信息吗?我猜如果解决方法失败,getOutputMediaFile中的递归可能会成为无限循环。 - Raimund Wege

1
自 Android 4.4 Kitkat 以后,截至 2017 年,Android 4.4 及以下版本的份额约为 20%,且逐渐减少,因此无法使用 File 类和 getExternalStorageDirectory() 方法保存到 SD 卡。该方法返回您设备的内部存储器,并且图像对每个应用程序都可见。您还可以使用 openFileOutput() 方法将图像仅保存为您的应用程序私有,并在用户删除您的应用程序时一并删除。
从 Android 6.0 开始,您可以将 SD 卡格式化为内部存储器,但仅限于您的设备私有。(如果您将 SD 卡格式化为内部存储器,则只有您的设备可以访问或查看其内容)。您可以使用其他答案将其保存到该 SD 卡,但如果要使用可移动 SD 卡,则应阅读下面的答案。
您应该使用 存储访问框架 来获取由用户选择的文件夹的 URI,然后在活动的 onActivityResult 方法中添加可检索的持久性权限,以便在用户重新启动设备后能够访问该文件夹。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (resultCode == RESULT_OK) {

        // selectDirectory() invoked
        if (requestCode == REQUEST_FOLDER_ACCESS) {

            if (data.getData() != null) {
                Uri treeUri = data.getData();
                tvSAF.setText("Dir: " + data.getData().toString());
                currentFolder = treeUri.toString();
                saveCurrentFolderToPrefs();

                // grantUriPermission(getPackageName(), treeUri,
                // Intent.FLAG_GRANT_READ_URI_PERMISSION |
                // Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

                final int takeFlags = data.getFlags()
                        & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
                // Check for the freshest data.
                getContentResolver().takePersistableUriPermission(treeUri, takeFlags);

            }
        }
    }
}

现在,将保存文件夹到共享偏好中,以免每次保存图像时向用户询问选择文件夹。
您应该使用DocumentFile类保存您的图像,而不是FileParcelFileDescriptor。有关更多信息,请参见this thread,了解使用compress(CompressFormat.JPEG,100,out)方法和DocumentFile类来将图像保存到SD卡的方法。

1

嘿,只需给.bmp命名即可。

做这个:

ByteArrayOutputStream bytes = new ByteArrayOutputStream();
_bitmapScaled.compress(Bitmap.CompressFormat.PNG, 40, bytes);

//you can create a new file name "test.BMP" in sdcard folder.
File f = new File(Environment.getExternalStorageDirectory()
                        + File.separator + "**test.bmp**")

虽然听起来像是我在胡闹,但试一次,它会以bmp格式保存...干杯


-1

// |==| 从位图创建PNG文件:

void devImjFylFnc(String pthAndFylTtlVar, Bitmap iptBmjVar)
{
    try
    {
        FileOutputStream fylBytWrtrVar = new FileOutputStream(pthAndFylTtlVar);
        iptBmjVar.compress(Bitmap.CompressFormat.PNG, 100, fylBytWrtrVar);
        fylBytWrtrVar.close();
    }
    catch (Exception errVar) { errVar.printStackTrace(); }
}

// |==| 从文件获取位图:

Bitmap getBmjFrmFylFnc(String pthAndFylTtlVar)
{
    return BitmapFactory.decodeFile(pthAndFylTtlVar);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接