I draw line like this:
- (void)drawRect:(CGRect)rect {
_lineColor = [UIColor blackColor];
[_lineColor setStroke];
[_lineColor setFill];
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextClearRect(c, rect);
[[UIColor clearColor] setFill];
CGContextAddRect(c, rect);
CGContextSetLineWidth(c, 1);
CGContextDrawPath(c, kCGPathFill);
CGContextBeginPath(c);
CGContextMoveToPoint(c, 0, 0);
CGContextAddLineToPoint(c, x1, y1);
CGContextStrokePath(c);
}
but, my line is have different width which angle is 90 or 45 degrees. How I can draw line with same width
I whipped up something that may make the effect here more visible. Here's the code:
- (void)drawRect:(CGRect)rect
{
CGContextRef c = UIGraphicsGetCurrentContext();
// Get us a gray background
CGContextSetFillColorWithColor(c, [[UIColor grayColor] CGColor]);
CGContextFillRect(c, CGRectInfinite);
CGContextSetStrokeColorWithColor(c, [[UIColor blackColor] CGColor]);
CGContextSetLineWidth(c, 1.0);
CGContextBeginPath(c);
// Draw some lines at various angles
for (CGFloat i = -10.; i <= 10.; i += 1.0)
{
CGContextMoveToPoint(c, CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
CGContextAddLineToPoint(c, CGRectGetMidX(self.bounds) + i * 10., CGRectGetMidY(self.bounds) + 100);
}
CGContextStrokePath(c);
}
Here's the output from that code on a retina device: (This should display at 100% in the standard StackOverflow format, but you can look at it full size to be sure)
Now here's a piece of that blown up:
What you're seeing here is anti-aliasing at work. For starters the vertical line is 2.0 pixels across, all the time, with no anti-aliasing (assuming you draw it on a pixel boundary). Now think about a 45deg line drawn using the same pixel grid, and employ the Pythagorean theorem. Here's another diagram:
At it's narrowest (i.e. in the dimension perpendicular to the line itself), a 45 deg line will appear 1.414px wide, and at it's widest opaque section (not counting the mostly transparent pixel that's bridging the space in the jaggy gaps) it's going to appear 2.828px across. When blown up, you can see how the work that's being done to anti-alias these lines is effecting the optical appearance of the lines.
Someone is probably going to come along and suggest that you turn off anti-aliasing, but for reference, that makes the optical effect even worse (because then the rasterizer is going to make every pixel with coverage completely opaque):
In short, this is expected behavior, and if you need to adjust how you draw each line to achieve some desired optical appearance that the default anti-aliasing code doesn't provide, then you just have to do that work. There's not a "make all my lines optically similar" setting in CoreGraphics -- they've done the best they can for the general case, and if you need something more specific that's up to you. FWIW, many many applications simply use the default, and people seem pretty satisfied with the results, so you might ask yourself if this is really the place in your app where you want to put in a bunch of extra work.
It occurred to me: I'm not sure you'll like the effect, but one possible approach for achieving optical similarity might be to draw your vertical lines not on pixel boundaries. This will cause them to appear anti-aliased too, which (depending on how you look at it) may make them appear more consistent with the angled, anti-aliased lines. Here's what that looks like:
What you lose doing this is a certain "crispness" to the lines. Drawing off pixel-boundaries can most easily be achieved by translating the context by 0.25pt in the X direction (for retina -- for non-retina, 0.5pt will have a similar effect) like this:
CGContextTranslateCTM(c, 0.25, 0);
Hope this helps.
Related
I am drawing a shape in a UIView drawRect function that involves clipping a path, and then adding coloured blocks behind so that the colors have the shape of the clipped path. However, for some reason the lines of the path are not coming out smoothly; its if the antialiasing isn't working properly.
CGContextSetStrokeColorWithColor(context, [UIColor clearColor].CGColor);
CGContextSetLineWidth(context, 1.0);
CGContextSetShouldAntialias(context, true);
CGContextSetAllowsAntialiasing(context, true);
CGContextBeginPath (context);
CGContextMoveToPoint(context, xStart, yStart);
for (int i=0; i<points.count; i++) {
CGContextAddLineToPoint(context, xPoint, yPoint);
}
}
CGContextAddLineToPoint(context, xStart, yStart );
CGContextClip(context);
CGRect colorRect = CGRectMake(0, 0 , rectWidth, rectHeight);
CGContextSetFillColorWithColor(context, blockColor.CGColor);
CGContextFillRect(context, colorRect);
CGContextRestoreGState(context);
The result should have smooth lines, but it comes out jagged with visible pixels as in this image:
Any idea what the problem is and how to fix it?
Thanks in advance
Okey. So, there is no problem in your code. The problem is that antialiasing works a little different. You drawing a vertical slopes expecting that edge of resulting histogram will be smoothed. But actually antialiasing doesn't smooth the resulting figure. It works with path elements (curves, lines) one by one. So, for example if you draw a circle, its edges will be smoothed.
There is a simple solution of your problem: just create a curve, enveloping the histogram. Add it to your context. It will look more smooth.
Sorry for bad english.
I want to make a visualisation for my music player.so that i draw a grid view and i want to change each square colour randomly or continuously.
My Code for draw grid
- (void)drawRect:(CGRect)rect
for (int i = 0; i < 4 ;i = i + 1) {
for (int j = 0; j < 4; j = j + 1) {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 2.0);
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGRect rectangle = CGRectMake((j*(100+2))+2,(i*(100+2))+2,100,100);
CGContextAddRect(context, rectangle);
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextFillPath(context);
CGContextStrokePath(context);
}
}
}
it look like
In my opinion you are overcomplicating yourself, and limiting future possibilities. If i were you, i would have a grid of UIViews or UIImageViews placed in an array. (You can do it programmatically or with the IB). (You can add the edges by modifying the border property in the view layer)
Then you can do all sort of things by setting their background colors independently, color evens, color odds, random all, anything you want since all you have to do is cycle through the array setting the colors accordingly per beat.
For the beats part is way more complicated than it seems. check this question, it offers a lot of tips on "music information retrieval".
How to detect the BPM of a song in php
I think you should have a single custom UIView.
Then call at short intervals setNeedsDisplayInRect: with the area of the view that you want to redraw.
Finally implement drawRect: and make sure to make optimize it by only redrawing the specified area, and doing it fast!
As for the music beats, better open a separate question ;)
In my custom control I have defined a few CGMutablePathRefs with the needed lines and arcs to draw my control; one draws the overall fill shape and others provide specular highlights. I have also defined two CGMutablePathRefs which contain the paths needed as clipping masks for the active and inactive state of the control.
What I'm struggling with is applying the clipping paths. I have previously used clipping paths for applying gradients to an image, but those drawing commands were of the CGContext... variety, not the CGPath... variety.
For testing purposes I have removed the specular highlight drawing aspects, just trying to get a large path clipped to a smaller path. This is what I had been testing with:
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextBeginPath(ctx);
CGContextAddPath(ctx, inactiveClip);
CGContextClosePath(ctx);
CGContextClip(ctx);
CGContextAddPath(ctx, frontFace);
CGContextSetLineWidth(ctx, 1.0);
CGContextSetFillColorWithColor(ctx, [[UIColor blackColor] CGColor]);
CGContextFillPath(ctx);
}
By putting the clipping command before any drawing, I thought I was saying to CoreGraphics, "Here's the region you should actually draw into."
Alas, nothing is drawn.
So assuming I had that ordering backwards I tried:
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextAddPath(ctx, frontFace);
CGContextSetLineWidth(ctx, 1.0);
CGContextSetFillColorWithColor(ctx, [[UIColor blackColor] CGColor]);
CGContextFillPath(ctx);
CGContextBeginPath(ctx);
CGContextAddPath(ctx, inactiveClip);
CGContextClosePath(ctx);
CGContextClip(ctx);
}
This was to say to CoreGraphics, "Okay before you actually color bits, check them against this clipping region."
Alas, nothing is clipped.
Since it is the case that my clipping path uses some of the same points and control points, in the same order, as the fill path, I have also replaced the call to CGContextClip with a call to CGContextEOClip to see if I was really struggling with the even-odd rule, but that doesn't seem to have had any visual affect.
I don't really know if I needed to bracket the CGContextAddPath call with CGContextBeginPath/CGContextClosePath calls, but what I was trying to do was minimize the differences between Apple's example code and my code. In theirs they do their CGContext... drawing calls between begin/close calls so I was too.
What am I misunderstanding here?
I'm trying to programmatically recreate the indented button look that can be seen on a UINavigationBarButton. Not the shiny two tone look or the gradient, just the perimeter shading:
It looks like an internal dark shadowing around the entire view perimeter, slightly darker at the top? And then an external highlighting shadow around the lower view perimeter.
I've played a bit with Core Graphics, and experimented with QuartzCore and shadowing with view.layer.shadowRadius and .shadowOffset, but can't even get the lower highlighting to look right. I'm also not sure where to start to achieve both a dark shadowing with internal offset and a light shadowing with external offset.
It seems as though you want a border that looks looks like a shadow. Since the shadow appears to some sort of gradient, setting a border as a gradient won't be possible at first glance. However, it is possible to create a path that represents the border and then fill that with a gradient. Apple provides what seems to be a little known function called CGPathCreateCopyByStrokingPath. This takes a path (say, a rounded rect, for example) and creates a new path that would be the stroke of the old path given the settings you pass into the function (like line width, join/cap setting, miter limit, etc). So lets say you define a path (this isn't exactly what Apple provides, but's it's similar):
+ (UIBezierPath *) bezierPathForBackButtonInRect:(CGRect)rect withRoundingRadius:(CGFloat)radius{
UIBezierPath *path = [UIBezierPath bezierPath];
CGPoint mPoint = CGPointMake(CGRectGetMaxX(rect) - radius, rect.origin.y);
CGPoint ctrlPoint = mPoint;
[path moveToPoint:mPoint];
ctrlPoint.y += radius;
mPoint.x += radius;
mPoint.y += radius;
if (radius > 0) [path addArcWithCenter:ctrlPoint radius:radius startAngle:M_PI + M_PI_2 endAngle:0 clockwise:YES];
mPoint.y = CGRectGetMaxY(rect) - radius;
[path addLineToPoint:mPoint];
ctrlPoint = mPoint;
mPoint.y += radius;
mPoint.x -= radius;
ctrlPoint.x -= radius;
if (radius > 0) [path addArcWithCenter:ctrlPoint radius:radius startAngle:0 endAngle:M_PI_2 clockwise:YES];
mPoint.x = rect.origin.x + (10.0f);
[path addLineToPoint:mPoint];
[path addLineToPoint:CGPointMake(rect.origin.x, CGRectGetMidY(rect))];
mPoint.y = rect.origin.y;
[path addLineToPoint:mPoint];
[path closePath];
return path;
}
This returns a path similar to Apple's back button (I use this in my app). I have added this method (along with dozens more) as a category to UIBezierPath.
Now lets add that inner shadow in a drawing routine:
- (void) drawRect:(CGRect)rect{
UIBezierPath *path = [UIBezierPath bezierPathForBackButtonInRect:rect withRoundingRadius:5.0f];
//Just fill with blue color, do what you want here for the button
[[UIColor blueColor] setFill];
[path fill];
[path addClip]; //Not completely necessary, but borders are actually drawn 'around' the path edge, so that half is inside your path, half is outside adding this will ensure the shadow only fills inside the path
//This strokes the standard path, however you might want to might want to inset the rect, create a new 'back button path' off the inset rect and create the inner shadow path off that.
//The line width of 2.0f will actually show up as 1.0f with the above clip: [path addClip];, due to the fact that borders are drawn around the edge
UIBezierPath *innerShadow = [UIBezierPath bezierPathWithCGPath: CGPathCreateCopyByStrokingPath(path.CGPath, NULL, 2.0f, path.lineCapStyle, path.lineJoinStyle, path.miterLimit)];
//You need this, otherwise the center (inside your path) will also be filled with the gradient, which you don't want
innerShadow.usesEvenOddFillRule = YES;
[innerShadow addClip];
//Now lets fill it with a vertical gradient
CGContextRef context = UIGraphicsGetCurrentContext();
CGPoint start = CGPointMake(0, 0);
CGPoint end = CGPointMake(0, CGRectGetMaxY(rect));
CGFloat locations[2] = { 0.0f, 1.0f};
NSArray *colors = [NSArray arrayWithObjects:(id)[UIColor colorWithWhite:.7f alpha:.5f].CGColor, (id)[UIColor colorWithWhite:.3f alpha:.5f].CGColor, nil];
CGGradientRef gradRef = CGGradientCreateWithColors(CGColorSpaceCreateDeviceRGB(), (__bridge CFArrayRef)colors, locations);
CGContextDrawLinearGradient(context, gradRef, start, end, 0);
CGGradientRelease(gradRef);
}
Now this is just a simple example. I don't save/restore contexts or anything, which you'll probably want to do. There are things you might still want to do to make it better, like maybe inset the 'shadow' path if you want to use a normal border. You might want to use more/different colors and locations. But this should get you started.
UPDATE
There is another method you can use to create this effect. I wrote an algorithm to bevel arbitrary bezier paths in core graphics. This can be used to create the effect you're looking for. This is an example of how I use it in my app:
You pass to the routine the CGContextRef, CGPathRef, size of the bevel and what colors you want it to use for the highlight/shadow.
The code I used for this can be found here:Github - Beveling Algorithm.
I also explain the code and my methodology here: Beveling-Shapes in Core Graphics
Using the layer's shadow won't do it. You need both a light outer shadow and a dark inner shadow to get that effect. A layer can only have one (outer) shadow. (Also, layer shadows are redrawn dynamically, and force CPU-based rendering which kills performance.)
You'll need to do your own drawing with CoreGraphics, either in a view's drawRect: method or a layer's drawInContext: method. (Or you draw into an image context and then reuse the image.) Said drawing will mostly use CGContext functions. (I'll name some below, but this link has documentation for them all.)
For a round rect button, you might find it tedious to create the appropriate CGPath -- instead, you can use +[UIBezierPath bezierPathWithRoundedRect:cornerRadius:] and then the path's CGPath property to set the context's current path with CGContextAddPath.
You can create an inner shadow by setting a clipping path (see CGContextClip and related functions) to the shape of the button, setting up a shadow (see CGContextSetShadowWithColor and related functions), and then drawing around the outside of the shape you want shadowed. For the inner shadow, stroke (CGContextStrokePath) a round-rect that's a bit larger than your button, using a thick stroke width (CGContextSetLineWidth) so there's plenty of "ink" to generate a shadow (remember, this stroke won't be visible due to the clipping path).
You can create an outer shadow in much the same way -- don't use a clipping path this time, because you want the shadow to be outside the shape, and fill (CGContextFillPath) the shape of your button instead of stroking it. Note that drawing a shadow is sort of a "mode": you save the graphics state (CGContextSaveGState), setup a shadow, then draw the shape you want to see a shadow of (the shape itself isn't drawn when you're in this mode), and finally restore state (CGContextRestoreGState) to get out of "shadow mode". Since that mode doesn't draw the shape, only the shadow, you'll need to draw the shape itself separately.
There's an order to do this all in, too. It should be obvious if you think about the order in which you'd paint these things with physical media: First draw the outer shadow, then the button's fill, then the inner shadow. You might add a stroke after that if the inner shadow doesn't give you a pronounced enough outline.
There are a few drawing tools which can output source code for CoreGraphics: Opacity is one that I use. Be careful with these, though, as they code they generate may not be efficient.
I'm able to use CGContextDrawRadialGradient to make a sphere that does an alpha fade to UIColor.clearColor and it works.
However, I'm trying to do this type of thing:
While placing some strategic spheres around makes for an interesting effect (similar to LED backlights in strategic places), I would love to get a true glow. How can I draw a glow around a rounded rectangle in drawRect?
You can create a glow effect around any path using CGContextSetShadowWithColor, but you don't get precise control over the appearance. In particular, the default shadow is fairly light:
And the only way I know of to make it darker is to draw it again over itself:
Not optimal, but it approximates what you want pretty well.
Those images were generated by the following drawRect:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGMutablePathRef path = CGPathCreateMutable();
int padding = 20;
CGPathMoveToPoint(path, NULL, padding, padding);
CGPathAddLineToPoint(path, NULL, rect.size.width - padding, rect.size.height / 2);
CGPathAddLineToPoint(path, NULL, padding, rect.size.height - padding);
CGContextSetShadowWithColor(context, CGSizeZero, 20, UIColor.redColor.CGColor);
CGContextSetFillColorWithColor(context, UIColor.blueColor.CGColor);
CGContextAddPath(context, path);
CGContextFillPath(context);
// CGContextAddPath(context, path);
// CGContextFillPath(context);
CGPathRelease(path);
}
One thing to bear in mind is that rendering fuzzy shadows is fairly expensive, which may or may not be a problem depending on how often your views are redrawn. If the shadows don't need to animate, consider rendering them to a UIImage once and just displaying the result in a UIImageView.