A CMY-255 color selector GUI

uniquename - 2012nov13

I published a nice, simple RGB color-selector Tk GUI script at A non-obfuscated color selector GUI. I have used that color selector repeatedly as an aid in various utilities published here, such as

But I have been thinking over the past couple of months about how the 'complementary' (a.k.a. 'subtractive') colors Cyan-Magenta-Yellow should be just as capable of presenting 256 x 256 x 256 = 16,777,216 colors as the 'primary' (a.k.a. 'additive') colors, Red-Green-Blue.

However, I was having a hard time devising a set of 3 conversion equations for converting CMY to RGB or vice versa.

I wanted to use values for CMY in the range 0 to 255, like is typically done with RGB. I know that people (in the printing profession, I guess) typically specify CMY values as fractional real numbers in the range 0 to 1, rather than using a range of integers.

I suppose this is because they do not want to be limited to 'only' 256 color levels for each of C, M, and Y. I can appreciate the desire for freedom. It must be very liberating to use real numbers for CMY rather than integers.

But come on, guys (and gals). Wouldn't 256 x 256 = 65,536 shades of Cyan or Magenta or Yellow be enough. After all, that is a total of 65536 x 65536 x 65536 colors. Isn't 281,474,976,710,656 (281 trillion) colors enough?

Well, I think I can do quite nicely with 256 x 256 x 256 = 16,777,216 colors, so I have been trying to think of a way to do the CMY to-from RGB mapping.

I thought "How do I get a pure RGB color like Blue from CMY?" Cyan(=blue+green) and Magenta(=blue+red) could be combined to give blue, but somehow I would have to subtract out the red and green contributions of those 2 CMY colors to get pure Blue.

I thought, in order to get Blue=RGB(0,0,255), if I use both Cyan and Magenta to get blue, I would need to put them in a scale that went to about 127 or 128 max. But then I got into questions of which should it be 127 or 128? And what should the lower range be? Should the minimum be -128? Or -127? And how do I use yellow to subtract out the extra red plus green?

Or maybe the range should be -255 to 0. But I still could not figure a way to map CMY to RGB, and back. I was pretty much stymied.

Then I saw an article that pointed out these three simple equations for converting from RGB to CMY:

   yellow = 1 - blue
   magenta = 1 - green
   cyan = 1 - red

That was it! It may not relate to the 'real world', but it looked like a workable technique. All I needed to do was scale things up to 255:

   yellow = 255 - blue
   magenta = 255 - green
   cyan = 255 - red

That's for converting RGB to CMY. And simply rearrange for CMY-to-RGB:

   blue = 255 - yellow
   green = 255 - magenta
   red = 255 - cyan

So then I was equipped to convert my RGB-255 color selector at A non-obfuscated color selector GUI to a CMY-255 color selector.

And I ended up with the following GUI --- with code below.

CMY255colorSelectorGUI_screenshot_732x202.jpg

I don't know how useful this might be. I tend to think in terms of RGB nowadays. But maybe there are people (printers? painters?) who think in terms of CMY. Maybe CMY is as natural to them as RGB is to me.

So maybe this has some usefulness. Maybe not. But at least it can get one's thinking out of a rut. For example, on this GUI, slide the three scales to (255,255,255) and the color swatch is black.

And, sure enough, slide to (0,0,0) and the color swatch is white. Of course, it is what you would expect if you know the equations at work behind the scenes. But even then ... it turns my color world upside down.

___

Actually, the translation formulas above correspond pretty well to the 'real world'. The CMY colors are said to be 'subtractive' colors. For example, yellow is yellow because it 'sucks' the blue frequency out of incident light, reflecting back red and green frequencies, yielding yellow.

So it makes quite good sense that yellow is found by subtracting blue: yellow = 1 - blue or yellow = 255 - blue.

In fact, my thought at one point of using a range from -255 to 0 might even make more sense. In that case, the transform equations could simply be

   yellow = - blue
   magenta =  - green
   cyan =  - red

In fact, these equations are related to the ones above by simply shifting the latter numbers 255 units to the right on the real (or integer) number scale.

So instead of making the 3 scales on the GUI go from 0 to 255, it would probably be just as valid to make the scales go from -255 to 0. The colors on the color swatch would still behave the same.

So it appears these mind-bending formulas for converting between CMY and RGB are compatible with the 'real world'. The CMY colors are subtractive both in a physical and a mathematical sense. We get the CMY colors by subtracting the RGB colors. The math here is compatible with what is happening with paints and printer's ink.


The code

I provide the code for this GUI below.

I follow my usual 'canonical' structure for Tk code, for this Tk script:

  0) Set general window & widget parms (win-name, win-position,
     win-color-scheme, fonts, widget-geometry-parms, win-size-control,
     text-array-for-labels-etc).

  1a) Define ALL frames (and sub-frames, if any).
  1b) Pack   ALL frames and sub-frames.

  2) Define & pack all widgets in the frames, frame by frame.

  3) Define keyboard and mouse/touchpad/touch-sensitive-screen action
     BINDINGS, if needed.

  4) Define PROCS, if needed.

  5) Additional GUI initialization (typically with one or more of
     the procs), if needed.

This structure is discussed in more detail on the page A Canonical Structure for Tk Code --- and variations.

This structure makes it easy for me to find code sections --- while generating and testing a Tk script, and when looking for code snippets to include in Tk scripts (code re-use).

One new thing that I have started doing recently is using a text-array for text in labels, buttons, and other widgets in the GUI. This can make it easier for people to internationalize my scripts. I will be using a text-array like this in most of my scripts in the future.


Experimenting with the GUI

As in all my scripts that use the 'pack' geometry manager (which is all of my 100-plus scripts, so far), I provide the four main pack parameters --- '-side', '-anchor', '-fill', '-expand' --- on all of the 'pack' commands for the frames and widgets.

