objective c - ¿Cómo dibujar un NSImage como imágenes en NSButtons(con una profundidad)?
objective-c cocoa (4)
Si simplemente desea aplicar este efecto cuando usa sus propias imágenes en un botón, use [myImage setTemplate:YES]
. No existe una forma incorporada de dibujar imágenes con este efecto fuera de un botón que tenga el estilo que se muestra en las capturas de pantalla.
Sin embargo, puedes replicar el efecto usando Core Graphics. Si miras con atención, el efecto consiste en un gradiente horizontal, una sombra blanca y una sombra interna oscura (esta última es la más difícil).
Puede implementar esto como una categoría en 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
Ejemplo de uso en una vista:
- (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];
}
Esto le daría el siguiente resultado (que se muestra en diferentes tamaños):
Es posible que necesite experimentar un poco con los colores de degradado y el radio de desplazamiento / desenfoque de las dos sombras para acercarse al efecto original.
¿Hay alguna forma de dibujar un NSImage como imágenes en NSButtons u otros elementos de la interfaz de cacao?
Aquí hay ejemplos:
Apple usa archivos PDF con iconos negros:
Para dibujar correctamente dentro de cualquier rect, el CGContextDrawImage
y CGContextFillRect
para la máscara interna deben tener el origen de (0,0)
. luego, cuando dibujas la imagen para la sombra interior, puedes reutilizar la máscara rect. Así que termina pareciéndose a:
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 );
También debe dejar un borde de 1px alrededor de la parte exterior de la imagen o las sombras no funcionarán correctamente.
Si no le importa llamar a una API privada, puede dejar que el sistema operativo (CoreUI) lo sombree. Necesitas algunas declaraciones:
typedef CFTypeRef CUIRendererRef;
extern void CUIDraw(CUIRendererRef renderer, CGRect frame, CGContextRef context, CFDictionaryRef object, CFDictionaryRef *result);
@interface NSWindow(CoreUIRendererPrivate)
+ (CUIRendererRef)coreUIRenderer;
@end
Y para el dibujo real:
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);
Esto tomará el canal alfa de cgimage y aplicará el efecto de relieve como se ve en los botones de la barra de herramientas. Puede o no necesitar la línea "is.flipped". Quítelo si su resultado está boca abajo.
Hay un montón de variaciones:
kCUIPresentationStateKey = kCUIPresentationStateInactive
: la ventana no está activa, la imagen será más clara.
state = rollover
: solo tiene sentido con la opción anterior. Esto significa que está sobre la imagen, la ventana está inactiva, pero el botón es sensible (el clic está habilitado). Se volverá más oscuro.
state = pressed
: ocurre cuando se presiona el botón. El ícono se pone un poco más oscuro.
Consejo de bonificación: para descubrir cosas como esta, puede usar el plugin SIMBL CUITrace . Imprime todas las invocaciones de CoreUI de una aplicación de destino. Este es un tesoro si tiene que dibujar su propia interfaz de usuario de aspecto nativo.
Aquí hay una solución mucho más simple: simplemente crea una celda y deja que dibuje. Sin perder el tiempo con API privadas o Core Graphics.
El código podría ser similar al siguiente:
NSButtonCell *buttonCell = [[NSButtonCell alloc] initImageCell:image];
buttonCell.bordered = YES;
buttonCell.bezelStyle = NSTexturedRoundedBezelStyle;
// additional configuration
[buttonCell drawInteriorWithFrame: someRect inView:self];
Puede usar diferentes celdas y configuraciones dependiendo del aspecto que desee tener (por ejemplo, NSImageCell
con NSBackgroundStyleDark
si desea el aspecto invertido en una fila de vista de tabla seleccionada)
Y como beneficio adicional, se verá automáticamente correcto en todas las versiones de OS X.