Как нарисовать NSImage как изображения в NSButtons (с глубиной)? - PullRequest
18 голосов
/ 21 августа 2011

Есть ли способ нарисовать NSImage как изображения в NSButtons или других элементах интерфейса какао?

Вот примеры: enter image description here enter image description here

Apple использует PDF с черными значками:enter image description here

Ответы [ 4 ]

58 голосов
/ 21 августа 2011

Если вы просто хотите, чтобы этот эффект применялся при использовании ваших собственных изображений в кнопке, используйте [myImage setTemplate:YES]. Нет встроенного способа рисования изображений с этим эффектом за пределами кнопки, стиль которой показан на скриншотах.

Однако вы можете повторить эффект, используя Core Graphics. Если вы присмотритесь, эффект состоит из горизонтального градиента, белой тени и внутренней темной тени (последняя наиболее сложная).

Вы можете реализовать это как категорию на NSImage:

//NSImage+EtchedDrawing.h:
@interface NSImage (EtchedImageDrawing)    
- (void)drawEtchedInRect:(NSRect)rect;
@end

//NSImage+EtchedDrawing.m:
@implementation NSImage (EtchedImageDrawing)

- (void)drawEtchedInRect:(NSRect)rect
{
    NSSize size = rect.size;
    CGFloat dropShadowOffsetY = size.width <= 64.0 ? -1.0 : -2.0;
    CGFloat innerShadowBlurRadius = size.width <= 32.0 ? 1.0 : 4.0;

    CGContextRef c = [[NSGraphicsContext currentContext] graphicsPort];

    //save the current graphics state
    CGContextSaveGState(c);

    //Create mask image:
    NSRect maskRect = rect;
    CGImageRef maskImage = [self CGImageForProposedRect:&maskRect context:[NSGraphicsContext currentContext] hints:nil];

    //Draw image and white drop shadow:
    CGContextSetShadowWithColor(c, CGSizeMake(0, dropShadowOffsetY), 0, CGColorGetConstantColor(kCGColorWhite));
    [self drawInRect:maskRect fromRect:NSMakeRect(0, 0, self.size.width, self.size.height) operation:NSCompositeSourceOver fraction:1.0];

    //Clip drawing to mask:
    CGContextClipToMask(c, NSRectToCGRect(maskRect), maskImage);

    //Draw gradient:
    NSGradient *gradient = [[[NSGradient alloc] initWithStartingColor:[NSColor colorWithDeviceWhite:0.5 alpha:1.0]
                                                          endingColor:[NSColor colorWithDeviceWhite:0.25 alpha:1.0]] autorelease];
    [gradient drawInRect:maskRect angle:90.0];
    CGContextSetShadowWithColor(c, CGSizeMake(0, -1), innerShadowBlurRadius, CGColorGetConstantColor(kCGColorBlack));

    //Draw inner shadow with inverted mask:
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef maskContext = CGBitmapContextCreate(NULL, CGImageGetWidth(maskImage), CGImageGetHeight(maskImage), 8, CGImageGetWidth(maskImage) * 4, colorSpace, kCGImageAlphaPremultipliedLast);
    CGColorSpaceRelease(colorSpace);
    CGContextSetBlendMode(maskContext, kCGBlendModeXOR);
    CGContextDrawImage(maskContext, maskRect, maskImage);
    CGContextSetRGBFillColor(maskContext, 1.0, 1.0, 1.0, 1.0);
    CGContextFillRect(maskContext, maskRect);
    CGImageRef invertedMaskImage = CGBitmapContextCreateImage(maskContext);
    CGContextDrawImage(c, maskRect, invertedMaskImage);
    CGImageRelease(invertedMaskImage);
    CGContextRelease(maskContext);

    //restore the graphics state
    CGContextRestoreGState(c);
}

@end

Пример использования в представлении:

- (void)drawRect:(NSRect)dirtyRect
{
    [[NSColor colorWithDeviceWhite:0.8 alpha:1.0] set];
    NSRectFill(self.bounds);

    NSImage *image = [NSImage imageNamed:@"MyIcon.pdf"];
    [image drawEtchedInRect:self.bounds];
}

Это даст вам следующий результат (показан в разных размерах): Screenshot

Возможно, вам придется немного поэкспериментировать с цветами градиента и радиусом смещения / размытия двух теней, чтобы приблизиться к исходному эффекту.