That helps me when I am initially testing the behavior of a GUI (the various widgets within it) as I resize the main window.

For this particular GUI, I have used the statement

wm resizeable . 0 0

to make the window (and its widgets) a fixed size --- since I have found no advantage in having the color swatch change size --- and no advantage to having the scale and button widgets change size, or position.

However, if anyone sees an advantage to allowing the GUI to change size, they can comment the 'wm resizeable' statement --- and then experiment with the '-side', '-anchor', '-fill', and '-expand' parameters on the 'pack' commands for the various widgets --- to get the widget behavior that they want.

___

In addition, you might want to change the fonts used for the various GUI widgets. For example, you could change '-weight' from 'bold' to 'normal' --- or '-slant' from 'roman' to 'italic'. Or change font families.

In fact, you may NEED to change the font families, because the families I used may not be available on your computer --- and the default font that the 'wish' interpreter chooses may not be very pleasing.

I use variables to set geometry parameters of widgets --- parameters such as border-widths and padding. And I have included the '-relief' parameter on the definitions of frames and widgets. Feel free to experiment with those 'appearance' parameters as well.

___

That said, here's the code --- with plenty of comments to describe what most of the code-sections are doing.

Most of the code that is involved in doing the CMY/RGB color translations and 'dynamically' updating the color swatch and the CMY-and-RGB labels is in the proc 'color_update'.

It is my hope that the copious comments in the code will help Tcl-Tk coding 'newbies' get started in making GUI's like this. Without the comments, it might not be clear why certain approaches are taken --- for example, certain 'packing' strategies.

Without the comments, potential young Tcler's might be tempted to return to their iPhones and iPads and iPods --- to watch AFHV (Antarctica's Funniest Home Videos).


 Code for the Tk script 'CMY255_color_selector.tk' :
#!/usr/bin/wish -f
##
##+#######################################################################
## NOTE:
##   If the 'wish' interpreter is in another directory, like
##   /usr/local/bin, you, as root, can make a soft-link from 'wish' there
##   to /usr/bin/wish --- with a command like
##             ln -s /usr/local/bin/wish  /usr/bin/wish
##   The form of this command:
##             ln -s <filename-of-existing-file> <name-of-new-link-file>
##+#######################################################################
## Tk SCRIPT NAME:   CMY255_color_selector.tk
##
##+#######################################################################
## PURPOSE:  This TkGUI script provides a GUI for selecting 'digitized'
##           Cyan-Magenta-Yellow values, from 0 thru 255, via
##           slider-bars/buttons on 3 Tk 'scale' widgets.  
##
##           For any slider-bar change, this GUI script
##           immediately shows the color as the background color of a
##           frame widget (a 'color swatch') in a corner of the GUI
##           window (covering about 20% of the window).
##
##           Also the GUI shows the current color's hex (and per-cent)
##           CMY and RGB values in two small text widgets about 12
##           characters long.
##
##           This script is based on a similar script for showing
##           and choosing colors for given RGB-255 values. That script
##           is the real 'workhorse'. This script is mainly a demo
##           that shows that 'digital' screen colors can be specified
##           via the 'complementary' CMY colors, as well as via the
##           'primary' RGB colors.
##
##           For someone who thinks in CMY rather than RGB, it might
##           have the following uses.
##
## SOME USES (perhaps by a CMY fan --- someone who thinks in terms of
## 'complementary' colors rather than 'primary' colors --- someone who
## thinks in terms of 'subtractive' colors rather than 'additive' colors
## --- someone who thinks in terms of CMY rather than RGB ---
## perhaps painters and printers):
##
##       1)  Useful in determining a CMY color specification in integer (0-255)
##           or percents (or hex values?) to MATCH A GIVEN COLOR. (The user
##           can drag this GUI to a portion of the screen, and use the
##           slider-bars to find color matches. Similarly, bring an
##           image viewer window next to the GUI.)
##
##       2)  Also may be useful in DETERMINING SUITABLE BACKGROUND COLORS
##           for black/white text --- in CMY format.
##
##       3)  IN A SHELL SCRIPT OR ANOTHER TK SCRIPT, this Tk script can
##           ACT AS A COLOR SELECTOR by passing the current color to stdout
##           (in RGB 0-255-integer and hex), when the OK/UseIt button is
##           clicked. Example output string:  0 156 255 009CFF
##
##+#######################################################################
## SOURCE (and CREDIT-TO):
##  This script is based on a color-chooser Tk script for the
##  FE (Freedom Environment) software systems (www.freedomenv.com).
##
##  That script was based on an example from Australia (circa 1999) at
##        http://www.sci.usq.edu.au/~devoil/66309/tut2.html
##  (Author's name unknown. The script may have been based on the Tk
##   color selector GUI that was announced by Ousterhout around 1996.
##   It may have been intended as a simplification of that GUI.)
##
##+#####################################################################
## INPUTS:  The user slides the slider bars.  Initial CMY values can be
##          set as described in the next section, 'CALL FORMAT'.
##
## OUTPUT:  The color-swatch and hex-and-percent CMY and RGB values
##          displayed on the Tk window.
##
##          Also the last CMY slider settings are passed, after conversion
##          to RGB-255 values, to stdout as a string giving integer (0-255)
##          and hex values of the RGB-color.
##
##          Sample output string:  0 156 255 009CFF
##
##          We pass RGB colors rather than CMY because, for most computer
##          applications such as web pages and Tk scripts and graphics
##          programming, colors need to be specified as RGB values.
##
##+#####################################################################
## CALL FORMAT:
##
##  $FEDIR_TKGUIS/CMY255_color_selector.tk  [rc255 m255 y255]
##
##      where c255 m255 y255 represent integers between 0 and 255,
##      and the values are ignored if there are not exactly three.
##      If no values are entered, the defaults are 0 156 255 or
##      some such triplet.
##
##   OR
##
##      use env vars C255, M255, Y255 to initialize the sliders.
## -----------------------------------------------------------------------
##   EXAMPLE CALLS in a shell script:
##                              (Could also be called in a tcl-tk script.)
##
##    1)    C255=0
##          M255=156
##          Y255=255
##          export C255 M255 Y2555
##
##          TEMP=`$FEDIR_TKGUIS/sho_colorvals_via_sliders3rgb.tk`
##  OR
##    2)
##          TEMP=`$FEDIR_TKGUIS/sho_colorvals_via_sliders3rgb.tk 80 156 255`
##       or
##          TEMP=`$FEDIR_TKGUIS/sho_colorvals_via_sliders3rgb.tk`
##
##
## (Note: The values in TEMP can be extracted with a command like
##  'cut' or 'awk'.)
##+########################################################################
## STRUCTURE OF THIS CODE:
##
##  0) Set general window parms (win-name,win-position,color-scheme,
##                        fonts, widget-geom-parms,win-size-control).
##  1a) Define ALL frames (and sub-frames).
##  1b) Pack   ALL the frames and sub-frames.
##  2) Define & pack all widgets in the frames.
##
##  3) Define key and mouse/touchpad/touch-sensitive-screen action
##     BINDINGS, if needed.
##  4) Define PROCS, if needed.
##  5) Additional GUI INITIALIZATION (typically with one or two
##                procs in section 4), if needed.
##
## In more detail for this particular script:
##
##  1a) Define ALL frames: 
##      Top-level :  '.fRtop'           , '.fRbottom'         (over-under)
##      Sub-frames:  '.fRtop.fRsliders' , '.fRtop.fRpreview'  (left-right)
##      Sub-sub-frames:
##                  '.fRtop.fRsliders.fRcyan',
##                  '.fRtop.fRsliders.fRmagenta' ,
##                  '.fRtop.fRsliders.fRyellow'             (over-under)
##
##        'fRsliders' and 'fRpreview' are to be on the top of the GUI,
##         on the left and right, respectively.
##        'fRbottom' is to be on the bottom of the GUI.
##
##  1b) Pack ALL frames.
##
##  2) Define & pack all widgets in the frames -- basically going through
##     frames & their interiors in top-to-bottom and/or left-to-right order:
##
##      - '.fRtop.fRsliders'     (to contain 3 sliderbars)
##
##      - '.fRtop.fRpreview'     (to contain only its background color;
##                                to display the color specified by the
##                                sliderbar settings)
##
##      - 'fRbottom'      (to contain several buttons and label & text widgets
##                         to display the current % [0 to 100] and hex [ 00
##                         to FF ] CMY and RGB values of the color.)
##
##  3) Define BINDINGS:  none currently
##
##  4) Define PROCS:
##     -  'color_update'         - called by '-command' options on 3 CMY
##                                 scales --- to update the color swatch
##                                 and the display hex & percent values
##     -  'toggle_side'          - called by ToggleSide button
##     -  'put_vars'             - called by UseIt button
##     -  'popup_msg_var_scroll' - called by Help button
##
##  5) Additional GUI INITIALIZATION:  none, except for setting
##                                     the HELPtext var for the Help button.
##
##+#######################################################################
## DEVELOPED WITH: Tcl-Tk 8.5 on Ubuntu 9.10 (2009 october, 'Karmic Koala)
##                 --- in 2012.
##
##   wish> puts "$tcl_version $tk_version"
##   showed
##   8.5 8.5
##+########################################################################
## FE system Copyright 2012+ by Blaise Montandon
##+########################################################################
## Created by: Blaise Montandon 2012nov13 Started version for the FE
##                                        'tkGooies' system, on Linux,
##                                         using Ubuntu 9.10 (2009oct version).    
## Changed by: ...... ......... 2012nov
##+############################################################################

