Mac OS X: Drawing into an offscreen NSGraphicsContext using CGContextRef C functions has no effect. Why? - objective-c

Mac OS X 10.7.4
I am drawing into an offscreen graphics context created via +[NSGraphicsContext graphicsContextWithBitmapImageRep:].
When I draw into this graphics context using the NSBezierPath class, everything works as expected.
However, when I draw into this graphics context using the CGContextRef C functions, I see no results of my drawing. Nothing works.
For reasons I won't get into, I really need to draw using the CGContextRef functions (rather than the Cocoa NSBezierPath class).
My code sample is listed below. I am attempting to draw a simple "X". One stroke using NSBezierPath, one stroke using CGContextRef C functions. The first stroke works, the second does not. What am I doing wrong?
NSRect imgRect = NSMakeRect(0.0, 0.0, 100.0, 100.0);
NSSize imgSize = imgRect.size;
NSBitmapImageRep *offscreenRep = [[[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:NULL
pixelsWide:imgSize.width
pixelsHigh:imgSize.height
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSDeviceRGBColorSpace
bitmapFormat:NSAlphaFirstBitmapFormat
bytesPerRow:0
bitsPerPixel:0] autorelease];
// set offscreen context
NSGraphicsContext *g = [NSGraphicsContext graphicsContextWithBitmapImageRep:offscreenRep];
[NSGraphicsContext setCurrentContext:g];
NSImage *img = [[[NSImage alloc] initWithSize:imgSize] autorelease];
CGContextRef ctx = [g graphicsPort];
// lock and draw
[img lockFocus];
// draw first stroke with Cocoa. this works!
NSPoint p1 = NSMakePoint(NSMaxX(imgRect), NSMinY(imgRect));
NSPoint p2 = NSMakePoint(NSMinX(imgRect), NSMaxY(imgRect));
[NSBezierPath strokeLineFromPoint:p1 toPoint:p2];
// draw second stroke with Core Graphics. This doesn't work!
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, 0.0, 0.0);
CGContextAddLineToPoint(ctx, imgSize.width, imgSize.height);
CGContextClosePath(ctx);
CGContextStrokePath(ctx);
[img unlockFocus];

You don't specify how you are looking at the results. I assume you are looking at the NSImage img and not the NSBitmapImageRep offscreenRep.
When you call [img lockFocus], you are changing the current NSGraphicsContext to be a context to draw into img. So, the NSBezierPath drawing goes into img and that's what you see. The CG drawing goes into offscreenRep which you aren't looking at.
Instead of locking focus onto an NSImage and drawing into it, create an NSImage and add the offscreenRep as one of its reps.
NSRect imgRect = NSMakeRect(0.0, 0.0, 100.0, 100.0);
NSSize imgSize = imgRect.size;
NSBitmapImageRep *offscreenRep = [[[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:NULL
pixelsWide:imgSize.width
pixelsHigh:imgSize.height
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSDeviceRGBColorSpace
bitmapFormat:NSAlphaFirstBitmapFormat
bytesPerRow:0
bitsPerPixel:0] autorelease];
// set offscreen context
NSGraphicsContext *g = [NSGraphicsContext graphicsContextWithBitmapImageRep:offscreenRep];
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:g];
// draw first stroke with Cocoa
NSPoint p1 = NSMakePoint(NSMaxX(imgRect), NSMinY(imgRect));
NSPoint p2 = NSMakePoint(NSMinX(imgRect), NSMaxY(imgRect));
[NSBezierPath strokeLineFromPoint:p1 toPoint:p2];
// draw second stroke with Core Graphics
CGContextRef ctx = [g graphicsPort];
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, 0.0, 0.0);
CGContextAddLineToPoint(ctx, imgSize.width, imgSize.height);
CGContextClosePath(ctx);
CGContextStrokePath(ctx);
// done drawing, so set the current context back to what it was
[NSGraphicsContext restoreGraphicsState];
// create an NSImage and add the rep to it
NSImage *img = [[[NSImage alloc] initWithSize:imgSize] autorelease];
[img addRepresentation:offscreenRep];
// then go on to save or view the NSImage

