How can I 'cut' a transparent hole in a UIImage?
Asked Answered
M

5

14

I'm trying to cut an transparent square in a UIImage, however I honestly have no idea where/how to start.

Any help would be greatly appreciated.

Thanks!

Modal answered 4/5, 2014 at 3:4 Comment(5)
Do you want to cut the hole in the image or in the UIImageView that displays it?Kaiak
Look at how I punch a circular hole in a UIImageView (and its image) in this code: https://mcmap.net/q/753461/-drawing-on-top-of-uiimageview-to-make-the-image-transparent You would do exactly the same except you'd draw a square instead of a circle.Kaiak
@matt: Cutting a hole in the actual UIImageView would actually be perfect! Except, I don't know how to use that code you linked :p what's a CLayer and CGContextRef? Thanks!!Modal
I'm delighted that you want to learn what's really going on here and not just copy a bunch of code blindly. Here's my full explanation all about how to draw in iOS, including everything involved in that code except the actual "masking the layer" part: apeth.com/iOSBook/ch15.html Here's my discussion of layers (you need to know about this because punching a hole involves masking a view's layer): apeth.com/iOSBook/ch16.html (and see especially apeth.com/iOSBook/ch16.html#_shadows_borders_and_more)Kaiak
@matt: I'll read up on this, thank you so much! Tons of useful informationModal
K
21

Presume that your image is being displayed in a view - probably a UIImageView. Then we can punch a rectangular hole in that view by masking the view's layer. Every view has a layer. We will apply to this view's layer a mask which is itself a layer containing an image, which we will generate in code. The image will be black except for a clear rectangle somewhere in the middle. That clear rectangle will cause the hole in the image view.

So, let self.iv be this UIImageView. Try running this code:

CGRect r = self.iv.bounds;
CGRect r2 = CGRectMake(20,20,40,40); // adjust this as desired!
UIGraphicsBeginImageContextWithOptions(r.size, NO, 0);
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextAddRect(c, r2);
CGContextAddRect(c, r);
CGContextEOClip(c);
CGContextSetFillColorWithColor(c, [UIColor blackColor].CGColor);
CGContextFillRect(c, r);
UIImage* maskim = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CALayer* mask = [CALayer layer];
mask.frame = r;
mask.contents = (id)maskim.CGImage;
self.iv.layer.mask = mask;

For example, in this image, the white square is not a superimposed square, it is a hole, showing the white of the window background behind it:

enter image description here

EDIT: I feel obligated, since I mentioned it in a comment, to show how to do the same thing with a CAShapeLayer. The result is exactly the same:

CGRect r = self.iv.bounds;
CGRect r2 = CGRectMake(20,20,40,40); // adjust this as desired!
CAShapeLayer* lay = [CAShapeLayer layer];
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, nil, r2);
CGPathAddRect(path, nil, r);
lay.path = path;
CGPathRelease(path);
lay.fillRule = kCAFillRuleEvenOdd;
self.iv.layer.mask = lay;
Kaiak answered 4/5, 2014 at 4:25 Comment(5)
This worked perfectly, thanks a ton Matt! I'm still reading up on the stuff you linked, but its awesome to know I have working code to reference :D Thanks again man, you're awesome! :)Modal
The really cool part (one of many) is that this works on any UIView. You can punch a hole in anything. Moreover, instead of punching a pure hole, you can punch a semi-transparent hole. Also, what we did here is not at all the only way; we could have used a CAShapeLayer as our mask, instead of making an image.Kaiak
That /is/ pretty cool!! I'll be sure to keep this in mind if I ever need to do something like this again :DModal
Edited my answer to show how to do it with a shape layer. You might find that easier when getting started. Also (get this) a shape layer lets you animate the shape it displays, so I think you could probably move or otherwise alter the hole in an animated way.Kaiak
That's so cool! And yeah, that makes a bit more sense, however I still have quite a bit of learning to do before all of this 'clicks' :P thanks again man! :DModal
P
15

Here's a simple Swift function cut#hole#inView to copy and paste for 2017

func cut(hole: CGRect, inView view: UIView) {
    let path: CGMutablePath = CGMutablePath()
    path.addRect(view.bounds)
    path.addRect(hole)

    let shapeLayer = CAShapeLayer()
    shapeLayer.path = path
    shapeLayer.fillRule = .evenOdd

    view.layer.mask = shapeLayer
}
Premonitory answered 19/2, 2017 at 18:49 Comment(0)
M
4

Just needed the Version from @Fattie, thanks again! Here is the updated Code for Swift 5.1:

private func cut(holeRect: CGRect, inView view: UIView) {
    let combinedPath = CGMutablePath()
    combinedPath.addRect(view.bounds)
    combinedPath.addRect(holeRect)

    let maskShape = CAShapeLayer()
    maskShape.path = combinedPath
    maskShape.fillRule = .evenOdd

    view.layer.mask = maskShape
}

If you want the cutout to have rounded corners you can replace combinedPath.addRect(holeRect) with rectanglePath.addRoundedRect(in: holeRect, cornerWidth: 8, cornerHeight: 8).

Mccauley answered 24/4, 2020 at 13:59 Comment(0)
C
0

Here's the updated code to cut a hole in an UIImage (instead of UIView) using Swift:

func cut(hole: CGRect, inView image: UIImage) -> UIImage? {
        UIGraphicsBeginImageContext(image.size)
        image.draw(at: CGPoint.zero)
        let context = UIGraphicsGetCurrentContext()!
        let bez = UIBezierPath(rect: hole)
        context.addPath(bez.cgPath)
        context.clip()
        context.clear(CGRect(x:0,y:0,width: image.size.width,height: image.size.height))
        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        return newImage
    }
Cumshaw answered 19/11, 2022 at 15:46 Comment(0)
P
0

If you are working with a graphics context, the answer is this simple:

Use the "clear" command which exists for the purpose.

context.clear(someRect)

That's the whole thing. .clear

(For example, here's a red image with a square hole.)

let frame = CGRect(origin: .zero, size: sz)
let fmt = UIGraphicsImageRendererFormat()
fmt.scale = 1
let image = UIGraphicsImageRenderer(size: sz, format: fmt).image { rc in

    .red.setFill()
    rc.fill(frame)
    
    rc.cgContext.clear(CGRect(x: 10, y: 10, width: 10, height: 10))

    let att = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 10.0)]
    "test".draw(in: frame, withAttributes: att)
}

try! image.pngData()!.write(to: "red.png")
Premonitory answered 10/8, 2023 at 16:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.