在 Android 中压缩图像以上传到服务器的过程

Process to compress an image for uploading to a server in Android

我想编写自己的代码来压缩图片,然后再将其上传到服务器。

我在 SO 上看过很多帖子,例如 this,还有很多其他帖子,但到处都只有代码示例。它实际上没有在任何地方解释。我无法从给定的代码中理解。

我只是想知道压缩图片的整体方法,这样我就可以开始自己写代码了。

我不是在寻找代码,只是在寻找一个人压缩图像所需遵循的步骤。 (就像在编写程序之前先编写算法即伪代码)

由于您实际上并不是在寻找代码,因此这里是了解图像压缩背后的思维过程的绝佳资源 Data Compression Theory

有多种算法利用不同的技术来利用所涉及的不同变量。计算复杂性、存储 space、压缩率和保持图像质量都是这些不同算法(或文件格式)寻求优化的因素,通常偏向于一个属性而不是另一个。例如 PNG 与 JPEG 的质量对比 space

Java 提供 ImageIO APIs 可以帮助您根据您选择的质量和压缩系数来压缩图像。 ImageWriterImageWriteParam 提供开箱即用的上述功能。

出于其他原因,您是否打算根据您的要求编写自己的 API?

如果不使用开箱即用的 java APIs,高级方法可能是: 1) 读取图像文件。 2)使用 ImageIO ,根据图像类型(jpeg、png 等)获取 ImageWriter 3) 从编写器获取 ImageWriteParam 对象并根据您的要求设置压缩参数。 4) 写入图像。

如果它对你不起作用,请告诉我。

您 link 中的代码描述的不是太多的压缩,而是以较低的密度对图像进行采样并缩小它。所以它不是真正的压缩,而是降低质量(像素数量和大小)以使其更小。好的压缩算法通常会找到一种方法来保存图像,使用较少 space 而不会过多地影响质量(使用数学和东西)。

Android 中的图像压缩意味着我们正在使用位图,而我们正在压缩任何大图像以降低我们在位图中使用压缩方法 class 但是如果原始位图的大小与想要的位图太高,位图质量总是降低。

为了高质量地压缩位图,我们必须使用递归过程来压缩任何位图,而不是直接将任何位图从原始高度宽度压缩到想要的高度和宽度,我们应该用 3 到 5 个步骤压缩图像,例如,如果我们的图像大小是 1000x1000 并且想要的宽度和高度是 300x300 然后我们应用递归过程来执行此操作,如下所示:

1000 x 1000

850 x 850

650 x 650

300 x 300 

这样我们的位图质量不会降低,但请确保不要每次都创建新的位图对象使用相同的位图对象来分配压缩位图,否则您必须面临 OutOfmemmory 问题。

我认为 Chetan Joshi 的回答是目前为止最接近的。但进一步解释一下,

"Compressing" 图像和 "scaling"(调整大小)图像不同。由于您只是想在上传到服务器时减少网络带宽 (kb/Mb),因此您很可能在谈论 "scaling",因为这对图像大小的影响最大。

请注意,与其他答案 Android 一样,您需要使用 Bitmaps。您需要使用与位图关联的 API 调用来执行您想要的操作。

起点:

如果您允许您的应用使用相机拍照或从图库中选择照片,您将获得一个 Uri,它允许您像这样打开一个 InputStream:

InputStream is = getContext().getContentResolver().openInputStream(photoUri);

这是第一步。下一部分是你想要这样的伪代码:

  1. 获取 photo/selected 图片的 InputStream。这只是表示图像的字节流。
  2. 使用BitmapFactory.decodeStream(InputStream src)
  3. 将流解码为Bitmap
  4. 使用 Bitmap.createScaledBitmap(src, dstWidth, dstHeight, filter); 缩放位图(调整大小)。您必须自己决定目标宽度和目标高度。见下一组伪代码。
  5. 使用 imageFormat.getBitmapCompressFormat(), 100, output) 压缩位图,其中输出是 ByteArrayOutputStream
  6. 的一个实例
  7. 您现在可以调用 output.toByteArray(); 来获取字节。
  8. 字节是您要发送到服务器的内容。

