如何将图像大小减少为1MB

7
我希望我的应用程序能够上传没有大小限制的图片,但是在代码中,如果图片大小超过1MB,我希望将其调整为1MB。我尝试了很多方法,但是无法找到符合我要求的代码。
曾经我试过以下代码:
public void scaleDown() {
    int width = stdImageBmp.getWidth();
    int height = stdImageBmp.getHeight();
    Matrix matrix = new Matrix();
    float scaleWidth = ((float) MAX_WIDTH) / width;
    float scaleHeight = ((float) MAX_HEIGHT) / height;


    matrix.postScale(scaleWidth, scaleHeight);
    stdImageBmp = Bitmap.createBitmap(stdImageBmp, 0, 0, width, height, matrix, true);

    File Image = new File("path");


    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    //compress bmp
    stdImageBmp.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream);
    byte[] byteArray = byteArrayOutputStream.toByteArray();


    imgViewStd.setImageBitmap(stdImageBmp);
    Log.d("resizedBitmap", stdImageBmp.toString());

    width = stdImageBmp.getWidth();
    height = stdImageBmp.getHeight();
    System.out.println("imgWidth" + width);
    System.out.println("imgHeight" + height);
}
3个回答

7

您可以使用此代码调整位图大小,对于图像大小小于1MB的情况,建议使用分辨率为480x640

public Bitmap getResizedBitmap(Bitmap bm, int newWidth, int newHeight) {
        int width = bm.getWidth();
        int height = bm.getHeight();
        float scaleWidth = ((float) newWidth) / width;
        float scaleHeight = ((float) newHeight) / height;
        // CREATE A MATRIX FOR THE MANIPULATION
        Matrix matrix = new Matrix();
        // RESIZE THE BIT MAP
        matrix.postScale(scaleWidth, scaleHeight);

        // "RECREATE" THE NEW BITMAP
        return Bitmap.createBitmap(
                bm, 0, 0, width, height, matrix, false);
    }

生成的图像大小将略大于1MB:480 x 640 x 4 = 1228800字节。 - Phantômaxx
实际上它是480 x 640 x 3 = 921600字节,小于1MB。 - M.Waqas Pervez
不,实际上是480 x 640 x 4:R、G、B和A。它是4个字节,而不是3个。 - Phantômaxx
请问您能否提供一些可靠的来源? - M.Waqas Pervez
1
谢谢 @BobMalooga。学到了一件新的事情。 - M.Waqas Pervez

4
如果您真的想要缩小到最接近给定字节数的位图,请使用我使用的方法。(它不使用while循环)
注意:此方法仅适用于传递的位图处于ARGB_8888配置中。请参见:在Android中将位图压缩到特定字节大小的转换方法
/**
 * Method to scale the Bitmap to respect the max bytes
 *
 * @param input    the Bitmap to scale if too large
 * @param maxBytes the amount of bytes the Image may be
 * @return The scaled bitmap or the input if already valid
 * @Note: The caller of this function is responsible for recycling once the input is no longer needed
 */
public static Bitmap scaleBitmap(final Bitmap input, final long maxBytes) {
    final int currentWidth = input.getWidth();
    final int currentHeight = input.getHeight();
    final int currentPixels = currentWidth * currentHeight;
    // Get the amount of max pixels:
    // 1 pixel = 4 bytes (R, G, B, A)
    final long maxPixels = maxBytes / 4; // Floored
    if (currentPixels <= maxPixels) {
        // Already correct size:
        return input;
    }
    // Scaling factor when maintaining aspect ratio is the square root since x and y have a relation:
    final double scaleFactor = Math.sqrt(maxPixels / (double) currentPixels);
    final int newWidthPx = (int) Math.floor(currentWidth * scaleFactor);
    final int newHeightPx = (int) Math.floor(currentHeight * scaleFactor);
    Timber.i("Scaled bitmap sizes are %1$s x %2$s when original sizes are %3$s x %4$s and currentPixels %5$s and maxPixels %6$s and scaled total pixels are: %7$s",
            newWidthPx, newHeightPx, currentWidth, currentHeight, currentPixels, maxPixels, (newWidthPx * newHeightPx));
    final Bitmap output = Bitmap.createScaledBitmap(input, newWidthPx, newHeightPx, true);
    return output;
}

示例用法可能如下:

// (1 MB)
final long maxBytes = 1024 * 1024; 
// Scale it
final Bitmap scaledBitmap = BitmapUtils.scaleBitmap(yourBitmap, maxBytes);
if(scaledBitmap != yourBitmap){
    // Recycle the bitmap since we can use the scaled variant:
    yourBitmap.recycle();
} 
// ... do something with the scaled bitmap


你的例子中为什么要除以4? - nutella_eater
1
如在ARGB_8888配置中提到的那样,存储一个像素的信息需要4个字节。请参见https://developer.android.com/reference/android/graphics/Bitmap.Config。 - Koen

1

我试图对其进行评论,但评论变得太大了。 因此,我尝试了许多解决方案,似乎只有两种解决方案可以解决这个问题,并且可以产生一些结果。

首先让我们讨论Koen的解决方案。它实际上是创建了一个缩放后的JPG文件。

Bitmap.createScaledBitmap(input, newWidthPx, newHeightPx, true)

似乎它并没有压缩图片,而只是削减了分辨率。
我已经测试过这段代码,当我传递MAX_IMAGE_SIZE = 1024000时,它会给我一个350kb的压缩图像,原始图像大小为2.33Mb。这是个bug吗? 此外,它也缺乏质量。我无法识别由Google Pixel拍摄的A4纸张照片上的文字。
还有另一种解决方案,它提供了良好的质量,但速度较慢。 一个WHILE LOOP! 基本上,您只需循环遍历图像大小,直到获得所需大小。
private fun scaleBitmap() {
    if (originalFile.length() > MAX_IMAGE_SIZE) {
        var streamLength = MAX_IMAGE_SIZE
        var compressQuality = 100
        val bmpStream = ByteArrayOutputStream()
        while (streamLength >= MAX_IMAGE_SIZE) {
            bmpStream.use {
                it.flush()
                it.reset()
            }

            compressQuality -= 8
            val bitmap = BitmapFactory.decodeFile(originalFile.absolutePath, BitmapFactory.Options())
            bitmap.compress(Bitmap.CompressFormat.JPEG, compressQuality, bmpStream)
            streamLength = bmpStream.toByteArray().size
        }

        FileOutputStream(compressedFile).use {
            it.write(bmpStream.toByteArray())
        }
    }
}

我认为这种方法将会消耗指数时间,取决于图像分辨率。 9MB的图像需要最多12秒钟才能压缩到1MB。 质量很好。 您可以通过减少原始位图分辨率来调整此操作(似乎是一个恒定的操作),方法如下:
options.inSampleSize = 2;
我们需要做的是以某种方式计算任何图像的压缩质量。 应该有一些数学公式,因此我们可以从原始图像大小或宽度+高度确定压缩质量。

我已经删除了问号,以使其更清晰易懂。 - nutella_eater
就我的实现而言,它只进行缩放,因此被命名为“scaleBitmap”是正确的。 但由于它在缩小图像时会降低质量,这一点需要注意。它仅适用于 ARGB_8888 配置的位图。如果不是这种配置的位图,则会产生错误的结果(大约 350 KB 而非近 1 MB)。 - Koen

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