############################################################################ # # File: palettes.icn # # Subject: Procedures for programmer-defined palettes # # Author: Ralph E. Griswold # # Date: January 23, 2001 # ############################################################################ # # This file is in the public domain. # ############################################################################ # # These procedures implement programmer-defined palettes. They overload # and build on top of the built-in palette mechanism. # ############################################################################ # # Data structures: # # Palette_() is a record that holds the information for a # programmer-defined palette. Its fields are: # # name: the name the palette is known by # keys: the string of the palette characters # table: a table keyed by the palette characters # whose corresponding values are the colors # # Color_() is a record that holds the components of an RGB # color in separate r, g, and b fields. # # PDB_ is a table whose keys are the names of programmer- # defined palettes and whose corresponding values are the # palettes. PDB_ is a global variable and provides the # way for programmer-defined palette procedures to access # a particular database. If it is null, a new database is # created. # # Procedures: # # BuiltinPalette(name) # succeeds if name is the name of a built-in palette but # fails otherwise. # # CreatePalette(name, keys, colors) # creates a new palette with the given colors and # corresponding keys. The colors used are the given ones. # # InitializePalettes() # initializes the built-in palette mechanism; it is called # by the first palette procedure that is called. # # Measure(color1, color2) returns the a measure of the distance # between color1 and color2 in RGB space. # # NearColor(name, color) # returns a color close to color in the palette name. # # PaletteChars(win, palette) # returns the palette characters of palette. It extends # the standard version. # # PaletteColor(win, palette, key) # returns color in palette for the given key. It extends # the standard version. # # PaletteKey(win, palette, color) # returns the key in palette closest to the given color. # # RGB(color) # parses RGB color and returns a corresponding record. # # makepalette(name, clist) # makes a palette from the list of colors, choosing # keys automatically. # # palette_colors(palette) # # returns the list of colors in palette. # # Procedures fail in case of errors. This leaves control and error # reporting to programs that use this module. This module is intended # to be used by programs that manage the necessary data and supply # the table through PDB_. The problem with this is that there is # no way to differentiate errors. A solution would be to post error # messages in a global variable. # # Limitations and problems: # # The names of built-in palettes may not be used for programmer- # defined ones. # # PaletteGrays() is not implemented for programmer-defined # palettes. The library version should work for built-in # palettes with this module linked. # # Transparency is not yet implemented for DrawImage(). # # ReadImage() does not yet support programmer defined palettes. # # Not tested: Capture(), which may work. # # There is some library code that checks for the names of # built-in palettes in an ad-hoc fashion. It therefore is # not advisable to use names for programmer-defined palettes # that begin with "c" or "g" followed by a digit. # ############################################################################ # # Requires: Version 9 graphics # ############################################################################ # # Links: imrutils, lists, sort # ############################################################################ link imrutils link lists link sort global PDB_ record Palette_(name, keys, table) record Color_(r, g, b) # Check for built-in palette procedure BuiltinPalette(name) #: check for built-in palette BuiltinPalette := proc("PaletteChars", 0) return BuiltinPalette(name) end procedure CreatePalette(name, keys, colors) #: create palette local i, k, t initial InitializePalettes() if BuiltinPalette(name) then fail if *keys ~= *cset(keys) then fail # duplicate keys if *keys ~= *colors then fail # mismatch t := table() every i := 1 to *colors do t[keys[i]] := ColorValue(colors[i]) | fail PDB_[name] := Palette_(name, keys, t) return PDB_[name] end # Extended version of DrawImage() procedure DrawImage(args[]) #: draw image local palette_pixels, palette_lookup, keys, c, i, row, imr static draw_image initial draw_image := proc("DrawImage", 0) if type(args[1]) ~== "window" then push(args, &window) imr := imstoimr(args[4]) | return draw_image ! args if BuiltinPalette(imr.palette) then return draw_image ! args palette_lookup := (\PDB_[imr.palette]).table | fail palette_pixels := copy(palette_lookup) keys := cset(imr.pixels) every !palette_pixels := [] # empty lists for coordinates every c := !keys do { i := 0 imr.pixels ? { while row := move(imr.width) do { row ? { every put(palette_pixels[c], upto(c) - 1, i) } i +:= 1 } } } every c := !keys do { Fg(palette_lookup[c]) | fail # fails for invalid character DrawPoint ! palette_pixels[c] } return end # Initialize defined palette mechanism procedure InitializePalettes() #: initialize palettes /PDB_ := table() if type(PDB_) ~== "table" then runerr(777) InitializePalettes := 1 # make this procedure a no-op return end procedure Measure(s1, s2) #: measure of RGB distance local color1, color2 color1 := RGB(s1) color2 := RGB(s2) return (color1.r - color2.r) ^ 2 + (color1.g - color2.g) ^ 2 + (color1.b - color2.b) ^ 2 end # Get color close to specified key procedure NearColor(name, s) #: close color in palette local palette_lookup, k, measure, close_key, color measure := 3 * (2 ^ 16 - 1) ^ 2 # maximum color := ColorValue(s) | fail palette_lookup := (\PDB_[name]).table | fail every k := key(palette_lookup) do if measure >:= Measure(palette_lookup[k], color) then { close_key := k if measure = 0 then break } return \close_key end # Extended version of PaletteChars() procedure PaletteChars(args[]) #: characters in palette local name static palette_chars initial { InitializePalettes() palette_chars := proc("PaletteChars", 0) } if type(args[1]) == "window" then get(args) name := args[1] if BuiltinPalette(name) then return palette_chars(name) else return (\PDB_[name]).keys end # Extended version of PaletteColor() procedure PaletteColor(args[]) #: color for key in palette local palette_lookup, name, s static palette_color initial { InitializePalettes() palette_color := proc("PaletteColor", 0) } if type(args[1]) == "window" then get(args) name := args[1] s := args[2] if BuiltinPalette(name) then return palette_color(name, s) palette_lookup := (\PDB_[name]).table | fail return \palette_lookup[s] end # Extended version of PaletteKey() procedure PaletteKey(args[]) #: key for color in palette local name, s static palette_key initial { InitializePalettes() palette_key := proc("PaletteKey", 0) } if type(args[1]) == "window" then get(args) name := args[1] s := args[2] if BuiltinPalette(name) then return palette_key(name, s) else return NearColor(name, s) end procedure RGB(s) #: convert RGB color to record local color color := Color_() ColorValue(s) ? { color.r := tab(upto(',')) & move(1) & color.g := tab(upto(',')) & move(1) & color.b := tab(0) } | fail return color end procedure makepalette(name, clist) #: make palette automatically local keys static alphan initial alphan := &digits || &letters if *clist = 0 then fail keys := if *clist < *alphan then alphan else &cset CreatePalette(name, keys[1+:*clist], clist) | fail return end procedure palette_colors(p) #: list of palette colors local clist clist := [] every put(clist, PaletteColor(p, !PaletteChars(p))) return clist end procedure keyseq(palette, colors[]) #: sequence of palette keys local chars chars := PaletteChars(palette) suspend upto(PaletteKey(palette, !colors), chars) end procedure color_range(color, range) #: adjust RGB range local r, g, b range := 2 ^ 16 / range color ? { r := tab(upto(',')) move(1) g := tab(upto(',')) move(1) b := tab(0) return (r * range) || "," || (g * range) || "," || (b * range) } end procedure colorseq(palette) #: sequence of palette colors suspend PaletteColor(palette, !PaletteChars(palette)) end procedure sort_colors(colors) return isort(colors, value) end procedure value(s) #: RGB magnitude local color color := RGB(s) return color.r ^ 2 + color.g ^ 2 + color.b ^ 2 end