Java -在不丢失质量的情况下调整图像大小

时间:2022-05-07 23:03:58

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.

我有10,000张照片需要调整大小,所以我有一个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/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: Java -在不丢失质量的情况下调整图像大小

与我合作的一个形象是:

This is the manual resizing I've done in Microsoft Paint:

这是我在微软画图中所做的手动调整:

Java -在不丢失质量的情况下调整图像大小

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

这是我程序的输出[双线性]:

Java -在不丢失质量的情况下调整图像大小

UPDATE: No significant difference using BICUBIC

更新:使用双三基体没有显著差异

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

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

Java -在不丢失质量的情况下调整图像大小

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!

提前谢谢你!

7 个解决方案

#1


35  

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:

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

  • Lanczos3 Resampling (usually visually better, but slower)
  • Lanczos3重新采样(通常在视觉上更好,但速度更慢)
  • 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)
  • 一步缩放比例缩放(使用Graphics2d双侧快速和良好的结果,通常不像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:

这是您的图像缩放到96x140与不同的方法/libs。点击图片获取完整尺寸:

Java -在不丢失质量的情况下调整图像大小

Java -在不丢失质量的情况下调整图像大小

  1. Morten Nobel's lib Lanczos3
  2. Morten诺贝尔的*Lanczos3
  3. Thumbnailator Bilinear Progressive Scaling
  4. Thumbnailator双线性渐进扩展
  5. Imgscalr ULTRA_QUALTY (1/7 step Bicubic Progressive Scaling)
  6. Imgscalr ultra - _qualty(1/7步双三次递增洗面)
  7. Imgscalr QUALTY (1/2 step Bicubic Progressive Scaling)
  8. Imgscalr QUALTY(半步双三次递增缩放)
  9. Morten Nobel's lib Bilinear Progressive Scaling
  10. Morten Nobel的*双线性渐进式量表法
  11. Graphics2d Bicubic interpolation
  12. Graphics2d双立方插值
  13. Graphics2d Nearest Neighbor interpolation
  14. Graphics2d最近邻插值
  15. Photoshop CS5 bicubic as reference
  16. Photoshop CS5双ubic作为参考

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:

据说它对升级很有好处,尤其是对降级。不幸的是,当前JDK中没有本机实现,所以您要么自己实现它,要么使用Morten Nobel的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.

lib是在maven-central上发布的,不幸的是它没有被提及。缺点是,如果没有我所知道的任何高度优化或硬件加速实现,它通常会很慢。诺贝尔的实现比使用Graphics2d的1/2步渐进缩放算法慢8倍。在他的博客上阅读更多关于这个库的信息。

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.

Chris Campbell在他的关于Java扩展的博客中提到,渐进式扩展基本上是以较小的步骤逐步扩展图像,直到达到最终的维度。坎贝尔将其描述为将宽度/高度减半,直到达到目标。这可以产生良好的结果,并且可以与Graphics2D一起使用,它可以是硬件加速的,因此在大多数情况下,通常具有非常好的性能和可接受的结果。这种方法的主要缺点是,如果使用Graphics2D缩小到不足一半就会得到相同的平庸结果,因为它只被缩小一次。

Here is a simple example on how it works:

这里有一个简单的例子:

Java -在不丢失质量的情况下调整图像大小

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

下面的libs结合了基于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.

如果目标至少是每个维度的一半,则使用累进双线性算法,否则使用简单的Graphics2d双线性标度和双三次线性标度。

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.

在我的基准测试中,Graphics2d的平均评分为6.9秒,这比一步扩展的速度快或略快。

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.

采用先进的双三次的扩展。在质量设置中,它使用Campbell风格的算法,每一步都将维度减半,而ultra - _quality有更精细的步骤,将每个增量的大小减少1/7,这将生成更柔和的图像,但将只使用1次迭代的实例最小化。

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.

主要的缺点是性能。超高质量比其他的libs慢得多。甚至质量也比Thumbnailator的实现慢一点。我的简单基准分别是26.2秒和11.1秒。

Morten Nobel's lib v0.8.6

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

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

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

当前的缩放图像的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();

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.

但是大多数人对降级的结果非常失望,无论使用什么插值或其他渲染提示。另一方面,向上缩放似乎可以产生可接受的图像(最好是双三次)。在之前的JDK版本(我们讨论的是90年代v1.1)中引入了Image.getScaledInstance(),它通过参数scale_area_average提供了很好的视觉效果,但是不建议您使用它——请阅读这里的完整解释。

#2


31  

Thumbnailator is a library that was written to create high-quality thumbnails in a simple manner, and doing a batch conversion of existing images is one of its use cases.

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

Performing batch resizing

For example, to adapt your example using Thumbnailator, you should be able to achieve similar results with the following code:

例如,要使用Thumbnailator调整示例,您应该能够使用以下代码实现类似的结果:

File folder = new File("/Users/me/Desktop/images/");
Thumbnails.of(folder.listFiles())
    .size(112, 75)
    .outputFormat("jpg")
    .toFiles(Rename.PREFIX_DOT_THUMBNAIL);

This will go ahead and takes all files in your images directory and proceed to process them one by one, try to resize them to fit in the dimensions of 112 x 75, and it will attempt to preserve the aspect ratio of the original image to prevent "warping" of the image.

这将继续,需要图片目录中的所有文件和继续处理它们一个接一个,试着调整,以适应他们的尺寸112 x 75,它将试图保持原始图像的宽高比,防止“扭曲”的形象。

Thumbnailator will go ahead and read all files, regardless of image types (as long as the Java Image IO supports the format, Thumbnailator will process it), perform the resizing operation and output the thumbnails as JPEG files, while tacking on a thumbnail. to the beginning of the file name.

Thumbnailator将继续读取所有文件,而不考虑图像类型(只要Java image IO支持这种格式,Thumbnailator将处理这种格式),执行调整大小操作,并将缩略图作为JPEG文件输出,同时附加一个缩略图。到文件名的开头。

The following is an illustration of how the file name of the original will be used in the file name of the thumbnail if the above code is executed.

下面的示例演示了如果执行上面的代码,将如何在缩略图的文件名中使用原始文件的文件名。

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

Generating high-quality thumbnails

In terms of image quality, as mentioned in Marco13's answer, the technique described by Chris Campbell in his The Perils of Image.getScaledInstance() is implemented in Thumbnailator, resulting in high-quality thumbnails without requiring any complicated processing.

在图像质量方面,正如Marco13的回答所提到的,Chris Campbell在他的《图片的危险》中所描述的技术。getscaledinstance()是在Thumbnailator中实现的,结果是高质量的缩略图,不需要任何复杂的处理。

The following is the thumbnail generated when resizing the fireworks image shown in the original question using Thumbnailator:

以下是用缩略图修改原始问题中的烟火图像时产生的缩略图:

Java -在不丢失质量的情况下调整图像大小

The above image was created with the following code:

上面的图像是用下面的代码创建的:

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

ImageIO.write(thumbnail, "png", new File("24745147.png"));

The code shows that it can also accept URLs as input, and that Thumbnailator is also capable of creating BufferedImages as well.

代码显示,它还可以接受url作为输入,而这个缩略图符也可以创建缓冲编辑器。


Disclaimer: I am the maintainer of the Thumbnailator library.

免责声明:我是缩略图库的维护人员。

#3


15  

Given your input image, the method from the answer in the first link in the comments (kudos to Chris Campbell) produces one of the following thumbnails:

给你输入的图片,在评论的第一个链接的答案的方法(对克里斯·坎贝尔的赞扬)产生以下的缩略图之一:

Java -在不丢失质量的情况下调整图像大小Java -在不丢失质量的情况下调整图像大小

(The other one is the thumbnail that you created with MS Paint. It's hard to call one of them "better" than the other...)

(另一个是你用MS Paint创建的缩略图。很难说其中的一个比另一个“好”。

EDIT: Just to point this out as well: The main problem with your original code was that you did not really scale the image in multiple steps. You just used a strange loop to "compute" the target size. The key point is that you actually perform the scaling in multiple steps.

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

Just for completeness, the MVCE

只是为了完整性,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();
    }    
}

#4


3  

We should not forget a TwelveMonkeys Library

我们不应该忘记12个键库

It contains a really impressive filter collection.

它包含了一个令人印象深刻的过滤器集合。

Usage example:

使用的例子:

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

#5


1  

The result seems to be better (than the result of your program), if you apply Gaussian blur before resizing:

如果您在调整大小之前应用高斯模糊,结果似乎比程序的结果更好:

This is the result I get, with sigma * (scale factor) = 0.3:

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

Java -在不丢失质量的情况下调整图像大小

With ImageJ the code to do this is quite short:

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

}

BTW: You only need ij-1.49d.jar (or equivalent for other version); there's no need to install ImageJ.

顺便说一下,你只需要ij-1.49d。jar(或同等版本);没有必要安装ImageJ。

#6


1  

After days of research i would prefer javaxt.

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

use Thejavaxt.io.Image class has a constructor like:

使用Thejavaxt.io。Image类有一个构造函数:

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

so you can do (another example):

你可以做(另一个例子)

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

Here's the output:

输出:

Java -在不丢失质量的情况下调整图像大小

#7


1  

Below are my own implementation of Progressive Scaling, without using any external library. Hope this help.

下面是我自己的渐进扩展实现,不使用任何外部库。希望这个有帮助。

private static BufferedImage progressiveScaling(BufferedImage before, Integer longestSideLength) {
    if (before != null) {
        Integer w = before.getWidth();
        Integer h = before.getHeight();

        Double ratio = h > w ? longestSideLength.doubleValue() / h : longestSideLength.doubleValue() / w;

        //Multi Step Rescale operation
        //This technique is describen in Chris Campbell’s blog The Perils of Image.getScaledInstance(). As Chris mentions, when downscaling to something less than factor 0.5, you get the best result by doing multiple downscaling with a minimum factor of 0.5 (in other words: each scaling operation should scale to maximum half the size).
        while (ratio < 0.5) {
            BufferedImage tmp = scale(before, 0.5);
            before = tmp;
            w = before.getWidth();
            h = before.getHeight();
            ratio = h > w ? longestSideLength.doubleValue() / h : longestSideLength.doubleValue() / w;
        }
        BufferedImage after = scale(before, ratio);
        return after;
    }
    return null;
}

