Масштабировать BufferedImage самый быстрый и простой способ
Задача:
У меня есть некоторые изображения, я их уменьшаю и присоединяю к одному изображению. Но у меня есть небольшая проблема с реализацией:
Конкретная проблема:
Я хочу изменить размер/масштабировать BufferedImage. Метод getScaledInstance возвращает объект Image, но я не могу передать его в BufferedImage:
Exception in thread "main" java.lang.ClassCastException: sun.awt.image.ToolkitImage cannot be cast to java.awt.image.BufferedImage
(я не знаю, почему это ToolkitImage вместо изображения...)
Я нашел решение:
Image tmp = bi.getScaledInstance(SMALL_SIZE, SMALL_SIZE, BufferedImage.SCALE_FAST);
BufferedImage buffered = new BufferedImage(SMALL_SIZE,SMALL_SIZE,BufferedImage.TYPE_INT_RGB);
buffered.getGraphics().drawImage(tmp, 0, 0, null);
Но это медленно, и я думаю, что должен быть лучший способ сделать это.
Мне нужен BufferedImage, потому что мне нужно, чтобы пиксели присоединились к маленьким изображениям.
Есть ли лучший (более удобный/быстрый) способ сделать это?
EDIT:
Если я сначала передам изображение в ToolkitImage, у него есть метод getBufferedImage(). Но он всегда возвращает null. Вы знаете, почему?
Ответы
Ответ 1
Объект Graphics
имеет метод рисования Image
, а также выполняет операцию изменения размера:
Graphics.drawImage(Image, int, int, int, int, ImageObserver)
метод может использоваться для указания местоположения вместе с размером изображения при рисовании.
Итак, мы могли бы использовать кусок кода следующим образом:
BufferedImage otherImage = // .. created somehow
BufferedImage newImage = new BufferedImage(SMALL_SIZE, SMALL_SIZE, BufferedImage.TYPE_INT_RGB);
Graphics g = newImage.createGraphics();
g.drawImage(otherImage, 0, 0, SMALL_SIZE, SMALL_SIZE, null);
g.dispose();
Это займет otherImage
и нарисует его на newImage
с шириной и высотой SMALL_SIZE
.
Или, если вы не против использования библиотеки, Thumbnailator может сделать то же самое с этим:
BufferedImage newImage = Thumbnails.of(otherImage)
.size(SMALL_SIZE, SMALL_SIZE)
.asBufferedImage();
Thumbnailator также будет выполнять операцию изменения размера быстрее, чем при использовании Image.getScaledInstance
, одновременно выполняя операции с более высоким качеством, чем использование только Graphics.drawImage
.
Отказ от ответственности: я являюсь хранителем библиотеки Thumbnailator.
Ответ 2
Я получаю его с помощью этого метода, он изменяет размер изображения и пытается сохранить пропорции:
/**
* Resizes an image using a Graphics2D object backed by a BufferedImage.
* @param srcImg - source image to scale
* @param w - desired width
* @param h - desired height
* @return - the new resized image
*/
private BufferedImage getScaledImage(BufferedImage src, int w, int h){
int finalw = w;
int finalh = h;
double factor = 1.0d;
if(src.getWidth() > src.getHeight()){
factor = ((double)src.getHeight()/(double)src.getWidth());
finalh = (int)(finalw * factor);
}else{
factor = ((double)src.getWidth()/(double)src.getHeight());
finalw = (int)(finalh * factor);
}
BufferedImage resizedImg = new BufferedImage(finalw, finalh, BufferedImage.TRANSLUCENT);
Graphics2D g2 = resizedImg.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(src, 0, 0, finalw, finalh, null);
g2.dispose();
return resizedImg;
}
Ответ 3
Вы также можете использовать библиотеку Java OpenCV. Операция изменения размера быстрее, чем Imgscalr's:
Test
Изображение 5184 x 3456 масштабируется до 150 x 100 (это меньшая версия, потому что исходный файл больше, чем 2 МБ):
![введите описание изображения здесь]()
Imgscalr
Зависимость:
<dependency>
<groupId>org.imgscalr</groupId>
<artifactId>imgscalr-lib</artifactId>
<version>4.2</version>
</dependency>
код:
BufferedImage thumbnail = Scalr.resize(img,
Scalr.Method.SPEED,
Scalr.Mode.AUTOMATIC,
150,
100);
Изображение результатов:
![введите описание изображения здесь]()
Среднее время: 80 миллис
OpenCV
Зависимость:
<dependency>
<groupId>nu.pattern</groupId>
<artifactId>opencv</artifactId>
<version>2.4.9-4</version>
</dependency>
Преобразование объекта BufferedImage в Mat (необходимо):
BufferedImage img = ImageIO.read(image); // load image
byte[] pixels = ((DataBufferByte) img.getRaster().getDataBuffer())
.getData();
Mat matImg = new Mat(img.getHeight(), img.getWidth(), CvType.CV_8UC3);
matImg.put(0, 0, pixels);
код:
Imgproc.resize(matImg, resizeimage, sz);
Дополнительная конфигурация (для окон):
Добавьте opencv_java249.dll в каталог JDK bin.
Изображение результатов:
![введите описание изображения здесь]()
Среднее время: 13 миллис
Общие результаты
В тесте просто рассчитываются функции "изменить размер". Imgscalr изменил заданное изображение в 80 милях, где OpenCV выполнил ту же задачу в 13 миллисекунд. Вы можете найти здесь весь проект, чтобы немного поиграть с ним.
Как вы просили также простой способ, если производительность библиотеки Imgscalr хороша для вас, тогда это смертельно-просто. Потому что для использования OpenCV, поскольку вы видите, что файл библиотеки должен быть расположен во всех средах разработки и серверах. Также вам нужно использовать объекты Mat.
Целый проект
pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.btasdemir</groupId>
<artifactId>testapp</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>testapp</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.imgscalr</groupId>
<artifactId>imgscalr-lib</artifactId>
<version>4.2</version>
</dependency>
<dependency>
<groupId>nu.pattern</groupId>
<artifactId>opencv</artifactId>
<version>2.4.9-4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp</artifactId>
<version>0.9</version>
</plugin>
</plugins>
</build>
</project>
App.java:
package com.btasdemir.testapp;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import org.imgscalr.Scalr;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.highgui.Highgui;
import org.opencv.imgproc.Imgproc;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args ) throws IOException
{
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
File image = new File("C:\\your_dir\\test.jpg");
BufferedImage img = ImageIO.read(image); // load image
long startTime = System.currentTimeMillis();//imgscalr------------------------------------------------------
//resize to 150 pixels max
BufferedImage thumbnail = Scalr.resize(img,
Scalr.Method.SPEED,
Scalr.Mode.AUTOMATIC,
150,
100);
// BufferedImage thumbnail = Scalr.resize(img,
// Scalr.Method.SPEED,
// Scalr.Mode.AUTOMATIC,
// 150,
// 100,
// Scalr.OP_ANTIALIAS);
System.out.println(calculateElapsedTime(startTime));//END-imgscalr------------------------------------------------------
File outputfile = new File("C:\\your_dir\\imgscalr_result.jpg");
ImageIO.write(thumbnail, "jpg", outputfile);
img = ImageIO.read(image); // load image
byte[] pixels = ((DataBufferByte) img.getRaster().getDataBuffer())
.getData();
Mat matImg = new Mat(img.getHeight(), img.getWidth(), CvType.CV_8UC3);
matImg.put(0, 0, pixels);
Mat resizeimage = new Mat();
Size sz = new Size(150, 100);
startTime = System.currentTimeMillis();//opencv------------------------------------------------------
Imgproc.resize(matImg, resizeimage, sz);
// Imgproc.resize(matImg, resizeimage, sz, 0.5, 0.5, Imgproc.INTER_CUBIC);
System.out.println(calculateElapsedTime(startTime));//END-opencv------------------------------------------------------
Highgui.imwrite("C:\\your_dir\\opencv_result.jpg", resizeimage);
}
protected static long calculateElapsedTime(long startTime) {
long stopTime = System.currentTimeMillis();
long elapsedTime = stopTime - startTime;
return elapsedTime;
}
}
Ответ 4
Ни один из этих ответов не был достаточно быстрым для меня. Поэтому я, наконец, запрограммировал свою собственную процедуру.
static BufferedImage scale(BufferedImage src, int w, int h)
{
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
int x, y;
int ww = src.getWidth();
int hh = src.getHeight();
for (x = 0; x < w; x++) {
for (y = 0; y < h; y++) {
int col = src.getRGB(x * ww / w, y * hh / h);
img.setRGB(x, y, col);
}
}
return img;
}
Ответ 5
Возможно, этот метод поможет:
public BufferedImage resizeImage(BufferedImage image, int width, int height) {
int type=0;
type = image.getType() == 0? BufferedImage.TYPE_INT_ARGB : image.getType();
BufferedImage resizedImage = new BufferedImage(width, height,type);
Graphics2D g = resizedImage.createGraphics();
g.drawImage(image, 0, 0, width, height, null);
g.dispose();
return resizedImage;
}
Не забудьте эти строки "import":
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
И о кастинге:
Абстрактный класс Image
является суперклассом всех классов, представляющих графические изображения.
Мы не можем отбрасывать Image
до BufferedImage
, потому что каждый BufferedImage
равен Image
, но наоборот не соответствует действительности.
Image im = new BufferedImage(width, height, imageType);//this is true
BufferedImage img = new Image(){//.....}; //this is wrong
Ответ 6
Использование imgscalr - Библиотека масштабирования Java:
BufferedImage image = Scalr.resize(originalImage, Scalr.Method.BALANCED, newWidth, newHeight);
Это достаточно быстро для меня.
Ответ 7
public static double[] reduceQuality(int quality, int width, int height) {
if(quality >= 1 && quality <= 100) {
double[] dims = new double[2];
dims[0] = width * (quality/100.0);
dims[1] = height * (quality/100.0);
return dims;
} else if(quality > 100) {
return new double[] { width, height };
} else {
return new double[] { 1, 1 };
}
}
public static byte[] resizeImage(byte[] data, int width, int height) throws Exception {
BufferedImage bi = ImageIO.read(new ByteArrayInputStream(data));
BufferedImage bo = resizeImage(bi, width, height);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ImageIO.write(bo, "jpg", bos);
bos.close();
return bos.toByteArray();
}
private static BufferedImage resizeImage(BufferedImage buf, int width, int height) {
final BufferedImage bufImage = new BufferedImage(width, height,
(buf.getTransparency() == Transparency.OPAQUE ? BufferedImage.TYPE_INT_RGB
: BufferedImage.TYPE_INT_ARGB));
final Graphics2D g2 = bufImage.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
g2.drawImage(buf, 0, 0, width, height, null);
g2.dispose();
return bufImage;
}
Это взято прямо из imgscalr на https://github.com/rkalla/imgscalr/blob/master/src/main/java/org/imgscalr/Scalr.java
Мое среднее время снижения качества изображения размером 8 Мб с размерами 5152x3864 составляло ~ 800 мс.
Нет зависимостей. Я ненавижу их. Иногда.
Это будет работать только с изображениями JPG. Насколько я понимаю.
Пример:
byte[] of = Files.readAllBytes(Paths.get("/home/user/Pictures/8mbsample.jpg"));
double[] wh = ImageUtil.reduceQuality(2, 6600, 4950);
long start = System.currentTimeMillis();
byte[] sof = ImageUtil.resizeImage(of, (int)wh[0], (int)wh[1]);
long end = System.currentTimeMillis();
if(!Files.exists(Paths.get("/home/user/Pictures/8mbsample_scaled.jpg"))) {
Files.createFile(Paths.get("/home/user/Pictures/8mbsample_scaled.jpg"), Util.getFullPermissions());
}
FileOutputStream fos = new FileOutputStream("/home/user/Pictures/8mbsample_scaled.jpg");
fos.write(sof); fos.close();
System.out.println("Process took: " + (end-start) + "ms");
Выход:
Process took: 783ms