##+#################################
## SET THE TOP WINDOW NAME.
##+#################################

wm title . \
   "CMY Colors - Integer (0-255)  and  PerCent  and  Hex"

wm iconname . "CMYcolorSel"

# catch { wm title    . "$env(FE_WIN_TITLE)" }
# catch { wm iconname . "$env(FE_ICON_TITLE)" }


##+###################################
##  SET THE TOP WINDOW POSITION.
##+###################################

wm geometry . +50+50

# catch {eval wm geometry . "$env(FE_COLORSEL_GEOM)" }


##+#######################################################################
## SET COLOR SCHEME (palette) FOR THE WINDOW.
##+#######################################################################

##  Gray palette 
set r255pal 210
set g255pal 210
set b255pal 210

set COLOR_pal [format "#%02X%02X%02X" $r255pal $g255pal $b255pal]
tk_setPalette $COLOR_pal


##+#######################################################################
## SET FONT VARS to use in the 'font create' statements below.
##+#######################################################################

set FONTsize 14
set FONT_SMALLsize 12

## For variable width:

set FONT_varwidth \
   " -family {comic sans ms} -size -$FONTsize -weight bold -slant roman "

set FONT_SMALL_varwidth \
   " -family {comic sans ms} -size -$FONT_SMALLsize -weight normal -slant roman "


## For fixed width:

set FONT_fixedwidth \
   " -family {dejavu sans mono} -size -$FONTsize -weight bold -slant roman "


set FONT_SMALL_fixedwidth \
   " -family {dejavu sans mono} -size -$FONT_SMALLsize -weight normal -slant roman "


##+#####################################################################
## DEFINE (temporary) 'font create' NAMES to be used
## in '-font' widget specs below.
##+#####################################################################

eval font create fontTEMP_button  $FONT_varwidth
eval font create fontTEMP_label   $FONT_varwidth

