在Objective-C中去除所有Exif数据

7
如何使用objective-c去除UIImage中的所有exif数据?我已经能够通过以下方式获取exif数据:
NSData* pngData =  UIImagePNGRepresentation(image);
CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)pngData, NULL);

NSDictionary* dic   =   nil;
if ( NULL == imageSource )
{
    #ifdef _DEBUG
    CGImageSourceStatus status = CGImageSourceGetStatus ( source );
    NSLog ( @"Error: file name : %@ - Status: %d", file, status );
    #endif
}
else
{
    CFDictionaryRef propertyRef = CGImageSourceCopyPropertiesAtIndex ( imageSource, 0, NULL );
    CGImageMetadataRef metadataRef = CGImageSourceCopyMetadataAtIndex ( imageSource, 0, NULL );
    // CFDictionaryRef metadataRef = CFDictionaryGetValue(imageProperties, kCGImagePropertyExifDictionary);
    if (metadataRef) {
        NSDictionary* immutableMetadata = (NSDictionary *)metadataRef;
        if ( immutableMetadata ) {
            dic = [ NSDictionary dictionaryWithDictionary : (NSDictionary *)metadataRef ];
        }   
        CFRelease ( metadataRef );
    }
    CFRelease(imageSource);
    imageSource = nil;
}


return dic;
2个回答

14

一些想法:

  1. 通常,从NSData或文件内容加载图像到UIImage中,再使用UIImagePNGRepresentation重新提取数据并将其保存回NSData中,将会剥离图像的元数据。

    然而,这种简单的技术也有其缺点。特别是,强制将其转换为PNG表示可能会大幅影响输出NSData的大小。例如,如果原始图像是压缩的JPEG(如由相机捕获),则生成的PNG文件实际上可能比原始JPEG大得多。

    一般来说,我更喜欢获取原始数据(如果是文档或包中的文件,则直接将其加载到NSData中;如果是从ALAssetsLibrary检索到的内容,则会检索ALAsset,从中转换为ALAssetRepresentation,然后使用getBytes来获取原始资产的二进制表示)。这避免了通过UIImage进行往返转换(特别是如果随后使用UIImagePNGRepresentationUIImageJEPGRepresentation)。

  2. 如果您想去除exif数据,可以:

    • 从原始NSData创建一个图像源;

    • 创建一个图像目标,使用相同的“图像数量”(几乎总是1)和“类型”;

    • 在将图像从源复制到目标时,告诉它应设置适当的键(kCGImagePropertyExifDictionarykCGImagePropertyGPSDictionary应设置为kCFNull),根据CGImageDestinationAddImageFromSource文档,这就是您在目标中指定这些属性应该被删除(如果它们出现在源中)的方法。

    因此,代码可能如下:

    - (NSData *)dataByRemovingExif:(NSData *)data
    {
        CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)data, NULL);
        NSMutableData *mutableData = nil;
    
        if (source) {
            CFStringRef type = CGImageSourceGetType(source);
            size_t count = CGImageSourceGetCount(source);
            mutableData = [NSMutableData data];
    
            CGImageDestinationRef destination = CGImageDestinationCreateWithData((CFMutableDataRef)mutableData, type, count, NULL);
    
            NSDictionary *removeExifProperties = @{(id)kCGImagePropertyExifDictionary: (id)kCFNull,
                                                   (id)kCGImagePropertyGPSDictionary : (id)kCFNull};
    
            if (destination) {
                for (size_t index = 0; index < count; index++) {
                    CGImageDestinationAddImageFromSource(destination, source, index, (__bridge CFDictionaryRef)removeExifProperties);
                }
    
                if (!CGImageDestinationFinalize(destination)) {
                    NSLog(@"CGImageDestinationFinalize failed");
                }
    
                CFRelease(destination);
            }
    
            CFRelease(source);
        }
    
        return mutableData;
    }
    

    请注意,GPS信息不是技术上的exif数据,但我假设您也想删除它。如果您想保留GPS数据,请从我的removeExifProperties字典中删除kCGImagePropertyGPSDictionary条目。

    顺便说一句,在提取元数据的代码中,您似乎将CGImageMetadataRef转换为NSDictionary。如果您的技术有效,那就没问题了,但我认为CGImageMetadataRef被认为是一个不透明的数据类型,应该使用CGImageMetadataCopyTags来提取标签数组:

    - (NSArray *)metadataForData:(NSData *)data
    {
        NSArray *metadataArray = nil;
        CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)data, NULL);
    
        if (source) {
            CGImageMetadataRef metadata = CGImageSourceCopyMetadataAtIndex(source, 0, NULL);
            if (metadata) {
                metadataArray = CFBridgingRelease(CGImageMetadataCopyTags(metadata));
                CFRelease(metadata);
            }
            CFRelease(source);
        }
    
        return metadataArray;
    }
    

    为了完整起见,在iOS 7.0之前的版本中,您可以从属性中提取数据:

    - (NSDictionary *)metadataForData:(NSData *)data
    {
        NSDictionary *properties = nil;
        CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)data, NULL);
    
        if (source) {
            properties = CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(source, 0, NULL));
            CFRelease(source);
        }
    
        return properties;
    }
    

谢谢你的回答,很有帮助。但是我使用CGImage移除了EXIF和GPS之后,通过"writeImageToSavedPhotosAlbum"保存UIImage,图像数据会被压缩。如果我在"removeExifProperties"中添加"kCGImageDestinationLossyCompressionQuality"参数并将其设置为1.0,输出图像会比原始数据更大。 你有什么建议可以保留图像数据的原始状态,但是去掉一些元数据例如EXIF和GPS信息? - Charlie Hung

2
根据苹果公司的 Image I/O编程指南,您可以使用CGImageDestinationSetProperties来添加属性的CFDictionaryRef
他们的示例代码如下:
float compression = 1.0; // Lossless compression if available.
int orientation = 4; // Origin is at bottom, left.
CFStringRef myKeys[3];
CFTypeRef   myValues[3];
CFDictionaryRef myOptions = NULL;
myKeys[0] = kCGImagePropertyOrientation;
myValues[0] = CFNumberCreate(NULL, kCFNumberIntType, &orientation);
myKeys[1] = kCGImagePropertyHasAlpha;
myValues[1] = kCFBooleanTrue;
myKeys[2] = kCGImageDestinationLossyCompressionQuality;
myValues[2] = CFNumberCreate(NULL, kCFNumberFloatType, &compression);
myOptions = CFDictionaryCreate( NULL, (const void **)myKeys, (const void **)myValues, 3,
                      &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
// Release the CFNumber and CFDictionary objects when you no longer need them.

我不明白为什么他们没有继续下去,我猜接下来的步骤会是:

  • 创建一个CFImageDestinationRef
  • 将你的图片复制到它里面
  • 在上面设置所需的元数据

从文档中并不清楚如何执行第一步。因此,我查看了CGImageDestination参考文献,看起来可以这样做(未经测试):

NSMutableData* mutableData = [[NSMutableData alloc] initWithCapacity:[pngData length]];
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge_transfer CFMutableDataRef)mutableData, <# Some UTI Type #>, 1, NULL);
CGImageDestinationAddImage(imageDestination, image, yourProperties);

CGImageDestinationFinalize(imageDestination);

按照Apple文档中所示的方式创建属性字典,然后创建图像目标并将所有内容(包括属性)写入其中。完成后,您可以访问NSMutableData对象并读取图像数据。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接