I'm trying to take an NSGradient and save it as an image in RubyMotion, but I can't get it to work. This is the code I have so far:
gradient = NSGradient.alloc.initWithColors(colors,
atLocations: locations.to_pointer(:double),
colorSpace: NSColorSpace.genericRGBColorSpace
)
size = Size(width, height)
image = NSImage.imageWithSize(size, flipped: false, drawingHandler: lambda do |rect|
gradient.drawInRect(rect, angle: angle)
true
end)
data = image.TIFFRepresentation
data.writeToFile('output.tif', atomically: false)
It runs without error, but the file that is saved is blank and there is no image data. Can anyone help point me in the right direction?
I don’t know about RubyMotion, but here’s how to do it in Objective-C:
NSGradient *grad = [[NSGradient alloc] initWithStartingColor:[NSColor redColor]
endingColor:[NSColor blueColor]];
NSRect rect = CGRectMake(0.0, 0.0, 50.0, 50.0);
NSImage *image = [[NSImage alloc] initWithSize:rect.size];
NSBezierPath *path = [NSBezierPath bezierPathWithRect:rect];
[image lockFocus];
[grad drawInBezierPath:path angle:0.0];
NSBitmapImageRep *imgRep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:rect];
NSData *data = [imgRep representationUsingType:NSPNGFileType properties:nil];
[image unlockFocus];
[data writeToFile: #"/path/to/file.png" atomically:NO];
In case you want to know how it works in Swift 5:
extension NSImage {
convenience init?(gradientColors: [NSColor], imageSize: NSSize) {
guard let gradient = NSGradient(colors: gradientColors) else { return nil }
let rect = NSRect(origin: CGPoint.zero, size: imageSize)
self.init(size: rect.size)
let path = NSBezierPath(rect: rect)
self.lockFocus()
gradient.draw(in: path, angle: 0.0)
self.unlockFocus()
}
}
Related
On a macOS (OSX) desktop app, I use NSBezierPath to draw a random closed shape which looks like this.
As you can see the dashed line shows the closed path which was drawn.
Now am trying to extract the masked image as per this path.
But i get a small portion of the image. The masked image seems to correctly get the outline from the BezierPath. But size is an issue.
This is the method which returns the masked/clipped image.
The sourceImage.size for this drawing - 1021 * 1031
- (NSImage *)imageByApplyingClippingBezierPath:(NSImage *)sourceImage
bezierPath:(NSBezierPath *)bezierPath
newFrame:(NSRect)newFrame
{
NSImage* newImage = [[NSImage alloc] initWithSize:newFrame.size];
NSBitmapImageRep* rep = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:NULL
pixelsWide:newFrame.size.width
pixelsHigh:newFrame.size.height
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSCalibratedRGBColorSpace
bytesPerRow:0
bitsPerPixel:0];
[newImage addRepresentation:rep];
[newImage lockFocus];
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
CGContextSaveGState(context);
[bezierPath addClip];
NSRect targetFrame = NSMakeRect(0, 0, newFrame.size.width, newFrame.size.height);
[sourceImage drawInRect:targetFrame];
[newImage unlockFocus];
CGContextRestoreGState(context);
return newImage;
}
How can i get perfectly sized image outlined by the BezierPath?
Any tips would be appreciated!
UPDATE
Just clarifying, how i draw the image.
I get rectangle bounds of the bezier path and rectangular image cropped like this.
CGRect bezierBounds = CGPathGetPathBoundingBox([self.smartLassoWavyBezierPath quartzPath]);
NSRect targetFrame = NSMakeRect(0, 0, bezierBounds.size.width, bezierBounds.size.height);
NSImage *targetImage = [[NSImage alloc initWithSize:targetFrame.size];
[targetImage lockFocus];
[self.view.originalLoadImageOnCanvas
drawInRect:targetFrame fromRect:bezierBounds operation:NSCompositeCopy
fraction:1.0f];
[targetImage unlockFocus];
For Swift 5;
func imageByApplyingClippingBezierPath(source: NSImage, path: NSBezierPath, frame: NSRect) -> NSImage? {
let new = NSImage(size: frame.size)
guard let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(frame.size.width), pixelsHigh: Int(frame.size.height), bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: NSColorSpaceName.calibratedRGB, bitmapFormat: .alphaFirst, bytesPerRow: 0, bitsPerPixel: 0) else { return nil}
new.addRepresentation(rep)
new.lockFocus()
let context = NSGraphicsContext.current?.cgContext
context?.saveGState()
path.addClip()
let targetFrame = NSRect(x: 0, y: 0, width: Int(frame.size.width), height: Int(frame.size.height))
source.draw(in: targetFrame)
new.unlockFocus()
context?.restoreGState()
return new
}
I'm doing some operations on images and after I'm done, I want to save the image as PNG on disk. I'm doing the following:
+ (void)saveImage:(NSImage *)image atPath:(NSString *)path {
[image lockFocus] ;
NSBitmapImageRep *imageRepresentation = [[NSBitmapImageRep alloc] initWithFocusedViewRect:NSMakeRect(0.0, 0.0, image.size.width, image.size.height)] ;
[image unlockFocus] ;
NSData *data = [imageRepresentation representationUsingType:NSPNGFileType properties:nil];
[data writeToFile:path atomically:YES];
}
This code is working, but the problem is with retina mac, if I print the NSBitmapImageRep object I get a different size and pixels rect and when my image is saved on disk, it's twice the size:
$0 = 0x0000000100413890 NSBitmapImageRep 0x100413890 Size={300, 300} ColorSpace=sRGB IEC61966-2.1 colorspace BPS=8 BPP=32 Pixels=600x600 Alpha=YES Planar=NO Format=0 CurrentBacking=<CGImageRef: 0x100414830>
I tied to force the pixel size to not take care about the retina scale, as I want to preserve the original size:
imageRepresentation.pixelsWide = image.size.width;
imageRepresentation.pixelsHigh = image.size.height;
This time I get the right size when I print the NSBitmapImageRep object, but when I save my file I still get the same issue:
$0 = 0x0000000100413890 NSBitmapImageRep 0x100413890 Size={300, 300} ColorSpace=sRGB IEC61966-2.1 colorspace BPS=8 BPP=32 Pixels=300x300 Alpha=YES Planar=NO Format=0 CurrentBacking=<CGImageRef: 0x100414830>
Any idea how to fix this, and preserve the original pixel size?
If you have an NSImage and want to save it as an image file to the filesystem, you should never use lockFocus! lockFocus creates a new image which is determined for getting shown an the screen and nothing else. Therefore lockFocus uses the properties of the screen: 72 dpi for normal screens and 144 dpi for retina screens. For what you want I propose the following code:
+ (void)saveImage:(NSImage *)image atPath:(NSString *)path {
CGImageRef cgRef = [image CGImageForProposedRect:NULL
context:nil
hints:nil];
NSBitmapImageRep *newRep = [[NSBitmapImageRep alloc] initWithCGImage:cgRef];
[newRep setSize:[image size]]; // if you want the same resolution
NSData *pngData = [newRep representationUsingType:NSPNGFileType properties:nil];
[pngData writeToFile:path atomically:YES];
[newRep autorelease];
}
NSImage is resolution aware and uses a HiDPI graphics context when you lockFocus on a system with retina screen.
The image dimensions you pass to your NSBitmapImageRep initializer are in points (not pixels). An 150.0 point-wide image therefore uses 300 horizontal pixels in a #2x context.
You could use convertRectToBacking: or backingScaleFactor: to compensate for the #2x context. (I didn't try that), or you can use the following NSImage category, that creates a drawing context with explicit pixel dimensions:
#interface NSImage (SSWPNGAdditions)
- (BOOL)writePNGToURL:(NSURL*)URL outputSizeInPixels:(NSSize)outputSizePx error:(NSError*__autoreleasing*)error;
#end
#implementation NSImage (SSWPNGAdditions)
- (BOOL)writePNGToURL:(NSURL*)URL outputSizeInPixels:(NSSize)outputSizePx error:(NSError*__autoreleasing*)error
{
BOOL result = YES;
NSImage* scalingImage = [NSImage imageWithSize:[self size] flipped:NO drawingHandler:^BOOL(NSRect dstRect) {
[self drawAtPoint:NSMakePoint(0.0, 0.0) fromRect:dstRect operation:NSCompositeSourceOver fraction:1.0];
return YES;
}];
NSRect proposedRect = NSMakeRect(0.0, 0.0, outputSizePx.width, outputSizePx.height);
CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
CGContextRef cgContext = CGBitmapContextCreate(NULL, proposedRect.size.width, proposedRect.size.height, 8, 4*proposedRect.size.width, colorSpace, kCGBitmapByteOrderDefault|kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colorSpace);
NSGraphicsContext* context = [NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:NO];
CGContextRelease(cgContext);
CGImageRef cgImage = [scalingImage CGImageForProposedRect:&proposedRect context:context hints:nil];
CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef)(URL), kUTTypePNG, 1, NULL);
CGImageDestinationAddImage(destination, cgImage, nil);
if(!CGImageDestinationFinalize(destination))
{
NSDictionary* details = #{NSLocalizedDescriptionKey:#"Error writing PNG image"};
[details setValue:#"ran out of money" forKey:NSLocalizedDescriptionKey];
*error = [NSError errorWithDomain:#"SSWPNGAdditionsErrorDomain" code:10 userInfo:details];
result = NO;
}
CFRelease(destination);
return result;
}
#end
I found this code on web , and it works on retina. Paste here, hope can help someone.
NSImage *computerImage = [NSImage imageNamed:NSImageNameComputer];
NSInteger size = 256;
NSBitmapImageRep *rep = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:NULL
pixelsWide:size
pixelsHigh:size
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSCalibratedRGBColorSpace
bytesPerRow:0
bitsPerPixel:0];
[rep setSize:NSMakeSize(size, size)];
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:rep]];
[computerImage drawInRect:NSMakeRect(0, 0, size, size) fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0];
[NSGraphicsContext restoreGraphicsState];
NSData *data = [rep representationUsingType:NSPNGFileType properties:nil];
Just incase anyone stumbles up on this thread. Here is certainly flawed solution that does the job of saving image at 1x size (image.size) regardless of device in swift
public func writeToFile(path: String, atomically: Bool = true) -> Bool{
let bitmap = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(self.size.width), pixelsHigh: Int(self.size.height), bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: NSDeviceRGBColorSpace, bytesPerRow: 0, bitsPerPixel: 0)!
bitmap.size = self.size
NSGraphicsContext.saveGraphicsState()
NSGraphicsContext.setCurrentContext(NSGraphicsContext(bitmapImageRep: bitmap))
self.drawAtPoint(CGPoint.zero, fromRect: NSRect.zero, operation: NSCompositingOperation.CompositeSourceOver, fraction: 1.0)
NSGraphicsContext.restoreGraphicsState()
if let imagePGNData = bitmap.representationUsingType(NSBitmapImageFileType.NSPNGFileType, properties: [NSImageCompressionFactor: 1.0]) {
return imagePGNData.writeToFile((path as NSString).stringByStandardizingPath, atomically: atomically)
} else {
return false
}
}
Here's a Swift 5 version based on Heinrich Giesen's answer:
static func saveImage(_ image: NSImage, atUrl url: URL) {
guard
let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil)
else { return } // TODO: handle error
let newRep = NSBitmapImageRep(cgImage: cgImage)
newRep.size = image.size // if you want the same size
guard
let pngData = newRep.representation(using: .png, properties: [:])
else { return } // TODO: handle error
do {
try pngData.write(to: url)
}
catch {
print("error saving: \(error)")
}
}
My 2 cents for OS X including write that handles extensions + offscreen image drawing (method 2); one can verify with NSGraphicsContext.currentContextDrawingToScreen()
func createCGImage() -> CGImage? {
//method 1
let image = NSImage(size: NSSize(width: bounds.width, height: bounds.height), flipped: true, drawingHandler: { rect in
self.drawRect(self.bounds)
return true
})
var rect = CGRectMake(0, 0, bounds.size.width, bounds.size.height)
return image.CGImageForProposedRect(&rect, context: bitmapContext(), hints: nil)
//method 2
if let pdfRep = NSPDFImageRep(data: dataWithPDFInsideRect(bounds)) {
return pdfRep.CGImageForProposedRect(&rect, context: bitmapContext(), hints: nil)
}
return nil
}
func PDFImageData(filter: QuartzFilter?) -> NSData? {
return dataWithPDFInsideRect(bounds)
}
func bitmapContext() -> NSGraphicsContext? {
var context : NSGraphicsContext? = nil
if let imageRep = NSBitmapImageRep(bitmapDataPlanes: nil,
pixelsWide: Int(bounds.size.width),
pixelsHigh: Int(bounds.size.height), bitsPerSample: 8,
samplesPerPixel: 4, hasAlpha: true, isPlanar: false,
colorSpaceName: NSCalibratedRGBColorSpace,
bytesPerRow: Int(bounds.size.width) * 4,
bitsPerPixel: 32) {
imageRep.size = NSSize(width: bounds.size.width, height: bounds.size.height)
context = NSGraphicsContext(bitmapImageRep: imageRep)
}
return context
}
func writeImageData(view: MyView, destination: NSURL) {
if let dest = CGImageDestinationCreateWithURL(destination, imageUTType, 1, nil) {
let properties = imageProperties
let image = view.createCGImage()!
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(queue) {
CGImageDestinationAddImage(dest, image, properties)
CGImageDestinationFinalize(dest)
}
}
}
I have a UIScrollView, and I need the bottom to fade to transparent, so that it does not abruptly cut off the content. The background of the UIScrollView is a custom color. This is what I have so far, but the layer is showing up white, instead of the custom color. This is what I am going for, but only on the bottom.
:
Here is what I have so far:
maskLayer = [CAGradientLayer layer];
CGColorRef firstColor = [UIColor colorWithRed:141 green:211 blue:244 alpha:1.0].CGColor;
CGColorRef secondColor = [UIColor colorWithRed:141 green:211 blue:244 alpha:0.8].CGColor;
CGColorRef thirdColor = [UIColor colorWithRed:141 green:211 blue:244 alpha:0.2].CGColor;
CGColorRef fourthColor = [UIColor colorWithRed:141 green:211 blue:244 alpha:0.0].CGColor;
maskLayer.colors = [NSArray arrayWithObjects:(__bridge id)firstColor, (__bridge id)secondColor, (__bridge id)thirdColor, (__bridge id)fourthColor, nil];
maskLayer.locations = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:0.2],
[NSNumber numberWithFloat:0.8],
[NSNumber numberWithFloat:1.0], nil];
maskLayer.frame = CGRectMake(0, 285, self.loginScrollView.frame.size.width, 40);
maskLayer.anchorPoint = CGPointZero;
[self.loginScrollView.layer addSublayer:maskLayer];
For some reason, the layer is showing up as white, and only half the width of the scrollView.
The approach I used was to subclass UIScrollView, and create the mask layer in the layoutSubviews method.
Here's my code, which fades the top and bottom of the UIScrollView from the background colour to transparent:
#import "FadingScrollView.h"
#import <QuartzCore/QuartzCore.h>
static float const fadePercentage = 0.2;
#implementation FadingScrollView
// ...
- (void)layoutSubviews
{
[super layoutSubviews];
NSObject * transparent = (NSObject *) [[UIColor colorWithWhite:0 alpha:0] CGColor];
NSObject * opaque = (NSObject *) [[UIColor colorWithWhite:0 alpha:1] CGColor];
CALayer * maskLayer = [CALayer layer];
maskLayer.frame = self.bounds;
CAGradientLayer * gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = CGRectMake(self.bounds.origin.x, 0,
self.bounds.size.width, self.bounds.size.height);
gradientLayer.colors = [NSArray arrayWithObjects: transparent, opaque,
opaque, transparent, nil];
// Set percentage of scrollview that fades at top & bottom
gradientLayer.locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0],
[NSNumber numberWithFloat:fadePercentage],
[NSNumber numberWithFloat:1.0 - fadePercentage],
[NSNumber numberWithFloat:1], nil];
[maskLayer addSublayer:gradientLayer];
self.layer.mask = maskLayer;
}
#end
If you just want to fade the bottom, change this line:
// Fade bottom of scrollview only
gradientLayer.locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0],
[NSNumber numberWithFloat:0],
[NSNumber numberWithFloat:1.0 - fadePercentage],
[NSNumber numberWithFloat:1], nil];
When I was implementing this myself, I found this SO question helpful, and this gist on github.
EDIT: I've put this code up on github, see here.
Really like Steph Sharp's implementation, I converted it to swift and thought I'd share in case anyone else needs it.
let fadePercentage = CGFloat(0.2)
override func layoutSubviews() {
super.layoutSubviews()
var transparent = UIColor.clearColor().CGColor
var opaque = UIColor.blackColor().CGColor
var maskLayer = CALayer()
maskLayer.frame = self.bounds
var gradientLayer = CAGradientLayer()
gradientLayer.frame = CGRectMake(self.bounds.origin.x, 0, self.bounds.size.width, self.bounds.size.height)
gradientLayer.colors = [transparent, opaque, opaque, transparent]
gradientLayer.locations = [0, fadePercentage, 1 - fadePercentage, 1]
maskLayer.addSublayer(gradientLayer)
self.layer.mask = maskLayer
}
Here goes the swift 3 version of Steph's answer:
let fadePercentage: Double = 0.2
override func layoutSubviews() {
super.layoutSubviews()
let transparent = UIColor.clear.cgColor
let opaque = UIColor.black.cgColor
let maskLayer = CALayer()
maskLayer.frame = self.bounds
let gradientLayer = CAGradientLayer()
gradientLayer.frame = CGRect(x: self.bounds.origin.x, y: 0, width: self.bounds.size.width, height: self.bounds.size.height)
gradientLayer.colors = [transparent, opaque, opaque, transparent]
gradientLayer.locations = [0, NSNumber(floatLiteral: fadePercentage), NSNumber(floatLiteral: 1 - fadePercentage), 1]
maskLayer.addSublayer(gradientLayer)
self.layer.mask = maskLayer
}
Theres also this answer which does not include any subclassing.
I'm in need of an UIImage from a Masked CALayer. This is the function I use:
- (UIImage *)imageFromLayer:(CALayer *)layer
{
UIGraphicsBeginImageContext([layer frame].size);
[layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return outputImage;
}
The problem is that the mask isn't maintained.
This is the completed code:
CAShapeLayer * layerRight= [CAShapeLayer layer];
layerRight.path = elasticoRight;
im2.layer.mask = layerRight;
CAShapeLayer * layerLeft= [CAShapeLayer layer];
layerLeft.path = elasticoLeft;
im3.layer.mask = layerLeft;
[viewImage.layer addSublayer:im2.layer];
[viewImage.layer addSublayer:im3.layer];
UIImage *image_result = [self imageFromLayer:viewImage.layer];
If I visualize the viewImage, the result is correct, but if I try to obtain the image relative to the layer, the masks are lost.
I've solved.
Now i obtaining the image mask and use CGContextClipToMask.
CGRect rect = CGRectMake(0, 0, 1024, 768);
UIGraphicsBeginImageContextWithOptions(rect.size, YES, 0.0);
{
[[UIColor blackColor] setFill];
UIRectFill(rect);
[[UIColor whiteColor] setFill];
UIBezierPath *leftPath = [UIBezierPath bezierPath];
// Set the starting point of the shape.
CGPoint p1 = [(NSValue *)[leftPoints objectAtIndex:0] CGPointValue];
[leftPath moveToPoint:CGPointMake(p1.x, p1.y)];
for (uint i=1; i<leftPoints.count; i++)
{
CGPoint p = [(NSValue *)[leftPoints objectAtIndex:i] CGPointValue];
[leftPath addLineToPoint:CGPointMake(p.x, p.y)];
}
[leftPath closePath];
[leftPath fill];
}
UIImage *mask = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0);
{
CGContextClipToMask(UIGraphicsGetCurrentContext(), rect, mask.CGImage);
[im_senza drawAtPoint:CGPointZero];
}
UIImage *maskedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
I have an NSImage which I am trying to resize like so;
NSImage *capturePreviewFill = [[NSImage alloc] initWithData:previewData];
NSSize newSize;
newSize.height = 160;
newSize.width = 120;
[capturePreviewFill setScalesWhenResized:YES];
[capturePreviewFill setSize:newSize];
NSData *resizedPreviewData = [capturePreviewFill TIFFRepresentation];
resizedCaptureImageBitmapRep = [[NSBitmapImageRep alloc] initWithData:resizedPreviewData];
saveData = [resizedCaptureImageBitmapRep representationUsingType:NSJPEGFileType properties:nil];
[saveData writeToFile:#"/Users/ricky/Desktop/Photo.jpg" atomically:YES];
My first issue is that my image gets squashed when I try to resize it and don't conform to the aspect ratio. I read that using -setScalesWhenResized would resolve this problem but it didn't.
My second issue is that when I try to write the image to a file, the image isn't actually resized at all.
Thanks in advance,
Ricky.
I found this blog post to be very helpful for resizing my image: http://weblog.scifihifi.com/2005/06/25/how-to-resize-an-nsimage/
You will need to enforce the aspect ratio on the image resizing yourself, it won't be done for you. This is how I did it when I was trying to fit the image into the printable area on the default paper:
NSImage *image = ... // get your image
NSPrintInfo *printInfo = [NSPrintInfo sharedPrintInfo];
NSSize paperSize = printInfo.paperSize;
CGFloat usablePaperWidth = paperSize.width - printInfo.leftMargin - printInfo.rightMargin;
CGFloat resizeWidth = usablePaperWidth;
CGFloat resizeHeight = usablePaperWidth * (image.size.height / image.size.width);
Here is a slightly modified version of his code from the blog:
NSData *sourceData = [image TIFFRepresentation];
float resizeWidth = ... // your desired width;
float resizeHeight = ... // your desired height;
NSImage *sourceImage = [[NSImage alloc] initWithData: sourceData];
NSImage *resizedImage = [[NSImage alloc] initWithSize: NSMakeSize(resizeWidth, resizeHeight)];
NSSize originalSize = [sourceImage size];
[resizedImage lockFocus];
[sourceImage drawInRect: NSMakeRect(0, 0, resizeWidth, resizeHeight) fromRect: NSMakeRect(0, 0, originalSize.width, originalSize.height) operation: NSCompositeSourceOver fraction: 1.0];
[resizedImage unlockFocus];
NSData *resizedData = [resizedImage TIFFRepresentation];
[sourceImage release];
[resizedImage release];
If you can require Mac OS X 10.6 or later, send your image a CGImageForProposedRect:context:hints: message, then write the CGImage out using a CGImageDestination object.
The rectangle should have NSZeroPoint as its origin, and its size be the size you want.
This still won't scale the image proportionally (maintaining aspect ratio); you have to do that yourself.
The pre-10.6 way to do this (without going through a TIFF representation) is to lock focus on the resized image, create an NSBitmapImageRep for the extent of the image (that is, a rectangle with zero origin and the image's size), unlock focus, and then ask that bitmap image rep for JPEG data.