Java – redimensionne l’image sans perte de qualité

J’ai 10 000 photos à redimensionner, j’ai donc un programme Java pour le faire. Malheureusement, la qualité de l’image est mal perdue et je n’ai pas access aux images non compressées.

import java.awt.Graphics; import java.awt.AlphaComposite; import java.awt.Graphics2D; import java.awt.Image; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; /** * This class will resize all the images in a given folder * @author * */ public class JavaImageResizer { public static void main(Ssortingng[] args) throws IOException { File folder = new File("/Users/me/Desktop/images/"); File[] listOfFiles = folder.listFiles(); System.out.println("Total No of Files:"+listOfFiles.length); BufferedImage img = null; BufferedImage tempPNG = null; BufferedImage tempJPG = null; File newFilePNG = null; File newFileJPG = null; for (int i = 0; i  height)targetw = 112; else targetw = 50; do { if (width > targetw) { width /= 2; if (width  targeth) { height /= 2; if (height < targeth) height = targeth; } } while (width != targetw || height != targeth); final BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); final Graphics2D graphics2D = bufferedImage.createGraphics(); graphics2D.setComposite(AlphaComposite.Src); graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR); graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY); graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); graphics2D.drawImage(image, 0, 0, width, height, null); graphics2D.dispose(); return bufferedImage; } 

Une image avec laquelle je travaille est la suivante: Firwork - original - grand

Voici le redimensionnement manuel que j’ai effectué dans Microsoft Paint:

redimensionner - en utilisant Paint - petit

et ceci est le résultat de mon programme [bilinear]:

redimensionner - en utilisant le programme java - petit

MISE À JOUR: Aucune différence significative en utilisant BICUBIC

et ceci est le résultat de mon programme [bicubic]:

entrer la description de l'image ici

Y a-t-il de toute façon une meilleure qualité de sortie du programme, donc je n’ai pas besoin de redimensionner manuellement toutes les photos?

Merci d’avance!

Malheureusement, il n’ya pas de mise à l’échelle prête à l’emploi en Java qui donne de bons résultats visuels. Entre autres, voici les méthodes que je recommande pour la mise à l’échelle:

  • Rééchantillonnage de Lanczos3 (généralement meilleur visuellement, mais plus lent)
  • Progressive Down Scaling (généralement visuellement bien, peut être assez rapide)
  • Mise à l’échelle en une étape pour la mise à l’échelle (avec Graphics2d bicubic rapide et bons résultats, généralement moins bons que Lanczos3)

Des exemples pour chaque méthode peuvent être trouvés dans cette réponse.

Comparaison visuelle

Voici votre image mise à l’échelle à 96x140 avec différentes méthodes / libs. Cliquez sur l’image pour obtenir la taille maximale:

Comparaison

zoom de comparaison

  1. La bibliothèque de Morten Nobel Lanczos3
  2. Mise à l’échelle progressive bilinéaire Thumbnailator
  3. Imgscalr ULTRA_QUALTY (Echelle progressive bicubique à 1/7 étape)
  4. Imgscalr QUALTY (échelonnage progressif bicubique en 1/2 étape)
  5. La Bilinear Progressive Scaling de Morten Nobel
  6. Interpolation bicubique Graphics2d
  7. Interpolation Graphics2d voisin le plus proche
  8. Photoshop CS5 bicubic comme référence

Malheureusement, une seule image ne suffit pas pour juger d’un algorithme de mise à l’échelle, vous devez tester les icons avec des arêtes vives, des photos avec du texte, etc.

Rééchantillonnage de Lanczos

Est dit être bon pour la montée et surtout la réduction d’échelle. Malheureusement, il n’y a pas d’implémentation native dans le JDK actuel , vous pouvez donc l’implémenter vous-même et utiliser une lib comme celle de Morten Nobel . Un exemple simple utilisant ladite lib:

 ResampleOp resizeOp = new ResampleOp(dWidth, dHeight); resizeOp.setFilter(ResampleFilters.getLanczos3Filter()); BufferedImage scaledImage = resizeOp.filter(imageToScale, null); 

La lib est publiée sur maven-central qui n’est malheureusement pas mentionnée. L’inconvénient est qu’il est généralement très lent sans aucune implémentation hautement optimisée ou accélérée par le matériel. L’implémentation de Nobel est environ 8 fois plus lente qu’un algorithme d’échelonnage progressif à 1/2 étape avec Graphics2d . En savoir plus sur cette lib sur son blog .

Mise à l’échelle progressive

