kCGTextStroke's Fill and Stroke aren't positioned correctly - objective-c

So I'm using the code below to apply a stroke (and fill) to text in a UILabel, and it's coming out like the image below. The stroke is heavier on one side than the other (look at the top of the letters compared to the bottom, and the right compared to the left. The period at the end makes it very noticeable, too, looks like a googly eye). I've not got any shadowing turned on at all, so I don't think it's the shadow interfering with the stroke.
What could be causing this?
- (void) drawTextInRect: (CGRect) rect
{
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSetTextDrawingMode(c, kCGTextFillStroke);
CGContextSaveGState(c);
CGContextSetRGBFillColor(c, 1.0, 0.0, 0.0, 1.0);
CGContextSetRGBStrokeColor(c, 0.0, 1.0, 0.0, 1.0);
[super drawTextInRect: rect];
CGContextRestoreGState(c);
}
EDIT: So, for kicks, I took at look at the label with only the fill, and only the stroke. Turning off the stroke creates a perfectly normal-looking piece of text, as if I'd just coloured it in Interface Builder. Turning off the fill, however, shows only the stroke, which doesn't look heavier on any side than any other. **This leads me to believe that the issue is where the fill is positioned in relation to the stroke, and that neither the fill or stroke themselves are at fault. Any other thoughts on this? How can I get the fill directly centred in the stroke?

You should probably just use kCGTextFillStroke as the drawing mode and only draw once (with separate stroke and fill colors set).
CGContextSetRGBFillColor(context, 1.0, 0.0, 0.0, 1.0); // any color you want (this is red)
CGContextSetRGBStrokeColor(context, 0.0, 1.0, 0.0, 1.0); // any color you want (this is green)
CGContextSetTextDrawingMode(context, kCGTextFillStroke);
[self.text drawInRect:rect withFont:self.font];
Alternatively you could just stroke afterwards. Strokes are usually drawn from the center which means that half of the width is inwards and half is outwards. That would mean that if you fill after you stroke some of the stroke is going to get covered up by the fill.

A possibility is that the overridden method translates the CTM using CGContextTranslateCTM or similar functions. The CTM is part of the state of a context and specifies a transform for all following draw calls.
You should try to save the context before the call to the overridden method and restore it afterwards:
CGContextSaveGState(c);
[super drawTextInRect: rect];
CGContextRestoreGState(c);

Related

NSBezierPath drawing

I want to do a rounded rectangle outline on an NSImage and I figured that using NSBezierPath would be the best way. However, I ran into a problem: instead of drawing a nice curve, I get this:
For reasons I can't understand, NSBezierPath is drawing the rounded part with a darker color than the rest.
Here's the code I'm using (inside a drawRect: call on a custom view):
NSBezierPath* bp = [NSBezierPath bezierPathWithRoundedRect: self.bounds xRadius: 5 yRadius: 5];
[[[NSColor blackColor] colorWithAlphaComponent: 0.5] setStroke];
[bp stroke];
Any ideas?
Edit:
If I inset the path by 0.5 everything draws just fine. But why is it that I get this when I offset the path by 10 pixels (for example)?
If I understand correctly, it should draw a thin line as well...
Many rendering systems are derived from the PostScript drawing model. Core Graphics is one of these derivative systems. (Here are some others: PDF, SVG, the HTML Canvas 2D Context, Cairo.)
All of these systems have the idea of stroking a path with a line of some fixed width. When you stroke the path, the line straddles the path: half of the line's width is on one side of the path, and half of the line's width is on the other side. Here's a diagram that may make this clearer:
Now, what happens when you stroke a path that lies along the boundary of your view? Half of the stroke falls outside of your view's bounds and is clipped away - not drawn. You only see the half of the stroke that falls inside the view's bounds.
When you use a rounded corner, that corner pulls away from the view's boundary, toward its center, so more of the stroke around the corner falls inside the view's boundary. So the stroke appears to get thicker around the rounded corner, like this:
To fix this, you need to inset your path by half the line width, so that the entire stroke falls inside your view's bounds along the entire path. The default line width is 1.0, so:
NSBezierPath* bp = [NSBezierPath bezierPathWithRoundedRect:
NSRectInset(self.bounds, 0.5, 0.5) xRadius:5 yRadius:5];
In iOS field, just minus the radius of the circle to prevent from being clipped.
UIBezierPath *roundPath = [UIBezierPath bezierPath];
[roundPath addArcWithCenter:
CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2)
radius:(self.frame.size.width / 2 - 0.5)
startAngle:M_PI_2 endAngle:M_PI * 3 / 2.f clockwise:YES];

UIImage redrawing efficiency

