在 Android 上将大型位图文件调整为缩放的输出文件

Posted

技术标签:

【中文标题】在 Android 上将大型位图文件调整为缩放的输出文件【英文标题】:Resize a large bitmap file to scaled output file on Android 【发布时间】:2011-03-20 21:06:36 【问题描述】:

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

我也无法读取位图,例如BitmapFactory.decodeFile(file, options),提供BitmapFactory.Options.inSampleSize,因为我想将其调整为精确的宽度和高度。使用 inSampleSize 会将位图大小调整为 972x648(如果我使用 inSampleSize=4)或 778x518(如果我使用 inSampleSize=5,这甚至不是 2 的幂)。

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

总结一下我的问题: 有没有办法读取 10MP 或更大的大图像文件并将其保存到新的图像文件,调整到特定的新宽度和高度,而不会出现 OutOfMemory 异常?

我还尝试了BitmapFactory.decodeFile(file, options) 并将 Options.outHeight 和 Options.outWidth 值手动设置为 800 和 533,但它不起作用。

【问题讨论】:

不,outHeight 和 outWidth 是 decode 方法中的 out 参数。话虽如此,我和你有同样的问题,而且我对两步方法不太满意。 经常,谢天谢地,你可以使用一行代码..***.com/a/17733530/294884 读者,请注意这个绝对关键的 QA !!! ***.com/a/24135522/294884 请注意,这个问题已经有 5 年历史了,完整的解决方案是 ..***.com/a/24135522/294884Cheers! 现在有关于该主题的官方文档:developer.android.com/training/displaying-bitmaps/… 【参考方案1】:

不。我很想有人纠正我,但我接受了你尝试的加载/调整大小方法作为妥协。

以下是任何人浏览的步骤:

    计算最大可能的inSampleSize 仍会产生比您的目标更大的图像。 使用 BitmapFactory.decodeFile(file, options) 加载图像,将 inSampleSize 作为选项传递。 使用Bitmap.createScaledBitmap() 调整到所需尺寸。

【讨论】:

我试图避免这种情况。所以没有办法一步一步直接调整大图的大小? 据我所知,但不要让这阻止您进一步探索。 好吧,到目前为止,我将把它作为我接受的答案。如果我发现任何其他方法,我会告诉你。 正如 PSIXO 在回答中提到的,如果在使用 inSampleSize 后仍有问题,您可能想使用 android:largeHeap。 位图变量变空【参考方案2】:

贾斯汀的答案翻译成代码(对我来说很完美):

private Bitmap getBitmap(String path) 

