ios 将元数据添加到现有图像URL而不将图像读入内存?

2fjabf4q  于 2022-11-26  发布在  iOS
关注(0)|答案(1)|浏览(151)

对于UIImages我写的图像与EXIF/IPTC/etc meta使用Photos像:

let dataBundle = mergeImageData(...)
let assetChangeRequest = PHAssetCreationRequest.forAsset()
let assetOptions = PHAssetResourceCreationOptions()
assetOptions.originalFilename =  "\(fileName)"
assetChangeRequest.addResource(with: .photo, data: dataBundle, options: assetOptions)
    
PHPhotoLibrary.shared().performChanges({
    ...
})

func mergeImageData(image: UIImage, with metadata: NSDictionary) -> Data {}

但我的应用程序可以处理可能太大而无法读入内存的图像,并将图像以URL而不是Data的形式保存到Photos中:

let creationRequest = PHAssetCreationRequest.forAsset()
creationRequest.addResource(with: .photo, fileURL: imageFileURL, options: options)

我没有看到任何.meta选项来添加一个资源,这似乎是一个耻辱。有没有任何办法来添加 meta到一个现有的资源,而不阅读它作为一个DataUIImage

hc2pp10m

hc2pp10m1#

下面是我从一个Objective-C项目中得到的一些代码,它是NSURL的一个扩展,用于获取和设置图像的标题。这些代码使用一些较低级别的CoreGraphics代码来读写图像元数据,而不需要将图像本身加载到内存中。(这是在一个生产应用程序中工作)我将发布我的最低测试的Swift版本的代码。一个快速测试Swift代码的工作。照片应用程序能够显示图片上的标题集,然后使用UIActivityViewController将其保存到照片库。
设定nil标题会移除任何现有的标题。
顺便说一句-这是图片的标题,可以查看和编辑时,在照片应用程序查看照片。如果你设置了一个标题与此代码的图像,然后保存到照片库,那么你将能够看到标题。或者,如果你从照片库导入图像,那么此代码可以访问图像的标题,如果它有一个。
Objective-C代码:
NSURL+Caption.h

@interface NSURL (Caption)

- (nullable NSString *)imageCaption;
- (BOOL)setImageCaption:(nullable NSString *)caption;

@end

NSURL+标题. m

// The following two functions are shared with the NSURL category and an NSData category
static NSString *imageCaption(CGImageSourceRef source) {
    NSString *caption = nil;

    if (source) {
        CGImageMetadataRef meta = CGImageSourceCopyMetadataAtIndex(source, 0, nil);
        CFRelease(source);
        if (meta) {
            //NSLog(@"meta: %@", meta);
            CGImageMetadataTagRef descTag = CGImageMetadataCopyTagWithPath(meta, nil, CFSTR("dc:description"));
            if (descTag) {
                CFTypeRef descVal = CGImageMetadataTagCopyValue(descTag);
                NSObject *val = CFBridgingRelease(descVal);
                if ([val isKindOfClass:[NSString class]]) {
                    caption = (NSString *)val;
                } else if ([val isKindOfClass:[NSArray class]]) {
                    NSArray *caps = (NSArray *)val;
                    CGImageMetadataTagRef descRef = (__bridge CGImageMetadataTagRef)(caps.firstObject);
                    if (descRef) {
                        CFTypeRef desc = CGImageMetadataTagCopyValue(descRef);
                        NSString *comment = CFBridgingRelease(desc);
                        if ([comment isKindOfClass:[NSString class]]) {
                            caption = comment;
                        }
                    }
                }
                CFRelease(descTag);
            }
            CFRelease(meta);
        }
    }

    return caption;
}

