Merging paths in Core Graphics? - core-graphics

I've drawn two circles that overlap. I want to be able to fill and stroke them as a merged new shape.
At the moment, I create the path sequence once, stroke it, then create a copy of it, fill it and add the two identical paths on top of each other, so they appear as a single shape. Is there a better approach or is this ok?
Update: Here is a sample code:
CGMutablePathRef path = CGPathCreateMutable();
CGContextSetStrokeColorWithColor(theContext, strokeColor.CGColor);
CGContextSetLineWidth(theContext, 2);
CGContextSetFillColorWithColor(theContext, fillColor.CGColor);
CGRect rect1 = CGRectMake(0,0, mySize*0.6, mySize*0.6);
CGRect rect2 = CGRectMake(mySize*0.4,0, mySize*0.6, mySize*0.6);
CGPathAddEllipseInRect(path, NULL, rect1);
CGPathAddEllipseInRect(path, NULL, rect2);
CGContextAddPath(theContext, path);
CGContextDrawPath(theContext, kCGPathFillStroke);
CGPathRef pathFill = CGPathCreateCopy ( path );
CGContextAddPath(theContext, pathFill);
CGContextDrawPath(theContext, kCGPathFill);
CGPathRelease(path);
CGPathRelease(pathFill);
As you can see, I create a copy of the original path and draw it on top without the stroke, so in the end it looks like one united shape. Is there a way to avoid creating the duplicate?

Is there a way to avoid creating the duplicate?
Yes: Just don't create it.
Path objects in Core Graphics are paths and nothing else. They have no colors, no patterns, no fill or stroke properties, nothing—just subpaths consisting of moveto, lineto, curveto, and closepath segments.
The fill color, stroke color, line width, etc. are all properties of the graphics state in the context. The current path is also a property of the context (but not of the gstate).
When you add a path to the context, that's all you're doing: Adding the subpaths from the path object into the current path in the context. The original path object remains unchanged; it has no graphics state, and even if it did, the “add subpaths from path to context” operation changes the context, not the path object.
Similarly, filling or stroking the current path of a context only resets the current path of the context; it makes no changes to any path objects you might have used to build up that path. If it did, copying the path when you do would be too late, as the original would have already been changed—but it doesn't, so copying the path is unnecessary.
So, just add the same path object to the current path both times.

Related

How do I control animation timing along a NSBezier path?

