How to get the path Quartz is using to stroke a NSBezierPath - cocoa

I'm using this code to stroke a NSBezierPath with a wide, dashed, black line.
(c and strForBezier are defined somewhere else)
NSGlyph glyph;
for(n = 0; n < len; n++) {
glyph = [font glyphWithName: [strForBezier substringWithRange:NSMakeRange(n, 1)]];
[path appendBezierPathWithGlyph: glyph inFont: font];
}
CGFloat pattern2[4]; pattern2[0]=5*c; pattern2[1]=2*c; pattern2[2]=2*c; pattern2[3]=2*c;
[path setLineDash:pattern2 count:4 phase:0];
[path setLineWidth:c];
[path stroke];
[[NSColor blueColor] set ];
[path fill];
How can I get the black NSBezierPath ? I'm making the assumption that a NSBezierPath is built and filled to stroke the initial curve.

You can create a path by dashing and stroking an existing path, but that requires to use CGPathRef instead of NSBezierPath.
CGMutablePathRef path0 = CGPathCreateMutable();
CGAffineTransform transform = CGAffineTransformMakeTranslation(20, 20);
// initial position is {20, 20}
CGGlyph glyph;
for(n = 0; n < len; n++)
{
glyph = [font glyphWithName: [strForBezier substringWithRange:NSMakeRange(n, 1)]];
CGPathRef glyphPath = CTFontCreatePathForGlyph((__bridge CTFontRef) font, glyph, NULL);
CGPathAddPath(path0, &transform, glyphPath);
CGPathRelease(glyphPath);
// append the glyph advance to the transform
CGSize advance;
CTFontGetAdvancesForGlyphs((__bridge CTFontRef) font, kCTFontDefaultOrientation, &glyph, &advance, 1);
transform.tx += advance.width;
transform.ty += advance.height;
}
CGFloat pattern2[4]; pattern2[0]=5*c; pattern2[1]=2*c; pattern2[2]=2*c; pattern2[3]=2*c;
CGPathRef path1 = CGPathCreateCopyByDashingPath(path0, NULL, 0, pattern2, 4);
CGPathRef path2 = CGPathCreateCopyByStrokingPath(path1, NULL, c, kCGLineCapButt, kCGLineJoinMiter, CGFLOAT_MAX);
CGContextRef context = [NSGraphicsContext currentContext].graphicsPort;
CGContextAddPath(context, path2);
CGContextDrawPath(context, kCGPathFill);
CGPathRelease(path0);
CGPathRelease(path1);
CGPathRelease(path2);
If you're not comfortable with CGPathRef, you may create a NSBezierPath then convert it to a CGPathRef using the method described in Apple documentation Building a CGPathRef From a NSBezierPath Object (the reverse is also possible).

Related

calculate height and width based on text , font , text color in xcode

what is the best way to calculate (formated text including text size , text color , text font) proper height and width of text field in a fixed UIView , including word wrap etc
in the following image text is not right align Check the Image
in the following image text is not properly showing check the image
i am calculating image height and width with the following code
NSString* text=[textField stringValue];
NSDictionary *attributes;
NSTextView* textView =[[NSTextView alloc] init];
[textView setString:text];
attributes = #{NSFontAttributeName : [NSFont fontWithName:fontName size:fontValue], NSForegroundColorAttributeName : [NSColor colorWithCalibratedRed:redValueTextColor green:GreenValueTextColor blue:blueValueTextColor alpha:1], NSBackgroundColorAttributeName : [NSColor colorWithCalibratedRed:redValueTextBackgroundColor green:GreenValueTextBackgroundColor blue:blueValueTextBackgroundColor alpha:1]};
textView.backgroundColor=[NSColor colorWithCalibratedRed:redValueTextBackgroundColor green:GreenValueTextBackgroundColor blue:blueValueTextBackgroundColor alpha:1];
NSInteger maxWidth = 600;
NSInteger maxHeight = 20000;
CGSize constraint = CGSizeMake(maxWidth, maxHeight);
NSRect newBounds = [text boundingRectWithSize:constraint options:NSLineBreakByCharWrapping|NSStringDrawingUsesFontLeading attributes:attributes];
textView.frame = NSMakeRect(textView.frame.origin.x, textView.frame.origin.y, newBounds.size.width, newBounds.size.height);
textView =[NSColor colorWithCalibratedRed:redValueTextColor green:GreenValueTextColor blue:blueValueTextColor alpha:1];
[textView setFont:[NSFont fontWithName:fontName size:fontValue]];
Core Text solution (note, maxWidth allows wrapping if you want it):
(CGSize)sizeForText:(NSAttributedString *)string maxWidth:(CGFloat)width
{
CTTypesetterRef typesetter = CTTypesetterCreateWithAttributedString((__bridge CFAttributedStringRef)string);
CFIndex offset = 0, length;
CGFloat y = 0, lineHeight;
do {
length = CTTypesetterSuggestLineBreak(typesetter, offset, width);
CTLineRef line = CTTypesetterCreateLine(typesetter, CFRangeMake(offset, length));
CGFloat ascent, descent, leading;
CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
CFRelease(line);
offset += length;
ascent = roundf(ascent);
descent = roundf(descent);
leading = roundf(leading);
lineHeight = ascent + descent + leading;
lineHeight = lineHeight + ((leading > 0) ? 0 : roundf(0.2*lineHeight)); //add 20% space
y += lineHeight;
} while (offset < [string length]);
CFRelease(typesetter);
return CGSizeMake(width, y);
}

