First off, here's an image of my current situation.
To accomplish this I am using a subclassed UIView with the following method:
-(void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextBeginPath(ctx);
CGContextMoveToPoint (ctx, CGRectGetMinX(rect), CGRectGetMinY(rect)); // top left
CGContextAddLineToPoint(ctx, CGRectGetMaxX(rect), CGRectGetMidY(rect)); // mid right
CGContextAddLineToPoint(ctx, CGRectGetMinX(rect), CGRectGetMaxY(rect)); // bottom left
CGContextClosePath(ctx);
CGContextSetRGBFillColor(ctx, 0, 1, 0, 1);
CGContextFillPath(ctx);
}
My ultimate goal is to clip the colored regions so that only the neon green remains. (I'll flip the colors later). I want it to look like a pie chart. I'm guessing there's a way to clip the colored corners out but I have no clue. CGContextClip(ctx) doesn't work.
I am using the Pixate framework as well. If anyone knows a way to accomplish a triangle shape with Pixate that would be even better.
The easiest way to clip a UIView to a path is to create a CAShapeLayer, add a path to the layer, and add the shape layer as a mask of your view's layer.
Or with a CGPathRef, essentially the same thing but at core foundation level
eg..
{
// before existing drawing code..
CGMutablePathRef clipTriangle = CGPathCreateMutable();
CGPathMoveToPoint (clipTriangle, nil, startX, startY);
CGPathAddLineToPoint(clipTriangle, nil, secondPointX, secondPointY);
CGPathAddLineToPoint(clipTriangle, nil, thirdPointX, thirdPointY);
CGPathCloseSubpath(clipTriangle);
CGContextSaveGstate(ctx);
CGContextAddPath(ctx, clipTriangle); /// this is the essential line I left out, we drew the triangle and forgot to use it, clipping to nothing instead, sorry mate
CGContextClip(ctx);
//draw stuff that you wanted clipped here...
CGContextRestoreGstate(ctx);
CGPathRelease(clippingTriangle); //CFstuff still needs manual memory management regardless of ARC
}
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 achieve the shape shown in image using UIBezier Path, and too the shape is filled with blocks in image it shows one block is filled, how to achieve this.
I have tried the following code taken from here
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, 10)];
[path addQuadCurveToPoint:CGPointMake(200, 10) controlPoint:CGPointMake(100, 5)];
[path addLineToPoint:CGPointMake(200, 0)];
[path addLineToPoint:CGPointMake(0, 0)];
[path closePath];
Thanks.
It looks to me like both the outline and also each block has the same shape. What you would probably do is to make one shape for the outline, and stroke it, and one shape for each cell and fill it.
Creating the shape
Each shape could be created something like this (as I've previously explained in this answer). It's done by stroking one path (the orange arc) which is a simple arc from one angle to another to get another path (the dashed outline)
Before we can stroke the path we to create it. CGPath's work just like UIBezierPath but with a C API. First we move to the start point, then we add an arc around the center from the one angle to another angle.
CGMutablePathRef arc = CGPathCreateMutable();
CGPathMoveToPoint(arc, NULL,
startPoint.x, startPoint.y);
CGPathAddArc(arc, NULL,
centerPoint.x, centerPoint.y,
radius,
startAngle,
endAngle,
YES);
Now that we have the centered arc, we can create one shape path by stroking it with a certain width. The resulting path is going to have the two straight lines (because we specify the "butt" line cap style) and the two arcs (inner and outer). As you saw in the image above, the stroke happens from the center an equal distance inwards and outwards.
CGFloat lineWidth = 10.0;
CGPathRef strokedArc =
CGPathCreateCopyByStrokingPath(arc, NULL,
lineWidth,
kCGLineCapButt,
kCGLineJoinMiter, // the default
10); // 10 is default miter limit
You would do this a couple of times to create one path for the stroked outline and one path for each cell.
Drawing the shape
Depending on if it's the outline or a cell you would either stroke it or fill it. You can either do this with Core Graphics inside drawRect: or with Core Animation using CAShapeLayers. Choose one and don't between them :)
Core Graphics
When using Core Graphics (inside drawRect:) you get the graphics context, configure the colors on it and then stroke the path. For example, the outline with a gray fill color and a black stroke color would look like this:
I know that your shape is filled white (or maybe it's clear) with a light blue stroke but I already had a gray and black image and I didn't want to create a new one ;)
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextAddPath(c, strokedArc); // the path we created above
CGContextSetFillColorWithColor(c, [UIColor lightGrayColor].CGColor);
CGContextSetStrokeColorWithColor(c, [UIColor blackColor].CGColor);
CGContextDrawPath(c, kCGPathFillStroke); // both fill and stroke
That will put something like this on screen
Core Animation
The same drawing could be done with a shape layer like this:
CAShapeLayer *outline = [CAShapeLayer layer];
outline.fillColor = [UIColor lightGrayColor].CGColor;
outline.strokeColor = [UIColor blackColor].CGColor;
outline.lineWidth = 1.0;
outline.path = strokedArc; // the path we created above
[self.view.layer addSublayer: outline];
I am trying to draw a CGPath that has a stroke for it's stroke.
Basically I want a draw a line using CGPath. I then want to go back and draw lines on both sides of the last CGPath giving it the effect that it is outlines.
This line can bend and turn in any way but I always need the two lines on the outside to follow.
EDIT: I need to be able to make the middle of the line transparent but the outlines solid black.
Use CGPathCreateCopyByStrokingPath to create a new path by stroking your old path at some width. Then draw your new path using kCGPathFillStroke.
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, 50, 50);
CGPathAddLineToPoint(path, NULL, 200, 200);
CGPathRef thickPath = CGPathCreateCopyByStrokingPath(path, NULL, 10, kCGLineCapButt, kCGLineJoinBevel, 0);
CGContextAddPath(context, thickPath);
CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
CGContextSetFillColorWithColor(context, [UIColor blueColor].CGColor);
CGContextSetLineWidth(context, 3);
CGContextDrawPath(context, kCGPathFillStroke);
CGPathRelease(thickPath);
CGPathRelease(path);
}
The simplest solution would just be to stroke the path twice. First with black at a bigger stroke width and then stroke the same path again using the blue color with a slightly smaller stroke width.
Edit:
If I remember correctly you can use CGPathCreateCopyByStrokingPath(...) to create a new path that you then can both stroke and fill. Then you could use semi-transparent colors.
From the documentation:
CGPathCreateCopyByStrokingPath
Creates a stroked copy of another path.
CGPathRef CGPathCreateCopyByStrokingPath(
CGPathRef path,
const CGAffineTransform *transform,
CGFloat lineWidth,
CGLineCap lineCap,
CGLineJoin lineJoin,
CGFloat miterLimit
);
Parameters
path
The path to copy.
transform
A pointer to an affine transformation matrix, or NULL if no transformation is needed. If specified, Quartz applies the transformation to elements of the converted path before adding them to the new path.
lineWidth
The line width to use, in user space units. The value must be greater than 0.
lineCap
A line cap style constant—kCGLineCapButt (the default), kCGLineCapRound, or kCGLineCapSquare. See “CGLineCap”.
lineJoin
A line join value—kCGLineJoinMiter (the default), kCGLineJoinRound, or kCGLineJoinBevel. See “CGLineJoin”.
miterLimit
The miter limit to use.
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 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.