# eval font create fontTEMP_entry   $FONT_fixedwidth
# eval font create fontTEMP_listbox $FONT_fixedwidth
# eval font create fontTEMP_msg     $FONT_fixedwidth
  eval font create fontTEMP_text    $FONT_fixedwidth


# eval font create fontTEMP_SMALL_button  $FONT_SMALL_varwidth
# eval font create fontTEMP_SMALL_label   $FONT_SMALL_varwidth

# eval font create fontTEMP_SMALL_entry   $FONT_SMALL_fixedwidth
# eval font create fontTEMP_SMALL_listbox $FONT_SMALL_fixedwidth
# eval font create fontTEMP_SMALL_msg     $FONT_SMALL_fixedwidth
# eval font create fontTEMP_SMALL_text    $FONT_SMALL_fixedwidth


##+#######################################################################
## SET GEOM VARS FOR THE VARIOUS WIDGET DEFINITIONS.
## (e.g. padx,pady for buttons)
##+#######################################################################

## For BUTTON widgets:

set PADY_button 0
set PADX_button 0
set BDwidth_button 2


## For LABEL widgets:
   
set PADY_label 0
set PADX_label 0
set BDwidth_label 2


## For ENTRY, LISTBOX widgets:

# set BDwidth_entry 2
# set BDwidth_listbox 2


## For TEXT, MESSAGE widgets:

set BDwidth_text 2

# set BDwidth_msg 2



##+########################################################################
##+########################################################################
##
##      GET INPUT PARM VALUES -- 
##      i.e. set C255init M255init Y255init from
##
##         1)  arguments passed to this script
##      OR 
##         2)  environment vars C255, M255, Y255.
##
##+########################################################################
##+########################################################################
##  Example argc/argv processing:
##
## if {$argc == 0} {
##    set VARtext "$env(CONFIRM_TEXT)"   
## } else {
##    set VARtext [lindex $argv 0]
## }
##+########################################################################

if {$argc == 3} {

   set C255init [lindex $argv 0]
   set M255init [lindex $argv 1]
   set Y255init [lindex $argv 2]

} else {

   # set C255init 40
   set C255init 0
   catch { set C255init "$env(C255)" }

   # set M255init 80
   set M255init 156
   catch { set M255init "$env(M255)" }

   # set Y255init 120
   set Y255init 255
   catch { set Y255init "$env(Y255)" }

}


##+################################################
##  SET THE WINDOW SIZE.
## (We 'fix' the size rather than setting a minsize
##  and allowing the window to expand.)
##+################################################

## 'minsize' not used now.  See 'wm resizable . 0 0' below.

#  wm minsize  . 550 200

# catch {eval wm minsize . "$env(FE_COLORSEL_MINSIZE)" }


## Alternative to minsize (make win not resizable):

wm resizable . 0 0


##+################################################
##  Load a TEXT-ARRAY variable with text for
## labels and other GUI widgets --- to facilitate
## 'internationalization' of this script.
###################################################

set aRtext(labelCYAN)    "Cyan"
set aRtext(labelMAGENTA) "Magenta"
set aRtext(labelYELLOW)  "Yellow"

set aRtext(butRETURN)    "UseIt"
set aRtext(butCANCEL)    "Cancel"
set aRtext(butTOGSIDE)   "ToggleSide"
set aRtext(butHELP)      "Help"

set aRtext(labCMYHEX)    "CMY-hex:" ;# not used?
set aRtext(labCMYPCNT)   "CMY-%s:"

set aRtext(labRGBHEX)    "RGB-hex:"
set aRtext(labRGBPCNT)   "RGB-%s:"


##+####################################################################
##+####################################################################
## DEFINE *ALL* THE FRAMES:
##   TOP-LEVEL FRAMES:
##     - 'fRsliders'  - to contain 3 sliderbars.
##     - 'fRpreview'  - to contain only its background; to display
##                      the color specified by the sliderbar settings.
##     - 'fRbottom'   - to contain several buttons (UseIt,Cancel,etc), 
##                      as well as 2 label & 2 text widgets to
##                      display the current per-cent and 
##                      hex RGB-values of the color.
##
##  'fRsliders' & 'fRpreview' are packed on left & right.
##
##   SUB-FRAMES:
##     - 'fRsliders.fRcyan'     (to contain 1 label, 1 sliderbar)
##     - 'fRsliders.fRmagenta'   (to contain 1 label, 1 sliderbar)
##     - 'fRsliders.fRyellow'    (to contain 1 label, 1 sliderbar)
##+####################################################################
##+####################################################################

## FOR TESTING of expansion of frames (esp. during window expansion):
# set RELIEF_frame raised
# set BDwidth_frame 2

set RELIEF_frame flat
set BDwidth_frame 0

frame .fRtop      -relief $RELIEF_frame  -borderwidth $BDwidth_frame
frame .fRbottom   -relief $RELIEF_frame  -borderwidth $BDwidth_frame


frame .fRtop.fRsliders  -relief $RELIEF_frame  -borderwidth $BDwidth_frame

frame .fRtop.fRpreview  -relief raised   -borderwidth 4 \
    -width 300 -height 150
## Note: We could set the size of this 'preview' frame according
## to the screensize, say about 15 to 20% of the screen width and height.

frame .fRtop.fRsliders.fRcyan \
   -relief $RELIEF_frame \
   -borderwidth $BDwidth_frame

frame .fRtop.fRsliders.fRmagenta \
   -relief $RELIEF_frame \
   -borderwidth $BDwidth_frame

frame .fRtop.fRsliders.fRyellow \
   -relief $RELIEF_frame  \
   -borderwidth $BDwidth_frame


##+########################################################
## PACK *ALL* the FRAMES.
##+########################################################

pack  .fRtop \
      .fRbottom \
   -side top \
   -anchor nw \
   -fill none \
   -expand 0