The solution by #Robin Stewart worked well for me. I was able to condense it to an NSImage extension.
extension NSImage {
convenience init(size: CGSize, actions: (CGContext) -> Void) {
self.init(size: size)
lockFocusFlipped(false)
actions(NSGraphicsContext.current!.cgContext)
unlockFocus()
}
}
Usage:
let image = NSImage(size: CGSize(width: 100, height: 100), actions: { ctx in
// Drawing commands here for example:
// ctx.setFillColor(.white)
// ctx.fill(pageRect)
})

I wonder why everyone writes such complicated code for drawing to an image. Unless you care for the exact bitmap representation of an image (and usually you don't!), there is no need to create one. You can just create a blank image and directly draw to it. In that case the system will create an appropriate bitmap representation (or maybe a PDF representation or whatever the system believes to be more suitable for drawing).
The documentation of the init method
- (instancetype)initWithSize:(NSSize)aSize
which exists since MacOS 10.0 and still isn't deprecated, clearly says:
After using this method to initialize an image object, you are
expected to provide the image contents before trying to draw the
image. You might lock focus on the image and draw to the image or you
might explicitly add an image representation that you created.
So here's how I would have written that code:
NSRect imgRect = NSMakeRect(0.0, 0.0, 100.0, 100.0);
NSImage * image = [[NSImage alloc] initWithSize:imgRect.size];
[image lockFocus];
// draw first stroke with Cocoa
NSPoint p1 = NSMakePoint(NSMaxX(imgRect), NSMinY(imgRect));
NSPoint p2 = NSMakePoint(NSMinX(imgRect), NSMaxY(imgRect));
[NSBezierPath strokeLineFromPoint:p1 toPoint:p2];
// draw second stroke with Core Graphics
CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort];
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, 0.0, 0.0);
CGContextAddLineToPoint(ctx, imgRect.size.width, imgRect.size.height);
CGContextClosePath(ctx);
CGContextStrokePath(ctx);
[image unlockFocus];
That's all folks.
graphicsPort is actually void *:
#property (readonly) void * graphicsPort
and documented as
The low-level, platform-specific graphics context represented
by the graphic port.
Which may be pretty much everything, but the final note says
In OS X, this is the Core Graphics context,
a CGContextRef object (opaque type).
This property has been deprecated in 10.10 in favor of the new property
#property (readonly) CGContextRef CGContext
which is only available in 10.10 and later. If you have to support older systems, it's fine to still use graphicsPort.

Swift 4: I use this code, which replicates the convenient API from UIKit (but runs on macOS):
public class UIGraphicsImageRenderer {
let size: CGSize
init(size: CGSize) {
self.size = size
}
func image(actions: (CGContext) -> Void) -> NSImage {
let image = NSImage(size: size)
image.lockFocusFlipped(true)
actions(NSGraphicsContext.current!.cgContext)
image.unlockFocus()
return image
}
}
Usage:
let renderer = UIGraphicsImageRenderer(size: imageSize)
let image = renderer.image { ctx in
// Drawing commands here
}