Need sample code to swing needle in Cocoa/Quartz 2d Speedometer for Mac App

I'm building this to run on the Mac, not iOS - which is quit different. I'm almost there with the speedo, but the math of making the needle move up and down the scale as data is input eludes me.
I'm measuring wind speed live, and want to display it as a gauge - speedometer, with the needle moving as the windspeed changes. I have the fundamentals ok. I can also - and will - load the images into holders, but later. For now I want to get it working ...
- (void)drawRect:(NSRect)rect
{
NSRect myRect = NSMakeRect ( 21, 21, 323, 325 ); // set the Graphics class square size to match the guage image
[[NSColor blueColor] set]; // colour it in in blue - just because you can...
NSRectFill ( myRect );
[[NSGraphicsContext currentContext] // set up the graphics context
setImageInterpolation: NSImageInterpolationHigh]; // highres image
//-------------------------------------------
NSSize viewSize = [self bounds].size;
NSSize imageSize = { 320, 322 }; // the actual image rectangle size. You can scale the image here if you like. x and y remember
NSPoint viewCenter;
viewCenter.x = viewSize.width * 0.50; // set the view center, both x & y
viewCenter.y = viewSize.height * 0.50;
NSPoint imageOrigin = viewCenter;
imageOrigin.x -= imageSize.width * 0.50; // set the origin of the first point
imageOrigin.y -= imageSize.height * 0.50;
NSRect destRect;
destRect.origin = imageOrigin; // set the image origin
destRect.size = imageSize; // and size
NSString * file = #"/Users/robert/Documents/XCode Projects/xWeather Graphics/Gauge_mph_320x322.png"; // stuff in the image
NSImage * image = [[NSImage alloc] initWithContentsOfFile:file];
//-------------------------------------------
NSSize view2Size = [self bounds].size;
NSSize image2Size = { 149, 17 }; // the orange needle
NSPoint view2Center;
view2Center.x = view2Size.width * 0.50; // set the view center, both x & y
view2Center.y = view2Size.height * 0.50;
NSPoint image2Origin = view2Center;
//image2Origin.x -= image2Size.width * 0.50; // set the origin of the first point
image2Origin.x = 47;
image2Origin.y -= image2Size.height * 0.50;
NSRect dest2Rect;
dest2Rect.origin = image2Origin; // set the image origin
dest2Rect.size = image2Size; // and size now is needle size
NSString * file2 = #"/Users/robert/Documents/XCode Projects/xWeather Graphics/orange-needle01.png";
NSImage * image2 = [[NSImage alloc] initWithContentsOfFile:file2];
// do image 1
[image setFlipped:YES]; // flip it because everything else is in this exerecise
// do image 2
[image2 setFlipped:YES]; // flip it because everything else is in this exerecise
[image drawInRect: destRect
fromRect: NSZeroRect
operation: NSCompositeSourceOver
fraction: 1.0];
[image2 drawInRect: dest2Rect
fromRect: NSZeroRect
operation: NSCompositeSourceOver
fraction: 1.0];
NSBezierPath * path = [NSBezierPath bezierPathWithRect:destRect]; // draw a red border around the whole thing
[path setLineWidth:3];
[[NSColor redColor] set];
[path stroke];
}
// flip the ocords
- (BOOL) isFlipped { return YES; }
#end
The result is here. The gauge part that is. Now all I have to do is make the needle move in response to input.
Apple has some sample code, called SpeedometerView, which does exactly what you're asking. It'll surely take some doing to adapt it for your use, but it's probably a decent starting point.

