Java - redimensionner l'image sans perte de qualité
j'ai 10 000 photos qui doivent être redimensionnées donc, j'ai un programme Java pour le faire. Malheureusement, la qualité de l'image est mal perdu 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 celle-ci:
voici le redimensionnement manuel que J'ai fait dans Microsoft Paint:
et voici le résultat de mon programme [bilinéaire]:
mise à jour: aucune différence significative en utilisant BICUBIC
et c'est la sortie de mon programme [bicubique]:
est-ce qu'il y a de toute façon pour augmenter la qualité de la sortie du programme pour que je n'aie pas à redimensionner manuellement toutes les photos?
Merci à l'avance!
7 réponses
malheureusement, il n'y a pas de mise à l'échelle recommandée en Java qui donne de bons résultats visuels. Entre autres, voici les méthodes que je recommande pour la mise à l'échelle:
- Lanczos3 Rééchantillonnage (généralement mieux visuellement, mais plus lent)
- Progressif vers le Bas d'Échelle (généralement visuellement fine, peut être très rapide)
- mise à l'échelle en une étape pour la mise à l'échelle (avec
Graphics2d
bicubic résultats rapides et bons, généralement pas aussi bon que Lanczos3)
des exemples pour chaque méthode peuvent être trouvés dans cette réponse.
Comparaison Visuelle
Voici votre image ajustée à 96x140
avec différentes méthodes / libs. Cliquez sur l'image pour obtenir la taille complète:
- Morten Nobel lib Lanczos3
- Thumbnailator Bilinear Progressive Mise à l'échelle
- Imgscalr ULTRA_QUALTY (1/7 Step Bicubic Progressive Scaling)
- Imgscalr QUALITÉ (pas de 1/2 Bicubique Progressive mise à l'Échelle)
- Morten Nobel lib Bilinear Progressive Scaling
Graphics2d
interpolation bicubiqueGraphics2d
interpolation du voisin le plus proche- Photoshop CS5 bicubique comme référence
malheureusement une seule image n'est pas suffisante pour juger un algorithme de mise à l'échelle, vous devriez test des icônes avec des bords tranchants, des photos avec du texte, etc.
Lanczos Resampling
on dit qu'il est bon pour la montée et surtout la descente. Malheureusement il n'est pas natif de la mise en œuvre dans le courant de JDK alors soit vous l'implémentez vous-même et utilisez une lib comme Morten Nobel lib. 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é sur maven central qui n'est pas mentionné malheureusement. Le 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 du matériel connu de moi. L'implémentation de Nobel est environ 8 fois plus lente qu'un algorithme de graduation progressive de 1/2 pas avec Graphics2d
. en savoir plus sur ce lib sur son blog.
Graduation Progressive
Mentionné dans Chris Campbell blog à propos de la mise à l'échelle en Java, la mise à l'échelle progressive est essentiellement la mise à l'échelle progressive d'une image en plus petit étapes jusqu'à ce que les dimensions finales sont atteintes. Campbell décrit cela comme la moitié de la largeur / hauteur jusqu'à ce que vous atteigniez la cible. Cela donne de bons résultats et peut être utilisé avec Graphics2D
qui peut être accéléré par le matériel, ayant donc généralement de très bonnes performances avec des résultats acceptables dans la plupart des cas. Le principal inconvénient de cette méthode est qu'elle réduit l'échelle de moins de la moitié en utilisant Graphics2D
fournit les mêmes résultats médiocres puisqu'il n'est mis à l'échelle qu'une seule fois.
Voici un exemple simple sur la façon dont il fonctionne:
les lib suivantes incorporent des formes de graduation 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 simple Graphics2d
mise à l'échelle Bilinéaire 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 échelle à un pas avec Graphics2d
marquant une moyenne de 6.9 sec dans mon test.
Imgscalr v4.2
utilise l'écaillage bicubique progressif. Dans le QUALITY
paramétrage il utilise un algorithme de style Campbell avec une réduction de moitié des dimensions à chaque étape tandis que le ULTRA_QUALITY
a des pas plus fins, réduisant la taille de chaque incrément de 1/7 ce qui génère généralement des images plus douces mais minimise les cas 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 major l'inconvénient, c'est la performance. ULTRA_QUALITY
est considérablement plus lent que les autres libs. Même QUALITY
un peu plus lent que L'implémentation de Thumbnailator. My simple test ont donné respectivement une moyenne de 26,2 sec et de 11,1 sec.
Morten Nobel lib v0.8.6
a également des implémentations pour la mise à l'échelle progressive pour tous lesGraphics2d
(bilinéaire, bicubique, & du plus proche voisin)
BufferedImage scaledImage = new MultiStepRescaleOp(dWidth, dHeight, RenderingHints.VALUE_INTERPOLATION_BILINEAR).filter(imageToScale, null);
un mot sur L'échelle JDK Méthodes
la façon JDK actuelle de dimensionner 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 autre RenderHints
sont utilisés. D'un autre côté, l'échelle ascendante semble produire des images acceptables (le meilleur serait bicubique). Dans la version JDK précédente (nous parlons 90s v1.1)Image.getScaledInstance()
a été introduit qui a fourni de bons résultats visuels avec le paramètre SCALE_AREA_AVERAGING
mais vous décourager de l'utiliser - lire l'explication complète ici.
Thumbnailator est une bibliothèque qui a été écrite pour créer des vignettes de haute qualité d'une manière simple, et faire une conversion par lots 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 être en mesure d'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 dans votre images
répertoire et passer à les traiter un par un, essayez de les redimensionner pour s'adapter aux dimensions de 112 x 75, et il va tenter de conserver les proportions de l'image d'origine pour éviter la "déformation" de l'image.
Thumbnailator va de l'avant et lire tous les fichiers, quels que soient les types d'image (aussi longtemps que L'IO D'Image Java supporte le format, Thumbnailator va le traiter), effectuer l'opération de redimensionnement et de sortie les thumbnails comme des fichiers JPEG, tout en tacking sur un thumbnail.
au début du fichier nom.
ce qui suit est 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
Générer un haut niveau de qualité des vignettes
en termes de qualité d'image, comme mentionné dans réponse de Marco13, la technique décrite par Chris Campbell dans son les périls de L'Image.getScaledInstance () est implémenté dans Thumbnailator, résultant en une qualité élevée vignettes sans aucun traitement compliqué.
voici la vignette générée lors du redimensionnement de l'image des feux d'artifice présentée dans la question originale à L'aide de la vignette:
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 aussi accepter des URLs comme entrée, et que Thumbnailator est aussi capable de créer BufferedImage
s bien.
Disclaimer: je suis le responsable de l' Thumbnailator bibliothèque.
compte tenu de votre image d'entrée, la méthode de la réponse dans le premier lien dans les commentaires (bravo à Chris Campbell) produit une des vignettes suivantes:
(l'autre est la vignette que vous avez créée avec MS Paint. Il est difficile d'appeler l'un d'entre eux "mieux" que l'autre...)
EDIT: juste pour souligner ceci aussi bien: le problème principal avec votre code original était que vous n'avez pas vraiment échelle l'image en plusieurs étapes. Vous avez juste utilisé une boucle étrange pour" calculer " la taille de la cible. Le point clé est que vous exécutez 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();
}
}
nous ne devrions pas oublier un 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);
après des jours de recherche, je préférerais javaxt.
javaxt.io.Image
classe a un constructeur de la forme:
public Image(java.awt.image.BufferedImage bufferedImage)
alors vous pouvez le faire (another example
):
javaxt.io.Image image = new javaxt.io.Image(bufferedImage);
image.setWidth(50);
image.setOutputQuality(1);
Voici la sortie:
Le résultat semble être mieux (que le résultat de votre programme), si vous appliquez flou Gaussien avant de redimensionner:
C'est le résultat que j'obtiens, avec sigma * (scale factor) = 0.3
:
ImageJ le code pour faire cela est très courte:
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 d' ij-1.49d.jar
(ou l'équivalent pour une autre version); il n'y a pas besoin de installer ImageJ.
ci-dessous sont ma propre mise en œuvre de L'échelle Progressive, sans utiliser de bibliothèque externe. Espérons 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;
}