get image metadata without decoding image data (iOS) - ios

I have an image file in my app directory. This is not encoded with the iOS default encoder but our own encoders.
The format can be JPEG (EXIF) or TIFF. They have been checked for compliancy with standards so for this post, we can treat it as any image.
I want to get metadata of the image but don't want to decode it. I have been suggested:
CGImageSourceRef imageSourceRef = CGImageSourceCreateWithData((__bridge_retained CFDataRef)(localImageData), nil);
CFDictionaryRef cfMetadata = CGImageSourceCopyPropertiesAtIndex(imageSourceRef, 0, nil);
NSDictionary *metaDataDic = [NSDictionary dictionaryWithDictionary:(__bridge_transfer NSDictionary *)(cfMetadata)];
What I want to know is 'does this decode the image?'
I know there is an incremental option which is used when transferring images to server. Is there a way to stop the decoding after it gets the EXIF info?

Related

Custom defined file metadata keys [Swift]

I'm currently trying to save a jpeg representation of a UIImage with additional custom metadata (e.g. Thermal temperature statistics etc.). These don't fit within the apple predefined keys (https://developer.apple.com/documentation/imageio/cgimageproperties), so solutions I've found don't apply to my scenario.
I've tried saving the metadata with the image as a dictionary of keys and values, but the image is saved without the additional metadata.
func saveImage(imageToSave: UIImage, metadata: NSMutableDictionary) {
if let data: Data = imageToSave.jpegData(compressionQuality: ThermalImageView.JPEG_COMPRESSION) {
let fileName = self.buildFileName();
let source = CGImageSourceCreateWithData(data as CFData, nil)!;
let uniformTypeIdentifier = CGImageSourceGetType(source)!;
let destination = CGImageDestinationCreateWithURL(fileName as CFURL, uniformTypeIdentifier, 1, nil)!;
CGImageDestinationAddImageFromSource(destination, source, 0, metadata);
CGImageDestinationFinalize(destination);
}
}
When I try to read these values back with ExifTool (exiftool -j filename.jpg), the metadata is nowhere to be found. I expected this to happen as Apple seems to restrict what keys you can add to your metadata. So, is there a way to do this or should I go another route?
Thanks!
Edit: I think I may be barking up the wrong tree here. It seems like what I actually want to do is modify the header with additional metadata.
Photos framework is a way to go when dealing with metadata of assets. Moreover, PHAsset is the object you'd like to tinker with. https://developer.apple.com/documentation/photokit/phasset
One related question is: https://forums.developer.apple.com/thread/60664
If you still want to have it in your current way, perhaps try this? https://github.com/Nikita2k/SimpleExif

Crop Captured RAW Photo and save to file iOS [duplicate]

I want to build an iOS 10 app that lets you shoot a RAW (.dng) image, edit it, and then saved the edited .dng file to the camera roll. By combining code from Apple's 2016 "AVCamManual" and "RawExpose" sample apps, I've gotten to the point where I have a CIFilter containing the RAW image along with the edits.
However, I can't figure out how to save the resulting CIImage to the camera roll as a .dng file. Is this possible?
A RAW file is "raw" output direct from a camera sensor, so the only way to get it is directly from a camera. Once you've processed a RAW file, what you have is by definition no longer "raw", so you can't go back to RAW.
To extend the metaphor presented at WWDC where they introduced RAW photography... a RAW file is like the ingredients for a cake. When you use Core Image to create a viewable image from the RAW file, you're baking the cake. (And as noted, there are many different ways to bake a cake from the same ingredients, corresponding to the possible options for processing RAW.) But you can't un-bake a cake — there's no going back to original ingredients, much less a way that somehow preserves the result of your processing.
Thus, the only way to store an image processed from a RAW original is to save the processed image in a bitmap image format. (Use JPEG if you don't mind lossy compression, PNG or TIFF if you need lossless, etc.)
If you're writing the results of an edit to PHPhotoLibrary, use JPEG (high quality / less compressed if you prefer), and Photos will store your edit as a derived result, allowing the user to revert to the RAW original. You can also describe the set of filters you applied in PHAdjustmentData saved with your edit — with adjustment data, another instance of your app (or Photos app extension) can reconstruct the edit using the original RAW data plus the filter settings you save, then allow a user to alter the filter parameters to create a different processed image.
Note: There is a version of the DNG format called Linear DNG that supports non-RAW (or "not quite RAW") images, but it's rather rare in practice, and Apple's imaging stack doesn't support it.
Unfortunately DNG isn't supported as an output format in Apple's ImageIO framework. See the output of CGImageDestinationCopyTypeIdentifiers() for a list of supported output types:
(
"public.jpeg",
"public.png",
"com.compuserve.gif",
"public.tiff",
"public.jpeg-2000",
"com.microsoft.ico",
"com.microsoft.bmp",
"com.apple.icns",
"com.adobe.photoshop-image",
"com.adobe.pdf",
"com.truevision.tga-image",
"com.sgi.sgi-image",
"com.ilm.openexr-image",
"public.pbm",
"public.pvr",
"org.khronos.astc",
"org.khronos.ktx",
"com.microsoft.dds",
"com.apple.rjpeg"
)
This answer comes late, but it may help others with the problem. This is how I saved a raw photo to the camera roll as a .dng file.
Just to note, I captured the photo using the camera with AVFoundation.
import Photos
import AVFoundation
//reading in the photo data in as a data object
let photoData = AVCapturePhotoOutput.dngPhotoDataRepresentation(forRawSampleBuffer: sampleBuffer, previewPhotoSampleBuffer: previewPhotoSampleBuffer)
// put it into a temporary file
let temporaryDNGFileURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("temp.dng")!
try! photoData?.write(to: temporaryDNGFileURL)
// get access to photo library
PHPhotoLibrary.requestAuthorization { status in
if status == .authorized {
// Perform changes to the library
PHPhotoLibrary.shared().performChanges({
let options = PHAssetResourceCreationOptions()
options.shouldMoveFile = true
//Write Raw:
PHAssetCreationRequest.forAsset().addResource(with: .photo, fileURL: temporaryDNGFileURL, options: options)
}, completionHandler: { success, error in
if let error = error { print(error) }
})
}
else { print("cant access photo album") }
}
Hope it helps.
The only way to get DNG data as of the writing of this response (iOS 10.1) is:
AVCapturePhotoOutput.dngPhotoDataRepresentation(forRawSampleBuffer: CMSampleBuffer, previewPhotoSampleBuffer: CMSampleBuffer?)
Noting the OP refers to Core Image. As mentioned by rickster, CI works on processed image data, therefore only offers processed image results (JPEG, TIFF):
CIContext.writeJPEGRepresentation(of:to:colorSpace:options:)
CIContext.writeTIFFRepresentation(of:to:format:colorSpace:options:)

Detecting that iOS image data is HEIF or HEIC

My server doesn't support the HEIF format. So I need to transform it to JPEG before uploading from my app.
I do this:
UIImage *image = [UIImage imageWithData:imageData];
NSData *data=UIImageJPEGRepresentation(image, 1.0);
But how can I know that the data is HEIF (or HEIC) ? I can look at a file:
([filePath hasSuffix:#".HEIC"] || [filePath hasSuffix:#".heic"])
But I don't think it's a good answer. Is there any other solution?
Both existing answers have good recommendations, but to attempt to tell the whole story...
UIImage doesn't represent an image file or even binary data in an image-file format. A UIImage is best thought of as an abstract representation of the displayable image encoded in that data — that is, a UIImage is the result of the decoding process. By the time you have a UIImage object, it doesn't care what file format it came from.
So, as #Ladislav's answer notes, if you have a UIImage already and you just want to get data in a particular image file format, call one of the convenience functions for getting a UIImage into a file-formatted data. As its name might suggest, UIImageJPEGRepresentation returns data appropriate for writing to a JPEG file.
If you already have a UIImage, UIImageJPEGRepresentation is probably your best bet, since you can use it regardless of the original image format.
As #ScottCorscadden implies, if you don't have a UIImage (yet) because you're working at a lower level such that you have access to the original file data, then you'll need to inspect that data to divine its format, or ask whatever source you got the data from for metadata describing its format.
If you want to inspect the data itself, you're best off reading up on the HIEF format standards. See nokiatech, MPEG group, or wikipedia.
There's a lot going on in the HEIF container format and the possible kinds of media that can be stored within, so deciding if you have not just a HEIF file, but an HEIF/HEVC file compatible with this-or-that viewer could be tricky. Since you're talking about excluding things your server doesn't support, it might be easier to code from the perspective of including only the things that your server does support. That is, if you have data with no metadata, look for something like the JPEG magic number 0xffd8ff, and use that to exclude anything that isn't JPEG.
Better, though, might be to look for metadata. If you're picking images from the Photos library with PHImageManager.requestImageData(for:options:resultHandler:), the second parameter to your result handler is the Uniform Type Identifier for the image data: for HEIF and HEIC files, public.heif, public.heif-standard, and public.heic have been spotted in the wild.
(Again, though, if you're looking for "images my sever doesn't support", you're better off checking for the formats your server does support and rejecting anything not on that list, rather than trying to identify all the possible unsupported formats.)
When you are sending to your server you are most likely decoding the UIImage and sending it as Data so just do
let data = UIImageJPEGRepresentation(image, 0.9)
Just decide what quality works best for you, here it is 0.9
A bit late to the party, but other than checking the extension (after the last dot), you can also check for the "magic number" aka file signature. Byte 5 to 8 should give you the constant "ftyp". The following 4 bytes would be the major brand, which I believe is one of "mif1", "heic" and "heix".
For example, the first 12 bytes of a .heic image would be:
00 00 00 18 66 74 79 70 6d 69 66 31
which, after removing 0s and trim the result, literally decoded to ftypmif1.
Well, you could look at magic bytes - JPEG and PNG certainly are known, and I seem to see some references that HEIF (.heic) starts with a NUL byte. If you're using any of the PHImageManager methods like requestImageDataForAsset:options:resultHandler, that resultHandler will be passed a NSString * _Nullable dataUTI reference. There's a decent WWDC video/slides on this (possibly here) that suggest if the UTI is not kUTTypeJPEG you convert it (and the slides have some lower-level sample code in swift to do it that preserve orientation too).
I should also mention, if you have control at your app layer and all uploads come from there, do all this there.
If you're using Photos framework and are importing images from photo library, there's a solution that was mentioned briefly during WWDC17. First, import core services:
import MobileCoreServices
Then, when you request the image, check the UTType that is returned as a second parameter to your block:
// asset: PHAsset
PHImageManager.default().requestImageData(for: asset, options: nil) { imageData, dataUTI, orientation, info in
guard let dataUTI = dataUTI else { return }
if !(UTTypeConformsTo(dataUTI as CFString, kUTTypeJPEG) || UTTypeConformsTo(dataUTI as CFString, kUTTypePNG)) {
// imageData is neither JPG not PNG, possibly subject for transcoding
}
}
Other UTTypes can be found here

When should I use UIImageJPEGRepresentation and UIImagePNGRepresentation for uploading different image formats to the server?

In my application I have to send images of different formats to the server (it must be all file formats that can be read by the UIImage class) https://developer.apple.com/library/ios/#documentation/uikit/reference/UIImage_Class/Reference/Reference.html
And the problem is: I don't know when I should use each of this methods. Of course it's obvious that for .png images I need to use UIImagePNGRepresentation and for .jpg/.jpeg UIImageJPEGRepresentation. But what about other formats (.tiff,.gif , etc.)? There are only two methods for image manipulations and so many formats.
You say:
Of course it's obvious that for .png images I need to use UIImagePNGRepresentation and for .jpg/.jpeg UIImageJPEGRepresentation.
No, that's not necessarily the case. If you have some original "digital asset", rather than creating a UIImage and then using one of those two functions to create the NSData that you'll upload, you will often just load the NSData from the original asset and bypass the round-trip to a UIImage at all. If you do this, you don't risk any loss of data that converting to a UIImage, and then back again, can cause.
There are some additional considerations, though:
Meta data:
These UIImageXXXRepresentation functions strip the image of its meta data. Sometimes that's a good thing (e.g. you don't want to upload photos of your children or expensive gadgets the include the GPS locations where malcontents could identify where the shot was taken). In other cases, you don't want the meta data to be thrown away (e.g. date of the original shot, which camera, etc.).
You should make an explicit decision as to whether you want meta data stripped or not. If not, don't round-trip your image through a UIImage, but rather use the original asset.
Image quality loss and/or file size considerations:
I'm particularly not crazy about UIImageJPEGRepresentation because it a lossy compression. Thus, if you use a compressionQuality value smaller than 1.0, you can lose some image quality (modest quality loss for values close to 1.0, more significant quality loss with lower compressionQuality values). And if you use a compressionQuality of 1.0, you mitigate much of the JPEG image quality loss, but the resulting NSData can often be bigger than the original asset (at least if the original was, itself, a compressed JPEG or PNG), resulting in slower uploads.
UIImagePNGRepresentation doesn't introduce compression-based data loss, but depending upon the image, you may still lose data (e.g. if the original file was a 48-bit TIFF or used a colorspace other than sRGB).
It's a question of whether you are ok with some image quality loss and/or larger file size during the upload process.
Image size:
Sometimes you don't want to upload the full resolution image. For example, you might be using a web service that wants images no bigger than 800px per side. Or if you're uploading a thumbnail, they might want something even smaller (e.g. 32px x 32px). By resizing images, you can make the upload much smaller and thus much faster (though with obvious quality loss). But if you use an image resizing algorithm, then creating a PNG or JPEG using these UIImageXXXRepresentation functions would be quite common.
In short, if I'm trying to minimize the data/quality loss, I would upload the original asset if it's in a format that the server accepts, and I'd use UIImagePNGRepresentation (or UIImageJPGRepresentation with quality setting of 1.0) if the original asset was not in a format accepted by the server. But the choice of using these UIImageXXXRepresentation functions is a question of your business requirements and what the server accepts.
Rob points out a lot of very good things to consider when working with images (+1), however here is an example of how to create tiff's and gif's as you asked:
First, you need to link to the ImageIO library (under the Build Phases of your app).
Next you need to #import <ImageIO/ImageIO.h> at the top of your file.
Then, the following code will convert the image for you:
// Get a reference to the image that you already have stored on disk somehow.
// If it isn't stored on disk, then you can use CGImageSourceCreateWithData() to create it from an NSData representation of your image.
NSURL *url = [[NSBundle mainBundle] URLForResource:#"01" withExtension:#"jpg"];
CGImageSourceRef src = CGImageSourceCreateWithURL((__bridge CFURLRef)(url), NULL);
// Create a URL referencing the Application Support Directory. We will save the new image there.
NSFileManager *fm = [NSFileManager defaultManager];
NSURL *suppurl = [fm URLForDirectory:NSApplicationSupportDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
create:YES
error:NULL];
// Append the name of the output file to the app support directory
// For tiff change the extension in the next line to .tiff
NSURL *gifURL = [suppurl URLByAppendingPathComponent:#"mytiff.gif"];
// Create the destination for the new image
// For tiff, use #"public.tiff" for the second argument of the next line (instead of #com.compuserve.gif".
CGImageDestinationRef dest = CGImageDestinationCreateWithURL((__bridge CFURLRef)gifURL,
(CFStringRef)#"com.compuserve.gif",
1,
NULL);
CGImageDestinationAddImageFromSource(dest, src, 0, NULL);
// Write the image data to the URL.
bool ok = CGImageDestinationFinalize(dest);
if (!ok)
NSLog(#"Unable to create gif file.");
// Cleanup
CFRelease(src);
CFRelease(dest);
This was adapted from the code in this book.

iOS: Preserve JFIF/EXIF data when saving to camera roll

Original Problem
I am trying to save an image into the camera roll while preserving all the original EXIF/JFIF data. I'd like the image as saved to the camera roll to be identical to the original file byte-for-byte. When I save the image via [ALAssetsLibrary writeImageDataToSavedPhotosAlbum:metadata:completionBlock:], the original EXIF data is preserved, but JFIF is stripped out.
ALAssetsLibrary *library = [[[ALAssetsLibrary alloc] init] autorelease];
[library writeImageDataToSavedPhotosAlbum:imageData metadata:nil completionBlock:^(NSURL *assetURL, NSError *error) {
[self imageDidFinishSavingToCameraRollWithError:error];
}];
I tried using the iphone-exif project to parse out the JFIF data and explicitly pass it in via the metadata parameter:
EXFJpeg *jpegScanner = [[EXFJpeg alloc] init];
[jpegScanner scanImageData:imageData];
EXFJFIF *jfif = [jpegScanner jfif];
NSMutableDictionary *jfifMetadata = [[NSMutableDictionary alloc] init];
[jfifMetadata setObject:[jfif version] forKey:(NSString *)kCGImagePropertyJFIFVersion];
...
NSMutableDictionary *metadata = [[NSMutableDictionary alloc] init];
[metadata setObject:jfifMetadata forKey:(NSString*)kCGImagePropertyJFIFDictionary];
But doing so results in the same file, byte-for-byte, as passing a nil metadata dictionary, meaning the JFIF data is still being stripped by iOS.
According to Wikipedia:
Formally, the Exif and JFIF standards are incompatible. This is because both specify that their particular application segment (APP0 for JFIF, APP1 for Exif) must be the first in the image file. In practice, many programs and digital cameras produce files with both application segments included. This will not affect the image decoding for most decoders, but poorly designed JFIF or Exif parsers may not recognise the file properly.
Is there any way to save the original file to the camera roll byte-for-byte, or will iOS always ignore JFIF data if EXIF is present?
Update: 2/26/13
I've filed <rdar://13291591> with Apple. I also created a sample project demonstrating the issue: http://spolet.to/3J0l1u3w0R2e
The sample app has three buttons: one for a JPEG with JFIF data, one for a JPEG without JFIF data, and one for a PNG. When the button is tapped, the corresponding image will be hashed and saved to the photo library. The resulting ALAsset will then be hashed as well.
Results:
The ALAsset for the saved PNG has the same hash as the original file.
The ALAssets for the saved JPEGs do not have the same hashes as their original counterparts.
Digging in to this, it appears that the 'Brightness Value', 'Components Configuration', 'Thumbnail Length' and 'Thumbnail Image' attributes within the ALAsset EXIF data have been modified by the OS. Further, the original JFIF data (for the image with JFIF) has been stripped.
Expected Results:
All three files should have the same hash when saved into the photo library.

Resources