## Set a variable to indicate the current
## '-side' option of the 2 subframes ---
## 'fRsliders' and 'fRpreview'.

set LR_side "left"

##+##########################################
## NOTE:  The variable 'LR_side' will
##        be used to help toggle the position
##        of 'fRpreview' to left or right.
##+##########################################

pack .fRtop.fRsliders \
     .fRtop.fRpreview \
   -side $LR_side \
   -anchor w \
   -fill none \
   -expand 0

pack .fRtop.fRsliders.fRcyan \
     .fRtop.fRsliders.fRmagenta \
     .fRtop.fRsliders.fRyellow \
   -side top \
   -anchor w \
   -fill none \
   -expand 0

## OK. All frames are defined.

##+################################################################
##+################################################################
## START DEFINING & PACKING WIDGETS WITHIN THEIR FRAMES. 
##+################################################################
##+################################################################

##+########################################################
## IN THE '.fRtop.fRsliders' frame -
## DEFINE 3 LABELS and 3 SLIDERBAR WIDGETS.
## THEN PACK THEM.
##+########################################################

## CYAN

label .fRtop.fRsliders.fRcyan.label \
   -text "$aRtext(labelCYAN)" \
   -width 8 \
   -font fontTEMP_label \
   -padx $PADX_label \
   -pady $PADY_label \
   -bd $BDwidth_label

scale .fRtop.fRsliders.fRcyan.scale \
   -orient horizontal \
   -from 0 -to 255 \
   -digits 0 \
   -length 350 \
   -command "color_update"

.fRtop.fRsliders.fRcyan.scale set $C255init


## MAGENTA 

label .fRtop.fRsliders.fRmagenta.label \
   -text "$aRtext(labelMAGENTA)" \
   -width 8 \
   -font fontTEMP_label \
   -padx $PADX_label \
   -pady $PADY_label \
   -bd $BDwidth_label

scale .fRtop.fRsliders.fRmagenta.scale \
   -orient horizontal \
   -from 0 -to 255 \
   -digits 0 \
   -length 350 \
   -command "color_update"

.fRtop.fRsliders.fRmagenta.scale set $M255init


## YELLOW

label .fRtop.fRsliders.fRyellow.label \
   -text "$aRtext(labelYELLOW)" \
   -width 8 \
   -font fontTEMP_label \
   -padx $PADX_label \
   -pady $PADY_label \
   -bd $BDwidth_label

scale .fRtop.fRsliders.fRyellow.scale \
   -orient horizontal \
   -from 0 -to 255 \
   -digits 0 \
   -length 350 \
   -command "color_update"

.fRtop.fRsliders.fRyellow.scale set $Y255init


## Pack ALL the widgets --- 3 LABELS & 3 SCALES
## --- in frame '.fRtop.fRsliders'.

pack .fRtop.fRsliders.fRcyan.label \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRtop.fRsliders.fRcyan.scale \
   -side left \
   -anchor w \
   -fill x \
   -expand 0

pack .fRtop.fRsliders.fRmagenta.label \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRtop.fRsliders.fRmagenta.scale \
   -side left \
   -anchor w \
   -fill x \
   -expand 0

pack .fRtop.fRsliders.fRyellow.label \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRtop.fRsliders.fRyellow.scale \
   -side left \
   -anchor w \
   -fill x \
   -expand 0


###########################################################
## IN THE '.fRtop.fRpreview' FRAME - 
## there are no widgets.  We simply set its
## background color in the 'color_update' proc.
###########################################################


##+########################################################
## IN THE '.fRbottom' FRAME -
## DEFINE several BUTTONS ---
## 1 'UseIt' BUTTON, 1 'Cancel' BUTTON,
## 1 'Toggle-Side' BUTTON, 1 'Help' BUTTON, and
## several TEXT WIDGETS for CMY and RGB hex and percent
## values (rather than a label or message widget,
## so that it is possible to paste the hex & percent
## values to another window).
## THEN PACK THE WIDGETS.
##+########################################################

button .fRbottom.butRETURN \
   -text "$aRtext(butRETURN)" \
   -font fontTEMP_button \
   -padx $PADX_button \
   -pady $PADY_button \
   -relief raised \
   -bd $BDwidth_button \
   -command {put_vars}

button .fRbottom.butCANCEL \
   -text "$aRtext(butCANCEL)" \
   -font fontTEMP_button \
   -padx $PADX_button \
   -pady $PADY_button \
   -relief raised \
   -bd $BDwidth_button \
   -command {exit}

button .fRbottom.butTOGSIDE \
   -text "$aRtext(butTOGSIDE)" \
   -font fontTEMP_button \
   -padx $PADX_button \
   -pady $PADY_button \
   -relief raised \
   -bd $BDwidth_button \
   -command {toggle_side}

button .fRbottom.butHELP \
   -text "$aRtext(butHELP)" \
   -font fontTEMP_button \
   -padx $PADX_button \
   -pady $PADY_button \
   -relief raised \
   -bd $BDwidth_button \
   -command {popup_msg_var_scroll "$HELPtext"}


## CMY text fields (CMY in percents & hex):

label .fRbottom.labCMYPCNT \
   -text "$aRtext(labCMYPCNT)" \
   -font fontTEMP_label \
   -padx $PADX_label \
   -pady $PADY_label \
   -relief flat \
   -bd $BDwidth_label

text .fRbottom.txtCMYPCNT \
   -relief raised \
   -borderwidth $BDwidth_text \
   -height 1 \
   -width 11 \
   -wrap none \
   -font fontTEMP_text