private static BufferedImage scale(BufferedImage imageToScale, Double ratio) {
    Integer dWidth = ((Double) (imageToScale.getWidth() * ratio)).intValue();
    Integer dHeight = ((Double) (imageToScale.getHeight() * ratio)).intValue();
    BufferedImage scaledImage = new BufferedImage(dWidth, dHeight, BufferedImage.TYPE_INT_RGB);
    Graphics2D graphics2D = scaledImage.createGraphics();
    graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    graphics2D.drawImage(imageToScale, 0, 0, dWidth, dHeight, null);
    graphics2D.dispose();
    return scaledImage;
}

#1


35  

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:

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

  • Lanczos3 Resampling (usually visually better, but slower)
  • Lanczos3重新采样(通常在视觉上更好,但速度更慢)
  • 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)
  • 一步缩放比例缩放(使用Graphics2d双侧快速和良好的结果,通常不像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:

这是您的图像缩放到96x140与不同的方法/libs。点击图片获取完整尺寸:

Java -在不丢失质量的情况下调整图像大小

Java -在不丢失质量的情况下调整图像大小

  1. Morten Nobel's lib Lanczos3
  2. Morten诺贝尔的*Lanczos3
  3. Thumbnailator Bilinear Progressive Scaling
  4. Thumbnailator双线性渐进扩展
  5. Imgscalr ULTRA_QUALTY (1/7 step Bicubic Progressive Scaling)
  6. Imgscalr ultra - _qualty(1/7步双三次递增洗面)
  7. Imgscalr QUALTY (1/2 step Bicubic Progressive Scaling)
  8. Imgscalr QUALTY(半步双三次递增缩放)
  9. Morten Nobel's lib Bilinear Progressive Scaling
  10. Morten Nobel的*双线性渐进式量表法
  11. Graphics2d Bicubic interpolation
  12. Graphics2d双立方插值
  13. Graphics2d Nearest Neighbor interpolation
  14. Graphics2d最近邻插值
  15. Photoshop CS5 bicubic as reference
  16. Photoshop CS5双ubic作为参考

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:

据说它对升级很有好处,尤其是对降级。不幸的是,当前JDK中没有本机实现,所以您要么自己实现它,要么使用Morten Nobel的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.

lib是在maven-central上发布的,不幸的是它没有被提及。缺点是,如果没有我所知道的任何高度优化或硬件加速实现,它通常会很慢。诺贝尔的实现比使用Graphics2d的1/2步渐进缩放算法慢8倍。在他的博客上阅读更多关于这个库的信息。

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.

Chris Campbell在他的关于Java扩展的博客中提到,渐进式扩展基本上是以较小的步骤逐步扩展图像,直到达到最终的维度。坎贝尔将其描述为将宽度/高度减半,直到达到目标。这可以产生良好的结果,并且可以与Graphics2D一起使用,它可以是硬件加速的,因此在大多数情况下,通常具有非常好的性能和可接受的结果。这种方法的主要缺点是,如果使用Graphics2D缩小到不足一半就会得到相同的平庸结果,因为它只被缩小一次。

Here is a simple example on how it works:

这里有一个简单的例子:

Java -在不丢失质量的情况下调整图像大小

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

下面的libs结合了基于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.

如果目标至少是每个维度的一半,则使用累进双线性算法,否则使用简单的Graphics2d双线性标度和双三次线性标度。

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.

在我的基准测试中,Graphics2d的平均评分为6.9秒,这比一步扩展的速度快或略快。

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.

采用先进的双三次的扩展。在质量设置中,它使用Campbell风格的算法,每一步都将维度减半,而ultra - _quality有更精细的步骤,将每个增量的大小减少1/7,这将生成更柔和的图像,但将只使用1次迭代的实例最小化。

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.

主要的缺点是性能。超高质量比其他的libs慢得多。甚至质量也比Thumbnailator的实现慢一点。我的简单基准分别是26.2秒和11.1秒。

Morten Nobel's lib v0.8.6

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

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

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

当前的缩放图像的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();

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.

但是大多数人对降级的结果非常失望,无论使用什么插值或其他渲染提示。另一方面,向上缩放似乎可以产生可接受的图像(最好是双三次)。在之前的JDK版本(我们讨论的是90年代v1.1)中引入了Image.getScaledInstance(),它通过参数scale_area_average提供了很好的视觉效果,但是不建议您使用它——请阅读这里的完整解释。

#2


31  

Thumbnailator is a library that was written to create high-quality thumbnails in a simple manner, and doing a batch conversion of existing images is one of its use cases.

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

Performing batch resizing

For example, to adapt your example using Thumbnailator, you should be able to achieve similar results with the following code:

例如,要使用Thumbnailator调整示例,您应该能够使用以下代码实现类似的结果:

File folder = new File("/Users/me/Desktop/images/");
Thumbnails.of(folder.listFiles())
    .size(112, 75)
    .outputFormat("jpg")
    .toFiles(Rename.PREFIX_DOT_THUMBNAIL);

This will go ahead and takes all files in your images directory and proceed to process them one by one, try to resize them to fit in the dimensions of 112 x 75, and it will attempt to preserve the aspect ratio of the original image to prevent "warping" of the image.

这将继续,需要图片目录中的所有文件和继续处理它们一个接一个,试着调整,以适应他们的尺寸112 x 75,它将试图保持原始图像的宽高比,防止“扭曲”的形象。

Thumbnailator will go ahead and read all files, regardless of image types (as long as the Java Image IO supports the format, Thumbnailator will process it), perform the resizing operation and output the thumbnails as JPEG files, while tacking on a thumbnail. to the beginning of the file name.

Thumbnailator将继续读取所有文件,而不考虑图像类型(只要Java image IO支持这种格式,Thumbnailator将处理这种格式),执行调整大小操作,并将缩略图作为JPEG文件输出,同时附加一个缩略图。到文件名的开头。

The following is an illustration of how the file name of the original will be used in the file name of the thumbnail if the above code is executed.

下面的示例演示了如果执行上面的代码,将如何在缩略图的文件名中使用原始文件的文件名。

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

Generating high-quality thumbnails

In terms of image quality, as mentioned in Marco13's answer, the technique described by Chris Campbell in his The Perils of Image.getScaledInstance() is implemented in Thumbnailator, resulting in high-quality thumbnails without requiring any complicated processing.

在图像质量方面,正如Marco13的回答所提到的,Chris Campbell在他的《图片的危险》中所描述的技术。getscaledinstance()是在Thumbnailator中实现的,结果是高质量的缩略图,不需要任何复杂的处理。

The following is the thumbnail generated when resizing the fireworks image shown in the original question using Thumbnailator:

以下是用缩略图修改原始问题中的烟火图像时产生的缩略图:

Java -在不丢失质量的情况下调整图像大小

The above image was created with the following code:

上面的图像是用下面的代码创建的:

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

ImageIO.write(thumbnail, "png", new File("24745147.png"));

The code shows that it can also accept URLs as input, and that Thumbnailator is also capable of creating BufferedImages as well.

代码显示,它还可以接受url作为输入,而这个缩略图符也可以创建缓冲编辑器。


Disclaimer: I am the maintainer of the Thumbnailator library.

免责声明:我是缩略图库的维护人员。

#3


15  

Given your input image, the method from the answer in the first link in the comments (kudos to Chris Campbell) produces one of the following thumbnails:

给你输入的图片,在评论的第一个链接的答案的方法(对克里斯·坎贝尔的赞扬)产生以下的缩略图之一:

Java -在不丢失质量的情况下调整图像大小Java -在不丢失质量的情况下调整图像大小

(The other one is the thumbnail that you created with MS Paint. It's hard to call one of them "better" than the other...)

(另一个是你用MS Paint创建的缩略图。很难说其中的一个比另一个“好”。

EDIT: Just to point this out as well: The main problem with your original code was that you did not really scale the image in multiple steps. You just used a strange loop to "compute" the target size. The key point is that you actually perform the scaling in multiple steps.

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

Just for completeness, the MVCE

只是为了完整性,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();
    }    
}

#4


3  

We should not forget a TwelveMonkeys Library

我们不应该忘记12个键库

It contains a really impressive filter collection.

它包含了一个令人印象深刻的过滤器集合。

Usage example:

使用的例子:

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

#5


1  

The result seems to be better (than the result of your program), if you apply Gaussian blur before resizing:

如果您在调整大小之前应用高斯模糊,结果似乎比程序的结果更好:

This is the result I get, with sigma * (scale factor) = 0.3:

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

Java -在不丢失质量的情况下调整图像大小

With ImageJ the code to do this is quite short:

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

}

