在 Android 上将大位图文件的大小调整为缩放的输出文件
我的文件中有一个大位图(例如 3888x2592)。现在,我想将该位图大小调整为 800x533 并将其保存到另一个文件中。 我通常会通过调用 Bitmap.createBitmap 方法来缩放位图,但它需要一个源位图作为第一个参数,我无法提供该参数,因为将原始图像加载到 Bitmap 对象中当然会超出内存(例如,请参见此处)。
我也无法使用例如 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,但它不起作用。
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).
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).
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.
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?
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(21)
这对我有用。该函数获取 sd 卡上文件的路径并返回最大可显示尺寸的位图。
该代码来自 Ofir,进行了一些更改,例如 SD 上的图像文件而不是资源,并且宽度和高度是从显示对象获取的。
This worked for me. The function gets a path to a file on the sd card and returns a Bitmap in the maximum displayable size.
The code is from Ofir with some changes like image file on sd instead a Ressource and the witdth and heigth are get from the Display Object.
这是我使用的代码,在 Android 上解码内存中的大图像时没有任何问题。只要我的输入参数约为 1024x1024,我就能够解码大于 20MB 的图像。您可以将返回的位图保存到另一个文件。在这个方法下面是另一种方法,我也用它来将图像缩放到新的位图。请随意使用此代码。
注意:除了上面的createScaledBitmap 调用decode 方法之外,方法之间没有任何关系。请注意,原始图像的宽度和高度可能会发生变化。
Here is the code I use which doesn't have any issues decoding large images in memory on Android. I have been able to decode images larger then 20MB as long as my input parameters are around 1024x1024. You can save the returned bitmap to another file. Below this method is another method which I also use to scale images to a new bitmap. Feel free to use this code as you wish.
NOTE: Methods have nothing to do with each other except createScaledBitmap calls decode method above. Note width and height can change from original image.
或者:
or:
我使用 Integer.numberOfLeadingZeros 来计算最佳样本量,性能更好。
kotlin 中的完整代码:
I use
Integer.numberOfLeadingZeros
to calculate the best sample size, better performance.Full code in kotlin:
使用以下代码调整位图大小
同样的也在以下提示/技巧中进行了解释
http://www.codeproject.com/Tips/625810/Android-Image-Operations-Using-BitmapFactory
Resize the bitmap using the following code
The same is also explained in the following tip/trick
http://www.codeproject.com/Tips/625810/Android-Image-Operations-Using-BitmapFactory
不。我很乐意有人纠正我,但我接受了您尝试的加载/调整大小方法作为妥协。
以下是适合所有浏览者的步骤:
inSampleSize
。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:
inSampleSize
that still yields an image larger than your target.BitmapFactory.decodeFile(file, options)
, passing inSampleSize as an option.Bitmap.createScaledBitmap()
.贾斯汀的答案翻译成代码(对我来说非常适合):
Justin answer translated to code (works perfect for me):
这是“Mojo Risin”和“Ofir”解决方案的“结合”。这将为您提供按比例调整大小的图像,其边界为最大宽度和最大高度。
对我来说,它在 5 兆像素以下的图像上表现良好。
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.
For me it has been performing fine on 5 MegaPixel images an below.
为什么不使用 API?
Why not use the API?
承认到目前为止的其他优秀答案,我见过的最好的代码是在照片拍摄工具的文档中。
请参阅标题为“解码缩放图像”的部分。
http://developer.android.com/training/camera/photobasics.html
它提出的解决方案是先调整大小然后缩放解决方案,就像这里的其他解决方案一样,但它非常简洁。
为了方便起见,我已将下面的代码复制为现成函数。
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
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.
阅读这些答案和 android 文档后,这里是调整位图大小的代码而不将其加载到内存中:
After reading these answers and android documentation here's the code to resize bitmap without loading it into memory:
当我有大位图并且我想解码它们调整大小时,我使用以下命令
When i have large bitmaps and i want to decode them resized i use the following
这对于其他人看这个问题可能有用。我重写了 Justin 的代码,以允许该方法也接收所需的目标大小对象。使用 Canvas 时效果非常好。所有的功劳都应该归功于 JUSTIN,因为他出色的初始代码。
Justin 的代码在减少处理大型位图的开销方面非常有效。
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's code is VERY effective at reducing the overhead of working with large Bitmaps.
我不知道我的解决方案是否是最佳实践,但我通过使用
inDensity
和inTargetDensity
选项实现了按所需缩放比例加载位图。当不加载可绘制资源时,inDensity
最初为0
,因此此方法用于加载非资源图像。变量
imageUri
、maxImageSideLength
和context
是我的方法的参数。为了清楚起见,我只发布了方法实现,没有包装 AsyncTask。I don't know if my solution is best practice, but I achieved loading a bitmap with my desired scaling by using the
inDensity
andinTargetDensity
options.inDensity
is0
initially when not loading a drawable resource, so this approach is for loading non resource images.The variables
imageUri
,maxImageSideLength
andcontext
are parameters of my method. I posted only the method implementation without the wrapping AsyncTask for clarity.考虑到您想要调整到精确尺寸并希望保持所需的质量,我认为您应该尝试这个。
动机:多步缩放可以为您提供更高质量的图片,但不能保证它比使用高 inSampleSize 效果更好。
实际上,我认为您也可以使用像 5 这样的 inSampleSize (不是 2 的 pow)在一次操作中直接缩放。或者只使用 4,然后您就可以在 UI 中使用该图像。如果您将其发送到服务器,那么您可以在服务器端缩放到精确大小,这允许您使用高级缩放技术。
注意:如果第 3 步中加载的位图至少大 4 倍(因此 4*targetWidth < 宽度),您可能可以使用多次调整大小来获得更好的质量。
至少在通用java中有效,在android中你没有选择指定用于缩放的插值
http://today. java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html
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.
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.
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
我使用这样的代码:
我尝试原始图像是 1230 x 1230,并且得到的位图说是 330 x 330。
如果尝试 2590 x 3849,我会遇到 OutOfMemoryError。
我跟踪它,它仍然在“BitmapFactory.decodeStream(is, null, options);”行上抛出 OutOfMemoryError,如果原始位图太大......
I used code like this:
I tried original image is 1230 x 1230, and got bitmap says is 330 x 330.
And if tried 2590 x 3849, I'll got OutOfMemoryError.
I traced it, it still throw OutOfMemoryError on line "BitmapFactory.decodeStream(is, null, options);", if original bitmap too large...
上面的代码变得更清晰了一些。输入流最终实现了封闭包装,以确保它们也被关闭:
*注意
输入:InputStream is, int w, int h
输出:位图
Above code made a little cleaner. InputStreams have finally close wrapping to ensure they get closed as well:
*Note
Input: InputStream is, int w, int h
Output: Bitmap
要以“正确”的方式缩放图像而不跳过任何像素,您必须连接到图像解码器以逐行执行下采样。 Android(及其底层的 Skia 库)不提供此类钩子,因此您必须自行构建。假设您正在谈论 jpeg 图像,您最好的选择是在 C 中直接使用 libjpeg。
考虑到所涉及的复杂性,使用两步 subsample-then-rescale 可能最适合图像预览类型的应用程序。
To scale the image the "correct" way, without skipping any pixels, you'd have to hook into the image decoder to perform the down-sampling row by row. Android (and the Skia library that underlies it) provides no such hooks, so you'd have to roll your own. Assuming you're talking jpeg images, your best bet would be to use libjpeg directly, in C.
Given the complexities involved, using the two-step subsample-then-rescale is probably best for image-preview type apps.
这是一篇文章,采用不同的方法来调整大小。它将尝试根据进程中的可用内存将尽可能大的位图加载到内存中,然后执行转换。
http://bricolsoftconsulting.com/2012/12/07/在 android 上处理大图像/
Here is an article that takes a different approach to resizing. It will attempt to load the largest possible bitmap into memory based on available memory in the process and then perform the transforms.
http://bricolsoftconsulting.com/2012/12/07/handling-large-images-on-android/
如果您绝对想要一步调整大小,您可能可以加载整个位图,如果
android:largeHeap = true 但正如你所看到的,这并不是真正可取的。
来自文档:
安卓:大堆
您的应用程序进程是否应该使用大型 Dalvik 堆来创建。这适用于为应用程序创建的所有进程。它仅适用于加载到进程中的第一个应用程序;如果您使用共享用户 ID 来允许多个应用程序使用一个进程,则它们都必须一致地使用此选项,否则将产生不可预测的结果。
大多数应用程序不需要这样做,而应该专注于减少总体内存使用量以提高性能。启用此功能也不能保证可用内存的固定增加,因为某些设备受到其总可用内存的限制。
If you absolutely want to do one step resize you could probably load entire bitmap if
android:largeHeap = true but as you can see this is not really advisable.
From docs:
android:largeHeap
Whether your application's processes should be created with a large Dalvik heap. This applies to all processes created for the application. It only applies to the first application loaded into a process; if you're using a shared user ID to allow multiple applications to use a process, they all must use this option consistently or they will have unpredictable results.
Most apps should not need this and should instead focus on reducing their overall memory usage for improved performance. Enabling this also does not guarantee a fixed increase in available memory, because some devices are constrained by their total available memory.
Android 开发者网站上有一篇关于这个问题的精彩文章:
高效加载大位图
There is a great article about this exact issue on the Android developer website:
Loading Large Bitmaps Efficiently