/*** Pull out (and generate) all desired metadata before rescaling the image ***/
// The CGImageSource for getting the image INTO Quartz
+ // TODO: All the metadata stuff needs to be pulled out into it's own class
CGImageSourceRef source;
// Dictionary to hold all metadata
NSMutableDictionary *metadata;
- // Read the image data into Quartz
- NSURL *url = [NSURL fileURLWithPath: [mExportMgr imagePathAtIndex:i]];
- source = CGImageSourceCreateWithURL((CFURLRef)url, NULL);
-
- // Prepare to get the data OUT of Quartz
- NSData *data = [NSMutableData data];
- CGImageDestinationRef destination = CGImageDestinationCreateWithData((CFMutableDataRef)data, (CFStringRef)@"public.jpeg", 1, NULL);
+ // Read the image into ImageIO (the only API that supports more then just EXIF metadata)
+ // Read it into a NSData object first, since we'll need that later on anyway...
+ NSData *theImageData = [[NSData alloc] initWithContentsOfFile: [mExportMgr imagePathAtIndex:i]];
+ source = CGImageSourceCreateWithData((CFDataRef)theImageData, NULL);
// Get the metadata dictionary, cast it to NSDictionary the get a mutable copy of it
CFDictionaryRef metadataRef = CGImageSourceCopyPropertiesAtIndex(source, 0, NULL);
NSDictionary *immutableMetadata = (NSDictionary *)metadataRef;
metadata = [immutableMetadata mutableCopy];
+
+ // Clean up some stuff we own that we don't need anymore
+ immutableMetadata = nil;
CFRelease(metadataRef);
+ CFRelease(source);
// Get a mutable copy of the IPTC Dictionary for the image...create a
// new one if one doesn't exist in the image.
if (!iptcDict) {
iptcDict = [[NSMutableDictionary alloc] init];
}
+ iptcData = nil;
- // Get the keywords from the image and put it into the dictionary...should we check for them first?
+ // Get the keywords from the image and put it into the dictionary...
+ // TODO: should we check for any existing keywords first?
NSArray *keywords = [mExportMgr imageKeywordsAtIndex: i];
- [iptcDict setObject:keywords forKey:(NSString *)kCGImagePropertyIPTCKeywords];
+ if (keywords) {
+ [iptcDict setObject:keywords forKey:(NSString *)kCGImagePropertyIPTCKeywords];
+ }
- // Put the IPTC Dictionary (back?) into the metadata dictionary
- [metadata setObject:iptcDict forKey:(NSString *)kCGImagePropertyIPTCDictionary];
-
- // Get the data out of quartz (image data is in *data now.
- CGImageDestinationAddImageFromSource(destination, source, 0, (CFDictionaryRef)metadata);
- BOOL success = CGImageDestinationFinalize(destination);
+ // Add the title to the ObjectName field
+ NSString *imageDescription = [mExportMgr imageTitleAtIndex:i];
+ [iptcDict setObject:imageDescription forKey:(NSString *)kCGImagePropertyIPTCObjectName];
- CFRelease(source);
- CFRelease(destination);
+ // Add any ratings...not sure what Ansel will do with them yet, but no harm in including them
+ // eh...seems like quartz mistakenly puts this value into the keywords field???
+ //NSNumber *imageRating = [NSNumber numberWithInt: [mExportMgr imageRatingAtIndex:i]];
+ //[iptcDict setObject:imageRating forKey:(NSString *)kCGImagePropertyIPTCStarRating];
- if (!success) {
- // ??
- }
+ // Add the IPTC Dictionary back into the metadata dictionary....we use this
+ // after the image is scaled.
+ [metadata setObject:iptcDict forKey:(NSString *)kCGImagePropertyIPTCDictionary];
- /*** TODO: This is wasteful, but do it this way for now to just test if this works as expected ***/
- // Need to resize the image, but all metadata is lost...ideally we should only read the image in once though...
- NSData *theImage = [[NSData alloc] initWithContentsOfFile: [mExportMgr imagePathAtIndex:i]];
- //NSData *theImage = (NSData *)data;
- CGFloat imageSize;
+ // Prepare to scale the image now that we have the metadata out of it
+ float imageSize;
switch([mSizePopUp selectedTag])
{
case 0:
break;
}
-
[self postProgressStatus: [NSString stringWithFormat: @"Resizing image %d out of %d", (i+1), count]];
- // Don't resize if we want original image...it will lose some metadata needlessly.
+ // Don't even touch this code if we are uploading the original image
NSData *scaledData;
if ([mSizePopUp selectedTag] != 3) {
- scaledData = [ImageResizer getScaledImageFromData: theImage
- toSize: NSMakeSize(imageSize, imageSize)];
+
+ // Put the image data into CIImage
+ CIImage *im = [CIImage imageWithData: theImageData];
+
+ // Calculate the scale factor and the actual dimensions.
+ float yscale;
+ if([im extent].size.height > [im extent].size.width) {
+ yscale = imageSize / [im extent].size.height;
+ } else {
+ yscale = imageSize / [im extent].size.width;
+ }
+ float finalW = ceilf(yscale * [im extent].size.width);
+ float finalH = ceilf(yscale * [im extent].size.height);
+
+ // Do an affine clamp (This essentially make the image extent
+ // infinite but removes problems with certain image sizes causing
+ // edge artifacts.
+ CIFilter *clamp = [CIFilter filterWithName:@"CIAffineClamp"];
+ [clamp setValue:[NSAffineTransform transform] forKey:@"inputTransform"];
+ [clamp setValue:im forKey:@"inputImage"];
+ im = [clamp valueForKey:@"outputImage"];
+
+ // Now perform the scale
+ CIFilter *f = [CIFilter filterWithName: @"CILanczosScaleTransform"];
+ [f setDefaults];
+ [f setValue:[NSNumber numberWithFloat:yscale]
+ forKey:@"inputScale"];
+ [f setValue:[NSNumber numberWithFloat:1.0]
+ forKey:@"inputAspectRatio"];
+ [f setValue:im forKey:@"inputImage"];
+ im = [f valueForKey:@"outputImage"];
+
+ // Crop back to finite dimensions
+ CIFilter *crop = [CIFilter filterWithName:@"CICrop"];
+ [crop setValue:[CIVector vectorWithX:0.0
+ Y:0.0
+ Z: finalW
+ W: finalH]
+ forKey:@"inputRectangle"];
+
+ [crop setValue: im forKey:@"inputImage"];
+ im = [crop valueForKey:@"outputImage"];
+
+ // Now get the image back out into a NSData object
+ NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithCIImage: im];
+ NSDictionary *properties = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithFloat: 0.9], NSImageCompressionFactor,
+ [NSNumber numberWithInt: 0], NSImageCompressionMethod, nil];
+ scaledData = [bitmap representationUsingType:NSJPEG2000FileType properties:properties];
+
} else {
- scaledData = theImage;
+ scaledData = theImageData;
}
// Now we have resized image data, put back the metadata...
source = CGImageSourceCreateWithData((CFDataRef)scaledData, NULL);
+ NSData *newData = [[NSMutableData alloc] init];
+ CGImageDestinationRef destination = CGImageDestinationCreateWithData((CFMutableDataRef)newData, (CFStringRef)@"public.jpeg", 1, NULL);
- // Should we release, or clear or use a new data object?
- NSData *newData = [NSMutableData data];
- destination = CGImageDestinationCreateWithData((CFMutableDataRef)newData, (CFStringRef)@"public.jpeg", 1, NULL);
-
- // Get the data out of quartz (image data is in the NSData *data object now.
- CGImageDestinationAddImageFromSource(destination, source, 0, (CFDictionaryRef)metadata);
- success = CGImageDestinationFinalize(destination); // write metadata into the data object
-
+ // Get the data out of quartz (image data is in the NSData *newData object now.
+ CGImageDestinationAddImageFromSource(destination, source, 0, (CFDictionaryRef)metadata);
+ CGImageDestinationFinalize(destination);
[self postProgressStatus: [NSString stringWithFormat: @"Encoding image %d out of %d", (i+1), count]];
-// NSString *base64ImageData = [NSString base64StringFromData: scaledData
-// length: [scaledData length]];
NSString *base64ImageData = [NSString base64StringFromData: newData
length: [newData length]];
+ [newData release];
+ [theImageData release];
// Get the filename/path for this image. This returns either the most
// recent version of the image, the original, or (if RAW) the jpeg
// version of the original.
NSString *filename = [mExportMgr imageFileNameAtIndex:i];
- NSString *imageDescription = [mExportMgr imageTitleAtIndex:i];
- // NSArray *keywords = [mExportMgr imageKeywordsAtIndex: i];
NSArray *keys = [[NSArray alloc] initWithObjects:
@"filename", @"description", @"data", @"type", @"tags", nil];
keywords,
nil];
- NSDictionary *imageData = [[NSDictionary alloc] initWithObjects:values
- forKeys:keys];
+ NSDictionary *imageDataDict = [[NSDictionary alloc] initWithObjects:values
+ forKeys:keys];
NSDictionary *params = [[NSDictionary alloc] initWithObjectsAndKeys:
- imageData, @"data",
+ imageDataDict, @"data",
[NSNumber numberWithBool:NO], @"default",
nil];
[currentGallery uploadImageObject: params];
[keys release];
[values release];
- [imageData release];
+ [imageDataDict release];
[params release];
[iptcDict release];
[pool release];
8D5B49B4048680CD000E48DA /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7ADFEA557BF11CA2CBB /* Cocoa.framework */; };
B00EF55A0EF5DD5900A9D71C /* AnselServers.nib in Resources */ = {isa = PBXBuildFile; fileRef = B00EF5580EF5DD5900A9D71C /* AnselServers.nib */; };
B00EF5670EF5E22900A9D71C /* TURAnselServersPanelController.m in Sources */ = {isa = PBXBuildFile; fileRef = B00EF5660EF5E22900A9D71C /* TURAnselServersPanelController.m */; };
- B03B92BC0FB5F43600F628AF /* TURAnselGalleryNode.m in Sources */ = {isa = PBXBuildFile; fileRef = B03B92BB0FB5F43600F628AF /* TURAnselGalleryNode.m */; };
B04FC1A90EEB4A2B008EEB0E /* AnselGalleryPanel.nib in Resources */ = {isa = PBXBuildFile; fileRef = B04FC1A70EEB4A2B008EEB0E /* AnselGalleryPanel.nib */; };
- B05C4A060EE9E001005B4B28 /* ImageResizer.m in Sources */ = {isa = PBXBuildFile; fileRef = B05C4A050EE9E001005B4B28 /* ImageResizer.m */; };
B06C1E030EB1644600BFAFCB /* AnselExportPluginBox.m in Sources */ = {isa = PBXBuildFile; fileRef = B06C1E020EB1644600BFAFCB /* AnselExportPluginBox.m */; };
B06C1E060EB164D900BFAFCB /* AnselExportController.m in Sources */ = {isa = PBXBuildFile; fileRef = B06C1E050EB164D900BFAFCB /* AnselExportController.m */; };
B06C1E3D0EB17E3700BFAFCB /* Panel.nib in Resources */ = {isa = PBXBuildFile; fileRef = B06C1E3C0EB17E3700BFAFCB /* Panel.nib */; };
B0C888490ED85DEA000E19FB /* ProgressSheet.nib in Resources */ = {isa = PBXBuildFile; fileRef = B0C888480ED85DEA000E19FB /* ProgressSheet.nib */; };
B0C8884E0ED85E02000E19FB /* FBProgressController.m in Sources */ = {isa = PBXBuildFile; fileRef = B0C8884D0ED85E02000E19FB /* FBProgressController.m */; };
B0CCED420EEC6E810012D3D3 /* TURAnselGalleryPanelController.m in Sources */ = {isa = PBXBuildFile; fileRef = B0CCED410EEC6E810012D3D3 /* TURAnselGalleryPanelController.m */; };
- B0DF843C0EE9E6EB000DAA9E /* QuickTime.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B0DF843B0EE9E6EB000DAA9E /* QuickTime.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
B00EF5590EF5DD5900A9D71C /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/AnselServers.nib; sourceTree = "<group>"; };
B00EF5650EF5E22900A9D71C /* TURAnselServersPanelController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TURAnselServersPanelController.h; sourceTree = "<group>"; };
B00EF5660EF5E22900A9D71C /* TURAnselServersPanelController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TURAnselServersPanelController.m; sourceTree = "<group>"; };
- B03B92BA0FB5F43600F628AF /* TURAnselGalleryNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TURAnselGalleryNode.h; sourceTree = "<group>"; };
- B03B92BB0FB5F43600F628AF /* TURAnselGalleryNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TURAnselGalleryNode.m; sourceTree = "<group>"; };
B03D3B590ED5BB3800CF5B92 /* XMLRPC-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "XMLRPC-Info.plist"; sourceTree = SOURCE_ROOT; };
B04FC1A80EEB4A2B008EEB0E /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/AnselGalleryPanel.nib; sourceTree = "<group>"; };
- B05C4A040EE9E001005B4B28 /* ImageResizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageResizer.h; sourceTree = "<group>"; };
- B05C4A050EE9E001005B4B28 /* ImageResizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ImageResizer.m; sourceTree = "<group>"; };
B06C1E010EB1644600BFAFCB /* AnselExportPluginBox.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AnselExportPluginBox.h; sourceTree = "<group>"; };
B06C1E020EB1644600BFAFCB /* AnselExportPluginBox.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AnselExportPluginBox.m; sourceTree = "<group>"; };
B06C1E040EB164D900BFAFCB /* AnselExportController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AnselExportController.h; sourceTree = "<group>"; };
B0C8884D0ED85E02000E19FB /* FBProgressController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBProgressController.m; sourceTree = "<group>"; };
B0CCED400EEC6E810012D3D3 /* TURAnselGalleryPanelController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TURAnselGalleryPanelController.h; sourceTree = "<group>"; };
B0CCED410EEC6E810012D3D3 /* TURAnselGalleryPanelController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TURAnselGalleryPanelController.m; sourceTree = "<group>"; };
- B0DF843B0EE9E6EB000DAA9E /* QuickTime.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickTime.framework; path = /System/Library/Frameworks/QuickTime.framework; sourceTree = "<absolute>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
files = (
B0BFBC780ED5B2AB006581A5 /* XMLRPC.framework in Frameworks */,
8D5B49B4048680CD000E48DA /* Cocoa.framework in Frameworks */,
- B0DF843C0EE9E6EB000DAA9E /* QuickTime.framework in Frameworks */,
B0B666750FB34604009459D5 /* Quartz.framework in Frameworks */,
B0B666760FB34604009459D5 /* QuartzCore.framework in Frameworks */,
);
children = (
B0B666730FB34604009459D5 /* Quartz.framework */,
B0B666740FB34604009459D5 /* QuartzCore.framework */,
- B0DF843B0EE9E6EB000DAA9E /* QuickTime.framework */,
089C1672FE841209C02AAC07 /* Foundation.framework */,
1058C7ADFEA557BF11CA2CBB /* Cocoa.framework */,
);
32C88E010371C26100C91783 /* Other Sources */ = {
isa = PBXGroup;
children = (
- B05C4A040EE9E001005B4B28 /* ImageResizer.h */,
- B05C4A050EE9E001005B4B28 /* ImageResizer.m */,
B0C8884D0ED85E02000E19FB /* FBProgressController.m */,
B0C8884C0ED85E02000E19FB /* FBProgressController.h */,
B07D44F50EC2AEC700B59765 /* TURXMLConnection.h */,
B07D426F0EC230B100B59765 /* TURAnselGallery.m */,
B00EF5650EF5E22900A9D71C /* TURAnselServersPanelController.h */,
B00EF5660EF5E22900A9D71C /* TURAnselServersPanelController.m */,
- B03B92BA0FB5F43600F628AF /* TURAnselGalleryNode.h */,
- B03B92BB0FB5F43600F628AF /* TURAnselGalleryNode.m */,
);
name = AnselToolkit;
sourceTree = "<group>";
B07D42710EC230B100B59765 /* TURAnselGallery.m in Sources */,
B07D44F70EC2AEC700B59765 /* TURXMLConnection.m in Sources */,
B0C8884E0ED85E02000E19FB /* FBProgressController.m in Sources */,
- B05C4A060EE9E001005B4B28 /* ImageResizer.m in Sources */,
B0CCED420EEC6E810012D3D3 /* TURAnselGalleryPanelController.m in Sources */,
B00EF5670EF5E22900A9D71C /* TURAnselServersPanelController.m in Sources */,
B0B6669B0FB357B3009459D5 /* AnselGalleryViewItem.m in Sources */,
- B03B92BC0FB5F43600F628AF /* TURAnselGalleryNode.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};