OS X - How to save NSImage or NSBitmapImageRep to PNG file without alpha channel? - objective-c

I'm building an OS X app that needs to save the file to disk.
I'm currently using NSBitmapImageRep to represent the image in my code, and while saving the image to disk with representationUsingType:properties: method, I want to set the hasAlpha channel for the image, but the properties dictionary does not seem to support this.
So, I've tried to create a no-alpha bitmap representation, but according to many SO questions, the 3 channel/24 bits combination is not supported. Well, what should I do then?
Big thanks!

First off, I would try just making sure you create your NSBitmapImageRep with
-initWithBitmapDataPlanes:... hasAlpha:NO ...
And write it out and see if it the result doesn’t have alpha—one would kind of hope so.
If you’re trying to write out an image that has alpha, but not write the alpha, just copy it into a non-alpha image first, and write that out.

`
NSURL *url = [NSURL fileURLWithPath:name];
CGImageSourceRef source;
NSImage *srcImage =[[NSImage alloc] initWithContentsOfURL:url];;
NSLog(#"URL: %#",url);
source = CGImageSourceCreateWithData((__bridge CFDataRef)[srcImage TIFFRepresentation], NULL);
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0, NULL);
CGRect rect = CGRectMake(0.f, 0.f, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef));
CGContextRef bitmapContext = CGBitmapContextCreate(NULL,
rect.size.width,
rect.size.height,
CGImageGetBitsPerComponent(imageRef),
CGImageGetBytesPerRow(imageRef),
CGImageGetColorSpace(imageRef),
kCGImageAlphaNoneSkipLast | kCGBitmapByteOrder32Little
);
CGContextDrawImage(bitmapContext, rect, imageRef);
CGImageRef decompressedImageRef = CGBitmapContextCreateImage(bitmapContext);
NSImage *finalImage = [[NSImage alloc] initWithCGImage:decompressedImageRef size:NSZeroSize];
NSData *imageData = [finalImage TIFFRepresentation];
NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:imageData];
NSDictionary *imageProps = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:0.9] forKey:NSImageCompressionFactor];
imageData = [imageRep representationUsingType:NSPNGFileType properties:imageProps];
[imageData writeToFile:name atomically:NO];
CGImageRelease(decompressedImageRef);
CGContextRelease(bitmapContext);
`
ref:
https://github.com/bpolat/Alpha-Channel-Remover

Related

Using NSAffineTransform to rotate NSImage results in a file twice the size

I'm using the following code to flip (rotate by 180 degrees) an NSImage.
But the new image is twice the size (MBs, not dimensions) of the original when saved to disk. I want it to be approximately the same as the original. How can I accomplish this?
NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:[[_imageView image] TIFFRepresentation]];
NSImage *img = [[NSImage alloc] initWithSize:NSMakeSize(imageRep.pixelsWide, imageRep.pixelsHigh)];
[img lockFocus];
NSAffineTransform *rotator = [NSAffineTransform transform];
[rotator translateXBy:imageRep.pixelsWide yBy:imageRep.pixelsHigh];
[rotator scaleXBy:-1 yBy:-1];
[rotator concat];
[imageRep drawInRect:NSMakeRect(0, 0, imageRep.pixelsWide, imageRep.pixelsHigh)];
[img unlockFocus];
Code I'm using to save image to disk :
[[NSFileManager defaultManager] createFileAtPath:path contents:[img TIFFRepresentation] attributes:nil];
Thanks in advance!
I still don't know the root cause of this, but one work around is to save to the JPEG representation instead of the TIFF. The method I wrote is as follows :
- (void)CompressAndSaveImg:(NSImage *)img ToDiskAt:(NSString *)path WithCompressionFactor:(float)value{
NSData *imgData = [img TIFFRepresentation];
NSBitmapImageRep *imgRep = [NSBitmapImageRep imageRepWithData:imgData];
NSNumber *compressionFactor = [NSNumber numberWithFloat:value];
NSDictionary *imageProps = [NSDictionary dictionaryWithObject:compressionFactor forKey:NSImageCompressionFactor];
imgData = [imgRep representationUsingType:NSJPEGFileType properties:imageProps];
[imgData writeToFile:path atomically:YES];
}
You can play around the with the WithCompressionFactor:(float)value parameter, but 0.75 works fine for me.

Image size anomaly

