Как масштабировать BufferedImage - PullRequest
40 голосов
/ 18 ноября 2010

После javadocs, я попытался масштабировать BufferedImage безуспешно, вот мой код:

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

Я не могу понять, почему это не работает, любая помощь?

Ответы [ 7 ]

61 голосов
/ 18 ноября 2010

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

Показанный фрагмент иллюстрирует повторную выборку , а не обрезку ; этот связанный ответ решает проблему ; некоторые связанные примеры рассматриваются здесь .

32 голосов
/ 18 ноября 2010

К сожалению, производительность getScaledInstance () очень низкая, если не проблемная.

Альтернативный подход - создать новый 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();

newWidth, newHeight указывают новый размер BufferedImage и должны быть правильно рассчитаны.В случае коэффициента масштабирования:

int newWidth = new Double(original.getWidth() * widthFactor).intValue();
int newHeight = new Double(original.getHeight() * heightFactor).intValue();

РЕДАКТИРОВАТЬ : нашел статью, иллюстрирующую проблему производительности: Опасности Image.getScaledInstance ()

12 голосов
/ 18 ноября 2010

Как говорит @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 :enter image description here

он производит duke_double_size.png :enter image description here

9 голосов
/ 24 февраля 2014

Использование imgscalr - библиотека масштабирования изображений Java :

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

Это достаточно быстро для меня.

6 голосов
/ 16 ноября 2014

Если вы не возражаете против использования внешней библиотеки, Thumbnailator может выполнить масштабирование BufferedImage s.

Thumbnailator позаботится о обработке Обработка Java 2D (например, использование Graphics2D и установка соответствующих подсказок рендеринга ), так что простой вызов API можно использовать для изменения размера изображений:

BufferedImage image = Thumbnails.of(originalImage).scale(2.0).asBufferedImage();

Несмотря на то, что Thumbnailator, как следует из его названия, ориентирован на сжатие изображений, он отлично справится и с увеличением изображений, используя билинейную интерполяцию в своей реализации изменения размера по умолчанию.


Отказ от ответственности: я поддерживаю библиотеку Thumbnailator .

3 голосов
/ 14 сентября 2017

Чтобы масштабировать изображение, вам нужно создать новое изображение и нарисовать в нем.Одним из способов является использование 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;
}

Другой способ - просто нарисовать исходное изображение в новом изображении, используя операцию масштабирования для масштабирования.Этот метод очень похож, но он также иллюстрирует, как вы можете нарисовать все, что вы хотите в конечном изображении.(Я вставил пустую строку, где два метода начинают различаться.)

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

Приложение: результаты

Чтобы проиллюстрировать различия, я сравнил результатыпять методов ниже.Вот как выглядят результаты, увеличенные и уменьшенные вместе с данными о производительности.(Производительность варьируется от одного прогона к другому, поэтому используйте эти цифры только в качестве приблизительных ориентиров.) Верхнее изображение - оригинал.Я масштабирую его вдвое и наполовину.

Как видите, AffineTransformOp.filter(), используемый в scaleBilinear(), быстрее, чем стандартный метод рисования Graphics2D.drawImage() в scale2().Также BiCubic интерполяция является самой медленной, но дает лучшие результаты при расширении изображения.(По производительности его следует сравнивать только с scaleBilinear() и scaleNearest().). Билинейный режим лучше подходит для сжатия изображения, хотя это сложный вызов.И NearestNeighbor самый быстрый, с худшими результатами.Билинейный, кажется, лучший компромисс между скоростью и качеством.Image.getScaledInstance(), вызванный методом questionable(), работал очень плохо и возвращал то же низкое качество, что и NearestNeighbor.(Номера производительности приведены только для увеличения изображения.)

enter image description here

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);
}
3 голосов
/ 18 ноября 2010

scale(..) работает немного по-другому.Вы можете использовать bufferedImage.getScaledInstance(..)

...