Uri uri = getImageUri(path);
InputStream in = null;
try 
    final int IMAGE_MAX_SIZE = 1200000; // 1.2MP
    in = mContentResolver.openInputStream(uri);

    // Decode image size
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(in, null, options);
    in.close();



    int scale = 1;
    while ((options.outWidth * options.outHeight) * (1 / Math.pow(scale, 2)) > 
          IMAGE_MAX_SIZE) 
       scale++;
    
    Log.d(TAG, "scale = " + scale + ", orig-width: " + options.outWidth + ", 
       orig-height: " + options.outHeight);

    Bitmap resultBitmap = null;
    in = mContentResolver.openInputStream(uri);
    if (scale > 1) 
        scale--;
        // scale to max possible inSampleSize that still yields an image
        // larger than target
        options = new BitmapFactory.Options();
        options.inSampleSize = scale;
        resultBitmap = BitmapFactory.decodeStream(in, null, options);

        // resize to desired dimensions
        int height = resultBitmap.getHeight();
        int width = resultBitmap.getWidth();
        Log.d(TAG, "1th scale operation dimenions - width: " + width + ",
           height: " + height);

        double y = Math.sqrt(IMAGE_MAX_SIZE
                / (((double) width) / height));
        double x = (y / height) * width;

        Bitmap scaledBitmap = Bitmap.createScaledBitmap(resultBitmap, (int) x, 
           (int) y, true);
        resultBitmap.recycle();
        resultBitmap = scaledBitmap;

        System.gc();
     else 
        resultBitmap = BitmapFactory.decodeStream(in);
    
    in.close();

    Log.d(TAG, "bitmap size - width: " +resultBitmap.getWidth() + ", height: " + 
       resultBitmap.getHeight());
    return resultBitmap;
 catch (IOException e) 
    Log.e(TAG, e.getMessage(),e);
    return null;

【讨论】:

当你使用像“b”这样的变量时会让人难以阅读,但很好的答案却不会少。 @Ofir : getImageUri(path);我必须在这个方法中传递什么? 代替 (wh)/Math.pow(scale, 2) 使用 (wh) >> scale 更有效。 请不要拨打System.gc() 感谢@Ofir,但这种转换不会保留图像方向:-/【参考方案3】:

这是 'Mojo Risin's 和 'Ofir's 解决方案的“组合”。这将为您提供具有最大宽度和最大高度边界的按比例调整大小的图像。

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

对我来说,它在下面的 5 兆像素图像上表现良好。

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

【讨论】:

【参考方案4】:

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

请参阅标题为“解码缩放图像”的部分。

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

【讨论】:

首先,您将整数除以结果。其次,代码在 targetW 或 targetH 为 0 时崩溃(尽管我知道这没有多大意义)。第三个 inSampleSize 应该是 2 的幂。 不要误会我的意思。这肯定会加载图像,但如果将 int 放置在 indend 上,则看起来并非如此。这也绝对不是正确的答案,因为图像不会按预期缩放。它不会做任何事情,直到图像视图是图像大小的一半或更小。然后什么都不会发生,直到图像视图是图像大小的 1/4。以此类推,以 2 次方为单位!【参考方案5】:

为什么不使用 API?

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

【讨论】:

因为它不能解决我的问题。即:“......它需要一个源位图作为第一个参数,我无法提供,因为将原始图像加载到 Bitmap 对象中当然会超出内存。”所以,我也不能将 Bitmap 传递给 .createScaledBitmap 方法,因为我仍然需要先将大图像加载到 Bitmap 对象中。 对。我重新阅读了您的问题,基本上(如果我理解正确的话)它归结为“我可以在不将原始文件加载到内存的情况下将图像调整为精确尺寸吗?”如果是这样 - 我对图像处理的复杂性知之甚少,无法回答这个问题,但有些事情告诉我 1. 它不能从 API 中获得,2. 它不会是 1-liner。我会将其标记为收藏 - 看看您(或其他人)是否会解决这个问题会很有趣。 它确实对我有用,因为我正在获取 uri 并转换为位图,因此对我来说缩放它们很容易 1+ 对于最简单的。【参考方案6】:

阅读这些答案和android documentation 后,这里是调整位图大小而不将其加载到内存中的代码:

public Bitmap getResizedBitmap(int targetW, int targetH,  String imagePath) 

    // Get the dimensions of the bitmap
    BitmapFactory.Options bmOptions = new BitmapFactory.Options();
    //inJustDecodeBounds = true <-- will not load the bitmap into memory
    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);
    return(bitmap);

【讨论】:

请注意 bmOptions.inPurgeable = true;已弃用。【参考方案7】:

当我有大的位图并且我想解码它们调整大小时,我使用以下

BitmapFactory.Options options = new BitmapFactory.Options();
InputStream is = null;
is = new FileInputStream(path_to_file);
BitmapFactory.decodeStream(is,null,options);
is.close();
is = new FileInputStream(path_to_file);
// here w and h are the desired width and height
options.inSampleSize = Math.max(options.outWidth/w, options.outHeight/h);
// bitmap is the resized bitmap
Bitmap bitmap = BitmapFactory.decodeStream(is,null,options);

【讨论】:

由于 inSampleSize 是一个整数,因此您很少会获得您想要获得的确切像素宽度和高度。有时您可能会接近,但也可能远离它,具体取决于小数点。 早上,我确实尝试了您的代码(在此线程的上方发布),但似乎无法正常工作,我哪里做错了?欢迎任何建议:-)【参考方案8】:

这可能对查看此问题的其他人有用。我重写了 Justin 的代码以允许该方法也接收所需的目标大小对象。这在使用 Canvas 时效果很好。所有功劳都应归功于 JUSTIN 出色的初始代码。

    private Bitmap getBitmap(int path, Canvas canvas) 

        Resources resource = null;
        try 
            final int IMAGE_MAX_SIZE = 1200000; // 1.2MP
            resource = getResources();

            // Decode image size
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeResource(resource, path, options);

            int scale = 1;
            while ((options.outWidth * options.outHeight) * (1 / Math.pow(scale, 2)) > 
                  IMAGE_MAX_SIZE) 
               scale++;
            
            Log.d("TAG", "scale = " + scale + ", orig-width: " + options.outWidth + ", orig-height: " + options.outHeight);

            Bitmap pic = null;
            if (scale > 1) 
                scale--;
                // scale to max possible inSampleSize that still yields an image
                // larger than target
                options = new BitmapFactory.Options();
                options.inSampleSize = scale;
                pic = BitmapFactory.decodeResource(resource, path, options);

                // resize to desired dimensions
                int height = canvas.getHeight();
                int width = canvas.getWidth();
                Log.d("TAG", "1th scale operation dimenions - width: " + width + ", height: " + height);

                double y = Math.sqrt(IMAGE_MAX_SIZE
                        / (((double) width) / height));
                double x = (y / height) * width;

                Bitmap scaledBitmap = Bitmap.createScaledBitmap(pic, (int) x, (int) y, true);
                pic.recycle();
                pic = scaledBitmap;

                System.gc();
             else 
                pic = BitmapFactory.decodeResource(resource, path);
            

            Log.d("TAG", "bitmap size - width: " +pic.getWidth() + ", height: " + pic.getHeight());
            return pic;
         catch (Exception e) 
            Log.e("TAG", e.getMessage(),e);
            return null;
        
    

Justin 的代码在减少使用大型位图的开销方面非常有效。

【讨论】:

【参考方案9】:

我不知道我的解决方案是否是最佳实践,但我通过使用inDensityinTargetDensity 选项实现了加载具有所需缩放比例的位图。 inDensity0 最初在不加载可绘制资源时,因此这种方法用于加载非资源图像。

变量imageUrimaxImageSideLengthcontext 是我的方法的参数。为了清楚起见,我只发布了没有包装 AsyncTask 的方法实现。

            ContentResolver resolver = context.getContentResolver();
            InputStream is;
            try 
                is = resolver.openInputStream(imageUri);
             catch (FileNotFoundException e) 
                Log.e(TAG, "Image not found.", e);
                return null;
            
            Options opts = new Options();
            opts.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(is, null, opts);

            // scale the image
            float maxSideLength = maxImageSideLength;
            float scaleFactor = Math.min(maxSideLength / opts.outWidth, maxSideLength / opts.outHeight);
            // do not upscale!
            if (scaleFactor < 1) 
                opts.inDensity = 10000;
                opts.inTargetDensity = (int) ((float) opts.inDensity * scaleFactor);
            
            opts.inJustDecodeBounds = false;

            try 
                is.close();
             catch (IOException e) 
                // ignore
            
            try 
                is = resolver.openInputStream(imageUri);
             catch (FileNotFoundException e) 
                Log.e(TAG, "Image not found.", e);
                return null;
            
            Bitmap bitmap = BitmapFactory.decodeStream(is, null, opts);
            try 
                is.close();
             catch (IOException e) 
                // ignore
            

            return bitmap;

【讨论】:

非常好!使用 inDensity 而不是 Bitmap.createScaledBitmap 为我节省了大量内存堆。与 inSamplesize 结合使用效果更好。【参考方案10】:

考虑到您想调整为精确大小并希望保持所需的尽可能多的质量,我认为您应该尝试一下。

    通过调用 BitmapFactory.decodeFile 并提供 checkSizeOptions.inJustDecodeBounds 找出调整后图像的大小 计算您可以在设备上使用的最大 inSampleSize 不超过内存。 bitmapSizeInBytes = 2*宽*高;一般来说,对于你的图片 inSampleSize=2 就可以了,因为你只需要 2*1944x1296)=4.8Mbб 就可以了 使用 BitmapFactory.decodeFile 和 inSampleSize 加载位图 将位图缩放到精确大小。

动机:多步缩放可以为您提供更高质量的图片,但不能保证它会比使用高 inSampleSize 效果更好。 实际上,我认为您也可以使用 inSampleSize 之类的 5(不是 2 的 pow)在一次操作中进行直接缩放。或者只使用 4,然后你就可以在 UI 中使用该图像。如果你将它发送到服务器 - 你可以在服务器端缩放到精确的大小,这允许你使用高级缩放技术。

注意:如果在步骤 3 中加载的位图至少大 4 倍(因此 4*targetWidth http://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html

【讨论】:

【参考方案11】:

我使用了这样的代码:

  String filePath=Environment.getExternalStorageDirectory()+"/test_image.jpg";
  BitmapFactory.Options options=new BitmapFactory.Options();
  InputStream is=new FileInputStream(filePath);
  BitmapFactory.decodeStream(is, null, options);
  is.close();
  is=new FileInputStream(filePath);
  // here w and h are the desired width and height
  options.inSampleSize=Math.max(options.outWidth/460, options.outHeight/288); //Max 460 x 288 is my desired...
  // bmp is the resized bitmap
  Bitmap bmp=BitmapFactory.decodeStream(is, null, options);
  is.close();
  Log.d(Constants.TAG, "Scaled bitmap bytes, "+bmp.getRowBytes()+", width:"+bmp.getWidth()+", height:"+bmp.getHeight());

我试过原始图像是 1230 x 1230,得到的位图是 330 x 330。 如果尝试 2590 x 3849,我会得到 OutOfMemoryError。

我跟踪了一下,如果原始位图太大,它仍然会在“BitmapFactory.decodeStream(is, null, options);”行抛出 OutOfMemoryError...

【讨论】:

【参考方案12】:

上面的代码变得更简洁了。 InputStreams 终于关闭包装以确保它们也被关闭:

*注意 输入:InputStream is, int w, int h 输出:位图

    try
    

        final int inWidth;
        final int inHeight;

        final File tempFile = new File(temp, System.currentTimeMillis() + is.toString() + ".temp");

        

            final FileOutputStream tempOut = new FileOutputStream(tempFile);

            StreamUtil.copyTo(is, tempOut);

            tempOut.close();

        



        

            final InputStream in = new FileInputStream(tempFile);
            final BitmapFactory.Options options = new BitmapFactory.Options();

            try 

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

            
            finally 
                in.close();
            

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

        

        final Bitmap roughBitmap;

        

            // decode full image pre-resized
            final InputStream in = new FileInputStream(tempFile);

            try 

                final BitmapFactory.Options options = new BitmapFactory.Options();
                // calc rought re-size (this is no exact resize)
                options.inSampleSize = Math.max(inWidth/w, inHeight/h);
                // decode full image
                roughBitmap = BitmapFactory.decodeStream(in, null, options);

            
            finally 
                in.close();
            

            tempFile.delete();

        

        float[] values = new float[9];

        

            // calc exact destination size
            Matrix m = new Matrix();
            RectF inRect = new RectF(0, 0, roughBitmap.getWidth(), roughBitmap.getHeight());
            RectF outRect = new RectF(0, 0, w, h);
            m.setRectToRect(inRect, outRect, Matrix.ScaleToFit.CENTER);
            m.getValues(values);

        

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

        return resizedBitmap;

    
    catch (IOException e) 

        logger.error("Error:" , e);
        throw new ResourceException("could not create bitmap");

    

【讨论】:

【参考方案13】:

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

鉴于所涉及的复杂性,使用两步 subsample-then-rescale 可能最适合图像预览类型的应用程序。

【讨论】:

【参考方案14】:

这是一篇采用不同方法调整大小的文章。它将尝试根据进程中的可用内存将最大可能的位图加载到内存中,然后执行转换。

http://bricolsoftconsulting.com/2012/12/07/handling-large-images-on-android/

【讨论】:

【参考方案15】:

如果您绝对想要一步调整大小,您可能会加载整个位图,如果 android:largeHeap = true 但正如您所见,这并不可取。

来自文档: 机器人:大堆 是否应使用大型 Dalvik 堆创建应用程序的进程。这适用于为应用程序创建的所有进程。它仅适用于加载到进程中的第一个应用程序;如果您使用共享用户 ID 来允许多个应用程序使用一个进程,则它们都必须一致地使用此选项,否则它们将产生不可预知的结果。 大多数应用程序不应该需要这个,而是应该专注于减少它们的整体内存使用以提高性能。启用此功能也不能保证可用内存的固定增加,因为某些设备受到其总可用内存的限制。

【讨论】:

【参考方案16】:

在 Android 开发者网站上有一篇关于这个确切问题的精彩文章: Loading Large Bitmaps Efficiently

【讨论】:

【参考方案17】:

这对我有用。该函数获取 sd 卡上文件的路径并返回最大可显示大小的位图。 代码来自 Ofir,有一些更改,例如 sd 上的图像文件而不是 Ressource,宽度和高度来自显示对象。

private Bitmap makeBitmap(String path) 

    try 
        final int IMAGE_MAX_SIZE = 1200000; // 1.2MP
        //resource = getResources();

        // Decode image size
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path, options);

        int scale = 1;
        while ((options.outWidth * options.outHeight) * (1 / Math.pow(scale, 2)) >
                IMAGE_MAX_SIZE) 
            scale++;
        
        Log.d("TAG", "scale = " + scale + ", orig-width: " + options.outWidth + ", orig-height: " + options.outHeight);

        Bitmap pic = null;
        if (scale > 1) 
            scale--;
            // scale to max possible inSampleSize that still yields an image
            // larger than target
            options = new BitmapFactory.Options();
            options.inSampleSize = scale;
            pic = BitmapFactory.decodeFile(path, options);

            // resize to desired dimensions

            Display display = getWindowManager().getDefaultDisplay();
            Point size = new Point();
            display.getSize(size);
            int width = size.y;
            int height = size.x;

            //int height = imageView.getHeight();
            //int width = imageView.getWidth();
            Log.d("TAG", "1th scale operation dimenions - width: " + width + ", height: " + height);

            double y = Math.sqrt(IMAGE_MAX_SIZE
                    / (((double) width) / height));
            double x = (y / height) * width;

            Bitmap scaledBitmap = Bitmap.createScaledBitmap(pic, (int) x, (int) y, true);
            pic.recycle();
            pic = scaledBitmap;

            System.gc();
         else 
            pic = BitmapFactory.decodeFile(path);
        

        Log.d("TAG", "bitmap size - width: " +pic.getWidth() + ", height: " + pic.getHeight());
        return pic;

     catch (Exception e) 
        Log.e("TAG", e.getMessage(),e);
        return null;
    