I have an image in the form of an NSURL as input. I converted this url to NSImage and then to NSData from which I could get CGImageRef. This imageRef helped me extracting the raw data information from the image such as the height, width, bytesPerRow, etc.
Here's the code that I used:
NSString * urlName = [url path];
NSImage *image = [[NSImage alloc] initWithContentsOfFile:urlName];
NSData *imageData = [image TIFFRepresentation];
CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)CFBridgingRetain(imageData), NULL);
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0, NULL);
NSUInteger numberOfBitsPerPixel = CGImageGetBitsPerPixel(imageRef);
NSUInteger height = CGImageGetHeight(imageRef);
...
...
Now, I checked the size of the image using:
int sz = [imageData length];
which is different from - int sz' = bytesPerRow * height
I cannot understand why is there such a difference. And sz is actually half of sz'.
Am I making some mistake while extracting various info? From what I can get is that maybe while conversion of image to NSData some decompressions are done. In such a case, what should I use that can get me the reliable data.
I am new to the world image processing in Objective-C, so please bear with me!
P.S. I actually checked the size of the file that I am getting as input in the form of NSURL which is same as sz.
Try This:
Instead of
NSData *imageData = [image TIFFRepresentation];
use this:
NSData *imageData = [image TIFFRepresentationUsingCompression:NSTIFFCompressionLZW factor:0];

How do I set the pixels per inch for an exported JPEG image in a Cocoa app?

