0

I have a class setup that allows users to add an image from their library, crop it and save it.

The code is set up so that if the retrieved image is portrait, a portrait shaped border appears to all them to align before cropping and if Landscape, a landscaped border appears.

If the image selected is a regular shaped image, all works well. However, if the image retrieved is portrait and not of a regular ratio (meaning closer to a square shape while not actually being square), the image rotates after being cropped. It seems as thought the system is treating it like a landscape image.

Here is an example of before and after crop. Even if I zoom in and make the image cover the entire screen, it rotates the image:

enter image description here

enter image description here

import Foundation
import UIKit

class SelectImageViewController: UIViewController, UIImagePickerControllerDelegate,UINavigationControllerDelegate,UIScrollViewDelegate{


    @IBOutlet weak var imageView: UIImageView!

    @IBOutlet weak var imageConstraintTop: NSLayoutConstraint!
    @IBOutlet weak var imageConstraintRight: NSLayoutConstraint!
    @IBOutlet weak var imageConstraintLeft: NSLayoutConstraint!
    @IBOutlet weak var imageConstraintBottom: NSLayoutConstraint!

    var lastZoomScale: CGFloat = -1
    var imageName: String = ""
    var userPhotoUUID = UUID().uuidString
    let userDefault = UserDefaults.standard
    var userDatabase: UserDatabase = UserDatabase()
    let picker = UIImagePickerController()


    @IBOutlet var scrollView: UIScrollView!{
        didSet{
            scrollView.delegate = self
            scrollView.minimumZoomScale = 1.0
            scrollView.maximumZoomScale = 5.0
        }
    }

    @IBOutlet weak var ratioSelector: UISegmentedControl!

    @IBOutlet var cropAreaViewL: CropAreaViewL!

    var cropAreaL:CGRect{
        get{
            let factor = imageView.image!.size.width/view.frame.width
            let scale = 1/scrollView.zoomScale
            let imageFrame = imageView.imageFrame()
            let x = (scrollView.contentOffset.x + cropAreaViewL.frame.origin.x - imageFrame.origin.x) * scale * factor
            let y = (scrollView.contentOffset.y + cropAreaViewL.frame.origin.y - imageFrame.origin.y) * scale * factor
            let width = cropAreaViewL.frame.size.width * scale * factor
            let height = cropAreaViewL.frame.size.height * scale * factor
            return CGRect(x: x, y: y, width: width, height: height)
        }
    }

    @IBOutlet var cropAreaViewP: CropAreaViewP!

    var cropAreaP:CGRect{
        get{
            let factor = imageView.image!.size.height/view.frame.height
            let scale = 1/scrollView.zoomScale
            let imageFrame = imageView.imageFrame()
            let x = (scrollView.contentOffset.x + cropAreaViewP.frame.origin.x - imageFrame.origin.x) * scale * factor
            let y = (scrollView.contentOffset.y + cropAreaViewP.frame.origin.y - imageFrame.origin.y) * scale * factor
            let width = cropAreaViewP.frame.size.width * scale * factor
            let height = cropAreaViewP.frame.size.height * scale * factor
            return CGRect(x: x, y: y, width: width, height: height)
        }
    }


    fileprivate var speciePhotos: Array<SpeciePhotoModel> = [SpeciePhotoModel]()


    func randomNumber(range: ClosedRange<Int> = 30000...99998) -> Int {
        let min = range.lowerBound
        let max = range.upperBound
        return Int(arc4random_uniform(UInt32(1 + max - min))) + min
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "+", style: .plain, target: self, action: #selector(SelectImageViewController.add(_:)))

        let id = randomNumber()
        userDefault.set(id, forKey: "photoID")

        self.cropAreaViewP.isHidden = true
        self.cropAreaViewL.isHidden = true
        self.cropAreaViewL.layer.borderColor = (UIColor.red).cgColor
        self.cropAreaViewL.layer.borderWidth = 1.0
        self.cropAreaViewP.layer.borderColor = (UIColor.red).cgColor
        self.cropAreaViewP.layer.borderWidth = 1.0
        self.add.layer.cornerRadius = 6.0
        self.ratioSelector.layer.cornerRadius = 6.0
        self.tabBarController?.tabBar.isHidden = true
        self.add.isHidden = true
        self.ratioSelector.isHidden = true

        updateZoom()

    }


