La tâche: J'ai quelques images, je les réduit et les associe à une image. Mais j'ai un petit problème avec l'implémentation:
Le problème concret: Je veux redimensionner/redimensionner une BufferedImage. La méthode getScaledInstance renvoie un objet Image, mais je ne peux pas le convertir en BufferedImage:
Exception in thread "main" Java.lang.ClassCastException: Sun.awt.image.ToolkitImage cannot be cast to Java.awt.image.BufferedImage
(Je ne sais pas pourquoi c'est une ToolkitImage au lieu d'une Image ...)
J'ai trouvé une solution:
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);
Mais c'est lent, et je pense qu'il devrait y avoir un meilleur moyen de le faire.
J'ai besoin de BufferedImage, car je dois obtenir les pixels pour joindre les petites images.
Y a-t-il un meilleur moyen (plus agréable/plus rapide) de le faire?
EDIT: .__ Si je convertis d'abord l'image en ToolkitImage, elle utilise une méthode getBufferedImage (). Mais il retourne toujours null. Est-ce que tu sais pourquoi?
L'objet Graphics
a une méthode pour dessiner une Image
tout en effectuant une opération de redimensionnement:
Graphics.drawImage(Image, int, int, int, int, ImageObserver)
method peut être utilisé pour spécifier l'emplacement avec la taille de l'image lors du dessin.
Donc, nous pourrions utiliser un morceau de code comme ceci:
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();
Cela va prendre otherImage
et le dessiner sur newImage
avec la largeur et la hauteur de SMALL_SIZE
.
Ou, si cela ne vous dérange pas d'utiliser une bibliothèque, Thumbnailator pourrait accomplir la même chose avec ceci:
BufferedImage newImage = Thumbnails.of(otherImage)
.size(SMALL_SIZE, SMALL_SIZE)
.asBufferedImage();
Thumbnailator effectuera également l'opération de redimensionnement plus rapidement que d'utiliser Image.getScaledInstance
tout en effectuant des opérations de redimensionnement de qualité supérieure à celle utilisant uniquement Graphics.drawImage
.
Avertissement: Je suis le responsable de la bibliothèque Thumbnailator.
Je l’obtiens avec cette méthode, il redimensionne l’image et essaie de maintenir les proportions:
/**
* 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;
}
Vous pouvez également utiliser la bibliothèque Java OpenCV. Son opération de redimensionnement est plus rapide que celle d'Imgscalr:
Image 5184 x 3456 dimensionnée à 150 x 100 (il s’agit de la version la plus petite car le fichier original est supérieure à 2 Mo):
Imgscalr
Dépendance:
<dependency>
<groupId>org.imgscalr</groupId>
<artifactId>imgscalr-lib</artifactId>
<version>4.2</version>
</dependency>
Code:
BufferedImage thumbnail = Scalr.resize(img,
Scalr.Method.SPEED,
Scalr.Mode.AUTOMATIC,
150,
100);
Résultat image:
Temps moyen: 80 millis
OpenCV
Dépendance:
<dependency>
<groupId>nu.pattern</groupId>
<artifactId>opencv</artifactId>
<version>2.4.9-4</version>
</dependency>
Convertir BufferedImage en objet Mat (obligatoirement):
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);
Code:
Imgproc.resize(matImg, resizeimage, sz);
Configuration supplémentaire (pour Windows):
Ajouter opencv_Java249.dll dans le répertoire bin de votre JDK.
Résultat image:
Temps moyen: 13 millis
Dans le test, seules les fonctions "redimensionner" sont calculées. Imgscalr a redimensionné l'image donnée en 80 millisecondes, alors qu'OpenCV effectuait la même tâche en 13 millisecondes. Vous pouvez trouver l'ensemble du projet ci-dessous ici pour en jouer un peu.
Comme vous l'avez également demandé, si les performances de la bibliothèque Imgscalr sont bonnes pour vous, il est extrêmement facile. En effet, pour utiliser OpenCV à mesure que vous voyez, un fichier de bibliothèque doit se trouver sur tous vos environnements de développement et serveurs. Aussi, vous devez utiliser des objets Mat.
Projet complet
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;
}
}
Aucune de ces réponses n'a été assez rapide pour moi. J'ai donc finalement programmé ma propre procédure.
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;
}
Utilisation de imgscalr - Bibliothèque Java Image Scaling :
BufferedImage image = Scalr.resize(originalImage, Scalr.Method.BALANCED, newWidth, newHeight);
C'est assez rapide pour moi.
Peut-être que cette méthode aidera:
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;
}
N'oubliez pas ces lignes "import":
import Java.awt.Graphics2D;
import Java.awt.image.BufferedImage;
Et à propos de casting:
La classe abstraite Image
est la super-classe de toutes les classes représentant des images graphiques . Nous ne pouvons pas convertir Image
en BufferedImage
car chaque BufferedImage
est Image
mais l'inverse n'est pas vrai.
Image im = new BufferedImage(width, height, imageType);//this is true
BufferedImage img = new Image(){//.....}; //this is wrong
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;
}
Ceci est pris directement de imgscalr à https://github.com/rkalla/imgscalr/blob/master/src/main/Java/org/imgscalr/Scalr.Java
Mon temps moyen de réduction de la qualité d'une image [5152x3864] était d'environ 800 ms.
Pas de dépendances. Je les déteste. Parfois.
CECI NE FONCTIONNERA QUE AVEC DES IMAGES jpg. En ce qui me concerne.
Exemple:
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");
Sortie:
Process took: 783ms