How to create an alpha mask in iOS using sprite kit - ios

The effect that I am trying to achieve is having a circle of light in an area of darkness. The effect is similar to that in pokemon games, when you are in a dark cave and only have limited vision surrounding you. From what I have tried and read, I have been unable to create a mask over nodes in sprite kit that has alpha levels. The masks I manage to create all have a hard edge, and basically just crop. Reading on the apple developer page about the SKCropNode, which has the maskNode property, it says "If the pixel in the mask has an alpha value of less than 0.05, the image pixel is masked out." This unfortunately sounds to me like the pixels will either be completely masked out or completely included, with no alpha values in between. If what I am trying to say has been hard to follow, here is an image of what I have achieved:
https://www.dropbox.com/s/y5gbk8qvuq4ynh0/iOS%20Simulator%20Screen%20shot%20Jan%2020%2C%202014%201.06.23%20PM.png
and here is an image of what I would like to achieve:
https://www.dropbox.com/s/wtwfdi1mjs2n8e6/iOS%20Simulator%20Screen%20shot%20Jan%2020%2C%202014%201.05.54%20PM.png
The way that I managed to get the above result, was I masked out the hard edge circle and then just added an image that has a gradient going from black on the outside to transparent on the inside. The reason this approach doesn't work is because I need to have multiple circles, and with the method I just mentioned, when the circles intersect the darkness on the outside of the transparent circle can be seen.
In conclusion, what I need is a way to have a circle that starts dark in the center, and then fades out. Then, have it so where the circle is dark, the image behind it can be seen, and where the circle is transparent, the image behind it cannot be seen. Again, sorry if what I am saying is difficult to follow. Here is the code I am using. Some of it was found from other posts.
SKSpriteNode *background = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(500, 500)];
background.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
SKCropNode *cropNode = [[SKCropNode alloc] init];
SKNode *area = [[SKNode alloc] init];
int x = 65; //radius of the circle
_circleMask = [[SKShapeNode alloc ]init];
CGMutablePathRef circle = CGPathCreateMutable();
CGPathAddArc(circle, NULL, 0, 0, x/2, 0, M_PI*2, YES);
_circleMask.path = circle;
_circleMask.lineWidth = x*2;
_circleMask.strokeColor = [SKColor whiteColor];
_circleMask.name=#"circleMask";
_circleMask.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
//Here is where I just added in the gradient circle To give the desired appearance, but this isn't necessary to the code
//_circleDark = [SKSpriteNode spriteNodeWithImageNamed:#"GradientCircle"];
//_circleDark.position = [cropNode convertPoint:_circleMask.position fromNode:area];
[area addChild:_circleMask];
[cropNode setMaskNode:area];
[cropNode addChild:background];
//[cropNode addChild:_circleDark];
[self addChild:cropNode];
This method has also allowed me to move the circles around, revealing different parts of the image behind it, which is what I want. To do this I just set it to change the _circleMask.position when the user taps the screen.
Also, just to make this clear in case anyone was confused, the black is just the background color of the scene, the picture is on top of that, and then the circle is part of the mask node.

A very simple (and maybe less... or more performant) version of this would be to simply add a SKSpriteNode on top which has your vignette on a transparent background. In other words, if viewed in Photoshop, you would see a decreasing amount of checkerboard visible in the circle as you go from the center out, eventually displaying solid black. When the PNG image is used in your app, this transparency will be preserved when the two sprites are composited.

I have an idea... I hope it would help.
Make a PNG with the gradient you want from white to black with no transparency.
Use a separate sprite node with the png for each light you want and add them all to a SKEffectNode or SKCropNode node. It doesn't matter which since they are both rendered in a separate context. Set each sprite node to screen blending mode.
Then, when adding the parent SKEffectNode or SKCropNode to the scene, set it to multiply blend mode.
In the end, the screening will merge the "lights" together nicely, while the multiply will make the white area transparent.

Related

Draw gradient below a CAShapeLayer graph

