Как масштабировать BufferedImage
После javadocs я попытался масштабировать BufferedImage
без успеха, вот мой код:
BufferedImage image = MatrixToImageWriter.getBufferedImage(encoded);
Graphics2D grph = image.createGraphics();
grph.scale(2.0, 2.0);
grph.dispose();
Я не понимаю, почему он не работает, никакой помощи?
Ответы
Ответ 1
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);
Описанный фрагмент иллюстрирует повторную выборку, а не обрезку; этот ответ отвечает на этот вопрос; некоторые связанные примеры рассматриваются здесь.
Ответ 2
К сожалению, производительность 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();
EDIT. Нашел статью, иллюстрирующую проблему производительности: Опасности Image.getScaledInstance() p >
Ответ 3
Как говорит @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]()
Ответ 4
Использование imgscalr - Библиотека масштабирования Java:
BufferedImage image =
Scalr.resize(originalImage, Scalr.Method.BALANCED, newWidth, newHeight);
Это достаточно быстро для меня.
Ответ 5
Если вы не против использования внешней библиотеки, Thumbnailator может выполнять масштабирование BufferedImage
s.
Thumbnailator позаботится о обработке обработки Java 2D (например, используя Graphics2D
и установив соответствующие подсказки для рендеринга), так что простой простой вызов API можно использовать для изменения размеров изображений:
BufferedImage image = Thumbnails.of(originalImage).scale(2.0).asBufferedImage();
Хотя Thumbnailator, как следует из его названия, ориентирован на сжатие изображений, он также сделает приличное задание, увеличивая изображения, используя билинейную интерполяцию в своей реализации resizer по умолчанию.
Отказ от ответственности: я являюсь сторонником библиотеки Thumbnailator.
Ответ 6
scale(..)
работает немного по-другому. Вы можете использовать bufferedImage.getScaledInstance(..)
Ответ 7
Чтобы масштабировать изображение, вам нужно создать новое изображение и нарисовать его. Один из способов - использовать метод 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 является самым быстрым, с худшими результатами. Кажется, что Bilinear является лучшим компромиссом между скоростью и качеством. 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 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 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);
}