NSBezierPath to NSImage in order to avoid CoreAnimation - cocoa

I have an app that currently has this line:
[myView setWantsLayer:YES];
In order to draw a GUI element via NSBezierPath. This line is required, otherwise when the user types in an adjacent (and overlapping) NSTextField, the contents of myView shudders.
I discovered that calling CoreAnimation loads the OpenGL framework, but does not unload it. See this question.
I think I can get around this by drawing the NSBezierPath to NSImage and then to display the NSImage in lieu of the NSBezierPath, but I haven't found a single source that shows me how to go about this.
Edit:
I should note that I want to save this BEFORE The NSBezierPath is displayed - so solutions that draw an existing view to an NSImage are not useful.
Question:
Can someone point me in the right direction for converting NSBezierPath to an NSImage?

You can draw anything directly into an NSImage, and you can create a blank NSImage. So, create an image whose size is the size of the bounds of the path, translate the path so that it's at the origin of the image, and then lock focus on the image, draw the path, and unlock focus.

Related

NSImage is always being scaled and looks bad

I want to create an NSImage of an NSScrollView object, so I can use the flat graphic for animation purposes.
When I render my scrollview object into a graphic and add it back to my window, it works but looks really bad like it's been scaled to 99% or something. I want the image to not be scaled and 100% pixel accurate. (Note: the image isn't scaled, it's the same size, it just looks like it's been poorly rescaled - the text looks rough and poor compared to the view onscreen in the scrollview)
My code:
(scrollView is my NSScrollView object)
NSData *pdf = [scrollView dataWithPDFInsideRect:[scrollView bounds]];
NSImage *image = [[NSImage alloc] initWithData:pdf];
NSImageView *imageView = [[NSImageView alloc] initWithFrame:[scrollView bounds]];
[imageView setImage: image];
[mainGUIPanel addSubview: imageView];
I've tried a heap of things, messed with pixel sizes, bounds, used IB to create the destination NSView and put the image inside that but just cannot get the image to not look bad. Any ideas?
Edit:
I tried writing the pdf data to a pdf file and viewed it, and it looked ok. So the bitmap image is being captured ok, it's just on the display that it looks like it's being scaled somewhat.
Edit2:
Also tried getting the bitmap like this:
NSBitmapImageRep *bitmap = [scrollView bitmapImageRepForCachingDisplayInRect:[scrollView bounds]];
[scrollView cacheDisplayInRect:[scrollView bounds] toBitmapImageRep:bitmap];
NSImage * image = [[NSImage alloc] initWithSize:[bitmap size]];
[image addRepresentation: bitmap];
Same results - the bitmap looks exactly the same, bad and scaled when displayed.
This leads me to believe that capturing the bitmap data either way works fine, it's creating the view and rendering the image that is doing the scaling. How can I make sure that the view and image are shown at the correct size and scaling?
Edit3:
Ok, I started a new blank project and set this up, and it works perfectly - the new imageview is identical to the grabbed bitmap. So I suspect my issue is stemming from some rendering/compositing issue when drawing the bitmap to the view. Investigating further...
It turns out the issue stems from the scrollView that I am rendering from. This has a transparent background (Draw Background is off in IB) and the text is the scrollView looks good. If I turn "Draw Background ON", with a transparent background color, the text is rendered badly, exactly as it is when I capture the image programatically.
So, in my app, even though Draw Background is off, the scrollView image is captured as though Draw Background is on. So I need to understand why the text is rendered badly when Draw Background is on and set to transparent, and hopefully this will lead me towards a solution.
Also tried creating an NSClipview with background drawing turned off and putting the bitmap view into that, but it sill renders the same. I can't find a way to render the transparent image to the screen without horrible artifacting.
Ok, I've found a solution. Instead of getting a grab of the transparent background scrollview object itself, I'm instead getting a grab of the parent view (essentially the window background), and restricting the bounds to the size of the scrollview object.
This captures both the background, and the contents of the scrollview, and displays correctly without any issues of transparency.

NSImage drawInRect and TemplateImages

In have an NSImage is a template image (that is, [NSImage isTemplate] returns YES).
When I use it inside an NSImageView, it is drawn correctly as a template image.
However, if I draw it manually using drawInRect:fromRect:operation:fraction:, it is drawn as a flat black image.
How can I draw an NSImage manually, and still get the 'template' effect?
The special drawing of template images is part of CoreUI and not public.
You can imitate the effect with Quartz though. Details and code can be found in this answer here on SO:
https://stackoverflow.com/a/7138497/100848

Convert NSGradient to NSColor

