Andrey05
Andrey05

Reputation: 37

Divide UIImage into two parts along a UIBezierPath

How to divide this UIImage by the black line into two parts. The upper contour set of UIBezierPath.

I need to get two resulting UIImages. So is it possible?

Upvotes: 2

Views: 1201

Answers (3)

tipycalFlow
tipycalFlow

Reputation: 7644

This can be done but it requires some trigonometry. Let's consider the case for the upper image. First, determine the bottommost end point of the UIBezierPath and use UIGraphicsBeginImageContext to get the top part of the image above the line. This will look as follows:

Top portion

Now, assuming that your line is straight, move pixel by pixel along the line drawing vertical strokes of clearColor (loop for top portion. Proceed on similar lines for bottom portion):

for(int currentPixel_x=0;currentPixel_x<your_ui_image_top.size.width)
    UIGraphicsBeginImageContext(your_ui_image_top.size);
    [your_ui_image_top drawInRect:CGRectMake(0, 0, your_ui_image_top.size.width, your_ui_image_top.size.height)];
    CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
    CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 1.0);
    CGContextSetBlendMode(UIGraphicsGetCurrentContext(),kCGBlendModeClear); 
    CGContextSetStrokeColorWithColor(UIGraphicsGetCurrentContext(),[UIColor clearColor].CGColor);
    CGContextBeginPath(UIGraphicsGetCurrentContext());
    CGContextMoveToPoint(UIGraphicsGetCurrentContext(), currentPixel_x, m*currentPixel_x + c);
    CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPixel_x, your_ui_image_top.size.height);
    CGContextStrokePath(UIGraphicsGetCurrentContext());
    your_ui_image_top = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
}

Your UIBezierPath will have to be converted to a straight line of the form y = m*x + c. The x in this equation will be currentPixel_x above. Iterate through the width of the image, increasingcurrentPixel_x by 1 each time. next_y_point_on_your_line will be calculated as:

next_y_point_on_your_line = m*currentPixel_x + c

Each vertical stroke will be 1 pixel wide and its height will depend on how you traverse through them. After some iterations, your image will look roughly (please excuse my poor photo-editing skills!) like:

enter image description here

There are multiple ways of how you draw the clear strokes and this is just one way of going about it. You can also have clear strokes that are parallel to the given path if it gives better results.

Another way is to set the alpha of the pixels below the line to 0.

Upvotes: 2

Cowirrie
Cowirrie

Reputation: 7226

I tested clipping, and in a few different tests it was 25% slower than masking to achieve the same result as the [maskImage: toAreaInsidePath:] method in my other answer. For completeness I include it here, but please don't use it without a good reason.

- (UIImage*) clipImage:(UIImage*) sourceImage toPath:(UIBezierPath*) path;
{
    // Create a new image of the same size as the source.
    UIGraphicsBeginImageContext([sourceImage size]);

    // Clipping means drawing only happens within the path.
    [path addClip];

    // Draw the image to the context.
    [sourceImage drawAtPoint:CGPointZero];

    // With drawing complete, store the composited image for later use.
    UIImage *clippedImage = UIGraphicsGetImageFromCurrentImageContext();

    // Graphics contexts must be ended manually.
    UIGraphicsEndImageContext();

    return clippedImage;
}

Upvotes: 2

Cowirrie
Cowirrie

Reputation: 7226

The following set of routines create versions of a UIImage with either only the content inside a path, or only content outside that path.

Both make use of the compositeImage method, which uses CGBlendMode. CGBlendMode is very powerful for masking anything you can draw against anything else you can draw. Calling compositeImage: with other blend modes can have interesting (if not always useful) effects. See the CGContext Reference for all the modes.

The clipping method I described in my comment to your OP does work and is probably faster, but only if you have UIBezierPaths defining all the regions you want to clip.

- (UIImage*) compositeImage:(UIImage*) sourceImage onPath:(UIBezierPath*) path usingBlendMode:(CGBlendMode) blend;
{
    // Create a new image of the same size as the source.
    UIGraphicsBeginImageContext([sourceImage size]);

    // First draw an opaque path...
    [path fill];
    // ...then composite with the image.
    [sourceImage drawAtPoint:CGPointZero blendMode:blend alpha:1.0];

    // With drawing complete, store the composited image for later use.
    UIImage *maskedImage = UIGraphicsGetImageFromCurrentImageContext();

    // Graphics contexts must be ended manually.
    UIGraphicsEndImageContext();

    return maskedImage;
}

- (UIImage*) maskImage:(UIImage*) sourceImage toAreaInsidePath:(UIBezierPath*) maskPath;
{
    return [self compositeImage:sourceImage onPath:maskPath usingBlendMode:kCGBlendModeSourceIn];
}

- (UIImage*) maskImage:(UIImage*) sourceImage toAreaOutsidePath:(UIBezierPath*) maskPath;
{
    return [self compositeImage:sourceImage onPath:maskPath usingBlendMode:kCGBlendModeSourceOut];
}

Upvotes: 7

Related Questions