Java 2D图像调整大小时忽略双三次/双线性插值渲染提示(OS X + Linux)

5

我正在尝试使用Image Voodoo插件为JRuby/Rails应用程序中上传的图像创建缩略图 - 问题是调整大小后的缩略图看起来很糟糕。

似乎生成缩略图的代码绝对正确地设置了插值渲染提示为"bicubic",但是在我们的开发环境(OS X)或生产Web服务器(Linux)上没有遵守这些提示。

我已经提取出生成缩略图的代码,并将其重写为一个纯Java应用程序(即从main()方法启动),并明确将插值渲染提示设置为"bicubic",并复制了(缺少的)双三次和双线性调整大小。

如预期,在OS X和Linux上,缩略图很丑陋和有锯齿,但在Windows上,它使用了双三次插值平滑地调整了图像大小。

是否有任何JVM环境设置和/或缺少的额外库可以使其工作?我为此问题苦思冥想。


好吧,至少我从这个上面得到了风滚草徽章。考虑只是将其外包给系统ImageMagick进程。 - madlep
4个回答

5
我知道这个问题很久以前就被提出了,但如果还有人遇到这个问题的话,请注意以下两点(主要是第一点):
  • Java中的非增量图像缩放非常粗糙,会抛弃大量像素数据,并且无论渲染提示如何,都会平均处理结果。
  • 在Java2D中处理支持性较差的BufferedImage类型(通常是GIF)可能会导致外观非常糟糕/抖动。
事实证明,旧的AreaAveragingScaleFilter可以做出好看的缩略图,但速度很慢,而且已经被Java2D团队弃用了。不幸的是,他们没有用任何好的开箱即用的替代方案来取代它,让我们自己摸索。
几年前,Java2D团队的Chris Campbell提出了增量缩放的概念——不是在一个操作中从起始分辨率到目标分辨率,而是分步进行,结果看起来更好。
考虑到这段代码相当庞大,我将所有最佳实践整理成一个名为imgscalr的库,并在Apache 2许可下发布。
最基本的使用方法如下:
BufferedImage img = ImageIO.read(...); // load image
BufferedImage scaledImg = Scalr.resize(img, 640);

在这种使用情况下,该库使用所谓的“自动”缩放模式,并将结果图像(保持其比例)适合于640x640的边界框中。因此,如果图像不是正方形而是标准的4:3图像,则会将其调整为640x480 - 参数只是它的最大尺寸。 Scalr class上有一堆其他方法(全部都是静态且易于使用),允许您控制一切。
为了获得可能的最佳缩略图,命令应如下所示:
BufferedImage img = ImageIO.read(...); // load image
BufferedImage scaledImg = Scalr.resize(img, Method.QUALITY, 
                                       150, 100, Scalr.OP_ANTIALIAS);

Scalr.OP_ANTIALIAS是可选的,但很多用户觉得当你在Java中缩小到足够小的缩略图时,一些像素值之间的转换有点过于离散,并使图像看起来“锐利”,因此很多用户要求软件提供一种方法来稍微软化缩略图。 这通过使用ConvolveOp ConvolveOp 完成,如果您以前从未使用过它们,请尝试找出正确的“内核”是一个痛苦的过程。 OP_ANTIALIAS常量定义在类上,在与另一个将imgscalr部署到其巴西社交网络(用于缩放资料照片)的用户进行了一周的测试后,我发现这是最好的反锯齿操作。 我将其包含在内,使每个人的生活变得更轻松。 此外,在所有这些示例之上,您可能会注意到当您缩放GIF和某些其他类型的图像(BMP)时,有时缩放结果与原始图像相比看起来非常糟糕...这是由于图像以不受支持的BufferedImage类型存在,因此Java2D会回退到使用其软件渲染管道,而不是更好支持的图像类型的硬件加速管道。

无论如何,这是一个非常冗长的方式来说“您可以使用imgscalr为您完成所有这些操作,而不必担心任何事情”。


非常酷的库,图像质量差异一下子就显而易见了。不过我注意到了一些有趣的事情——当我尝试将这个图片 http://postimg.org/image/4icpja3qj/ 调整为 134 x 100 时,Scalr.resize 调用给出的图像高度只有 99 像素,而不是 100 像素。我使用了你列出的 "Method.QUALITY" 示例,但即使加上额外的标志,它仍然短了一个像素。有什么想法吗? - Alkanshel
1
所有的resize调用都需要一个“目标”大小 - 但是imgscalr永远不会扭曲图像,这意味着它首先计算出比例,然后尽最大努力将图像调整为目标大小而不违反比例。可能无法使图像变为134x100而不违反其中的一个维度,因此它将其变为134x99。因此,如果您传入例如200x30的尺寸,对于该图像,它会将图像压缩到最小的框中并保持正确的比例。这就是为什么如果您只针对常见宽度进行调整,只需传入“200”作为示例。 - Riyad Kalla
考虑到图像的大小比例与之前的比例相同(450 x 334 vs. 134 x 100),比例真的需要改变吗?我理解你的想法,但这样的尺寸差异很难处理。 - Alkanshel
糟糕,看起来你已经使用了Scalr.Mode.FIT_EXACT提供了我所需的功能。谢谢,太棒了。 - Alkanshel

1

也许这对你来说是一个解决方案:

public BufferedImage resizeImage(BufferedImage source, int width, int height)
{
     BufferedImage result = new BufferedImage(widht, height, BufferedImage.TYPE_INT_ARGB);
     Graphics g = result.getGraphics();
     g.drawImage(source, 0, 0, widht, height, null);
     g.dispose();
     return result;
}

1
你可能想要强制转换 (Graphics2D)g,这样你就可以使用 setRenderingHints(),特别是使用 KEY_RENDERINGKEY_ANTIALIASING 来控制输出质量。 哦,还有一个拼写错误 widht/width :) - Matthieu

0
最终,升级到最新版本的ImageVoodoo似乎提高了质量。
通过查看源代码,它看起来像是他们正在进行一些奇怪的AWT渲染,然后将其拿出来。很糟糕,但它似乎有效。
仍然不如ImageMagick好,但比以前好多了。

0

1
路易斯 - 我猜你对我使用“相当大”的词语感到不满...对此我很抱歉,你是对的 - 核心逻辑非常简单...但它可以改进,这也是我在库中所做的,并围绕该核心机制添加了相当多的附加功能/功能 - 所以对于你的评论“拥有一个提供其他选项的库...”是的...我完全同意...这就是为什么我写了一个:D - Riyad Kalla
现在我想了想,我觉得我那个说法是错误的。你的库很有意义,因为它简化了整个过程...对此我感到非常抱歉。谢谢! - Luis Daniel Mesa Velasquez

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接