Saving CGImageRef to a png file? - swift

in my Cocoa application, I load a .jpg file from disk, manipulate it. Now it needs to be written to disk as a .png file. How can you do that?
Thanks for your help!

Using CGImageDestination and passing kUTTypePNG is the correct approach. Here's a quick snippet:
#import MobileCoreServices; // or `#import CoreServices;` on Mac
#import ImageIO;
BOOL CGImageWriteToFile(CGImageRef image, NSString *path) {
CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:path];
CGImageDestinationRef destination = CGImageDestinationCreateWithURL(url, kUTTypePNG, 1, NULL);
if (!destination) {
NSLog(#"Failed to create CGImageDestination for %#", path);
return NO;
}
CGImageDestinationAddImage(destination, image, nil);
if (!CGImageDestinationFinalize(destination)) {
NSLog(#"Failed to write image to %#", path);
CFRelease(destination);
return NO;
}
CFRelease(destination);
return YES;
}
You'll need to add ImageIO and CoreServices (or MobileCoreServices on iOS) to your project and include the headers.
If you're on iOS and don't need a solution that works on Mac too, you can use a simpler approach:
// `image` is a CGImageRef
// `path` is a NSString with the path to where you want to save it
[UIImagePNGRepresentation([UIImage imageWithCGImage:image]) writeToFile:path atomically:YES];
In my tests, the ImageIO approach was about 10% faster than the UIImage approach on my iPhone 5s. In the simulator, the UIImage approach was faster. It's probably worth testing each for your particular situation on the device if you're really concerned with performance.

Here is a macOS-friendly, Swift 3 & 4 example:
#discardableResult func writeCGImage(_ image: CGImage, to destinationURL: URL) -> Bool {
guard let destination = CGImageDestinationCreateWithURL(destinationURL as CFURL, kUTTypePNG, 1, nil) else { return false }
CGImageDestinationAddImage(destination, image, nil)
return CGImageDestinationFinalize(destination)
}

Create a CGImageDestination, passing kUTTypePNG as the type of file to create. Add the image, then finalize the destination.

Swift 5+ adopted version
import Foundation
import CoreGraphics
import CoreImage
import ImageIO
import MobileCoreServices
extension CIImage {
public func convertToCGImage() -> CGImage? {
let context = CIContext(options: nil)
if let cgImage = context.createCGImage(self, from: self.extent) {
return cgImage
}
return nil
}
public func data() -> Data? {
convertToCGImage()?.pngData()
}
}
extension CGImage {
public func pngData() -> Data? {
let cfdata: CFMutableData = CFDataCreateMutable(nil, 0)
if let destination = CGImageDestinationCreateWithData(cfdata, kUTTypePNG as CFString, 1, nil) {
CGImageDestinationAddImage(destination, self, nil)
if CGImageDestinationFinalize(destination) {
return cfdata as Data
}
}
return nil
}
}

The provided solutions probably still work fine but there is some newer API for this in CoreImage which does the same and is a bit more "Swifty" to use:
import CoreImage
func write(cgimage: CGImage, to url: URL) throws {
let cicontext = CIContext()
let ciimage = CIImage(cgImage: cgimage)
try cicontext.writePNGRepresentation(of: ciimage, to: url, format: .RGBA8, colorSpace: ciimage.colorSpace!)
}

Related

NSImage to NSData as PNG Swift

I am writing a Mac app based on an iOS app. The code below converts a UIImage to NSData to upload to Parse.com.
I would like to do the same for Mac but I do not seem to be able to convert it to NSData. What should I be doing?
Thanks
var image = UIImage(named: "SmudgeInc")
let imageData = UIImagePNGRepresentation(image)
let imageFile = PFFile(name:"image.png", data:imageData)
You can use the NSImage property TIFFRepresentation to convert your NSImage to NSData:
let imageData = yourImage.TIFFRepresentation
If you need to save your image data to a PNG file you can use NSBitmapImageRep(data:) and representationUsingType to create an extension to help you convert Data to PNG format:
Update: Xcode 11 • Swift 5.1
extension NSBitmapImageRep {
var png: Data? { representation(using: .png, properties: [:]) }
}
extension Data {
var bitmap: NSBitmapImageRep? { NSBitmapImageRep(data: self) }
}
extension NSImage {
var png: Data? { tiffRepresentation?.bitmap?.png }
}
usage
let picture = NSImage(contentsOf: URL(string: "https://i.stack.imgur.com/Xs4RX.jpg")!)!
let imageURL = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first!.appendingPathComponent("image.png")
if let png = picture.png {
do {
try png.write(to: imageURL)
print("PNG image saved")
} catch {
print(error)
}
}

