Search code examples
iosavfoundationcmsamplebufferref

Resizing CMSampleBufferRef provided by captureStillImageBracketAsynchronouslyFromConnection:withSettingsArray:completionHandler:


In the app I'm working on, we're capturing photos which need to have 4:3 aspect ratio in order to maximize the field of view we capture. Up untill now we were using AVCaptureSessionPreset640x480 preset, but now we're in need of larger resolution.

As far as I've figured, the only other two 4:3 formats are 2592x1936 and 3264x2448. Since these are too large for our use case, I need a way to downsize them. I looked into a bunch of options but did not find a way (prefereably without copying the data) to do this in an efficient manner without losing the exif data.

vImage was one of the things I looked into but as far as I've figured the data would need to be coppied and the exif data would be lost. Another option was creating an UIImage from data provided by jpegStillImageNSDataRepresentation, scaling it and getting the data back. This approach also seems to strip the exif data.

The ideal approach here would be resizing the buffer contents directly and resizing the photo. Does anyone have an idea how I would go about doing this?


Solution

  • I ended up using ImageIO for resizing purposes. Leaving this piece of code here in case someone runs into the same problem, as I've spent way too much time on this.

    This code will preserve the exif data, but will create a copy of the image data. I ran some benchmarks - the execution time for this method is ~0.05sec on iPhone6, using AVCaptureSessionPresetPhoto as the preset for the original photo.

    If someone does have a more optimal solution, please leave a comment.

    - (NSData *)resizeJpgData:(NSData *)jpgData
    {
        CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)jpgData, NULL);
    
        // Create a copy of the metadata that we'll attach to the resized image
        NSDictionary *metadata = (NSDictionary *)CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(source, 0, NULL));
        NSMutableDictionary *metadataAsMutable = [metadata mutableCopy];
    
        // Type of the image (e.g. public.jpeg)
        CFStringRef UTI = CGImageSourceGetType(source);
    
        NSDictionary *options = @{ (id)kCGImageSourceCreateThumbnailFromImageIfAbsent: (id)kCFBooleanTrue,
                                   (id)kCGImageSourceThumbnailMaxPixelSize: @(MAX(FORMAT_WIDTH, FORMAT_HEIGHT)),
                                   (id)kCGImageSourceTypeIdentifierHint: (__bridge NSString *)UTI };
        CGImageRef resizedImage = CGImageSourceCreateThumbnailAtIndex(source, 0, (CFDictionaryRef)options);
    
        NSMutableData *destData = [NSMutableData data];
        CGImageDestinationRef destination = CGImageDestinationCreateWithData((CFMutableDataRef)destData, UTI, 1, NULL);
        if (!destination) {
            NSLog(@"Could not create image destination");
        }
    
        CGImageDestinationAddImage(destination, resizedImage, (__bridge CFDictionaryRef) metadataAsMutable);
    
        // Tell the destination to write the image data and metadata into our data object
        BOOL success = CGImageDestinationFinalize(destination);
        if (!success) {
            NSLog(@"Could not create data from image destination");
        }
    
        if (destination) {
            CFRelease(destination);
        }
        CGImageRelease(resizedImage);
        CFRelease(source);
    
        return destData;
    }