if {0} {
label .fRbottom.labCMYHEX \
   -text "$aRtext(labCMYHEX)" \
   -font fontTEMP_label \
   -padx $PADX_label \
   -pady $PADY_label \
   -relief flat \
   -bd $BDwidth_label

text .fRbottom.txtCMYHEX \
   -relief raised \
   -borderwidth $BDwidth_text \
   -height 1 \
   -width  8 \
   -wrap none \
   -font fontTEMP_text
}
## END OF if {0}  (to hide this code)


## RGB text fields (RGB in percents & hex):

label .fRbottom.labRGBPCNT \
   -text "$aRtext(labRGBPCNT)" \
   -font fontTEMP_label \
   -padx $PADX_label \
   -pady $PADY_label \
   -relief flat \
   -bd $BDwidth_label

text .fRbottom.txtRGBPCNT \
   -relief raised \
   -borderwidth $BDwidth_text \
   -height 1 \
   -width 11 \
   -wrap none \
   -font fontTEMP_text

label .fRbottom.labRGBHEX \
   -text "$aRtext(labRGBHEX)" \
   -font fontTEMP_label \
   -padx $PADX_label \
   -pady $PADY_label \
   -relief flat \
   -bd $BDwidth_label

text .fRbottom.txtRGBHEX \
   -relief raised \
   -borderwidth $BDwidth_text \
   -height 1 \
   -width  8 \
   -wrap none \
   -font fontTEMP_text



## Pack ALL the widgets in frame '.fRbottom'.

pack .fRbottom.butRETURN \
     .fRbottom.butCANCEL \
     .fRbottom.butTOGSIDE \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRbottom.butHELP \
   -side left \
   -anchor w \
   -fill none \
   -expand 0 \
   -padx {0 10}

pack .fRbottom.labCMYPCNT \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRbottom.txtCMYPCNT \
   -side left \
   -anchor w \
   -fill none \
   -expand 0 \
   -padx {0 10}


## COMMENTED, for now.
#     .fRbottom.labCMYHEX \
#     .fRbottom.txtCMYHEX \

pack .fRbottom.labRGBPCNT \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRbottom.txtRGBPCNT \
   -side left \
   -anchor w \
   -fill none \
   -expand 0 \
   -padx {0 10}

pack .fRbottom.labRGBHEX \
     .fRbottom.txtRGBHEX \
   -side left \
   -anchor w \
   -fill none \
   -expand 0
   

##+#######################################
## END OF MAIN SECTION TO SETUP THE GUI.
## FRAMES AND WIDGETS ARE DEFINED.
##+#######################################

##+#####################################################################
## BINDINGS SECTION: none
##+#####################################################################


##+#####################################################################
## PROCS SECTION:
##    -  'color_update'
##    -  'toggle_side'
##    -  'put_vars'
##    -  'popup_msg_var_scroll'
##+#####################################################################

##+#####################################################################
## proc  'color_update'
##+##################################################################### 
## PURPOSE: To set 
##             1) the background color of the preview window 
##             2) display the hex value of the CMY and RGB values 
##          as any slider is changed.
## 
## CALLED BY:  the '-command' option of 3 scale widgets:
##               - .fRtop.fRsliders.fRcyan.scale
##               - .fRtop.fRsliders.fRmagenta.scale
##               - .fRtop.fRsliders.fRyellow.scale
##+#####################################################################

proc color_update {x} {

   global c255 m255 y255

   set c255   [.fRtop.fRsliders.fRcyan.scale   get]
   set m255   [.fRtop.fRsliders.fRmagenta.scale get]
   set y255   [.fRtop.fRsliders.fRyellow.scale  get]


   ## Set the background color of frame '.fRtop.fRpreview'.

   set r255 [expr {255 - $c255}]
   set g255 [expr {255 - $m255}]
   set b255 [expr {255 - $y255}]
   set hexRGBcolor [format "#%02X%02X%02X" $r255 $g255 $b255]

   .fRtop.fRpreview config -bg $hexRGBcolor


   ## Show the CMY values in percents.

   set c_pcnt [expr {$c255  * 100 / 255}]
   set m_pcnt [expr {$m255  * 100 / 255}]
   set y_pcnt [expr {$y255  * 100 / 255}]

   .fRbottom.txtCMYPCNT delete 1.0 end
   .fRbottom.txtCMYPCNT insert end "$c_pcnt $m_pcnt $y_pcnt"


   ## Show the RGB values in hex.

   .fRbottom.txtRGBHEX delete 1.0 end
   .fRbottom.txtRGBHEX insert end $hexRGBcolor


   ## Show the RGB values in percents.

   set r_pcnt [expr {$r255  * 100 / 255}]
   set g_pcnt [expr {$g255  * 100 / 255}]
   set b_pcnt [expr {$b255  * 100 / 255}]

   .fRbottom.txtRGBPCNT delete 1.0 end
   .fRbottom.txtRGBPCNT insert end "$r_pcnt $g_pcnt $b_pcnt"

}
## END of proc 'color_update'


##+#####################################################################
## proc  'toggle_side'
##+##################################################################### 
## PURPOSE: To switch the sides of the 'sliders' & 'preview' frames 
## 
## CALLED BY:  .fRbottom.butTOGSIDE button
##+#####################################################################

proc toggle_side { } {

   global LR_side

   if { "$LR_side" == "left" } {
      set LR_side "right"
   } else {
      set LR_side "left"
   }

   ## Re-pack the side-by-side frames 'fRsliders' and 'fRpreview'
   ## to the opposite sides from where they were.
 
   pack forget .fRtop.fRsliders .fRtop.fRpreview

   pack .fRtop.fRsliders \
        .fRtop.fRpreview \
      -side $LR_side \
      -anchor w \
      -fill both \
      -expand 1

}
## END of proc 'toggle_side'


##+#####################################################################
## proc  'put_vars'
########################################################################
## PURPOSE: Puts RGB values (three 0-255 values and one hex value)
##          to standard output.
##
## CALLED BY:   .fRbottom.butRETURN button
##+#####################################################################