Concave NSBezierPath

I have the following drawing code:
[[NSColor redColor] set];
NSRect fillRect = NSMakeRect(bounds.size.width - 20.0f, 0.0f, 20.0f, 20.0f);
NSBezierPath *bezier1 = [NSBezierPath bezierPathWithRoundedRect:fillRect xRadius:10.0f yRadius:10.0f];
[bezier1 fill];
NSRect fill2 = fillRect;
fill2.origin.x += 5;
fill2.origin.y += 5;
fill2.size.width -= 10.0f;
fill2.size.height -= 10.0f;
NSBezierPath *bezier2 = [NSBezierPath bezierPathWithRoundedRect:fill2 xRadius:5.0f yRadius:5.0f];
[[NSColor greenColor] set];
[bezier2 fill];
Which result in this:
How do I reach that the inner green circle is transparent? Replacing the green NSColor with a transparent color doesn't work, logical ;-)
Is there a way to intersect instances of NSBezierPath or solve this with another approach?
I think what you're looking for is a bezier path for a ring, you can do that by creating a single NSBezierPath and setting the winding rule:
[[NSColor redColor] set];
NSRect fillRect = NSMakeRect(bounds.size.width - 20.0f, 0.0f, 20.0f, 20.0f);
NSBezierPath *bezier1 = [NSBezierPath new];
[bezier1 setWindingRule:NSEvenOddWindingRule]; // set the winding rule for filling
[bezier1 appendBezierPathWithRoundedRect:fillRect xRadius:10.0f yRadius:10.0f];
NSRect innerRect = NSInsetRect(fillRect, 5, 5); // the bounding rect for the hole
[bezier1 appendBezierPathWithRoundedRect:innerRect xRadius:5.0f yRadius:5.0f];
[bezier1 fill];
The NSEvenOddWindingRule rule determines whether to fill a particular point by considering a line from that point to outside the overall path bounds; if that line crosses an even number of paths it is not filled, otherwise it is. So any point in the inner circle will not be filled, while points between the two will be - result a ring.

Core Graphics: Drawing along a path with a normal gradient

There are a number of resources here and elsewhere on the web regarding how to draw with a gradient - fill or stroke.
However, AFAICT, none addresses the following requirement: how to draw a path with a normal gradient, whereby normal means orthogonal to the path. The net effect could be something like toothpaste or a tube when applied with a dark->light->dark linear gradient. Here is this idea in the case of a round rectangle:
round-rect tube http://muys.net/cadre_blanc.png
(this was hand drawn and the corners are not very good).
In the specific case of the round rect, I think I can achieve this effect with 4 linear gradients (the sides) and 4 radial gradients (the corners). But is there better?
Is there an easy solution for any path?
The only "easy" solution I can think of would be to stroke the path multiple times, reducing the stroke width and changing the color slightly each time, to simulate a gradient.
Obviously, this could be an expensive operation for complex paths so you would want to cache the result if possible.
#define RKRandom(x) (arc4random() % ((NSUInteger)(x) + 1))
#implementation StrokeView
- (void)drawRect:(NSRect)rect
{
NSRect bounds = self.bounds;
//first draw using Core Graphics calls
CGContextRef c = [[NSGraphicsContext currentContext] graphicsPort];
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, NSMidX(bounds), NSMidY(bounds));
CGContextSetMiterLimit(c,90.0);
CGContextSetLineJoin(c, kCGLineJoinRound);
CGContextSetLineCap(c, kCGLineCapRound);
for(NSUInteger f = 0; f < 20; f++)
{
CGPathAddCurveToPoint(
path,
NULL,
(CGFloat)RKRandom((NSInteger)NSWidth(bounds)) + NSMinX(bounds),
(CGFloat)RKRandom((NSInteger)NSHeight(bounds)) + NSMinY(bounds),
(CGFloat)RKRandom((NSInteger)NSWidth(bounds)) + NSMinX(bounds),
(CGFloat)RKRandom((NSInteger)NSHeight(bounds)) + NSMinY(bounds),
(CGFloat)RKRandom((NSInteger)NSWidth(bounds)) + NSMinX(bounds),
(CGFloat)RKRandom((NSInteger)NSHeight(bounds)) + NSMinY(bounds)
);
}
for(NSInteger i = 0; i < 8; i+=2)
{
CGContextSetLineWidth(c, 8.0 - (CGFloat)i);
CGFloat tint = (CGFloat)i * 0.15;
CGContextSetRGBStrokeColor (
c,
1.0,
tint,
tint,
1.0
);
CGContextAddPath(c, path);
CGContextStrokePath(c);
}
CGPathRelease(path);
//now draw using Cocoa drawing
NSBezierPath* cocoaPath = [NSBezierPath bezierPathWithRoundedRect:NSInsetRect(self.bounds, 20.0, 20.0) xRadius:10.0 yRadius:10.0];
for(NSInteger i = 0; i < 8; i+=2)
{
[cocoaPath setLineWidth:8.0 - (CGFloat)i];
CGFloat tint = (CGFloat)i * 0.15;
NSColor* color = [NSColor colorWithCalibratedRed:tint green:tint blue:1.0 alpha:1.0];
[color set];
[cocoaPath stroke];
}
}
#end

