How to rotate JPEG images based on the orientation metadata?

JavaImage ProcessingGroovy

Java Problem Overview


I have some server code that is generating thumbnails when an image is uploaded. The issue is that when the image was taken and the camera/device was rotated, the thumbnails are rotated, even though the full size images themselves are displayed in the correct orientation in any image viewing software. This is only happening with jpgs.

Using Preview on OSX, I can see that jpgs have orientation metadata embedded within. When I use ImageTools (Grails Plugin) to generate a thumbnail, the EXIF metadata is not in the thumbnail, which is why the thumbnails appear rotated.

Via offline conversations, I have learned that while it is relatively easy to read EXIF metadata, there is no easy way to write it, which is why the data is lost when generating a jpg thumbnail.

So it seems I have two options:

  1. Use ImageMagick to generate the thumbnails. The downside is it requires installed more software on our servers.
  2. Read the EXIF Orientation data is code and rotate the thumbnail appropriately.

Does anyone know of any other options?

Java Solutions


Solution 1 - Java

If you want to rotate your images, I would suggest to use the metadata extractor library http://code.google.com/p/metadata-extractor/. You can get the image information with the following code:

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

Then given the orientation you retrieve, you can rotate and/or flip the image to the right orientation. The Affine transform for the EXIF orientation is given by the following method:

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

The rotation of the image would be done by the following method:

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

In a server environment, don't forget to run with -Djava.awt.headless=true

Solution 2 - Java

The Thumbnailator library honors EXIF orientation flags. To read an image at full size with correct orientation:

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

Solution 3 - Java

This can be done surprisingly easily by using the image part of JavaXT core library :

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

That's it!

I have tried Apache Commons Imaging, but that was a mess. JavaXT is way more elegant.

Solution 4 - Java

Exif seems to be hard to write because of proprietary stuff in it. However, you can consider another option

Read original but only write orientation tag to thumbnails.

Apache Sanselan seems to have nice collection of tools to do it.

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

Look at ExifRewriter class, for example.

Solution 5 - Java

As dnault mentioned in previous comment, Thumbnaliator library resolves the issue. But you should use correct input/output formats to avoid color change on this automatic rotation.

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

Solution 6 - Java

My solution is a combination of @PerLindberg's answer as well as @AntoineMartin's. I tried the other answers with Java 8 on Windows 10 and none seemed to do the trick. @AntoinMartin's com.drew.imaging solution was slow and image turned out black and white and full of artifacts. @PerLindberg's JavaXT solution did not read Exif 2.2 data.

  1. Use com.drew.imaging to read exif information:

    // 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. Use JavaXT to perform the rotation based on Exif data.

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

    }

Solution 7 - Java

If you just want it to look right. You can just add a "rotate" -PI/2 (-90 degrees), PI/2 (90 degrees), or PI (+180 degrees) as necessary depending on the 'orientation' you've already extracted. The browser or any other program will correctly display the image as the orientation will have been applied and the metadata stripped from the thumbnail output.

Solution 8 - Java

The rotation functions above rotate the image data. And there is one more thing to do. You must set the width and height of BufferedImage. When rotating 90 degrees and 270 degrees, the width and height must be interchanged.

    BufferedImage transformed;
    switch (orientation) {
        case 5:
        case 6:
        case 7:
        case 8:
            transformed = new BufferedImage(image.getHeight(), image.getWidth(), image.getType());
            break;
        default:
            transformed = new BufferedImage(image.getWidth(), image.getHeight(), image.getType());
    }

Solution 9 - Java

Based on the answers of Antoine Martin I created an own class for correcting the orientation of a given jpeg image (in my case as an input stream) based on the exif information of the image. With his solution I had the problem, that the colors of the resulting image were wrong, therefore I created this one. For retrieving the metadata of the image I used the metadata-extractor library.

I hope it will help some people.

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionhvgotcodesView Question on Stackoverflow
Solution 1 - JavaAntoine MartinView Answer on Stackoverflow
Solution 2 - JavadnaultView Answer on Stackoverflow
Solution 3 - JavaPer LindbergView Answer on Stackoverflow
Solution 4 - JavaAlex GitelmanView Answer on Stackoverflow
Solution 5 - JavaAnton PetrovskyiView Answer on Stackoverflow
Solution 6 - JavaTed GulesserianView Answer on Stackoverflow
Solution 7 - JavakarmakazeView Answer on Stackoverflow
Solution 8 - JavaHeeGuView Answer on Stackoverflow
Solution 9 - JavaFlorianView Answer on Stackoverflow