15

I have an app where I take a UIBezierPath and use it as a brush by a series of appendPath: calls. After a few goes and with really complex brush shapes the memory runs out and the app grinds to a halt. What I really want to do is a full on union exactly like Paint Code does but I can't find any way of doing this.

How would I go about unioning two or more UIBezierPaths?

EDIT:

Here is a visual of what I want to achieve dynamically.

In Paint Code you take two paths and overlap them like this: Overlapping paths in Paint Code

BUT I want to merge / union them into one new single path like:

Merged paths in Paint Code

Note that in the bottom panel of Paint Code there is code for now one single shape and this is what I want to be able to get to programatically with maybe 1000 original paths.

Martin
  • 1,135
  • 1
  • 8
  • 19
  • one approach is to render out to an image the current paths when, for example, the path count reaches a certain number. This will prevent having to draw each path every time a new path is appended. – MDB983 May 06 '14 at 14:52
  • The problem is that this is the first of several steps including creating a mask from the path and then stroking. – Martin May 06 '14 at 21:33
  • posting some code might give others a better idea of what you're trying to accomplish – MDB983 May 07 '14 at 02:34
  • "After a few goes and with really complex brush shapes the memory runs out and the app grinds to a halt" That is the problem you should be working on. There is a WWDC video devoted to this very issue, IIRC... – matt May 08 '14 at 15:12
  • 2
    I believe that [VectorBoolean](https://bitbucket.org/andyfinnell/vectorboolean) is the best library out there. I haven't used it myself but [this blog post](http://losingfight.com/blog/2014/01/24/fixes-and-performance-enhancements-for-vectorboolean/) seem to indicate that Sketch.app is using it, and Sketch is a powerful vector graphics app. – David Rönnqvist May 08 '14 at 15:19
  • 1
    The same blog has 3 blog posts ([one](http://losingfight.com/blog/2011/07/07/how-to-implement-boolean-operations-on-bezier-paths-part-1/), [two](http://losingfight.com/blog/2011/07/08/how-to-implement-boolean-operations-on-bezier-paths-part-2/), [three](http://losingfight.com/blog/2011/07/09/how-to-implement-boolean-operations-on-bezier-paths-part-3/)) about boolean operators on Bézier paths that you might find useful. – David Rönnqvist May 08 '14 at 15:19
  • @matt, the memory issue this is the problem I am trying to work on. Imagine a path containing 50 points, now brush that so that several hundred copies are appended and now try to stroke those paths - always going to grind to a halt. This is why I want to simplify the path to remove the unnecessary stuff in the middle of the shape. – Martin May 08 '14 at 23:33
  • @DavidRönnqvist thanks, looks promising. The VectorBoolean project is for mac rather than iOS but it will hopefully work. If you want to add this as an answer I'll test this and mark correct if it works. – Martin May 08 '14 at 23:34
  • @Martin But that sort of problem is exactly what the WWDC video talks about and shows you how to avoid, so that you are NOT making "several hundred copies". – matt May 09 '14 at 01:21

3 Answers3

3

You can get desired result easily by following 2 concepts of Core Graphics :-

i)CGBlendMode ii)OverLap2Layer

Blend modes tell a context how to apply new content to itself. They determine how pixel data is digitally blended.

 class UnionUIBezierPaths : UIView {

    var firstBeizerPath:UIImage!
    var secondBeizerPath:UIImage!

    override func draw(_ rect: CGRect) {
        super.draw(rect)

       firstBeizerPath = drawOverLapPath(firstBeizerpath: drawCircle(), secondBeizerPath: polygon())
       secondBeizerPath = drawOverLapPath(firstBeizerpath: polygon(), secondBeizerPath: drawCircle())

        let image = UIImage().overLap2Layer(firstLayer:firstBeizerPath , secondLayer:secondBeizerPath)
    }


    func drawCircle() -> UIBezierPath {
        let path = UIBezierPath(ovalIn: CGRect(x: 40, y: 120, width: 100, height: 100) )
        return path
    }

    func polygon() -> UIBezierPath {
        let beizerPath = UIBezierPath()
        beizerPath.move(to: CGPoint(x: 100, y: 10) )
        beizerPath.addLine(to: CGPoint(x: 200.0, y: 40.0) )
        beizerPath.addLine(to: CGPoint(x: 160, y: 140) )
        beizerPath.addLine(to: CGPoint(x: 40, y: 140) )
        beizerPath.addLine(to: CGPoint(x: 0, y: 40) )
        beizerPath.close()
        return beizerPath
    }

    func drawOverLapPath(firstBeizerpath:UIBezierPath ,secondBeizerPath:UIBezierPath )  -> UIImage {

        UIGraphicsBeginImageContext(self.frame.size)

        let firstpath = firstBeizerpath
        UIColor.white.setFill()
        UIColor.black.setStroke()
        firstpath.stroke()
        firstpath.fill()

        // sourceAtop = 20
        let mode = CGBlendMode(rawValue:20)
        UIGraphicsGetCurrentContext()!.setBlendMode(mode!)


        let secondPath = secondBeizerPath
        UIColor.white.setFill()
        UIColor.white.setStroke()
        secondPath.fill()
        secondPath.stroke()

        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return image!
    }



    func drawImage(image1:UIImage , secondImage:UIImage ) ->UIImage
    {
        UIGraphicsBeginImageContext(self.frame.size)
        image1.draw(in: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height) )
        secondImage.draw(in: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height) )

        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return newImage!
    }

      }

      //OverLap2Layer
      extension UIImage {
      func overLap2Layer(firstLayer:UIImage , secondLayer:UIImage ) -> UIImage {

                UIGraphicsBeginImageContext(firstLayer.size)
                firstLayer.draw(in: CGRect(x: 0, y: 0, width: firstLayer.size.width, height: firstLayer.size.height) )
                secondLayer.draw(in: CGRect(x: 0, y: 0, width: firstLayer.size.width, height: firstLayer.size.height) )

                let newImage = UIGraphicsGetImageFromCurrentImageContext()
                UIGraphicsEndImageContext()
                return newImage!
            }
        }

First Path :-

enter image description here

Second Path :-

enter image description here

Final Result :-

enter image description here

Reference:- Blend in Core Graphics , Creating Image

GitHub Demo

Shrawan
  • 7,128
  • 4
  • 29
  • 40
2

Finally a solution!!

Using https://github.com/adamwulf/ClippingBezier you can find the intersecting points. Then you can walk through the path, turning left if clockwise or vice-versa to stay on the outside. Then you can generate a new path using the sequence of points.

Martin
  • 1,135
  • 1
  • 8
  • 19
0

You can use the GPCPolygon, an Objective-C wrapper for GPC

-GPCPolygonSet*) initWithPolygons:(NSMutableArray*)points;

or

- (GPCPolygonSet*) unionWithPolygonSet:(GPCPolygonSet*)p2;

M. Porooshani
  • 1,797
  • 5
  • 34
  • 42