【讨论】:

【参考方案18】:

这是我使用的代码,它在 Android 上解码内存中的大图像没有任何问题。只要我的输入参数在 1024x1024 左右,我就能够解码大于 20MB 的图像。您可以将返回的位图保存到另一个文件中。在这个方法下面是另一种方法,我也用它来将图像缩放到新的位图。随意使用此代码。

/*****************************************************************************
 * public decode - decode the image into a Bitmap
 * 
 * @param xyDimension
 *            - The max XY Dimension before the image is scaled down - XY =
 *            1080x1080 and Image = 2000x2000 image will be scaled down to a
 *            value equal or less then set value.
 * @param bitmapConfig
 *            - Bitmap.Config Valid values = ( Bitmap.Config.ARGB_4444,
 *            Bitmap.Config.RGB_565, Bitmap.Config.ARGB_8888 )
 * 
 * @return Bitmap - Image - a value of "null" if there is an issue decoding
 *         image dimension
 * 
 * @throws FileNotFoundException
 *             - If the image has been removed while this operation is
 *             taking place
 */
public Bitmap decode( int xyDimension, Bitmap.Config bitmapConfig ) throws FileNotFoundException

    // The Bitmap to return given a Uri to a file
    Bitmap bitmap = null;
    File file = null;
    FileInputStream fis = null;
    InputStream in = null;

    // Try to decode the Uri
    try
    
        // Initialize scale to no real scaling factor
        double scale = 1;

        // Get FileInputStream to get a FileDescriptor
        file = new File( this.imageUri.getPath() );

        fis = new FileInputStream( file );
        FileDescriptor fd = fis.getFD();

        // Get a BitmapFactory Options object
        BitmapFactory.Options o = new BitmapFactory.Options();

        // Decode only the image size
        o.inJustDecodeBounds = true;
        o.inPreferredConfig = bitmapConfig;

        // Decode to get Width & Height of image only
        BitmapFactory.decodeFileDescriptor( fd, null, o );
        BitmapFactory.decodeStream( null );

        if( o.outHeight > xyDimension || o.outWidth > xyDimension )
        
            // Change the scale if the image is larger then desired image
            // max size
            scale = Math.pow( 2, (int) Math.round( Math.log( xyDimension / (double) Math.max( o.outHeight, o.outWidth ) ) / Math.log( 0.5 ) ) );
        

        // Decode with inSampleSize scale will either be 1 or calculated value
        o.inJustDecodeBounds = false;
        o.inSampleSize = (int) scale;

        // Decode the Uri for real with the inSampleSize
        in = new BufferedInputStream( fis );
        bitmap = BitmapFactory.decodeStream( in, null, o );
    
    catch( OutOfMemoryError e )
    
        Log.e( DEBUG_TAG, "decode : OutOfMemoryError" );
        e.printStackTrace();
    
    catch( NullPointerException e )
    
        Log.e( DEBUG_TAG, "decode : NullPointerException" );
        e.printStackTrace();
    
    catch( RuntimeException e )
    
        Log.e( DEBUG_TAG, "decode : RuntimeException" );
        e.printStackTrace();
    
    catch( FileNotFoundException e )
    
        Log.e( DEBUG_TAG, "decode : FileNotFoundException" );
        e.printStackTrace();
    
    catch( IOException e )
    
        Log.e( DEBUG_TAG, "decode : IOException" );
        e.printStackTrace();
    

    // Save memory
    file = null;
    fis = null;
    in = null;

    return bitmap;

 // decode

