Crop/Mask circular image node in sprite kit gives jagged edges
Asked Answered
S

4

1

Is it possible give a circular mask/crop to an image node without jagged edges?

Following this example from Apple (https://developer.apple.com/reference/spritekit/skcropnode), the result is not ideal. You can click on the link to see.

    let shapeNode = SKShapeNode()
    shapeNode.physicsBody = SKPhysicsBody(circleOfRadius: radius)
    shapeNode.physicsBody?.allowsRotation = false
    shapeNode.strokeColor = SKColor.clearColor()

    // Add a crop node to mask the profile image
    // profile images (start off with place holder)
    let scale = 1.0
    let profileImageNode = SKSpriteNode(imageNamed: "PlaceholderUser")
    profileImageNode.setScale(CGFloat(scale))

    let circlePath = CGPathCreateWithEllipseInRect(CGRectMake(-radius, -radius, radius*2, radius*2), nil)

    let circleMaskNode = SKShapeNode()
    circleMaskNode.path = circlePath
    circleMaskNode.zPosition = 12
    circleMaskNode.name = "connection_node"
    circleMaskNode.fillColor = SKColor.whiteColor()
    circleMaskNode.strokeColor = SKColor.clearColor()

    let zoom = SKAction.fadeInWithDuration(0.25)
    circleMaskNode.runAction(zoom)

    let cropNode = SKCropNode()
    cropNode.maskNode = circleMaskNode
    cropNode.addChild(profileImageNode)
    cropNode.position = shapeNode.position

    shapeNode.addChild(cropNode)
    self.addChild(shapeNode)
Strother answered 10/10, 2016 at 22:39 Comment(3)
can you add a screenshot of the jaggies?Genetic
click on the link i provided that is provided above. :) developer.apple.com/reference/spritekit/skcropnodeStrother
Total solution: https://mcmap.net/q/404044/-skspritenode-create-a-round-corner-nodeJohen
S
2

UPDATE:

Ok, so here's one solution I came up. Not super ideal but it works perfectly. Essentially, I have a to size/scale, and cut the image exactly the way it would go on the SKSpriteNode so I would not have to use SKCropNode or some variation of SKShapeNode.

  1. I used these UIImage extensions by Leo Dabus to resize/shape the image exactly as needed. Cut a UIImage into a circle Swift(iOS)

    var circle: UIImage? {
         let square = CGSize(width: min(size.width, size.height), height: min(size.width, size.height))
         let imageView = UIImageView(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: square))
         imageView.contentMode = .ScaleAspectFill
         imageView.image = self
         imageView.layer.cornerRadius = square.width/2
         imageView.layer.masksToBounds = true
         UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, false, scale)
         guard let context = UIGraphicsGetCurrentContext() else { return nil }
         imageView.layer.renderInContext(context)
         let result = UIGraphicsGetImageFromCurrentImageContext()
         UIGraphicsEndImageContext()
         return result
    }
    
    func resizedImageWithinRect(rectSize: CGSize) -> UIImage {
         let widthFactor = size.width / rectSize.width
         let heightFactor = size.height / rectSize.height
    
         var resizeFactor = widthFactor
         if size.height > size.width {
         resizeFactor = heightFactor
         }
    
         let newSize = CGSizeMake(size.width/resizeFactor, size.height/resizeFactor)
         let resized = resizedImage(newSize)
         return resized
    }
    
  2. The final codes look like this:

    //create/shape image
    let image = UIImage(named: "TestImage")
    let scaledImage = image?.resizedImageWithinRect(CGSize(width: 100, height: 100))
    let circleImage = scaledImage?.circle
    
    //create sprite
    let sprite = SKSpriteNode(texture: SKTexture(image: circleImage!))
    sprite.position = CGPoint(x: view.frame.width/2, y: view.frame.height/2)
    
    //set texture/image
    sprite.texture = SKTexture(image: circleImage!)
    sprite.physicsBody = SKPhysicsBody(texture: SKTexture(image: circleImage!), size: CGSizeMake(100, 100))
    if let physics = sprite.physicsBody {
        //add the physic properties
    }
    
    //scale node
    sprite.setScale(1.0)
    addChild(sprite)
    

So if you have a perfectly scaled asset/image, then you probably dont need to do all this work, but I'm getting images from the backend that could come in any sizes.

Strother answered 11/10, 2016 at 17:43 Comment(1)
You're passing in "scale" to UIGraphicsBeginImageContextWithOptions, but it's never set beforehand.Zodiac
G
0

There are two different techniques that can be combined to reduce the aliasing of edges created from cropping.

  1. Create bigger images than you need, and then scale them down. Both the target (to be cropped) and the mask. Perform the cropping action, then scale down to required size.

  2. Use very subtle blurring of the cropping shape, to soften its edges. This is best done in Photoshop or a similar editing program, to taste and need.

When these two techniques are combined, the results can be very good.

Genetic answered 11/10, 2016 at 17:32 Comment(6)
Ah, yup! I ended up going with #1 because we have to get images from the server which we don't really know the size of until we get it.Strother
Cool. I just remembered, having done your sizing, you could apply a very gentle blur with Core Image, try this, at very low numbers, on the mask image: developer.apple.com/library/content/documentation/…Genetic
nice! that would give it a more pro look. ;)Strother
Yeah, sorry, I had a mental blank when writing the answer, completely forgot about the Core Image effects.Genetic
btw, not sure if you know this... but applying Blur effects at very small values, repeatedly, gives better results than doing it once with a larger number. Obviously more expensive in terms of time, but if you have that time available, this is the best way to get very smooth edges.Genetic
No problem! What I'm learning from your code is going to be very useful to me, shortly, so THANK YOU!!!Genetic
I
0

Let the stroke color to be displayed. Also, you can make line width a little thicker and the jagged edges will dissapear.

 circleMaskNode.strokeColor = SKColor.whiteColor()
Inkling answered 9/7, 2019 at 6:50 Comment(0)
L
0

All you have to do is change the SKShapeNode's lineWidth property to be twice the radius of the circle:

func circularCropNode(radius: CGFloat, add: SKNode) {
    let cropper = SKCropNode.init()
    cropper.addChild(add)
    addChild(cropper)
        
    let circleMask = SKShapeNode.init(circleOfRadius: radius/2)
    circleMask.lineWidth = radius
    cropper.maskNode = circleMask
}
Layard answered 21/7, 2021 at 18:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.