Java - 在不降低质量的情况下调整图像大小 [英] Java - resize image without losing quality

查看:31
本文介绍了Java - 在不降低质量的情况下调整图像大小的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有 10,000 张照片需要调整大小,所以我有一个 Java 程序可以做到这一点.不幸的是,图像质量损失惨重,我无法访问未压缩的图像.

import java.awt.Graphics;导入 java.awt.AlphaComposite;导入 java.awt.Graphics2D;导入 java.awt.Image;导入 java.awt.RenderingHints;导入 java.awt.image.BufferedImage;导入 java.io.File;导入 java.io.IOException;导入 javax.imageio.ImageIO;/*** 此类将调整给定文件夹中所有图像的大小* @作者**/公共类 JavaImageResizer {public static void main(String[] args) 抛出 IOException {文件夹 = new File("/Users/me/Desktop/images/");File[] listOfFiles = folder.listFiles();System.out.println("文件总数:"+listOfFiles.length);缓冲图像 img = null;BufferedImage tempPNG = null;BufferedImage tempJPG = null;文件 newFilePNG = null;文件 newFileJPG = null;for (int i = 0; i < listOfFiles.length; i++) {如果 (listOfFiles[i].isFile()) {System.out.println("文件" + 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("完成");}/*** 此函数调整图像文件大小并返回可保存到文件系统的BufferedImage 对象.*/public static BufferedImage resizeImage(最终图像图像,整数宽度,整数高度){int targetw = 0;int targeth = 75;如果(宽度>高度)targetw = 112;否则目标 w = 50;做 {如果(宽度>目标w){宽度/= 2;if (width  targeth){高度/= 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();返回缓冲图像;}

我正在使用的图像是这样的:

这是我在 Microsoft Paint 中手动调整大小:

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

更新:使用BICUBIC

没有显着差异

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

有没有办法提高程序输出的质量,这样我就不必手动调整所有照片的大小?

先谢谢你!

解决方案

不幸的是,在 Java 中没有推荐的开箱即用缩放来提供良好的视觉效果.其中,以下是我推荐的缩放方法:

  • Lanczos3 重采样(通常视觉效果更好,但速度较慢)
  • 渐进式缩小(通常在视觉上很好,可以很快)
  • 一步缩放以进行放大(使用 Graphics2d 双三次快速且效果良好,通常不如 Lanczos3)

可以在此答案中找到每种方法的示例.

视觉对比

这是您使用不同方法/库缩放到 96x140 的图像.点击图片获取完整尺寸:

  1. 莫滕诺贝尔的库 Lanczos3
  2. 缩略图双线性渐进式缩放
  3. Imgscalr ULTRA_QUALTY(1/7 步双三次渐进缩放)
  4. Imgscalr 质量(1/2 步双三次渐进缩放)
  5. Morten Nobel 的 lib Bilinear Progressive Scaling
  6. Graphics2d 双三次插值
  7. Graphics2d 最近邻插值
  8. Photoshop CS5 双三次作为参考

遗憾的是单张图片不足以判断缩放算法,您应该测试边缘锐利的图标,带有文字的照片等.

Lanczos 重采样

据说有利于扩大规模,尤其是缩小规模.不幸的是

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

缩略图 v0.4.8

如果目标至少是每个维度的一半,则使用渐进式双线性算法,否则使用简单的Graphics2d双线性缩放和双三次放大.

Resizer resizer = DefaultResizerFactory.getInstance().getResizer(新维度(imageToScale.getWidth(),imageToScale.getHeight()),新尺寸(dWidth,dHeight))BufferedImage scaledImage = new FixedSizeThumbnailMaker(dWidth, dHeight, false, true).resizer(resizer).make(imageToScale);

它与 Graphics2d 的一步缩放一样快或略快,在我的 基准.

Imgscalr v4.2

使用渐进式双三次缩放.在 QUALITY 设置中,它使用 Campbell 风格算法,每一步将尺寸减半,而 ULTRA_QUALITY 具有更精细的步长,每增加 1/7 将尺寸减小 1/7,从而生成通常更柔和的图像但最小化仅使用 1 次迭代的实例.

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

主要缺点是性能.ULTRA_QUALITY 比其他库慢得多.甚至 QUALITY 都比 Thumbnailator 的实现慢一点.我的简单基准分别导致平均 26.2 秒和 11.1 秒.

Morten Nobel 的 lib v0.8.6

还实现了所有基本 Graphics2d(双线性、双三次和最近邻)的渐进式缩放

BufferedImage scaledImage = new 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 版本(我们说的是 90 年代 v1.1)中引入了 Image.getScaledInstance(),它通过参数 SCALE_AREA_AVERAGING 提供了良好的视觉效果,但不鼓励您使用它 - 在此处阅读完整说明.

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.

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:

and this is the output from my program [bilinear]:

UPDATE: No significant difference using BICUBIC

and this is the output from my program [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!

解决方案

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:

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

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:

  1. Morten Nobel's lib Lanczos3
  2. Thumbnailator Bilinear Progressive Scaling
  3. Imgscalr ULTRA_QUALTY (1/7 step Bicubic Progressive Scaling)
  4. Imgscalr QUALTY (1/2 step Bicubic Progressive Scaling)
  5. Morten Nobel's lib Bilinear Progressive Scaling
  6. Graphics2d Bicubic interpolation
  7. Graphics2d Nearest Neighbor interpolation
  8. Photoshop CS5 bicubic as reference

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

Is said to be good for up- and especially downscaling. Unfortunately there is no native implementation in current JDK so you either implement it yourself and use a lib like Morten Nobel's lib. A simple example using said lib:

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

The lib is published on maven-central which 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.

Progressive Scaling

Mentioned in Chris Campbell's blog about scaling in 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.

Here is a simple example on how it works:

The following libs incorporate forms of progressive scaling based on Graphics2d:

Thumbnailator 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.

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.

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.

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 benchmark resulted in 26.2 sec and 11.1 sec average respectively.

Morten Nobel's lib v0.8.6

Has also implementations for progressive scaling for all basic Graphics2d (bilinear, bicubic & nearest neighbor)

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

A word on JDK Scaling Methods

Current jdk way to scale an image would be something like this

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.

这篇关于Java - 在不降低质量的情况下调整图像大小的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