I have a Cocoa Mac image editing app which lets users export JPEG images. I'm currently using the following code to export these images as JPEG files:
//this is user specified
NSInteger resolution;
NSImage* savedImage = [[NSImage alloc] initWithSize:NSMakeSize(600, 600)];
[savedImage lockFocus];
//draw here
[savedImage unlockFocus];
NSBitmapImageRep* savedImageBitmapRep = [NSBitmapImageRep imageRepWithData:[savedImage TIFFRepresentationUsingCompression:NSTIFFCompressionNone factor:1.0]];
NSDictionary* properties = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithFloat:1.0], NSImageCompressionFactor, nil];
//holds the jpeg file
NSData * imageData = nil;
imageData = [savedImageBitmapRep representationUsingType:NSJPEGFileType properties:properties];
However, I would like for the user to be able to provide the pixels per inch for this JPEG image (like you can in Photoshop's export options). What would I need to modify in the above code to adjust this value for the exported JPEG?
I couldn't find a way to do it with the NSImage APIs but CGImage can by setting kCGImagePropertyDPIHeight/Width.
I also set kCGImageDestinationLossyCompressionQuality which I think is the same as NSImageCompressionFactor.
//this is user specified
NSInteger resolution = 100;
NSImage* savedImage = [[NSImage alloc] initWithSize:NSMakeSize(600, 600)];
[savedImage lockFocus];
//draw here
[savedImage unlockFocus];
NSBitmapImageRep* savedImageBitmapRep = [NSBitmapImageRep imageRepWithData:[savedImage TIFFRepresentationUsingCompression:NSTIFFCompressionNone factor:1.0]];
NSDictionary* properties = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat:1.0], kCGImageDestinationLossyCompressionQuality,
[NSNumber numberWithInteger:resolution], kCGImagePropertyDPIHeight,
[NSNumber numberWithInteger:resolution], kCGImagePropertyDPIWidth,
nil];
NSMutableData* imageData = [NSMutableData data];
CGImageDestinationRef imageDest = CGImageDestinationCreateWithData((CFMutableDataRef) imageData, kUTTypeJPEG, 1, NULL);
CGImageDestinationAddImage(imageDest, [savedImageBitmapRep CGImage], (CFDictionaryRef) properties);
CGImageDestinationFinalize(imageDest);
// Do something with imageData
if (![imageData writeToFile:[#"~/Desktop/test.jpg" stringByExpandingTildeInPath] atomically:NO])
NSLog(#"Failed to write imageData");
For NSImage or NSImageRep you do not set the resolution directly but set the size instead.
For size, numberOfPixels and resolution the following equation holds:
size = numberOfPixels * 72.0 / resolution
size is a length and is expressed in dots with the unit inch/72.
(size and resolution are floats). You can see that for an image with dpi=72 size and numberOfPixels are numerally the same (but the meaning is very different).
After creating an NSBitmapImageRep the size with the desired resolution can be set:
NSBitmapImageRep* savedImageBitmapRep = . . . ; // create the new rep
NSSize newSize;
newSize.width = [savedImageBitmapRep pixelsWide] * 72.0 / resolution; // x-resolution
newSize.height = [savedImageBitmapRep pixelsHigh] * 72.0 / resolution; // y-resolution
[savedImageBitmapRep setSize:newSize];
// save the rep
Two remarks: do you really need the lockFocus / unlockFocus way? The preferred way to build a new NSBitmapImageRep is to use NSGraphicsContext. see : http://www.mail-archive.com/cocoa-dev#lists.apple.com/msg74857.html
And: to use TIFFRepresentation for an NSBitmapImageRep is very time and space consuming. Since 10.6 another
way exists and costs nothing, because lockFocus and unlockFocus create an object of class NSCGImageSnapshotRep which under the hood is a CGImage. (In OS versions before 10.6 it was an NSCachedImageRep.) The following does it:
[anImage lockFocus];
// draw something
[anImage unlockFocus];
// now anImage contains an NSCGImageSnapshotRep
CGImageRef cg = [anImage CGImageForProposedRect:NULL context:nil hints:nil];
NSBitmapImageRep *newRep = [[NSBitmapImageRep alloc] initWithCGImage:cg];
// set the resolution
// here you may NSLog anImage, cg and newRep
// save the newRep
// release the newRep if needed

Embed color profile sRGB in JPEG created from cocoa mac app

I use the following code in my cocoa mac app to create a JPEG. How can I also embed srgb color profile in the created JPEG?
NSImage* savedImage = [[NSImage alloc] initWithSize:NSMakeSize(600, 600)];
[savedImage lockFocus];
//draw here
[savedImage unlockFocus];
NSBitmapImageRep* savedImageBitmapRep = [NSBitmapImageRep imageRepWithData:[savedImage TIFFRepresentationUsingCompression:NSTIFFCompressionNone factor:1.0]];
NSDictionary* properties = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat:1.0], kCGImageDestinationLossyCompressionQuality,
nil];
NSMutableData* imageData = [NSMutableData data];
CGImageDestinationRef imageDest = CGImageDestinationCreateWithData((CFMutableDataRef) imageData, kUTTypeJPEG, 1, NULL);
CGImageDestinationAddImage(imageDest, [savedImageBitmapRep CGImage], (CFDictionaryRef) properties);
CGImageDestinationFinalize(imageDest);
// Do something with imageData
if (![imageData writeToFile:[#"~/Desktop/test.jpg" stringByExpandingTildeInPath] atomically:NO])
NSLog(#"Failed to write imageData");
No idea how to do this with these high level APIs. I have done it by adding the needed IFDs to the JPEG data myself.

How to draw to a file in objective-c

How can I draw to an image in objective-c? all I need to do is create an image with a size I set, draw few AA lines and save the image to a png file. I tried to find it in apple docs but there are CGImage, NSImage, CIImage and more. which one is easiest for my goal? I only need to support the latest mac os x version so new things are not a problem.
Probably the easiest way is to use an NSImage and draw directly into it after calling lockFocus.
Example:
NSSize imageSize = NSMakeSize(512, 512);
NSImage *image = [[[NSImage alloc] initWithSize:imageSize] autorelease];
[image lockFocus];
//draw a line:
[NSBezierPath strokeLineFromPoint:NSMakePoint(100, 100) toPoint:NSMakePoint(200, 200)];
//...
NSBitmapImageRep *imageRep = [[[NSBitmapImageRep alloc] initWithFocusedViewRect:NSMakeRect(0, 0, imageSize.width, imageSize.height)] autorelease];
NSData *pngData = [imageRep representationUsingType:NSPNGFileType properties:nil];
[image unlockFocus];
[pngData writeToFile:#"/path/to/your/file.png" atomically:YES];
Well your question is actually two questions in one.
First question is about how to draw an image. You should first read the docs about drawing images. Apple has a Cocoa Drawing Guide about this topic. Start from there to draw images.
Then you need to save the image to disk. Here is a nice piece of code from over here:
NSBitmapImageRep *bits = ...; // get a rep from your image, or grab from a view
NSData *data;
data = [bits representationUsingType: NSPNGFileType
properties: nil];
[data writeToFile: #"/path/to/wherever/test.png"
atomically: NO];