来自存储在 iCloud 上的图像的 CGImage 看起来不对

Posted

技术标签:

【中文标题】来自存储在 iCloud 上的图像的 CGImage 看起来不对【英文标题】:CGImage from images stored on iCloud look wrong 【发布时间】:2020-02-16 23:08:58 【问题描述】:

我正在使用this answer 中描述的代码从CGImage 中提取像素颜色。

但是,我刚刚意识到,如果我加载在另一台设备上创建的图像,像素值看起来是错误的。第一个明显的问题是 alpha 已经消失了。 CGImageAlphaInfo 报告 .noneSkipLast,但我知道图像是 RGBA。如果我从创建它的同一设备读取它,它看起来很好。第二个问题是有一些渗色,好像图像已调整大小。也许正在被压缩或其他什么。

这是一个例子:

源图是这个西瓜,12x12

它是在我的 iPad 上创建的。但是,如果我使用我链接的代码通过 iCloud 将它加载到我的 iPhone 上,我会得到:

Alpha 通道消失,颜色溢出。

如果我从同一部 iPhone 使用 Airdrop 将小西瓜发送到我的 Mac,然后再次使用 Airdrop 将其发送回(所以它应该是相同的图像!),现在加载它,我得到正确的图像:

(深棕色区域是 alpha 为 0 的地方)

如果您有几个启用了 iCloud 的 ios 设备,您可以在此应用中重现此行为:http://swiftpixels.endavid.com 我正在使用该代码读取像素颜色。

这些图像之间可能有什么区别?如何从 iCloud 读取正确的图像?我应该在 UIImage 而不是 CGImage 中寻找提示吗?

有什么线索吗?谢谢!

更新

作为参考,我正在使用UIImagePickerController 读取图像,使用以下代码:

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) 
        if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage 
            loadImageInMemory(image)
        
        picker.dismiss(animated: true, completion: nil)
        self.presentingViewController?.dismiss(animated: true, completion: nil)
    

    fileprivate func loadImageInMemory(_ image: UIImage) 
       /// skipping some preparation
       guard let cgImage = image.cgImage else 
          return
       
       /// getImageData is a function like getPixelColor linked above,
       /// but for a Rect 
       self.imageData = cgImage.getImageData(rect, width, height)
    

我还发现了这个可能相关的问题:UIImagePickerController and iCloud photos

正如 Rob 在下面的 cmets 中建议的那样,我将手机中的照片设置更改为“下载并保留原件”(而不是“优化 iPhone 存储”),这样就解决了问题。所以我想问题是为什么iCloud试图压缩一个只有1355字节的PNG图像,以及是否可以从UIImagePickerController访问原始图像

【问题讨论】:

“...如果我加载在另一台设备上创建的图像”...您这样做的精确度如何?您在这些设备之间交换什么样的表示? PNG? JPG?归根结底,问题不在于 UIImageCGImage,而是您交换的文件表示/有效负载的排序。 这是我在此处发布的 PNG。 JPG 没有透明度,除非您使用 Jpeg2000。 PNG是使用我链接的应用程序创建的。它在 iPad 上保存到照片文件夹,然后它出现在 iPhone 上,因为我启用了 iCloud 照片。它说:“自动上传所有照片和视频并将其安全地存储在 iCloud 中,以便您可以从任何设备浏览、搜索和共享”。它看起来不像 iCloud 将 PNG 转换为 Jpeg,因为如果我将它从 iPhone 空投到我的 Mac,我可以看到它仍然是 PNG(我的问题上附有小西瓜)。 是的!谢谢 :) 如果我将其设置为“下载并保留原件”,则图像看起来正确。 12x12 西瓜是一个只有 1,355 字节的 PNG,所以我不明白对其应用了什么压缩......不过,Airdrop 似乎可以访问原始图像。如何让我的应用也访问原始版本? 我发现了一个相关的问题,我想,***.com/q/25012731/1765629 -- 我已经用那个链接和一些代码更新了我的问题。为了方便起见,我使用了照片库,我认为这对应用程序的用户来说会是一个更好的体验。该应用程序自 2016 年以来一直存在,我现在才注意到这个问题,所以我想这不是一个主要的不便。不过,很高兴了解发生了什么。 【参考方案1】:

在“设置”应用中,“照片”下方有“优化 iPhone 存储”与“下载并保留原件”选项。后者应该在不以任何方式更改图像的情况下同步照片库。

不过,Airdrop 似乎可以访问原始图像。

Airdrop 不依赖照片库 iCloud 同步来发送图像。这就是为什么资产会保持不变。

如何让我的应用也访问原始版本?

这取决于您是否真的想使用照片库来同步您的资源。这似乎特别真实,因为许多人可能根本不会选择将他们庞大的照片库同步到 iCloud。我不确定你想依赖它。

您可能只想自己使用 CloudKit,而不是依赖照片库。也许您应该在应用程序中有一个选项,以便用户可以选择使用 iCloud 存储,如果是这样,请在您的应用程序中使用 iCloud 存储,而不是依赖照片库。

【讨论】:

【参考方案2】:

图像看起来模糊且没有 alpha 的原因似乎是照片默认从 iCloud 流式传输 Jpeg 压缩图像。即使您的原始图像在没有压缩的情况下会更小,就像在这个像素艺术示例中一样。

正如Rob 所指出的,验证是否属于这种情况的一种方法是更改​​您的照片设置。在“设置”应用中:

Settings -> Photos -> Download and Keep Originals

这可以解决问题,但当然是不可取的。如果您想继续使用照片,而不是实现自己的 iCloud 解决方案,同时保持Optimize iPhone Storage 设置,您可以使用PhotoKit 检索原始图像。

替换此代码,

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) 
        if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage 
            loadImageInMemory(image)
        
        picker.dismiss(animated: true, completion: nil)
        self.presentingViewController?.dismiss(animated: true, completion: nil)
    

通过其他代码:

import Photos

// ...

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) 
        // it will be loaded asynchronously
        loadImageFromPicker(info: info)
        picker.dismiss(animated: true, completion: nil)
        self.presentingViewController?.dismiss(animated: true, completion: nil)
    

    private func loadImageFromPicker(info: [UIImagePickerController.InfoKey : Any]) 
        var phAsset: PHAsset?
        if #available(iOS 11.0, *) 
            phAsset = info[UIImagePickerController.InfoKey.phAsset] as? PHAsset
         else 
            // Fallback on earlier versions
            if let referenceURL = info[UIImagePickerController.InfoKey.referenceURL] as? URL 
                let fetchResult = PHAsset.fetchAssets(withALAssetURLs: [referenceURL], options: nil)
                phAsset = fetchResult.firstObject
            
        
        guard let asset = phAsset else 
            return
        
        // size doesn't matter, because resizeMode = .none
        let size = CGSize(width: 32, height: 32)
        let options = PHImageRequestOptions()
        options.version = .original
        options.deliveryMode = .highQualityFormat
        options.resizeMode = .none
        options.isNetworkAccessAllowed = true
        PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFit, options: options)  [weak self] (image, info) in
            if let s = self, let image = image 
                s.loadImageInMemory(image)
            
        
    

此代码适用于本地图像和 iCloud 图像。

【讨论】:

以上是关于来自存储在 iCloud 上的图像的 CGImage 看起来不对的主要内容,如果未能解决你的问题,请参考以下文章

在 Apple Watch 上接收来自 iCloud 的推送通知

从 iCloud 获取图像将冻结 UI

iCloud 核心数据未同步第二台设备上的所有记录

检测 OS X 上的 iCloud 可用性变化

如果内容需要与 iCloud 同步,我应该如何存储图像

ios swift图库库性能问题,一旦加载图像/视频列表,数据来自iCloud