Add JFIF metadata to the EXIF decoder (which is actually more a JPEG metadata decoder) - needed to get the proper DPI info

This commit is contained in:
Airy ANDRE 2014-02-12 19:54:37 +01:00
parent e168c0496f
commit 5a6bbb7b81
2 changed files with 165 additions and 56 deletions

View File

@ -7,6 +7,7 @@
//
#import "O2EXIFDecoder.h"
#import "O2ImageSource.h"
@implementation O2EXIFDecoder
@ -18,6 +19,7 @@ enum {
kEXIF,
kInterop,
kGPS,
kJFIF,
kModes
};
@ -374,6 +376,9 @@ static unsigned long _getLong(const unsigned char*data, size_t offset, bool bige
case kEXIF:
typeKey = @"{Exif}";
break;
case kJFIF:
typeKey = @"{JFIF}";
break;
default:
typeKey = [NSString stringWithFormat:@"{%d}", type];
break;
@ -393,7 +398,8 @@ static unsigned long _getLong(const unsigned char*data, size_t offset, bool bige
info++;
}
if (numberKey == nil) {
numberKey = [NSString stringWithFormat:@"<%d>", tagNumber];
// We'll ignore unexpected tags - but we could add them anyway
// numberKey = [NSString stringWithFormat:@"<%d>", tagNumber];
}
return numberKey;
}
@ -409,6 +415,17 @@ static unsigned long _getLong(const unsigned char*data, size_t offset, bool bige
}
}
- (id)tagForTypeKey:(id)typeKey numberKey:(id)numberKey
{
if (typeKey && numberKey) {
return [[_tags objectForKey:typeKey] objectForKey:numberKey];
} else if (numberKey) {
return [_tags objectForKey:numberKey];
} else {
return nil;
}
}
- (void)setTagForTypeKey:(id)typeKey numberKey:(id)numberKey value:(id)tagValue
{
NSMutableDictionary *dict = _tags;
@ -497,9 +514,9 @@ static unsigned long _getLong(const unsigned char*data, size_t offset, bool bige
}
else {
val = [NSMutableArray arrayWithCapacity:count];
for (int i = 0; i < count; ++i) {
for (int i = 0; i < count; ++i) {
value = _getShort(rawValue, i*2, bigendian);
[val addObject:[NSNumber numberWithShort:value]];
[val addObject:[NSNumber numberWithShort:value]];
}
}
break;
@ -605,6 +622,86 @@ static unsigned long _getLong(const unsigned char*data, size_t offset, bool bige
return _getLong(data, base+offset, bigendian);
}
- (void)_decodeJFIF:(const uint8_t *)bytes length:(int)length
{
if (length < 12) {
return;
}
if (bcmp(bytes, "JFIF\0", 5)) {
return;
}
BOOL bigendian = YES;
size_t offset = 5;
uint8_t versionH = _getByte(bytes, offset, bigendian);
offset += 1;
uint8_t versionL = _getByte(bytes, offset, bigendian);
offset += 1;
uint8_t density = _getByte(bytes, offset, bigendian);
offset += 1;
uint16_t xResolution = _getShort(bytes, offset, bigendian);
offset += 2;
uint16_t yResolution = _getShort(bytes, offset, bigendian);
offset += 2;
id typekey = [self keyForType:kJFIF];
[self setTagForTypeKey:typekey numberKey:@"DensityUnit" value:[NSNumber numberWithInt:density]];
[self setTagForTypeKey:typekey numberKey:@"XDensity" value:[NSNumber numberWithInt:xResolution]];
[self setTagForTypeKey:typekey numberKey:@"YDensity" value:[NSNumber numberWithInt:yResolution]];
[self setTagForTypeKey:typekey numberKey:@"JFIFVersion" value:[NSArray arrayWithObjects:
[NSNumber numberWithInt:versionH],
[NSNumber numberWithInt:versionL],
nil]];
float DPIWidth = 0;
float DPIHeight = 0;
if (density == 1 || density == 2) {
DPIWidth = xResolution;
DPIHeight = yResolution;
if (density == 2) {
// pixels/cm
DPIWidth /= 2.54;
DPIHeight /= 2.54;
}
}
// Set the global DPI settings to the one from the JFIF
if (DPIWidth > 0) {
[self setTagForTypeKey:nil numberKey:kO2ImagePropertyDPIWidth value:[NSNumber numberWithFloat:DPIWidth]];
}
if (DPIHeight > 0) {
[self setTagForTypeKey:nil numberKey:kO2ImagePropertyDPIHeight value:[NSNumber numberWithFloat:DPIHeight]];
}
}
- (void)_decodeEXIF:(const uint8_t *)bytes length:(int)length
{
if (length < 6) {
return;
}
if (bcmp(bytes, "Exif\0\0", 6)) {
return;
}
bytes += 6;
length -= 6;
short endianness = _getShort(bytes, 0, true);
BOOL bigendian = (endianness == 0x4d4d);
short align = _getShort(bytes, 2, bigendian);
if (align != 0x2a) {
return;
}
long offsetIFD0 = _getLong(bytes, 4, bigendian);
long offset = 0;
if (offsetIFD0 && offsetIFD0 != -1) {
offset = [self _readIFD:bytes base:0 offset:offsetIFD0 bigendian:bigendian mode:0];
if (offset && offset != -1) {
long offset2 = [self _readIFD:bytes base:0 offset:offset bigendian:bigendian mode:1];
if (offset2) {
offset = offset2;
}
}
}
}
- (void)_analyze:(const uint8_t *)data length:(size_t)length
{
@ -617,10 +714,8 @@ static unsigned long _getLong(const unsigned char*data, size_t offset, bool bige
return; // ERROR: File is not a JPEG
}
// Scan for the EXIF block
bool done = false;
while (!done) {
// Scan for the EXIF & JFIF blocks
while (1) {
int marker;
// First, skip any non 0xFF bytes
@ -657,61 +752,75 @@ static unsigned long _getLong(const unsigned char*data, size_t offset, bool bige
break;
}
if (marker == 0xE1) {
done = true;
break;
[self _decodeEXIF:data+index length:length];
}
if (marker == 0xE0) {
[self _decodeJFIF:data+index length:length];
}
index += len;
}
if (done) {
const uint8_t* bytes = data+index;
if (length < 6) {
return;
}
if (bcmp(bytes, "Exif\0\0", 6)) {
return;
}
bytes += 6;
length -= 6;
short endianness = _getShort(bytes, 0, true);
BOOL bigendian = (endianness == 0x4d4d);
short align = _getShort(bytes, 2, bigendian);
if (align != 0x2a) {
return;
}
long offsetIFD0 = _getLong(bytes, 4, bigendian);
if (offsetIFD0 && offsetIFD0 != -1) {
long offset = [self _readIFD:bytes base:0 offset:offsetIFD0 bigendian:bigendian mode:0];
if (offset && offset != -1) {
offset = [self _readIFD:bytes base:0 offset:offset bigendian:bigendian mode:1];
// promote some TIFF & EXIF keys to the main level (orientation, dpi, pixel size...)
id value = [self tagForKey:kTIFF number:kTIFF_TIFFOrientation];
if (value) {
[self setTagForTypeKey:nil numberKey:@"Orientation" value:value];
}
// Try to use the EXIF one if none is set by the JFIF block - else, update the EXIF one so
// everything is consistent - that's what Quartz is doing
value = [self tagForKey:kTIFF number:kTIFF_TIFFXResolution];
if (value) {
id currentValue = [self tagForTypeKey:nil numberKey:kO2ImagePropertyDPIWidth];
if (currentValue == nil) {
int unit = [[self tagForKey:kTIFF number:kTIFF_TIFFResolutionUnit] intValue];
if (unit == 2 || unit == 3) {
float dpi = [value floatValue];
if (unit == 3) {
// pixels/cm
dpi /= 2.54;
}
[self setTagForTypeKey:nil numberKey:kO2ImagePropertyDPIWidth value:[NSNumber numberWithFloat:dpi]];
}
} else {
// Update the TIFF info
[self setTagForType:kTIFF number:kTIFF_TIFFXResolution value:currentValue];
// Set the resolution unit to inch
[self setTagForType:kTIFF number:kTIFF_TIFFResolutionUnit value:[NSNumber numberWithInt:2]];
}
// promote some TIFF & EXIF keys to the main level (orientation, dpi, pixel size...)
id value = [self tagForKey:kTIFF number:kTIFF_TIFFOrientation];
if (value) {
[self setTagForTypeKey:nil numberKey:@"Orientation" value:value];
}
value = [self tagForKey:kTIFF number:kTIFF_TIFFXResolution];
if (value) {
[self setTagForTypeKey:nil numberKey:@"DPIWidth" value:value];
}
value = [self tagForKey:kTIFF number:kTIFF_TIFFYResolution];
if (value) {
[self setTagForTypeKey:nil numberKey:@"DPIHeight" value:value];
}
value = [self tagForKey:kEXIF number:kEXIF_PixelXDimension];
if (value) {
[self setTagForTypeKey:nil numberKey:@"PixelWidth" value:value];
}
value = [self tagForKey:kEXIF number:kEXIF_PixelYDimension];
if (value) {
[self setTagForTypeKey:nil numberKey:@"PixelHeight" value:value];
}
value = [self tagForKey:kTIFF number:kTIFF_TIFFYResolution];
if (value) {
id currentValue = [self tagForTypeKey:0 numberKey:kO2ImagePropertyDPIHeight];
if (currentValue == nil) {
int unit = [[self tagForKey:kTIFF number:kTIFF_TIFFResolutionUnit] intValue];
if (unit == 2 || unit == 3) {
float dpi = [value floatValue];
if (unit == 3) {
// pixels/cm
dpi /= 2.54;
}
[self setTagForTypeKey:nil numberKey:kO2ImagePropertyDPIHeight value:[NSNumber numberWithFloat:dpi]];
}
} else {
// Update the TIFF info
[self setTagForType:kTIFF number:kTIFF_TIFFYResolution value:currentValue];
// Set the resolution unit to inch
[self setTagForType:kTIFF number:kTIFF_TIFFResolutionUnit value:[NSNumber numberWithInt:2]];
}
}
value = [self tagForKey:kEXIF number:kEXIF_PixelXDimension];
if (value) {
[self setTagForTypeKey:nil numberKey:kO2ImagePropertyPixelWidth value:value];
}
value = [self tagForKey:kEXIF number:kEXIF_PixelYDimension];
if (value) {
[self setTagForTypeKey:nil numberKey:kO2ImagePropertyPixelHeight value:value];
}
}
- (id)initWithBytes:(const uint8_t *)s length:(size_t)length

View File

@ -11,8 +11,8 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
#import <Onyx2D/O2DataProvider.h>
#import <Onyx2D/O2Exceptions.h>
NSString *kO2ImagePropertyDPIWidth=@"kCGImagePropertyDPIWidth";
NSString *kO2ImagePropertyDPIHeight=@"kCGImagePropertyDPIHeight";
NSString *kO2ImagePropertyDPIWidth=@"DPIWidth";
NSString *kO2ImagePropertyDPIHeight=@"DPIHeight";
NSString *kO2ImagePropertyPixelHeight=@"PixelHeight";
NSString *kO2ImagePropertyPixelWidth=@"PixelWidth";
NSString *kO2ImagePropertyOrientation=@"Orientation";