Is there a way I can change the color space of an NSimage or NSBitmapImageRep/CGimage or the like. I am open to any way. Preferably the way photoshop does it.
2 Answers
The problem with the code you see below
CGImageRef CGImageCreateCopyWithColorSpace (
CGImageRef image,
CGColorSpaceRef colorspace
);
is not working because Quartz2D does not support alpha for CMYK images or gray images, it is supported only by RGB images. What you should do is creating 2 images then combine then in case of a CMYK color space and image with alpha. I have searched a lot about this topic and finally found. May be author of this question doesn't need it anymore but someone else may need it.
Create new
NSBitmapImageRep
let imageRep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(round(imageSize.width)), pixelsHigh: Int(round(imageSize.height)), bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: false, isPlanar: false, colorSpaceName: NSDeviceCMYKColorSpace, bitmapFormat: NSBitmapFormat(rawValue: 0), bytesPerRow: Int(round(imageSize.width) * CGFloat(4)), bitsPerPixel: 0)
You need to draw your image into the new bitmap lets say you have
colorSpace: NSColorSpace
let context = NSGraphicsContext(bitmapImageRep: imageRep) NSGraphicsContext.saveGraphicsState() NSGraphicsContext.setCurrentContext(context) imageRep.setProperty(NSImageColorSyncProfileData, withValue: colorSpace.ICCProfileData) // Do your drawing here NSGraphicsContext.restoreGraphicsState()
After this you will have an imageRep that is the image with correct color space but no alpha (transparency).
You need a mask bitmap. Obtaining mask is tricky.
let imageRep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(round(imageSize.width)), pixelsHigh: Int(round(imageSize.height)), bitsPerSample: 8, samplesPerPixel: 1, hasAlpha: false, isPlanar: false, colorSpaceName: NSDeviceWhiteColorSpace, bitmapFormat: NSBitmapFormat(rawValue: 0), bytesPerRow: Int(round(imageSize.width)), bitsPerPixel: 0)
Clip white image to your image with transparency
if let graphicsPort = NSGraphicsContext.currentContext()?.graphicsPort { let context = unsafeBitCast(graphicsPort, CGContextRef.self) let imgRect = NSRect(origin: NSPoint.zero, size: image.extent.size) let ciContext = CIContext() let cgImage = ciContext.createCGImage(image, fromRect: image.extent) CGContextClipToMask(context, imgRect, cgImage) }
Colorize all pixel in white color, this will be clipped to image.
let context = NSGraphicsContext(bitmapImageRep: imageRep) NSGraphicsContext.saveGraphicsState() NSGraphicsContext.setCurrentContext(context) imageRep.setProperty(NSImageColorSyncProfileData, withValue: colorSpace.ICCProfileData) NSColor.whiteColor().setFill() NSBezierPath.fillRect(NSRect(origin: NSPoint.zero, size: imageSize)) NSGraphicsContext.restoreGraphicsState()
At this step you have a image and a mask. Next we need to combine them. I am using this little algorithm (you need to verify that mask and source image have same sizes and same colorSpaceModel):
func createCMYKAImageRepByApplingAlphaMask(srcImageRep: NSBitmapImageRep, alphaMaskImageRep alphaMask: NSBitmapImageRep) -> NSBitmapImageRep? { if canApplyMaskRepOnImageRep(srcImageRep, maskRep: alphaMask) == false { return nil } let alphaData = alphaMask.bitmapData let srcData = srcImageRep.bitmapData if let imageWithAlphaMaskRep = createEmptyCMYKABitmapImageRep(alphaMask.size) { if let colorSpaceData = imageColorSpace?.ICCProfileData { imageWithAlphaMaskRep.setProperty(NSImageColorSyncProfileData, withValue: colorSpaceData) } fillPixelsWithComponentsData(imageWithAlphaMaskRep, components: { (pixelIdx: Int) -> (UInt8, UInt8, UInt8, UInt8, UInt8) in let cyan = srcData[pixelIdx * 4 + 0] let magenta = srcData[pixelIdx * 4 + 1] let yellow = srcData[pixelIdx * 4 + 2] let black = srcData[pixelIdx * 4 + 3] let alpha = alphaData[pixelIdx] return (cyan, magenta, yellow, black, alpha) }) return imageWithAlphaMaskRep } return nil } func createEmptyBitmapImageRep() -> NSBitmapImageRep? { let imageRep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(round(imageSize.width)), pixelsHigh: Int(round(imageSize.height)), bitsPerSample: 8, samplesPerPixel: 5, hasAlpha: true, isPlanar: false, colorSpaceName: NSDeviceCMYKColorSpace, bitmapFormat: NSBitmapFormat(rawValue: 0), bytesPerRow: Int(round(imageSize.width) * CGFloat(5)), bitsPerPixel: 0) return imageRep } private func fillPixelsWithComponentsData(imgRep: NSBitmapImageRep, components: (Int) -> (cyan:UInt8, magenta:UInt8, yellow:UInt8, black:UInt8, alpha:UInt8)) { let imageRawData = imgRep.bitmapData let imageWidth = Int(imgRep.size.width) let imageHeight = Int(imgRep.size.height) for pixelIdx in 0 ..< (imageWidth * imageHeight) { let (cyan, magenta, yellow, black, alpha) = components(pixelIdx) let fAlpha = Float(alpha) / 255 imageRawData[pixelIdx * 5 + 0] = UInt8(Float(cyan) * fAlpha) imageRawData[pixelIdx * 5 + 1] = UInt8(Float(magenta) * fAlpha) imageRawData[pixelIdx * 5 + 2] = UInt8(Float(yellow) * fAlpha) imageRawData[pixelIdx * 5 + 3] = UInt8(Float(black) * fAlpha) imageRawData[pixelIdx * 5 + 4] = alpha } }
-
@HAS Hello mates, how about if I want to create an greyscaled image instead of a CYMK? Do I still need to create an alpha mask? But it seems doesnt make sense for a greyscaled image to have a alpha channel.. I tried simple without doing a mask, but it returns a black image.. Or, should I just simply access to and take average from the colour channels ? – user3390652 Feb 04 '17 at 19:12
-
@user3390652 "Or, should I just simply access to and take average from the colour channels ? " I think that's a very good option! (didn't really think about it, but probably only the average of the RGB channels and leave the alpha as-is). – HAS Feb 04 '17 at 19:14
-
@HAS By the way, would you mind give an example of your step two above please? `//do your drawing here` I tried, with a `NSImage image`, `image.representation.first.draw()`... It seems only work with within a `lock focus` but not a `graphic state`. This is hence I am struggling why I have got an blank image with all pixels are zeros(I loaded the image into MATLAB and found this out..).. – user3390652 Feb 04 '17 at 19:29
-
@user3390652 This is not my answer, I just edited some time ago to make it more readable, maybe @ostafi-ion can chime in? Have you tried [`image.draw(in: CGRect(origin: .zero, size: imageSize)`](https://developer.apple.com/reference/appkit/nsimage/1519863-draw)? – HAS Feb 04 '17 at 19:33
-
1@HAS I have found a way to grayscale an image. I have posted my code and my references in [My post](http://stackoverflow.com/questions/42036448/swift-3-colour-space-macos-not-ios/42046118#42046118), although I don't know if there can still be a better way of doing it. – user3390652 Feb 04 '17 at 22:03
CGImageRef CGImageCreateCopyWithColorSpace (
CGImageRef image,
CGColorSpaceRef colorspace
);
To get a CGColorSpaceRef, you can do things like
CGColorSpaceRef CGColorSpaceCreateDeviceRGB();
OR see this page for how to create other color spaces.

- 3,976
- 18
- 32

- 16,250
- 3
- 42
- 65
-
Hmm. I tried that and it didn't work when I tried to use a GrayScale colorspace. – Alex Zielenski Jul 15 '10 at 17:20
-
This is a good thing to know, but not an answer to the question. The question was about how to change the colour space of an image, not how to create a colour space. – danpalmer Aug 17 '11 at 08:33
-
5I did answer the question. The first function I posted was how to create a new image in a specific colour space. The idea is you make yourself a colour space ref for the desired colour space, then you use `CGImageCreateCopyWithColorSpace()` to create a new copy of your image in the new colour space. – Amy Worrall Aug 17 '11 at 10:14
-
I hadn't seen that the question included CGImage. The above method does not work for NSImage or NSBitmapImageRep. – danpalmer Aug 17 '11 at 10:36
-
1@danpalmer: It's possible to go from NSBitmapImageRep to CGImage and back on 10.5 and later, and from NSImage to CGImage and back on 10.6 and later. – Peter Hosey Apr 22 '13 at 08:51