-2

I'm trying to allow the user to mark an area with her hand and then the image will be cropped to that particular area. The problem is that i've got everything wrong with the cropping sizing, positioning and scaling.

I'm definitely missing something but am not sure what is it that I'm doing wrong? Here is the original image along with the crop rectangle that the user can mark with his finger:

enter image description here

This is the broken outcome:

enter image description here

Here is my custom UIImageView where I intercept the touch events. This is just for the user to draw the rectangle...

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    if let touch = touches.first?.preciseLocation(in: self){
        self.newTouch = touch
    }
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    if let touch = touches.first {
        let currentPoint = touch.preciseLocation(in: self)
        reDrawSelectionArea(fromPoint: newTouch, toPoint: currentPoint)
    }
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    self.newBoxSelected?(box.frame)
    box.frame = CGRect.zero //reset overlay for next tap
}

func reDrawSelectionArea(fromPoint: CGPoint, toPoint: CGPoint) {
    
    //Calculate rect from the original point and last known point
    let rect = CGRect(x: min(fromPoint.x, toPoint.x),
                      y: min(fromPoint.y, toPoint.y),
                      width: abs(fromPoint.x - toPoint.x),
                      height: abs(fromPoint.y - toPoint.y));
    
    box.frame = rect
}

This is the actual cropping logic. What am I doing wrong here?

   func cropToBounds(image: UIImage, newFrame: CGRect) -> UIImage {
        
        let xScaleFactor = (imageView.image?.size.width)! / (self.imageView.bounds.size.width)
        let yScaleFactor = (imageView.image?.size.height)! / (self.imageView.bounds.size.height)

        let contextImage: UIImage = UIImage(cgImage: image.cgImage!)
        
        print("NewFrame is: \(newFrame)")
     
        let xPos = newFrame.minX * xScaleFactor
        let yPos = newFrame.minY * yScaleFactor
        let width = newFrame.size.width * xScaleFactor
        let height = newFrame.size.height * xScaleFactor
        
        print("xScaleFactor: \(xScaleFactor)")
        print("yScaleFactor: \(yScaleFactor)")

        print("xPos: \(xPos)")
        print("yPos: \(yPos)")
        print("width: \(width)")
        print("height: \(height)")
                
//        let rect: CGRect = CGRect(x: xPos, y: yPos , width: width, height: height)
        let rect: CGRect = CGRect(x: xPos, y: yPos , width: width, height: height)

          
        // Create bitmap image from context using the rect
        let imageRef: CGImage = contextImage.cgImage!.cropping(to: rect)!
    
        // Create a new image based on the imageRef and rotate back to the original orientation
        let image: UIImage = UIImage(cgImage: imageRef, scale: image.scale, orientation: image.imageOrientation)
        
        return image
    }
Yosi199
  • 1,745
  • 4
  • 22
  • 47
  • Please post code, not screen-shots of code. That makes it possible for someone to try to run your code to help. – DonMag Jul 15 '20 at 13:49
  • @DonMag thanks, added code instead of images – Yosi199 Jul 15 '20 at 13:58
  • It *looks* like you are getting the x-position and width relative to the "drawn" rectangle.. and it *looks* like the height is correct, but the y-position is wrong. What is the `Content Mode` of your "drawable image view"? If it's `.scaleAspectFit` it sounds like you are not accounting for that. – DonMag Jul 15 '20 at 14:03
  • Just checked now and it is scaleAspectFit. How can I account for that? – Yosi199 Jul 15 '20 at 14:07
  • As for the X position - I'm taking it from the location point I got when I intercepted the touch event – Yosi199 Jul 15 '20 at 14:09

1 Answers1

0

You can import AVFoundation and use func AVMakeRect(aspectRatio: CGSize, insideRect boundingRect: CGRect) -> CGRect to get the actual rectangle of the aspect-fit image:

// import this in your class
import AVFoundation
    

then:

    guard let img = imageView.image else {
        fatalError("imageView has no image!")
    }
    // Original size which you want to preserve the aspect ratio of
    let aspect: CGSize = img.size
    
    // Rect to fit that size within
    let rect: CGRect = CGRect(x: 0, y: 0, width: imageView.bounds.size.width, height: imageView.bounds.size.height)
    
    // resulting size
    let resultSize: CGSize = AVMakeRect(aspectRatio: aspect, insideRect: rect).size
    
    // get y-position (1/2 of (imageView height - resulting size)
    let resultOrigin: CGPoint = CGPoint(x: 0.0, y: (imageView.bounds.size.height - resultSize.height) / 2.0)
    
    // this is the actual rect for the aspect-fit image
    let actualImageRect: CGRect = CGRect(origin: resultOrigin, size: resultSize)
    
    print(actualImageRect)

    // you now have the actual rectangle for the image
    // on which you can base your scale calculations

}
DonMag
  • 69,424
  • 5
  • 50
  • 86
  • So if I got you right this code is meant to run on the original (un-cropped) image and return it's rect value (while considering the aspectFit mode)? – Yosi199 Jul 15 '20 at 14:52
  • @Yosi199 - this will calculate the rectangle used by the aspect-fit image in the image view. However, the link to the duplicate question provides a complete solution. Probably a better reference to work from. – DonMag Jul 15 '20 at 14:55