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