Подтвердить что ты не робот

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

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

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

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

4b9b3361

Ответ 1

Если вы просто хотите, чтобы этот эффект применялся при использовании ваших собственных изображений кнопкой, используйте [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

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

Ответ 2

Если вы не против вызова частного 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 целевого приложения. Это сокровищница, если вам нужно нарисовать свой собственный пользовательский интерфейс.

Ответ 3

Здесь гораздо более простое решение: просто создайте ячейку и дайте ей рисовать. Никаких ошибок при работе с частными 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.

Ответ 4

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

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 );

Вам также нужно оставить границу 1px по внешней стороне изображения или тени не будут работать правильно.