I have an UIimage with some text drawn on the image. I know the exact coordinates of the text rectangle on the image. When the image pan event is fired, I check if the pan is starting with a click on the rectangle in the image where there is text.
If (pan started from a click on the rectangle) then
I get the Pan translate coordinates and move the rectangle. So what I actually do is get the new coordinates and redraw the image with the rectangle in the new position. this gives the panning effect of the rectangle (for other reasons I cannot use a label but have to redraw).
This works perfectly, except that if the panning is done "too fast" there is an elasticity effect and the rectangle kind of is lagging behind. In theory I neednot redraw the whole image but rather only the intersection of the old and new rectangles. This I think will certainly make it more smoother.
Question: How to make it fast.
//Code where I redraw
UIGraphicsBeginImageContext(img.size);
[img drawInRect: CGRectMake(0,0, img.size.width, img.size.height)];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(context, 0.0, 0.0, 0.0, 1.0);
CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 0.2);
CGContextFillRect(context, CGRectMake(myRect.origin.x, myRect.origin.y, myRect.size.width, myRect.size.height));
UIImage *theImage=UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CurrentImageView.Image = theImage;
I personally think that the most time consuming step here is
[img drawInRect: CGRectMake(0,0, img.size.width, img.size.height)];
It is illogical to be redrawing the whole image. I want to take a old image and just redraw a part of the image and assign it to the view controller. Any suggestions?
Thanks a lot in advance,
prasad.

CGContextClipToMask UIImage Mask Invert Up and Down

I am trying to add a clip mask to my image.
And then making painting with finger touches on the screen.
Finger touches and clip mask works but the problem is my UIImage looks invert up and down.
How can I correct that?
This is the line of code for my mask:
CGContextClipToMask(context, rect, myIconImage.CGImage);
Thanks
I found the answer and I wanna share it.
It is quite simply actually. Since the CGImage is inverting a UIImage I added these lines before the ClipToMask line:
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
with this code, it works!

custom view draws a rectangle

I've got a custom view with the following drawRect method
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
float w = rect.size.width;
CGContextSetFillColorWithColor(ctx, [UIColor blackColor].CGColor);
CGContextAddArc(ctx, w / 2, w / 2, w / 2 - 10, 0, 2 * M_PI, 0);
CGContextDrawPath(ctx, kCGPathFill);
}
I'm expecting to see a black circle, however, for some reason it draws a black rectangle instead. I guess the whole view is just getting filled with black. Where is the problem?
Perhaps your view's backgroundColor is also black (which is the default for opaque views)?
Also, you shouldn't assume that the rect parameter covers the whole view, it could be only a part of the view that has been marked as needing to be redrawn. You should base your geometry calculations on the view's bounds instead.
You need to add....
[super drawRect:rect];
as the first line, this will then render the background as per the views background property, OR if you prefer you can fill the context with the background colour yourself, I prefer the former.
Maybe if you clip it that it will be shaped into the right size?
CGContextClip(context);
(Why aren't you using CGMutablePathRef btw?)

NSBackgroundColorAttributeName-like attribute in NSAttributedString on iOS?

I was planning on using NSAttributedString to highlight portions of strings with the matching query of a user's search. However, I can't find an iOS equivalent of NSBackgroundColorAttributeName—there's no kCTBackgroundColorAttributeName. Does such a thing exist, similar to the way NSForegroundColorAttributeName becomes kCTForegroundColorAttributeName?
No, such an attribute doesn't exist in Core Text, you'll have to draw your own rectangles underneath the text to simulate it.
Basically, you'll have to figure out which rectangle(s) to fill for a given range in the string. If you do your layout with a CTFramesetter that produces a CTFrame, you need to get its lines and their origins using CTFrameGetLines and CTFrameGetLineOrigins.
Then iterate over the lines and use CTLineGetStringRange to find out which lines are part of the range you want to highlight. To get the rectangles to fill, use CTLineGetTypographicBounds (for the height) and CTLineGetOffsetForStringIndex (for the horizontal offset and width).
NSBackgroundColorAttributeName is available in iOS 6, and you can use it by following way:
[_attributedText addAttribute: NSBackgroundColorAttributeName value:[UIColor yellowColor] range:textRange];
[_attributedText drawInRect:rect];
drawInRect: will support NSBackgroundColorAttributeName and all NS*AttributeNames supported by iOS 6.
For CTFrameDraw() there is no support for background text color.
Code:
- (void)drawRect:(CGRect)rect {
// First draw selection / marked text, then draw text
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
[_attributedText drawInRect:rect];
CGContextRestoreGState(context);
// CTFrameDraw(_frame, UIGraphicsGetCurrentContext());
}