I have a rectangular CGMutablePathRef and I want to subtract a circle which lays exactly on centered on one edge of that rectangle, so the edge does not cross the circle anymore.
There seem to be no functions to intersect or subtract paths from another. How can I do it?
You need to look at the CGContext you are drawing into and use the clipping on the context rather than the path.
Apple's documentation is here.
If I understand your question, you can draw your rectangle into the context and then "clip out" the circle path. If you are filling the paths, you'll need to pay attention to the winding rules.
Alternatively, you can make your path with a series of commands such as CGPathAddLineToPoint, CGPathAddArcToPoint, etc and then stoke the path in your context. If you use this approach, you can then apply transforms to the final path for scaling and rotating as needed. Depending on what you are trying to accomplish, this may be the better approach.
Related
In this thread I asked how to create animations of blurred-edged filled paths, to create a "circle wipe" animation with feathered edges.
The solution is to install the path in the shadowPath property of a layer an animate that. Core Graphics applies a variable amount of blur to the shadow path, and the shadowPath property is animatable. However, it fills whatever path you install into it. You can't use it to draw feathered/blurred stroked paths, since what gets rendered as the shadow is a closed, filled version of whatever path you install.
There is a method copy(strokingWithWidth:lineCap:lineJoin:miterLimit:transform:) that will convert a stroked path into a closed path that traces the outline of the stroked path. If you fill the output of that method, you get what looks like a stroked version of the original path. You can install that converted path into the shadowPath of a layer and get a nice, blurred stroked path.
However, under the covers, the copy(strokingWithWidth:lineCap:lineJoin:miterLimit:transform:) method does some things that make the resulting path unsuitable for path animation. If you look at Rob Mayoff's excellent answer in this thread, you see that for inside corners, Core Graphics creates odd triangles that overlap with the interior of the stroked shape. It also seems to sometimes vary the number of vertices in the output of the path depending on it's shape.
For paths stroked with a fairly thin line width, they animate well. Here is an example:
But when you increase the line width and/or blur radius, things go "all pear-shaped" (meaning they go very wrong.) Here is an animation of the same curve with different settings:
You can see the project that generates these images on Github, and try it yourself.
Question:
Does anybody know of a way to convert a stroked path to a filled path that does not have the artifacts described in Rob Mayoff's post?
If I have to I can write my own version of copy(strokingWithWidth:lineCap:lineJoin:miterLimit:transform:) that creates a filled path with a fixed number of vertexes and without the artifacts that method creates, but it would be pretty involved. I'm hoping there is an existing solution.
Is it possible to have irregularly shaped images positioned adjacent to each other, where each individual image is clickable within its own boundaries?
For example, if I had a map of the US and I want to click each state and have a separate segue for each:
(https://upload.wikimedia.org/wikipedia/commons/thumb/a/a5/Map_of_USA_with_state_names.svg/2000px-Map_of_USA_with_state_names.svg.png)
I appreciate any tips/pointers in the right direction. Thanks!
Whether the map is really a bunch of irregularly shaped images, or just one image, is immaterial. (The latter will be easier.) You can just define a separate UIBezierPath objects that outline each of the states, and then you can use the UIBezierPath method containsPoint to determine whether some tap point is contained within the respective state.
Frankly, you might consider how much accuracy you really need. For example, if looking at map of US from continental scale, you really don't need extremely accurate bezier paths. Often a simple irregular polygon shape can approximate the boundaries and is more than sufficient for hit tests.
In fact, you sometimes deliberately use a much bigger bezier path. For example, you might draw a single path that goes around all of the Hawaiian islands, with some leeway, so that you don't have to tap right on the actual island, but just somewhere close. Or, for Rhode Island, you might allow a tap on the text "Rhode Island", as well as the state itself.
How would I efficiently draw a CGPath on a CATiledLayer? I'm currently checking if the bounding box of the tile intersects the bounding box of the path like this:
-(void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context {
CGRect boundingBox = CGPathGetPathBoundingBox(drawPath);
CGRect rect = CGContextGetClipBoundingBox(context);
if( !CGRectIntersectsRect(boundingBox, rect) )
return;
// Draw path...
}
This is not very efficient as the drawLayer:inContext: is called multiple times from multiple threads and results in drawing the path many times.
Is there a better, more efficient way to do this?
The simplest option is to draw your curve into a large image and then tile the image. But if you're tiling, it probably means the image would be too large, or you would have just drawn the path in the first place, right?
So you probably need to split your path up. The simplest approach is to split it up element by element using CGPathApply. For each element, you can check its bounding box and determine if that element falls in your bounds. If not, just keep track of the last end point. If so, then move to the last end point you saw and add the element to a new path for this tile. When you're done, each tile will draw its own path.
Technically you will "draw" things that go outside your bounds here (such as a line that extends beyond the tile), but this is much cheaper than it sounds. Core Graphics is going to clip single elements very easily. The goal is to avoid calculating elements that are not in your bounding box at all.
Be sure to cache the resulting path. You don't need to calculate the path for every tile; just the ones you're drawing. But avoid recalculating it every time the tile draws. Whenever the data changes, dump your cache. If there are a very large number of tiles, you can also use NSCache to optimize this even better.
You don't show where the path gets created. If possible, you might try building the path up in the -drawLayer:inContext: method, only creating the portion of it needed for the tile being drawn.
As with all performance problems, you should use Instruments to profile your code and find out exactly where the bottlenecks are. Have you tried that already, and if so, what did you find?
As a side note, is there a reason you're using CGPath instead of UIBezierPath? From Apple's documentation:
For creating paths in iOS, it is recommended that you use UIBezierPath
instead of CGPath functions unless you need some of the capabilities
that only Core Graphics provides, such as adding ellipses to paths.
For more on creating and rendering paths in UIKit, see “Drawing Shapes
Using Bezier Paths.”
I have a sprite with its bounding box and I want to detect the intersection of it with a CGMutablePathRef. How would I do that ?
There's no readily available solution for that. It also depends on what features of CGPath you are using. If it's just a series of points, you can use a regular line with rectangle intersection test.
Everything else (eg bezier curve and rectangle intersection) is going to be very complicated.
If it's a pure rectangular comparison, you can use CGPathGetBoundingBox to obtain the path's bounding box and then use CGRectIntersectsRect to determine if the intersection occurs.
Depending on performance needs, draw into a 1-bit deep bitmap, clipping to the CGRect. Then scan for a pixel. (This technique tends to be more appropriate for testing to a point.)
My Question is something similar to this.
I have 2 CGPathRef and 1 will be moved by finger touch. I want to find that whether the 2 CGPathRef are intersected? That question was asked almost 2 years ago and I want to know whether something has been found in the mean time.
This is fairly old, but I found it looking for a similar solution, in my problem I wanted to find when a circle overlapped with a path (a special case of your question).
I solved this by using CGPathCreateCopyByStrokingPath to create a stroked version of the original path using the radius of the circle as the stroke width. If the center point of the circle overlaps the stroked path then the original path overlaps the circle.
BOOL CGPathIntersectsCircle(CGPathRef path, CGPoint center, CGFloat radius)
{
CGPathRef fuzzyPath;
fuzzyPath = CGPathCreateCopyByStrokingPath(path, NULL, radius,
kCGLineCapRound,
kCGLineJoinRound, 0.0);
if (CGPathContainsPoint(fuzzyPath, NULL, center, NO))
{
CGPathRelease(fuzzyPath);
return YES;
}
CGPathRelease(fuzzyPath);
return NO;
}
Edit: A minor bug where the fuzzyPath was not released.
I have written a small pixel based path collision detection API for CGPathRefs. It requires that you add a few source directories to your project, and it only works with ARC, but it should at least show you how one might do something like this. It basically draws the two paths on two separate contexts, and then does pixel-by-pixel checks to see if any pixels are on both paths. Obviously this would be slow to run every time the user drags their finger, but it certainly could be done once every half second or so, maybe not even on the main thread.
This is the easiest way I've found of doing something like this, and it may easily be that there's no better way, besides using lots of math.
The source on Github
A quick Youtube demo.
Generally speaking, finding the intersection of two arbitrary CGPaths is going to be very complex.
There are ways to do approximations. Checking the intersections of the bounding boxes is a good first step. You can also subdivide the curve and repeat the process to get better approximations. Another option is to flatten the paths and see if any of the line segments of the flattened paths intersect.
For the general case, however, things get very nasty very fast. Consider, for example, the fact that two cubic bezier segments (never mind an entire path... just one segment) can intersect with another segment at up to 6 points. The more segments in your path, the more potential intersections. There is also the problem of degenerate bezier curves where a segment has a cusp that just touches one point of another segment. Does that count as an intersection? (sometimes yes, sometimes no)
It's not clear from your question, but you might also want to consider the intersections of the strokes that are applied to the curves, and correctly account for line joins and miters. That that gets even harder. Macromedia FreeHand (a drawing program similar to Adobe Illustrator) had a very large, complex, intensely mathematical library for discovering arbitrary bezier curve intersections. The problem is not easily solved.
To find the intersection of two CAShapeLayers, we can use below method, CAShapeLayer won't return frame. But we can get the refPath frame using CGPathGetBoundingBox. But this one will give the frame in rectangle.I thing you may understand.
if (CGRectIntersectsRect(CGPathGetBoundingBox(layer.path), CGPathGetBoundingBox(layer.path)))