注意:方法之间没有任何关系,除了 createScaledBitmap 调用上面的 decode 方法。注意宽度和高度可以从原始图像改变。

/*****************************************************************************
 * public createScaledBitmap - Creates a new bitmap, scaled from an existing
 * bitmap.
 * 
 * @param dstWidth
 *            - Scale the width to this dimension
 * @param dstHeight
 *            - Scale the height to this dimension
 * @param xyDimension
 *            - The max XY Dimension before the original image is scaled
 *            down - XY = 1080x1080 and Image = 2000x2000 image will be
 *            scaled down to a value equal or less then set value.
 * @param bitmapConfig
 *            - Bitmap.Config Valid values = ( Bitmap.Config.ARGB_4444,
 *            Bitmap.Config.RGB_565, Bitmap.Config.ARGB_8888 )
 * 
 * @return Bitmap - Image scaled - a value of "null" if there is an issue
 * 
 */
public Bitmap createScaledBitmap( int dstWidth, int dstHeight, int xyDimension, Bitmap.Config bitmapConfig )

    Bitmap scaledBitmap = null;

    try
    
        Bitmap bitmap = this.decode( xyDimension, bitmapConfig );

        // Create an empty Bitmap which will contain the new scaled bitmap
        // This scaled bitmap should be the size we want to scale the
        // original bitmap too
        scaledBitmap = Bitmap.createBitmap( dstWidth, dstHeight, bitmapConfig );

        float ratioX = dstWidth / (float) bitmap.getWidth();
        float ratioY = dstHeight / (float) bitmap.getHeight();
        float middleX = dstWidth / 2.0f;
        float middleY = dstHeight / 2.0f;

        // Used to for scaling the image
        Matrix scaleMatrix = new Matrix();
        scaleMatrix.setScale( ratioX, ratioY, middleX, middleY );

        // Used to do the work of scaling
        Canvas canvas = new Canvas( scaledBitmap );
        canvas.setMatrix( scaleMatrix );
        canvas.drawBitmap( bitmap, middleX - bitmap.getWidth() / 2, middleY - bitmap.getHeight() / 2, new Paint( Paint.FILTER_BITMAP_FLAG ) );
    
    catch( IllegalArgumentException e )
    
        Log.e( DEBUG_TAG, "createScaledBitmap : IllegalArgumentException" );
        e.printStackTrace();
    
    catch( NullPointerException e )
    
        Log.e( DEBUG_TAG, "createScaledBitmap : NullPointerException" );
        e.printStackTrace();
    
    catch( FileNotFoundException e )
    
        Log.e( DEBUG_TAG, "createScaledBitmap : FileNotFoundException" );
        e.printStackTrace();
    

    return scaledBitmap;
 // End createScaledBitmap