Mentionné dans le blog de Chris Campbell à propos de la mise à l’ échelle en Java, la mise à l’échelle progressive consiste essentiellement à mettre à l’échelle une image de manière progressive jusqu’à ce que les dimensions finales soient atteintes. Campbell le décrit comme divisant par deux la largeur / hauteur jusqu’à atteindre la cible. Cela produit de bons résultats et peut être utilisé avec Graphics2D qui peut être accéléré par le matériel, ce qui permet généralement d’obtenir de très bonnes performances avec des résultats acceptables dans la plupart des cas. L’inconvénient majeur est que si vous réduisez la taille de moitié en utilisant Graphics2D vous Graphics2D les mêmes résultats médiocres car il n’est mis à l’échelle qu’une seule fois.

Voici un exemple simple de fonctionnement:

mise à l'échelle progressive

Les bibliothèques suivantes incorporent des formes de mise à l’échelle progressive basées sur Graphics2d :

Thumbnailator v0.4.8

Utilise l’algorithme bilinéaire progressif si la cible est au moins la moitié de chaque dimension, sinon il utilise une simple mise à l’échelle bilinéaire Graphics2d et bicubique pour la mise à l’échelle.

 Resizer resizer = DefaultResizerFactory.getInstance().getResizer( new Dimension(imageToScale.getWidth(), imageToScale.getHeight()), new Dimension(dWidth, dHeight)) BufferedImage scaledImage = new FixedSizeThumbnailMaker( dWidth, dHeight, false, true).resizer(resizer).make(imageToScale); 

Il est aussi rapide ou légèrement plus rapide qu’une mise à l’échelle en une étape avec Graphics2d obtenant en moyenne 6,9 ​​secondes dans mon test de performance.

Imgscalr v4.2

Utilise un détartrage bicubique progressif. Dans le paramètre QUALITY , l’algorithme de style Campbell réduit de moitié les dimensions à chaque étape, tandis que ULTRA_QUALITY présente des étapes plus fines, réduisant la taille de chaque incrément de 1/7, ce qui génère des images généralement plus douces mais minimise les instances où une seule itération est utilisée.

 BufferedImage scaledImage = Scalr.resize(imageToScale, Scalr.Method.ULTRA_QUALITY, Scalr.Mode.FIT_EXACT, dWidth, dHeight, bufferedImageOpArray); 

Le principal inconvénient est la performance. ULTRA_QUALITY est considérablement plus lent que les autres bibliothèques. Même QUALITY un peu plus lent que l’implémentation de Thumbnailator. Mon benchmark simple a donné respectivement 26,2 secondes et 11,1 secondes en moyenne.

La lib v0.8.6 de Morten Nobel

A également des implémentations pour la mise à l’échelle progressive pour tous les Graphics2d base (bilinéaire, bicubique et voisin le plus proche)

 BufferedImage scaledImage = new MultiStepRescaleOp(dWidth, dHeight, RenderingHints.VALUE_INTERPOLATION_BILINEAR).filter(imageToScale, null); 

Un mot sur les méthodes de mise à l’échelle du JDK

Le moyen actuel de jdk de mettre à l’échelle une image serait quelque chose comme ça

 scaledImage = new BufferedImage(dWidth, dHeight, imageType); Graphics2D graphics2D = scaledImage.createGraphics(); graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); graphics2D.drawImage(imageToScale, 0, 0, dWidth, dHeight, null); graphics2D.dispose(); 

mais la plupart sont très déçus du résultat de la réduction d’échelle, quelle que soit l’interpolation ou les autres RenderHints utilisés. D’autre part, la mise à l’échelle semble produire des images acceptables (le mieux serait bicubique). Dans la version précédente du JDK (nous parlons des années 90 v1.1), Image.getScaledInstance() été introduit et a fourni de bons résultats visuels avec le paramètre SCALE_AREA_AVERAGING mais nous vous déconseillons de l’utiliser – lisez l’explication complète ici .

Thumbnailator est une bibliothèque qui a été écrite pour créer des vignettes de haute qualité de manière simple. La conversion par lot d’images existantes est l’un de ses cas d’utilisation.

Effectuer le redimensionnement par lots

Par exemple, pour adapter votre exemple à l’aide de Thumbnailator, vous devriez pouvoir obtenir des résultats similaires avec le code suivant:

 File folder = new File("/Users/me/Desktop/images/"); Thumbnails.of(folder.listFiles()) .size(112, 75) .outputFormat("jpg") .toFiles(Rename.PREFIX_DOT_THUMBNAIL); 