关于如何选择要缩放到的目标宽度和高度的注意事项:

  1. 确定最大高度和最大宽度,比如 240 x 240。您将把它与位图的实际高度和实际宽度进行比较。
  2. 如果太高(实际高度 > 最大高度),计算一个比例因子,即最大高度 / 实际高度
  3. 如果太宽(实际宽度 > 最大宽度),计算一个比例因子,即最大宽度 / 实际宽度
  4. 您的实际比例因子是这两个比例因子中的最小值
  5. Return实际高度*比例因子和实际宽度*比例因子

总的来说,高级过程是这样的:

  1. 打开图像字节流
  2. 将字节转换为位图
  3. 缩放位图
  4. 压缩位图
  5. 将位图转换回字节

裁剪图像然后发送到 server.Its 效果不错,

将依赖添加到 gradle,

compile 'com.soundcloud.android:android-crop:1.0.1@aar'

图库意图,

private static final int PICK_FROM_FILE = 3;
private Uri mImageCaptureUri=null;
File sdCard = Environment.getExternalStorageDirectory();
String pathName;
File filepath;

Intent i = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(i, PICK_FROM_FILE);

 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (resultCode != RESULT_OK)
        return;
     switch (requestCode) {
             case PICK_FROM_FILE:
               mImageCaptureUri = data.getData();
               // METHOD FOR CROP
               beginCrop(mImageCaptureUri);
               System.out.println("CHECK_GALLERY_path :" + mImageCaptureUri);
              break;
              case Crop.REQUEST_CROP:
              handleCrop(resultCode, data);
              break;
     }
  }

  private void beginCrop(Uri source) {
    Uri destination = Uri.fromFile(new File(getCacheDir(), "cropped"));
    Crop.of(source, destination).start(this);
 }

  private void handleCrop(int resultCode, Intent result) {

    if (resultCode == RESULT_OK) {

        mImageCaptureUri = Crop.getOutput(result);

        pathName = mImageCaptureUri.getPath();
        //uploadImageToServer();
        //  getImageUri(mImageCaptureUri);
                       Picasso.with(Activity_My_Account.this).load(mImageCaptureUri).into(getimage);          //IMAGE SAVE TO STORAGE
        getImageUri(mImageCaptureUri);
        System.out.println("path_name+++" + mImageCaptureUri);


    } else if (resultCode == Crop.RESULT_ERROR) {
        Toast.makeText(this, Crop.getError(result).getMessage(),        Toast.LENGTH_SHORT).show();
    }
}


    private void getImageUri(Uri mImageCaptureUri) {
     Bitmap photo = null;
     try {
        InputStream image_stream = getContentResolver().openInputStream(mImageCaptureUri);
        java.util.Date date = new java.util.Date();
         pathName = sdCard + "/" + "LEUF";
            File myDir = new File(pathName);
        System.out.println("GET_CHECK_PATH_NAME :" + pathName);
        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();
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        Bitmap bitmapOrg = Bitmap.createScaledBitmap(BitmapFactory.decodeStream(image_stream), 350, 350, false);
        photo.compress(Bitmap.CompressFormat.JPEG, 100, bytes);
         filepath=file;
         try {
            // file.createNewFile();
            FileOutputStream fo = new FileOutputStream(file);
            fo.write(bytes.toByteArray());
            fo.flush();
            fo.close();
        } catch (IOException e) {

            e.printStackTrace();
        }
      //here call to upload image to server
        public void UploadProfilePic(filepath){
        }

     } catch (FileNotFoundException e) {
        e.printStackTrace();
    }
}

您可以只使用 android SDK - bitmap.compress();

ByteArrayOutputStream bos = new ByteArrayOutputStream();

if (myBitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos)) {
    //image is now compressed into the output stream
    uploadImage(bos);
} else {
    //compress failed
}

您可以随时查看 android 源代码以了解更多信息,但如果您是 android 开发人员,我认为花时间学习如何压缩不应该是您的主要关注点特别是当你有这样一个简单的解决方案时,如果你仍然认为你这样做,我会先学习如何压缩文件,然后我会转向图像,然后我才会查看源代码