J'ai 10 000 photos qui doivent être redimensionnées, donc j'ai un programme Java). Malheureusement, la qualité de l'image est mal perdue et je n'ai pas accès 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(String[] 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 < listOfFiles.length; i++) {
if (listOfFiles[i].isFile()) {
System.out.println("File " + listOfFiles[i].getName());
img = ImageIO.read(new File("/Users/me/Desktop/images/"+listOfFiles[i].getName()));
tempJPG = resizeImage(img, img.getWidth(), img.getHeight());
newFileJPG = new File("/Users/me/Desktop/images/"+listOfFiles[i].getName()+"_New");
ImageIO.write(tempJPG, "jpg", newFileJPG);
}
}
System.out.println("DONE");
}
/**
* This function resize the image file and returns the BufferedImage object that can be saved to file system.
*/
public static BufferedImage resizeImage(final Image image, int width, int height) {
int targetw = 0;
int targeth = 75;
if (width > height)targetw = 112;
else targetw = 50;
do {
if (width > targetw) {
width /= 2;
if (width < targetw) width = targetw;
}
if (height > 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:
Voici le redimensionnement manuel que j'ai effectué dans Microsoft Paint:
et voici le résultat de mon programme [bilinéaire]:
PDATE: Pas de différence significative en utilisant BICUBIC
et voici le résultat de mon programme [bicubic]:
y a-t-il un moyen d'augmenter la qualité de la sortie du programme pour ne pas avoir à redimensionner manuellement toutes les photos?
Merci d'avance!
Malheureusement, il n'y a pas de mise à l'échelle prête à l'emploi recommandée dans Java) qui fournit des résultats visuellement satisfaisants. Voici, entre autres, les méthodes que je recommande pour la mise à l'échelle:
Graphics2d
Bicubic rapide et de bons résultats, généralement pas aussi bon que Lanczos3)Des exemples pour chaque méthode peuvent être trouvés dans cette réponse.
Voici votre image redimensionnée à 96x140
Avec différentes méthodes/libs. Cliquez sur l'image pour obtenir la taille complète:
Graphics2d
Interpolation bicubiqueGraphics2d
Interpolation du voisin le plus procheMalheureusement, une seule image ne suffit pas pour juger un algorithme de redimensionnement, vous devez tester les icônes aux contours nets, les photos avec du texte, etc.
Est dit être bon pour le haut et surtout la réduction d'échelle. Malheureusement, il n'y a pas d'implémentation native dans le JDK actuel vous devez donc l'implémenter vous-même et utiliser une bibliothèque telle que la bibliothèque de Morten Nobel . Un exemple simple utilisant dit 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ération matérielle que je connaisse. L'implémentation de Nobel est environ 8 fois plus lente qu'un algorithme de mise à l'échelle progressive avec 1/2 étape avec Graphics2d
. En savoir plus sur cette lib sur son blog .
Mentionné dans blog de Chris Campbell sur le redimensionnement en Java, le redimensionnement progressif consiste à redimensionner progressivement une image par incréments jusqu'à atteindre les dimensions définitives. 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é matériellement. Par conséquent, il offre généralement de très bonnes performances avec des résultats acceptables dans la plupart des cas. L'inconvénient majeur de ceci est que, même si la réduction d'échelle est inférieure à la moitié, l'utilisation de Graphics2D
Fournit les mêmes résultats médiocres puisqu'elle n'est réduite qu'une seule fois.
Voici un exemple simple sur la façon dont cela fonctionne:
Les bibliothèques suivantes incorporent des formes de mise à l'échelle progressive basées sur Graphics2d
:
Utilise l'algorithme bilinéaire progressif si la cible représente au moins la moitié de chaque dimension. Sinon, il utilise une simple mise à l'échelle bilinéaire Graphics2d
Et bicubique pour la conversion ascendante.
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 que la mise à l'échelle en une étape avec Graphics2d
Marquant en moyenne 6,9 secondes sur un repère .
Utilise une mise à l'échelle bicubique progressive. Dans le paramètre QUALITY
, il utilise un algorithme de style Campbell avec une réduction de moitié des dimensions à chaque étape, tandis que le paramètre ULTRA_QUALITY
Comporte 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 tout en réduisant les occurrences. où seulement 1 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 simple repère a abouti à une moyenne de 26,2 secondes et de 11,1 secondes respectivement.
Possède également des implémentations pour une mise à l'échelle progressive de tous les éléments de base Graphics2d
(Bilinéaire, bicubique et le plus proche voisin)
BufferedImage scaledImage = new MultiStepRescaleOp(dWidth, dHeight, RenderingHints.VALUE_INTERPOLATION_BILINEAR).filter(imageToScale, null);
JDK actuelle pour redimensionner 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, peu importe l'interpolation ou les autres RenderHints
utilisés. D'autre part, la conversion ascendante semble produire des images acceptables (le mieux serait bicubique). Dans la version précédente de JDK (nous parlons de 90s v1.1), Image.getScaledInstance()
a été introduit. Il fournit de bons résultats visuels avec le paramètre SCALE_AREA_AVERAGING
, Mais vous êtes déconseillé 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 lots d'images existantes est l'un de ses cas d'utilisation.
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 va aller de l'avant et prend tous les fichiers de votre répertoire images
et les traiter un par un, essayez de les redimensionner pour les adapter à la dimension de 112 x 75, et il tentera de préserver les proportions de l'image d'origine pour éviter le "gauchissement" de l'image.
Thumbnailator continue et lit tous les fichiers, quel que soit le type d’image (tant que le Java Image IO prend en charge le format, Thumbnailator le traite)), effectue l'opération de redimensionnement et afficher les vignettes sous forme de fichiers JPEG, tout en ajoutant un thumbnail.
au début du nom de fichier.
Voici une illustration de la façon dont le nom de fichier de l'original sera utilisé 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
En termes de qualité d'image, comme mentionné dans réponse de Marco1 , la technique décrite par Chris Campbell dans son Les dangers de Image.getScaledInstance () est implémentée dans Thumbnailator, ce qui entraîne une Des vignettes de qualité sans nécessiter de traitement compliqué.
La vignette suivante est générée lors du redimensionnement de l'image Fireworks affichée dans la question d'origine à l'aide de Thumbnailator:
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 les URL comme entrée et que Thumbnailator est également capable de créer BufferedImage
s.
Avertissement: 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 miniatures suivantes:
(L'autre est la vignette que vous avez créée avec MS Paint. Il est difficile d'appeler l'une d'entre elles "meilleure" que l'autre ...)
EDIT: Juste pour le souligner aussi: le problème principal avec votre code original était que vous n’avez pas vraiment échelle l’image en plusieurs étapes. Vous venez d'utiliser une boucle étrange pour "calculer" la taille cible. Le point clé est que vous effectuez réellement le 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(String[] 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<ImageWriter> 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();
}
}
Après des jours de recherche, je préférerais javaxt.
utilisez le javaxt.io.Image
la classe a un constructeur comme:
public Image(Java.awt.image.BufferedImage bufferedImage)
afin que vous puissiez faire ( another example
):
javaxt.io.Image image = new javaxt.io.Image(bufferedImage);
image.setWidth(50);
image.setOutputQuality(1);
Voici la sortie:
Nous ne devrions pas oublier a TwelveMonkeys Library
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 de redimensionner:
Voici le résultat obtenu avec sigma * (scale factor) = 0.3
:
Avec ImageJ le code à faire est assez court:
import ij.IJ;
import ij.ImagePlus;
import ij.io.Opener;
import ij.process.ImageProcessor;
public class Resizer {
public static void main(String[] args) {
processPicture("X0aPT.jpg", "output.jpg", 0.0198, ImageProcessor.NONE, 0.3);
}
public static void processPicture(String inputFile, String 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.substring(outputFilePath.lastIndexOf('.')+1), outputFilePath);
}
}
BTW: Vous avez seulement besoin de ij-1.49d.jar
_ (ou équivalent pour une autre version); il n'y a pas besoin de installer ImageJ.
Ci-dessous, ma propre implémentation de Progressive Scaling, sans utiliser aucune 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;
}