    func updateConstraints() {
        if let image = imageView.image {
            let imageWidth = image.size.width
            let imageHeight = image.size.height

            let viewWidth = scrollView.bounds.size.width
            let viewHeight = scrollView.bounds.size.height

            // center image if it is smaller than the scroll view
            var hPadding = (viewWidth - scrollView.zoomScale * imageWidth) / 2
            if hPadding < 0 { hPadding = 0 }

            var vPadding = (viewHeight - scrollView.zoomScale * imageHeight) / 2
            if vPadding < 0 { vPadding = 0 }

            imageConstraintLeft.constant = hPadding
            imageConstraintRight.constant = hPadding

            imageConstraintTop.constant = vPadding
            imageConstraintBottom.constant = vPadding

            view.layoutIfNeeded()
        }
    }

    fileprivate func updateZoom() {
        if let image = imageView.image {
            var minZoom = min(scrollView.bounds.size.width / image.size.width,
                              scrollView.bounds.size.height / image.size.height)

            if minZoom > 1 { minZoom = 1 }

            scrollView.minimumZoomScale = 0.3 * minZoom

            // Force scrollViewDidZoom fire if zoom did not change
            if minZoom == lastZoomScale { minZoom += 0.000001 }

            scrollView.zoomScale = minZoom
            lastZoomScale = minZoom
        }
    }


    @IBAction func ratioSelector(_ sender: AnyObject) {

        switch ratioSelector.selectedSegmentIndex
        {
        case 0:// Landscape

            self.cropAreaViewP.isHidden = true
            self.cropAreaViewL.isHidden = false

        case 1: // Portrait

            self.cropAreaViewL.isHidden = true
            self.cropAreaViewP.isHidden = false

        default:
            break; 
        } 
    }


    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        dismiss(animated: true, completion: nil)
    }

    @IBOutlet weak var add : UIButton!


    @IBAction func add(_ sender: UIButton) {

        imageView.image = nil

        let picker = UIImagePickerController()
        picker.delegate = self
        picker.sourceType = .photoLibrary
        picker.allowsEditing = false
        self.present(picker, animated: true, completion: nil)

        self.ratioSelector.isHidden = false
        self.add.isHidden = false

        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Crop", style: .plain, target: self, action: #selector(SelectImageViewController.crop(_:)))

    }

    @IBAction func change(_ sender: UIButton) {

        imageView.image = nil

        let picker = UIImagePickerController()
        picker.delegate = self
        picker.sourceType = .photoLibrary
        picker.allowsEditing = false
        self.present(picker, animated: true, completion: nil)

        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Crop", style: .plain, target: self, action: #selector(SelectImageViewController.crop(_:)))

    }


    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {

        let chosenImage = info[UIImagePickerControllerOriginalImage] as! UIImage

        if chosenImage.size.height > chosenImage.size.width
        {
            self.cropAreaViewL.isHidden = true
            self.cropAreaViewP.isHidden = false
            self.ratioSelector.selectedSegmentIndex = 1
            imageView.image = chosenImage
        }
        else
        {
            self.cropAreaViewP.isHidden = true
            self.cropAreaViewL.isHidden = false
            self.ratioSelector.selectedSegmentIndex = 0
            imageView.image = chosenImage
        }
        self.dismiss(animated: true, completion: nil)
    }


    @IBAction func crop(_ sender: UIButton) {

        if cropAreaViewP.isHidden == true {
            self.cropAreaViewL.layer.borderColor = (UIColor.clear).cgColor
            let croppedCGImage = imageView.image?.cgImage?.cropping(to: cropAreaL)
            let croppedImage = UIImage(cgImage: croppedCGImage!)
            imageView.image = croppedImage
            scrollView.zoomScale = 1
        } else {
            self.cropAreaViewP.layer.borderColor = (UIColor.clear).cgColor
            let croppedCGImage = imageView.image?.cgImage?.cropping(to: cropAreaP)
            let croppedImage = UIImage(cgImage: croppedCGImage!)
            imageView.image = croppedImage
            scrollView.zoomScale = 1
        }

        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Next", style: .plain, target: self, action: #selector(SelectImageViewController.saveButtonAction(_:)))


    }
}