proc put_vars { } {

   global c255 m255 y255

   set r255 [expr {255 - $c255}]
   set g255 [expr {255 - $m255}]
   set b255 [expr {255 - $y255}]

   set rgbCOLORhex [format "%02X%02X%02X" $r255 $g255 $b255]

   ## ALTERNATIVE:
   ##    puts "R255=\"$r255\" ; G255=\"$g255\" ; B255=\"$b255\""

   puts "$r255 $g255 $b255 $rgbCOLORhex"

   exit

}
## END of proc  'puts_vars'


##+########################################################################
## PROC 'popup_msg_var_scroll'
##+########################################################################
## PURPOSE: Report help or error conditions to the user.
## CALLED BY: 'help' button
##+########################################################################
## To have more control over the formatting of the message (esp.
## words per line), we use this 'toplevel-text' method, 
## rather than the 'tk_dialog' method -- like on page 574 of the book 
## by Hattie Schroeder & Mike Doyel,'Interactive Web Applications
## with Tcl/Tk', Appendix A "ED, the Tcl Code Editor".
##+########################################################################

proc popup_msg_var_scroll { VARtext } {

   ## global fontTEMP_text #; Not needed. 'wish' makes this global.
   ## global env

   # bell
   # bell
  
   #################################################
   ## Set VARwidth & VARheight from $VARtext.
   #################################################
   ## To get VARheight,
   ##    split at '\n' (newlines) and count 'lines'.
   #################################################
 
   set VARlist [ split $VARtext "\n" ]

   ## For testing:
   #  puts "VARlist: $VARlist"

   set VARheight [ llength $VARlist ]

   ## For testing:
   #  puts "VARheight: $VARheight"


   #################################################
   ## To get VARwidth,
   ##    loop through the 'lines' getting length
   ##     of each; save max.
   #################################################

   set VARwidth 0

   #############################################
   ## LOOK AT EACH LINE IN THE LIST.
   #############################################
   foreach line $VARlist {

      #############################################
      ## Get the length of the line.
      #############################################
      set LINEwidth [ string length $line ]

      if { $LINEwidth > $VARwidth } {
         set VARwidth $LINEwidth 
      }

   }
   ## END OF foreach line $VARlist

   ## For testing:
   #   puts "VARwidth: $VARwidth"


   ###############################################################
   ## NOTE: VARwidth works for a fixed-width font used for the
   ##       text widget ... BUT the programmer may need to be
   ##       careful that the contents of VARtext are all
   ##       countable characters by the 'string length' command.
   ###############################################################


   #####################################
   ## SETUP 'TOP LEVEL' HELP WINDOW.
   #####################################

   catch {destroy .fRtopmsg}
   toplevel  .fRtopmsg

   # wm geometry .fRtopmsg 600x400+100+50

   wm geometry .fRtopmsg +100+50

   wm title     .fRtopmsg "Note"
   # wm title   .fRtopmsg "Note to $env(USER)"

   wm iconname  .fRtopmsg "Note"


   #####################################
   ## In frame '.fRtopmsg' -
   ## DEFINE a TEXT widget and scrollbars,
   ## and a BUTTON widget.
   #####################################

   text .fRtopmsg.text \
      -wrap none \
      -font fontTEMP_text \
      -width  $VARwidth \
      -height $VARheight \
      -bg "#f0f0f0" \
      -relief raised \
      -bd 2 \
      -yscrollcommand ".fRtopmsg.scrolly set" \
      -xscrollcommand ".fRtopmsg.scrollx set"

   scrollbar .fRtopmsg.scrolly \
                 -orient vertical \
      -command ".fRtopmsg.text yview"

   scrollbar .fRtopmsg.scrollx \
                -orient horizontal \
                -command ".fRtopmsg.text xview"

   button .fRtopmsg.butt \
      -text "OK" \
      -font fontTEMP_button \
      -command  "destroy .fRtopmsg"


   #######################################
   ## PACK ALL the widgets in '.fRtopmsg'.
   #######################################

   ## Pack the button at the bottom, before
   ## we pack the x-scrollbar there.

   pack  .fRtopmsg.butt \
      -side bottom \
      -anchor center \
      -fill none \
      -expand 0


   ## Pack the scrollbars BEFORE the text widget,
   ## so that the text does not monopolize the space.

   pack .fRtopmsg.scrolly \
      -side right \
      -anchor center \
      -fill y \
      -expand 0

   ## DO NOT USE '-expand 1' on the Y-scrollbar.
   ## THAT ALLOWS Y-SCROLLBAR TO EXPAND AND PUTS
   ## BLANK SPACE BETWEEN Y-SCROLLBAR & THE TEXT AREA.
                
   pack .fRtopmsg.scrollx \
      -side bottom \
      -anchor center \
      -fill x  \
      -expand 0

   ## DO NOT USE '-expand 1' on the X-scrollbar.
   ## THAT KEEPS THE TEXT AREA FROM EXPANDING.

   pack .fRtopmsg.text \
      -side top \
      -anchor center \
      -fill both \
      -expand 1



   #####################################
   ## LOAD MSG INTO TEXT WIDGET.
   #####################################

   ##  .fRtopmsg.text delete 1.0 end
 
   .fRtopmsg.text insert end $VARtext
   
   .fRtopmsg.text configure -state disabled
  
}
## END OF PROC 'popup_msg_var_scroll'


##+###############################################
## END OF PROCS SECTION.
##+###############################################
## ADDITONAL GUI INITIALIZATION FOLLOWS.
## (We simply set the help-text variable here.)
##+###############################################

set HELPtext \
"
** CMY-255 Color Selector HELP ***

Sometimes people want to set a color by color name --- or, perhaps,
get an idea of numbers to use to get in a certain color 'area' and
then fine tune from there.