static BOOL setImageCaption(NSString *caption, CGImageSourceRef source, CGImageDestinationRef dest) {
    BOOL res = NO;
    CGMutableImageMetadataRef meta = CGImageMetadataCreateMutable();

    if (caption.length) {
        CGImageMetadataTagRef capTag = CGImageMetadataTagCreate(kCGImageMetadataNamespaceDublinCore, kCGImageMetadataPrefixDublinCore, CFSTR("[x-default]"), kCGImageMetadataTypeString, (__bridge CFTypeRef _Nonnull)(caption));
        //NSLog(@"capTag: %@", capTag);
        NSArray *capTags = @[ CFBridgingRelease(capTag) ];
        CGImageMetadataTagRef descTag = CGImageMetadataTagCreate(kCGImageMetadataNamespaceDublinCore, kCGImageMetadataPrefixDublinCore, CFSTR("description"), kCGImageMetadataTypeArrayOrdered, (__bridge CFTypeRef _Nonnull)(capTags));
        CGImageMetadataSetTagWithPath(meta, nil, CFSTR("dc:description"), descTag);
        CFRelease(descTag);
    } else {
        CGImageMetadataSetValueWithPath(meta, nil, CFSTR("dc:description"), kCFNull);
    }
    //NSLog(@"new meta: %@", meta);

    CFMutableDictionaryRef options = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    CFDictionarySetValue(options, kCGImageDestinationMergeMetadata, kCFBooleanTrue);
    CFDictionarySetValue(options, kCGImageDestinationMetadata, meta);
    CFErrorRef error;
    if (!CGImageDestinationCopyImageSource(dest, source, options, &error)) {
        NSLog(@"error: %@", error);
    } else {
        // Finalize isn't to be used with CGImageDestinationCopyImageSource. Using it prints an error to the console
        // even though the image caption is updated properly.
        //CGImageDestinationFinalize(dest);
        res = YES;
    }
    CFRelease(options);
    CFRelease(meta);

    return res;
}

@implementation NSURL (Caption)

- (nullable NSString *)imageCaption {
    CGImageSourceRef source = CGImageSourceCreateWithURL((__bridge CFURLRef)self, nil);
    //NSLog(@"%@", self);
    return imageCaption(source);
}

- (BOOL)setImageCaption:(nullable NSString *)caption {
    BOOL res = NO;
    CGImageSourceRef source = CGImageSourceCreateWithURL((__bridge CFURLRef)self, nil);
    if (source) {
        CFStringRef uti = CGImageSourceGetType(source);
        CGImageDestinationRef dest = CGImageDestinationCreateWithURL((__bridge CFURLRef)self, uti, 1, NULL);
        if (dest) {
            if (setImageCaption(caption, source, dest)) {
                res = YES;
            }
            CFRelease(dest);
        }

        CFRelease(source);
    }

    return res;
}

@end

斯威夫特翻译:

private func getImageCaption(from source: CGImageSource) -> String? {
    var caption: String? = nil

    if let meta = CGImageSourceCopyMetadataAtIndex(source, 0, nil) {
        if let descTag = CGImageMetadataCopyTagWithPath(meta, nil, "dc:description" as CFString) {
            let descVal = CGImageMetadataTagCopyValue(descTag)
            if let str = descVal as? String {
                caption = str
            } else if let caps = descVal as? [CGImageMetadataTag] {
                if let descRef = caps.first {
                    let desc = CGImageMetadataTagCopyValue(descRef)
                    if let comment = desc as? String {
                        caption = comment
                    }
                }
            }
        }
    }

    return caption
}

private func setImageCaption(_ caption: String?, source: CGImageSource, destination: CGImageDestination) -> Bool {
    var res = false

    var meta = CGImageMetadataCreateMutable()
    if let caption, !caption.isEmpty {
        if let capTag = CGImageMetadataTagCreate(kCGImageMetadataNamespaceDublinCore, kCGImageMetadataPrefixDublinCore, "[x-default]" as CFString, .string, caption as CFTypeRef) {
            let capTags = [ capTag ]
            if let descTag = CGImageMetadataTagCreate(kCGImageMetadataNamespaceDublinCore, kCGImageMetadataPrefixDublinCore, "description" as CFString, .arrayOrdered, capTags as CFTypeRef) {
                CGImageMetadataSetTagWithPath(meta, nil, "dc:description" as CFString, descTag)
            }
        }
    } else {
        CGImageMetadataSetValueWithPath(meta, nil, "dc:description" as CFString, kCFNull)
    }

    let options = [ kCGImageDestinationMergeMetadata : true, kCGImageDestinationMetadata : meta ] as CFDictionary
    var error: Unmanaged<CFError>? = nil
    if CGImageDestinationCopyImageSource(destination, source, options, &error) {
        res = true
    } else {
        print("Can't copy image info: \(error)")
    }

    return res
}

extension URL {
    var imageCaption: String? {
        get {
            if let source = CGImageSourceCreateWithURL(self as CFURL, nil) {
                return getImageCaption(from: source)
            } else {
                // Probably means the URL isn't for an image
                return nil
            }
        }
        set {
            if let source = CGImageSourceCreateWithURL(self as CFURL, nil) {
                if let uti = CGImageSourceGetType(source),
                   let destination = CGImageDestinationCreateWithURL(self as CFURL, uti, 1, nil) {
                    _ = setImageCaption(newValue, source: source, destination: destination)
                }
            }
        }
    }
}

相关问题