I'm drawing a graph using a CGPath applied to a CAShapeLayer. The graph itself is drawn just fine, but I want to add a gradient underneath it afterwards. My problem is that the path is closed with a straight line going from the last point to the first point (see below) – this would make a gradient fill look totally ridiculous.
As far as I can see, the only way to circumvent this issue is to draw two additional lines: one from the last point of the graph to the bottom-right corner, and from there, another one to the bottom-left corner. This would close the path off nicely, but it would add a bottom line to the graph, which I don't want.
If I were using CGContext, I could easily solve this by changing the stroke color to transparent for the last two lines. However, with the code below, I don't see how that would be possible.
CGMutablePathRef graphPath = CGPathCreateMutable();
for (NSUInteger i = 0; i < self.coordinates.count; i++) {
CGPoint coordinate = [self.coordinates[i] CGPointValue];
if (!i) {
CGPathMoveToPoint(graphPath, NULL, coordinate.x, coordinate.y);
} else {
CGPathAddLineToPoint(graphPath, NULL, coordinate.x, coordinate.y);
}
}
CAShapeLayer *graphLayer = [CAShapeLayer new];
graphLayer.path = graphPath;
graphLayer.strokeColor = [UIColor whiteColor].CGColor;
graphLayer.fillColor = [UIColor redColor].CGColor;
[self.layer addSublayer:graphLayer];
I hope you guys can help me out!
Update: You suggest that I could create a CAGradientLayer, and then apply the graph layer as its mask. I don't see how that would work, though, when the graph/path looks the way it does. I have replaced the image above with another graph that hopefully illustrates the problem better (note that I've given the CAShapeLayer a red fill). As I see it, if I were to apply above layer as the mask of a CAGradientLayer, some of the gradient would lie above the graph, some it below. What I want is for all of the gradient to be placed right beneath the graph.
Maybe I'm not understanding the problem correctly, but if you're looking to add a consistent gradient, couldn't you create a gradient layer and then make your graphLayer be that layer's mask?
Figure out whatever the min max bounds of your coordinates, create a CAGradientLayer that size, configure it however you might like and then, apply your graphLayer as it's mask. Then add the new CAGradientLayer to your self.layer.
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
// ... Configure the gradientLayer colors / locations / size / etc...
gradientLayer.mask = graphLayer;
[self.layer addSubLayer:gradientLayer];
This doesn't take into account the stroke, but it shouldn't be difficult to apply that as well if that's important.
I solved the problem by creating two separate paths: One for the graph (as shown in my original post), and one that starts in the lower-right corner, moves in a straight line to the lower-left corner, and from there follows the same path as the graph. By doing so, the path gets closed off nicely, since the graph ends at the same x-coordinate as where the path started.
From there, I applied the second path to a CAShapeLayer, and then used this layer as the mask of a gradient layer.

Is it possible to add a transparent layer or view above a colored view?

I'm using AVFoundation framework to scan a barcode, but that may be unrelevant for my problem.
What I want:
I would like that the square bordered in green be transparent (not with the darkened black).
Here is what I have done:
I have 2 views: backgroundView( which occupies the whole screen) and highlightView which is the square bordered with green, on top of backgroundView (I have used a XIB for dimensions and positions) :
self.highlightView.layer.borderColor = [UIColor greenColor].CGColor;
self.highlightView.layer.borderWidth = 3;
// this following line does not allow the square to be transparent
self.highlightView.layer.opacity = 0;
// relative to AVFoundation
_previewLayer.frame = _backgroundView.bounds;
[_backgroundView.layer addSublayer:_previewLayer];
_previewLayer.backgroundColor = [UIColor blackColor].CGColor;
_previewLayer.opacity = 0.3;
UPDATE : xib (here representing the square with a clear color background), the backgroundView has the property black color background).
As I mentioned, you were looking in the wrong direction. There are multiple posts with a problem similar to yours, which have pretty decent answers. (You will have to study and understand to make the most of them):
Cut Out Shape with Animation
Simply mask a UIView with a rectangle
To sum it up, you need to apply the semi-transparent color to the layer of backgroundView and then play around with the layer's mask property to get the work done.
You can find many tutorials to learn using the layer and mask together.
Hope this helps.

How to remove gray color effect when touch move using objective c

