package jnpf.fileStorage.dms; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.Raster; import java.awt.image.WritableRaster; final class DmsJpegExifOrientation { private static final int JPEG_MARKER_PREFIX = 0xFF; private static final int JPEG_START_OF_IMAGE = 0xD8; private static final int JPEG_END_OF_IMAGE = 0xD9; private static final int JPEG_START_OF_SCAN = 0xDA; private static final int JPEG_APP1 = 0xE1; private static final int EXIF_ORIENTATION_TAG = 0x0112; private static final int TIFF_SHORT = 3; private DmsJpegExifOrientation() { } static int readOrientation(byte[] jpeg) { if (jpeg == null || jpeg.length < 4 || unsigned(jpeg[0]) != JPEG_MARKER_PREFIX || unsigned(jpeg[1]) != JPEG_START_OF_IMAGE) { return 1; } int offset = 2; while (offset < jpeg.length) { if (unsigned(jpeg[offset]) != JPEG_MARKER_PREFIX) { return 1; } while (offset < jpeg.length && unsigned(jpeg[offset]) == JPEG_MARKER_PREFIX) { offset++; } if (offset >= jpeg.length) { return 1; } int marker = unsigned(jpeg[offset++]); if (marker == JPEG_END_OF_IMAGE || marker == JPEG_START_OF_SCAN) { return 1; } if (marker == JPEG_START_OF_IMAGE || marker == 0x01 || marker >= 0xD0 && marker <= 0xD7) { continue; } if (!fits(offset, 2, jpeg.length)) { return 1; } int segmentLength = readUnsignedShortBigEndian(jpeg, offset); if (segmentLength < 2 || !fits(offset, segmentLength, jpeg.length)) { return 1; } if (marker == JPEG_APP1) { int orientation = readExifOrientation(jpeg, offset + 2, segmentLength - 2); if (orientation != 0) { return orientation; } } offset += segmentLength; } return 1; } static BufferedImage normalize(BufferedImage source, int orientation) { if (source == null || orientation < 2 || orientation > 8) { return source; } int sourceWidth = source.getWidth(); int sourceHeight = source.getHeight(); boolean swapsAxes = orientation >= 5; int targetWidth = swapsAxes ? sourceHeight : sourceWidth; int targetHeight = swapsAxes ? sourceWidth : sourceHeight; ColorModel colorModel = source.getColorModel(); WritableRaster targetRaster = colorModel.createCompatibleWritableRaster(targetWidth, targetHeight); BufferedImage target = new BufferedImage(colorModel, targetRaster, colorModel.isAlphaPremultiplied(), null); Raster sourceRaster = source.getRaster(); Object pixel = null; for (int sourceY = 0; sourceY < sourceHeight; sourceY++) { for (int sourceX = 0; sourceX < sourceWidth; sourceX++) { int targetX; int targetY; switch (orientation) { case 2: targetX = sourceWidth - 1 - sourceX; targetY = sourceY; break; case 3: targetX = sourceWidth - 1 - sourceX; targetY = sourceHeight - 1 - sourceY; break; case 4: targetX = sourceX; targetY = sourceHeight - 1 - sourceY; break; case 5: targetX = sourceY; targetY = sourceX; break; case 6: targetX = sourceHeight - 1 - sourceY; targetY = sourceX; break; case 7: targetX = sourceHeight - 1 - sourceY; targetY = sourceWidth - 1 - sourceX; break; case 8: targetX = sourceY; targetY = sourceWidth - 1 - sourceX; break; default: return source; } pixel = sourceRaster.getDataElements(sourceX, sourceY, pixel); targetRaster.setDataElements(targetX, targetY, pixel); } } return target; } private static int readExifOrientation(byte[] data, int payloadOffset, int payloadLength) { if (payloadLength < 14 || !fits(payloadOffset, payloadLength, data.length) || unsigned(data[payloadOffset]) != 'E' || unsigned(data[payloadOffset + 1]) != 'x' || unsigned(data[payloadOffset + 2]) != 'i' || unsigned(data[payloadOffset + 3]) != 'f' || data[payloadOffset + 4] != 0 || data[payloadOffset + 5] != 0) { return 0; } int tiffOffset = payloadOffset + 6; int tiffLength = payloadLength - 6; boolean littleEndian; if (unsigned(data[tiffOffset]) == 'I' && unsigned(data[tiffOffset + 1]) == 'I') { littleEndian = true; } else if (unsigned(data[tiffOffset]) == 'M' && unsigned(data[tiffOffset + 1]) == 'M') { littleEndian = false; } else { return 0; } if (readUnsignedShort(data, tiffOffset + 2, littleEndian) != 42) { return 0; } long firstIfdRelativeOffset = readUnsignedInt(data, tiffOffset + 4, littleEndian); if (firstIfdRelativeOffset > Integer.MAX_VALUE) { return 0; } int firstIfdOffset = tiffOffset + (int) firstIfdRelativeOffset; int tiffEnd = tiffOffset + tiffLength; if (firstIfdOffset < tiffOffset || !fits(firstIfdOffset, 2, tiffEnd)) { return 0; } int entryCount = readUnsignedShort(data, firstIfdOffset, littleEndian); for (int index = 0; index < entryCount; index++) { long entryOffsetLong = (long) firstIfdOffset + 2L + (long) index * 12L; if (entryOffsetLong > Integer.MAX_VALUE) { return 0; } int entryOffset = (int) entryOffsetLong; if (!fits(entryOffset, 12, tiffEnd)) { return 0; } if (readUnsignedShort(data, entryOffset, littleEndian) != EXIF_ORIENTATION_TAG) { continue; } if (readUnsignedShort(data, entryOffset + 2, littleEndian) != TIFF_SHORT) { return 0; } long valueCount = readUnsignedInt(data, entryOffset + 4, littleEndian); if (valueCount < 1) { return 0; } int valueOffset; if (valueCount <= 2) { valueOffset = entryOffset + 8; } else { long relativeValueOffset = readUnsignedInt(data, entryOffset + 8, littleEndian); if (relativeValueOffset > Integer.MAX_VALUE) { return 0; } valueOffset = tiffOffset + (int) relativeValueOffset; } if (valueOffset < tiffOffset || !fits(valueOffset, 2, tiffEnd)) { return 0; } int orientation = readUnsignedShort(data, valueOffset, littleEndian); return orientation >= 1 && orientation <= 8 ? orientation : 0; } return 0; } private static int readUnsignedShortBigEndian(byte[] data, int offset) { return unsigned(data[offset]) << 8 | unsigned(data[offset + 1]); } private static int readUnsignedShort(byte[] data, int offset, boolean littleEndian) { int first = unsigned(data[offset]); int second = unsigned(data[offset + 1]); return littleEndian ? first | second << 8 : first << 8 | second; } private static long readUnsignedInt(byte[] data, int offset, boolean littleEndian) { long first = unsigned(data[offset]); long second = unsigned(data[offset + 1]); long third = unsigned(data[offset + 2]); long fourth = unsigned(data[offset + 3]); return littleEndian ? first | second << 8 | third << 16 | fourth << 24 : first << 24 | second << 16 | third << 8 | fourth; } private static boolean fits(int offset, int length, int endExclusive) { return offset >= 0 && length >= 0 && (long) offset + length <= endExclusive; } private static int unsigned(byte value) { return value & 0xFF; } }