Emboss effect in Core Graphics - iphone

I am again here with two Question, both inter-related
I want to draw embossed lines with core graphics.
Can any one suggest me how to give inner shadows to line drawn on touch events?
Even for drawing outer shadows.
Shadow drawn overlaps in between. and line drawn with colors other than black is like worm..
Can any one help me?
Following image illustrates what I mean to explain for Question 2:
Shadows creates are not even. They darken at some points
I am adding the code that I am using to draw lines..
for (int i=0; i<[currentPath count]; i++)
{
CGPoint mid1 = [[self midPoint:[currentPath objectAtIndex:i+1] :[currentPath objectAtIndex:i]] CGPointValue];
CGPoint mid2 = [[self midPoint:[currentPath objectAtIndex:i+2] :[currentPath objectAtIndex:i+1]] CGPointValue];
CGContextMoveToPoint(context, mid1.x, mid1.y);
CGContextAddQuadCurveToPoint(context, [[currentPath objectAtIndex:i+1] CGPointValue].x, [[currentPath objectAtIndex:i+1] CGPointValue].y, mid2.x, mid2.y);
CGContextSetShadow(context, CGSizeMake(-2, -2), 3);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetStrokeColorWithColor(context,[color CGColor]);
CGContextSetLineWidth(context, linewidth);
CGContextStrokePath(context);
i+=2;
}

I found my solution..
Problem was very silly...
I was stoking path on every iteration which was creating the issue.. Now I can draw even with alpha less then 1..
CGContextStrokePath(context);
This line goes outside for loop.. And all is working fine now :)

For your overlapping shadows, you want a transparency layer to composite them first. See Transparency Layers in the Quartz 2D Programming Guide.

It looks like you are drawing the path by using a series of circles.
The trouble is that you have set the shadow on the individual dots, and that's why you are getting the strange effects.
A possible solution - dont put a shadow on the dots, put on the path: duplicate the line that you have drawn, draw it in a different colour, offset it and put in under your actual line.
Alternatively, if you are using layers - have a look at shadow paths.

Related

Questions regarding drawing in iOS

I want to create a little navigation bar on the bottom of my iPhone screen where I basically just draw 5 rectangles next to each other. However, only the active page should have the opacity of 1.0 and others should be slightly transparent (alpha=0.4). That is what I already have.
Now my questions:
How do I change the opacity of the individual elements of my navigation ? Do I have to redraw the whole thing whenever something changes ? So I would have global variables called nav1Opacity,nav2Opacity...nav5Opacity, change them when the navigation changes and redraw the whole thing ? If so,
How do I clear what I have drawn before ? Do i create the rectangles as CGMutablePathRef()s and store them in an array and clear them all ?
I have very little experience with drawing, so I am a little lost there. I have read the documentation of Quartz2d and contexts, but still, as I mentioned I have not fully figured out how it works.
Here is some code I use:
-(void)drawRect:(CGRect)rect{
CGContextRef context = UIGraphicsGetCurrentContext();
//save state
CGContextSaveGState(context);
//NAV1
CGMutablePathRef nav1 = CGPathCreateMutable();
CGPathAddRect(nav1, NULL, CGRectMake(0 , 15, 64, 10));
UIColor *blueColor = UIColorFromRGB(0x35BFE5,0.1);
CGColorRef bC = [blueColor CGColor];
[colorArray addObject:(__bridge id)bC];
[navArray addObject:(__bridge id)nav1];
CGPathRelease(nav1);
/*
*
*
... I do this for all 5 navigation elements
*
*
*/
//then I go through all my rectangles and add/fill them
for(int i=0;i<[navArray count];i++){
CGContextAddPath(context, (__bridge CGMutablePathRef)[navArray objectAtIndex:i]);
CGContextSetFillColorWithColor(context, (__bridge CGColorRef)[colorArray objectAtIndex:i]);
CGContextFillPath(context);
}
// restore to last saved context state
CGContextRestoreGState(context);
}
//and this is how I redraw
-(void)updateActiveNav{
[navArray removeAllObjects];
[colorArray removeAllObjects];
[self setNeedsDisplay];
}
If you actually draw the interface, you will have to redraw it whenever it changes, at least the rectangles that change. You can reuse CGPaths, but they aren't graphical objects on screen, they are just instructions on how to draw shapes, so you will have to draw everything again.
That being said, you can use individual UIViews instead, which represent onscreen objects, and you can change their opacity, which will reflect on screen.
This is the problem:
for(int i=0;i<[navArray count];i++){
CGContextAddPath(context, (__bridge CGMutablePathRef)[navArray objectAtIndex:i]);
CGContextSetFillColorWithColor(context, (__bridge CGColorRef)[colorArray objectAtIndex:i]);
CGContextFillPath(context);
}
You are added the path to your context, then setting a fill color, then filling it. Then without restoring your context you're doing it again so your filling the previous path and the new one. Its not the drawing from the last drawRect its the drawing from here. Try something like below so that after you fill the path you reset the context and draw the next block by itself instead of both the 1st and 2nd etc.
for(int i=0;i<[navArray count];i++){
CGContextSaveGState(context);
// Add Path, Fill
CGContextRestoreGState(context);
}

