【问题标题】:Save bitmap to location将位图保存到位置
【发布时间】:2010-10-13 13:04:53
【问题描述】:

我正在开发一个功能,从网络服务器下载图像,将其显示在屏幕上,如果用户希望保留图像,请将其保存在 SD 卡上的某个文件夹中。有没有一种简单的方法来获取位图并将其保存到我选择的文件夹中的 SD 卡中?

我的问题是我可以下载图像,将其作为位图显示在屏幕上。我能够找到将图像保存到特定文件夹的唯一方法是使用 FileOutputStream,但这需要一个字节数组。我不确定如何将 Bitmap 转换为字节数组(如果这是正确的方法),所以我可以使用 FileOutputStream 来写入数据。

我的另一个选择是使用 MediaStore :

MediaStore.Images.Media.insertImage(getContentResolver(), bm,
    barcodeNumber + ".jpg Card Image", barcodeNumber + ".jpg Card Image");

这可以很好地保存到 SD 卡,但不允许您自定义文件夹。

【问题讨论】:

  • 正是我在我的应用程序中所做的。我下载了一个大型图像表单网络服务器对其进行操作,并在我的onTaskComplete() 回调中通过mImage.setImageBitmap(_result.getBitmap()); 将位图直接加载到图像视图中。我现在必须允许用户通过长按上下文菜单在本地保存文件。我应该能够使用下面的解决方案。不过我想知道的是,您是否发现了更好的方法?
  • 这里有一种优雅的方式:stackoverflow.com/questions/4263375/…
  • 在这里查看我的答案stackoverflow.com/a/68110559/6039240

标签: android bitmap save


【解决方案1】:

我知道这个问题很老了,但现在我们可以在没有WRITE_EXTERNAL_STORAGE 许可的情况下达到同样的结果。我们可以使用文件提供程序来代替。

private fun storeBitmap(bitmap: Bitmap, file: File){
        requireContext().getUriForFile(file)?.run {
            requireContext().contentResolver.openOutputStream(this)?.run {
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, this)
                close()
            }
        }
    }

如何从提供者那里获取文件?

fun Context.getUriForFile(file: File): Uri? {
        return FileProvider.getUriForFile(
            this,
            "$packageName.fileprovider",
            file
        )
    }

别忘了在 Android manifest 中注册您的 provider

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