Is it possible to convert an NSGradient to an NSColor
- (void) viewWillDraw {
NSGradient *grad = [[NSGradient alloc] initWithStartingColor:[NSColor lightGrayColor] endingColor:[NSColor darkGrayColor]];
[super setBackgroundColor:*gradient*;
}
This is my method, I want to be able to pass the NSGradient in as an NSColor, which obviously i cant, is there any way to convert it to one?
On 10.8, you can create, in the following order:
A block that draws the gradient however you like.
An image that is backed by the block.
A color that repeats the image as a pattern.
In this way, you can create a color that looks like anything, including a gradient.
That said, this may not work correctly with window resizing if you try to have the gradient adapt to the size of the background (by using the rect passed to the block) and the background is of a text view in a scroll view. (When I tried it awhile back, the pattern didn't redraw the block; it simply tiled, which looked weird in at least one dimension.) If either your gradient or your window is fixed in size, then you will not have that problem.
NSGradient is not Convertible to NSColor.
The NSGradient class provides support for drawing gradient fill
colors, also known as shadings in Quartz. This class provides
convenience methods for drawing radial or linear (axial) gradients for
rectangles and NSBezierPath objects.
As you want to set the viewBackground to to an effect (Gradient effect) you need to do as:
[grad drawInRect:<the rect of your view> angle:270]; //angle is upto your requirement,

Change bounds origin + cropping an image

I am a newbie to Cocoa, I have a few doubts regarding NSImage.
Question1:
Changing the bounds origin of an image doesn't seem to have any effect. I expected the image to be drawn from the newly set origin but that doesn't seem to the case. Am I missing something ?
code:
NSImage* carImage = [NSImage imageNamed:#"car"];
[self.imageView setImage:carImage];
//Following line has no effect:
self.imageView.bounds = CGRectMake(self.imageView.bounds.origin.x + 100, self.imageView.bounds.origin.y, self.imageView.bounds.size.width,self.imageView.bounds.size.height);
Note: imageView is an IBOutlet
Question2:
I was trying to crop an image, but it doesn't seem to be cropping the image, I can see the complete image. What is that I am missing ?
code:
NSRect sourceRect = CGRectMake(150, 25, 100, 50);
NSRect destRect = CGRectMake(0, 0, 100, 50);
NSImage* carImage = [NSImage imageNamed:#"car"];
[carImage drawInRect:destRect fromRect:sourceRect operation:NSCompositeSourceOver fraction:1.0];
[self.imageView setImage:carImage];
Thanks
Changing the bounds origin of an image doesn't seem to have any effect. …
//Following line has no effect:
self.imageView.bounds = CGRectMake(self.imageView.bounds.origin.x + 100, self.imageView.bounds.origin.y, self.imageView.bounds.size.width,self.imageView.bounds.size.height);
That's an image view, not an image.
The effect of changing the bounds of a view depends on what the view does to draw. Effectively, this means you shouldn't change the bounds of a view that isn't an instance of a view class you created, since you can't predict exactly how an NSImageView will draw its image (presumably, since it's a control, it involves its cell, but more than that, I wouldn't rely on).
More generally, it's pretty rare to change a view's bounds origin. I don't remember having ever done it, and I can't think of a reason off the top of my head to do it. Changing its bounds size will scale, not crop.
I was trying to crop an image, but it doesn't seem to be cropping the image, I can see the complete image. What is that I am missing ?
[carImage drawInRect:destRect fromRect:sourceRect operation:NSCompositeSourceOver fraction:1.0];
[self.imageView setImage:carImage];
Telling an image to draw does not change anything about the image. It will not “crop the image” such that the image will thereafter be smaller or larger. You are telling it to draw, nothing more.
Consequently, the statement after that sets the image view's image to the whole image, exactly as if you hadn't told the image to draw, because telling it to draw made no difference.
What telling an image to draw does is exactly that: It tells the image to draw. There are only two correct places to do that:
In between lockFocus and unlockFocus messages to a view or image (or after setting the current NSGraphicsContext).
Within a view's drawRect: method.
Anywhere else, you should not tell any Cocoa object to draw.
One correct way to crop an image is to create a new image of the desired/adjusted size, lock focus on it, draw the desired portion of the original image into it, and unlock focus on the new image. You will then have both the original and a cropped version.
Another correct way would be to create your own custom image view that has two properties: One owning an image to draw, and the other holding a rectangle. When told to draw, this custom view would tell the image to draw the given rectangle into the view's bounds. You would then always hold the original image and simply draw only the desired section.

What do lockFocus and unlockFocus actually do?

Warning: I'm a Cocoa newbie.
I'm reading "Cocoa Programming For Mac OS X" by Hillegass.
On p.301 it's written:
To make the drawing appear on the image instead of on the screen, you must first lock focus on the image. When the drawing is complete, you must unlock focus.
The code I have, inside -(void)mouseDragged:(NSEvent *)theEvent of an NSView is as follows:
[resizedImage lockFocus];
[sourceImage drawInRect: NSMakeRect(0, 0, resizeWidth, resizeHeight) fromRect: NSMakeRect(0, 0, originalSize.width, originalSize.height) operation: NSCompositeSourceOver fraction: 1.0];
[resizedImage unlockFocus];
Without the lock/unlock, this does not work, but I still don't understand exactly what is going on.
I see that the 2nd line of code has no mention of resizedImage so does that mean when I use lockFocus it makes sure any 'drawing' that happens takes place there? Could someone explain this better?
Drawing requires a 'graphics context'. You'll notice that, unlike Core Graphics, none of the AppKit drawing methods take a parameter that specifies where the drawing ends up. Instead, the destination is stored globally as [NSGraphicsContext currentContext]. All AppKit drawing methods affect this current context.
The main purpose of -lockFocus (on images and views alike) is to set up the graphics context so your drawing ends up going where you want it to.
From the docs for -[NSImage lockFocus]:
This method sets the current drawing context to the area of the offscreen window used to cache the receiver's contents.
So there exists an offscreen window which you draw on when you draw to the image. This image has a graphics context and lockFocus makes this context the current drawing context so that drawInRect:... uses it for its drawing. It's similar to +[NSGraphicsContext setCurrentContext].

Resources