Reputation: 5403
In an iOS app, I have an NSData
object that is a JPEG file that needs to be resized to a given resolution (2048x2048) and needs the JPEG quality set to 75%. These need to be set while retaining EXIF data in the file. The photo is not in the camera roll -- it was pulled over the network from a DSLR camera and is just temporarily stored with the app. If the image takes a trip through UIImage, the EXIF data is lost. How can I perform a resize and set quality without losing EXIF? Or is there a way to strip the EXIF data before the conversion and a way to add it back when done?
Upvotes: 2
Views: 3933
Reputation: 932
Upvoted Greener Chen's answer but this is how I implemented it in Swift 3. Few comments:
My function takes a jpeg buffer and return a jpeg buffer. It also checks whether any resizing is required (checking max dimension of source) and simply returns the input buffer if there's nothing to do here. You might want to adapt to your use case
I don't think you can set a destination property like kCGImageDestinationLossyCompressionQuality
to CGImageSourceCreateThumbnailAtIndex()
which is a CGImageSource
function - but never tried it.
Please note that compression is added to the destination image by adding the relevant key (kCGImageDestinationLossyCompressionQuality
)to the retained set of source metadata as an option for CGImageDestinationAddImage
What confused me initially was that although we're providing the full source image metadata to the resized destination image, CGImageDestinationAddImage()
is smart enough to ignore any dimension data (W+H) of the source image and replace it automatically with the correct, resized image, dimensions. So PixelHeight
,PixelWidth
("root" metadata) & PixelXDimension
& PixelYDimension
(EXIF) are not carried over from the source and are set properly to the resized image dimensions.
class func resizeImage(imageData: Data, maxResolution: Int, compression: CGFloat) -> Data? {
// create image source from jpeg data
if let myImageSource = CGImageSourceCreateWithData(imageData as CFData, nil) {
// get source properties so we retain metadata (EXIF) for the downsized image
if var metaData = CGImageSourceCopyPropertiesAtIndex(myImageSource,0, nil) as? [String:Any],
let width = metaData[kCGImagePropertyPixelWidth as String] as? Int, let height = metaData[kCGImagePropertyPixelHeight as String] as? Int {
let srcMaxResolution = max(width, height)
// if max resolution is exceeded, then scale image to new resolution
if srcMaxResolution >= maxResolution {
let scaleOptions = [ kCGImageSourceThumbnailMaxPixelSize as String : maxResolution,
kCGImageSourceCreateThumbnailFromImageAlways as String : true] as [String : Any]
if let scaledImage = CGImageSourceCreateThumbnailAtIndex(myImageSource, 0, scaleOptions as CFDictionary) {
// add compression ratio to desitnation options
metaData[kCGImageDestinationLossyCompressionQuality as String] = compression
//create new jpeg
let newImageData = NSMutableData()
if let cgImageDestination = CGImageDestinationCreateWithData(newImageData, "public.jpeg" as CFString, 1, nil) {
CGImageDestinationAddImage(cgImageDestination, scaledImage, metaData as CFDictionary)
CGImageDestinationFinalize(cgImageDestination)
return newImageData as Data
}
}
}
}
}
return nil
}
}
Upvotes: 1
Reputation: 370
I faced the same problem, now I can upload files with EXIF data, also you can compress photo if need it, this solved the issue for me:
// Get your image.
NSURL *url = @"http://somewebsite.com/path/to/some/image.jpg";
UIImage *loImgPhoto = [NSData dataWithContentsOfURL:url];
// Get your metadata (includes the EXIF data).
CGImageSourceRef loImageOriginalSource = CGImageSourceCreateWithData(( CFDataRef) loDataFotoOriginal, NULL);
NSDictionary *loDicMetadata = (__bridge NSDictionary *) CGImageSourceCopyPropertiesAtIndex(loImageOriginalSource, 0, NULL);
// Set your compression quality (0.0 to 1.0).
NSMutableDictionary *loDicMutableMetadata = [loDicMetadata mutableCopy];
[loDicMutableMetadata setObject:@(lfCompressionQualityValue) forKey:(__bridge NSString *)kCGImageDestinationLossyCompressionQuality];
// Create an image destination.
NSMutableData *loNewImageDataWithExif = [NSMutableData data];
CGImageDestinationRef loImgDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)loNewImageDataWithExif, CGImageSourceGetType(loImageOriginalSource), 1, NULL);
// Add your image to the destination.
CGImageDestinationAddImage(loImgDestination, loImgPhoto.CGImage, (__bridge CFDictionaryRef) loDicMutableMetadata);
// Finalize the destination.
if (CGImageDestinationFinalize(loImgDestination))
{
NSLog(@"Successful image creation.");
// process the image rendering, adjustment data creation and finalize the asset edit.
//Upload photo with EXIF metadata
[self myUploadMethod:loNewImageDataWithExif];
}
else
{
NSLog(@"Error -> failed to finalize the image.");
}
CFRelease(loImageOriginalSource);
CFRelease(loImgDestination);
Upvotes: 0
Reputation: 81
You may try CGImageSourceCreateThumbnailAtIndex and CGImageSourceCopyPropertiesAtIndex to resize a NSData object that is a jpeg image without losing EXIF.
I'm inspired from Problem setting exif data for an image
The following is my code for the same purpose.
Cheers
+ (NSData *)JPEGRepresentationSavedMetadataWithImage:(NSData *)imageData compressionQuality:(CGFloat)compressionQuality maxSize:(CGFloat)maxSize
{
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
CFDictionaryRef options = (__bridge CFDictionaryRef)@{(id)kCGImageSourceCreateThumbnailWithTransform: (id)kCFBooleanTrue,
(id)kCGImageSourceCreateThumbnailFromImageIfAbsent: (id)kCFBooleanTrue,
(id)kCGImageSourceThumbnailMaxPixelSize: [NSNumber numberWithDouble: maxSize], // The maximum width and height in pixels of a thumbnail
(id)kCGImageDestinationLossyCompressionQuality: [NSNumber numberWithDouble:compressionQuality]};
CGImageRef thumbnail = CGImageSourceCreateThumbnailAtIndex(source, 0, options); // Create scaled image
CFStringRef UTI = kUTTypeJPEG;
NSMutableData *destData = [NSMutableData data];
CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)destData, UTI, 1, NULL);
if (!destination) {
NSLog(@"Failed to create image destination");
}
CGImageDestinationAddImage(destination, thumbnail, CGImageSourceCopyPropertiesAtIndex(source, 0, NULL)); // copy all metadata in source to destination
if (!CGImageDestinationFinalize(destination)) {
NSLog(@"Failed to create data from image destination");
}
CFRelease(destination);
CFRelease(source);
CFRelease(thumbnail);
return [destData copy];
}
Upvotes: 5
Reputation: 400
You can use a utility like ExifTool to strip and restore the EXIF. This is a cross-platform command-line utility. Below are the appropriate commands to do what you want.
To remove the EXIF:
exiftool -exif= image.jpg
To restore the EXIF again after editing the image:
exiftool -tagsfromfile image.jpg_original -exif image.jpg
In this example I have taken advantage of the "_original" backup automatically generated by ExifTool.
Upvotes: -3