Here are 3 ways of drawing same image (Swift 4).
The method suggested by #Mecki produces an image without blurring artefacts (like blurred curves). But this can be fixed by adjusting CGContext settings (not included in this example).
public struct ImageFactory {
public static func image(size: CGSize, fillColor: NSColor, rounded: Bool = false) -> NSImage? {
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
return drawImage(size: size) { context in
if rounded {
let radius = min(size.height, size.width)
let path = NSBezierPath(roundedRect: rect, xRadius: 0.5 * radius, yRadius: 0.5 * radius).cgPath
context.addPath(path)
context.clip()
}
context.setFillColor(fillColor.cgColor)
context.fill(rect)
}
}
}
extension ImageFactory {
private static func drawImage(size: CGSize, drawingCalls: (CGContext) -> Void) -> NSImage? {
return drawImageInLockedImageContext(size: size, drawingCalls: drawingCalls)
}
private static func drawImageInLockedImageContext(size: CGSize, drawingCalls: (CGContext) -> Void) -> NSImage? {
let image = NSImage(size: size)
image.lockFocus()
guard let context = NSGraphicsContext.current else {
image.unlockFocus()
return nil
}
drawingCalls(context.cgContext)
image.unlockFocus()
return image
}
// Has scalling or antialiasing issues, like blurred curves.
private static func drawImageInBitmapImageContext(size: CGSize, drawingCalls: (CGContext) -> Void) -> NSImage? {
guard let offscreenRep = NSBitmapImageRep(pixelsWide: Int(size.width), pixelsHigh: Int(size.height),
bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true,
isPlanar: false, colorSpaceName: .deviceRGB) else {
return nil
}
guard let context = NSGraphicsContext(bitmapImageRep: offscreenRep) else {
return nil
}
NSGraphicsContext.saveGraphicsState()
NSGraphicsContext.current = context
drawingCalls(context.cgContext)
NSGraphicsContext.restoreGraphicsState()
let img = NSImage(size: size)
img.addRepresentation(offscreenRep)
return img
}
// Has scalling or antialiasing issues, like blurred curves.
private static func drawImageInCGContext(size: CGSize, drawingCalls: (CGContext) -> Void) -> NSImage? {
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
guard let context = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: 8,
bytesPerRow: 0, space: colorSpace, bitmapInfo: bitmapInfo.rawValue) else {
return nil
}
drawingCalls(context)
guard let image = context.makeImage() else {
return nil
}
return NSImage(cgImage: image, size: size)
}
}

Related

UIImage from SKTexture

How to get UIImage from SKTexture?
I tried to get UIImage from SKTextureAtlas, but it seems not working too:
// p40_prop1 is a part of SKTextureAtlas
UIImage *image = [UIImage imageNamed:#"p40_prop1"];
image is nil.
Starting from iOS 9 it is a piece of cake. SKTexture now has CGImage property, which is of CGImageRef type. So getting image from a texture is just one line now:
let image : UIImage = UIImage(CGImage:texture.CGImage)
This seems to be working for me:
- (UIImage*) imageWithView:(UIView *)view
{
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0);
[view drawViewHierarchyInRect:view.bounds afterScreenUpdates:YES];
UIImage * img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
- (UIImage*) imageFromNode:(SKNode*)node
{
SKTexture* tex = [self.scene.view textureFromNode:node];
SKView* view = [[SKView alloc]initWithFrame:CGRectMake(0, 0, tex.size.width, tex.size.height)];
SKScene* scene = [SKScene sceneWithSize:tex.size];
SKSpriteNode* sprite = [SKSpriteNode spriteNodeWithTexture:tex];
sprite.position = CGPointMake( CGRectGetMidX(view.frame), CGRectGetMidY(view.frame) );
[scene addChild:sprite];
[view presentScene:scene];
return [self imageWithView:view];
}
get the SKTexture for your node using the current SKView
make another SKView that is just big enough for your texture
add a SKSpriteNode with the texture into your new scene, placing it in the middle
render the view into a graphics context
Or for those who prefer Swift:
func imageWithView(view : UIView) -> UIImage {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0)
view.drawViewHierarchyInRect(view.bounds, afterScreenUpdates: true)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img
}
func imageFromNode(node : SKNode) -> UIImage? {
if let tex = self.scene?.view?.textureFromNode(node) {
let view = SKView(frame:CGRectMake(0, 0, tex.size().width, tex.size().height))
let scene = SKScene(size: tex.size())
let sprite = SKSpriteNode(texture: tex)
sprite.position = CGPoint(x: CGRectGetMidX(view.frame), y: CGRectGetMidY(view.frame))
scene.addChild(sprite)
view.presentScene(scene)
return self.imageWithView(view)
}
return nil
}
There is actually a way to get a UIImage out of a SKView in iOS 7.0!
It uses regular UIView APIs to render the view into an ImageContext, then pulls a UIImage out of that. However, this solution is very limited in scope. It draws the SKView into a UIImage, then crops the resulting image to fit a given node's frame. So there must not be anything covering that node you want to snapshot. Also, both the view and scene must be visible on-screen (which is stricter than the usual -[SKView textureFromNode:] method). There may even be further restrictions that I haven't discovered.
Given all that, this procedure was still enough for what I needed, so I thought it was worth sharing.
+(UIImage *)imageFromNode:(SKNode *)node {
SKView *view = node.scene.view;
CGFloat scale = [UIScreen mainScreen].scale;
CGRect nodeFrame = [node calculateAccumulatedFrame];
// render SKView into UIImage
UIGraphicsBeginImageContextWithOptions(view.bounds.size, YES, 0.0);
[view drawViewHierarchyInRect:view.bounds afterScreenUpdates:YES];
UIImage *sceneSnapshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// crop to the requested node (making sure to flip the y-coordinate)
CGFloat originY = sceneSnapshot.size.height*scale - nodeFrame.origin.y*scale - nodeFrame.size.height*scale;
CGRect cropRect = CGRectMake(nodeFrame.origin.x * scale, originY, nodeFrame.size.width*scale, nodeFrame.size.height*scale);
CGImageRef croppedSnapshot = CGImageCreateWithImageInRect(sceneSnapshot.CGImage, cropRect);
UIImage *nodeSnapshot = [UIImage imageWithCGImage:croppedSnapshot];
CGImageRelease(croppedSnapshot);
return nodeSnapshot;
}
I've tested this on the simulator in 3.5" and 4" retina iPhones, retina and non-retina iPads. As for actual devices, it worked on iPhone 4S, iPhone 5S, and iPad 2, all running 7.0.4.
func loadBackground() {
guard let _ = childNode(withName: "background") as! SKSpriteNode? else {
let texture = SKTexture(image: UIImage(named: "stick.jpg")!)
let node = SKSpriteNode(texture: texture)
node.size = texture.size()
node.zPosition = StickHeroGameSceneZposition.backgroundZposition.rawValue
self.physicsWorld.gravity = CGVector(dx: 0, dy: gravity)
addChild(node)
return
}
}
As of iOS 7.0 there's no way to get a UIImage from SKTexture, SKTextureAtlas or the SKView.

