I am trying to draw an inner shadow in an NSView. The shadow itself is not the problem, but the color setting is driving me nuts :/
#define ShadowBlurRadius 10.0
#define SRGB (CGFloat [4]){184.0, 184.0, 184.0, 1.0}
#implementation SWShadowedView
- (void)drawRect:(NSRect)dirtyRect {
NSGraphicsContext *context = [NSGraphicsContext currentContext];
[context saveGraphicsState];
[context setCompositingOperation:NSCompositePlusDarker];
NSBezierPath *path = [NSBezierPath bezierPathWithRect:NSMakeRect(0, dirtyRect.size.height -ShadowBlurRadius, self.superview.frame.size.width, ShadowBlurRadius)];
[[NSColor whiteColor] setStroke];
NSShadow * shadow = [[NSShadow alloc] init];
NSColorSpace *colorSpace = [NSColorSpace sRGBColorSpace];
NSColor *color = [NSColor colorWithColorSpace:colorSpace components:SRGB count:4];
[shadow setShadowColor:color];
[shadow setShadowBlurRadius:ShadowBlurRadius];
[shadow set];
[path stroke];
[context restoreGraphicsState];
[super drawRect:dirtyRect];
}
#end
If I replace the shadow color with [NSColor redColor] it works but with the wrong color. This is where I got the sRGB from: link
The way to convert sRGB to NSColor is taken from another post from here but obviously it's not working.
best regards
Your code is almost completely correct, the only problem is that you're using numerical values from 0-255 in your array. All the NSColor creation methods use CGFloat values from 0-1.0.
All you need to do is define your SRGB array like so:
#define SRGB (CGFloat [4]){184.0/255.0, 184.0/255.0, 184.0/255.0, 1.0}
Your code will then work correctly. Please note that using the colorWithCalibratedRed:green:blue:alpha: method of NSColor will not give you the correct color from your sRGB values.
To get correct sRGB values, you must use the method in your original code, which specifically uses the sRGB color space to create the color. A category on NSColor that creates colors using 255-based sRGB values might look something like this:
#implementation NSColor (sRGB_Additions)
+ (NSColor *)colorWith255sRGBRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha
{
CGFloat sRGBComponents[4] = {red / 255.0, green / 255.0, blue / 255.0, alpha};
NSColorSpace *colorSpace = [NSColorSpace sRGBColorSpace];
return [NSColor colorWithColorSpace:colorSpace components:sRGBComponents count:4];
}
#end
Then you could just do this:
NSColor* someColor = [NSColor colorWith255sRGBRed:184.0 green:184.0 blue:184.0 alpha:1.0];
Here is the simplest, most modern way of creating the color that you need:
NSColor *color = [NSColor colorWithSRGBRed:(184.0 / 255.5) green:(184.0 / 255.5) blue:(184.0 / 255.5) alpha:1.0];
Use RGB not sRGB:
You can create color with RGB like this:
float red = 182.0f/256.0f;
float green = 182.0f/256.0f;
float blue = 182.0f/256.0f;
NSColor *color = [NSColor colorWithCalibratedRed:red green:green blue:blue alpha:1.0f];
Related
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.
Can I change the color of NSTableView's border. The gray line at the pointer.
Thanks.
You need to SubClass your NSScrollView . NSScrollView doesn't usually do any drawing, and probably has a weird interaction with its child views in that way. I'd suggest putting something like
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// We're going to be modifying the state for this,
// so allow it to be restored later
[NSGraphicsContext saveGraphicsState];
// Choose the correct color; isFirstResponder is a custom
// ivar set in becomeFirstResponder and resignFirstResponder
[[NSColor redColor]set];
// Create two rects, one slightly outset from the bounds,
// one slightly inset
NSRect bounds = [self bounds];
NSRect innerRect = NSInsetRect(bounds, 2, 2);
NSRect outerRect = NSMakeRect(bounds.origin.x - 2,
bounds.origin.y - 2,
bounds.size.width + 4,
bounds.size.height + 4);
// Create a bezier path using those two rects; this will
// become the clipping path of the context
NSBezierPath * clipPath = [NSBezierPath bezierPathWithRect:outerRect];
[clipPath appendBezierPath:[NSBezierPath bezierPathWithRect:innerRect]];
// Change the current clipping path of the context to
// the enclosed area of clipPath; "enclosed" defined by
// winding rule. Drawing will be restricted to this area.
// N.B. that the winding rule makes the order that the
// rects were added to the path important.
[clipPath setWindingRule:NSEvenOddWindingRule];
[clipPath setClip];
// Fill the rect; drawing is clipped and the inner rect
// is not drawn in
[[NSBezierPath bezierPathWithRect:outerRect] fill];
[NSGraphicsContext restoreGraphicsState];
}
Easy way to set border to scrollview
let scrollView = NSScrollView()
scrollView.contentView.wantsLayer = true
scrollView.contentView.layer?.borderColor = NSColor.black
scrollView.contentView.layer?.cornerRadius = 6
There are several ways to four draw rounded corners in Cocoa, either using CALayer or NSBezierPath. But how can I draw a single rounded corner on a NSButton?
The current structure of the button is:
NSButton *button = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 50, 20)];
[button setTitle:#"My button"];
[button.cell setBackgroundColor:[NSColor grayColor]];
What I want to do is have a rounded top-right corner with a radius of 10. How do I do that?
SOLUTION:
Override drawRect:
CGFloat cornerRadius = 10;
NSBezierPath *path = [NSBezierPath bezierPath];
// Start drawing from upper left corner
[path moveToPoint:NSMakePoint(NSMinX(self.bounds), NSMinY(self.bounds))];
// Draw top border and a top-right rounded corner
NSPoint topRightCorner = NSMakePoint(NSMaxX(self.bounds), NSMinY(self.bounds));
[path lineToPoint:NSMakePoint(NSMaxX(self.bounds) - cornerRadius, NSMinY(self.bounds))];
[path curveToPoint:NSMakePoint(NSMaxX(self.bounds), NSMinY(self.bounds) + cornerRadius)
controlPoint1:topRightCorner
controlPoint2:topRightCorner];
// Draw right border, bottom border and left border
[path lineToPoint:NSMakePoint(NSMaxX(self.bounds), NSMaxY(self.bounds))];
[path lineToPoint:NSMakePoint(NSMinX(self.bounds), NSMaxY(self.bounds))];
[path lineToPoint:NSMakePoint(NSMinX(self.bounds), NSMinY(self.bounds))];
// Fill path
[[NSColor whiteColor] setFill];
[path fill];
You need to use NSBezierPath and draw a custom button as per your requirement.
You need to work something like this :
NSBezierPath *path = [NSBezierPath bezierPath];
[path setLineWidth:1];
[path moveToPoint:NSMakePoint(0, 0)];
[path curveToPoint:NSMakePoint(width * 0.1, height)
controlPoint1:NSMakePoint(width * 0.05, height)
controlPoint2:NSMakePoint(width * 0.03, height * 0.05)];
And so on... Untill you make a closed button area and you get exact shape.
Based on Christoffer Christoffer's approach, I've created a more general solution where you can choose which corner you want to round. It's in Swift 3 and also works on both macOS & iOS/tvOS.
You can find the Swift Playground here: https://github.com/janheiermann/BezierPath-Corners
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.
I am using NSAffineTransform to rotate/ reflect an NSImage and when using larger images i run into an error:
NSImage: Insufficient memory to allocate pixel data buffer of 4496342739800064 bytes
The image i am transforming here is 6,998,487 bytes at 4110px x 2735. Does NSAffineTransform really need this much memory to do this transformation or am i going wrong somewhere? Heres my rotate code:
-(NSImage *)rotateLeft:(NSImage *)img{
NSImage *existingImage = img;
NSSize existingSize;
existingSize.width = existingImage.size.width;
existingSize.height = existingImage.size.height;
NSSize newSize = NSMakeSize(existingSize.height, existingSize.width);
NSImage *rotatedImage = [[NSImage alloc] initWithSize:newSize];
[rotatedImage lockFocus];
NSAffineTransform *rotateTF = [NSAffineTransform transform];
NSPoint centerPoint = NSMakePoint(newSize.width / 2, newSize.height / 2);
[rotateTF translateXBy: centerPoint.x yBy: centerPoint.y];
[rotateTF rotateByDegrees: 90];
[rotateTF translateXBy: -centerPoint.y yBy: -centerPoint.x];
[rotateTF concat];
NSRect r1 = NSMakeRect(0, 0, newSize.height, newSize.width);
[existingImage drawAtPoint:NSMakePoint(0,0)
fromRect:r1
operation:NSCompositeCopy fraction:1.0];
[rotatedImage unlockFocus];
return rotatedImage;
}
I am using ARC in my project.
Thanks in advance, Ben