Skip to content

Commit

Permalink
Add Exif orientation rewriter
Browse files Browse the repository at this point in the history
Co-authored-by: YavizGuldalf <[email protected]>
Co-authored-by: Hasti Mohebali Zadeh <[email protected]>
Co-authored-by: chenyi <[email protected]>
Co-authored-by: regusta <[email protected]>
  • Loading branch information
5 people committed Mar 7, 2023
1 parent ff13fa4 commit 0ab16f7
Show file tree
Hide file tree
Showing 2 changed files with 358 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.commons.imaging.formats.jpeg.exif;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteOrder;
import java.util.ArrayList;

import org.apache.commons.imaging.ImageReadException;
import org.apache.commons.imaging.ImageWriteException;
import org.apache.commons.imaging.Imaging;
import org.apache.commons.imaging.common.bytesource.ByteSource;
import org.apache.commons.imaging.common.bytesource.ByteSourceArray;
import org.apache.commons.imaging.common.bytesource.ByteSourceFile;
import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata;
import org.apache.commons.imaging.formats.tiff.TiffContents;
import org.apache.commons.imaging.formats.tiff.TiffHeader;
import org.apache.commons.imaging.formats.tiff.TiffImageMetadata;
import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
import org.apache.commons.imaging.formats.tiff.write.TiffOutputDirectory;
import org.apache.commons.imaging.formats.tiff.write.TiffOutputField;
import org.apache.commons.imaging.formats.tiff.write.TiffOutputSet;