In Apple's docs about CAKeyframeAnimation they say:
"For most types of animations, you specify the keyframe values using
the values and keyTimes properties. During the animation, Core
Animation generates intermediate values by interpolating between the
values you provide. When animating a value that is a coordinate point,
such as the layer’s position, you can specify a path for that point to
follow instead of individual values. The pacing of the animation is
controlled by the timing information you provide."
What I want to do is to make an animation of an image along the path above while controlling the timing. More specific, the path starts at (0,0) and goes to (100,0) during 1 sec, then follows a half-circle path to point (300,0) during 3 sec, and the goes to point (400,0) during 1 sec.
I have already definied this path as a NSBezier path and I can make the animation, but I don't know how to control the timing of the different parts of the path. From Apple's docs it seems that this should be possible, but how?
Get reference from here Controlling Animation Timing
There is a simple solution. The only thing you have to choose are the points on your path that you want to use for time control. In my case I have 4 points p0=(0,0), p1=(100,0), p2 =(200,0), and p3 =(300,0). I then use
animation.values = #[[NSValue valueWithPoint:p0],
[NSValue valueWithPoint:p1],
[NSValue valueWithPoint:p2],
[NSValue valueWithPoint:p3]];
animation.keyTimes = #[#0.0, #0.2, #0.8, #1.0];
The fact that we are using a half circle (or any other complicated) path DOES NOT have any influence (as long as it doesn't cross itself). You just choose some points on the path, use them to define animation.values, and then choose (relative) times for those points and use them to define animation.keyTimes. That's all, really.

Stroking rect background

I've recently updated PDFBox to version 2.0.0 (for its new images methods) but they've changed the way you fill a rect.
Previously you would addRect, set the stroking color and struck, set the non-stroking color and fill the rect.
Now the fillRect has been replaced with fill and I'm unable to stroke and fill. Calling stroking after filling will do nothing and vice-versa.
Now I'm forced to call addRect for a second time to get the desired effect.
Any help with updating my method?
Previously you would addRect, set the stroking color and struck
... and in doing so create an invalid PDF! Between the start of path creation (here: addRect) and its drawing (here: stroke), only additional path creation operations and eventually optionally a clipping path operation are allowed. Your setting the stroking color in-between is invalid.
Most PDF viewers don't complain about this invalid syntax but it is invalid nonetheless...
Now the fillRect has been replaced with fill and I'm unable to stroke and fill.
fillRect still is there and merely has been deprecated. Thus, you can look what it does:
public void fillRect(float x, float y, float width, float height) throws IOException
{
if (inTextMode)
{
throw new IllegalStateException("Error: fillRect is not allowed within a text block.");
}
addRect(x, y, width, height);
fill();
}
Thus, if you used to do
setStrokingColor(...);
addRect(...);
stroke();
setNonStrokingColor(...);
fillRect(...);
in PDFBox 1.8.x, you can do the same in 2.0.0 or (to not use deprecated methods) replace the last line by
addRect(...);
fill();
Now I'm forced to call addRect for a second time to get the desired effect.
But that is what you under the hood used to do before, too!
PS Recently two new operations have been added to PDFBox' PDPageContentStream class: fillAndStroke, fillAndStrokeEvenOdd, closeAndFillAndStroke, and closeAndFillAndStrokeEvenOdd. Thus, now you can also use
setStrokingColor(...);
setNonStrokingColor(...);
addRect(...);
fillAndStroke();

Getting the width of a path drawn on a XAML canvas

I'm drawing a canvas programmatically, given a bunch of path data from somewhere else and adding it to the canvas as
// This is actually done more elaborately, but will do for now
PathFigureCollection figures = GetPathFigureCollection();
var path = new Path
{
Data = new PathGeometry { Figures = figures },
Fill = GetFill(),
Stroke = GetStroke(),
StrokeThickness = GetThickness()
};
MyCanvas.Children.Add(path);
Now, I have the canvas in a ScrollViewer, so I want to make sure that I can scroll all the way to reveal the entire path (actually paths - I have several, generated the same way) but no further. I tried this:
var drawingWidth = MyCanvas.Children
.OfType<FrameworkElement>()
.Max(e => Canvas.GetLeft(e) + e.ActualWidth);
MyCanvas.Width = drawingWidth;
This works well for some other elements (the drawing also has a few text blocks and ellipses), but for the paths both Canvas.GetLeft(e) and e.ActualWith (as well as some other things I tried like e.RenderSize.Width and e.DesiredSize.With) all return 0. Since the element that extends farthest to the right is a path, this results in a canvas that is too small.
How do I get the width of the Path elements too?
Ha, found it!
Rewriting the LINQ query as a loop, I could cast paths to Path, and use path.Data.Bounds.Right as the right edge of that element.** I might be able to convert the code back to a LINQ query now that I know what I want to do (I always find them more readable than stateful loops...).
I found this when I, after having perused the link provided by markE where, as a side note, it was stated that
If your design requirements allow more rough approximates, then you will find that cubic Bezier curves are always contained within their control points.
So, if I could find the right-most control point of all the path figures in my path, I would be home. Intellisense did the rest of the job for me :)

Make independent copy of UIBezierPath?

