package jnpf.fileStorage.dms; import org.junit.Test; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.Arrays; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; public class DmsJpegExifOrientationTest { @Test public void readsAndNormalizesAllEightExifOrientations() throws Exception { String[][] expected = { null, {"ABC", "DEF"}, {"CBA", "FED"}, {"FED", "CBA"}, {"DEF", "ABC"}, {"AD", "BE", "CF"}, {"DA", "EB", "FC"}, {"FC", "EB", "DA"}, {"CF", "BE", "AD"} }; BufferedImage source = pattern(); byte[] jpeg = jpeg(); for (int orientation = 1; orientation <= 8; orientation++) { byte[] tagged = withExifOrientation(jpeg, orientation, orientation % 2 == 0); assertNotNull(ImageIO.read(new ByteArrayInputStream(tagged))); int parsed = DmsJpegExifOrientation.readOrientation(tagged); assertEquals(orientation, parsed); assertGrid(expected[orientation], DmsJpegExifOrientation.normalize(source, parsed)); } } @Test public void defaultsToOneForMissingOrMalformedExif() throws Exception { byte[] jpeg = jpeg(); byte[] invalidOrientation = withExifOrientation(jpeg, 9, true); byte[] truncated = Arrays.copyOf(withExifOrientation(jpeg, 6, false), 14); assertEquals(1, DmsJpegExifOrientation.readOrientation(null)); assertEquals(1, DmsJpegExifOrientation.readOrientation(new byte[]{1, 2, 3})); assertEquals(1, DmsJpegExifOrientation.readOrientation(jpeg)); assertEquals(1, DmsJpegExifOrientation.readOrientation(invalidOrientation)); assertEquals(1, DmsJpegExifOrientation.readOrientation(truncated)); } @Test public void returnsOriginalImageForIdentityInvalidAndNullInputs() { BufferedImage source = pattern(); assertSame(source, DmsJpegExifOrientation.normalize(source, 1)); assertSame(source, DmsJpegExifOrientation.normalize(source, 0)); assertSame(source, DmsJpegExifOrientation.normalize(source, 9)); assertNull(DmsJpegExifOrientation.normalize(null, 6)); } private BufferedImage pattern() { BufferedImage image = new BufferedImage(3, 2, BufferedImage.TYPE_INT_ARGB); String[] rows = {"ABC", "DEF"}; for (int y = 0; y < rows.length; y++) { for (int x = 0; x < rows[y].length(); x++) { image.setRGB(x, y, color(rows[y].charAt(x))); } } return image; } private void assertGrid(String[] expected, BufferedImage image) { assertEquals(expected[0].length(), image.getWidth()); assertEquals(expected.length, image.getHeight()); for (int y = 0; y < expected.length; y++) { for (int x = 0; x < expected[y].length(); x++) { assertEquals("pixel at " + x + "," + y, color(expected[y].charAt(x)), image.getRGB(x, y)); } } } private int color(char label) { return 0xFF000000 | label * 0x00010101; } private byte[] jpeg() throws Exception { BufferedImage image = new BufferedImage(4, 3, BufferedImage.TYPE_INT_RGB); ByteArrayOutputStream output = new ByteArrayOutputStream(); ImageIO.write(image, "jpeg", output); return output.toByteArray(); } private byte[] withExifOrientation(byte[] jpeg, int orientation, boolean littleEndian) { byte[] payload = new byte[32]; payload[0] = 'E'; payload[1] = 'x'; payload[2] = 'i'; payload[3] = 'f'; int tiff = 6; payload[tiff] = littleEndian ? (byte) 'I' : (byte) 'M'; payload[tiff + 1] = payload[tiff]; putShort(payload, tiff + 2, 42, littleEndian); putInt(payload, tiff + 4, 8, littleEndian); putShort(payload, tiff + 8, 1, littleEndian); int entry = tiff + 10; putShort(payload, entry, 0x0112, littleEndian); putShort(payload, entry + 2, 3, littleEndian); putInt(payload, entry + 4, 1, littleEndian); putShort(payload, entry + 8, orientation, littleEndian); int segmentLength = payload.length + 2; byte[] result = new byte[jpeg.length + payload.length + 4]; result[0] = jpeg[0]; result[1] = jpeg[1]; result[2] = (byte) 0xFF; result[3] = (byte) 0xE1; result[4] = (byte) (segmentLength >>> 8); result[5] = (byte) segmentLength; System.arraycopy(payload, 0, result, 6, payload.length); System.arraycopy(jpeg, 2, result, 6 + payload.length, jpeg.length - 2); return result; } private void putShort(byte[] data, int offset, int value, boolean littleEndian) { if (littleEndian) { data[offset] = (byte) value; data[offset + 1] = (byte) (value >>> 8); } else { data[offset] = (byte) (value >>> 8); data[offset + 1] = (byte) value; } } private void putInt(byte[] data, int offset, int value, boolean littleEndian) { if (littleEndian) { data[offset] = (byte) value; data[offset + 1] = (byte) (value >>> 8); data[offset + 2] = (byte) (value >>> 16); data[offset + 3] = (byte) (value >>> 24); } else { data[offset] = (byte) (value >>> 24); data[offset + 1] = (byte) (value >>> 16); data[offset + 2] = (byte) (value >>> 8); data[offset + 3] = (byte) value; } } }