bensie
bensie

Reputation: 5403

Resize and set quality on JPEG image while retaining EXIF in iOS

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

Answers (4)

Shai Ben-Tovim
Shai Ben-Tovim

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

CGR
CGR

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

Greener Chen
Greener Chen

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

Phil Harvey
Phil Harvey

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

Related Questions