public class ExifOrientationRewriter {

public enum Orientation {
HORIZONTAL((short)1),
MIRROR_HORIZONTAL((short)2),
ROTATE_180((short)3),
MIRROR_VERTICAL((short)4),
MIRROR_HORIZONTAL_AND_ROTATE_270((short)5),
ROTATE_90((short)6),
MIRROR_HORIZONTAL_AND_ROTATE_90((short)7),
ROTATE_270((short)8);

private short val;

Orientation(short orVal) {
this.val = orVal;
}

public short getVal() {
return val;
}
}

private ByteSource fileSrc;

public ExifOrientationRewriter(File imageFile) {
fileSrc = new ByteSourceFile(imageFile);
}
public ExifOrientationRewriter(byte[] byteArray) {
fileSrc = new ByteSourceArray(byteArray);
}
public ExifOrientationRewriter(ByteSource byteSource) {
fileSrc = byteSource;
}

/***
* Get the orientation (enum) of the current image
* Returns horizontal by default
* @return Orientation enum
* @throws IOException
* @throws ImageReadException
* @throws ImageWriteException
*/
public Orientation getExifOrientation() throws IOException, ImageReadException, ImageWriteException {

final JpegImageMetadata metadata = (JpegImageMetadata) Imaging.getMetadata(this.fileSrc.getAll());

if (metadata == null) {
return Orientation.HORIZONTAL;
}

final TiffImageMetadata exifMetadata = metadata.getExif();

if (exifMetadata == null) {
return Orientation.HORIZONTAL;
}

final TiffOutputSet outputSet = exifMetadata.getOutputSet();

TiffOutputDirectory tod = outputSet.getRootDirectory();
if (tod == null) {
return Orientation.HORIZONTAL;
}

TiffOutputField tof = tod.findField(TiffTagConstants.TIFF_TAG_ORIENTATION);
if (tof == null) {
return Orientation.HORIZONTAL;
}

short imageOrientationVal = (short) exifMetadata.getFieldValue(TiffTagConstants.TIFF_TAG_ORIENTATION);

for (Orientation orientation : Orientation.values()) {
if(orientation.getVal() == imageOrientationVal) {
return orientation;
}
}

return Orientation.HORIZONTAL;
}

/**
* A method that sets a new value to the orientation field in the EXIF metadata of a JPEG file.
* @param orientation the value as a enum of the direction to set as the new EXIF orientation
*
*/
public void setExifOrientation(Orientation orientation) throws ImageWriteException, IOException, ImageReadException {

JpegImageMetadata metadata = (JpegImageMetadata) Imaging.getMetadata(this.fileSrc.getAll());

if (metadata == null) {
metadata = new JpegImageMetadata(null, new TiffImageMetadata(new TiffContents(new TiffHeader(ByteOrder.BIG_ENDIAN, 0, 0), new ArrayList<>(), new ArrayList<>())));
}

TiffImageMetadata exifMetadata = metadata.getExif();

if (exifMetadata == null) {
exifMetadata = new TiffImageMetadata(new TiffContents(new TiffHeader(ByteOrder.BIG_ENDIAN, 0, 0), new ArrayList<>(), new ArrayList<>()));
}

final TiffOutputSet outputSet = exifMetadata.getOutputSet();

TiffOutputDirectory tod = outputSet.getOrCreateRootDirectory();
tod.removeField(TiffTagConstants.TIFF_TAG_ORIENTATION);
tod.add(TiffTagConstants.TIFF_TAG_ORIENTATION, orientation.getVal());

final ByteArrayOutputStream baos = new ByteArrayOutputStream();
new ExifRewriter().updateExifMetadataLossy(this.fileSrc, baos, outputSet);

this.fileSrc = new ByteSourceArray(baos.toByteArray());
}

/**
* @return the ByteSource of the current file
*/
public ByteSource getOutput() {
return fileSrc;
}

/**
* Writes Bytesource to file with given path
* @param path String of the path in which the file is written
* @throws IOException
*/
public void getOutput(String path)
throws IOException {
final File tempFile = new File(path);
try (FileOutputStream outputStream = new FileOutputStream(tempFile)) {
outputStream.write(fileSrc.getAll());
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


package org.apache.commons.imaging.formats.jpeg.exif;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.List;

import org.apache.commons.imaging.Imaging;
import org.apache.commons.imaging.ImageReadException;
import org.apache.commons.imaging.ImageWriteException;
import org.apache.commons.imaging.common.bytesource.ByteSource;
import org.apache.commons.imaging.common.bytesource.ByteSourceFile;
import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata;
import org.apache.commons.imaging.formats.jpeg.exif.ExifOrientationRewriter.Orientation;
import org.apache.commons.imaging.formats.tiff.TiffImageMetadata;
import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
import org.apache.commons.imaging.formats.tiff.write.TiffOutputSet;
import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.Test;

public class ExifOrientationRewriterTest extends ExifBaseTest {

@Test
public void originalOrientationMatchesRewriterGet()
throws ImageReadException, IOException, ImageWriteException {

final List<File> images = getImagesWithExifData();
for (final File imageFile : images) {
final JpegImageMetadata originalMetadata = (JpegImageMetadata) Imaging.getMetadata(imageFile);
assertNotNull(originalMetadata);

final TiffImageMetadata originalExifMetadata = originalMetadata.getExif();
assertNotNull(originalExifMetadata);

short originalOrt = (short) originalExifMetadata.getFieldValue(TiffTagConstants.TIFF_TAG_ORIENTATION);

ExifOrientationRewriter rewriter = new ExifOrientationRewriter(imageFile);
short ort = rewriter.getExifOrientation().getVal();

assertEquals(ort, originalOrt);
}

}

@Test
public void rewrittenOrientationMatchesRewriterGet()
throws ImageReadException, IOException, ImageWriteException {

final List<File> images = getImagesWithExifData();
for (final File imageFile : images) {
final ByteSource byteSource = new ByteSourceFile(imageFile);

final JpegImageMetadata originalMetadata = (JpegImageMetadata) Imaging.getMetadata(imageFile);
assertNotNull(originalMetadata);

final TiffImageMetadata originalExifMetadata = originalMetadata.getExif();
assertNotNull(originalExifMetadata);

final TiffOutputSet outputSet = originalExifMetadata.getOutputSet();

outputSet.getOrCreateRootDirectory().removeField(TiffTagConstants.TIFF_TAG_ORIENTATION);
outputSet.getOrCreateRootDirectory().add(TiffTagConstants.TIFF_TAG_ORIENTATION, (short) TiffTagConstants.ORIENTATION_VALUE_ROTATE_270_CW);

final ByteArrayOutputStream baos = new ByteArrayOutputStream();

new ExifRewriter().updateExifMetadataLossy(byteSource.getAll(), baos,
outputSet);

final byte[] bytes = baos.toByteArray();
final File tempFile = Files.createTempFile("inserted" + "_", ".jpg").toFile();

FileUtils.writeByteArrayToFile(tempFile, bytes);

final ExifOrientationRewriter rewriter = new ExifOrientationRewriter(tempFile);
short ort = rewriter.getExifOrientation().getVal();

assertEquals(ort, (short) TiffTagConstants.ORIENTATION_VALUE_ROTATE_270_CW);
}

}

@Test
public void getWithNullExifReturnsHorizontal()
throws ImageReadException, IOException, ImageWriteException {

final List<File> images = getImagesWithExifData();
for (final File imageFile : images) {
final ByteSource byteSource = new ByteSourceFile(imageFile);
final JpegImageMetadata originalMetadata = (JpegImageMetadata) Imaging.getMetadata(imageFile);
assertNotNull(originalMetadata);

final TiffImageMetadata originalExifMetadata = originalMetadata.getExif();
assertNotNull(originalExifMetadata);

final ByteArrayOutputStream baos = new ByteArrayOutputStream();
new ExifRewriter().removeExifMetadata(byteSource, baos);
final byte[] bytes = baos.toByteArray();
final File tempFile = Files.createTempFile("removed", ".jpg").toFile();
FileUtils.writeByteArrayToFile(tempFile, bytes);

assertFalse(hasExifData(tempFile));

final ExifOrientationRewriter rewriter = new ExifOrientationRewriter(tempFile);
short ort = rewriter.getExifOrientation().getVal();

assertEquals(ort, Orientation.HORIZONTAL.getVal());
}

}

@Test
void testSetExifOrientation()
throws IOException, ImageReadException, ImageWriteException {

final List<File> images = getImagesWithExifData();
for (final File imageFile : images) {
ExifOrientationRewriter eor = new ExifOrientationRewriter(imageFile);
ExifOrientationRewriter.Orientation eo = ExifOrientationRewriter.Orientation.ROTATE_180;
eor.setExifOrientation(eo);

ByteSource bs = eor.getOutput();
final JpegImageMetadata newMetadata = (JpegImageMetadata) Imaging.getMetadata(bs.getAll());
final TiffImageMetadata newExifMetadata = newMetadata.getExif();

assertNotNull(newExifMetadata, "The new EXIF metadata is null");
assertEquals(newExifMetadata.getFieldValue(TiffTagConstants.TIFF_TAG_ORIENTATION), Short.valueOf(eo.getVal()), "The orientation field in the EXIF metadata is not set correctly");
}
}

@Test
public void setWithNullExifDoesNotThrow()
throws ImageReadException, IOException, ImageWriteException {

final List<File> images = getImagesWithExifData();
for (final File imageFile : images) {
final ByteSource byteSource = new ByteSourceFile(imageFile);
final JpegImageMetadata originalMetadata = (JpegImageMetadata) Imaging.getMetadata(imageFile);
assertNotNull(originalMetadata);

final TiffImageMetadata originalExifMetadata = originalMetadata.getExif();
assertNotNull(originalExifMetadata);

final ByteArrayOutputStream baos = new ByteArrayOutputStream();
new ExifRewriter().removeExifMetadata(byteSource, baos);
final byte[] bytes = baos.toByteArray();
final File tempFile = Files.createTempFile("removed", ".jpg").toFile();
FileUtils.writeByteArrayToFile(tempFile, bytes);

assertFalse(hasExifData(tempFile));

final ExifOrientationRewriter rewriter = new ExifOrientationRewriter(tempFile);
assertDoesNotThrow(() -> rewriter.setExifOrientation(Orientation.ROTATE_180));

ByteSource bs = rewriter.getOutput();
final JpegImageMetadata newMetadata = (JpegImageMetadata) Imaging.getMetadata(bs.getAll());
final TiffImageMetadata newExifMetadata = newMetadata.getExif();

assertNotNull(newExifMetadata, "The new EXIF metadata is null");
assertEquals(newExifMetadata.getFieldValue(TiffTagConstants.TIFF_TAG_ORIENTATION), Orientation.ROTATE_180.getVal(), "The orientation field in the EXIF metadata is not set correctly");
}
}
}

0 comments on commit 0ab16f7

Please sign in to comment.