Cela ira de l’avant et prendra tous les fichiers de votre répertoire d’ images et procédera à leur traitement un par un, essayez de les redimensionner pour les adapter aux dimensions de 112 x 75, et tentera de préserver le rapport hauteur / largeur de l’image d’origine. “déformation” de l’image.

Thumbnailator ira de l’avant et lira tous les fichiers, quels que soient les types d’image (tant que Java Image IO prend en charge le format, Thumbnailator le traitera), effectuera le redimensionnement et produira les vignettes sous forme de fichiers JPEG, tout en plaçant une thumbnail. au début du nom de fichier.

Ce qui suit est une illustration de l’utilisation du nom de fichier de l’original dans le nom de fichier de la vignette si le code ci-dessus est exécuté.

 images/fireworks.jpg -> images/thumbnail.fireworks.jpg images/illustration.png -> images/thumbnail.illustration.png images/mountains.jpg -> images/thumbnail.mountains.jpg 

Générer des vignettes de haute qualité

En termes de qualité d’image, comme mentionné dans la réponse de Marco13 , la technique décrite par Chris Campbell dans The Perils of Image.getScaledInstance () est implémentée dans Thumbnailator, ce qui permet d’obtenir des vignettes de haute qualité sans nécessiter de traitement compliqué.

Voici la miniature générée lors du redimensionnement de l’image de feu d’artifice affichée dans la question d’origine à l’aide de Thumbnailator:

Miniature d'image dans la question originale

L’image ci-dessus a été créée avec le code suivant:

 BufferedImage thumbnail = Thumbnails.of(new URL("http://i.stack.imgur.com/X0aPT.jpg")) .height(75) .asBufferedImage(); ImageIO.write(thumbnail, "png", new File("24745147.png")); 

Le code montre qu’il peut également accepter des URL en entrée et que Thumbnailator est également capable de créer des BufferedImage .


Disclaimer: Je suis le responsable de la bibliothèque Thumbnailator .

Compte tenu de votre image d’entrée, la méthode de la réponse dans le premier lien dans les commentaires (félicitations à Chris Campbell) produit l’une des vignettes suivantes:

entrer la description de l'image icientrer la description de l'image ici

(L’autre est la vignette que vous avez créée avec MS Paint. Il est difficile d’appeler l’un d’entre eux “meilleur” que l’autre …)

EDIT: Juste pour le signaler également: Le principal problème avec votre code d’origine était que vous n’avez pas vraiment redimensionné l’image en plusieurs étapes. Vous venez d’utiliser une boucle étrange pour “calculer” la taille de la cible. Le point clé est que vous effectuez réellement la mise à l’ échelle en plusieurs étapes.