I converted an image to fully gray when loaded, but I want to remove the gray color from it when touch move and view original image color.
I want to know how to convert from gray color effect to the original image and from original to gray when user moves finger over the image.
My solution is the following.
You need TWO UIImageViews. Add one on the other and make them to overlap pixel by pixel. You have two options:
B&W in foreground
Color in foreground
Both solutions fundamentally are the same. You need to use a mask on the topmost view. With a mask layer the white areas will be fully opaque, blacks are 100% transparent, grays are semi-transparent.
Example for the mask:
CALayer *maskLayer = [CALayer layer];
UIImage *mask = [UIImage imageNamed:#"mask.png"];
maskLayer.contents = (id)mask.CGImage;
maskLayer.bounds = (CGRect){CGPointZero, mask.size};
UIImageView *viewToMask = ;
viewToMask.image = [UIImage imageNamed:#"blackandwhite.png"];
viewToMask.layer.mask = maskLayer;
[self.view addSubview:viewToMask];
Certainly you have to add/remove the mask on touch/release. I leave you with that.
Now what happens when you move your finger? Just move the mask to different positions like so:
viewToMask.layer.mask.position = offset position based on touch position
I hope I could help you.

How to use a SKEmitterNode in sprite kit to achieve a "fog of war" effect?

I'm trying to use a SKEmitterNode to create a shader, kind of like in Pokemon when you are in a cave:
http://www.serebii.net/pokearth/maps/johto-hgss/38-route31.png
Here is the code I have so far :
NSString *burstPath =
[[NSBundle mainBundle] pathForResource:#"MyParticle" ofType:#"sks"];
SKNode *area = [[SKNode alloc] init];
SKSpriteNode *background = [SKSpriteNode spriteNodeWithColor:[SKColor blackColor] size:CGSizeMake(self.frame.size.width, self.frame.size.width)];
background.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
SKEmitterNode *burstNode =
[NSKeyedUnarchiver unarchiveObjectWithFile:burstPath];
burstNode.position = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame));
burstNode.particleBlendMode = SKBlendModeSubtract;
[area addChild:background];
[area addChild:burstNode];
[self addChild:area];
Here is the SKEmitterNode : http://postimg.org/image/60zflqjzt/
I've had two ideas.
The first one was to create a rectangular SKSpriteNode and remove the SKEmitterNode from the rectangular SKSpriteNode. That way, we have a black rectangle with a "hole" in the center, where we can see through.
The second one was to add the rectangular SKSpriteNode and the SKEmitter node to another SKNode (area), then set the particleBlendMode of the SKEmitterNode and finally set the alpha of the SKNode (area) in function of the color. For exemple, if the color of a pixel is black, the alpha value of that pixel will be 1.0 and another pixel is white, that other pixel's alpha value will be 0.0.
This question is a possible duplicate of How to create an alpha mask in iOS using sprite kit in some ways, but since no good answer has been given, I assume it isn't a problem.
Thank you very much.
These are not the nodes you are looking for! ;)
Particles can't be used to make a fog of war, even if you could make them behave to generate a fog of war it would be prohibitively slow.
Based on the linked screenshot you really only need an image with a "hole" in it, a transparent area. The image should be screen-sized and just cover up the borders to whichever degree you need it. This will be a non-revealing fog of war, or rather just the effect of darkness surrounding the player.
A true fog of war implementation where you uncover the world's area typically uses a pattern, in its simplest form it would just be removing (fading out) rectangular black sprites.
Now, with the powerful devices of this era (iPhone 12) is it possible to use 'SKEmitterNode' without lost too much frames per second.
You must build an SKS (SpriteKit Particle File) with this image:
Then, set your vars like this picture:
So, go to your code and add your particle with something like this example:
let fog = SKEmitterNode(fileNamed: "fog")
fog.zPosition = 6
self.addChild(fog)
fog.position.y = self.frame.midY
fog.particlePositionRange.dx = self.size.width * 2.5

Sprite Kit, masking node is aliased

I'm experimenting a bit with Sprite Kit. I'm trying to mask an image with an alpha png. This is easy enough but I can't find information on how to mask an image with alpha values. Right now it renders the pixel or it doesn't.
From the docs on SKCropNode it says: If the pixel in the mask has an alpha value of less than 0.05, the image pixel is masked out.
So this is a logical outcome from the way I'm doing this right now:
CGPoint location = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
SKCropNode *cropNode = [SKCropNode node];
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"];
SKSpriteNode *maskImage = [SKSpriteNode spriteNodeWithImageNamed:#"mask5.png"];
[maskImage setName:#"mask"];
[cropNode setMaskNode:maskImage];
[cropNode addChild:sprite];
[cropNode setPosition:location];
[self addChild:cropNode];
The result is that the edges are jagged. Is there a way to mask the image in such way the edges are smooth?
Thanks!
No, the mask nodes are binary in the way they work, either they show or the hide. Apple provides an example in their documentation where they apply a blur filter to the masknode afterwards. A 1px box blur might do it for you.
Documentation -> Mask and blur effect

Resources