Platforms to show: All Mac Windows Linux Cross-Platform
/Images/LCMS2/Drawing on Mac with Colorspaces/Drawing on Mac with Colorspaces
Required plugins for this example: MBS MacBase Plugin, MBS Main Plugin, MBS MacCocoa Plugin, MBS Picture Plugin, MBS MacCG Plugin, MBS MacCF Plugin, MBS Images Plugin
You find this example project in your Plugins Download as a Xojo project file within the examples folder: /Images/LCMS2/Drawing on Mac with Colorspaces/Drawing on Mac with Colorspaces
This example is the version from Sat, 28th May 2021.
Project "Drawing on Mac with Colorspaces.xojo_binary_project"
MiscFileTypes
Filetype ICC profile (*.ICC)
Filetype ICC profile (*.ICM)
Filetype All profiles
End MiscFileTypes
Class App Inherits Application
Const kEditClear = "&Delete"
Const kFileQuit = "&Quit"
Const kFileQuitShortcut = ""
End Class
Class Window1 Inherits Window
Control CanvasCarbon Inherits Canvas
ControlInstance CanvasCarbon Inherits Canvas
EventHandler Sub MouseExit()
'set the patch number out-of-range
newPatchNumber = -1
'redraw the canvas picture and update the canvas
DrawCanvasPict
me.Invalidate
End EventHandler
EventHandler Sub MouseMove(X As Integer, Y As Integer)
Dim column, row As Integer
'determine row
row = floor(Y / patchDim)
'determine column
column = floor(X / patchDim)
'check if the mouse if over a patch
newPatchNumber = (column * patchRows) + row
if newPatchNumber < 0 then
'the mouse position corresponds to a negative patch number; assign an out-of-range value
newPatchNumber = -1
elseif newPatchNumber < patchQTY then
' the mouse is over a patch; check if the patch number changed since the last move
if newPatchNumber <> oldPatchNumber then
'redraw the canvas picture and update the canvas
'Note : the old patch number will be re-assigned when redrawing the canvas
DrawCanvasPict
me.Invalidate
end if
else
'the patch number is higher than the patch quantity
newPatchNumber = -1
end if
End EventHandler
EventHandler Sub Open()
' assign an out-of-range value to the index of the patch under the mouse
newPatchNumber = -1
End EventHandler
EventHandler Sub Paint(g As Graphics, areas() As REALbasic.Rect)
// Note: This event redraws the canvas picture (CanvasPict).
// The canvas background (CanvasBackground) is made (Method/DrawCanvasBackground) only when
// the canvas is resized or the patches positions are changed (these events cannot happen in this demo).
// The canvas picture is made from the canvas background picture to which is added the borders of the patch
// under the mouse cursor in Method/DrawCanvasPict.
// The canvas picture is redrawn when the mouse cursor moves to another patch or out of the canvas.
// Because the background does not need to be redrawn each time, this method can rapidly refresh the canvas
// for many thousand patches.
// The code is based on the Carbon Framework and the colors are NOT displayed accurately in relation to the monitor profile.
'check if this is the first instance of the event
if CanvasBackground = nil then
'prepare the buffer and redraw the canvas picture; this is a picture with all the patches
DrawCanvasBackground
'add a frame indicating over which patch the mouse is located
DrawCanvasPict
end if
'transfer the canvas picture to the canvas
g.DrawPicture CanvasPict, 0, 0
End EventHandler
End Control
Control CanvasCocoaSlow Inherits Canvas
ControlInstance CanvasCocoaSlow Inherits Canvas
EventHandler Sub MouseExit()
'set the patch number out-of-range
newPatchNumber = -1
'redraw the canvas picture and update the canvas
me.Invalidate
End EventHandler
EventHandler Sub MouseMove(X As Integer, Y As Integer)
Dim column, row As Integer
'determine row
row = floor(Y / patchDim)
'determine column
column = floor(X / patchDim)
'check if the mouse if over a patch
newPatchNumber = (column * patchRows) + row
if newPatchNumber < 0 then
'the mouse position corresponds to a negative patch number; assign an out-of-range value
newPatchNumber = -1
elseif newPatchNumber < patchQTY then
' the mouse is over a patch; check if the patch number changed since the last move
if newPatchNumber <> oldPatchNumber then
'redraw the canvas picture and update the canvas
'Note : the old patch number will be re-assigned when redrawing the canvas
me.Invalidate
end if
else
'the patch number is higher than the patch quantity
newPatchNumber = -1
end if
End EventHandler
EventHandler Sub Open()
' assign an out-of-range value to the index of the patch under the mouse
newPatchNumber = -1
End EventHandler
EventHandler Sub Paint(g As Graphics, areas() As REALbasic.Rect)
dim CanvasGraphics as new NSGraphicsMBS
dim NSspace as NSColorSpaceMBS
dim c1 as NSColorMBS
Dim RectMBS As NSRectMBS
dim row, column As Integer
Dim sampleRow, sampleColumn As Integer
// Note: This event redraws the canvas background AND the borders of the patch under the mouse cursor at every refresh.
// It is fast enough up to many hundred patches but will show delays for more patches.
// The code is optimized for the Cocoa Framework and the colors are displayed accurately in relation to the monitor profile.
// define the data color space
// Note: we assign the display profile since we computed the display RGB for this profile
if profileFolderItem = nil then
// a display profile was not found; use sRGB as a default space
NSspace = NSColorSpaceMBS.sRGBColorSpace
else
'NSspace = NSColorSpaceMBS.adobeRGB1998ColorSpace
NSspace = NSColorSpaceMBS.ColorSpaceWithICCProfileData(profileFolderItem)
end if
'define a rectangle to be filled with the color of each patch
RectMBS = New NSRectMBS
RectMBS.Width = patchDim
RectMBS.Height = patchDim
for row = 0 to 1
for column = 0 to 1
'set the patch/rectangle position
RectMBS.X = patchDim * column
RectMBS.Y = (CanvasCocoaSlow.Height - patchDim) - (patchDim * row)
'define and set the patch color
c1 = NSColorMBS.colorWithColorSpace(NSspace, patchColors(0, row+2*column)/255, patchColors(1, row+2*column)/255, patchColors(2, row+2*column)/255,1.0)
CanvasGraphics.setColor(c1)
'draw the filled rectangle
CanvasGraphics.fillRect(RectMBS)
next column
next row
' add a border to the patch under the mouse; check if the mouse is over a patch
if newPatchNumber <> -1 then
'the mouse is over a patch; define and set the border color (black)
c1 = NSColorMBS.colorWithColorSpace(NSspace, 0/255, 0/255, 0/255,1.0)
CanvasGraphics.setColor(c1)
'derive the patch row and column
// Note: computing the row must take into account the reversed Y origin of the Cocoa canvas relative to how the patches are displayed (from top to bottom)
// sampleRow = (NumberOfRows - 1) - newPatchNumber Mod patchRows
sampleRow = 1 - newPatchNumber Mod patchRows
sampleColumn = floor (newPatchNumber / patchRows)
'draw the patch border
CanvasGraphics.drawRect(sampleColumn * patchDim, sampleRow * patchDim, patchDim, patchDim)
CanvasGraphics.drawRect((sampleColumn * patchDim)+1, (sampleRow * patchDim)+1, patchDim-2, patchDim-2)
end if
'assign the new patch number as the old number
oldPatchNumber = newPatchNumber
End EventHandler
End Control
Control CanvasCocoaFast Inherits Canvas
ControlInstance CanvasCocoaFast Inherits Canvas
EventHandler Sub MouseExit()
'set the patch number out-of-range
newPatchNumber = -1
'redraw the canvas picture and update the canvas
DrawCanvasPict
CanvasCocoaFast.Invalidate
End EventHandler
EventHandler Sub MouseMove(X As Integer, Y As Integer)
Dim column, row As Integer
'determine row
row = floor(Y / patchDim)
'determine column
column = floor(X / patchDim)
'check if the mouse if over a patch
newPatchNumber = (column * patchRows) + row
if newPatchNumber < 0 then
'the mouse position corresponds to a negative patch number; assign an out-of-range value
newPatchNumber = -1
elseif newPatchNumber < patchQTY then
' the mouse is over a patch; check if the patch number changed since the last move
if newPatchNumber <> oldPatchNumber then
'redraw the canvas picture and update the canvas
'Note : the old patch number will be re-assigned when redrawing the canvas
DrawCanvasPict
CanvasCocoaFast.Invalidate
end if
else
'the patch number is higher than the patch quantity
newPatchNumber = -1
end if
End EventHandler
EventHandler Sub Open()
' assign an out-of-range value to the index of the patch under the mouse
newPatchNumber = -1
End EventHandler
EventHandler Sub Paint(g As Graphics, areas() As REALbasic.Rect)
Dim BitmapContext As CGBitmapContextMBS
Dim BitmapImageCG As CGImageMBS
Dim BitmapMB As MemoryBlock
dim ICCProfileData as memoryblock
dim ICCProfile As LCMS2ProfileMBS
dim w as CGContextMBS // of window
// Note: This event redraws the canvas picture (CanvasPict).
// The canvas background (CanvasBackground) is made (Method/DrawCanvasBackground) only when
// the canvas is resized or the patches positions are changed (these events cannot happen in this demo).
// The canvas picture is made from the canvas background picture to which is added the borders of the patch
// under the mouse cursor in Method/DrawCanvasPict.
// The canvas picture is redrawn when the mouse cursor moves to another patch or out of the canvas.
// Because the background does not need to be redrawn each time, this method can rapidly refresh the canvas
// for many thousand patches.
// The code is optimized for the Cocoa Framework and the colors are displayed accurately in relation to the monitor profile.
// get profile info in MemoryBlock form in order to define the color space
if profileFolderItem = nil then
// a display profile was not found; define an sRGB profile
ICCProfile = LCMS2ProfileMBS.CreateSRGBProfile()
else
ICCProfile = LCMS2ProfileMBS.OpenProfileFromFile(profileFolderItem)
end if
ICCProfileData = ICCProfile.SaveProfileToMemory
// define the color space
dim colorspace as CGColorSpaceMBS = CGColorSpaceMBS.CreateWithICCProfile(ICCProfileData)
'check if this is the first instance of the event
if CanvasBackground = nil then
'prepare the buffer and redraw the canvas picture; this is a picture with all the patches
DrawCanvasBackground
'add a frame indicating over which patch the mouse is located
DrawCanvasPict
end if
// define a MemoryBlock for the bitmap
BitmapMB = new MemoryBlock(200*3*200)
// Method-A
// paint the Canvas Picture (using CGImageMBS)
// Note: drawing is done through CGContextMBS
if CanvasPict.CopyRGBtoMemoryblockMBS(BitmapMB, 0, 0, -1, 0, 0) then
BitmapContext = CGBitmapContextMBS.CreateRGB(BitmapMB, 200, 200, 600, colorspace)
BitmapImageCG = BitmapContext.CGImage(False, 0)
// we are inside paint event!
w = CGContextMBS.contextWithCGContext(g.Handle(g.HandleTypeCGContextRef))
w.DrawPicture(BitmapImageCG, CGMakeRectMBS(0,0,200,200))
end if
// Method-B
// paint the Canvas Picture (using NSImageMBS)
// Note: drawing is done through the Paint event g (Graphics) class
// if CanvasPict.CopyRGBtoMemoryblockMBS(BitmapMB, 0, 0, -1, 0, 0) then
// BitmapContext = CGBitmapContextMBS.CreateRGB(BitmapMB, 200, 200, 600, colorspace)
// BitmapImageCG = BitmapContext.CGImage(False, 0)
// // create an instance of the NSImageMBS with the content of CGImageMBS
// BitmapImageNS = NSImageMBS.imageWithCGImage(BitmapImageCG)
// g.DrawPicture BitmapImageNS.CopyPicture, 0, 0
// end if
End EventHandler
End Control
Control TestLabel Inherits Label
ControlInstance TestLabel(0) Inherits Label
ControlInstance TestLabel(1) Inherits Label
ControlInstance TestLabel(2) Inherits Label
End Control
Control RGBlabel Inherits Label
ControlInstance RGBlabel(0) Inherits Label
ControlInstance RGBlabel(1) Inherits Label
ControlInstance RGBlabel(2) Inherits Label
ControlInstance RGBlabel(3) Inherits Label
End Control
Control SelectProfileButton Inherits PushButton
ControlInstance SelectProfileButton Inherits PushButton
EventHandler Sub Action()
dim f As FolderItem
dim dlg As OpenDialog
' let the user browse for an external profile
'*************************
' define a new OpenDialog
dlg= New OpenDialog
#If TargetMachO Then
dlg.Filter = MiscFileTypes.ICCProfileICC + MiscFileTypes.ICCProfileICM
#else
' for Windows
dlg.Filter = MiscFileTypes.AllProfiles + MiscFileTypes.ICCProfileICC + MiscFileTypes.ICCProfileICM // All profiles or separate categories
#endif
dlg.Title = "Open an RGB profile (*.icc, *.icm)"
'*************************
' open the dialog
f = dlg.ShowModal
If f = Nil then
' the user did not select a file
Return
End if
if Not f.IsReadable then
' the file cannot be read; it may be locked by another application
MsgBox "ERROR: The < " + f.DisplayName + " > file cannot be read!" + EndOfLine + "It may be opened in another program."
Return
end if
'*************************
'*************************
profileFolderItem = f
ProfileNameLabel.Text = f.DisplayName
// update the display
CanvasCarbon.Invalidate
CanvasCocoaSlow.Invalidate
CanvasCocoaFast.Invalidate
End EventHandler
End Control
Control ProfileNameLabel Inherits Label
ControlInstance ProfileNameLabel Inherits Label
End Control
EventHandler Sub Close()
#if TargetMacOS then
Quit
#endif
End EventHandler
EventHandler Sub Open()
Dim i As Integer
'load the 4 patches colors; these values presume that they were computed to correspond to a specific RGB space
'Note: the ICC computation for the "Carbon" case are not included; the "Cocoa" case requires that the user selects an external profile
patchColors(0,0) = 65
patchColors(1,0) = 135
patchColors(2,0) = 164
patchColors(0,1) = 165
patchColors(1,1) = 85
patchColors(2,1) = 147
patchColors(0,2) = 227
patchColors(1,2) = 198
patchColors(2,2) = 53
patchColors(0,3) = 182
patchColors(1,3) = 148
patchColors(2,3) = 129
' write the patch RGB values in the window
for i = 0 to 3
RGBlabel(i).text = Str(patchColors(0, i)) + ", " + Str(patchColors(1, i)) + ", " + Str(patchColors(2, i))
next i
' set the patch size
patchDim = 100
'set the patch qty
patchQty = 4
'set the number of rows
patchRows = 2
' refresh only CanvasCarbon since the other two canvases require that a profile be loaded beforehand
CanvasCarbon.Refresh
End EventHandler
Sub DrawCanvasBackground()
Dim i, xpos, ypos As Integer
'define a new Canvas background
CanvasBackground = New Picture(CanvasCarbon.width, CanvasCarbon.height, 32)
'define a new Canvas Picture
CanvasPict = New Picture(CanvasCarbon.width, CanvasCarbon.height, Screen(0).Depth)
' draw the patches on the Canvas background
for i = 0 to patchQty -1
ypos = (i Mod patchRows) * patchDim
xpos = Floor(i / patchRows) * patchDim
CanvasBackground.Graphics.ForeColor = RGB(patchColors(0,i), patchColors(1,i), patchColors(2,i))
CanvasBackground.Graphics.FillRect(xpos, ypos, patchDim, patchDim)
next i
End Sub
Sub DrawCanvasPict()
Dim sampleRow, sampleColumn As Integer
'check if the canvas picture was defined
'Note: it will not be defined when this Method is called during window initialization
if CanvasPict = nil then
Return
end if
'transfer the Canvas background to the Canvas Picture
CanvasPict.Graphics.DrawPicture CanvasBackground, 0, 0
'*****************************************************
' add a border to the patch under the mouse; check if the mouse is over a patch
if newPatchNumber <> -1 then
'the mouse is over a patch; select the border color (black)
CanvasPict.Graphics.foreColor = RGB(0,0,0)
'draw the patch border
sampleRow = newPatchNumber Mod patchRows
sampleColumn = floor (newPatchNumber / patchRows)
CanvasPict.Graphics.drawRect (sampleColumn * patchDim), (sampleRow * patchDim), patchDim, patchDim
CanvasPict.Graphics.drawRect (sampleColumn * patchDim)+1, (sampleRow * patchDim)+1, patchDim-2, patchDim-2
end if
'assign the new patch number as the old number
oldPatchNumber = newPatchNumber
End Sub
Note "About"
This example is provided by Danny Pascal
dpascale@babelcolor.com
It shows various ways to draw color measurement patch fields using color spaces.
Property Protected CanvasBackground As Picture
Property Protected CanvasPict As Picture
Property newPatchNumber As Integer
Property oldPatchNumber As Integer
Property profileFolderItem As FolderItem
End Class
MenuBar MainMenuBar
MenuItem FileMenu = "&File"
MenuItem FileQuit = "#App.kFileQuit"
MenuItem EditMenu = "&Edit"
MenuItem EditUndo = "&Undo"
MenuItem EditSeparator1 = "-"
MenuItem EditCut = "Cu&t"
MenuItem EditCopy = "&Copy"
MenuItem EditPaste = "&Paste"
MenuItem EditClear = "#App.kEditClear"
MenuItem EditSeparator2 = "-"
MenuItem EditSelectAll = "Select &All"
End MenuBar
Module common
Property PrefLastFolderProfile As String
Property patchColors(2,3) As Integer
Property patchDim As Integer
Property patchQty As Integer
Property patchRows As Integer
End Module
End Project
The items on this page are in the following plugins: MBS Images Plugin.