CGPathRef intersection

2020-02-08 21:43发布

问题:

Is there a way to find out whether two CGPathRefs are intersected or not. In my case all the CGPaths are having closePath.

For example, I am having two paths. One path is the rectangle which is rotated with some angle and the other path is curved path. Two paths origin will be changing frequently. At some point they may intersect. I want to know when they are intersected. Please let me know if you have any solution.

Thanks in advance

回答1:

Make one path the clipping path, draw the other path, then search for pixels that survived the clipping process:

// initialise and erase context
CGContextAddPath(context, path1);
CGContextClip(context);

// set fill colour to intersection colour
CGContextAddPath(context, path2);
CGContextFillPath(context);

// search for pixels that match intersection colour

This works because clipping = intersecting.

Don't forget that intersection depends on the definition of interiority, of which there are several. This code uses the winding-number fill rule, you might want the even odd rule or something else again. If interiority doesn't keep you up at night, then this code should be fine.

My previous answer involved drawing transparent curves to an RGBA context. This solution is superior to the old one because it is

  1. simpler
  2. uses a quarter of the memory as an 8bit greyscale context suffices
  3. obviates the need for hairy, difficult-to-debug transparency code

Who could ask for more?

I guess you could ask for a complete implementation, ready to cut'n'paste, but that would spoil the fun and obfuscate an otherwise simple answer.

OLDER, HARDER TO UNDERSTAND AND LESS EFFICIENT ANSWER

Draw both CGPathRefs separately at 50% transparency into a zeroed, CGBitmapContextCreate-ed RGBA memory buffer and check for any pixel values > 128. This works on any platform that supports CoreGraphics (i.e. iOS and OSX).

In pseudocode

// zero memory

CGContextRef context;
context = CGBitmapContextCreate(memory, wide, high, 8, wide*4, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaPremultipliedLast);
CGContextSetRGBFillColor(context, 1, 1, 1, 0.5);  // now everything you draw will be at 50%

// draw your path 1 to context

// draw your path 2 to context

// for each pixel in memory buffer
if(*p > 128) return true; // curves intersect  
else p+= 4; // keep looking

Let the resolution of the rasterised versions be your precision and choose the precision to suit your performance needs.



回答2:

1) There isn't any CGPath API to do this. But, you can do the math to figure it out. Take a look at this wikipedia article on Bezier curves to see how the curves in CGPath are implemented.

2) This is going to be slow on the iPhone I would expect but you could fill both paths into a buffer in difference colors (say, red and blue, with alpha=0.5) and then iterate through the buffer to find any pixels that occur at intersections. This will be extremely slow.



回答3:

For iOS, the alpha blend seems to be ignored.

Instead, you can do a color blend, which will achieve the same effect, but doesn't need alpha:

CGContextSetBlendMode(context, kCGBlendModeColorDodge);
CGFloat semiTransparent[] = { .5,.5,.5,1};

Pixels in output Image will be:

  • RGB = 0,0,0 = (0.0f) ... no path
  • RGB = 64,64,64 = (0.25f) ... one path, no intersection
  • RGB = 128,128,128 = (0.5f) ... two paths, intersection found

Complete code for drawing:

-(void) drawFirst:(CGPathRef) first second:(CGPathRef) second into:(CGContextRef)context
{
    /** setup the context for DODGE (everything gets lighter if it overlaps) */
    CGContextSetBlendMode(context, kCGBlendModeColorDodge);

    CGFloat semiTransparent[] = { .5,.5,.5,1};

    CGContextSetStrokeColor(context, semiTransparent);
    CGContextSetFillColor(context, semiTransparent);

    CGContextAddPath(context, first);
    CGContextFillPath(context);
    CGContextStrokePath(context);

    CGContextAddPath(context, second);
    CGContextFillPath(context);
    CGContextStrokePath(context);
}

Complete code for checking output:

[self drawFirst:YOUR_FIRST_PATH second:YOUR_SECOND_PATH into:context];

// Now we can get a pointer to the image data associated with the bitmap
// context.
BOOL result = FALSE;
unsigned char* data = CGBitmapContextGetData (context);
if (data != NULL) {

    for( int i=0; i<width; i++ )
        for( int k=0; k<width; k++ )
        {
    //offset locates the pixel in the data from x,y.
    //4 for 4 bytes of data per pixel, w is width of one row of data.
    int offset = 4*((width*round(k))+round(i));
    int alpha =  data[offset];
    int red = data[offset+1];
    int green = data[offset+2];
    int blue = data[offset+3];

            if( red > 254 )
            {
                result = TRUE;
                break;
            }
        }

And, finally, here's a slightly modified code from another SO answer ... complete code for creating an RGB space on iOS 4, iOS 5, that will support the above functions:

- (CGContextRef) createARGBBitmapContextWithFrame:(CGRect) frame
{
   /** NB: this requires iOS 4 or above - it uses the auto-allocating behaviour of Apple's method, to reduce a potential memory leak in the original StackOverflow version */
    CGContextRef    context = NULL;
    CGColorSpaceRef colorSpace;
    void *          bitmapData;
    int             bitmapByteCount;
    int             bitmapBytesPerRow;

    // Get image width, height. We'll use the entire image.
    size_t pixelsWide = frame.size.width;
    size_t pixelsHigh = frame.size.height;

    // Declare the number of bytes per row. Each pixel in the bitmap in this
    // example is represented by 4 bytes; 8 bits each of red, green, blue, and
    // alpha.
    bitmapBytesPerRow   = (pixelsWide * 4);
    bitmapByteCount     = (bitmapBytesPerRow * pixelsHigh);

    // Use the generic RGB color space.
    colorSpace = CGColorSpaceCreateDeviceRGB();
    if (colorSpace == NULL)
    {
        fprintf(stderr, "Error allocating color space\n");
        return NULL;
    }

    // Create the bitmap context. We want pre-multiplied ARGB, 8-bits
    // per component. Regardless of what the source image format is
    // (CMYK, Grayscale, and so on) it will be converted over to the format
    // specified here by CGBitmapContextCreate.
    context = CGBitmapContextCreate (NULL,
                                     pixelsWide,
                                     pixelsHigh,
                                     8,      // bits per component
                                     bitmapBytesPerRow,
                                     colorSpace,
                                     kCGImageAlphaPremultipliedFirst
                                     //kCGImageAlphaFirst
                                     );
    if (context == NULL)
    {
        fprintf (stderr, "Context not created!");
    }

    // Make sure and release colorspace before returning
    CGColorSpaceRelease( colorSpace );

    return context;
}