Animating Circle's endAngle with CABasicAnimation

I have some UIView with drawrect code for drawing pie:
- (void)drawRect:(CGRect)rect
{
CGRect parentViewBounds = self.bounds;
CGFloat x = CGRectGetWidth(parentViewBounds)/2;
CGFloat y = CGRectGetHeight(parentViewBounds)/2;
// Get the graphics context and clear it
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextClearRect(ctx, rect);
// define line width
CGContextSetLineWidth(ctx, 4.0);
CGContextSetFillColor(ctx, CGColorGetComponents( [someColor CGColor]));
CGContextMoveToPoint(ctx, x, y);
CGContextAddArc(ctx, x, y, 100, radians(270), radians(270), 0); // 27
CGContextClosePath(ctx);
CGContextFillPath(ctx);
}
But when I try to animate "endAngle" property after drawing, it doesn't work:
CABasicAnimation *theAnimation;
theAnimation=[CABasicAnimation animationWithKeyPath:#"endAngle"];
theAnimation.duration=1;
theAnimation.repeatCount=1;
theAnimation.autoreverses=NO;
theAnimation.fromValue=[NSNumber numberWithFloat:270];
theAnimation.toValue=[NSNumber numberWithFloat:60];
[[self.layer presentationLayer] addAnimation:theAnimation forKey:#"animateLayer"];
Where I'm making a mistake?
If you want to make an animation then you should look into using Core Animation for the drawing. It makes animation much simpler.
Have a look at this great tutorial on making a custom animatable pie chart with Core Animation. I'm sure that you can modify it to get what you want.
If it seems to complicated to you then you can have a look at my answer to "Draw part of a circle" which draws a pie chart shape by making a very wide stroke of a circle.
Several things. You can't use a CAAnimation to animate drawing that you do in a drawRect method like that.
Second, if you did use a CAShapeLayer, you could animate changes to the path that's installed in the layer, but you need to make sure that the path has the same number and type of control points for all steps in the animation.
CGPath arc commands use different numbers of control points to draw different angles. Thus, you can't change the angle of the arc and animate it.
What you have to do is to install a path that's the full length of your arc, and then animate changes to the shape layer's strokeStart and/or strokeEnd properties. Those properties cause the path to omit the first part/last part of the path from the drawing. I daresay the tutorial David linked to on the animatable pie chart uses that technique.

Core Graphics draw line with outline

I'm drawing an arbitrary line with Core Graphics with a width of 4 pixels, now I would like this line to have a 1 pixel outline of another colour. I can't see any CG functions that would achieve this "out of the box" but I'm looking for suggestions on how it could be done. This is my existing code:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 4.0);
CGPoint curPoint = [(NSValue*)[points objectAtIndex:0] CGPointValue];
CGContextMoveToPoint(context, curPoint.x, curPoint.y);
for( int i = 1; i < [points count]; i++ ) {
curPoint = [(NSValue*)[points objectAtIndex:i] CGPointValue];
CGContextAddLineToPoint(context, curPoint.x, curPoint.y);
CGContextStrokePath(context);
CGContextMoveToPoint(context, curPoint.x, curPoint.y);
}
This produces the single line. I would like to produce a 4px line with a 1px line highlighting the 4px line like this:
iOS 5.0 added a new feature CGPathCreateCopyByStrokingPath() that does what you want.
First create a CGPathRef for the black path, and then create a copy of it with CGPathCreateCopyByStrokingPath().
This will give you a new path, which you can fill in black and stroke in red, to get what you want.
Also, creating paths is a bit slow. You should avoid creating paths while performing screen drawing. All your paths should be stored in RAM and ready to go before you start drawing to the screen. drawRect: should only draw the path, not create it.
Similar to what Abhi Beckert suggests, but you can use this function:
CGContextReplacePathWithStrokedPath(CGContextRef c);
Which is present in older SDKs too - iOS 4.1, MacOS X 10.6, for example.
Also it is better to create the whole path and then stroke it (or stroke and fill in the same time) at the end - in other words no need to have CGContextStrokePath inside the loop.
I am afraid you will have to draw the path again with the line width set to 1. If you want it to be on the outside of your 4 pixel path, you will have to adjust your path accordingly.
Edit: One other option comes to mind - you can stroke a pattern - see Apple's QuartzDemo for an example how.
To add an answer to my own question, it can be done by drawing a line a few pixels wider in the highlight colour followed by the actual line on top. This produces the outline effect.
There isn't a built-in way to convert a stroke to a path, and then stroke that path. That said, you may be able to approximate this by drawing the line twice: once with a 6 pixel stroke (4 pixels + 1 on each side) and then again with a 4 pixel stroke in a different color
Similar to:
CGContextRef context = UIGraphicsGetCurrentContext();
CGPoint curPoint = [(NSValue*)[points objectAtIndex:0] CGPointValue];
CGContextMoveToPoint(context, curPoint.x, curPoint.y);
for( int i = 1; i < [points count]; i++ ) {
curPoint = [(NSValue*)[points objectAtIndex:i] CGPointValue];
CGContextAddLineToPoint(context, curPoint.x, curPoint.y);
}
// Set your 1 pixel highlight color here using CGContextSetRGBStrokeColor or equivalent
// CGContextSetRGBStrokeColor(...)
CGContextSetLineWidth(context, 6.0);
CGContextStrokePath(context);
// Set your 4 pixel stroke color here using CGContextSetRGBStrokeColor or equivalent
// CGContextSetRGBStrokeColor(...)
CGContextSetLineWidth(context, 4.0);
CGContextStrokePath(context);
Another idea would be setting up a shadow via CGContextSetShadowWithColor(context, CGSizeZero, 1.0, yourHighlightColorHere) prior to drawing the stroke, although this won't draw the highlight color with full opacity. (I also can't remember if shadows property shadow strokes - I have only used them with fills)

Draw squares with Core Graphics or include pngs?

I'm making a legend for a graph that will basically look like this:
[ ] Line 1
[ ] Line 2
[ ] Line 3
The boxes on the left need to be the same color as the lines on the graph.
Anyhow, all I need to know, is whether it's faster to draw the boxes with Core Graphics or just make some pngs with GIMP for the squares and include them.
Use UIView for each legend and set their background color to the color you want.
Both approaches are fast enough that it shouldn't make a difference. However, using Core Graphics has the advantage that you're a lot more flexible, e.g. when you later decide that you need additional colors. Plus, your app will be smaller, because you don't have to include the PNG files.
Drawing boxes is a snap! I would go with Core Graphics everyday, especially since you get retina support for free.
As can be seen in this example you can do it using UIKit only classes:
// Setup colors
[myBoxColor setFill];
[myBoxBorderColor set];
// Setup a path for the box
UIBezierPath* path = [UIBezierBath bezierPathWithRect:rectOfTheBox];
path.lineWidth = 2;
// Draw!
[path fill];
[path stroke];
One warning; stroke fills using the edges of the path as the center of the line. So you will get a blurry line if you stroke a path with integral rect with a 1 point line width.
You can remedy this is you want a 1 point line for the border by doing something like this:
CGRect strokeRect = UIEdgeInsetsInsetRect(rectOfTheBox,
UIEdgeInsetsMake(0.5f,0.5f,0.5f,0.5f));
UIBezierPath* path = [UIBezierPath bezierPathWithRect:strokeRect];
[path stroke];
On iOS, Core Graphics is quite simple to use. In your view's drawRect: method, just do this to draw a square:
- (void)drawRect:(CGRect)frame {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBFillColor(context, 0.5, 0.5, 0.5, 1); // gray
CGContextFillRect(context, CGRectMake(10, 10, 20, 20)); // our rect is {10,10,20,20)
// draw a line
CGContextSetRGBStrokeColor(context, 1, 0, 0, 1);
CGContextBeginPath(context);
CGContextMoveToPoint(context, startX, startY);
CGContextAddLineToPoint(context, endX, endY);
CGContextStrokePath(context);
}
Hope this helps!

Drawing a solid line in a UITableViewCell

I don't know anything about graphics or drawing so I assume that's why I can't figure out what to look for to solve this.
Here's what I got in my UITableViewCell's drawRect
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(ctx, 0.0, 0.0, 0.0, 1.0);
CGContextSetLineWidth(ctx, 0.25);
for (int i = 0; i < [columns count]; i++) {
CGFloat f = [((NSNumber*) [columns objectAtIndex:i]) floatValue];
CGContextMoveToPoint(ctx, f, 0);
CGContextAddLineToPoint(ctx, f, self.bounds.size.height);
}
CGContextStrokePath(ctx);
}
I think this should be drawing a black line but the line is gray, blending with what's in the background. How can I draw a solid line that is not influenced by a background image? I've tried all the blend mode's thinking maybe one would be like a blend mode none, because I don't want any blending, but all draw lines that blend in some way with the background.
You can try turning off antialiasing using CGContextSetShouldAntialias(). Also, 0.25 seems like a pretty thin line. Setting it to 1.0 might give you what you're looking for.
Two problems:
* You're drawing a black line that's 0.25 pixels (or 0.5 pixels on iPhone 4) wide; antialiasing will make this appear semi-transparent. Try setting the line width to 1.
* If f is an integer, then the center of the line is aligned on a pixel boundary (you want it on a pixel center). Try adding 0.5f.
If you want thinner lines on iPhone 4, then check if self.layer.scale == 2 and if so, set the line width to 0.5 and add 0.25f.
Alternatively, set the fill colour and call CGContextFillRect() instead, which means you don't have to worry about pixel centers.
UIView *line = [[UIView alloc]initWithFrame:CGRectMake(100,0,1,44)];
line.backgroundColor = [UIColor colorWithRed:244.0f/255.0f green:244.0f/255.0f blue:244.0f/255.0f alpha:1];
[cell addSubview:line];