代码之家  ›  专栏  ›  技术社区  ›  Louis

将JPG图像压缩到给定大小(KB)?

  •  0
  • Louis  · 技术社区  · 8 年前

    我有一个由摄像机在输入中返回的字节数组。

    我需要缩小我的图像,使其大小等于500KB。我试图使用位图实现这一点,但我找不到如何获得正确的 compression 价值

    public static byte[] compressCapture(byte[] capture) {
    
        // How to get the right compression value ?
        int compression = 50;
    
        Bitmap bitmap  = BitmapFactory.decodeByteArray(capture, 0, capture.length);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, compression, outputStream);
        return outputStream.toByteArray();
    }
    

    问题是压缩与图像大小不成比例,这意味着 压缩 等于50不会将图像大小除以2。

    有没有一种方法可以在输出中获得不依赖于焦点或智能手机型号的固定图像大小?

    编辑:

    我不想使用文件(我处理机密数据)。

    6 回复  |  直到 8 年前
        1
  •  2
  •   Louis    8 年前

    2018年1月16日更新

    最简单的方法是使用 BitmapFactory.Options.inSampleSize 解码字节数组并同时压缩。 Here is the doc

     BitmapFactory.Options options = new BitmapFactory.Options();
     options.inSampleSize = 4; // If you want an image four times smaller than the original
     Bitmap decoded = BitmapFactory.decodeByteArray(data, 0, data.length, options);
    

    旧答案,请不要用这个

    由于显然无法一次性实现这一点,我实现了一个迭代过程,以达到给定的大小(KB)。

    我从一个等于80的压缩系数开始,如果这还不够,我就减小这个系数,然后重试,直到得到一个低于阈值的大小。

    static COMPRESSION_PERCENTAGE_START = 80;
    static IMAGE_COMPRESSION_EXPECTED_MAX_ITERATIONS = 3;
    static IMAGE_COMPRESSION_STEP_PERCENT = 5;
    
    // For logging
    static IMAGE_COMPRESSION_EXPECTED_MAX_ITERATIONS = 5;
    
    static byte[] compressCapture(byte[] capture, int maxSizeKB) {
        long maxSizeByte = ((long) maxSizeKB) * 1_000;
    
        if (capture.length <= maxSizeByte) return capture;
    
        byte[] compressed = capture;
    
        // Chosen arbitrarily so that we can compress multiple times to reach the expected size.
        int compression = COMPRESSION_PERCENTAGE_START;
    
        Bitmap bitmap = BitmapFactory.decodeByteArray(capture, 0, capture.length);
        ByteArrayOutputStream outputStream;
        int iterations = 0;
        while (compressed.length > maxSizeByte) {
            // Just a counter
            iterations++;
    
            compression -= IMAGE_COMPRESSION_STEP_PERCENT;
    
            outputStream = new ByteArrayOutputStream();
            bitmap.compress(Bitmap.CompressFormat.JPEG, compression, outputStream);
            compressed = outputStream.toByteArray();
        }
    
        if (iterations > IMAGE_COMPRESSION_EXPECTED_MAX_ITERATIONS) {
            Timber.w("Compression process has iterated more than expected, with " + iterations + " iterations.");
        }
    
        return compressed;
    }
    

    以下是原始大小的输出大小 1_871_058 字节数

    • 迭代#1(压缩等于80): 528_593 字节数
    • 迭代#2(压缩等于75): 456_591 字节数

    这对我来说很有用,但如果你使用它,请小心,它肯定需要一些微调,我刚刚在一个给定的智能手机模型上测试了它。

        2
  •  1
  •   user3344003    8 年前

    您面临的问题是,压缩取决于图像的性质。我的猜测是,有学术研究关注图像并选择参数以达到给定大小。

    在JPEG中,您可以使用多种设置来缩小尺寸:

    1. COM和APPn标记的包含或排除。
    2. 使用优化或现成的哈夫曼表。
    3. 量化表的选择。
    4. Cb和Cr组件的二次采样。
    5. 渐进编码或顺序编码(如果是前者,您可以使用过多的扫描设置)。

    不过,这主要是试错。

        3
  •  0
  •   lmaooooo    8 年前

    很难压缩到一个给定的大小,我建议使用ImageMagick并调整选项,直到你达到一个适合使用的大小。

    我总是使用ImageMagick。考虑此命令

    convert -strip -interlace Plane -gaussian-blur 0.05 -quality 85% source.jpg result.jpg
    

    该命令使用以下选项:

    • 85中的质量
    • 渐进式(压缩)
    • 用于优化大小(半径的0.05或0.5)的非常小的高斯模糊取决于图片的质量和大小,这一点值得注意 优化jpeg的大小。
    • 去掉任何注释或exif标记

    Source

    尝试调整 质量参数 . 如果减少,则会增加压缩并减小文件大小。

        4
  •  0
  •   Shivam Mathur    8 年前

    首先,为了获得更好的图片质量,请将文件保存到存储器中,然后对其进行压缩。它帮助用户拥有最佳质量的图像供自己使用。 在我的一些项目中,我正在使用这个库 https://android-arsenal.com/details/1/3758 这是一个很好的图书馆,压缩效果很好。 如果需要文件,请使用此

    compressedImage = new Compressor(this)
            .setMaxWidth(640)
            .setMaxHeight(480)
            .setQuality(75)
            .setCompressFormat(Bitmap.CompressFormat.WEBP)
            .setDestinationDirectoryPath(Environment.getExternalStoragePublicDirectory(
              Environment.DIRECTORY_PICTURES).getAbsolutePath())
            .compressToFile(actualImage);
    

    或者如果你需要一个比这个更大的位图

    compressedImageBitmap = new Compressor(this).compressToBitmap(actualImageFile);
    

    快乐编码:)

        5
  •  0
  •   Bonzo    8 年前

    没有注意到@dlemstra的回复

    Imagemagick有一个文件大小选项,但速度较慢,因为它在内存中尝试了不同的输出大小选项,并且不精确:

    convert input -define jpeg:extent=500kb output.jpg
    

    当然,您的手机可能不支持define。

        6
  •  -1
  •   dlemstra    8 年前

    你可以用魔法。净额( https://github.com/dlemstra/Magick.NET )为此:

    using (var image = new MagickImage(capture))
    {
        return image.ToByteArray(new JpegWriteDefines()
        {
            // Will make the maximum size 500KB (it might be smaller)
            Extent = 500
        });
    }