Swift - Clipping image with UIBezierPath
Asked Answered
B

3

1

I would like to clip a bezier path from an image. For some reason, the image remains the unclipped. And how do I position the path so it would be properly cut?

extension UIImage {

func imageByApplyingMaskingBezierPath(_ path: UIBezierPath, _ pathFrame: CGFrame) -> UIImage {

    UIGraphicsBeginImageContext(self.size)
    let context = UIGraphicsGetCurrentContext()!
    context.saveGState()

    path.addClip()
    draw(in: CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height))

    let maskedImage = UIGraphicsGetImageFromCurrentImageContext()!

    context.restoreGState()
    UIGraphicsEndImageContext()

    return maskedImage
}

}

example

Badillo answered 16/4, 2018 at 8:47 Comment(3)
try removing the context.saveGState() and context.restoreGState()Selfforgetful
If you draw the path instead of clipping it, does it appear in the size and location that you expect to clip?Subaudition
@DavidRönnqvist The path is what I want to be a black hole. and the image is what I want to be cropped. I've added an example imageBadillo
S
4

You need to add your path.cgPath to your current context, also you need to remove context.saveGState() and context.restoreGState()

Use this code

func imageByApplyingMaskingBezierPath(_ path: UIBezierPath, _ pathFrame: CGRect) -> UIImage {

            UIGraphicsBeginImageContext(self.size)
            let context = UIGraphicsGetCurrentContext()!

            context.addPath(path.cgPath)
            context.clip()
            draw(in: CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height))

            let maskedImage = UIGraphicsGetImageFromCurrentImageContext()!

            UIGraphicsEndImageContext()

            return maskedImage
        }

Using it

let testPath = UIBezierPath()
testPath.move(to: CGPoint(x: self.imageView.frame.width / 2, y: self.imageView.frame.height))
testPath.addLine(to: CGPoint(x: 0, y: 0))
testPath.addLine(to: CGPoint(x: self.imageView.frame.width, y: 0))
testPath.close()

self.imageView.image = UIImage(named:"Image")?.imageByApplyingMaskingBezierPath(testPath, self.imageView.frame)

Result

enter image description here

Selfforgetful answered 16/4, 2018 at 9:5 Comment(3)
@luda did my answer help you?Selfforgetful
Hi Reinier Melain, I need to crop the inner part of the image and not the outer. Or maybe I didn't understand you? I have a UIBezierPath of the area I need to remove from the image. Check out the example image I've addedBadillo
@Badillo did you find a way to do it?Pelasgian
S
0

You can try like this.

 var path = UIBezierPath()
 var shapeLayer = CAShapeLayer()
 var cropImage = UIImage()


 override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    if let touch = touches.first as UITouch?{
        let touchPoint = touch.location(in: self.YourimageView)
        print("touch begin to : \(touchPoint)")
        path.move(to: touchPoint)
    }
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    if let touch = touches.first as UITouch?{
        let touchPoint = touch.location(in: self.YourimageView)
        print("touch moved to : \(touchPoint)")
        path.addLine(to: touchPoint)
        addNewPathToImage()
    }
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    if let touch = touches.first as UITouch?{
        let touchPoint = touch.location(in: self.YourimageView)
        print("touch ended at : \(touchPoint)")
        path.addLine(to: touchPoint)
        addNewPathToImage()
        path.close()
    }
}
 func addNewPathToImage(){
    shapeLayer.path = path.cgPath
    shapeLayer.strokeColor = strokeColor.cgColor
    shapeLayer.fillColor = UIColor.clear.cgColor
    shapeLayer.lineWidth = lineWidth
    YourimageView.layer.addSublayer(shapeLayer)
}
   func cropImage(){

    UIGraphicsBeginImageContextWithOptions(YourimageView.bounds.size, false, 1)
    tempImageView.layer.render(in: UIGraphicsGetCurrentContext()!)
    let newImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    self.cropImage = newImage!
    }


    @IBAction func btnCropImage(_ sender: Any) {
         cropImage()
    }

Once you draw a path on particular button action just call your imageByApplyingMaskingBezierPath

Sibert answered 16/4, 2018 at 8:59 Comment(3)
Hi kalpash, I am doing something like that. In the end, I have a UIBezierPath of the area I need to remove from the image. How do I do the last part? Check out the example image I've addedBadillo
What is tempImageView?Badillo
It is just image view name ignore it.Sibert
A
0

Here is Swift code to get clips from an image based on a UIBezierPath and it is very quickly implemented. This method works if the image is already being shown on the screen, which will most often be the case. The resultant image will have a transparent background, which is what most people want when they clip part of a photo image. You can use the resultant clipped image locally because you will have it in a UIImage object that I called imageWithTransparentBackground. This very simple code also shows you how to save the image to the camera roll, and how to also put it right into the pasteboard so a user can paste that image directly into a text message, paste it into Notes, an email, etc. Note that in order to write the image to the camera roll, you need to edit the info.plist and provide a reason for “Privacy - Photo Library Usage Description”

import Photos // Needed if you save to the camera roll

Provide a UIBezierPath for clipping. Here is my declaration for one.

let clipPath = UIBezierPath()

Populate the clipPath with some logic of your own using some combination of commands. Below are a few I used in my drawing logic. Provide CGPoint equivalents for aPointOnScreen, etc Build your path relative to the main screen as self.view is this apps ViewController (for this code), and self.view.layer is rendered through the clipPath.

                        
clipPath.move(to: aPointOnScreen)
clipPath.addLine(to: otherPointOnScreen)
clipPath.addLine(to: someOtherPointOnScreen)

clipPath.close()

This logic uses all of the devices screen as the context size. A CGSize is declared for that. fullScreenX and fullScreenY are my variables where I have already captured the devices width and height. It is nice if the photo you are clipping from is already zoomed into and is an adequate size as shown on the whole of the screen. What you see, is what you get.

                
let mainScreenSize =  CGSize(width: fullScreenX, height: fullScreenY)

// Get an empty context
UIGraphicsBeginImageContext(mainScreenSize)
// Specify the clip path
clipPath.addClip()
// Render through the clip path from the whole of the screen.
self.view.layer.render(in: UIGraphicsGetCurrentContext()!)
// Get the clipped image from the context
let image : UIImage = UIGraphicsGetImageFromCurrentImageContext()!
// Done with the context, so end it.
UIGraphicsEndImageContext()
                
// The PNG data has the alpha channel for the transparent background
let imageData = image.pngData()
// Below is the local UIImage to use within your code
let imageWithTransparentBackground = UIImage.init(data: imageData!)
// Make the image available to the pasteboard.
UIPasteboard.general.image = imageWithTransparentBackground
// Save the image to the camera roll.
PHPhotoLibrary.shared().performChanges({
                    PHAssetChangeRequest.creationRequestForAsset(from: imageWithTransparentBackground!)
                }, completionHandler: { success, error in
                    if success {
                        //
                    }
                    else if let error = error {
                        //
                    }
                    else {
                        //
                    }
                })
Aesthetic answered 1/1, 2021 at 21:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.