how to get png representation of an NSImage object in Swift

I know I can use "UIImagePNGRepresentation(UIImagexxx)" to get the png representation of a UIImage object.
But I'm not sure how I can do something similar for NSImage. I could only find TIFFRepresentation available for NSImage objects.
Any ideas? Thanks a lot
To help with cross-platform code, I implemented a version ofUIImagePNGRepresentation() that runs on Mac (and uses NSImage):
#if os(macOS)
public func UIImagePNGRepresentation(_ image: NSImage) -> Data? {
guard let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil)
else { return nil }
let imageRep = NSBitmapImageRep(cgImage: cgImage)
imageRep.size = image.size // display size in points
return imageRep.representation(using: .png, properties: [:])
}
#endif

Unable to load image from asset URL

I have a class that stores information about the assets on the phone (images, videos).
My class has the ResourceURLString defined as such
#property NSURL *ResourceURL;
I am setting the property while looping trough the assets on the phone as such
Item.ResourceURLString = [[asset valueForProperty:ALAssetPropertyURLs] objectForKey:[[asset valueForProperty:ALAssetPropertyRepresentations] objectAtIndex:0]];
When the user clicks on an image I want to load the image.
The code that I have is this
NSData *imageUrl = [NSData dataWithContentsOfURL:[NSURL URLWithString:[CurrentItem.ResourceURL absoluteString]]];
Img = [UIImage imageWithData:imageUrl];
But the Image is always nil
I have verified that the ResourceURL property contains the URL
assets: library://asset/asset.JPG?id=82690321-91C1-4650-8348-F3FD93D14613&ext=JPG
You can't load images in this way.
You need to use ALAssetsLibrary class for this.
Add assetslibrary framework to your project and add header files.
Use the below code for loading image:
ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *myasset)
{
ALAssetRepresentation *rep = [myasset defaultRepresentation];
CGImageRef iref = [rep fullResolutionImage];
if (iref) {
UIImage *largeimage = [UIImage imageWithCGImage:iref];
yourImageView.image = largeImage;
}
};
ALAssetsLibraryAccessFailureBlock failureblock = ^(NSError *myerror)
{
NSLog(#"Can't get image - %#",[myerror localizedDescription]);
};
NSURL *asseturl = [NSURL URLWithString:yourURL];
ALAssetsLibrary* assetslibrary = [[[ALAssetsLibrary alloc] init] autorelease];
[assetslibrary assetForURL:asseturl
resultBlock:resultblock
failureBlock:failureblock];
Since iOS 8 you can use the Photos Framework here is how to do it in Swift 3
import Photos // use the Photos Framework
// declare your asset url
let assetUrl = URL(string: "assets-library://asset/asset.JPG?id=9F983DBA-EC35-42B8-8773-B597CF782EDD&ext=JPG")!
// retrieve the list of matching results for your asset url
let fetchResult = PHAsset.fetchAssets(withALAssetURLs: [assetUrl], options: nil)
if let photo = fetchResult.firstObject {
// retrieve the image for the first result
PHImageManager.default().requestImage(for: photo, targetSize: PHImageManagerMaximumSize, contentMode: .aspectFill, options: nil) {
image, info in
let myImage = image //here is the image
}
}
Use PHImageManagerMaximumSize if you want to retrieve the original size of the picture. But if you want to retrieve a smaller or specific size you can replace PHImageManagerMaximumSize by CGSize(width:150, height:150)
As of iOS 9.0 ALAssetsLibraryis deprecated. Since iOS 8.0, this works with the PHPhotoLibrary. This is a small UIImage extension, Swift 2X.
This uses a fixed image size.
import Photos
extension UIImageView {
func imageFromAssetURL(assetURL: NSURL) {
let asset = PHAsset.fetchAssetsWithALAssetURLs([assetURL], options: nil)
guard let result = asset.firstObject where result is PHAsset else {
return
}
let imageManager = PHImageManager.defaultManager()
imageManager.requestImageForAsset(result as! PHAsset, targetSize: CGSize(width: 200, height: 200), contentMode: PHImageContentMode.AspectFill, options: nil) { (image, dict) -> Void in
if let image = image {
self.image = image
}
}
}
}
Getting the imageReferenceURL from the UIImagePickerController delegate:
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
imageURL = info[UIImagePickerControllerReferenceURL] as? NSURL
}
Setting the image
let imageView = UIImageView()
imageView.imageFromAssetURL(imageURL)
There might be effects I haven't encountered yet, a classic would be UITableViewCell or thread problems. I'll keep this updated, also appreciate your feedback.
For Swift 5
fetchAssets(withALAssetURLs) will be removed in a future release. Hence we using fetchAssets to get image from asset local identifier
extension UIImageView {
func imageFromLocalIdentifier(localIdentifier: String, targetSize: CGSize) {
let fetchOptions = PHFetchOptions()
// sort by date desending
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
// fetch photo with localIdentifier
let results = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: fetchOptions)
let manager = PHImageManager.default()
results.enumerateObjects { (thisAsset, _, _) in
manager.requestImage(for: thisAsset, targetSize: targetSize, contentMode: .aspectFit, options: nil, resultHandler: {(image, _) in
DispatchQueue.main.async {[weak self] in
self?.image = image
}
})
}
}
}
Update
let image = UIImage(data: NSData(contentsOf: imageURL as URL)! as Data)
ALAsset *asset = "asset array index"
[tileView.tileImageView setImage:[UIImage imageWithCGImage:[asset thumbnail]]];

