我有一个开源应用程序,可以将照片上传到Facebook。为了节省带宽,照片会在上传前自动调整大小(Facebook规定了最大大小限制)。有些人抱怨照片质量,事实上你可以看到差异(参见this issue了解一些演示图片)。
所以我的问题是,在不降低质量的情况下缩小Java中的图像(即照片)的“最佳”方法是什么,或者至少在质量损失/伪影最小的情况下?
答案 0 :(得分:16)
我已经用这些方法做了相当多的测试,增量缩放以及坚持支持良好的图像类型是关键 - 我看到亚历山大提到他仍然没有好运,这是一个无赖
我在大约6个月前发布了imgscalr library(Apache 2),以解决“我想要这张图片的好看的缩放副本,现在就开始!”的问题。在读完这样的10个问题之后就读了。
标准用法如下:
BufferedImage img = ImageIO.read(...); // load image
BufferedImage scaledImg = Scalr.resize(img, 640);
第二个参数是imgscalr用于缩放图像的边界宽度和高度 - 即使传入无效尺寸也保持其比例正确 - 有许多more detailed methods,但这是最简单的用法。
您想要的用例,例如,如果Facebook将图像限制为800x600像素,则看起来像这样:
BufferedImage img = ImageIO.read(...); // load image
BufferedImage scaledImg = Scalr.resize(img, Method.QUALITY, 800, 600);
这将确保图像保持最佳支持的图像类型,并使用Java可以集合的最高质量方法进行缩放。
在我自己的高分辨率测试中,我没有注意到使用此库/这些方法与缩放图像存在任何差距,除非ImageIO加载器将图像置于支持不良的图像类型时 - 例如,这发生了很多GIF。如果你把它们留下来并且不能从那些支持不好的类型中取出它们,它最终看起来真的很眩晕和可怕。
原因在于Java2D团队实际上为JDK可以处理的所有不同类型的BufferedImages提供了不同的硬件加速流水线 - 这些不常见的图像类型的子集都可以使用相同的软件渲染Java2D封面下的管道,导致图像质量差,有时完全不正确。这是一个PIA来解释并试图找出我刚刚将该逻辑直接写入库中。
如果你很好奇,两个最受支持的类型是BufferedImage.TYPE_INT_RGB和_ARGB。
答案 1 :(得分:12)
我已经尝试了所有这些 - 包括技巧here,而且我可以说你最好在任何界面上使用ImageMagick,Javas成像库在这方面不合适。您需要支持如此多的格式和算法才能使其正确。
答案 2 :(得分:6)
目前在Java中专门用于图像大小调整的两个最流行的开源库是:
Additonal使用Java Graphics2D
(see this question on how to do it)的JDK方式,这是创建bad results especially with downscaling的臭名昭着。此处还会省略Java interface to ImageMagick,因为它需要外部工具。
以下是将------WebKitFormBoundary6CDiwAgytdoviD7R
Content-Disposition: form-data; name="data"
{"key":"logos/TEST.png","acl":"public-read","policy":"eyJleHBpcmF0aW9uIjoiMjAzMC0xMi0zMVQxMjowMDowMC4wMDBaIiwiY29uZGl0aW9ucyI6W3siYWNsIjoicHVibGljLXJlYWQifSx7ImJ1Y2tldCI6ImhhdmVuLWRldi1pbWFnZXMifSxbInN0YXJ0cy13aXRoIiwiJGtleSIsImxvZ29zLyJdXX0=","X-amz-credential":"AKIAJAXCR6PRBXGHLU4Q/20160401/us-west-1/s3/aws4_request","X-amz-algorithm":"AWS4-HMAC-SHA256","X-amz-signature":"674d747f7e83375a1742c1a42cfd08799ffed7223aa9a255f4ea5eaad0df4470","Content-Type":"image/png","filename":"TEST.png","file":{}}
------WebKitFormBoundary6CDiwAgytdoviD7R--
png调整大小/降级到580x852
的结果的比较。作为参考,使用Photoshop CS5“save for web”调整大小。 注意:结果是1:1 libs创建的只是复制在一起。缩放不使用任何过滤,只使用简单的最近邻居算法。 Here you can find the original image.
- Thumbnailator 0.4.8,默认设置,无尺寸调整
- Photoshop CS5 with bicubic algorithm
- imgscalr 4.2,ULTRA_QUALITY设置,无尺寸调整
- Graphics2D(Java 8),提示提示VALUE_INTERPOLATION_BICUBIC,VALUE_RENDER_QUALITY,VALUE_ANTIALIAS_ON
醇>
我留给读者选择最佳结果,因为这是主观的。通常,除了145x213
之外,所有输出都有良好的输出。 Thumbnailator生成更清晰的图像,非常类似于Photoshop输出,而imgscalr的输出则相当柔和。对于图标/文本等,您需要更清晰的输出,对于图片,您可能需要更柔和的输出。
以下是使用此tool的非科学基准测试和114张图片,其尺寸从大约Graphics2D
到96x96
将其视为创建的425%图像:100%,150%,200 %,300%和400%缩放版本(因此114 * 5缩放操作)。所有库都使用与质量比较相同的设置(因此可能具有最高质量)。时间只是缩放而不是整个过程。使用8GB Ram和5次运行完成i5-2520M。
- Thumbnailator :7003.0ms | 6581.3ms | 6019.1ms | 6375.3ms | 8700.3ms
- imgscalr :25218.5ms | 25786.6ms | 25095.7ms | 25790.4ms | 29296.3ms
- Graphics2D :7387.6ms | 7177.0ms | 7048.2ms | 7132.3ms | 7510.3ms
Here is the code used in this benchmark.
有趣的是 Thumbnailator也是最快的,平均时间为6.9秒,其次是 Java2D,7.2秒让 imgscalr落后于26.2秒。这可能不公平,因为imgscalr设置为2560x1440
,这似乎非常昂贵; ULTRA_QUALITY
设置的平均值为11.1秒,更具竞争力。
答案 3 :(得分:5)
要使用自定义质量调整图像大小,请使用thumbnailator.jar。
答案 4 :(得分:2)
你使用什么渲染提示?通常双三次重采样将是最好的。在您链接的照片中,它们非常锯齿状,这让我觉得您使用最近邻居作为提示。
在您链接到的PictureScaler课程中,在paintComponent
方法中,它使用六种不同的方法调整图片大小。你有没有尝试过所有六个看哪个效果最好?
答案 5 :(得分:0)
经过一些令人沮丧的实验后,我找到了以下resize evaluation,并在我的项目中使用了多遍方法。
为此,我将getScaledInstance()方法复制到我的缩略图生成器类中,更改了我的图像读取方法以使用ImageIO(一个返回BufferedImage)并且现在非常高兴!
我将结果与Photoshop CS3中的调整大小进行了比较,结果非常相似。
答案 6 :(得分:0)
我想要保留高宽比的最高质量调整大小。 尝试了几件事并阅读了几个条目。失去了两天,最后我用普通的Java方法获得了最好的结果(同时尝试了ImageMagick和java-image-scaling库):
public static boolean resizeUsingJavaAlgo(String source, File dest, int width, int height) throws IOException {
BufferedImage sourceImage = ImageIO.read(new FileInputStream(source));
double ratio = (double) sourceImage.getWidth()/sourceImage.getHeight();
if (width < 1) {
width = (int) (height * ratio + 0.4);
} else if (height < 1) {
height = (int) (width /ratio + 0.4);
}
Image scaled = sourceImage.getScaledInstance(width, height, Image.SCALE_AREA_AVERAGING);
BufferedImage bufferedScaled = new BufferedImage(scaled.getWidth(null), scaled.getHeight(null), BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = bufferedScaled.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g2d.drawImage(scaled, 0, 0, width, height, null);
dest.createNewFile();
writeJpeg(bufferedScaled, dest.getCanonicalPath(), 1.0f);
return true;
}
/**
* Write a JPEG file setting the compression quality.
*
* @param image a BufferedImage to be saved
* @param destFile destination file (absolute or relative path)
* @param quality a float between 0 and 1, where 1 means uncompressed.
* @throws IOException in case of problems writing the file
*/
private static void writeJpeg(BufferedImage image, String destFile, float quality)
throws IOException {
ImageWriter writer = null;
FileImageOutputStream output = null;
try {
writer = ImageIO.getImageWritersByFormatName("jpeg").next();
ImageWriteParam param = writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(quality);
output = new FileImageOutputStream(new File(destFile));
writer.setOutput(output);
IIOImage iioImage = new IIOImage(image, null, null);
writer.write(null, iioImage, param);
} catch (IOException ex) {
throw ex;
} finally {
if (writer != null) {
writer.dispose();
}
if (output != null) {
output.close();
}
}
}