I have a complex UIBezierCurve which I need to draw once with some particular line parameters, and then draw it again as overlay with other line parameters, but also I need the last part of the curve to be slightly shorter than in previous one.
To do this I want to create the curve by addLineToPoint:, moveToPoint: up to the last part, then make a copy of this curve and add the final segments of the line differently in original and copied curves. And then I stroke the original curve, and the copied one.
The problem is that it doesn't work as I expected.
I create a copy of the curve by:
UIBezierPath* copyCurve = [originalCurve copy];
And the drawing which I do in the originalCurve after that, is applied also to the copyCurve, so I can't do independent drawing for any of this curves.
What is the reason for this connection between original and copy and how can I get rid of it?
EDIT 1:
A solution which I've found is to create the copy in the following way:
UIBezierPath* copyCurve=[UIBezierPath bezierPathWithCGPath:CGPathCreateMutableCopy(originalCurve.CGPath)];
Since this works properly, maybe the problem is in the immutability of the copy I get with
[originalCurve copy]
Create a new, identical path by using the CGPath.
path2 = [UIBezierPath bezierPathWithCGPath:path1.CGPath];
The CGPath property docs state that:
This property contains a snapshot of the path at any given point in time. Getting this property returns an immutable path object that you can pass to Core Graphics functions.
copy() works fine for me as of Swift 4.
let copiedPath = originalPath.copy() as! UIBezierPath
copiedPath.addLine(...)
The originalPath does not get modified.
In addition to #jrturton answer :-
Alternatively we can use :-
let path = UIBezierPath(ovalIn: pathRect)
let newPath = path.cgPath.copy(strokingWithWidth: strokeWidth, lineCap: .butt, lineJoin: .miter, miterLimit: 0)
Reference

How to detect/handle touch events on curved regions?

I'm starting on a native iPad app (we can assume iOS 4.3+ if necessary) where I need to have a series of curved regions that bump up against each other.
I'd love some advice about the best way to handle this.
One thought I had was to use a WebView and just have a JPG and an HTML image map but I'd really prefer to use some kind of native UI element that supports curves.
Any recommendations?
We had a problem something like this. To resolve it, we created a black and white mask in Adobe Illustrator. You'll need to do this for each and every distinct region that you want.
Next, we exported this file. I can't remember the file export option, but basically you get a text file that has a load of path data that includes lines, bezier curves, etc.
We then took this file and wrote an importer that parsed it and created a CGPath.
The final stage is the easy bit. You get your touch point from UITouch and do a CGPathContainsPoint call.
Here's some pseudo code for this:
Skip lines until we get to one with "1 XR". That's the
indication of something meaningful in the subsequent line.
Split the line into an array of components using a separator of " ".
The last component of the line is the action. If it's "m" it's a path move, if it's "C", it's a bezier curve, and if it's "L" it's a line.
If it's a "Y" or a "V" then you need to get the previous line's components and parse as follows:
(a) Component count includes action, so we need to reduce this. E.g. 274.5600 463.6800 m
(b) If this line has four coordinates and the previous one has two, then it's a point node to a bezier node. Figure your bezier curve points as follows:
cp1x = previous line's components object at index 0
cp1y = previous line's components object at index 1
cp2x = this line's components object at index 0
cp2y = this line's components object at index 1
x = this line's components object at index 2
y = this line's components object at index 3
(c) Otherwise if this line has four coordinates and the previous line has four or six coordinates, figure as follows:
cp1x = this line's components object at index 0
cp1y = this line's components object at index 1
cp2x = this line's components object at index 2
cp2y = this line's components object at index 3
x = this line's components object at index 2
y = this line's components object at index 3
Where cp is "control point". So you have control point one and control point two with their respective x and y coordinates.
Create bezier paths that each represent separate regions (by doing lineToPoint or similar functions).
UIBezierPath *p1 = [UIBezierPath bezierPath];
[path1 lineToPoint:somePoint];
[pointArray1 addObject:NSStringFromCGPoint(somePoint)];
// create lots of points and close p1 path
Then find some triangulation algorithm for concave shapes that would turn each bezier path to an array of triangles (i.e. instead of storing array of bezier path point coordinates you'd store array of triangles' coordinates (array of array of points). Algorithm and explanations can be found in any game development forum or even on GameDev of stack exchange.
Repeat the bezier path creation and triangulation for each region.
Then having these arrays it's just a matter of simple iterations to check if certain point of interest is in one of these triangles.
update seems that #omz comment renders my answer useless, hence it's just a matter of creating bezier paths and calling method on each of them .)