6

I have this extension and it works perfect in app target but crash in share extension when trying to rotate image captured on camera. How to rotate image in share extension? Or maybe it possible to load image from Photo Library already in right orientation.

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!
        }
    }
}


Crash screenshots:

crash screenshot #1 crash screenshot #2

ChikabuZ
  • 10,031
  • 5
  • 63
  • 86
  • You're doing this on main thread? – Gal Jul 17 '19 at 09:32
  • @Gal, yes, I do. – ChikabuZ Jul 17 '19 at 09:34
  • I think you're using to much memory. – Gal Jul 17 '19 at 10:42
  • will you be displaying the image? if not you can use other ways of fixing the orientation, like thru a CIImage. Displaying it will probably use the same memory as the draw call. you can try using a smaller size when instantiating the graphics context then and generate a smaller image – riadhluke Jul 17 '19 at 16:52
  • @riadhluke, yes, I'm displaying the image. `draw` call use much more memory than displaying... – ChikabuZ Jul 17 '19 at 16:56
  • You can try to limit the size of the graphics context. You can limit it to the size of the image view you'll be displaying the image on. I've tried using ```var size = self.size; size.width /= 5; size.height /= 5``` and it didn't crash on a device – riadhluke Jul 17 '19 at 17:03
  • Did you try to rotate few images at same time or just one? Is it panorama image? May you please show code that uses `fixOrientation`? – Sergey Kuryanov Jul 18 '19 at 23:23

3 Answers3

6

First of all it is clear that you have a memory crash. According to App Extension Programming Guide:

Memory limits for running app extensions are significantly lower than the memory limits imposed on a foreground app. On both platforms, the system may aggressively terminate extensions because users want to return to their main goal in the host app.

And from error it is clear that you exceed 120 mb. But you might wonder what is took so much memory.

According to Optimizing Images Written by Jordan Morgan:

iOS essentially derives its memory hit from an image’s dimensions - whereas the actual file size has much less to do with it.

So if we calculate size or 4032 x 3024 photo it will be... 46mb for 4 bit color and 79mb for 8 bit color. Pretty big, but still less that a limit...

Thing is - you have two copies of your image. One is original and second one - rotated.

To solve this issue you need load only rotated image into memory, without original. This can be done with 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)
    }
}

In sample app:

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)
    }
}

it reduced memory peaks from 180+mb to just 80mb.

Sergey Kuryanov
  • 6,114
  • 30
  • 52
  • 1
    It works for me if I limit image size to 2500x2500, with 3000x3000 or original 4032 x 3024 it crash. – ChikabuZ Jul 19 '19 at 14:55
0

You may try to use more optimized UIGraphicsImageRendererFormat instead of using old UIGraphicsBeginImageContextWithOptions, you can find more information in this post. So your code will look something like below:

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
}
ATV
  • 338
  • 1
  • 4
  • 19
  • it crashes on this line: `let image = renderer.image(actions: { context in` – ChikabuZ Jul 17 '19 at 09:39
  • Could you please provide any specific information about the crash, because I can not reproduce it? – ATV Jul 17 '19 at 09:49
  • The crash the same: `Thread 1: EXC_RESOURCE RESOURCE_TYPE_MEMORY (limit=120 MB, unused=0x0)`. You should check on real device with photo from camera. – ChikabuZ Jul 17 '19 at 10:05
  • Or here: `ctx.draw(cgImageCopy, in: CGRect(origin: .zero, size: workSize))` – ChikabuZ Jul 17 '19 at 10:10
-1

Did you try using the function you provided within an didFinishPicking delegate method?

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()
}
kotabear
  • 11
  • 1