【讨论】:

这里的秤的功率计算是错误的;只需使用 android doco 页面上的计算即可。【参考方案19】:
 Bitmap yourBitmap;
 Bitmap resized = Bitmap.createScaledBitmap(yourBitmap, newWidth, newHeight, true);

或:

 resized = Bitmap.createScaledBitmap(yourBitmap,(int)(yourBitmap.getWidth()*0.8), (int)(yourBitmap.getHeight()*0.8), true);

【讨论】:

【参考方案20】:

我使用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)

【讨论】:

【参考方案21】:

使用以下代码调整位图大小

    public static Bitmap decodeFile(File file, int reqWidth, int reqHeight)

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;        
    BitmapFactory.decodeFile(file.getPath(), options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeFile(file.getPath(), options);
   

    private static int calculateInSampleSize(
    BitmapFactory.Options options, int reqWidth, int reqHeight) 
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) 

        // Calculate ratios of height and width to requested height and width
        final int heightRatio = Math.round((float) height / (float) reqHeight);
        final int widthRatio = Math.round((float) width / (float) reqWidth);

        // Choose the smallest ratio as inSampleSize value, this will guarantee
        // a final image with both dimensions larger than or equal to the
        // requested height and width.
        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
     

     return inSampleSize;
       

下面的提示/技巧中也有同样的解释

http://www.codeproject.com/Tips/625810/Android-Image-Operations-Using-BitmapFactory

【讨论】:

以上是关于在 Android 上将大型位图文件调整为缩放的输出文件的主要内容,如果未能解决你的问题,请参考以下文章

调整可绘制图层列表内的位图大小 2

在Android上将int数组转换为Bitmap

Android位图解码A

在android中创建缩放位图时保持图像质量

Android位图调整为中心文本

Android 拍照或相册选择照片进行显示缩放位图 Demo