So here is a little HELP on setting colors that you know by name
using appropriate CMY values.

Most people know that:
   yellow  = red   + green ; bright-yellow  = 255 255 0 (add some blue  to brighten further)
   magenta = red   + blue  ; bright-magenta = 255 0 255 (add some green to brighten further)
   cyan    = green + blue  ; bright-cyan    = 0 255 255 (add some red   to brighten further)

So it seems that if you combine
   yellow  + magenta = lots of red   and a medium amount of blue and green
   magenta + cyan    = lots of blue  and a medium amount of cyan and yellow
   cyan    + yellow  = lots of green and a medium amount of red and blue 

It turns out that a good way to think of the relationship of CMY to RGB is:

yellow  is the opposite of blue  ( yellow  = 255 - blue , in 'base 255')
magenta is the opposite of green ( magenta = 255 - green, in 'base 255')
cyan    is the opposide of red   ( cyan    = 255 - red  , in 'base 255')

For some colors, like brown, it is not so obvious which CMY (or RGB) colors
to use to create the color.

Brown is basically lots of red, a medium amount of green, and even less blue.
So a medium brightness brown is about RGB(200,150,100).

In CMY-255, using our 3 RGB-to-CMY equations above,
a medium brightness brown is about CMY(55,105,155).

In particular, 'sandy brown' is RGB(244,164,96).
In CMY-255, 'sandy brown' is CMY(11,91,159).

X color names and their RGB values are shown by the command 'showrgb'.
We could run the output of 'showrgb' through a script using 'awk' or Tcl,
and convert the RGB-255 values to CMY-255.

After I do that, I will fill out the following sections.

Here are some color-names and CMY-255 values for RED-ISH colors,
in alphabetic order by name:

brown,sandy     RGB(244   164    96)   CMY(? ? ?)
coral1          RGB(255   114    86)   CMY(? ? ?)
firebrick       RGB(178    34    34)   CMY(? ? ?)
khaki,dark      RGB(189   183   107)   CMY(? ? ?)
lavenderblush3  RGB(205   193   197)   CMY(? ? ?)
magenta3        RGB(205     0   205)   CMY(? ? ?)
maroon1         RGB(255    52   179)   CMY(? ? ?)
orange,dark     RGB(255   127     0)   CMY(? ? ?)
orchid1         RGB(255   131   250)   CMY(? ? ?)
peach puff      RGB(255   218   185)   CMY(? ? ?)
pink,deep       RGB(255    20   147)   CMY(? ? ?)
pink3           RGB(205   145   158)   CMY(? ? ?)
plum            RGB(221   160   221)   CMY(? ? ?)
rose,misty,3    RGB(205   183   181)   CMY(? ? ?)
salmon1         RGB(255   140   105)   CMY(? ? ?)
sienna1         RGB(255   130    71)   CMY(? ? ?)
tan1            RGB(255   165    79)   CMY(? ? ?)
tomato          RGB(255    99    71)   CMY(? ? ?)
violet          RGB(238   130   238)   CMY(? ? ?)
wood,burly,3    RGB(205   170   125)   CMY(? ? ?)


Here are some names and CMY values for GREEN-ISH colors,
in alphabetic order by name:

aquamarine2     RGB(118   238   198)   CMY(? ? ?)
chartreuse      RGB(127   255     0)   CMY(? ? ?)
cyan,light      RGB(224   255   255)   CMY(? ? ?)
green,lawn      RGB(124   252     0)   CMY(? ? ?)
green,lime      RGB( 50   205    50)   CMY(? ? ?)
green,pale      RGB(152   251   152)   CMY(? ? ?)
green,sea,1     RGB( 84   255   159)   CMY(? ? ?)
green,spring    RGB(  0   255   127)   CMY(? ? ?)
honeydew        RGB(240   255   240)   CMY(? ? ?)
mint cream      RGB(245   255   250)   CMY(? ? ?)
olivedrab2      RGB(179   238    58)   CMY(? ? ?)
turquoise       RGB( 64   224   208)   CMY(? ? ?)


Here are some names and CMY values for BLUE-ISH colors,
in alphabetic order by name:

azure2          RGB(224   238   238)   CMY(? ? ?)
blue,cornflower RGB(100   149   237)   CMY(? ? ?)
blue,dodger     RGB( 30   144   255)   CMY(? ? ?)
blue,midnight   RGB( 25    25   112)   CMY(? ? ?)
lavender        RGB(230   230   250)   CMY(? ? ?)
lightblue2      RGB(178   223   238)   CMY(? ? ?)
purple1         RGB(155    48   255)   CMY(? ? ?)
royalblue1      RGB( 72   118   255)   CMY(? ? ?)
skyblue1        RGB(135   206   255)   CMY(? ? ?)
steelblue1      RGB( 99   184   255)   CMY(? ? ?)
slateblue1      RGB(131   111   255)   CMY(? ? ?)
turquoise1      RGB(  0   245   255)   CMY(? ? ?)
turquoise,pale  RGB(175   238   238)   CMY(? ? ?)

Of course, you can scale the RGB values down to get
darker colors. And you can scale the RGB values up
to get brighter colors.
"


I hope that some Tcl-Tk 'newbies' can learn from this Tk script. The 'pack forget' technique (seen in the 'toggle_side' proc) might open some eyes as to how flexible Tk is --- namely, Tk can be used to make some rather 'wild' self-re-configuring GUI's.

And who knows? Maybe there are some CMY fans out there who really don't like to specify colors in RGB terms. So maybe this GUI can be of use to someone, other than as an exercise in Tk coding.

Maybe there are some people who think in terms of 'complementary' colors rather than 'primary' colors --- people who think in terms of 'subtractive' colors rather than 'additive' colors --- people who think in terms of CMY rather than RGB. Perhaps painters and printers. Others?