我在一个文件中有一个大的位图(比如3888x2592)。现在,我想将位图大小调整为800x533,并将其保存到另一个文件中。 我通常通过调用bitmap来缩放位图。createBitmap方法,但它需要一个源位图作为第一个参数,我不能提供,因为加载原始图像到位图对象当然会超过内存(例如,见这里)。

我也不能读取位图,例如,BitmapFactory.decodeFile(文件,选项),提供BitmapFactory.Options。inSampleSize,因为我想把它调整到精确的宽度和高度。使用inSampleSize将位图大小调整为972x648(如果我使用inSampleSize=4)或778x518(如果我使用inSampleSize=5,这甚至不是2的幂)。

我还希望避免在第一步中使用inSampleSize读取图像,例如,在第一步中使用972x648,然后在第二步中将其大小调整为800x533,因为与直接调整原始图像的大小相比,质量会较差。

总结一下我的问题: 是否有一种方法来读取一个10MP或更多的大图像文件,并将其保存到一个新的图像文件,调整到一个特定的新宽度和高度,而不得到OutOfMemory异常?

我还尝试了BitmapFactory.decodeFile(文件,选项)和设置选项。out theight and Options。outidth值手动为800和533,但它不是这样工作的。


当前回答

为了以“正确”的方式缩放图像,而不跳过任何像素,您必须连接到图像解码器来逐行执行下采样。Android(以及基于它的Skia库)没有提供这样的钩子,所以你必须自己滚动。假设您谈论的是jpeg图像,最好的办法是直接使用C语言中的libjpeg。

考虑到其中的复杂性,对于图像预览类型的应用程序来说,使用两步先采样再缩放的方法可能是最好的。

其他回答

这是“Mojo Risin”和“Ofir”解决方案的“结合”。这将给你一个按比例调整图像的最大宽度和最大高度的边界。

它只读取元数据以获得原始大小(选项。 它使用粗略的调整大小来节省内存(itmap.createScaledBitmap) 它使用了基于之前创建的粗糙Bitamp精确调整大小的图像。

对我来说,它在500万像素以下的图像上表现良好。

try
{
    int inWidth = 0;
    int inHeight = 0;

    InputStream in = new FileInputStream(pathOfInputImage);

    // decode image size (decode metadata only, not the whole image)
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(in, null, options);
    in.close();
    in = null;

    // save width and height
    inWidth = options.outWidth;
    inHeight = options.outHeight;

    // decode full image pre-resized
    in = new FileInputStream(pathOfInputImage);
    options = new BitmapFactory.Options();
    // calc rought re-size (this is no exact resize)
    options.inSampleSize = Math.max(inWidth/dstWidth, inHeight/dstHeight);
    // decode full image
    Bitmap roughBitmap = BitmapFactory.decodeStream(in, null, options);

    // calc exact destination size
    Matrix m = new Matrix();
    RectF inRect = new RectF(0, 0, roughBitmap.getWidth(), roughBitmap.getHeight());
    RectF outRect = new RectF(0, 0, dstWidth, dstHeight);
    m.setRectToRect(inRect, outRect, Matrix.ScaleToFit.CENTER);
    float[] values = new float[9];
    m.getValues(values);

    // resize bitmap
    Bitmap resizedBitmap = Bitmap.createScaledBitmap(roughBitmap, (int) (roughBitmap.getWidth() * values[0]), (int) (roughBitmap.getHeight() * values[4]), true);

    // save image
    try
    {
        FileOutputStream out = new FileOutputStream(pathOfOutputImage);
        resizedBitmap.compress(Bitmap.CompressFormat.JPEG, 80, out);
    }
    catch (Exception e)
    {
        Log.e("Image", e.getMessage(), e);
    }
}
catch (IOException e)
{
    Log.e("Image", e.getMessage(), e);
}

承认到目前为止另一个很好的答案,我所见过的最好的代码是在拍照工具的文档中。

请参阅“解码缩放图像”一节。

http://developer.android.com/training/camera/photobasics.html

它提出的解决方案是一个调整大小然后缩放的解决方案,就像这里的其他解决方案一样,但它非常简洁。

为了方便起见,我复制了下面的代码作为一个现成的函数。

private void setPic(String imagePath, ImageView destination) {
    int targetW = destination.getWidth();
    int targetH = destination.getHeight();
    // Get the dimensions of the bitmap
    BitmapFactory.Options bmOptions = new BitmapFactory.Options();
    bmOptions.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(imagePath, bmOptions);
    int photoW = bmOptions.outWidth;
    int photoH = bmOptions.outHeight;

    // Determine how much to scale down the image
    int scaleFactor = Math.min(photoW/targetW, photoH/targetH);

    // Decode the image file into a Bitmap sized to fill the View
    bmOptions.inJustDecodeBounds = false;
    bmOptions.inSampleSize = scaleFactor;
    bmOptions.inPurgeable = true;

    Bitmap bitmap = BitmapFactory.decodeFile(imagePath, bmOptions);
    destination.setImageBitmap(bitmap);
}

在Android开发者网站上有一篇关于这个问题的很棒的文章: 有效加载大位图

为什么不使用API呢?

int h = 48; // height in pixels
int w = 48; // width in pixels    
Bitmap scaled = Bitmap.createScaledBitmap(largeBitmap, w, h, true);

我使用Integer。numberOfLeadingZeros计算最佳样本量,性能更好。

kotlin完整代码:

@Throws(IOException::class)
fun File.decodeBitmap(options: BitmapFactory.Options): Bitmap? {
    return inputStream().use {
        BitmapFactory.decodeStream(it, null, options)
    }
}

@Throws(IOException::class)
fun File.decodeBitmapAtLeast(
        @androidx.annotation.IntRange(from = 1) width: Int,
        @androidx.annotation.IntRange(from = 1) height: Int
): Bitmap? {
    val options = BitmapFactory.Options()

    options.inJustDecodeBounds = true
    decodeBitmap(options)

    val ow = options.outWidth
    val oh = options.outHeight

    if (ow == -1 || oh == -1) return null

    val w = ow / width
    val h = oh / height

    if (w > 1 && h > 1) {
        val p = 31 - maxOf(Integer.numberOfLeadingZeros(w), Integer.numberOfLeadingZeros(h))
        options.inSampleSize = 1 shl maxOf(0, p)
    }
    options.inJustDecodeBounds = false
    return decodeBitmap(options)
}