UIImageWriteToSavedPhotosAlbum save as PNG with transparency?

I'm using UIImageWriteToSavedPhotosAlbum to save a UIImage to the user's photo album. The problem is that the image doesn't have transparency and is a JPG. I've got the pixel data set correctly to have transparency, but there doesn't seem to be a way to save in a transparency-supported format. Ideas?
EDIT: There is no way to accomplish this, however there are other ways to deliver PNG images to the user. One of which is to save the image in the Documents directory (as detailed below). Once you've done that, you can email it, save it in a database, etc. You just can't get it into the photo album (for now) unless it is a lossy non-transparent JPG.
As pointed out on this SO question there is a simple way to save pngs in your Photo Albums:
UIImage* image = ...; // produce your image
NSData* imageData = UIImagePNGRepresentation(image); // get png representation
UIImage* pngImage = [UIImage imageWithData:imageData]; // rewrap
UIImageWriteToSavedPhotosAlbum(pngImage, nil, nil, nil); // save to photo album
This is a problem I have noticed before and reported on the Apple Developer Forums about a year ago. As far as I know it is still an open issue.
If you have a moment, please take the time to file a feature request at Apple Bug Report. If more people report this issue, it is more likely that Apple will fix this method to output non-lossy, alpha-capable PNG.
EDIT
If you can compose your image in memory, I think something like the following would work or at least get you started:
- (UIImage *) composeImageWithWidth:(NSInteger)_width andHeight:(NSInteger)_height {
CGSize _size = CGSizeMake(_width, _height);
UIGraphicsBeginImageContext(_size);
// Draw image with Quartz 2D routines over here...
UIImage *_compositeImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return _compositeImage;
}
//
// cf. https://developer.apple.com/iphone/library/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/FilesandNetworking/FilesandNetworking.html#//apple_ref/doc/uid/TP40007072-CH21-SW20
//
- (BOOL) writeApplicationData:(NSData *)data toFile:(NSString *)fileName {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
if (!documentsDirectory) {
NSLog(#"Documents directory not found!");
return NO;
}
NSString *appFile = [documentsDirectory stringByAppendingPathComponent:fileName];
return ([data writeToFile:appFile atomically:YES]);
}
// ...
NSString *_imageName = #"myImageName.png";
NSData *_imageData = [NSData dataWithData:UIImagePNGRepresentation([self composeImageWithWidth:100 andHeight:100)];
if (![self writeApplicationData:_imageData toFile:_imageName]) {
NSLog(#"Save failed!");
}
In Swift 5:
func pngFrom(image: UIImage) -> UIImage {
let imageData = image.pngData()!
let imagePng = UIImage(data: imageData)!
return imagePng
}
I created an extension of UIImage with safe unwrapping:
Extension
extension UIImage {
func toPNG() -> UIImage? {
guard let imageData = self.pngData() else {return nil}
guard let imagePng = UIImage(data: imageData) else {return nil}
return imagePng
}
}
Usage:
let image = //your UIImage
if let pngImage = image.toPNG() {
UIImageWriteToSavedPhotosAlbum(pngImage, nil, nil, nil)
}
As an alternative to creating a secondary UIImage for UIImageWriteToSavedPhotosAlbum the PNG data can be written directly using the PHPhotoLibrary.
Here is a UIImage extension named 'saveToPhotos' which does this:
extension UIImage {
func saveToPhotos(completion: #escaping (_ success:Bool) -> ()) {
if let pngData = self.pngData() {
PHPhotoLibrary.shared().performChanges({ () -> Void in
let creationRequest = PHAssetCreationRequest.forAsset()
let options = PHAssetResourceCreationOptions()
creationRequest.addResource(with: PHAssetResourceType.photo, data: pngData, options: options)
}, completionHandler: { (success, error) -> Void in
if success == false {
if let errorString = error?.localizedDescription {
print("Photo could not be saved: \(errorString))")
}
completion(false)
}
else {
print("Photo saved!")
completion(true)
}
})
}
else {
completion(false)
}
}
}
To use:
if let image = UIImage(named: "Background.png") {
image.saveToPhotos { (success) in
if success {
// image saved to photos
}
else {
// image not saved
}
}
}

NSData to UIImage

I'm trying to save a UIIMage and then retrieve it and display it. I've been successful by saving an image in the bundle, retrieving it from there and displaying. My problem comes from when I try to save an image to disk (converting it to NSData), then retrieving it. I convert the image to NSData like so...
NSData* topImageData = UIImageJPEGRepresentation(topImage, 1.0);
then I write it to disk like so...
[topImageData writeToFile:topPathToFile atomically:NO];
then I tried to retrieve it like so...
topSubView.image = [[UIImage alloc] initWithContentsOfFile:topPathToFile];
which returns no image (the size is zero). so then I tried...
NSData *data = [[NSData alloc] initWithContentsOfFile:topPathToFile];
topSubView.image = [[UIImage alloc] initWithData:data];
to no avail. When I step through the debugger I do see that data contains the correct number of bytes but I'm confused as to why my image is not being created. Am I missing a step? Do I need to do something with NSData before converting to an image?
Try this code. This worked for me.
Saving the data.
create path to save the image.
let libraryDirectory = NSSearchPathForDirectoriesInDomains(.libraryDirectory,
.userDomainMask,
true)[0]
let libraryURL = URL(fileURLWithPath: libraryDirectory, isDirectory: true)
let fileURL = libraryURL.appendingPathComponent("image.data")
convert the image to a Data object and save it to the file.
let data = UIImageJPEGRepresentation(myImage, 1.0)
try? data?.write(to: fileURL)
retrieving the saved image
let newImage = UIImage(contentsOfFile: fileURL.relativePath)
Create an imageview and load it with the retrieved image.
let imageView = UIImageView(image: newImage)
self.view.addSubview(imageView)
You should be able to use class methods of UIImage
[UIImage imageWithContentsOfFile:topPathToFile];
OR
[UIImage imageWithData:data];
Did that not work?
Hope this helps!
Just in case this helps someone, from iOS6 we have the imageWithData:scale method. To get an UIImage with the right scale from an NSData object, use that method, declaring the scale you use to store the original image. For example:
CGFloat screenScale = [[UIScreen mainScreen] scale];
UIImage *image = [UIImage imageWithData:myimage scale:screenScale];
Here, myimage is the NSData object where you stored the original image. In the example, I used the scale of the screen. If you use another scale for the original image, use that value instead.
Check this out: http://www.nixwire.com/getting-uiimage-to-work-with-nscoding-encodewithcoder/.
It has exactly what I think the solution to your problem is. You can't just make an image out of NSData, even though that's what common sense suggests. You have to use UIImagePNGRepresentation. Let me know if this helps.
Use if-let block with Data to prevent app crash & safe execution of code, as function UIImagePNGRepresentation returns an optional value.
if let img = UIImage(named: "TestImage.png") {
if let data:Data = UIImagePNGRepresentation(img) {
// Handle operations with data here...
}
}
Note: Data is Swift 3+ class. Use Data instead of NSData with
Swift 3+
Generic image operations (like png & jpg both):
if let img = UIImage(named: "TestImage.png") { //UIImage(named: "TestImage.jpg")
if let data:Data = UIImagePNGRepresentation(img) {
handleOperationWithData(data: data)
} else if let data:Data = UIImageJPEGRepresentation(img, 1.0) {
handleOperationWithData(data: data)
}
}
*******
func handleOperationWithData(data: Data) {
// Handle operations with data here...
if let image = UIImage(data: data) {
// Use image...
}
}
By using extension:
extension UIImage {
var pngRepresentationData: Data? {
return UIImagePNGRepresentation(img)
}
var jpegRepresentationData: Data? {
return UIImageJPEGRepresentation(self, 1.0)
}
}
*******
if let img = UIImage(named: "TestImage.png") { //UIImage(named: "TestImage.jpg")
if let data = img.pngRepresentationData {
handleOperationWithData(data: data)
} else if let data = img.jpegRepresentationData {
handleOperationWithData(data: data)
}
}
*******
func handleOperationWithData(data: Data) {
// Handle operations with data here...
if let image = UIImage(data: data) {
// Use image...
}
}