Java 如何缩放BuffereImage

Java 如何缩放BuffereImage,java,image,image-processing,bufferedimage,image-scaling,Java,Image,Image Processing,Bufferedimage,Image Scaling,遵循javadocs,我尝试缩放BufferedImage,但没有成功。下面是我的代码: BufferedImage image = MatrixToImageWriter.getBufferedImage(encoded); Graphics2D grph = image.createGraphics(); grph.scale(2.0, 2.0); grph.dispose(); 我不明白它为什么不起作用,有什么帮助吗?scale(…)的工作原理有点不同。您可以使用bufferedImag

遵循javadocs,我尝试缩放
BufferedImage
,但没有成功。下面是我的代码:

BufferedImage image = MatrixToImageWriter.getBufferedImage(encoded);
Graphics2D grph = image.createGraphics();
grph.scale(2.0, 2.0);
grph.dispose();

我不明白它为什么不起作用,有什么帮助吗?

scale(…)
的工作原理有点不同。您可以使用
bufferedImage.getScaledInstance(..)

正如@Bozho所说,您可能想要使用
getScaledInstance

然而,要了解
grph.scale(2.0,2.0)
是如何工作的,您可以看看以下代码:

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;


class Main {
    public static void main(String[] args) throws IOException {

        final int SCALE = 2;

        Image img = new ImageIcon("duke.png").getImage();

        BufferedImage bi = new BufferedImage(SCALE * img.getWidth(null),
                                             SCALE * img.getHeight(null),
                                             BufferedImage.TYPE_INT_ARGB);

        Graphics2D grph = (Graphics2D) bi.getGraphics();
        grph.scale(SCALE, SCALE);

        // everything drawn with grph from now on will get scaled.

        grph.drawImage(img, 0, 0, null);
        grph.dispose();

        ImageIO.write(bi, "png", new File("duke_double_size.png"));
    }
}
给定duke.png


它会生成duke\u double\u size.png:

不幸的是,getScaleInstance()的性能非常差,如果没有问题的话

另一种方法是创建一个新的BuffereImage,并在新的BuffereImage上绘制原始的缩放版本

BufferedImage resized = new BufferedImage(newWidth, newHeight, original.getType());
Graphics2D g = resized.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
    RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(original, 0, 0, newWidth, newHeight, 0, 0, original.getWidth(),
    original.getHeight(), null);
g.dispose();
newWidth、newHeight表示新的BuffereImage大小,必须正确计算。 在因子缩放的情况下:

int newWidth = new Double(original.getWidth() * widthFactor).intValue();
int newHeight = new Double(original.getHeight() * heightFactor).intValue();
编辑:找到了说明性能问题的文章:

提供了选择插值类型的额外灵活性

BufferedImage before = getBufferedImage(encoded);
int w = before.getWidth();
int h = before.getHeight();
BufferedImage after = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
AffineTransform at = new AffineTransform();
at.scale(2.0, 2.0);
AffineTransformOp scaleOp = 
   new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR);
after = scaleOp.filter(before, after);

所示片段说明了,而不是;这涉及到:;使用imgscalr–Java图像缩放库检查了一些相关示例。

BufferedImage image =
     Scalr.resize(originalImage, Scalr.Method.BALANCED, newWidth, newHeight);

这对我来说已经足够快了。

如果您不介意使用外部库,可以执行s的缩放

Thumbnailator将负责处理处理(如使用和设置适当),以便使用简单的fluent API调用调整图像大小:

BufferedImage image = Thumbnails.of(originalImage).scale(2.0).asBufferedImage();
虽然Thumbnailator,顾名思义,是面向缩小图像的,但它也可以很好地放大图像,在默认的大小调整器实现中使用双线性插值



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

要缩放图像,您需要创建一个新图像并绘制到其中。一种方法是使用
AffineTransferOp
filter()
方法,如建议的那样。这允许您选择插值技术

private static BufferedImage scale1(BufferedImage before, double scale) {
    int w = before.getWidth();
    int h = before.getHeight();
    // Create a new image of the proper size
    int w2 = (int) (w * scale);
    int h2 = (int) (h * scale);
    BufferedImage after = new BufferedImage(w2, h2, BufferedImage.TYPE_INT_ARGB);
    AffineTransform scaleInstance = AffineTransform.getScaleInstance(scale, scale);
    AffineTransformOp scaleOp 
        = new AffineTransformOp(scaleInstance, AffineTransformOp.TYPE_BILINEAR);

    scaleOp.filter(before, after);
    return after;
}
另一种方法是简单地将原始图像绘制到新图像中,使用缩放操作进行缩放。此方法非常相似,但它也说明了如何在最终图像中绘制所需的任何内容。(我在两种方法开始不同的地方放了一个空白行。)

附录:结果

为了说明差异,我比较了下面五种方法的结果。下面是结果的样子,放大和缩小,以及性能数据。(每次跑步的表现各不相同,因此仅将这些数字作为粗略的指南。)上图为原始图像。我把它放大两倍半

如您所见,
scaleBilinear()
中使用的
AffineTransformOp.filter()
,比
scale2()
中的
Graphics2D.drawImage()
标准绘图方法要快。此外,双三次插值速度最慢,但在扩展图像时效果最好。(就性能而言,只能将其与
scaleBilinear()
scaleNearest()。
进行比较)双线性似乎更适合缩小图像,尽管这是一个困难的要求。而最近的邻居是最快的,结果最差。双线性似乎是速度和质量之间的最佳折衷。在
problem()
方法中调用的
Image.getScaledInstance()
,性能非常差,返回的质量与NearestNeighbor相同。(性能编号仅用于扩展图像。)



