Как сохранить фотографию TIFF из файла захвата AVFoundationsStillImageAsynchronouslyFromConnection в файл с метаданными EXIF на iPhone (iOS)?
С этим вопросом я спрашиваю только о возможностях, которые у меня есть с Xcode и iOS без внешних библиотек. Я уже изучаю возможность использования libtiff
в другом вопросе.
проблема
Я просеивал переполнение стека в течение нескольких недель и нашел рабочие решения для каждой из моих проблем самостоятельно. У меня есть 4 вещи, которые должны работать:
- Мне нужны данные RGBA, так как они поступают с камеры, никакого сжатия
- Мне нужно как можно больше метаданных, особенно EXIF
- Мне нужно сохранить в формате TIFF для совместимости с другим программным обеспечением и без потерь
- Мне нужна защита от случайного просмотра путем сохранения в файл, а не в библиотеку фотографий
Я могу иметь 2 и 4 с использованием JPEG. Я могу иметь 1, 3 и 4 с необработанными данными (соответственно NSData), сделанными из буфера камеры. Могу ли я иметь все 4 моих предварительных условия с Xcode и iOS? Я собираюсь сдаться и ищу ваш вклад в качестве последнего средства.
Пока я изучаю это, я также застрял на другом проспекте, который я пробовал, литифф. Я все еще пытаюсь, хотя...
Вот список полезных советов, которые я попробовал, мой собственный код просто собран из источников переполнения стека, подобных этим:
- Как записать exif-метаданные в изображение (не снимок камеры, просто UIImage или JPEG) (мне хочется, чтобы я мог использовать формат JPEG, это так легко, когда вы делаете то, что предпочитает Apple)
- Необработанные данные изображения с камеры, такие как "645 PRO" (это будет смысл использовать, например, libtiff)
- Сохранение CGImageRef в файл png? (работает с
kUTTypeTIFF
тоже но без метаданных)
Решение
Полная последовательность действий от captureStillImageAsynchronouslyFromConnection
:
[[self myAVCaptureStillImageOutput] captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error)
{
//get all the metadata in the image
CFDictionaryRef metadata = CMCopyDictionaryOfAttachments(kCFAllocatorDefault, imageSampleBuffer, kCMAttachmentMode_ShouldPropagate);
// get image reference
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(imageSampleBuffer);
// >>>>>>>>>> lock buffer address
CVPixelBufferLockBaseAddress(imageBuffer, 0);
//Get information about the image
uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddress(imageBuffer);
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
// create suitable color space
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
//Create suitable context (suitable for camera output setting kCVPixelFormatType_32BGRA)
CGContextRef newContext = CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
// <<<<<<<<<< unlock buffer address
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
// release color space
CGColorSpaceRelease(colorSpace);
//Create a CGImageRef from the CVImageBufferRef
CGImageRef newImage = CGBitmapContextCreateImage(newContext);
// release context
CGContextRelease(newContext);
// create destination and write image with metadata
CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:filePath isDirectory:NO];
CGImageDestinationRef destination = CGImageDestinationCreateWithURL(url, kUTTypeTIFF, 1, NULL);
CGImageDestinationAddImage(destination, imageRef, metadata);
// finalize and release destination
CGImageDestinationFinalize(destination);
CFRelease(destination);
}
Настройки камеры, связанные с выводом неподвижного изображения:
[[self myAVCaptureSession] setSessionPreset:AVCaptureSessionPresetPhoto];
а также
NSDictionary *outputSettings = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA], (id)kCVPixelBufferPixelFormatTypeKey, nil];
[myAVCaptureStillImageOutput setOutputSettings:outputSettings];
Я получаю хорошее номинально несжатое изображение в номинальном формате TIFF со всеми метаданными. (Это отражается в других системах, но теперь я могу написать EXIF и другие метаданные, я также могу точно настроить это, я уверен).
2 ответа
Поскольку вы уже взломали 1, 3 и 4, кажется, что единственное препятствие, которое вам не хватает, - это сохранение данных и метаданных вместе. Попробуйте это (при условии, что необработанные данные находятся в CMSampleBufferRef
называется myImageDataSampleBuffer
и вы сделали тяжелую работу по размещению графических данных в CGImageRef
называется myImage
):
CFDictionaryRef metadata = CMCopyDictionaryOfAttachments(kCFAllocatorDefault,
myImageDataSampleBuffer,
kCMAttachmentMode_ShouldPropagate);
NSFileManager* fm = [[NSFileManager alloc] init];
NSURL* pathUrl = [fm URLForDirectory:saveDir
inDomain:NSUserDomainMask
appropriateForURL:nil
create:YES
error:nil];
NSURL* saveUrl = [pathUrl URLByAppendingPathComponent:@"myfilename.tif"];
CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef)saveUrl,
(CFStringRef)@"public.tiff", 1, NULL);
CGImageDestinationAddImage(destination, myImage, metadata);
CGImageDestinationFinalize(destination);
CFRelease(destination);
Этот поток очень помог в решении очень похожей проблемы, поэтому я подумал, что могу предложить реализацию решения Swift 2.0 на случай, если кто-то придет посмотреть.
stillImageOutput?.captureStillImageAsynchronouslyFromConnection(videoConnection, completionHandler: { (imageDataSampleBuffer, error) -> Void in
// get image meta data (EXIF, etc)
let metaData: CFDictionaryRef? = CMCopyDictionaryOfAttachments( kCFAllocatorDefault, imageDataSampleBuffer, kCMAttachmentMode_ShouldPropagate )
// get reference to image
guard let imageBuffer = CMSampleBufferGetImageBuffer( imageDataSampleBuffer ) else { return }
// lock the buffer
CVPixelBufferLockBaseAddress( imageBuffer, 0 )
// read image properties
let baseAddress = CVPixelBufferGetBaseAddress( imageBuffer )
let bytesPerRow = CVPixelBufferGetBytesPerRow( imageBuffer )
let width = CVPixelBufferGetWidth( imageBuffer )
let height = CVPixelBufferGetHeight( imageBuffer )
// color space
let colorSpace = CGColorSpaceCreateDeviceRGB()
// context - camera output settings kCVPixelFormatType_32BGRA
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.PremultipliedFirst.rawValue).union(.ByteOrder32Little)
let newContext = CGBitmapContextCreate( baseAddress, width, height, 8, bytesPerRow, colorSpace, bitmapInfo.rawValue )
//unlock buffer
CVPixelBufferUnlockBaseAddress( imageBuffer, 0 )
//Create a CGImageRef from the CVImageBufferRef
guard let newImage = CGBitmapContextCreateImage( newContext ) else {
return
}
// create tmp file and write image with metadata
let fileName = String(format: "%@_%@", NSProcessInfo.processInfo().globallyUniqueString, "cap.tiff")
let fileURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent(fileName)
if let destination = CGImageDestinationCreateWithURL( fileURL, kUTTypeTIFF, 1, nil) {
CGImageDestinationAddImage( destination, newImage, metaData )
let wrote = CGImageDestinationFinalize( destination )
if !wrote || NSFileManager.defaultManager().fileExistsAtPath(fileURL.URLString) {
return
}
}
}
PS, чтобы это работало, вы должны настроить свой буфер изображения следующим образом:
stillImageOutput = AVCaptureStillImageOutput()
stillImageOutput?.outputSettings = [ kCVPixelBufferPixelFormatTypeKey: Int(kCVPixelFormatType_32BGRA) ]