Getting into pixel data of NSImage

I'm writing application that operates on black&white images. I'm doing it by passing a NSImage object into my method and then making NSBitmapImageRep from NSImage. All works but quite slow. Here's my code:
- (NSImage *)skeletonization: (NSImage *)image
{
int x = 0, y = 0;
NSUInteger pixelVariable = 0;
NSBitmapImageRep *bitmapImageRep = [[NSBitmapImageRep alloc] initWithData:[image TIFFRepresentation]];
[myHelpText setIntValue:[bitmapImageRep pixelsWide]];
[myHelpText2 setIntValue:[bitmapImageRep pixelsHigh]];
NSColor *black = [NSColor blackColor];
NSColor *white = [NSColor whiteColor];
[myColor set];
[myColor2 set];
for (x=0; x<=[bitmapImageRep pixelsWide]; x++) {
for (y=0; y<=[bitmapImageRep pixelsHigh]; y++) {
// This is only to see if it's working
[bitmapImageRep setColor:myColor atX:x y:y];
}
}
[myColor release];
[myColor2 release];
NSImage *producedImage = [[NSImage alloc] init];
[producedImage addRepresentation:bitmapImageRep];
[bitmapImageRep release];
return [producedImage autorelease];
}
So I tried to use CIImage but I don't know how to get into each pixel by (x,y) coordinates. That is really important.
Use the representations array property from NSImage, to get your NSBitmapImageRep. It should be faster than serializing your image to a TIFF and then back.
Use the bitmapData property of the NSBitmapImageRep to access the image bytes directly.
eg
unsigned char black = 0;
unsigned char white = 255;
NSBitmapImageRep* bitmapImageRep = [[image representations] firstObject];
// you will need to do checks here to determine the pixelformat of your bitmap data
unsigned char* imageData = [bitmapImageRep bitmapData];
int rowBytes = [bitmapImageRep bytesPerRow];
int bpp = [bitmapImageRep bitsPerPixel] / 8;
for (x=0; x<[bitmapImageRep pixelsWide]; x++) { // don't use <=
for (y=0; y<[bitmapImageRep pixelsHigh]; y++) {
*(imageData + y * rowBytes + x * bpp ) = black; // Red
*(imageData + y * rowBytes + x * bpp +1) = black; // Green
*(imageData + y * rowBytes + x * bpp +2) = black; // Blue
*(imageData + y * rowBytes + x * bpp +3) = 255; // Alpha
}
}
You will need to know what pixel format you are using in your images before you can go playing with its data, look at the bitsPerPixel property of NSBitmapImageRep to help determine if your image is in RGBA format.
You could be working with a gray scale image, or an RGB image, or possibly CMYK. And either convert the image to what you want first. Or handle the data in the loop differently.

Resources