Creating multiply effect in painting iPad app - ios

I am trying to create a painting feature in an iPad app for iOS. I have managed to get the colour to appear through touch, but I would like to recreate the multiply functionality of photoshop so the underlying black and white image continues to show through the colour. I started doing it with opacity but going over the same spot will result in it eventually being removed. I am using cocos2d and this is the sample code.
in header
CCSprite *background;
CCRenderTexture *target;
CCSprite *brush;
in init method:
background = [CCSprite spriteWithFile:#"background.png"];
background.position = ccp(self.size.width/2, self.size.height/2);
[self addChild: background z:-1];
target = [[CCRenderTexture alloc] initWithWidth:self.size.width height:self.size.height pixelFormat:kCCTexture2DPixelFormat_RGBA8888];
[target setPosition:ccp(self.size.width/2, self.size.height/2)];
brush = [[CCSprite spriteWithSpriteFrameName:#"brush_spot.png"] retain];
[brush setColor:ccRED];
in -(void) ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event method:
[target begin];
[brush setPosition:<CALCULATED POSITION>];
[brush visit];
[target end];
I have tried using different blend functions on the brush but nothing has managed to create the look I want. I did get the correct effect when adding a sprite directly on top of the background and setting its blend function to
[sprite setBlendFunc:(ccBlendFunc) { GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA }];
but when I try to use this blend function on the brush nothing appears on the screen.
Thanks
Iain

The blend function blends whatever is on top with whatever is behind. When you render to a texture you blend with whatever the texture is blanked to.. Which I guess is something with alpha 0. So you should draw to the texture using opacity.
When you have filled the texture with color and attached it to a sprite, you can render it again and blend it with your background sprite. Then you can blend using multiply.

Related

How to create an alpha mask in iOS using sprite kit

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.

Cocos2d image masking

I'm currently working on game where the main character rides on a ship and when an enemy is parallel to the ship, it drops a tube. My main problem is the tube is bigger than the ship so it is visible from behind while it is going down or up. Please note that the image (the ship) on top of the tube is a transparent image. Thanks!
You can clip draw regions in Cocos2d without too much effort. If you add this code to the tube object then you can define a suitable region to draw the object. Anything outside of this rectangle doesn't get drawn.
-(void) visit
{
if(!self.visible)
return;
glEnable(GL_SCISSOR_TEST);
CGRect thisClipRegion = _clipRegion;
thisClipRegion = CC_RECT_POINTS_TO_PIXELS(thisClipRegion);
glScissor(thisClipRegion.origin.x, thisClipRegion.origin.y, thisClipRegion.size.width, thisClipRegion.size.height);
[super visit];
glDisable(GL_SCISSOR_TEST);
}
I think you have to manage two tube image ,one is big and other is small, which is fit to your ship.
you have to change tube image when you drops this tube.
to change tube image you use this code
CCTexture2D* tex = [[CCTextureCache sharedTextureCache] addImage:#"blast.png"];
[player setTexture: tex];
here player is CCSprite.
CCSprite *player;

CCSprite not showing Background Image

I am facing an unknown error since from yesterday. I am creating CCSprites or CCMenuItemImage but it set black background instead of background image. Following is my code, I know its fine because I used it before many times.
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"challenge_screen.plist"];
CCSprite *bg = [CCSprite spriteWithFile:#"ads.png"];
[bg setPosition:background.position];
// [bg setContentSize:CGSizeMake(100, 100)];
[self addChild:bg z:1000];
//CGSize windowSize = [[CCDirector sharedDirector] winSize];
CCMenuItemImage *coinMenuItem = [[CCMenuItemImage alloc] initWithNormalSprite:[CCSprite spriteWithSpriteFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:#"coin.png"]] selectedSprite:nil disabledSprite:nil block:^(id sender)
{
NSLog(#"I am Tapped");
}];
coinMenuItem.position = ccp(100, 100);
CCMenu *mainMenu = [CCMenu menuWithItems:coinMenuItem, nil];
mainMenu.position = CGPointZero;
[self addChild:mainMenu];
Attached is screenshot.
Thanks in advance.
I am guessing that you are loading this sprite sheet (challenge_screen.plist and the associated texture file, which frequently is challenge_screen.png or challenge_screen.pvr.*) in a color mode that doesn't have transparency.
First, make sure that the associated texture file shows transparency itself. Maybe something messed with this particular texture.
Once you checked that, if the associated texture is .PNG. then you have to set the texture loading format in code like this. You have to set the texture format before loading the texture itself (the texture loads as a side effect of adding the SpriteFrames to the cache).
[CCTexture2D setDefaultAlphaPixelFormat:kCCTexture2DPixelFormat_RGBA4444];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"challenge_screen.plist"];`
...
You can also try the kCCTexture2DPixelFormat_RGBA8888 mode if RGBA444 produces banding with your graphics and if you are good regarding free memory.
On the other hand, if the texture is a PVR.*, then the format in which the texture loads is embedded in the file, and setting the texture format in code doesn't make a difference. You will then need to regenerate your sprite sheet using the appropriate format (through TexturePacker or similar).
Is your background in the sprite sheet? If so try:
CCSprite *bg = [CCSprite spriteWithSpriteFrameName:#"ads.png"];
If it is the menu item and you know the code works, it must be an asset issue.

""CCSprite is not using the same texture id"" workaround?

I am animating a whole body through SpriteSheets with CCSpriteBatchNode and CCSpriteFrameCache. Now user can add his own pic to that body which when i try to addChild to Spritesheet crashes with error "CCSprite is not using the same texture id"
Now i know the face CCSprite was not in that cache/texture(it was created through texturepacker) and the crash was normal but i wanted to know if there was a workaround to this as i have to add a face to that body through user interaction and animate that body. And by far using spritesheets is the best option for animation. anyone??
In this case what you can do is You take picture of user , then you make texture from user's image .
Then Add that texture to the CCTextureCache . Now you have texture of user image. Now you can use that texture in animation.
Make Texture from Sprite(You can make sprite from user image)
CCSprite *spr = nil;//your sprite
CCRenderTexture* renderTexture = [CCRenderTexture renderTextureWithWidth:spr.contentSize.width height:spr.contentSize.height];
spr.anchorPoint = ccp(0, 0);
spr.position = ccp(0, 0);
[renderTexture addChild:spr];
[renderTexture begin];
[spr draw]; // or [spr visit];
[renderTexture end];
CCTexture2D *result = renderTexture.sprite.texture;
Add that Texture in to Texture Cache.
[[CCTextureCache sharedTextureCache] addTexture]
When you create a CCSpriteBatchNode it is linked to a single texture. This is the point of a CCSpriteBatchNode: to draw different sprites that use the same texture to reduce OpenGL draw calls and increase efficiency.
The easiest workaround (if you have not reached a performance-critical point) would be to use a regular CCLayer instead of a CCSpriteBatchNode.
If you still want to add different CCSprites (say, the body, the limbs and the head of your character) to the same CCSpriteBatchNode, the you need to build a single sprite sheet (or texture pack) which contains all the body parts that you need to add to the CCSpriteBatchNode. This single sprite sheet will be the only one that the CCSpriteBatchNode will use. You won't be able to add CCSprites that are not using this sprite sheet.
You can not Manually add CCSprite to SpriteSheets. Because When you create animated Sprite using texturepacker then i hope that you know it also created with .plist file and it loas image from it with its size.
When you add manually CCSprite then it is nor found from SpriteFramesWithFile. may be you got error.
another way for add animated CCSprite without use of texturepacker
CCSprite *dog = [CCSprite spriteWithFile:#"dog1.gif"];
dog.position = ccp(winSize.width/2, winSize.height/2);
[self addChild:dog z:2];
NSMutableArray *animFrames = [NSMutableArray array];
for( int i=1;i<=5;i++)
{
NSString* file = [NSString stringWithFormat:#"dog%d.gif", i];
CCTexture2D* texture = [[CCTextureCache sharedTextureCache] addImage:file];
CGSize texSize = texture.contentSize;
CGRect texRect = CGRectMake(0, 0, texSize.width, texSize.height);
CCSpriteFrame* frame = [CCSpriteFrame frameWithTexture:texture rect:texRect];
[animFrames addObject:frame];
}
CCAnimation * animation = [CCAnimation animationWithSpriteFrames:animFrames];
animation.delayPerUnit = 0.07f;
animation.restoreOriginalFrame = YES;
CCAnimate *animAction = [CCRepeatForever actionWithAction:[CCAnimate actionWithAnimation:animation]];
[dog runAction:animAction];
Above code is just describe that how can you add animated CCSprite without use of texturepacker
Here you can also change array of Images so you may be add manually image also.

cocos2D CCLayer or CCLayerColor setOpacity

I'm trying to animate the opacity of a CCLayerColor and its not working. Here are my efforts so far. I've defined retryMenuLayer in .h like
CCLayerColor *retryMenuLayer;
and in .m file
retryMenuLayer = [CCLayerColor node];
[self addChild:retryMenuLayer z:5];
retryMenuLayer.scale = 0.5;
[retryMenuLayer setOpacity:0];
and in appearing method, I'm calling this.
[retryMenuLayer runAction:[CCFadeIn actionWithDuration:1]];
//OR
[retryMenuLayer setOpacity:255];
What happens is the background of retryMenuLayer animates from transparent to solid black but the contents inside (its children - a Menu with buttons) doesn't animate. In fact I have to use visibility property to at least disappear until the method is called.
retryMenuLayer.visible = NO; // When initiating.
retryMenuLayer.visible = YES; // When need to appear the layer.
For transparency in CCLayerColor, I use
CCLayerColor *_shadowLayer = [CCLayerColor layerWithColor: ccc4(0,0,0, 100)];
for a transparent black color.
To make it FadeIn, this should work:
CCLayerColor _shadowLayer = [CCLayerColor layerWithColor: ccc4(0,0,0, 0)];
[_shadowLayer setContentSize: CGSizeMake(_winSize.width, _winSize.height)];
_shadowLayer.anchorPoint = ccp(0.0f, 0.0f);
_shadowLayer.position = ccp(0, 0);
[self addChild: _shadowLayer];
[_shadowLayer runAction: [CCFadeTo actionWithDuration:1.5f opacity:100]];
Note here that I added the shadow layer to self, that is my own customized layer. If I would add it to a sprite that is faded in, the opacity does not pull through to the shadow layer. There seems to be a workaround on that by user "aerostat" in the link in qklxtlx's answer, though.
CCLayer doesn't have opacity. Please refer to this http://www.cocos2d-iphone.org/forum/topic/5088

Resources