Java - 在不降低质量的情况下调整图像大小
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/24745147/
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
Java - resize image without losing quality
提问by user3362954
I have 10,000 photos that need to be resized so I have a Java program to do that. Unfortunately, the quality of the image is poorly lost and I don't have access to the uncompressed images.
我有 10,000 张照片需要调整大小,所以我有一个 Java 程序来做到这一点。不幸的是,图像质量损失惨重,我无法访问未压缩的图像。
import java.awt.Graphics;
import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
/**
* This class will resize all the images in a given folder
* @author
*
*/
public class JavaImageResizer {
public static void main(String[] args) throws IOException {
File folder = new File("/Users/me/Desktop/images/");
File[] listOfFiles = folder.listFiles();
System.out.println("Total No of Files:"+listOfFiles.length);
BufferedImage img = null;
BufferedImage tempPNG = null;
BufferedImage tempJPG = null;
File newFilePNG = null;
File newFileJPG = null;
for (int i = 0; i < listOfFiles.length; i++) {
if (listOfFiles[i].isFile()) {
System.out.println("File " + listOfFiles[i].getName());
img = ImageIO.read(new File("/Users/me/Desktop/images/"+listOfFiles[i].getName()));
tempJPG = resizeImage(img, img.getWidth(), img.getHeight());
newFileJPG = new File("/Users/me/Desktop/images/"+listOfFiles[i].getName()+"_New");
ImageIO.write(tempJPG, "jpg", newFileJPG);
}
}
System.out.println("DONE");
}
/**
* This function resize the image file and returns the BufferedImage object that can be saved to file system.
*/
public static BufferedImage resizeImage(final Image image, int width, int height) {
int targetw = 0;
int targeth = 75;
if (width > height)targetw = 112;
else targetw = 50;
do {
if (width > targetw) {
width /= 2;
if (width < targetw) width = targetw;
}
if (height > targeth) {
height /= 2;
if (height < targeth) height = targeth;
}
} while (width != targetw || height != targeth);
final BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
final Graphics2D graphics2D = bufferedImage.createGraphics();
graphics2D.setComposite(AlphaComposite.Src);
graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
graphics2D.drawImage(image, 0, 0, width, height, null);
graphics2D.dispose();
return bufferedImage;
}
An image I am working with is this:
我正在使用的图像是这样的:
This is the manual resizing I've done in Microsoft Paint:
这是我在 Microsoft Paint 中所做的手动调整大小:
and this is the output from my program [bilinear]:
这是我的程序 [bilinear] 的输出:
UPDATE:No significant difference using BICUBIC
更新:使用没有显着差异BICUBIC
and this is the output from my program [bicubic]:
这是我的程序 [bicubic] 的输出:
is there anyway to increase the quality of the program output so I don't have to manually resize all photos?
有没有办法提高程序输出的质量,这样我就不必手动调整所有照片的大小?
Thank you in advance!
先感谢您!
回答by Marco13
Given your input image, the method from the answer in the first link in the comments (kudos to Chris Campbell) produces one of the following thumbnails:
给定您的输入图像,评论中第一个链接中的答案中的方法(感谢 Chris Campbell)生成以下缩略图之一:
(The other one is the thumbnail that you created with MS Paint. It's hard to call one of them "better" than the other...)
(另一个是您使用 MS Paint 创建的缩略图。很难称其中一个比另一个“更好”......)
EDIT: Just to point this out as well: The main problem with your original code was that you did not really scalethe image in multiple steps. You just used a strange loop to "compute" the target size. The key point is that you actually perform the scalingin multiple steps.
编辑:还要指出这一点:原始代码的主要问题是您没有真正分多个步骤缩放图像。您只是使用了一个奇怪的循环来“计算”目标大小。关键是您实际上分多个步骤执行缩放。
Just for completeness, the MVCE
只是为了完整性,MVCE
(Edit: I mentioned Chris Campbell and referred to the source via the comments, but to make this more clear here: The following is based on the article The Perils of Image.getScaledInstance())
(编辑:我提到了 Chris Campbell 并通过评论引用了来源,但为了更清楚地说明这一点:以下内容基于文章The Perils of Image.getScaledInstance())
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import javax.imageio.stream.MemoryCacheImageOutputStream;
public class ResizeQuality
{
public static void main(String[] args) throws IOException
{
BufferedImage image = ImageIO.read(new File("X0aPT.jpg"));
BufferedImage scaled = getScaledInstance(
image, 51, 75, RenderingHints.VALUE_INTERPOLATION_BILINEAR, true);
writeJPG(scaled, new FileOutputStream("X0aPT_tn.jpg"), 0.85f);
}
public static BufferedImage getScaledInstance(
BufferedImage img, int targetWidth,
int targetHeight, Object hint,
boolean higherQuality)
{
int type =
(img.getTransparency() == Transparency.OPAQUE)
? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
BufferedImage ret = (BufferedImage) img;
int w, h;
if (higherQuality)
{
// Use multi-step technique: start with original size, then
// scale down in multiple passes with drawImage()
// until the target size is reached
w = img.getWidth();
h = img.getHeight();
}
else
{
// Use one-step technique: scale directly from original
// size to target size with a single drawImage() call
w = targetWidth;
h = targetHeight;
}
do
{
if (higherQuality && w > targetWidth)
{
w /= 2;
if (w < targetWidth)
{
w = targetWidth;
}
}
if (higherQuality && h > targetHeight)
{
h /= 2;
if (h < targetHeight)
{
h = targetHeight;
}
}
BufferedImage tmp = new BufferedImage(w, h, type);
Graphics2D g2 = tmp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
g2.drawImage(ret, 0, 0, w, h, null);
g2.dispose();
ret = tmp;
} while (w != targetWidth || h != targetHeight);
return ret;
}
public static void writeJPG(
BufferedImage bufferedImage,
OutputStream outputStream,
float quality) throws IOException
{
Iterator<ImageWriter> iterator =
ImageIO.getImageWritersByFormatName("jpg");
ImageWriter imageWriter = iterator.next();
ImageWriteParam imageWriteParam = imageWriter.getDefaultWriteParam();
imageWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
imageWriteParam.setCompressionQuality(quality);
ImageOutputStream imageOutputStream =
new MemoryCacheImageOutputStream(outputStream);
imageWriter.setOutput(imageOutputStream);
IIOImage iioimage = new IIOImage(bufferedImage, null, null);
imageWriter.write(null, iioimage, imageWriteParam);
imageOutputStream.flush();
}
}
回答by fabian
The result seems to be better (than the result of your program), if you apply Gaussian blurbefore resizing:
如果在调整大小之前应用高斯模糊,结果似乎更好(比程序的结果):
This is the result I get, with sigma * (scale factor) = 0.3
:
这是我得到的结果sigma * (scale factor) = 0.3
:
With ImageJthe code to do this is quite short:
使用ImageJ执行此操作的代码非常短:
import ij.IJ;
import ij.ImagePlus;
import ij.io.Opener;
import ij.process.ImageProcessor;
public class Resizer {
public static void main(String[] args) {
processPicture("X0aPT.jpg", "output.jpg", 0.0198, ImageProcessor.NONE, 0.3);
}
public static void processPicture(String inputFile, String outputFilePath, double scaleFactor, int interpolationMethod, double sigmaFactor) {
Opener opener = new Opener();
ImageProcessor ip = opener.openImage(inputFile).getProcessor();
ip.blurGaussian(sigmaFactor / scaleFactor);
ip.setInterpolationMethod(interpolationMethod);
ImageProcessor outputProcessor = ip.resize((int)(ip.getWidth() * scaleFactor), (int)(ip.getHeight()*scaleFactor));
IJ.saveAs(new ImagePlus("", outputProcessor), outputFilePath.substring(outputFilePath.lastIndexOf('.')+1), outputFilePath);
}
}
BTW: You only need ij-1.49d.jar
(or equivalent for other version); there's no need to installImageJ.
顺便说一句:您只需要ij-1.49d.jar
(或其他版本的等效项);无需安装ImageJ。
回答by coobird
Thumbnailatoris a library that was written to create high-quality thumbnails in a simple manner, and doing a batch conversion of existing images is one of its use cases.
Thumbnailator是一个库,旨在以简单的方式创建高质量的缩略图,对现有图像进行批量转换是其用例之一。
Performing batch resizing
执行批量调整大小
For example, to adapt your example using Thumbnailator, you should be able to achieve similar results with the following code:
例如,要使用 Thumbnailator 调整您的示例,您应该能够使用以下代码获得类似的结果:
File folder = new File("/Users/me/Desktop/images/");
Thumbnails.of(folder.listFiles())
.size(112, 75)
.outputFormat("jpg")
.toFiles(Rename.PREFIX_DOT_THUMBNAIL);
This will go ahead and takes all files in your images
directory and proceed to process them one by one, try to resize them to fit in the dimensions of 112 x 75, and it will attempt to preserve the aspect ratio of the original image to prevent "warping" of the image.
这将继续并获取您images
目录中的所有文件并继续处理它们,尝试调整它们的大小以适应 112 x 75 的尺寸,并且它将尝试保留原始图像的纵横比以防止“扭曲”的图像。
Thumbnailator will go ahead and read all files, regardless of image types (as long as the Java Image IO supports the format, Thumbnailator will process it), perform the resizing operation and output the thumbnails as JPEG files, while tacking on a thumbnail.
to the beginning of the file name.
缩略图将继续读取所有文件,无论图像类型如何(只要 Java Image IO 支持该格式,缩略图就会处理它),执行调整大小操作并将缩略图输出为 JPEG 文件,同时添加thumbnail.
到开头的文件名。
The following is an illustration of how the file name of the original will be used in the file name of the thumbnail if the above code is executed.
下面举例说明如果执行上述代码,缩略图的文件名中将如何使用原件的文件名。
images/fireworks.jpg -> images/thumbnail.fireworks.jpg
images/illustration.png -> images/thumbnail.illustration.png
images/mountains.jpg -> images/thumbnail.mountains.jpg
Generating high-quality thumbnails
生成高质量的缩略图
In terms of image quality, as mentioned in Marco13's answer, the technique described by Chris Campbell in his The Perils of Image.getScaledInstance()is implemented in Thumbnailator, resulting in high-quality thumbnails without requiring any complicated processing.
在图像质量方面,正如Marco13 的回答中提到的,Chris Campbell 在他的The Perils of Image.getScaledInstance() 中描述的技术是在 Thumbnailator 中实现的,从而无需任何复杂的处理即可生成高质量的缩略图。
The following is the thumbnail generated when resizing the fireworks image shown in the original question using Thumbnailator:
以下是使用缩略图调整原始问题中显示的烟花图像大小时生成的缩略图:
The above image was created with the following code:
上图是使用以下代码创建的:
BufferedImage thumbnail =
Thumbnails.of(new URL("http://i.stack.imgur.com/X0aPT.jpg"))
.height(75)
.asBufferedImage();
ImageIO.write(thumbnail, "png", new File("24745147.png"));
The code shows that it can also accept URLs as input, and that Thumbnailator is also capable of creating BufferedImage
s as well.
代码显示它也可以接受 URL 作为输入,并且 Thumbnailator 也能够创建BufferedImage
s。
Disclaimer: I am the maintainer of the Thumbnailatorlibrary.
免责声明:我是缩略图库的维护者。
回答by Patrick Favre
Unfortunately, there is no recommended out-of-the-box scaling in Java that provides visually good results. Among others, here are the methods I recommend for scaling:
不幸的是,在 Java 中没有推荐的开箱即用缩放来提供良好的视觉效果。其中,以下是我推荐的缩放方法:
- Lanczos3 Resampling (usually visually better, but slower)
- Progressive Down Scaling (usually visually fine, can be quite fast)
- One-Step scaling for up scaling (with
Graphics2d
bicubic fast and good results, usually not as good as Lanczos3)
- Lanczos3 重采样(通常视觉效果更好,但速度较慢)
- 渐进式缩小(通常在视觉上很好,可以很快)
- 用于向上扩展的一步扩展(
Graphics2d
双三次快速和良好的结果,通常不如 Lanczos3)
Examples for every method can be found in this answer.
可以在此答案中找到每种方法的示例。
Visual Comparison
视觉比较
Here is your image scaled to 96x140
with different methods/libs. Click on the image to get the full size:
这是您96x140
使用不同方法/库缩放的图像。单击图像以获取完整尺寸:
- Morten Nobel's lib Lanczos3
- Thumbnailator Bilinear Progressive Scaling
- Imgscalr ULTRA_QUALTY (1/7 step Bicubic Progressive Scaling)
- Imgscalr QUALTY (1/2 step Bicubic Progressive Scaling)
- Morten Nobel's lib Bilinear Progressive Scaling
Graphics2d
Bicubic interpolationGraphics2d
Nearest Neighbor interpolation- Photoshop CS5 bicubic as reference
- Morten Nobel 的库 Lanczos3
- 缩略图双线性渐进缩放
- Imgscalr ULTRA_QALTY(1/7 步双三次渐进缩放)
- Imgscalr QUALTY(1/2 步双三次渐进缩放)
- Morten Nobel 的 lib Bilinear Progressive Scaling
Graphics2d
双三次插值Graphics2d
最近邻插值- Photoshop CS5 bicubic 作为参考
Unfortunately a single image is not enough to judge a scaling algorithm, you should test icons with sharp edges, photos with text, etc.
不幸的是,单个图像不足以判断缩放算法,您应该测试具有锐利边缘的图标、带有文本的照片等。
Lanczos Resampling
Lanczos 重采样
Is said to be good for up- and especially downscaling. Unfortunately there is no native implementation in current JDKso you either implement it yourself and use a lib like Morten Nobel's lib. A simple example using said lib:
据说有利于扩大规模,尤其是缩小规模。不幸的是,当前 JDK 中没有本地实现,因此您要么自己实现它,要么使用像Morten Nobel 的 lib 之类的库。使用上述库的一个简单示例:
ResampleOp resizeOp = new ResampleOp(dWidth, dHeight);
resizeOp.setFilter(ResampleFilters.getLanczos3Filter());
BufferedImage scaledImage = resizeOp.filter(imageToScale, null);
The lib is published on maven-centralwhich is not mentioned unfortunately. The downside is that it usually is very slow without any highly optimized or hardware accelerated implementations known to me. Nobel's implementation is about 8 times slower than a 1/2 step progressive scaling algorithm with Graphics2d
. Read more about this lib on his blog.
lib发布在 maven-central 上,不幸的是没有提到。缺点是它通常很慢,没有我知道的任何高度优化或硬件加速的实现。Nobel 的实现比 1/2 步渐进式缩放算法慢 8 倍Graphics2d
。在他的博客上阅读有关此库的更多信息。
Progressive Scaling
渐进式缩放
Mentioned in Chris Campbell's blog about scalingin Java, progressive scaling is basically incrementally scaling an image in smaller steps until the final dimensions are reached. Campbell describes it as halving width/height until you reach target. This produces good results and can be used with Graphics2D
which can be hardware accelerated, therefore usually having very good performance with acceptable results in most cases. The major downside of this is if downscaled less than half using Graphics2D
provides the same mediocre results since it is only scaled once.
在Chris Campbell 的关于Java缩放的博客中提到,渐进式缩放基本上是以较小的步长逐步缩放图像,直到达到最终尺寸。Campbell 将其描述为将宽度/高度减半,直到达到目标。这会产生良好的结果,并且可以与Graphics2D
硬件加速一起使用,因此在大多数情况下通常具有非常好的性能和可接受的结果。这样做的主要缺点是,如果缩小不到一半,则会Graphics2D
提供相同的平庸结果,因为它只缩小了一次。
Here is a simple example on how it works:
这是一个关于它如何工作的简单示例:
The following libs incorporate forms of progressive scaling based on Graphics2d
:
以下库包含基于以下内容的渐进式缩放形式Graphics2d
:
Thumbnailator v0.4.8
缩略图 v0.4.8
Uses the progressive bilinear algorithm if the target is at least half of every dimension, otherwise it uses simple Graphics2d
bilinear scaling and bicubic for upscaling.
如果目标至少是每个维度的一半,则使用渐进式双线性算法,否则使用简单的Graphics2d
双线性缩放和双三次放大。
Resizer resizer = DefaultResizerFactory.getInstance().getResizer(
new Dimension(imageToScale.getWidth(), imageToScale.getHeight()),
new Dimension(dWidth, dHeight))
BufferedImage scaledImage = new FixedSizeThumbnailMaker(
dWidth, dHeight, false, true).resizer(resizer).make(imageToScale);
It is as fast or slightly faster than one-step scaling with Graphics2d
scoring an average of 6.9 sec in my benchmark.
它与一步缩放一样快或略快Graphics2d
,在我的基准测试中平均得分为 6.9 秒。
Imgscalr v4.2
imgscalr v4.2
Uses progressive bicubic scaling. In the QUALITY
setting it uses Campbell style algorithm with halving the dimensions every step while the ULTRA_QUALITY
has finer steps, reducing the size every increment by 1/7 which generates generally softer images but minimizes the instances where only 1 iteration is used.
使用渐进式双三次缩放。在QUALITY
设置中,它使用 Campbell 风格算法,每一步将尺寸减半,同时ULTRA_QUALITY
具有更精细的步长,将每次增量的尺寸减小 1/7,这会生成通常更柔和的图像,但最大限度地减少仅使用 1 次迭代的实例。
BufferedImage scaledImage = Scalr.resize(imageToScale, Scalr.Method.ULTRA_QUALITY, Scalr.Mode.FIT_EXACT, dWidth, dHeight, bufferedImageOpArray);
The major downside is performance. ULTRA_QUALITY
is considerably slower than the other libs. Even QUALITY
a bit slower than Thumbnailator's implementation. My simple benchmarkresulted in 26.2 sec and 11.1 sec average respectively.
主要的缺点是性能。ULTRA_QUALITY
比其他库慢得多。甚至QUALITY
比 Thumbnailator 的实现慢一点。我的简单基准测试结果分别为 26.2 秒和 11.1 秒的平均值。
Morten Nobel's lib v0.8.6
Morten Nobel 的库 v0.8.6
Has also implementations for progressive scaling for all basic Graphics2d
(bilinear, bicubic & nearest neighbor)
还实现了所有基本Graphics2d
(双线性、双三次和最近邻)的渐进式缩放
BufferedImage scaledImage = new MultiStepRescaleOp(dWidth, dHeight, RenderingHints.VALUE_INTERPOLATION_BILINEAR).filter(imageToScale, null);
A word on JDK Scaling Methods
关于 JDK 缩放方法的一句话
Current jdk way to scale an image would be something like this
当前 jdk 缩放图像的方式是这样的
scaledImage = new BufferedImage(dWidth, dHeight, imageType);
Graphics2D graphics2D = scaledImage.createGraphics();
graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
graphics2D.drawImage(imageToScale, 0, 0, dWidth, dHeight, null);
graphics2D.dispose();
but most are very disappointed with the result of downscaling no matter what interpolation or other RenderHints
are used. On the other hand upscaling seems to produce acceptable images (best would be bicubic). In previous JDK version (we talking 90s v1.1) Image.getScaledInstance()
was introduced which provided good visual results with parameter SCALE_AREA_AVERAGING
but you are discouraged to use it - read the full explanation here.
但无论使用什么插值或其他RenderHints
方法,大多数人都对缩小比例的结果感到非常失望。另一方面,放大似乎可以产生可接受的图像(最好是双三次)。在以前的 JDK 版本(我们说的是 90 年代 v1.1)中Image.getScaledInstance()
,它提供了带有参数的良好视觉效果,SCALE_AREA_AVERAGING
但不鼓励您使用它 -请在此处阅读完整说明。
回答by Quark
We should not forget a TwelveMonkeys Library
我们不应该忘记十二猴图书馆
It contains a really impressive filter collection.
它包含一个非常令人印象深刻的过滤器集合。
Usage example:
用法示例:
BufferedImage input = ...; // Image to resample
int width, height = ...; // new width/height
BufferedImageOp resampler = new ResampleOp(width, height, ResampleOp.FILTER_LANCZOS);
BufferedImage output = resampler.filter(input, null);
回答by MaMaRo N0
After days of research i would prefer javaxt.
经过几天的研究,我更喜欢javaxt。
use Thejavaxt.io.Image
class has a constructor like:
使用javaxt.io.Image
该类有一个构造函数,如:
public Image(java.awt.image.BufferedImage bufferedImage)
so you can do (another example
):
所以你可以这样做(another example
):
javaxt.io.Image image = new javaxt.io.Image(bufferedImage);
image.setWidth(50);
image.setOutputQuality(1);
Here's the output:
这是输出:
回答by Maxwell Cheng
Below are my own implementation of Progressive Scaling, without using any external library. Hope this help.
下面是我自己的渐进式缩放实现,没有使用任何外部库。希望这有帮助。
private static BufferedImage progressiveScaling(BufferedImage before, Integer longestSideLength) {
if (before != null) {
Integer w = before.getWidth();
Integer h = before.getHeight();
Double ratio = h > w ? longestSideLength.doubleValue() / h : longestSideLength.doubleValue() / w;
//Multi Step Rescale operation
//This technique is describen in Chris Campbell's blog The Perils of Image.getScaledInstance(). As Chris mentions, when downscaling to something less than factor 0.5, you get the best result by doing multiple downscaling with a minimum factor of 0.5 (in other words: each scaling operation should scale to maximum half the size).
while (ratio < 0.5) {
BufferedImage tmp = scale(before, 0.5);
before = tmp;
w = before.getWidth();
h = before.getHeight();
ratio = h > w ? longestSideLength.doubleValue() / h : longestSideLength.doubleValue() / w;
}
BufferedImage after = scale(before, ratio);
return after;
}
return null;
}
private static BufferedImage scale(BufferedImage imageToScale, Double ratio) {
Integer dWidth = ((Double) (imageToScale.getWidth() * ratio)).intValue();
Integer dHeight = ((Double) (imageToScale.getHeight() * ratio)).intValue();
BufferedImage scaledImage = new BufferedImage(dWidth, dHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics2D = scaledImage.createGraphics();
graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
graphics2D.drawImage(imageToScale, 0, 0, dWidth, dHeight, null);
graphics2D.dispose();
return scaledImage;
}