« Sign up for MBS Newsl… | Home | MonkeyBread Software … »

Colorspaces in MacOS with Xojo

Some of our clients love to make applications to edit photos and want to display them in the correct colorspace.

The example project used for a test shows various colorspaces and you need to look very close at them on the screen so you can spot the difference between the red in the three color spaces: screenshot For my screen left is RGB(237, 65, 36), middle is RGB(235, 51, 36) and right is RGB(255, 2, 0). So the right one is basically 1 to 1 to the screen. In that screen colorspace a SRGB or generic RGB red color is quite a difference.

The drawing is done in Paint event of canvas with code like this:

dim context as CGContextMBS = CGContextMBS.contextWithCGContext(g.Handle(g.HandleTypeCGContextRef)) dim ColorSpace as CGColorSpaceMBS ColorSpace = CGColorSpaceMBS.CreateWithName(CGColorSpaceMBS.kCGColorSpaceGenericRGB) dim Red as CGColorMBS = CGColorMBS.Create(ColorSpace, array(1.0, 0.0, 0.0, 1.0)) context.SetFillColorSpace(ColorSpace) context.SetFillColor(Red) context.FillRect CGMakeRectMBS(0, 0, g.Width, g.Height) context.Flush
This draws a red area in generic RGB. You can swap the colorspace name with other names or as the following example use the screen colorspace:

dim context as CGContextMBS = CGContextMBS.contextWithCGContext(g.Handle(g.HandleTypeCGContextRef)) dim screen as NSScreenMBS = NSScreenMBS.mainScreen dim ScreenColorSpace as NSColorSpaceMBS = screen.colorSpace dim ColorSpace as CGColorSpaceMBS = CGColorSpaceMBS.CreateWithHandle(ScreenColorSpace.CGColorSpaceHandle) dim Red as CGColorMBS = CGColorMBS.Create(ColorSpace, array(1.0, 0.0, 0.0, 1.0)) context.SetFillColorSpace(ColorSpace) context.SetFillColor(Red) context.FillRect CGMakeRectMBS(0, 0, g.Width, g.Height) context.Flush
As you see we query colorspace from screen, get CGColorSpaceMBS object and use it for drawing. Xojo uses pictures with generic RGB colorspace as default as you see with this code snippet:

// show colorspace of picture dim p as new Picture(100, 100) dim colorspace as CGColorSpaceMBS = p.CGColorSpaceMBS MsgBox colorspace.Name // generic RGB
The color space reported is generic RGB. That means you can use your picture color handling and just convert all your colors to generic RGB and draw them into a picture. For example you can use a Xojo Picture and access pixels with PictureMBS. You can do color matching with our LCMS plugin functions to match colors to the generic RGB colorspace into the Xojo picture. This should look correct in most cases.

If you need colorspace for LCMS to convert, you can query ICCProfileData from the NSColorSpaceMBS like the following code:
// shows Display profile dim screen as NSScreenMBS = NSScreenMBS.mainScreen dim ScreenColorSpace as NSColorSpaceMBS = screen.colorSpace MsgBox ScreenColorSpace.localizedName // open same profile in LCMS for conversion dim screenICCProfile as Memoryblock = ScreenColorSpace.ICCProfileData dim LCMS2Profile as LCMS2ProfileMBS = LCMS2ProfileMBS.OpenProfileFromMemory(screenICCProfile) MsgBox LCMS2Profile.Name
The final example for today is to make a bitmap ourself with setting pixels in memory block. So first get the target colorspace, in our case the one from screen. Than we build a memory block with red pixels. To draw we first build a CGBitmapContextMBS referencing pixels in the memoryblock. To draw we must make a CGImageMBS, which copies pixels to VRAM, so further changes to memoryblock are not longer affecting the image. You can cache the image until you do changes to the bitmap. The image can now be drawn into the window via CGContextMBS object.
dim context as CGContextMBS = CGContextMBS.contextWithCGContext(g.Handle(g.HandleTypeCGContextRef)) dim ColorSpace as CGColorSpaceMBS ColorSpace = CGColorSpaceMBS.CreateWithName(CGColorSpaceMBS.kCGColorSpaceGenericRGB) dim w as integer = g.Width * 2 dim h as integer = g.Height * 2 const kCGImageAlphaNone = 0 const kCGImageAlphaPremultipliedLast = 1 // For example, premultiplied RGBA const kCGImageAlphaPremultipliedFirst = 2 // For example, premultiplied ARGB const kCGImageAlphaLast = 3 // For example, non-premultiplied RGBA const kCGImageAlphaFirst = 4 // For example, non-premultiplied ARGB const kCGImageAlphaNoneSkipLast = 5 // Equivalent to kCGImageAlphaNone. const kCGImageAlphaNoneSkipFirst = 6 dim rowBytes as integer = w * 4 dim data as new MemoryBlock(rowBytes * h) for y as integer = 0 to 199 for x as integer = 0 to 199 data.UInt32Value( y * rowBytes + x * 4) = &h0000FF00 // red next next dim bitmap as CGBitmapContextMBS bitmap = CGBitmapContextMBS.Create(data, w, h, 8, rowBytes, ColorSpace, kCGImageAlphaNoneSkipFirst) dim image as CGImageMBS = bitmap.CreateImage context.DrawPicture(image, CGMakeRectMBS(0, 0, g.Width, g.Height)) context.Flush
if you have further questions, please do not hesitate to contact us. For above call to CGColorMBS.Create in older plugins, please add an extra component. This bug is fixed for final 18.0 plugins. The biggest plugin in space...
20 01 18 - 12:48