web-dev-qa-db-fra.com

Comment faire pivoter les images JPEG en fonction des métadonnées d'orientation?

J'ai un code de serveur qui génère des vignettes quand une image est téléchargée. Le problème est que, lorsque l'image a été prise et que l'appareil photo/périphérique a été pivoté, les vignettes sont pivotées, même si les images en taille réelle elles-mêmes sont affichées dans l'orientation correcte dans tout logiciel de visualisation d'images. Cela ne se produit qu'avec des jpgs.

En utilisant Preview sur OSX, je peux voir que les jpgs ont des métadonnées d’orientation intégrées. Lorsque j'utilise ImageTools (Grails Plugin) pour générer une vignette, les métadonnées EXIF ​​ne sont pas dans la vignette, c'est pourquoi les vignettes sont pivotées. 

Lors de 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 miniature jpg.

Il semble donc que j'ai deux options: 

  1. Utilisez ImageMagick pour générer les vignettes. L'inconvénient est qu'il faut installer plus de logiciels sur nos serveurs.
  2. Lire les données EXIF ​​d'orientation est un code et faire pivoter la vignette de manière appropriée.

Est-ce que quelqu'un sait d'autres options?

51
hvgotcodes

Si vous souhaitez faire pivoter vos images, je vous suggère d'utiliser la bibliothèque d'extraction de métadonnées http://code.google.com/p/metadata-extractor/ . Vous pouvez obtenir les informations sur l'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, en fonction de l’orientation que vous récupérez, vous pouvez faire pivoter et/ou retourner l’image dans le bon sens. 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 la méthode suivante:

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 de serveur, n'oubliez pas de courir avec -Djava.awt.headless=true

54
Antoine Martin

La bibliothèque Thumbnailator respecte les drapeaux d'orientation EXIF. Pour lire une image en taille réelle avec une orientation correcte:

BufferedImage image = Thumbnails.of(inputStream).scale(1).asBufferedImage();
14
dnault

Ceci peut être fait étonnamment facilement en utilisant la partie image de la bibliothèque principale de JavaXT :

// 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 tout!

J'ai essayé Apache Commons Imaging, mais c'était un gâchis. JavaXT est beaucoup plus élégant.

6
Per Lindberg

Exif semble difficile à écrire à cause de son contenu propriétaire . Cependant, vous pouvez envisager une autre option.

Lire l'original mais n'écrire que la balise d'orientation sur 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.

3
Alex Gitelman

Si vous voulez juste que ça ait l'air correct. Vous pouvez simplement ajouter une "rotation" -PI/2 (-90 degrés), PI/2 (90 degrés) ou PI (+180 degrés) selon les besoins, en fonction de 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 miniature.

1
karmakaze

Comme dnault mentionné dans le commentaire précédent, Thumbnaliator library résout le problème. Toutefois, vous devez utiliser les formats d’entrée/de sortie corrects pour éviter tout 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();
0
Anton Petrovskyi

Ma solution est une combinaison des réponses de @ PerLindberg et de @ AntoineMartin. J'ai essayé les autres réponses avec Java 8 sur Windows 10 et aucune ne semblait faire l'affaire. La solution com.drew.imaging de @ AntoinMartin était lente et l'image s'est avérée noir et blanc et pleine d'artefacts. @ La solution JavaXT de PerLindberg n'a pas lu les données Exif 2.2.

1) Utilisez com.drew.imaging pour lire les informations exif:

// 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 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;
    if (directory != null) {
        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);
    } else {
        return null;
    }
}

2) Utilisez JavaXT pour effectuer la rotation basée sur les données Exif.

public void rotateMyImage(String imageDownloadFilenme);
    File imageDownloadFile =  new File(imgageDownloadFilenme);
    Image image = new Image(imgageDownloadFilenme);
    ImageInformation imageInformation = readImageInformation(imageDownloadFile);
    if (imageInformation != null) {
        rotate(imageInformation, image);
    }
    image.saveAs(imgageDownloadFilenme);
}

public void rotate(ImageInformation info, Image image) {

    switch(info.orientation) {
        case 1:
            return;
        case 2:
            image.flip();
            break;
        case 3:
            image.rotate(180.0D);
            break;
        case 4:
            image.flip();
            image.rotate(180.0D);
            break;
        case 5:
            image.flip();
            image.rotate(270.0D);
            break;
        case 6:
            image.rotate(90.0D);
            break;
        case 7:
            image.flip();
            image.rotate(90.0D);
            break;
        case 8:
            image.rotate(270.0D);
    }

}
0
Ted Gulesserian