How can I make an UIImage programmatically?

This isn't what you probably thought it was to begin with. I know how to use UIImage's, but I now need to know how to create a "blank" UIImage using:
CGRect screenRect = [self.view bounds];
Well, those dimensions. Anyway, I want to know how I can create a UIImage with those dimensions colored all white. No actual images here.
Is this even possible? I am sure it is, but maybe I am wrong.
Edit
This needs to be a "white" image. Not a blank one. :)
You need to use CoreGraphics, as follows.
CGSize size = CGSizeMake(desiredWidth, desiredHeight);
UIGraphicsBeginImageContextWithOptions(size, YES, 0);
[[UIColor whiteColor] setFill];
UIRectFill(CGRectMake(0, 0, size.width, size.height));
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
The code creates a new CoreGraphics image context with the options passed as parameters; size, opaqueness, and scale. By passing 0 for scale, iOS automatically chooses the appropriate value for the current device.
Then, the context fill colour is set to [UIColor whiteColor]. Immediately, the canvas is then actually filled with that color, by using UIRectFill() and passing a rectangle which fills the canvas.
A UIImage is then created of the current context, and the context is closed. Therefore, the image variable contains a UIImage of the desired size, filled white.
Swift version:
extension UIImage {
static func emptyImage(with size: CGSize) -> UIImage? {
UIGraphicsBeginImageContext(size)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
If you want to draw just an empty image, you could use UIKit UIImageBeginImageContextWithOptions: method.
UIGraphicsBeginImageContext(CGSizeMake(width, height));
CGContextAddRect(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, width, height)); // this may not be necessary
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
The code above assumes that you draw a image with a size of width x height. It adds rectangle into the graphics context, but it may not be necessary. Try it yourself. This is the way to go. :)
Or, if you want to create a snapshot of your current view you would type code like;
UIGraphicsBeginImageContext(CGSizeMake(self.view.size.width, self.view.size.height));
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Don't forget to include Quartz library if you use layer.
Based on #HixField and #Rudolf Adamkovič answer. here's an extension which returns an optional, which I believe is the correct way to do this (correct me if I'm wrong!)?
This extension allows you to create a an empty UIImage of what ever size you need (up to memory limit) with what ever fill color you want, which defaults to white, if you want the image to be the clear color you would use something like the following:
let size = CGSize(width: 32.0, height: 32.0)
if var image = UIImage.imageWithSize(size:size, UIColor.clear) {
//image was successfully created, do additional stuff with it here.
}
This is for swift 3.x:
extension UIImage {
static func imageWithSize(size : CGSize, color : UIColor = UIColor.white) -> UIImage? {
var image:UIImage? = nil
UIGraphicsBeginImageContext(size)
if let context = UIGraphicsGetCurrentContext() {
context.setFillColor(color.cgColor)
context.addRect(CGRect(origin: CGPoint.zero, size: size));
context.drawPath(using: .fill)
image = UIGraphicsGetImageFromCurrentImageContext();
}
UIGraphicsEndImageContext()
return image
}
}
Using the latest UIGraphics classes, and in swift, this looks like this (note the CGContextDrawPath that is missing in the answer from user1834305, this is the reason that is produces an transparant image) :
static func imageWithSize(size : CGSize, color : UIColor) -> UIImage {
UIGraphicsBeginImageContext(size)
let context = UIGraphicsGetCurrentContext()
CGContextSetFillColorWithColor(context, color.CGColor)
CGContextAddRect(context, CGRect(x: 0, y: 0, width: size.width, height: size.height));
CGContextDrawPath(context, .Fill)
let image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image
}
Same solution in C# for Xamarin.iOS :
UIGraphics.BeginImageContext(new CGSize(width, height));
var image = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();

