共享扩展中旋转图像

6
我有这个扩展程序,在应用程序目标中运行得很完美,但在共享扩展程序中尝试旋转相机拍摄的图像时崩溃。如何在共享扩展程序中旋转图像?或者从照片库中加载已在正确方向上的图像是否可行。
extension UIImage {

    func fixOrientation() -> UIImage {
        switch imageOrientation {
        case .up:
            return self
        default:
            UIGraphicsBeginImageContextWithOptions(size, false, scale)
            draw(in: CGRect(origin: .zero, size: size)) //Thread 1: EXC_RESOURCE RESOURCE_TYPE_MEMORY (limit=120 MB, unused=0x0)
            let result = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
            return result!
        }
    }
}


崩溃截图:

崩溃截图 #1 崩溃截图 #2


你在主线程上操作吗? - Gal
@Gal,是的,我有。 - ChikabuZ
我认为你正在使用太多的内存。 - Gal
你会显示这张图片吗? 如果不需要,你可以通过CIImage等其他方式来修正方向。 如果要显示它,可能会使用与绘制调用相同的内存。 然后,你可以尝试在实例化图形上下文时使用较小的尺寸,然后生成一个较小的图像。 - riadhluke
@riadhluke,是的,我正在显示图像。相比于简单的显示,draw调用会使用更多的内存... - ChikabuZ
显示剩余2条评论
3个回答

6
首先很明显,您遇到了内存崩溃问题。根据App Extension Programming Guide

运行应用扩展的内存限制明显低于前台应用的内存限制。在两个平台上,系统可能会因为用户想返回主机应用的主要目标而积极终止扩展。

从错误信息可以看出您超过了120MB。但您可能想知道为什么会占用这么多内存。

根据Optimizing Images Written by Jordan Morgan

iOS从图像的尺寸中获取其内存占用情况,而实际文件大小与此关系不大。

所以如果我们计算4032 x 3024照片的大小,对于4位色彩,它将占用46MB,对于8位色彩,它将占用79MB。非常大,但仍然低于限制...

问题是 - 您有两个图像副本。一个是原始副本,另一个是旋转副本。

为解决此问题,您需要仅加载旋转图像,而无需加载原始副本。可以使用Image I/O Framework实现此操作:

extension UIImage {
    static func imageWithFixedOrientation(at url: URL) -> UIImage? {
        guard let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil) else { return nil }

        guard let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? Dictionary<CFString, Any> else { return nil }

        guard
            let width = imageProperties[kCGImagePropertyPixelWidth] as? CGFloat,
            let height = imageProperties[kCGImagePropertyPixelHeight] as? CGFloat
            else { return nil }

        let options: [NSString: Any] = [
            kCGImageSourceThumbnailMaxPixelSize: max(width, height),
            kCGImageSourceCreateThumbnailFromImageAlways: true,
            kCGImageSourceCreateThumbnailWithTransform: true
        ]

        guard let cgImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) else { return nil }

        return UIImage(cgImage: cgImage)
    }
}

在示例应用程序中:
extension ViewController: UIImagePickerControllerDelegate & UINavigationControllerDelegate {
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        picker.dismiss(animated: true)
        guard let url = info[.imageURL] as? URL else { return }

        let image = UIImage.imageWithFixedOrientation(at: url)
    }
}

它将内存峰值从180+MB降至仅80MB。


1
如果我将图像大小限制在2500x2500以内,它可以正常工作,但如果使用3000x3000或原始的4032 x 3024大小,则会崩溃。 - ChikabuZ

0

您可以尝试使用更优化的UIGraphicsImageRendererFormat而不是旧的UIGraphicsBeginImageContextWithOptions,您可以在post中找到更多信息。因此,您的代码将如下所示:

func fixOrientation() -> UIImage {
    let format = UIGraphicsImageRendererFormat()
    format.scale = scale
    format.prefersExtendedRange = true
    let renderer = UIGraphicsImageRenderer(size: size, format: format)
    let image = renderer.image(actions: { context in

        var workSize = size;
        workSize.width = floor(workSize.width / scale)
        workSize.height = floor(workSize.height / scale)

        // if the orientation is already correct
        // if image.imageOrientation == .up { draw image }

        var transform = CGAffineTransform.identity

        //TO-DO - set transform depends on current image orientation
        //transform =

        let ctx = context.cgContext
        ctx.concatenate(transform)

        guard let cgImageCopy = cgImage else {
            return
        }

        switch imageOrientation {
        case .left, .leftMirrored, .right, .rightMirrored:
            ctx.draw(cgImageCopy, in: CGRect(x: 0.0, y:0.0, width: workSize.height, height: workSize.width))
            break;

        default:
            ctx.draw(cgImageCopy, in: CGRect(origin: .zero, size: workSize))
            break;
        }
    })

    return image
}

它在这一行崩溃了:let image = renderer.image(actions: { context in - ChikabuZ
请问您能否提供有关崩溃的具体信息,因为我无法重现它? - ATV
崩溃信息如下:Thread 1: EXC_RESOURCE RESOURCE_TYPE_MEMORY (limit=120 MB, unused=0x0)。建议您在真机上测试,并使用相机拍摄照片。 - ChikabuZ
或者在这里:ctx.draw(cgImageCopy, in: CGRect(origin: .zero, size: workSize)) - ChikabuZ

-1

你尝试在didFinishPicking委托方法中使用你提供的函数了吗?

func photoLibrary() {
    if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {
        let myPickerController = UIImagePickerController()
        myPickerController.delegate = self
        myPickerController.sourceType = .photoLibrary
        self.present(myPickerController, animated: true, completion: nil)
    }
}

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
    picker.dismiss(animated: true)

    guard let image = info[.originalImage] as? UIImage else {
        print("No image found")
        return
    }

    // Flip the image here
    self.mainImageView.image = image.fixOrientation()
}

这是共享扩展,我不使用这个委托方法,它也没有帮助。 - ChikabuZ

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接