我尝试过这种方法,但GetScaleInstance返回ToolkitImage,就我而言,它不适合我。谢谢您可以通过将其光栅复制到新的BuffereImage来将其转换为BuffereImage。对于“将图像转换为缓冲区图像”来说,当您有一个类似于:
after=…
?@Martijn的语句时,是否真的需要为
after
分配所有内存?这取决于您想要加入哪个
ColorModel
。它返回一个引用,因此没有额外的内存。有没有办法用所需的宽度和高度而不是比例因子来实现这一点?是的,比例参数只是x和y的新旧比例;保持它们相等以保持纵横比。我刚刚测试过。正如我所怀疑的,之后的
大小相同,它只是原版的左上角四分之一。修复方法是在
之后创建
时将w和h乘以比例。我认为GetScaleInstance()现在更快了,至少如果你有一张像样的图形卡,这要归功于优化的Java2D渲染管道。仅供参考,我看到了
渲染提示的其他可能值。按键插值
非常有效!这在不改变图书馆的情况下对我帮助很大。同意,这是最好的解决方案,避免了使用仿射变换和其他各种方法时出现的透明度、错误翻译、错误颜色等问题。太棒了!我在这篇文章中看到的第一个解决方案让我得到了我所需要的。一个优秀的教程:在撰写本文时,最流行的答案是错误的答案。它放大图像,但返回相同大小的图像,图像丢失了3/4。这是垃圾神给的答案。很近,但有个小虫子。谢谢,@MiguelMunoz。由于我们有更多的反馈,我可以更改答案。这是一个优秀的库!与Graphics2DGreat库相比,缩略图简直太神奇了!与科特林也很相配。也似乎比其他一些选项更新。我尝试了这段代码,但没有得到显示的结果。我得到的结果是更严重的别名。如果放大浏览器中的第一个图像,直到得到与第二个图像大小相同的图像,您将更好地了解此代码生成的内容。(我尝试将生成的图像放入此注释中,但没有效果。我猜注释中不允许使用图像。)您可以尝试
grph.setRenderingHint(R
private static BufferedImage scale2(BufferedImage before, double scale) {
    int w = before.getWidth();
    int h = before.getHeight();
    // Create a new image of the proper size
    int w2 = (int) (w * scale);
    int h2 = (int) (h * scale);
    BufferedImage after = new BufferedImage(w2, h2, BufferedImage.TYPE_INT_ARGB);
    AffineTransform scaleInstance = AffineTransform.getScaleInstance(scale, scale);
    AffineTransformOp scaleOp 
        = new AffineTransformOp(scaleInstance, AffineTransformOp.TYPE_BILINEAR);

    Graphics2D g2 = (Graphics2D) after.getGraphics();
    // Here, you may draw anything you want into the new image, but we're
    // drawing a scaled version of the original image.
    g2.drawImage(before, scaleOp, 0, 0);
    g2.dispose();
    return after;
}
public static BufferedImage scaleBilinear(BufferedImage before, double scale) {
    final int interpolation = AffineTransformOp.TYPE_BILINEAR;
    return scale(before, scale, interpolation);
}

public static BufferedImage scaleBicubic(BufferedImage before, double scale) {
    final int interpolation = AffineTransformOp.TYPE_BICUBIC;
    return scale(before, scale, interpolation);
}

public static BufferedImage scaleNearest(BufferedImage before, double scale) {
    final int interpolation = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
    return scale(before, scale, interpolation);
}

@NotNull
private static 
BufferedImage scale(final BufferedImage before, final double scale, final int type) {
    int w = before.getWidth();
    int h = before.getHeight();
    int w2 = (int) (w * scale);
    int h2 = (int) (h * scale);
    BufferedImage after = new BufferedImage(w2, h2, before.getType());
    AffineTransform scaleInstance = AffineTransform.getScaleInstance(scale, scale);
    AffineTransformOp scaleOp = new AffineTransformOp(scaleInstance, type);
    scaleOp.filter(before, after);
    return after;
}

/**
 * This is a more generic solution. It produces the same result, but it shows how you 
 * can draw anything you want into the newly created image. It's slower 
 * than scaleBilinear().
 * @param before The original image
 * @param scale The scale factor
 * @return A scaled version of the original image
 */
private static BufferedImage scale2(BufferedImage before, double scale) {
    int w = before.getWidth();
    int h = before.getHeight();
    // Create a new image of the proper size
    int w2 = (int) (w * scale);
    int h2 = (int) (h * scale);
    BufferedImage after = new BufferedImage(w2, h2, before.getType());
    AffineTransform scaleInstance = AffineTransform.getScaleInstance(scale, scale);
    AffineTransformOp scaleOp
            = new AffineTransformOp(scaleInstance, AffineTransformOp.TYPE_BILINEAR);

    Graphics2D g2 = (Graphics2D) after.getGraphics();
    // Here, you may draw anything you want into the new image, but we're just drawing
    // a scaled version of the original image. This is slower than 
    // calling scaleOp.filter().
    g2.drawImage(before, scaleOp, 0, 0);
    g2.dispose();
    return after;
}

/**
 * I call this one "questionable" because it uses the questionable getScaledImage() 
 * method. This method is no longer favored because it's slow, as my tests confirm.
 * @param before The original image
 * @param scale The scale factor
 * @return The scaled image.
 */
private static Image questionable(final BufferedImage before, double scale) {
    int w2 = (int) (before.getWidth() * scale);
    int h2 = (int) (before.getHeight() * scale);
    return before.getScaledInstance(w2, h2, Image.SCALE_FAST);
}