Java-在不损失质量的情况下调整图像大小
image
image-processing
java
4
0

我有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;
}

我正在使用的图像是这样的: 烟花-原创-大

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

调整大小-使用绘画-小

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

调整大小-使用Java程序-小

更新:使用BICUBIC没有显着差异

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

在此处输入图片说明

是否有提高程序输出质量的方法,所以我不必手动调整所有照片的大小?

先感谢您!

参考资料:
Stack Overflow
收藏
评论
共 6 个回答
高赞 时间 活跃

不幸的是,在Java中没有建议的即用型缩放可以提供视觉上良好的结果。除其他外,以下是我推荐的缩放方法:

  • Lanczos3重采样(通常在视觉上更好,但速度较慢)
  • 渐进式降级(通常在视觉上不错,可能很快)
  • 单步缩放以进行放大(具有Graphics2d双三次快速和良好的结果,通常不如Lanczos3更好)

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

视觉比较

这是您使用不同方法/库将图像缩放到96x140的图像。单击图像以获取完整大小:

比较

比较变焦

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

不幸的是,单个图像不足以判断缩放算法,您应该测试带有锐利边缘的图标,带有文本的照片等。

Lanczos重采样

据说对扩大规模特别是缩小规模很有好处。不幸的是,当前的JDK中没有本机实现,因此您可以自己实现,也可以使用Morten Nobel的lib之的库 。一个使用上述lib的简单示例:

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

该库发布在maven-central上 ,不幸的是它没有被提及。不利之处在于,如果没有我所知道的任何高度优化或硬件加速的实现,它通常会非常缓慢。 Nobel的实现比使用Graphics2d的1/2步渐进缩放算法慢大约8倍。 在他的博客上阅读有关此库的更多信息

逐步缩放

Chris Campbell的博客中提到有关 Java 缩放的内容时,渐进缩放基本上就是以较小的步长逐步缩放图像,直到达到最终尺寸为止。坎贝尔将其描述为将宽度/高度减半直至达到目标。这样可以产生良好的结果,并且可以与可以通过硬件加速的Graphics2D一起使用,因此通常在大多数情况下都具有非常好的性能和可接受的结果。这样做的主要缺点是,如果使用Graphics2D缩小比例不到一半,则会产生相同的中等结果,因为它只能缩放一次。

这是一个有关其工作原理的简单示例:

渐进缩放

以下库结合了基于Graphics2d的渐进缩放形式:

Thumbnailator v0.4.8

如果目标至少是每个尺寸的一半,则使用渐进式双线性算法,否则使用简单的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);

Graphics2d在我的基准测试中平均获得6.9秒的单步缩放速度一样快或稍快。

imgscalr v4.2

使用渐进式双三次缩放。在“ QUALITY设置中,它使用Campbell样式算法将每一步的尺寸减半,而ULTRA_QUALITY具有更精细的步长,将每个增量的大小减小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可以提供良好的视觉效果,但是不鼓励您使用它- 请在此处阅读完整说明

收藏
评论

给定您的输入图像,注释中第一个链接的答案中的方法(对Chris Campbell表示敬意)将产生以下缩略图之一:

在此处输入图片说明在此处输入图片说明

(另一个是您使用MS Paint创建的缩略图。很难称其中一个比另一个“更好” ...)

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

为了完整起见,MVCE

(编辑:我提到了Chris Campbell,并通过注释引用了源代码,但在此要更清楚:以下内容基于《 Perils of Image.getScaledInstance()》

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();
    }    
}
收藏
评论

经过几天的研究,我更喜欢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);

这是输出:

在此处输入图片说明

收藏
评论

我们不应忘记一个十二猴子图书馆

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

用法示例:

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);
收藏
评论

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

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;
}
收藏
评论

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

执行批量调整大小

例如,为了使用Thumbnailator修改示例,您应该能够通过以下代码获得相似的结果:

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

它将继续进行,并获取images目录中的所有文件,并一步一步地处理它们,尝试调整它们的大小以适合112 x 75的尺寸,并且它将尝试保留原始图像的长宽比以防止图像的“扭曲”。

Thumbnailator将继续读取所有文件,无论图像类型如何(只要Java Image IO支持该格式,Thumbnailator都会对其进行处理),执行调整大小操作并将缩略图输出为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)在他的《 Perils of Image.getScaledInstance()》中描述的技术是在Thumbnailator中实现的,从而可以生成高质量的缩略图,而无需任何复杂的处理。

以下是使用Thumbnailator调整原始问题中显示的烟花图像大小时生成的缩略图:

原始问题中图像的缩略图

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

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作为输入,并且Thumbnailator也能够创建BufferedImage


免责声明:我是Thumbnailator库的维护者。

收藏
评论
新手导航
  • 社区规范
  • 提出问题
  • 进行投票
  • 个人资料
  • 优化问题
  • 回答问题

关于我们

常见问题

内容许可

联系我们

@2020 AskGo
京ICP备20001863号