NSImage transparency - cocoa

I'm trying to set a custom drag icon for use in an NSTableView. Everything seems to work but I've run into a problem due to my inexperience with Quartz.
- (NSImage *)dragImageForRowsWithIndexes:(NSIndexSet *)dragRows tableColumns:(NSArray *)tableColumns event:(NSEvent *)dragEvent offset:(NSPointPointer)dragImageOffset
{
NSImage *dragImage = [NSImage imageNamed:#"icon.png"];
NSString *count = [NSString stringWithFormat:#"%d", [dragRows count]];
[dragImage lockFocus];
[dragImage compositeToPoint:NSZeroPoint fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:0.5];
[count drawAtPoint:NSZeroPoint withAttributes:nil];
[dragImage unlockFocus];
return dragImage;
}
Essentially what I'm looking to do is render my icon.png file with 50% opacity along with an NSString which shows the number of rows currently being dragged. The issue I'm seeing is that my NSString renders with a low opacity, but not my icon.

The issue is that you’re drawing your icon on top of itself. What you probably want is something like this:
- (NSImage *)dragImageForRowsWithIndexes:(NSIndexSet *)dragRows tableColumns:(NSArray *)tableColumns event:(NSEvent *)dragEvent offset:(NSPointPointer)dragImageOffset
{
NSImage *icon = [NSImage imageNamed:#"icon.png"];
NSString *count = [NSString stringWithFormat:#"%lu", [dragRows count]];
NSImage *dragImage = [[[NSImage alloc] initWithSize:[icon size]] autorelease];
[dragImage lockFocus];
[icon drawAtPoint:NSZeroPoint fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:0.5];
[count drawAtPoint:NSZeroPoint withAttributes:nil];
[dragImage unlockFocus];
return dragImage;
}

Related

Looking for simple example to write text into a NSImage using Cocoa

NSImage *myNewIconImage=[[NSImage imageNamed:#"FanImage"] copy];
[myNewIconImage lockFocus];
[#"15" drawAtPoint:NSZeroPoint withAttributes:nil];
[myNewIconImage unlockFocus];
[myNewIconImage setTemplate:YES];
[[NSApplication sharedApplication]] setApplicationIconImage:myNewIconImage];
I am looking for a way to simply write a String onto this image.... and coming up very short. This does not worker me.
The following code will place a mutable attributed string on an NSImage:
NSImageView *imageView = [[NSImageView alloc] initWithFrame:NSMakeRect( 0, 0, _wndW, _wndH )];
[[window contentView] addSubview:imageView];
NSImage *image = [NSImage imageNamed:#"myImage.jpg"];
[image lockFocus];
NSMutableDictionary *attr = [NSMutableDictionary dictionary];
[attr setObject:[NSFont fontWithName:#"Lucida Grande" size:36] forKey:NSFontAttributeName];
[attr setObject: [NSNumber numberWithFloat: 10.0] forKey: NSStrokeWidthAttributeName];
[attr setObject:[NSColor whiteColor] forKey:NSForegroundColorAttributeName];
NSString *myStr = #"Welcome to Cocoa";
NSMutableAttributedString *s = [[NSMutableAttributedString alloc] initWithString: myStr attributes:attr];
[s drawAtPoint:NSMakePoint(130,130)];
[image unlockFocus];
[imageView setImage:image];

iTunes -like status bar

EDITED:
I am trying to show an iTunes-style like information bar. This was subject was covered in detail earlier, for example at iTunes or Xcode style information box at top of window
I only slightly modified the code from the above referenced link, so make it compile under a recent XCode.
My code is below:
- (void)drawRect:(NSRect)dirtyRect
{
// Drawing code here.
static NSShadow *kDropShadow = nil;
static NSShadow *kInnerShadow = nil;
static NSGradient *kBackgroundGradient = nil;
static NSColor *kBorderColor = nil;
if (kDropShadow == nil) {
kDropShadow = [[NSShadow alloc] init];
[kDropShadow setShadowColor:[NSColor colorWithCalibratedWhite:.863 alpha:.75]];
[kDropShadow setShadowOffset:NSMakeSize(0.0, -1.0)];
[kDropShadow setShadowBlurRadius:1.0];
kInnerShadow = [[NSShadow alloc] init];
[kInnerShadow setShadowColor:[NSColor colorWithCalibratedWhite:0.0 alpha:0.52]];
[kInnerShadow setShadowOffset:NSMakeSize(0.0, -1.0)];
[kInnerShadow setShadowBlurRadius:4.0];
kBorderColor = [[NSColor colorWithCalibratedWhite:0.569 alpha:1.0] retain];
// iTunes style
// kBackgroundGradient = [[NSGradient alloc] initWithColorsAndLocations:[NSColor colorWithCalibratedRed:0.929 green:0.945 blue:0.882 alpha:1.0],0.0,[NSColor colorWithCalibratedRed:0.902 green:0.922 blue:0.835 alpha:1.0],0.5,[NSColor colorWithCalibratedRed:0.871 green:0.894 blue:0.78 alpha:1.0],0.5,[NSColor colorWithCalibratedRed:0.949 green:0.961 blue:0.878 alpha:1.0],1.0, nil];
// Xcode style
kBackgroundGradient = [[NSGradient alloc] initWithColorsAndLocations:[NSColor colorWithCalibratedRed:0.957 green:0.976 blue:1.0 alpha:1.0],0.0,[NSColor colorWithCalibratedRed:0.871 green:0.894 blue:0.918 alpha:1.0],0.5,[NSColor colorWithCalibratedRed:0.831 green:0.851 blue:0.867 alpha:1.0],0.5,[NSColor colorWithCalibratedRed:0.82 green:0.847 blue:0.89 alpha:1.0],1.0, nil];
}
NSRect bounds = [self bounds];
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:bounds xRadius:3.5 yRadius:3.5];
[NSGraphicsContext saveGraphicsState];
[kDropShadow set];
[path fill];
[NSGraphicsContext restoreGraphicsState];
[kBackgroundGradient drawInBezierPath:path angle:-90.0];
[kBorderColor setStroke];
[path stroke];
}
It is not working, however. I don't think drawRect() method ever gets called. What am I missing? Please advise.
Thank you
I had an extra line at the end:
[path fill];
Removing it does the trick.

Create an animation like that of an NSPopOver

I'm working at a custom Window object that is displayed as child in a parent Window.
For this object I'd like to create an animation like that of an NSPopover.
My first idea is to create a Screenshot of the child Window, than animate it using Core Animation and finally showing the real Window.
Before begging the implementation I would like to know if exists a better method and what you think about my solution.
It's not trivial. Here's how I do it:
#interface ZoomWindow : NSWindow
{
CGFloat animationTimeMultiplier;
}
#property (nonatomic, readwrite, assign) CGFloat animationTimeMultiplier;
#end
#implementation ZoomWindow
#synthesize animationTimeMultiplier;
- (NSTimeInterval)animationResizeTime: (NSRect)newWindowFrame
{
float multiplier = animationTimeMultiplier;
if (([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) != 0) {
multiplier *= 10;
}
return [super animationResizeTime: newWindowFrame] * multiplier;
}
#end
#implementation NSWindow (PecuniaAdditions)
- (ZoomWindow*)createZoomWindowWithRect: (NSRect)rect
{
// Code mostly from http://www.noodlesoft.com/blog/2007/06/30/animation-in-the-time-of-tiger-part-1/
// Copyright 2007 Noodlesoft, L.L.C.. All rights reserved.
// The code is provided under the MIT license.
// The code has been extended to support layer-backed views. However, only the top view is
// considered here. The code might not produce the desired output if only a subview has its layer
// set. So better set it on the top view (which should cover most cases).
NSImageView *imageView;
NSImage *image;
NSRect frame;
BOOL isOneShot;
frame = [self frame];
isOneShot = [self isOneShot];
if (isOneShot) {
[self setOneShot: NO];
}
BOOL hasLayer = [[self contentView] wantsLayer];
if ([self windowNumber] <= 0) // <= 0 if hidden
{
// We need to temporarily switch off the backing layer of the content view or we get
// context errors on the second or following runs of this code.
[[self contentView] setWantsLayer: NO];
// Force window device. Kinda crufty but I don't see a visible flash
// when doing this. May be a timing thing wrt the vertical refresh.
[self orderBack: self];
[self orderOut: self];
[[self contentView] setWantsLayer: hasLayer];
}
// Capture the window into an off-screen bitmap.
image = [[NSImage alloc] initWithSize: frame.size];
[[self contentView] lockFocus];
NSBitmapImageRep* rep = [[NSBitmapImageRep alloc] initWithFocusedViewRect: NSMakeRect(0.0, 0.0, frame.size.width, frame.size.height)];
[[self contentView] unlockFocus];
[image addRepresentation: rep];
// If the content view is layer-backed the above initWithFocusedViewRect call won't get the content
// of the view (seems it doesn't work for CALayers). So we need a second call that captures the
// CALayer content and copies it over the captured image (compositing so the window frame and its content).
if (hasLayer)
{
NSRect contentFrame = [[self contentView] bounds];
int bitmapBytesPerRow = 4 * contentFrame.size.width;
CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
CGContextRef context = CGBitmapContextCreate (NULL,
contentFrame.size.width,
contentFrame.size.height,
8,
bitmapBytesPerRow,
colorSpace,
kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colorSpace);
[[[self contentView] layer] renderInContext: context];
CGImageRef img = CGBitmapContextCreateImage(context);
CFRelease(context);
NSImage *subImage = [[NSImage alloc] initWithCGImage: img size: contentFrame.size];
CFRelease(img);
[image lockFocus];
[subImage drawAtPoint: NSMakePoint(0, 0)
fromRect: NSMakeRect(0, 0, contentFrame.size.width, contentFrame.size.height)
operation: NSCompositeCopy
fraction: 1];
[image unlockFocus];
}
ZoomWindow *zoomWindow = [[ZoomWindow alloc] initWithContentRect: rect
styleMask: NSBorderlessWindowMask
backing: NSBackingStoreBuffered
defer: NO];
zoomWindow.animationTimeMultiplier = 0.3;
[zoomWindow setBackgroundColor: [NSColor colorWithDeviceWhite: 0.0 alpha: 0.0]];
[zoomWindow setHasShadow: [self hasShadow]];
[zoomWindow setLevel: [self level]];
[zoomWindow setOpaque: NO];
[zoomWindow setReleasedWhenClosed: NO];
[zoomWindow useOptimizedDrawing: YES];
imageView = [[NSImageView alloc] initWithFrame: [zoomWindow contentRectForFrameRect: frame]];
[imageView setImage: image];
[imageView setImageFrameStyle: NSImageFrameNone];
[imageView setImageScaling: NSScaleToFit];
[imageView setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable];
[zoomWindow setContentView: imageView];
[self setOneShot: isOneShot];
return zoomWindow;
}
- (void)fadeIn
{
[self setAlphaValue: 0.f];
[self orderFront: nil];
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration: 0.3];
[[self animator] setAlphaValue: 1.f];
[NSAnimationContext endGrouping];
}
- (void)zoomInWithOvershot: (NSRect)overshotFrame withFade: (BOOL)fade makeKey: (BOOL)makeKey
{
[self setAlphaValue: 0];
NSRect frame = [self frame];
ZoomWindow *zoomWindow = [self createZoomWindowWithRect: frame];
zoomWindow.alphaValue = 0;
[zoomWindow orderFront: self];
NSDictionary *windowResize = #{NSViewAnimationTargetKey: zoomWindow,
NSViewAnimationEndFrameKey: [NSValue valueWithRect: overshotFrame],
NSViewAnimationEffectKey: NSViewAnimationFadeInEffect};
NSArray *animations = #[windowResize];
NSViewAnimation *animation = [[NSViewAnimation alloc] initWithViewAnimations: animations];
[animation setAnimationBlockingMode: NSAnimationBlocking];
[animation setAnimationCurve: NSAnimationEaseIn];
[animation setDuration: 0.2];
[animation startAnimation];
zoomWindow.animationTimeMultiplier = 0.5;
[zoomWindow setFrame: frame display: YES animate: YES];
[self setAlphaValue: 1];
if (makeKey) {
[self makeKeyAndOrderFront: self];
} else {
[self orderFront: self];
}
[zoomWindow close];
}
This is implemented in an NSWindow category. So you can call:
- (void)zoomInWithOvershot: (NSRect)overshotFrame withFade: (BOOL)fade makeKey: (BOOL)makeKey
on any NSWindow.
I should add that I haven't been able to get both animations to run at the same time (fade and size), but the effect is quite similar to how NSPopover does it. Maybe someone else can fix the animation issue.
Needless to say this code works on 10.6 too where you don't have NSPopover (that's why I have written it in the first place).

Drawing on NSBitmapImageRep

I'm trying to create in-memory image, draw on it and save to disk.
Current code is:
NSBitmapImageRep *rep = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:NULL
pixelsWide:256
pixelsHigh:256
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:YES
colorSpaceName:NSDeviceRGBColorSpace
bitmapFormat:NSAlphaFirstBitmapFormat
bytesPerRow:0
bitsPerPixel:8
];
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:rep]];
// Draw your content...
NSRect aRect=NSMakeRect(10.0,10.0,30.0,30.0);
NSBezierPath *thePath=[NSBezierPath bezierPathWithRect:aRect];
[[NSColor redColor] set];
[thePath fill];
[NSGraphicsContext restoreGraphicsState];
NSData *data = [rep representationUsingType: NSPNGFileType properties: nil];
[data writeToFile: #"test.png" atomically: NO];
when trying to draw in current context, I'm getting error
CGContextSetFillColorWithColor: invalid context 0x0
What is wrong here? Why context returned by NSBitmapImageRep is NULL?
What is the best way to create drawn image and save it?
UPDATE:
Finally came to following solution:
NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize(256, 256)];
[image lockFocus];
NSRect aRect=NSMakeRect(10.0,10.0,30.0,30.0);
NSBezierPath *thePath=[NSBezierPath bezierPathWithRect:aRect];
[[NSColor redColor] set];
[thePath fill];
[image unlockFocus];
NSData *data = [image TIFFRepresentation];
[data writeToFile: #"test.png" atomically: NO];
Your workaround is valid for the task at hand, however, it's a more expensive operation than actually getting an NSBitmapImageRep to work! See http://cocoadev.com/wiki/NSBitmapImageRep for a bit of a discussion..
Notice that [NSGraphicsContext graphicsContextWithBitmapImageRep:] documentation says:
"This method accepts only single plane NSBitmapImageRep instances."
You are setting up your NSBitmapImageRep with isPlanar:YES, which therefore uses multiple planes... Set it to NO - you should be good to go!
In other words:
NSBitmapImageRep *rep = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:NULL
pixelsWide:256
pixelsHigh:256
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSDeviceRGBColorSpace
bitmapFormat:NSAlphaFirstBitmapFormat
bytesPerRow:0
bitsPerPixel:0
];
// etc...

NSString *text to NSString *icon?

I am making an app that is a standalone menu item and the basis for the code is sample code I found on a website. The sample code uses a number as the menu icon, but I want to change it to an image.
I want it to be like other apps where it shows icon.png when not clicked and icon-active.png when clicked.
The current code is this:
- (void)drawRect:(NSRect)rect {
// Draw background if appropriate.
if (clicked) {
[[NSColor selectedMenuItemColor] set];
NSRectFill(rect);
}
// Draw some text, just to show how it's done.
NSString *text = #"3"; // whatever you want
NSColor *textColor = [NSColor controlTextColor];
if (clicked) {
textColor = [NSColor selectedMenuItemTextColor];
}
NSFont *msgFont = [NSFont menuBarFontOfSize:15.0];
NSMutableParagraphStyle *paraStyle = [[NSMutableParagraphStyle alloc] init];
[paraStyle setParagraphStyle:[NSParagraphStyle defaultParagraphStyle]];
[paraStyle setAlignment:NSCenterTextAlignment];
[paraStyle setLineBreakMode:NSLineBreakByTruncatingTail];
NSMutableDictionary *msgAttrs = [NSMutableDictionary dictionaryWithObjectsAndKeys:
msgFont, NSFontAttributeName,
textColor, NSForegroundColorAttributeName,
paraStyle, NSParagraphStyleAttributeName,
nil];
[paraStyle release];
NSSize msgSize = [text sizeWithAttributes:msgAttrs];
NSRect msgRect = NSMakeRect(0, 0, msgSize.width, msgSize.height);
msgRect.origin.x = ([self frame].size.width - msgSize.width) / 2.0;
msgRect.origin.y = ([self frame].size.height - msgSize.height) / 2.0;
[text drawInRect:msgRect withAttributes:msgAttrs];
}
Also, I found a post describing a method on how to do this, but it did not work for me. The url to that is this: http://mattgemmell.com/2008/03/04/using-maattachedwindow-with-an-nsstatusitem/comment-page-1#comment-46501.
Thanks!
Use an NSImage and draw it where desired. For example:
NSString *name = clicked? #"icon-active" : #"icon";
NSImage *image = [NSImage imageNamed:name];
NSPoint p = [self bounds].origin;
[image drawAtPoint:p fromRect:NSZeroRect
operation:NSCompositeSourceOver fraction:1.0];
If this is for a status item and you just want an icon with no programmatic drawing, drop the view and set the status item's image and alternateImage. The former is what the status item uses normally; the status item switches to the alternate image (if it has one) when the user opens its menu.

Resources