Juste pour être complet, le MVCE

 import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.Transparency; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Iterator; import javax.imageio.IIOImage; import javax.imageio.ImageIO; import javax.imageio.ImageWriteParam; import javax.imageio.ImageWriter; import javax.imageio.stream.ImageOutputStream; import javax.imageio.stream.MemoryCacheImageOutputStream; public class ResizeQuality { public static void main(Ssortingng[] args) throws IOException { BufferedImage image = ImageIO.read(new File("X0aPT.jpg")); BufferedImage scaled = getScaledInstance( image, 51, 75, RenderingHints.VALUE_INTERPOLATION_BILINEAR, true); writeJPG(scaled, new FileOutputStream("X0aPT_tn.jpg"), 0.85f); } public static BufferedImage getScaledInstance( BufferedImage img, int targetWidth, int targetHeight, Object hint, boolean higherQuality) { int type = (img.getTransparency() == Transparency.OPAQUE) ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB; BufferedImage ret = (BufferedImage) img; int w, h; if (higherQuality) { // Use multi-step technique: start with original size, then // scale down in multiple passes with drawImage() // until the target size is reached w = img.getWidth(); h = img.getHeight(); } else { // Use one-step technique: scale directly from original // size to target size with a single drawImage() call w = targetWidth; h = targetHeight; } do { if (higherQuality && w > targetWidth) { w /= 2; if (w < targetWidth) { w = targetWidth; } } if (higherQuality && h > targetHeight) { h /= 2; if (h < targetHeight) { h = targetHeight; } } BufferedImage tmp = new BufferedImage(w, h, type); Graphics2D g2 = tmp.createGraphics(); g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint); g2.drawImage(ret, 0, 0, w, h, null); g2.dispose(); ret = tmp; } while (w != targetWidth || h != targetHeight); return ret; } public static void writeJPG( BufferedImage bufferedImage, OutputStream outputStream, float quality) throws IOException { Iterator iterator = ImageIO.getImageWritersByFormatName("jpg"); ImageWriter imageWriter = iterator.next(); ImageWriteParam imageWriteParam = imageWriter.getDefaultWriteParam(); imageWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); imageWriteParam.setCompressionQuality(quality); ImageOutputStream imageOutputStream = new MemoryCacheImageOutputStream(outputStream); imageWriter.setOutput(imageOutputStream); IIOImage iioimage = new IIOImage(bufferedImage, null, null); imageWriter.write(null, iioimage, imageWriteParam); imageOutputStream.flush(); } } 

Nous ne devons pas oublier une bibliothèque TwelveMonkeys

Il contient une collection de filtres vraiment impressionnante.

Exemple d’utilisation:

 BufferedImage input = ...; // Image to resample int width, height = ...; // new width/height BufferedImageOp resampler = new ResampleOp(width, height, ResampleOp.FILTER_LANCZOS); BufferedImage output = resampler.filter(input, null); 

Le résultat semble être meilleur (que le résultat de votre programme), si vous appliquez le flou gaussien avant le redimensionnement:

C’est le résultat que j’obtiens, avec sigma * (scale factor) = 0.3 :

Vignette lors du premier flou (sigma = 15.0)

Avec ImageJ, le code pour faire cela est assez court:

 import ij.IJ; import ij.ImagePlus; import ij.io.Opener; import ij.process.ImageProcessor; public class Resizer { public static void main(Ssortingng[] args) { processPicture("X0aPT.jpg", "output.jpg", 0.0198, ImageProcessor.NONE, 0.3); } public static void processPicture(Ssortingng inputFile, Ssortingng outputFilePath, double scaleFactor, int interpolationMethod, double sigmaFactor) { Opener opener = new Opener(); ImageProcessor ip = opener.openImage(inputFile).getProcessor(); ip.blurGaussian(sigmaFactor / scaleFactor); ip.setInterpolationMethod(interpolationMethod); ImageProcessor outputProcessor = ip.resize((int)(ip.getWidth() * scaleFactor), (int)(ip.getHeight()*scaleFactor)); IJ.saveAs(new ImagePlus("", outputProcessor), outputFilePath.subssortingng(outputFilePath.lastIndexOf('.')+1), outputFilePath); } } 

BTW: Vous avez seulement besoin de ij-1.49d.jar (ou équivalent pour une autre version); il n’est pas nécessaire d’ installer ImageJ.

Après des jours de recherche, je préférerais javaxt.

use La classe javaxt.io.Image a un constructeur comme:

 public Image(java.awt.image.BufferedImage bufferedImage) 

vous pouvez donc faire ( another example ):

 javaxt.io.Image image = new javaxt.io.Image(bufferedImage); image.setWidth(50); image.setOutputQuality(1); 

Voici le résultat:

entrer la description de l'image ici

Vous trouverez ci-dessous ma propre implémentation de Progressive Scaling, sans utiliser de bibliothèque externe. J’espère que cette aide

 private static BufferedImage progressiveScaling(BufferedImage before, Integer longestSideLength) { if (before != null) { Integer w = before.getWidth(); Integer h = before.getHeight(); Double ratio = h > w ? longestSideLength.doubleValue() / h : longestSideLength.doubleValue() / w; //Multi Step Rescale operation //This technique is describen in Chris Campbell's blog The Perils of Image.getScaledInstance(). As Chris mentions, when downscaling to something less than factor 0.5, you get the best result by doing multiple downscaling with a minimum factor of 0.5 (in other words: each scaling operation should scale to maximum half the size). while (ratio < 0.5) { BufferedImage tmp = scale(before, 0.5); before = tmp; w = before.getWidth(); h = before.getHeight(); ratio = h > w ? longestSideLength.doubleValue() / h : longestSideLength.doubleValue() / w; } BufferedImage after = scale(before, ratio); return after; } return null; } private static BufferedImage scale(BufferedImage imageToScale, Double ratio) { Integer dWidth = ((Double) (imageToScale.getWidth() * ratio)).intValue(); Integer dHeight = ((Double) (imageToScale.getHeight() * ratio)).intValue(); BufferedImage scaledImage = new BufferedImage(dWidth, dHeight, BufferedImage.TYPE_INT_RGB); Graphics2D graphics2D = scaledImage.createGraphics(); graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); graphics2D.drawImage(imageToScale, 0, 0, dWidth, dHeight, null); graphics2D.dispose(); return scaledImage; }