Comment faire pivoter les images JPEG en fonction des métadonnées d'orientation?
J'ai du code de serveur qui génère des vignettes lorsqu'une image est téléchargée. Le problème est que lorsque l'image a été prise et que la caméra / l'appareil a été tourné, les vignettes sont tournées, même si les images en taille réelle elles-mêmes sont affichées dans la bonne orientation dans n'importe quel logiciel de visualisation d'image. Cela ne se produit qu'avec les jpg.
En utilisant Preview sur OSX, je peux voir que les jpg ont des métadonnées d'orientation intégrées. Lorsque J'utilise ImageTools (Plugin Grails) pour générer un vignette, les métadonnées EXIF ne sont pas dans la vignette, ce qui explique pourquoi les vignettes apparaissent pivotées.
Via des conversations hors ligne, j'ai appris que s'il est relativement facile de lire les métadonnées EXIF, il n'y a pas de moyen facile de les écrire, c'est pourquoi les données sont perdues lors de la génération d'une vignette jpg.
Il semble donc que j'ai deux options:
- Utilisez ImageMagick pour générer les vignettes. L'inconvénient est qu'il nécessite installé plus de logiciels sur nos serveurs.
- lire le Les données D'Orientation EXIF sont codées et font pivoter la vignette de manière appropriée.
Est-ce que quelqu'un connaît d'autres options?
7 réponses
Si vous voulez faire pivoter vos images, je suggère d'utiliser la bibliothèque metadata extractor http://code.google.com/p/metadata-extractor/. Vous pouvez obtenir les informations d'image avec le code suivant:
// Inner class containing image information
public static class ImageInformation {
public final int orientation;
public final int width;
public final int height;
public ImageInformation(int orientation, int width, int height) {
this.orientation = orientation;
this.width = width;
this.height = height;
}
public String toString() {
return String.format("%dx%d,%d", this.width, this.height, this.orientation);
}
}
public static ImageInformation readImageInformation(File imageFile) throws IOException, MetadataException, ImageProcessingException {
Metadata metadata = ImageMetadataReader.readMetadata(imageFile);
Directory directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
JpegDirectory jpegDirectory = metadata.getFirstDirectoryOfType(JpegDirectory.class);
int orientation = 1;
try {
orientation = directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
} catch (MetadataException me) {
logger.warn("Could not get orientation");
}
int width = jpegDirectory.getImageWidth();
int height = jpegDirectory.getImageHeight();
return new ImageInformation(orientation, width, height);
}
Ensuite, compte tenu de l'orientation que vous récupérez, vous pouvez faire pivoter et/ou retourner l'image vers la bonne orientation. La transformation Affine pour L'orientation EXIF est donnée par la méthode suivante:
// Look at http://chunter.tistory.com/143 for information
public static AffineTransform getExifTransformation(ImageInformation info) {
AffineTransform t = new AffineTransform();
switch (info.orientation) {
case 1:
break;
case 2: // Flip X
t.scale(-1.0, 1.0);
t.translate(-info.width, 0);
break;
case 3: // PI rotation
t.translate(info.width, info.height);
t.rotate(Math.PI);
break;
case 4: // Flip Y
t.scale(1.0, -1.0);
t.translate(0, -info.height);
break;
case 5: // - PI/2 and Flip X
t.rotate(-Math.PI / 2);
t.scale(-1.0, 1.0);
break;
case 6: // -PI/2 and -width
t.translate(info.height, 0);
t.rotate(Math.PI / 2);
break;
case 7: // PI/2 and Flip
t.scale(-1.0, 1.0);
t.translate(-info.height, 0);
t.translate(0, info.width);
t.rotate( 3 * Math.PI / 2);
break;
case 8: // PI / 2
t.translate(0, info.width);
t.rotate( 3 * Math.PI / 2);
break;
}
return t;
}
La rotation de l'image se ferait par ce qui suit méthode:
public static BufferedImage transformImage(BufferedImage image, AffineTransform transform) throws Exception {
AffineTransformOp op = new AffineTransformOp(transform, AffineTransformOp.TYPE_BICUBIC);
BufferedImage destinationImage = op.createCompatibleDestImage(image, (image.getType() == BufferedImage.TYPE_BYTE_GRAY) ? image.getColorModel() : null );
Graphics2D g = destinationImage.createGraphics();
g.setBackground(Color.WHITE);
g.clearRect(0, 0, destinationImage.getWidth(), destinationImage.getHeight());
destinationImage = op.filter(image, destinationImage);
return destinationImage;
}
Dans un environnement serveur, n'oubliez pas de fonctionner avec -Djava.awt.headless=true
La bibliothèqueThumbnailator honore les indicateurs d'orientation EXIF. Pour lire une image en taille réelle avec une orientation correcte:
BufferedImage image = Thumbnails.of(inputStream).scale(1).asBufferedImage();
Cela peut être fait étonnamment facilement en utilisant la partie image de la bibliothèque JavaXT core :
// Browsers today can't handle images with Exif Orientation tag
Image image = new Image(uploadedFilename);
// Auto-rotate based on Exif Orientation tag, and remove all Exif tags
image.rotate();
image.saveAs(permanentFilename);
C'est ça!
J'ai essayé Apache Commons Imaging, mais c'était un gâchis. JavaXT est beaucoup plus élégant.
Exif semble être difficile à écrire à cause de choses propriétaires en elle. Cependant, vous pouvez envisager une autre option
Lire l'original mais seulement écrire la balise d'orientation pour les vignettes.
Apache Sanselan semble avoir une belle collection d'outils pour le faire.
Http://commons.apache.org/proper/commons-imaging/
Regardez la classe ExifRewriter, par exemple.
Si vous voulez juste qu'il ait l'air juste. Vous pouvez simplement ajouter une" rotation " - PI / 2 (-90 degrés), PI/2 (90 degrés) ou PI (+180 degrés) selon l'orientation que vous avez déjà extraite. Le navigateur ou tout autre programme affichera correctement l'image car l'orientation aura été appliquée et les métadonnées supprimées de la sortie de la vignette.
Sur la base des réponses D'Antoine Martin, j'ai créé une propre classe pour corriger l'orientation d'une image jpeg donnée (dans mon cas en tant que flux d'entrée) en fonction des informations exif de l'image. Avec sa solution, j'ai eu le problème, que les couleurs de l'image résultante étaient fausses, donc j'ai créé celui-ci. Pour récupérer les métadonnées de l'image, j'ai utilisé le métadonnées-extracteur bibliothèque.
J'espère que cela aidera certaines personnes.
public class ImageOrientationUtil {
/**
* Checks the orientation of the image and corrects it if necessary.
* <p>If the orientation of the image does not need to be corrected, no operation will be performed.</p>
* @param inputStream
* @return
* @throws ImageProcessingException
* @throws IOException
* @throws MetadataException
*/
public static BufferedImage correctOrientation(InputStream inputStream) throws ImageProcessingException, IOException, MetadataException {
Metadata metadata = ImageMetadataReader.readMetadata(inputStream);
if(metadata != null) {
if(metadata.containsDirectoryOfType(ExifIFD0Directory.class)) {
// Get the current orientation of the image
Directory directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
int orientation = directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
// Create a buffered image from the input stream
BufferedImage bimg = ImageIO.read(inputStream);
// Get the current width and height of the image
int[] imageSize = {bimg.getWidth(), bimg.getHeight()};
int width = imageSize[0];
int height = imageSize[1];
// Determine which correction is needed
AffineTransform t = new AffineTransform();
switch(orientation) {
case 1:
// no correction necessary skip and return the image
return bimg;
case 2: // Flip X
t.scale(-1.0, 1.0);
t.translate(-width, 0);
return transform(bimg, t);
case 3: // PI rotation
t.translate(width, height);
t.rotate(Math.PI);
return transform(bimg, t);
case 4: // Flip Y
t.scale(1.0, -1.0);
t.translate(0, -height);
return transform(bimg, t);
case 5: // - PI/2 and Flip X
t.rotate(-Math.PI / 2);
t.scale(-1.0, 1.0);
return transform(bimg, t);
case 6: // -PI/2 and -width
t.translate(height, 0);
t.rotate(Math.PI / 2);
return transform(bimg, t);
case 7: // PI/2 and Flip
t.scale(-1.0, 1.0);
t.translate(height, 0);
t.translate(0, width);
t.rotate( 3 * Math.PI / 2);
return transform(bimg, t);
case 8: // PI / 2
t.translate(0, width);
t.rotate( 3 * Math.PI / 2);
return transform(bimg, t);
}
}
}
return null;
}
/**
* Performs the tranformation
* @param bimage
* @param transform
* @return
* @throws IOException
*/
private static BufferedImage transform(BufferedImage bimage, AffineTransform transform) throws IOException {
// Create an transformation operation
AffineTransformOp op = new AffineTransformOp(transform, AffineTransformOp.TYPE_BICUBIC);
// Create an instance of the resulting image, with the same width, height and image type than the referenced one
BufferedImage destinationImage = new BufferedImage( bimage.getWidth(), bimage.getHeight(), bimage.getType() );
op.filter(bimage, destinationImage);
return destinationImage;
}
}
Comme dnault mentionné dans le commentaire précédent, Thumbnaliator bibliothèque résout le problème. Mais vous devez utiliser des formats d'entrée/sortie corrects pour éviter le changement de couleur lors de cette rotation automatique.
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(file.getContents());
Thumbnails.of(in)
.scale(1)
.toOutputStream(baos);
byte[] bytes = baos.toByteArray();