extension UIImageView{
    func imageFrame()->CGRect{
        let imageViewSize = self.frame.size
        guard let imageSize = self.image?.size else{return CGRect.zero}
        let imageRatio = imageSize.width / imageSize.height
        let imageViewRatio = imageViewSize.width / imageViewSize.height

        if imageRatio < imageViewRatio { // Portrait
            let scaleFactor = imageViewSize.height / imageSize.height
            let width = imageSize.width * scaleFactor
            let topLeftX = (imageViewSize.width - width) * 0.5
            return CGRect(x: topLeftX, y: 0, width: width, height: imageViewSize.height)
        }else{  // Landscape
            let scaleFactor = imageViewSize.width / imageSize.width
            let height = imageSize.height * scaleFactor
            let topLeftY = (imageViewSize.height - height) * 0.5
            return CGRect(x: 0, y: topLeftY, width: imageViewSize.width, height: height)
        }
    }
}


class CropAreaViewL: UIView {

    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        return false
    }
}

class CropAreaViewP: UIView {

    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        return false
    }

}

Any help would be huge.

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
David Sanford
  • 735
  • 12
  • 26
  • Not related to your problem which can be probably solved redrawing your image. You can use max method to make sure you don't get a value lower than zero `let vPadding = max((viewHeight - scrollView.zoomScale * imageHeight) / 2, 0)` – Leo Dabus Mar 05 '17 at 18:21
  • Thanks Leo, can you give me an example of redrawing? Also, I am about to board a flight and will try your vPadding idea soon – David Sanford Mar 06 '17 at 05:14
  • 1
    Leo, the vPadding fixed my issue. Please put that as the answer and I will accept it. – David Sanford Mar 06 '17 at 08:51
  • 1
    about redrawing the image you can check this answer http://stackoverflow.com/a/42591183/2303865 and this one that fixes incorrect rotation when saving PNG http://stackoverflow.com/questions/42098390/swift-png-image-being-saved-with-incorrect-orientation/42098812#42098812 – Leo Dabus Mar 06 '17 at 15:49

1 Answers1

1

You can use max method to make sure you don't get a value lower than zero:

let vPadding = max((viewHeight - scrollView.zoomScale * imageHeight) / 2, 0)

If you need to make your image squared you can do it as follow:

extension UIImage {

    var isPortrait:  Bool    { return size.height > size.width }
    var isLandscape: Bool    { return size.width > size.height }
    var breadth:     CGFloat { return min(size.width, size.height) }
    var breadthSize: CGSize  { return CGSize(width: breadth, height: breadth) }
    var squared: UIImage? {
        guard let cgImage = cgImage?.cropping(to:
            CGRect(origin: CGPoint(x: isLandscape ? floor((size.width-size.height)/2) : 0, y: isPortrait  ? floor((size.height-size.width)/2) : 0),
                   size: breadthSize)) else { return nil }
        return UIImage(cgImage: cgImage)
    }
}

To fix the orientation issue you need to redraw your image you can use the flatten property from this answer.


Playground:

let profilePicture = UIImage(data: try! Data(contentsOf: URL(string:"https://i.stack.imgur.com/Xs4RX.jpg")!))!
if let squared = profilePicture.squared {
    squared

}
Community
  • 1
  • 1
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • 1
    Leo, it appears I spoke too soon. It fixed it on the simulator, but now I am testing it on my iPhone and it still rotates. Sorry, but I do not understand the redraw comment above. I checked the sites and it seems unrelated, but that is probably due to my inexperience – David Sanford Mar 06 '17 at 21:03
  • 1
    You need to begin a new context at the desired size and draw the cropped image there. Can you create a sample project so I can add the redraw part to you? Do you have Gist (Github) ? – Leo Dabus Mar 06 '17 at 21:04
  • 1
    @DavidSanford Check my edit. I have added a sample showing how to crop your image automatically depending on its size. – Leo Dabus Mar 06 '17 at 21:15
  • 1
    Thanks, I see the direction you are going. One note: my finished image will always be 16:9 or 9:16. I am wanting to be sure that is someone loads an image that is not already the above dimensions, they can crop accordingly – David Sanford Mar 06 '17 at 21:17
  • 1
    @DavidSanford Do you have a sample project of your code above so I can try to fix the rotation issue? – Leo Dabus Mar 06 '17 at 21:25
  • Thanks Leo, I will create one. However, I am about to board another flight. I will do it tomorrow late morning. I hope you don't mind – David Sanford Mar 06 '17 at 21:26
  • @DavidSanford no problem take your time and have a good flight – Leo Dabus Mar 06 '17 at 21:26
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/137385/discussion-between-david-sanford-and-leo-dabus). – David Sanford Mar 06 '17 at 21:27