To sort out my last problem : Draw/Paint like sketch color inside a bezier path in UIView, I took an exact Image (of path) filled with selected color. Then, to draw that in UIView, for context color I took:
lineColor=[UIColor colorWithPatternImage:img];
in drawrect :
CGPoint mid1 = midPoint(previousPoint1, previousPoint2);
CGPoint mid2 = midPoint(currentPoint, previousPoint1);
[curImage drawInRect:CGRectZero];
CGContextRef context = UIGraphicsGetCurrentContext();
[self.layer renderInContext:context];
CGContextMoveToPoint(context, mid1.x, mid1.y);
CGContextAddQuadCurveToPoint(context,
previousPoint1.x,
previousPoint1.y,
mid2.x,
mid2.y);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetBlendMode(context, kCGBlendModeCopy);
CGContextSetLineJoin(context, kCGLineJoinRound);
CGContextSetLineWidth(context, self.lineWidth);
CGContextSetStrokeColorWithColor(context,lineColor.CGColor);
CGContextSetShouldAntialias(context, YES);
CGContextSetAllowsAntialiasing(context, YES);
CGContextSetFlatness(context,0.5);
CGContextSetAlpha(context, 1.0);
CGContextStrokePath(context);
It draws exactly in the path of view but on boundaries the color goes out like melting effect.
Source Image in which color has to be filled :
image to be used as colorWithPatternImage (img)
After drawing on touches ,resultimg Image :
What am I missing here to fill it perfectly?
Any help with core graphics is most welcomed!
Thank You!
To mask the image I also did :
-(void)clipView :(MyUIBezierPath*)path drawingView:(DrawingView*)myView
{
MyUIBezierPath *myClippingPath =path;
CAShapeLayer *mask = [CAShapeLayer layer];
mask.path = myClippingPath.CGPath;
myView.layer.mask = mask;
}
But colorWithPatternImage and this masking does the same trick i.e lets me draw only inside the path , the problem I am facing now is : It gives a bad effect on drawing on touch points around the boundaries of path !
Well its working with my own masking method via colorWithPatternImage , though masking it by clipping is the same thing.Drawing issue was because of the image being drawn at touch point in the method "calculateMinImageArea: CGPoint1 :CGPoint2 :CGPoint3". Changing its dimensions (height-width according to line width) sorts it out.
You can create a path points out of this image. Then make a CGPathRef or CGMutablePathRef and then use it as a mask to your CGContextRef.
Related
Question
I have a CGImageRef image. It is actually a image which is going to be used as a mask. So, the "alpha layer" of it is what I really care about. I want to "thicken" the opaque part of the image, as if I would add a stroke around the opaque part. How to do that?
More context
I have an image (a CGImageRef) like that:
It has been created by
mask = CGBitmapContextCreateImage(context);
I'm using it as a mask. The UIView under the mask is the same text. The problem is that the text underneath is not completed covered by the mask. See:
More code
This code is in a UILabel subclass.
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
// Draw text normally
[super drawTextInRect:rect];
if (self.alternativeTextColor) {
CGImageRef mask = NULL;
// Create a mask from the text
mask = CGBitmapContextCreateImage(context);
CGContextSaveGState(context);
CGContextTranslateCTM(context, 0, self.frame.size.height);
CGContextScaleCTM(context, 1.0, (CGFloat) -1.0);
// Clip the current context to our mask
CGContextClipToMask(context, rect, mask);
// Set fill color
CGContextSetFillColorWithColor(context, [self.alternativeTextColor CGColor]);
// Path from mask
CGPathRef path;
if (CGRectIsEmpty(self.maskFrame)) {
path = CGPathCreateMutable();
} else {
UIBezierPath *roundRectBezierPath = [UIBezierPath bezierPathWithRoundedRect:self.maskFrame
cornerRadius:self.maskCornerRadius];
path = CGPathCreateCopy([roundRectBezierPath CGPath]);
}
CGContextAddPath(context, path);
// Fill the path
CGContextFillPath(context);
CFRelease(path);
// Clean up
CGContextRestoreGState(context);
CGImageRelease(mask);
}
}
Link to the project
https://github.com/colasjojo/Test-NYSegmentedControl
If you are trying to clip to text, I'd suggest not doing it via an intermediate bitmap and instead clipping to the paths of the actual text as in the snippet below from SVGgh's GHText class:
CGContextSaveGState(quartzContext);
CGContextSetTextDrawingMode(quartzContext, kCGTextStrokeClip);
CGContextSetLineWidth(quartzContext, strokeWidthToUse);
... Draw Text here ....
CGContextReplacePathWithStrokedPath(quartzContext);
CGContextClip(quartzContext);
... Do clipped drawing here ...
CGContextSetTextDrawingMode(quartzContext, kCGTextFill);
CGContextRestoreGState(quartzContext);
This isn't answering your question, but it's probably what you should do instead. Play around with CGContextSetTextDrawingMode to get the effect you are going for.
I might not be explaining this in the best way possible, so please bear with me.
What I have is a drawn CGPath on top of a MKMapView object:
The way I was able to achieve this is to create a CGPath for the darker blue line, then create a copy of that path, and then stroke a thicker version of it with a semi-transparent blue color. Here's the code that I'm currently using for this:
// set the shadow around the path line
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextSetRGBStrokeColor(context, 0.0f, 0.0f, 0.0f, 0.0f);
CGContextSetRGBFillColor(context, 0.0f, 0.0f, 1.0f, 0.4f);
CGPathRef shadowPath = CGPathCreateCopyByStrokingPath(self.path.CGPath, NULL, 80.0f, kCGLineCapRound, kCGLineJoinRound, 0.0f);
CGContextBeginPath(context);
CGContextAddPath(context, shadowPath);
CGContextDrawPath(context, kCGPathFillStroke);
CGContextRestoreGState(context);
CGPathRelease(shadowPath);
Works pretty well, nothing wrong so far.
However, what I would like to do though is to get a CGPathRef of the outline of that thicker semi-transparent blue area. Here's another screenshot showing the pseudo-path that I want out of this (hand drawn in red):
How is this possible?
Simple: just use CGPathCreateCopyByStrokingPath. Pass in a wide line width, and a cap of kCGLineCapRound.
You can draw two stokes.
one stroke with width n (which n is the outline width) and black color.
another stroke is then drawn on top of the first one, with eraser mode:
CGContextSetBlendMode(context, kCGBlendModeCopy);
CGContextSetFillColorWithColor(context, [[UIColor clearColor] CGColor]);
I'm having some issues while trying to draw lines smoothly on touches moved when my UIView is zoomed in. The thing is that lines start to look very pixelated upon certain zoom level.
My view hierarchy is really simple as of now, since this is more like a proof of concept. I have a UIScrollView, my UIView as a child and also it is set as the zoom view.
My drawRect: implementation looks like this:
CGContextRef context = UIGraphicsGetCurrentContext();
[self.layer renderInContext:context];
CGPoint mid1 = midPoint(_previousPoint1, _previousPoint2);
CGPoint mid2 = midPoint(_currentPoint, _previousPoint1);
CGContextSetBlendMode(context, kCGBlendModeNormal);
CGContextMoveToPoint(context, mid1.x, mid1.y);
CGContextAddQuadCurveToPoint(context, _previousPoint1.x, _previousPoint1.y, mid2.x, mid2.y);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineJoin(context, kCGLineJoinRound);
CGContextSetLineWidth(context, self.lineWidth);
CGContextSetStrokeColorWithColor(context, self.lineColor.CGColor);
CGContextStrokePath(context);
Dumping the contents of the layer into the context is an important part since I'm only refreshing the bounding box enclosing the path formed by the last three touched points (_previousPoint1, _previousPoint2 and _currentPoint).
That operation is actually done on touchesMoved method and the function that handles it is the following:
- (void)calculateMinImageArea:(CGPoint)pp1 :(CGPoint)pp2 :(CGPoint)cp
{
// calculate mid point
CGPoint mid1 = midPoint(pp1, pp2);
CGPoint mid2 = midPoint(cp, pp1);
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, mid1.x, mid1.y);
CGPathAddQuadCurveToPoint(path, NULL, pp1.x, pp1.y, mid2.x, mid2.y);
CGRect bounds = CGPathGetBoundingBox(path);
CGPathRelease(path);
CGRect drawBox = [self extendedRect:bounds];
UIGraphicsBeginImageContext(drawBox.size);
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
UIGraphicsEndImageContext();
[self setNeedsDisplayInRect:drawBox];
}
I was able to find a workaround using CAShapeLayers to draw the paths. However, the low performance wasn't acceptable.
I would really appreciate if someone can point me to the right approach.
Thanks.
[self setContentScaleFactor:scale];
use this in scrollViewDidEndZooming: delegate method
Hope this help's you
I want to fill the remaining part of a CGCntext after drawing a rect? How can I do that? Thank you!
But the thing is, I set the blend mode of the cgcontext kCGBlendModeClear. I want to make the little rect in the context to transparent. If a draw the background first, can I still see the image in the rect?
If you want to fill the context (whose frame is bigRect), except for a rectangle inside it (whose frame is smallRect):
CGContextBeginPath(context);
CGContextAddRect(context, bigRect);
CGContextAddRect(context, smallRect);
CGContextSetFillColorWithColor(context, color);
CGContextEOFillPath(context, bigRect);
Do the background first...
CGRect bounds = [self bounds];
[[UIColor blackColor] set];
UIBezierPath* backgroundPath = [UIBezierPath bezierPathWithRect:bounds];
[backgroundPath fill];
CGRect innerRect = CGRectMake(self.bounds-10,
self.bounds-10,
self.bounds.width-20
self.bounds.height-20);
[[UIColor redColor] set];
UIBezierPath* foregroundPath = [UIBezierPath bezierPathWithRect:innerRect];
[foregroundPath fill];
Draw your background and then use CGContextClearRect to clear the area you want to be transparent.
arbitrary shape:
// do your foreground drawing
CGPathRef arbitraryShape; // asign your arbitrary shape
CGContextBeginPath(ctx);
CGContextAddRect(ctx, bounds); // rect in full size of the context
CGContextAddPath(ctx, arbitraryShape); // set the area you dont want to be drawn on
CGContextClip(ctx);
// do your background drawing
i'm using drawRect inside a custom UIButton to draw a bordered button with an image inside. The code is the following:
- (void) drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
//Draw a rectangle
CGContextSetFillColorWithColor(context, [UIColor clearColor].CGColor);
CGContextSetStrokeColorWithColor(context, [[UIColor grayColor] CGColor]);
//Define a rectangle
CGRect drawrect = CGRectMake(CGRectGetMinX(rect),CGRectGetMinY(rect),rect.size.width,rect.size.height);
CGContextStrokeRect(context,drawrect);}
The problem is that on corners i got an extra pixel (see the attached image). What am i doing wrong?
thanks
You are drawing your rectangle along the edges of pixels, instead of drawing it along the centers of pixels. So your rectangle only covers half of most pixels. On the corners it covers three-quarters of the pixels.
To draw along the pixel centers, you have to use half-integer coordinates. Try this:
CGContextStrokeRect(context, CGRectInset(rect, 0.5, 0.5));