How to draw a tapered line + oval shadow in Cocoa

前端 未结 2 467
再見小時候
再見小時候 2020-12-23 18:33

Background:

The shot below is of Mail.app in OS X Lion. When the source list gets too long, a nice shadowy line appears just above the buttons at th

相关标签:
2条回答
  • 2020-12-23 18:53

    General Idea:

    .

    1. Create a layer "Layer A" with dimensions 150px × 10px
      and fill it with a Gradient with:
      • lower color: #535e71 opacity: 33%
      • upper color: #535e71 opacity: 0%
    2. Create a layer "Layer B" with dimensions 150px × 1px
      and fill it with solid #535e71 opacity: 50%
    3. Compose "Layer A" and "Layer B" together into "Layer C".
    4. Apply reflected gradient mask from #ffffff to #000000 to "Layer C".

    Visual Steps:

    enter image description here

    Functional Code:

    MyView.h:

    #import <Cocoa/Cocoa.h>
    
    @interface MyView : NSView {
    @private
    
    }
    
    @end
    

    MyView.m:

    #import "MyView.h"
    
    @implementation MyView
    
    - (CGImageRef)maskForRect:(NSRect)dirtyRect {
        NSSize size = [self bounds].size;
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        CGContextRef context = CGBitmapContextCreate(NULL, size.width, size.height, 8, 0, colorSpace, kCGImageAlphaPremultipliedLast);
    
        CGContextClipToRect(context, *(CGRect*)&dirtyRect);
    
        CGRect rect = CGRectMake(0.0, 0.0, size.width, size.height);
    
        size_t num_locations = 3;
        CGFloat locations[3] = { 0.0, 0.5, 1.0 };
        CGFloat components[12] = {
            1.0, 1.0, 1.0, 1.0,  // Start color
            0.0, 0.0, 0.0, 1.0,  // Middle color
            1.0, 1.0, 1.0, 1.0,  // End color
        };
    
        CGGradientRef myGradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, num_locations);
    
        CGPoint myStartPoint = CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect));
        CGPoint myEndPoint = CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect));
    
        CGContextDrawLinearGradient(context, myGradient, myStartPoint, myEndPoint, 0);
    
        CGImageRef theImage = CGBitmapContextCreateImage(context);
        CGImageRef theMask = CGImageMaskCreate(CGImageGetWidth(theImage), CGImageGetHeight(theImage), CGImageGetBitsPerComponent(theImage), CGImageGetBitsPerPixel(theImage), CGImageGetBytesPerRow(theImage), CGImageGetDataProvider(theImage), NULL, YES);
    
        [(id)theMask autorelease];
    
        CGColorSpaceRelease(colorSpace);
        CGContextRelease(context);
    
        return theMask;
    }
    
    - (void)drawRect:(NSRect)dirtyRect {
        NSRect nsRect = [self bounds];
        CGRect rect = *(CGRect*)&nsRect;
        CGRect lineRect = CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, (CGFloat)1.0);
    
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    
        CGContextRef context = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
        CGContextClipToRect(context, *(CGRect*)&dirtyRect);
        CGContextClipToMask(context, rect, [self maskForRect:dirtyRect]);
    
        size_t num_locations = 2;
        CGFloat locations[2] = { 0.0, 1.0 };
        CGFloat components[8] = {
            0.315, 0.371, 0.450, 0.3,  // Bottom color
            0.315, 0.371, 0.450, 0.0  // Top color
        };
    
        CGGradientRef myGradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, num_locations);
    
        CGPoint myStartPoint = CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect));
        CGPoint myEndPoint = CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect));
    
        CGContextDrawLinearGradient(context, myGradient, myStartPoint, myEndPoint, 0);
    
        CGContextSetRGBFillColor(context, 0.315, 0.371, 0.450, 0.5 );
        CGContextFillRect(context, lineRect);
    
        CGColorSpaceRelease(colorSpace);    
    }
    
    @end
    

    (My first time using pure low-level CoreGraphics, thus possibly sub-par optimal, open for improvements.)

    This is an actual screenshot of what the code above produces:
    enter image description here
    The drawing stretches to the view's dimensions.

    (I formerly had two techniques shown here: "Technique A" & "Technique B".
    "Technique B" provided superior results and was way simpler to implement as well, so I ditched "Technique A".
    Some comments may still refer to "Technique A" though. Just ignore them and enjoy the fully functional code snippet.).

    0 讨论(0)
  • 2020-12-23 18:55

    How about an image that's stretched horizontally?

    Or if you know how you'd make one in Photoshop, you could apply those same steps programmatically.

    0 讨论(0)
提交回复
热议问题