BTW: You only need ij-1.49d.jar (or equivalent for other version); there's no need to install ImageJ.

顺便说一下,你只需要ij-1.49d。jar(或同等版本);没有必要安装ImageJ。

#6


1  

After days of research i would prefer javaxt.

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

use Thejavaxt.io.Image class has a constructor like:

使用Thejavaxt.io。Image类有一个构造函数:

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

so you can do (another example):

你可以做(另一个例子)

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

Here's the output:

输出:

Java -在不丢失质量的情况下调整图像大小

#7


1  

Below are my own implementation of Progressive Scaling, without using any external library. Hope this help.

下面是我自己的渐进扩展实现,不使用任何外部库。希望这个有帮助。

private static BufferedImage progressiveScaling(BufferedImage before, Integer longestSideLength) {
    if (before != null) {
        Integer w = before.getWidth();
        Integer h = before.getHeight();

        Double ratio = h > w ? longestSideLength.doubleValue() / h : longestSideLength.doubleValue() / w;

        //Multi Step Rescale operation
        //This technique is describen in Chris Campbell’s blog The Perils of Image.getScaledInstance(). As Chris mentions, when downscaling to something less than factor 0.5, you get the best result by doing multiple downscaling with a minimum factor of 0.5 (in other words: each scaling operation should scale to maximum half the size).
        while (ratio < 0.5) {
            BufferedImage tmp = scale(before, 0.5);
            before = tmp;
            w = before.getWidth();
            h = before.getHeight();
            ratio = h > w ? longestSideLength.doubleValue() / h : longestSideLength.doubleValue() / w;
        }
        BufferedImage after = scale(before, ratio);
        return after;
    }
    return null;
}

private static BufferedImage scale(BufferedImage imageToScale, Double ratio) {
    Integer dWidth = ((Double) (imageToScale.getWidth() * ratio)).intValue();
    Integer dHeight = ((Double) (imageToScale.getHeight() * ratio)).intValue();
    BufferedImage scaledImage = new BufferedImage(dWidth, dHeight, BufferedImage.TYPE_INT_RGB);
    Graphics2D graphics2D = scaledImage.createGraphics();
    graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    graphics2D.drawImage(imageToScale, 0, 0, dWidth, dHeight, null);
    graphics2D.dispose();
    return scaledImage;
}