在 Android 上将大的位图文件调整为缩放的输出文件
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3331527/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Resize a large bitmap file to scaled output file on Android
提问by Manuel
I have a large bitmap (say 3888x2592) in a file. Now, I want to resize that bitmap to 800x533 and save it to another file.
I normally would scale the bitmap by calling Bitmap.createBitmap
method but it needs a source bitmap as the first argument, which I can't provide because loading the original image into a Bitmap object would of course exceed the memory (see here, for example).
我在一个文件中有一个大的位图(比如 3888x2592)。现在,我想将该位图的大小调整为 800x533 并将其保存到另一个文件中。我通常会通过调用Bitmap.createBitmap
方法来缩放位图,但它需要一个源位图作为第一个参数,我无法提供,因为将原始图像加载到 Bitmap 对象中当然会超出内存(例如,请参见此处)。
I also can't read the bitmap with, for example, BitmapFactory.decodeFile(file, options)
, providing a BitmapFactory.Options.inSampleSize
, because I want to resize it to an exact width and height. Using inSampleSize
would resize the bitmap to 972x648 (if I use inSampleSize=4
) or to 778x518 (if I use inSampleSize=5
, which isn't even a power of 2).
我也无法读取位图,例如,BitmapFactory.decodeFile(file, options)
提供BitmapFactory.Options.inSampleSize
,因为我想将其调整为精确的宽度和高度。使用inSampleSize
会将位图大小调整为 972x648(如果我使用inSampleSize=4
)或 778x518(如果我使用inSampleSize=5
,它甚至不是 2 的幂)。
I would also like to avoid reading the image using inSampleSize with, for example, 972x648 in a first step and then resizing it to exactly 800x533 in a second step, because the quality would be poor compared to a direct resizing of the original image.
我还想避免使用 inSampleSize 读取图像,例如,在第一步中使用 972x648,然后在第二步中将其大小调整为 800x533,因为与直接调整原始图像大小相比,质量会很差。
To sum up my question: Is there a way to read a large image file with 10MP or more and save it to a new image file, resized to a specific new width and height, without getting an OutOfMemory exception?
总结一下我的问题:有没有办法读取 10MP 或更大的大图像文件并将其保存到一个新的图像文件,调整为特定的新宽度和高度,而不会出现 OutOfMemory 异常?
I also tried BitmapFactory.decodeFile(file, options)
and setting the Options.outHeight and Options.outWidth values manually to 800 and 533, but it doesn't work that way.
我还尝试BitmapFactory.decodeFile(file, options)
将 Options.outHeight 和 Options.outWidth 值手动设置为 800 和 533,但它不能那样工作。
采纳答案by Justin
No.I'd love for someone to correct me, but I accepted the load/resize approach you tried as a compromise.
不。我希望有人纠正我,但我接受了您尝试的加载/调整大小方法作为妥协。
Here are the steps for anyone browsing:
以下是任何人浏览的步骤:
- Calculate the maximum possible
inSampleSize
that still yields an image larger than your target. - Load the image using
BitmapFactory.decodeFile(file, options)
, passing inSampleSize as an option. - Resize to the desired dimensions using
Bitmap.createScaledBitmap()
.
- 计算
inSampleSize
仍然产生大于目标图像的最大可能值。 - 使用 加载图像
BitmapFactory.decodeFile(file, options)
,将 inSampleSize 作为选项传递。 - 使用 调整到所需的尺寸
Bitmap.createScaledBitmap()
。
回答by Ofir
Justin answer translated to code (works perfect for me):
贾斯汀的答案翻译成代码(对我来说很完美):
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;
}
回答by blubl
This is 'Mojo Risin's and 'Ofir's solutions "combined". This will give you a proportionally resized image with the boundaries of max width and max height.
这是'Mojo Risin's 和'Ofir's 解决方案的“结合”。这将为您提供一个按比例调整大小的图像,其边界为最大宽度和最大高度。
- It only reads meta data to get the original size (options.inJustDecodeBounds)
- It uses a rought resize to save memory (itmap.createScaledBitmap)
- It uses a precisely resized image based on the rough Bitamp created earlier.
- 它只读取元数据以获得原始大小(options.inJustDecodeBounds)
- 它使用粗略调整大小来节省内存(itmap.createScaledBitmap)
- 它使用基于之前创建的粗略 Bitamp 精确调整大小的图像。
For me it has been performing fine on 5 MegaPixel images an below.
对我来说,它在下面的 5 个 MegaPixel 图像上表现良好。
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);
}
回答by Bostone
Why not use the API?
为什么不使用 API?
int h = 48; // height in pixels
int w = 48; // width in pixels
Bitmap scaled = Bitmap.createScaledBitmap(largeBitmap, w, h, true);
回答by Alex
Acknowledging the other excellent answer so far, the best code I've seen yet for this is in the documentation for the photo taking tool.
承认到目前为止的另一个优秀答案,我见过的最好的代码是在照片拍摄工具的文档中。
See the section entitled "Decode a Scaled Image".
请参阅标题为“解码缩放图像”的部分。
http://developer.android.com/training/camera/photobasics.html
http://developer.android.com/training/camera/photobasics.html
The solution it proposes is a resize then scale solution like the others here, but it's quite neat.
它提出的解决方案是像这里的其他解决方案一样调整大小然后缩放解决方案,但它非常简洁。
I've copied the code below as a ready-to-go function for convenience.
为方便起见,我已将下面的代码复制为现成的功能。
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);
}
回答by penduDev
After reading these answers and android documentationhere's the code to resize bitmap without loading it into memory:
阅读这些答案和android 文档后,这里是调整位图大小而不将其加载到内存中的代码:
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);
}
回答by Mojo Risin
When i have large bitmaps and i want to decode them resized i use the following
当我有大的位图并且我想解码它们调整大小时,我使用以下
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);
回答by Music Monkey
This may be useful for someone else looking at this question. I rewrote Justin's code to allow the method to receive the target size object required as well. This works very well when using Canvas. All credit should go to JUSTIN for his great initial code.
这对于查看此问题的其他人可能很有用。我重写了 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's code is VERY effective at reducing the overhead of working with large Bitmaps.
Justin 的代码在减少处理大型位图的开销方面非常有效。
回答by cybergen
I don't know if my solution is best practice, but I achieved loading a bitmap with my desired scaling by using the inDensity
and inTargetDensity
options. inDensity
is 0
initially when not loading a drawable resource, so this approach is for loading non resource images.
我不知道我的解决方案是否是最佳实践,但我通过使用inDensity
和inTargetDensity
选项实现了加载具有所需缩放比例的位图。最初inDensity
是0
在不加载可绘制资源时,因此这种方法用于加载非资源图像。
The variables imageUri
, maxImageSideLength
and context
are parameters of my method. I posted only the method implementation without the wrapping AsyncTask for clarity.
变量imageUri
,maxImageSideLength
和context
是我的方法的参数。为了清楚起见,我只发布了没有包装 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;
回答by Andrey Chorniy
Taking into account that you want to resize to exact size and want to keep as much quality as needed I think you should try this.
考虑到您想要调整到确切尺寸并希望保持尽可能多的质量,我认为您应该尝试这个。
- Find out the size of the resized image with call of BitmapFactory.decodeFile and providing the checkSizeOptions.inJustDecodeBounds
- Calculate the maximumpossible inSampleSize you can use on your device to not exceed the memory. bitmapSizeInBytes = 2*width*height; Generally for your picture inSampleSize=2 would be fine since you will need only 2*1944x1296)=4.8Mbб which should feet in memory
- Use BitmapFactory.decodeFile with inSampleSize to load the Bitmap
- Scale the bitmap to exact size.
- 通过调用 BitmapFactory.decodeFile 并提供 checkSizeOptions.inJustDecodeBounds 找出调整大小的图像的大小
- 计算您可以在设备上使用的最大可能 inSampleSize 以不超过内存。bitmapSizeInBytes = 2*宽*高;通常对于您的图片 inSampleSize=2 就可以了,因为您只需要 2*1944x1296)=4.8Mbб 应该在内存中
- 使用 BitmapFactory.decodeFile 和 inSampleSize 加载位图
- 将位图缩放到精确大小。
Motivation: multiple-steps scaling could give you higher quality picture, however there is no guarantee that it will work better than using high inSampleSize. Actually, I think that you also can use inSampleSize like 5 (not pow of 2) to have direct scaling in one operation. Or just use 4 and then you can just use that image in UI. if you send it to server - than you can do scaling to exact size on server side which allow you to use advanced scaling techniques.
动机:多步缩放可以为您提供更高质量的图片,但是不能保证它比使用高 inSampleSize 效果更好。实际上,我认为您也可以使用 inSampleSize 之类的 5(不是 2 的 pow)在一次操作中直接缩放。或者只使用 4,然后您就可以在 UI 中使用该图像。如果你将它发送到服务器 - 你可以在服务器端缩放到精确的大小,这允许你使用高级缩放技术。
Notes: if the Bitmap loaded in step-3 is at least 4 times larger (so the 4*targetWidth < width) you probably can use several resizing to achieve better quality. at least that works in generic java, in android you don't have the option to specify the interpolation used for scaling http://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html
注意:如果第 3 步中加载的位图至少大 4 倍(因此 4*targetWidth < 宽度),您可能可以使用几种调整大小来获得更好的质量。至少在通用 java 中有效,在 android 中您无法指定用于缩放的插值 http://today.java.net/pub/a/today/2007/04/03/perils-of-图像-getscaledinstance.html