【讨论】:

    【解决方案2】:

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

    最小 SDK - 14

    先决条件

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

    保存位图/Drawable/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阅读更多内容

    【讨论】:

      【解决方案3】:

      将位图保存到您的画廊而不进行压缩。

      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", "无法创建目录保存图片");返回空值;
      【解决方案4】:

      有些新设备不保存位图所以我解释了一点..

      确保您已在权限下添加

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

      并在xml文件夹名称下创建一个xml文件provider_paths.xml

      <?xml version="1.0" encoding="utf-8"?>
      <paths>
          <external-path
              name="external_files"
              path="." />
      </paths>
      

      并在AndroidManifest下

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

      然后只需调用 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();
          }
      

      在哪里

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

      和其他方法

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

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

      【讨论】:

      • 你有更多关于error handling for PIE devices.. 的信息吗?我猜如果解决方法失败,getOutputMediaFile 中的递归可能是一个无限循环。
      【解决方案5】:
      try (FileOutputStream out = new FileOutputStream(filename)) {
          bmp.compress(Bitmap.CompressFormat.PNG, 100, out); // bmp is your Bitmap instance
          // PNG is a lossless format, the compression factor (100) is ignored
      } catch (IOException e) {
          e.printStackTrace();
      }
      

      【讨论】:

      • 我也压缩了图像,但压缩到了 100%,当我在画布中获取图像时,它非常小。有什么原因吗?
      • @Aizaz 这不会改变图像的大小,只会改变格式和(可能的)质量。还值得注意的是,上面示例中的压缩质量90 在保存为 PNG 时不会有任何影响,但会对 JPEG 产生影响。对于 JPEG,您可以选择 0 到 100 之间的任何数字。
      • 应该注意,以这种方式保存 100% 质量的 .JPEG 实际上会保存与网络上的原始图像不同的图像(至少会占用更多空间),请考虑替代方法。
      • 需要重新压缩吗?我只想保存原始图像。
      • @HeinduPlessis 不必这样做,但您可能应该这样做。保存原始位图将占用更多空间,具体取决于格式(例如 ARGB_4444 与 ARGB_8888)。
      【解决方案6】:

      为视频创建视频缩略图。如果视频损坏或格式不受支持,它可能会返回 null。

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

      【讨论】:

        【解决方案7】:

        在 Android 4.4 Kitkat 之后,截至 2017 年,Android 4.4 及以下的份额约为 20% 并且正在下降,使用 File 类和 getExternalStorageDirectory() 方法无法保存到 SD 卡。此方法返回您的设备内存和图像保存对每个应用程序可见。您还可以仅将图像保存为您的应用程序私有,并在用户使用openFileOutput() 方法删除您的应用程序时将其删除。

        从 Android 6.0 开始,您可以将 SD 卡格式化为内存,但仅限于您的设备。(如果您将 SD 卡格式化为内存,则只有您的设备可以访问或查看其内容)您可以保存到该内存SD 卡使用其他答案,但如果您想使用可移动 SD 卡,您应该阅读下面的答案。

        您应该使用 Storage Access Framework 将 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 卡。

        【讨论】:

          【解决方案8】:

          // |==|从位图创建一个 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(); }
          }
          

          // |==|从文件中获取 Bimap:

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

          【讨论】:

            【解决方案9】:

            您应该使用Bitmap.compress() 方法将位图保存为文件。它将压缩(如果使用的格式允许)您的图片并将其推送到 OutputStream。

            这里是一个通过getImageBitmap(myurl)获得的Bitmap实例的例子,可以压缩成JPEG,压缩率为85%:

            // Assume block needs to be inside a Try/Catch block.
            String path = Environment.getExternalStorageDirectory().toString();
            OutputStream fOut = null;
            Integer counter = 0;
            File file = new File(path, "FitnessGirl"+counter+".jpg"); // the File to save , append increasing numeric counter to prevent files from getting overwritten.
            fOut = new FileOutputStream(file);
            
            Bitmap pictureBitmap = getImageBitmap(myurl); // obtaining the Bitmap
            pictureBitmap.compress(Bitmap.CompressFormat.JPEG, 85, fOut); // saving the Bitmap to a file compressed as a JPEG with 85% compression rate
            fOut.flush(); // Not really required
            fOut.close(); // do not forget to close the stream
            
            MediaStore.Images.Media.insertImage(getContentResolver(),file.getAbsolutePath(),file.getName(),file.getName());
            

            【讨论】:

            • @JoaquinG fOut.flush() 有什么理由可以省略吗?
            • @Niklas 我认为你可以省略flush。
            • 您应该将措辞从“85% 的压缩率”更改为“85% 的质量率”以减少歧义。我会将“85% 的压缩率”解释为“15% 的质量”,但 Bitmap.compress 的 int 参数指定了质量。
            • 你能把方法贴出来getImageBitmap(myurl)
            【解决方案10】:

            这是将位图保存到文件的示例代码:

            public static File savebitmap(Bitmap bmp) throws IOException {
                ByteArrayOutputStream bytes = new ByteArrayOutputStream();
                bmp.compress(Bitmap.CompressFormat.JPEG, 60, bytes);
                File f = new File(Environment.getExternalStorageDirectory()
                        + File.separator + "testimage.jpg");
                f.createNewFile();
                FileOutputStream fo = new FileOutputStream(f);
                fo.write(bytes.toByteArray());
                fo.close();
                return f;
            }
            

            现在调用此函数将位图保存到内存中。

            File newfile = savebitmap(bitmap);

            希望对你有所帮助。 快乐的编码生活。

            【讨论】:

              【解决方案11】:

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

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

              【讨论】:

                【解决方案12】:

                内部onActivityResult

                String filename = "pippo.png";
                File sd = Environment.getExternalStorageDirectory();
                File dest = new File(sd, filename);
                
                Bitmap bitmap = (Bitmap)data.getExtras().get("data");
                try {
                     FileOutputStream out = new FileOutputStream(dest);
                     bitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
                     out.flush();
                     out.close();
                } catch (Exception e) {
                     e.printStackTrace();
                }
                

                【讨论】:

                • 您称它为“pippo.jpg”,但您使用的是 PNG 压缩
                • 如果你想改变位图的质量,压缩格式应该是.JPEG。无法以 PNG 格式更改质量。
                【解决方案13】:
                Bitmap bbicon;
                
                bbicon=BitmapFactory.decodeResource(getResources(),R.drawable.bannerd10);
                //ByteArrayOutputStream baosicon = new ByteArrayOutputStream();
                //bbicon.compress(Bitmap.CompressFormat.PNG,0, baosicon);
                //bicon=baosicon.toByteArray();
                
                String extStorageDirectory = Environment.getExternalStorageDirectory().toString();
                OutputStream outStream = null;
                File file = new File(extStorageDirectory, "er.PNG");
                try {
                    outStream = new FileOutputStream(file);
                    bbicon.compress(Bitmap.CompressFormat.PNG, 100, outStream);
                    outStream.flush();
                    outStream.close();
                } catch(Exception e) {
                
                }
                

                【讨论】:

                • 如果您在 'compress' 方法中传递它,则不需要刷新 outStream。该方法将代表您。
                【解决方案14】:

                我发现发送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,将忽略质量设置`
                【解决方案15】:
                outStream = new FileOutputStream(file);
                

                会在AndroidManifest.xml中未经许可抛出异常(至少在os2.2中):

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

                【讨论】:

                • 如果您的文件 absolutePath 是内部路径,则不是?
                【解决方案16】:

                我还想保存一张图片。但我的问题(?)是我想从我绘制的位图中保存它。

                我是这样设计的:

                 @Override
                 public boolean onOptionsItemSelected(MenuItem item) {
                            switch (item.getItemId()) {
                            case R.id.save_sign:      
                
                                myView.save();
                                break;
                
                            }
                            return false;    
                
                    }
                
                public void save() {
                            String filename;
                            Date date = new Date(0);
                            SimpleDateFormat sdf = new SimpleDateFormat ("yyyyMMddHHmmss");
                            filename =  sdf.format(date);
                
                            try{
                                 String path = Environment.getExternalStorageDirectory().toString();
                                 OutputStream fOut = null;
                                 File file = new File(path, "/DCIM/Signatures/"+filename+".jpg");
                                 fOut = new FileOutputStream(file);
                
                                 mBitmap.compress(Bitmap.CompressFormat.JPEG, 85, fOut);
                                 fOut.flush();
                                 fOut.close();
                
                                 MediaStore.Images.Media.insertImage(getContentResolver()
                                 ,file.getAbsolutePath(),file.getName(),file.getName());
                
                            }catch (Exception e) {
                                e.printStackTrace();
                            }
                
                 }
                

                【讨论】:

                • 您的保存方法只对我有效.. 浪费了几个小时之后.. 非常感谢先生。
                【解决方案17】:

                为什么不用 100 调用Bitmap.compress 方法(听起来是无损的)?

                【讨论】:

                • 即使它被忽略,它也应该是 100。如果有一天压缩格式更改为松散格式,那么图像将与松散格式最接近。另请注意,如果您有抽象此调用的代码,这可能更重要。
                • 100% 在 JPEG、FWIW 中不是无损的。您可以通过重复加载和保存位图来验证这一点。
                【解决方案18】:

                嘿,只需将名称命名为 .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 格式中就试试吧..干杯

                【讨论】:

                  【解决方案19】:

                  某些格式,例如无损的 PNG,会忽略质量设置。

                  【讨论】:

                  • PNG 仍然是一种压缩格式。质量设置不会修改压缩质量吗?
                  • 文档状态(由我突出显示):提示压缩器,0-100。 0 表示压缩为小尺寸,100 表示压缩为最大质量。 某些格式,例如无损的 PNG,会忽略质量设置
                  猜你喜欢
                  • 1970-01-01
                  • 2012-01-29
                  • 1970-01-01
                  • 1970-01-01
                  • 2020-12-25
                  • 1970-01-01
                  相关资源
                  最近更新 更多