Xcode Screenshot EAGLContext [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How to get UIImage from EAGLView?
So I was just wondering if anybody knows any way to save what is stored in an EAGLContext as a UIImage.
I am currently using:
UIGraphicsBeginImageContext(CGSizeMake(768, 1024));
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
in other apps I have and this works fine, but obviously, EAGLContext doesn't have a .layer property. I've tried casting to UIView, but that - unsurprisingly - doesn't work:
UIView *newView = [[UIView alloc] init];
newView = (UIView *)context;
I am drawing to an EAGLContext property on a UIView (technically an EAGLContext on a UIView on another UIView on a View Controller, but I figure that shouldn't make any difference) using OpenGLES 1.
If anybody knows anything about this, even if its just that I'm completely barking up an impossible tree, please let me know!
Matt
After a few days I finally got a working solution to this. There is code provided by Apple which produces an UIImage from an EAGLView. Then you simply need to flip the image vertically since UIKit is upside down. The link to the documentation where I found this method doesn't exist anymore.
Method to capture EAGLView:
-(UIImage *)drawableToCGImage
{
GLint backingWidth2, backingHeight2;
//Bind the color renderbuffer used to render the OpenGL ES view
// If your application only creates a single color renderbuffer which is already bound at this point,
// this call is redundant, but it is needed if you're dealing with multiple renderbuffers.
// Note, replace "_colorRenderbuffer" with the actual name of the renderbuffer object defined in your class.
glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
// Get the size of the backing CAEAGLLayer
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth2);
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight2);
NSInteger x = 0, y = 0, width2 = backingWidth2, height2 = backingHeight2;
NSInteger dataLength = width2 * height2 * 4;
GLubyte *data = (GLubyte*)malloc(dataLength * sizeof(GLubyte));
// Read pixel data from the framebuffer
glPixelStorei(GL_PACK_ALIGNMENT, 4);
glReadPixels(x, y, width2, height2, GL_RGBA, GL_UNSIGNED_BYTE, data);
// Create a CGImage with the pixel data
// If your OpenGL ES content is opaque, use kCGImageAlphaNoneSkipLast to ignore the alpha channel
// otherwise, use kCGImageAlphaPremultipliedLast
CGDataProviderRef ref = CGDataProviderCreateWithData(NULL, data, dataLength, NULL);
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGImageRef iref = CGImageCreate(width2, height2, 8, 32, width2 * 4, colorspace, kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast,
ref, NULL, true, kCGRenderingIntentDefault);
// OpenGL ES measures data in PIXELS
// Create a graphics context with the target size measured in POINTS
NSInteger widthInPoints, heightInPoints;
if (NULL != UIGraphicsBeginImageContextWithOptions) {
// On iOS 4 and later, use UIGraphicsBeginImageContextWithOptions to take the scale into consideration
// Set the scale parameter to your OpenGL ES view's contentScaleFactor
// so that you get a high-resolution snapshot when its value is greater than 1.0
CGFloat scale = self.contentScaleFactor;
widthInPoints = width2 / scale;
heightInPoints = height2 / scale;
UIGraphicsBeginImageContextWithOptions(CGSizeMake(widthInPoints, heightInPoints), NO, scale);
}
else {
// On iOS prior to 4, fall back to use UIGraphicsBeginImageContext
widthInPoints = width2;
heightInPoints = height2;
UIGraphicsBeginImageContext(CGSizeMake(widthInPoints, heightInPoints));
}
CGContextRef cgcontext = UIGraphicsGetCurrentContext();
// UIKit coordinate system is upside down to GL/Quartz coordinate system
// Flip the CGImage by rendering it to the flipped bitmap context
// The size of the destination area is measured in POINTS
CGContextSetBlendMode(cgcontext, kCGBlendModeCopy);
CGContextDrawImage(cgcontext, CGRectMake(0.0, 0.0, widthInPoints, heightInPoints), iref);
// Retrieve the UIImage from the current context
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// Clean up
free(data);
CFRelease(ref);
CFRelease(colorspace);
CGImageRelease(iref);
return image;
}
Method to flip the image vertically:
- (UIImage *)flipImageVertically:(UIImage *)originalImage
{
UIImageView *tempImageView = [[UIImageView alloc] initWithImage:originalImage];
UIGraphicsBeginImageContext(tempImageView.frame.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGAffineTransform flipVertical = CGAffineTransformMake(
1, 0, 0, -1, 0, tempImageView.frame.size.height
);
CGContextConcatCTM(context, flipVertical);
[tempImageView.layer renderInContext:context];
UIImage *flippedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//[tempImageView release];
return flippedImage;
}

Cocoa Touch - Adding texture with overlay view

I have a set of tiles as UIViews that have a programmable background color, and each one
can be a different color. I want to add texture, like a side-lit bevel, to each one. Can this be done with an overlay view or by some other method?
I'm looking for suggestions that don't require a custom image file for each case.
This may help someone, although this was pieced together from other topics on SO.
To create a beveled tile image with an arbitrary color for normal and for retina display, I made a beveled image in photoshop and set the saturation to zero, making a grayscale image called tileBevel.png
I also created one for the retina display (tileBevel#2x.png)
Here is the code:
+ (UIImage*) createTileWithColor:(UIColor*)tileColor {
int pixelsHigh = 44;
int pixelsWide = 46;
UIImage *bottomImage;
if([UIScreen respondsToSelector:#selector(scale)] && [[UIScreen mainScreen] scale] == 2.0) {
pixelsHigh *= 2;
pixelsWide *= 2;
bottomImage = [UIImage imageNamed:#"tileBevel#2x.png"];
}
else {
bottomImage = [UIImage imageNamed:#"tileBevel.png"];
}
CGImageRef theCGImage = NULL;
CGContextRef tileBitmapContext = NULL;
CGRect rectangle = CGRectMake(0,0,pixelsWide,pixelsHigh);
UIGraphicsBeginImageContext(rectangle.size);
[bottomImage drawInRect:rectangle];
tileBitmapContext = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(tileBitmapContext, kCGBlendModeOverlay);
CGContextSetFillColorWithColor(tileBitmapContext, tileColor.CGColor);
CGContextFillRect(tileBitmapContext, rectangle);
theCGImage=CGBitmapContextCreateImage(tileBitmapContext);
UIGraphicsEndImageContext();
return [UIImage imageWithCGImage:theCGImage];
}
This checks to see if the retina display is used, sizes the rectangle to draw in, picks the appropriate grayscale base image, set the blending mode to overlay, then draws a rectangle on top of the bottom image. All of this is done inside a graphics context bracketed by the BeginImageContext and EndImageContext calls. These set the current context needed by the UIImage drawRect: method. The Core Graphics functions need the context as a parameter, which is obtained by a call to get the current context.
And the result looks like this:
If you want to preserve the alpha channel of the source image, just add this to jim's code before the fill rect:
// Apply mask
CGContextTranslateCTM(tileBitmapContext, 0, rectangle.size.height);
CGContextScaleCTM(tileBitmapContext, 1.0f, -1.0f);
CGContextClipToMask(tileBitmapContext, rectangle, bottomImage.CGImage);
Swift 3 solution, essentially based on Jim's answer with Scriptease's addition, and some minor changes:
class func image(bottomImage: UIImage, topImage: UIImage, tileColor: UIColor) -> UIImage? {
let pixelsHigh: CGFloat = bottomImage.size.height
let pixelsWide: CGFloat = bottomImage.size.width
let rectangle = CGRect.init(x: 0, y: 0, width: pixelsWide, height: pixelsHigh)
UIGraphicsBeginImageContext(rectangle.size);
bottomImage.draw(in: rectangle)
if let tileBitmapContext = UIGraphicsGetCurrentContext() {
tileBitmapContext.setBlendMode(.overlay)
tileBitmapContext.setFillColor(tileColor.cgColor)
tileBitmapContext.scaleBy(x: 1.0, y: -1.0)
tileBitmapContext.clip(to: rectangle, mask: bottomImage.cgImage!)
tileBitmapContext.fill(rectangle)
let theCGImage = tileBitmapContext.makeImage()
UIGraphicsEndImageContext();
if let theImage = theCGImage {
return UIImage.init(cgImage: theImage)
}
}
return nil
}

How to draw a NSImage like images in NSButtons (with a deepness)?

Is there any way to draw an NSImage like images in NSButtons or other cocoa interface elements?
Here are examples:
Apple uses pdf's with black icons:
If you simply want this effect to be applied when you use your own images in a button, use [myImage setTemplate:YES]. There is no built-in way to draw images with this effect outside of a button that has the style shown in your screenshots.
You can however replicate the effect using Core Graphics. If you look closely, the effect consists of a horizontal gradient, a white drop shadow and a dark inner shadow (the latter is the most difficult).
You could implement this as a category on 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
Example usage in a view:
- (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];
}
This would give you the following result (shown in different sizes):
You may need to experiment a bit with the gradient colors and offset/blur radius of the two shadows to get closer to the original effect.
If you don't mind calling a private API, you can let the operating system (CoreUI) do the shading for you. You need a few declarations:
typedef CFTypeRef CUIRendererRef;
extern void CUIDraw(CUIRendererRef renderer, CGRect frame, CGContextRef context, CFDictionaryRef object, CFDictionaryRef *result);
#interface NSWindow(CoreUIRendererPrivate)
+ (CUIRendererRef)coreUIRenderer;
#end
And for the actual drawing:
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);
This will take the alpha channel of cgimage and apply the embossing effect as seen on toolbar buttons. You may or may not need the "is.flipped" line. Remove it if your result is upside-down.
There are a bunch of variations:
kCUIPresentationStateKey = kCUIPresentationStateInactive: The window is not active, the image will be lighter.
state = rollover: Only makes sense with the previous option. This means you are hovering over the image, the window is inactive, but the button is sensitive (click-through is enabled). It will become darker.
state = pressed: Occurs when the button is pressed. The icon gets slightly darker.
Bonus tip: To find out stuff like this, you can use the SIMBL plugin CUITrace. It prints out all the CoreUI invocations of a target app. This is a treasure trove if you have to draw your own native-looking UI.
Here's a much simpler solution: just create a cell and let it draw. No mucking around with private APIs or Core Graphics.
Code could look similar to the following:
NSButtonCell *buttonCell = [[NSButtonCell alloc] initImageCell:image];
buttonCell.bordered = YES;
buttonCell.bezelStyle = NSTexturedRoundedBezelStyle;
// additional configuration
[buttonCell drawInteriorWithFrame: someRect inView:self];
You can use different cells and configurations depending on the look you want to have (eg. NSImageCell with NSBackgroundStyleDark if you want the inverted look in a selected table view row)
And as a bonus, it will automatically look correct on all versions of OS X.
To get to draw correctly within any rect, the CGContextDrawImage and CGContextFillRect for the inner mask must have the origin of (0,0). then when you draw the image for the inner shadow you can then reuse the mask rect. So ends up looking like:
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 );
You also have to leave a 1px border around the outside of the image or the shadows won't work correctly.