Search code examples
iosswiftios9phasset

Image data from PHImageManager differs from image data saved as asset


Hi and thanks in advance for taking the time to look at my question.

I'm creating a bitmap and then building a PNG from that bitmap. Then I'm saving that PNG to a photo album. After fetching that asset back via its local identifier, and then using a PHImageManager to retrieve the UIImage associated with that asset, I'm noticing the bytes of the image have changed. I'm not sure if headers are being added when saving as a PHAsset or what, but I don't really see any common bytes between the inserted and retrieved images. When I put the retrieved image itself into the view though, it looks right enough so I'm quite stumped.

The following is some sample code demonstrating the problem.

// image is a UIImage

// This is to show what the bytes are prior to saving
let imageRef = image.CGImage!
let cgData = CGDataProviderCopyData(CGImageGetDataProvider(imageRef))
let data = NSData(data: cgData)
let bytes = UnsafeMutablePointer<UInt8>(data.bytes)

print(data.length)
for i in 0..<16 {
    print(bytes[i])
}

var assetIdentifier: String!

// collectionIdentifier is a valid local identifier for an existing collection
let collectionFetchRequest = PHAssetCollection.fetAssetCollectionsWithLocalIdentifiers([collectionIdentifier], options: nil)
let collection = collectionFetchRequest.firstObject as! PHAssetCollection

PHPhotoLibrary.sharedPhotoLibrary().performChanges({

    let assetCreationRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(image)
    let collectionChangeRequest = PHAssetCollectionChangeRequest(forAssetCollection: collection)!
    let assetPlaceholder = assetCreationRequest.placeholderForCreatedAsset!
    collectionChangeRequest.addAssets([assetPlaceholder])
    assetIdentifier = assetPlaceholder.localIdentifier

}) { (_, _) in

    let assetFetchRequest = PHAsset.fetchAssetsWithLocalIdentifiers([assetIdentifier], options: nil)
    let asset = assetFetchRequest.firstObject as! PHAsset

    let options = PHImageRequestOptions()
    options.synchronous = true
    options.version = .Current
    options.resizeMode = .None

    // The image is actually 64 x 64, but the targetSize doesn't seem to affect the bytes I'm getting out, I assume because the options aren't saying not to resize or crop anything
    // This was the not working code.
    // PHImageManager.defaultManager().requestImageForAsset(asset, targetSize: CGSizeMake(256, 256), contentMode: .Default, options: options) { (retrievedImage, _) in

    // This is the working code.
    PHImageManager.defaultManager().requestImageDataForAsset(asset, options: options) { (data, _, _, _) in

        let retrievedImage = UIImage(data: data!)!

        // This is to show what the bytes are after retrieving
        let retrievedCGImage = retrievedImage!.CGImage!
        let retrievedCFData = CGDataProviderCopyData(CGImageGetDataProvider(retrievedCGImage))!
        let retrievedData = NSData(data: retrievedCFData)
        let retrievedBytes = UnsafeMutablePointer<UInt8>(retrievedData.bytes)

        // Length is the same but the bytes are not even close to right
        print(retrievedData.length)
        for i in 0..<16 {
            print(retrievedBytes[i])
        }

    }
}

Please feel free to correct any part of this code that is doing something improperly or inefficiently. This code should run, so if you see any syntax errors or mistyped variables, those are only on StackOverflow and not in XCode, so they are not the cause of this byte problem. Also as far as the bytes within the image, I'm not caring about the 4th byte, which is the alpha component, because from my testing so far it is 255 always. If anyone knows how I can make use of this 4th byte without having it just be set to 255 upon saving the image or just not have an alpha component, that would also be helpful.

Thanks again for your time and consideration!

EDIT:

Here's a full response to the person who answered:

Thanks for the heads up. It turns out you're right about requestImageDataForAsset working. I'll edit the original code block to reflect the working code. Do you have any idea where in the documentation it describes the image that gets returned by requestImageForAsset?

Further, do you know of any documentation that describes the data that gets returned by requestImageDataForAsset? I'm not too familiar with picture storage mechanisms, but the data that comes back from requestImageData also does not match the picture exactly until it is turned into a UIImage.

Thanks again!

StackOverflow's lovely interface makes an enter keypress submit a comment, and then gives a max of 5 minutes to edit. So that's great.


Solution

  • The requestImageForAsset method does not guarantee that you get byte perfect output. I would suggest you try the requestImageDataForAsset method, which should give the results you expect.