Подтвердить что ты не робот

Как поворачивать изображения JPEG на основе метаданных ориентации?

У меня есть код сервера, который создает эскизы при загрузке изображения. Проблема в том, что когда изображение было снято и камера/устройство было повернуто, миниатюры поворачиваются, даже если сами изображения полного размера отображаются в правильной ориентации в любом программном обеспечении для просмотра изображений. Это происходит только с jpgs.

Используя Preview в OSX, я вижу, что jpg имеют встроенные метаданные ориентации. Когда я использую ImageTools (плагин Grails) для генерации миниатюры, метаданные EXIF ​​не находятся в миниатюре, поэтому миниатюры выглядят повернутыми.

Через автономные разговоры я узнал, что, хотя относительно легко читать метаданные EXIF, нет простого способа написать его, поэтому данные теряются при создании миниатюры jpg.

Итак, кажется, у меня есть два варианта:

  • Используйте ImageMagick для создания эскизов. Недостатком является то, что на наших серверах требуется установить больше программного обеспечения.
  • Чтение данных EXIF ​​Orientation - это код и поворот эскиза соответствующим образом.

Кто-нибудь знает какие-либо другие варианты?

4b9b3361

Ответ 1

Если вы хотите повернуть свои изображения, я бы предложил использовать библиотеку экстрактор метаданных http://code.google.com/p/metadata-extractor/. Вы можете получить информацию об изображении со следующим кодом:

// 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);
}

Затем, задав ориентацию, которую вы получаете, вы можете повернуть и/или перевернуть изображение в нужную ориентацию. Аффинное преобразование для ориентации EXIF ​​задается следующим методом:

// 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;
}

Вращение изображения будет выполняться с помощью следующего метода:

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;
}

В серверной среде не забудьте запустить с помощью -Djava.awt.headless=true

Ответ 2

Библиотека Thumbnailator почитает флаги ориентации EXIF. Чтобы прочитать изображение в полном размере с правильной ориентацией:

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

Ответ 3

Это может быть сделано на удивление легко с помощью графической части базовой библиотеки 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);

Это!

Я пробовал Apache Commons Imaging, но это был беспорядок. JavaXT намного элегантнее.

Ответ 4

Exif, похоже, трудно писать из-за запатентованного материала. Однако вы можете рассмотреть другой вариант

Прочтите оригинал, но только пишите ориентационный тег в миниатюры.

У Apache Sanselan, похоже, есть своя коллекция инструментов для этого.

http://commons.apache.org/proper/commons-imaging/

Посмотрите, например, на класс ExifRewriter.

Ответ 5

Если вы просто хотите, чтобы это выглядело правильно. Вы можете просто добавить "поворот" -PI/2 (-90 градусов), PI/2 (90 градусов) или PI (+180 градусов) по мере необходимости в зависимости от "ориентации", которую вы уже извлекли. Браузер или любая другая программа будет правильно отображать изображение по мере того, как будет применена ориентация, и метаданные, отделенные от вывода эскизов.

Ответ 6

Как упоминалось в предыдущем комментарии, библиотека Thumbnaliator решает эту проблему. Но вы должны использовать правильные форматы ввода/вывода, чтобы избежать изменения цвета при этом автоматическом вращении.

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(file.getContents());
Thumbnails.of(in)
    .scale(1)
    .toOutputStream(baos);
byte[] bytes = baos.toByteArray();

Ответ 7

Мое решение - это сочетание ответа @PerLindberg и @AntoineMartin. Я попробовал другие ответы с Java 8 на Windows 10, и ни один из них не помог. Решение @AntoinMartin com.drew.imaging было медленным, и изображение получилось черно-белым и полным артефактов. @PerLindberg Решение JavaXT не считывало данные Exif 2.2.

1) Используйте com.drew.imaging для чтения 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) Используйте JavaXT для выполнения поворота на основе данных 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);
    }

}

Ответ 8

Основываясь на ответах Антуана Мартина, я создал собственный класс для исправления ориентации данного jpeg-изображения (в моем случае как входного потока) на основе exif-информации изображения. С его решением у меня возникла проблема, что цвета полученного изображения были неправильными, поэтому я создал этот. Для извлечения метаданных изображения я использовал библиотеку metadata-extractor.

Я надеюсь, что это поможет некоторым людям.

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;
}
}