2011-08-21 7 views

Respuesta

56

Si simplemente desea este efecto se aplique cuando utiliza sus propias imágenes en un botón, el uso [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, puede replicar el efecto utilizando Core Graphics. Si miras con atención, el efecto consiste en un gradiente horizontal, una sombra blanca y una sombra interior oscura (esta última es la más difícil).

Se podría aplicar 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 (mostrado en diferentes tamaños): Screenshot

Puede que necesite experimentar un poco con los colores de degradado y el radio de desplazamiento/desenfoque de las dos sombras para ge t más cerca del efecto original.

+1

Wow, gracias! Esto es exactamente lo que estaba intentando. – Flocked

+0

Edité su código agregando llamadas para guardar y restaurar el estado de los gráficos. Si no hace esto, el contexto actual estará en un estado diferente después de llamar a este método de dibujo, que no es el comportamiento esperado. –

+2

Además, poner 'Plantilla' (con la mayúscula T) al final del nombre de la imagen producirá el mismo efecto, con menos código (Simplemente establezca el nombre en IB). – spudwaffle

0

Para dibujar correctamente dentro de cualquier rect, 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í 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 hay que dejar un borde de 1 píxel alrededor del exterior de la imagen o las sombras no funcionará correctamente.

4

Si no le importa llamar a una API privada, puede dejar que el sistema operativo (CoreUI) lo sombree. Se necesita un par de 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 la barra de botones. 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 ligero.

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: Se produce cuando se presiona el botón.El ícono se pone un poco más oscuro.

Consejo extra: para averiguar cosas como esta, puede usar el complemento 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.

2

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.

Código podría ser similar a la siguiente:

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

se pueden utilizar diferentes células y configuraciones dependiendo de la apariencia que desea tener (por ejemplo NSImageCell con NSBackgroundStyleDark si desea que el aspecto invertida en una tabla seleccionada. Ver fila)

Y como un bono, que se verá automáticamente correcta en todas las versiones de OS X.

Cuestiones relacionadas