4 голосов
/ 23 апреля 2013

Если вы не возражаете против вызова частного API, вы можете позволить операционной системе (CoreUI) сделать за вас затенение.Вам нужно несколько объявлений:

typedef CFTypeRef CUIRendererRef;
extern void CUIDraw(CUIRendererRef renderer, CGRect frame, CGContextRef context, CFDictionaryRef object, CFDictionaryRef *result);

@interface NSWindow(CoreUIRendererPrivate)
+ (CUIRendererRef)coreUIRenderer;
@end

И для фактического рисунка:

CGRect drawRect = CGRectMake(x, y, width, height);
CGImageRef cgimage = your_image;

CFDictionaryRef dict = (CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:
        @"backgroundTypeRaised", @"backgroundTypeKey",
        [NSNumber numberWithBool:YES], @"imageIsGrayscaleKey",
        cgimage, @"imageReferenceKey",
        @"normal", @"state",
        @"image", @"widget",
        [NSNumber numberWithBool:YES], @"is.flipped",
        nil];
CUIDraw ([NSWindow coreUIRenderer], drawRect, cg, dict, nil);
CGImageRelease (cgimage);

Это займет альфа-канал cgimage и применяет эффект тиснения, как видно на кнопках панели инструментов.Вы можете или не можете нуждаться в строке "is.flipped".Удалите его, если ваш результат перевернут.

Существует несколько вариантов:

kCUIPresentationStateKey = kCUIPresentationStateInactive: окно не активно, изображение будет светлее.

state = rollover: имеет смысл только с предыдущей опцией.Это означает, что вы наводите курсор на изображение, окно неактивно, но кнопка чувствительна (переход по нажатию включен).Становится темнее.

state = pressed: Происходит при нажатии кнопки.Значок становится немного темнее.

Дополнительный совет: чтобы узнать подобные вещи, вы можете использовать плагин SIMBL CUITrace .Он распечатывает все вызовы CoreUI целевого приложения.Это сокровищница, если вам нужно нарисовать свой собственный интерфейс.

2 голосов
/ 30 января 2015

Вот гораздо более простое решение: просто создайте ячейку и дайте ей нарисовать.Запрещено возиться с частными API или Core Graphics.

Код может выглядеть примерно так:

NSButtonCell *buttonCell = [[NSButtonCell alloc] initImageCell:image];
buttonCell.bordered = YES;
buttonCell.bezelStyle =  NSTexturedRoundedBezelStyle;
// additional configuration
[buttonCell drawInteriorWithFrame: someRect inView:self];

Вы можете использовать разные ячейки и конфигурации в зависимости от желаемого вида (например,. NSImageCell с NSBackgroundStyleDark, если вы хотите перевернутый вид в выбранной строке табличного представления)

И в качестве бонуса, он автоматически будет выглядеть корректно во всех версиях OS X.

0 голосов
/ 22 мая 2012

Чтобы правильно рисовать в любом прямоугольнике, CGContextDrawImage и CGContextFillRect для внутренней маски должны иметь начало (0,0).затем, когда вы рисуете изображение для внутренней тени, вы можете снова использовать прямоугольную маску.В итоге получается, что выглядело так:

CGRect cgRect = CGRectMake( 0, 0, maskRect.size.width, maskRect.size.height );    
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef maskContext = CGBitmapContextCreate( NULL, CGImageGetWidth( maskImage ), CGImageGetHeight( maskImage ), 8, CGImageGetWidth( maskImage ) * 4, colorSpace, kCGImageAlphaPremultipliedLast );
CGColorSpaceRelease( colorSpace );
CGContextSetBlendMode( maskContext , kCGBlendModeXOR );
CGContextDrawImage( maskContext, cgRect, maskImage );
CGContextSetRGBFillColor( maskContext, 1.0, 1.0, 1.0, 1.0 );
CGContextFillRect( maskContext, cgRect );
CGImageRef invertedMaskImage = CGBitmapContextCreateImage( maskContext );

CGContextDrawImage( context, maskRect, invertedMaskImage );
CGImageRelease( invertedMaskImage );
CGContextRelease( maskContext );
CGContextRestoreGState( context );

Вы также должны оставить границу в 1 пиксель вокруг изображения, иначе тени не будут работать правильно.

...