如何缩放BufferedImage

Thi*_*niz 42 java bufferedimage image image-processing image-scaling

在javadocs之后,我试图扩展一个BufferedImage没有成功的是我的代码:

BufferedImage image = MatrixToImageWriter.getBufferedImage(encoded);
Graphics2D grph = image.createGraphics();
grph.scale(2.0, 2.0);
grph.dispose();
Run Code Online (Sandbox Code Playgroud)

我无法理解为什么它不起作用,有什么帮助吗?

tra*_*god 62

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

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);
Run Code Online (Sandbox Code Playgroud)

显示的片段说明了重新取样,而不是裁剪 ; 这个相关答案解决的问题 ; 这里检查了一些相关的例子.

  • 我刚刚测试了它。正如我所怀疑的,`after` 是相同的大小,它只是原始的左上角四分之一。解决方法是在创建“after”时将 w 和 h 乘以比例。 (4认同)
  • @Martijn:这取决于你想要在``filter()`中使用哪个`ColorModel`(http://download.oracle.com/javase/6/docs/api/java/awt/image/AffineTransformOp.html#filter %28java.awt.image.BufferedImage,%20java.awt.image.BufferedImage%29).它返回一个引用,因此没有额外的内存. (3认同)
  • 当您有如下语句时,是否真的需要为 `after` 分配所有内存:`after = ...`? (2认同)

cha*_*sis 34

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

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

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();
Run Code Online (Sandbox Code Playgroud)

newWidth,newHeight表示新的BufferedImage大小,必须正确计算.在因子缩放的情况下:

int newWidth = new Double(original.getWidth() * widthFactor).intValue();
int newHeight = new Double(original.getHeight() * heightFactor).intValue();
Run Code Online (Sandbox Code Playgroud)

编辑:找到说明性能问题的文章:Image.getScaledInstance()的危险


aio*_*obe 11

正如@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"));
    }
}
Run Code Online (Sandbox Code Playgroud)

鉴于duke.png:
在此输入图像描述

它产生duke_double_size.png:
在此输入图像描述


cek*_*ock 9

使用imgscalr - Java Image Scaling Library:

BufferedImage image =
     Scalr.resize(originalImage, Scalr.Method.BALANCED, newWidth, newHeight);
Run Code Online (Sandbox Code Playgroud)

这对我来说足够快了.


Mig*_*noz 9

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

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;
}
Run Code Online (Sandbox Code Playgroud)

另一种方法是简单地将原始图像绘制到新图像中,使用缩放操作进行缩放。这种方法非常相似,但它也说明了如何在最终图像中绘制任何你想要的东西。(我在这两种方法开始不同的地方加了一个空行。)

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;
}
Run Code Online (Sandbox Code Playgroud)

附录:结果

为了说明差异,我比较了以下五种方法的结果。这是结果的样子,放大和缩小,以及性能数据。(性能因一次运行而异,因此仅将这些数字作为粗略的指导。)顶部图像是原始图像。我把它放大成双倍尺寸和半尺寸。

正如你所看到的,AffineTransformOp.filter(),用于scaleBilinear(),比标准绘制方法快Graphics2D.drawImage()scale2()。BiCubic 插值也是最慢的,但在扩展图像时给出了最好的结果。(对于性能,它应该只与scaleBilinear()和进行比较scaleNearest().)双线性似乎更适合缩小图像,尽管这是一个艰难的决定。NearestNeighbor 是最快的,结果最差。双线性似乎是速度和质量之间的最佳折衷。的Image.getScaledInstance(),堪称questionable()方法,进行非常差,并返回相同的品质较低,因为最近邻。(性能数字仅用于扩展图像。)

在此处输入图片说明

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);
}
Run Code Online (Sandbox Code Playgroud)


coo*_*ird 6

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

Thumbnailator将采取的处理护理的Java 2D处理(例如,使用Graphics2D和设置适当的呈现提示),使得简单流利API调用可用于调整图像:

BufferedImage image = Thumbnails.of(originalImage).scale(2.0).asBufferedImage();
Run Code Online (Sandbox Code Playgroud)

虽然Thumbnailator,正如其名称所暗示的那样,是为了缩小图像,但它在放大图像时也会做得不错,在默认的缩放器实现中使用双线性插值.


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