【发布时间】:2011-08-21 10:33:58
【问题描述】:
有没有办法在 NSButtons 或其他可可界面元素中绘制类似 NSImage 的图像?
以下是示例:
Apple 使用带有黑色图标的 pdf:
【问题讨论】:
-
使用
NSButtonCell绘制
标签: objective-c cocoa draw nsimage
有没有办法在 NSButtons 或其他可可界面元素中绘制类似 NSImage 的图像?
以下是示例:
Apple 使用带有黑色图标的 pdf:
【问题讨论】:
NSButtonCell 绘制
标签: objective-c cocoa draw nsimage
如果您只是希望在按钮中使用自己的图像时应用此效果,请使用[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];
}
这将为您提供以下结果(以不同的尺寸显示):
您可能需要对两个阴影的渐变颜色和偏移/模糊半径进行一些试验,以更接近原始效果。
【讨论】:
Template(大写T)放在图片名称的末尾也会产生相同的效果,代码更少(只需在IB中设置名称即可)。
maskContext 从未被释放。就是现在。
如果您不介意调用私有 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 的 alpha 通道并应用在工具栏按钮上看到的浮雕效果。您可能需要也可能不需要“is.flipped”行。如果您的结果是颠倒的,请将其删除。
有很多变体:
kCUIPresentationStateKey = kCUIPresentationStateInactive: 窗口没有激活,图片会变亮。
state = rollover:只有上一个选项才有意义。这意味着您将鼠标悬停在图像上,窗口处于非活动状态,但按钮是敏感的(启用点击)。它会变暗。
state = pressed:按下按钮时发生。图标变暗了一点。
额外提示:要查找此类内容,您可以使用 SIMBL 插件CUITrace。它打印出目标应用程序的所有 CoreUI 调用。如果您必须绘制自己的原生 UI,这是一个宝库。
【讨论】:
这里有一个更简单的解决方案:只需创建一个单元格并让它绘制。不要乱用私有 API 或核心图形。
代码可能类似于以下内容:
NSButtonCell *buttonCell = [[NSButtonCell alloc] initImageCell:image];
buttonCell.bordered = YES;
buttonCell.bezelStyle = NSTexturedRoundedBezelStyle;
// additional configuration
[buttonCell drawInteriorWithFrame: someRect inView:self];
您可以根据您想要的外观使用不同的单元格和配置(例如,NSImageCell 和 NSBackgroundStyleDark,如果您希望在选定的表格视图行中反转外观)
作为奖励,它会自动在所有版本的 OS X 上看起来正确。
【讨论】:
要在任何矩形内正确绘制,内部掩码的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 像素的边框,否则阴影将无法正常工作。
【讨论】: