1#!/usr/bin/env ruby
2#
3# tcolor --
4#   simple color editor which supports RGB, HSB and CYM color space
5#
6# Copyright (C) 1998 Takaaki Tateishi(ttate@jaist.ac.jp)
7# last update: Thu Jun 18 06:32:35 JST 1998
8#
9
10require "tk"
11
12
13# use TkVariable instance for the variable which is changed by Tk interpreter
14
15$colorSpace = TkVariable.new(:rgb)
16$master = nil
17$red = 65535
18$green = 0
19$blue = 0
20$color = "#ffff00000000"
21$updating = TkVariable.new(0)
22$autoUpdate = TkVariable.new(1)
23$name = TkVariable.new($color)
24$command = TkVariable.new("print(%%,\"\n\")")
25# $command = TkVariable.new("")
26$label1 = TkVariable.new("label1")
27$label2 = TkVariable.new("label2")
28$label3 = TkVariable.new("label3")
29
30
31# setup the entry of the resourc database
32if (TkVarAccess.new('tcl_platform')['platform'] == 'unix')
33  TkOptionDB.add('*Entry.background', 'white')
34end
35
36
37# methods for events
38
39def rgbToHsv(red,green,blue)
40
41  if ( red > green )
42    max = red
43    min = green
44  else
45    max = green
46    min = red
47  end
48
49  if ( blue > max )
50    max = blue
51  else
52    if ( blue < min )
53      min = blue
54    end
55  end
56
57  range = max - min
58
59  if ( max == 0 )
60    sat = 0.0
61  else
62    sat = (max-min)/max
63  end
64
65  if ( sat == 0 )
66    hue = 0.0
67  else
68    rc = (max-red)/range
69    gc = (max-green)/range
70    bc = (max-blue)/range
71    if ( red == max )
72      hue = 0.166667 * (bc - gc)
73    else
74      if ( green == max )
75	hue = 0.166667 * (2.0 + rc - bc)
76      else
77	hue = 0.166667 * (4.0 + gc - rc)
78      end
79    end
80    if ( hue < 0.0 )
81      hue = hue + 1.0
82    end
83  end
84
85  [hue,sat,max/65535]
86end
87
88
89def hsbToRgb(hue,sat,value)
90  v = 65535.0 * value
91  if( sat == 0 )
92    ans = [v,v,v]
93  else
94    hue = hue*6.0
95    if ( hue >= 6 )
96      hue = 0.0
97    end
98    i = hue.to_i
99    f = hue - i
100    p = 65535.0 * value * (1.0 - sat)
101    q = 65535.0 * value * (1.0 - (sat * f))
102    t = 65535.0 * value * (1.0 - (sat * (1.0 - f)))
103    case i
104    when 0
105      ans = [v,t,p]
106    when 1
107      ans = [q,v,p]
108    when 2
109      ans = [p,v,t]
110    when 3
111      ans = [p,q,v]
112    when 4
113      ans = [t,p,v]
114    when 5
115      ans = [v,p,q]
116    else
117      raise(eException,"i value #{i} is out of range")
118    end
119  end
120  return ans
121end
122
123
124def _null_binding
125  Module.new.instance_eval{binding}
126end
127private :_null_binding
128
129def doUpdate
130  newCmd = $command.to_s.gsub("%%","\"#{$color}\"")
131  eval(newCmd, _null_binding)
132end
133
134
135def tc_scaleChanged
136  if( $updating.to_i == 1 )
137    return
138  end
139
140  $master = :scale if $master == nil
141
142  scale1 = $root.middle.middle.scale1
143  scale2 = $root.middle.middle.scale2
144  scale3 = $root.middle.middle.scale3
145
146  case $colorSpace.value.intern
147  when :rgb
148    $red = (scale1.get * 65.535).to_i
149    $green = (scale2.get * 65.535).to_i
150    $blue = (scale3.get * 65.535).to_i
151  when :cmy
152    $red = (65535 - scale1.get * 65.535).to_i
153    $green = (65535 - scale2.get * 65.535).to_i
154    $blue = (65535 - scale3.get * 65.535).to_i
155  when :hsb
156    list = hsbToRgb(scale1.get / 1000.0,
157		    scale2.get / 1000.0,
158		    scale3.get / 1000.0)
159    $red = list[0]
160    $green = list[1]
161    $blue = list[2]
162  else
163    raise(Exception,"unknown colorSpace")
164  end
165  $color = format("#%04x%04x%04x",$red.to_i,$green.to_i,$blue.to_i)
166  $name.value = $color if $master == :scale
167  $root.middle.right.set_color($color)
168  if( $autoUpdate.to_i == 1 )
169    doUpdate
170  end
171  Tk.update(true)
172  $master = nil if $master == :scale
173end
174
175
176def tc_setScales
177  $updating.value = 1
178
179  scale1 = $root.middle.middle.scale1
180  scale2 = $root.middle.middle.scale2
181  scale3 = $root.middle.middle.scale3
182
183  case $colorSpace.value.intern
184  when :rgb
185    scale1.set($red / 65.535)
186    scale2.set($green / 65.535)
187    scale3.set($blue / 65.535)
188  when :cmy
189    scale1.set((65535 - $red) / 65.535)
190    scale2.set((65535 - $green) / 65.535)
191    scale3.set((65535 - $blue) / 65.535)
192  when :hsb
193    list = rgbToHsv($red,$green,$blue)
194    scale1.set( list[0] * 1000.0 )
195    scale2.set( list[1] * 1000.0 )
196    scale3.set( list[2] * 1000.0 )
197  else
198    raise(Exception,"unknown colorSpace")
199  end
200
201  $updating.value = 0
202end
203
204
205def tc_loadNamedColor(name)
206  $name.value = name
207  $master = :name if $master == nil
208  if name[0,1] != "#"
209    list = TkWinfo.rgb($root.middle.right.swatch,name)
210    $red = list[0]
211    $green = list[1]
212    $blue = list[2]
213  else
214    case name.length
215    when 4
216      fmt = /#(.{1})(.{1})(.{1})/
217      shift = 12
218    when 7
219      fmt = /#(.{2})(.{2})(.{2})/
220      shift = 8
221    when 10
222      fmt = /#(.{3})(.{3})(.{3})/
223      shift = 4
224    when 13
225      fmt = /#(.{4})(.{4})(.{4})/
226      shift = 0
227    else
228      raise(eException,"syntax error in color name \"#{name}\"")
229    end
230    name.scan(fmt){|strlist|
231      if strlist.length != 3
232	raise(eException,"syntax error in color name \"#{name}\"")
233      end
234      $red = strlist[0].hex
235      $green = strlist[1].hex
236      $blue = strlist[2].hex
237    }
238    $red = $red << shift
239    $green = $green << shift
240    $blue = $blue << shift
241  end
242
243  tc_setScales
244  $color = format("#%04x%04x%04x",$red,$green,$blue)
245  $root.middle.right.set_color($color)
246  if $autoUpdate.to_i == 1
247    doUpdate
248  end
249  Tk.update(true)
250  $master = nil if $master == :name
251end
252
253
254def changeColorSpace(space)
255  case space
256  when :rgb
257    $label1.value = "Red"
258    $label2.value = "Green"
259    $label3.value = "Blue"
260  when :cmy
261    $label1.value = "Cyan"
262    $label2.value = "Magenta"
263    $label3.value = "Yellow"
264  when :hsb
265    $label1.value = "Hue"
266    $label2.value = "Saturation"
267    $label3.value = "Brightness"
268  end
269  tc_setScales
270end
271
272
273# menu
274
275class TkColorMenuFrame<TkFrame
276  def initialize(parent)
277    super(parent,
278	  "relief"=>"raised",
279	  "borderwidth"=>"2")
280
281    # File menubutton
282    @file = TkMenubutton.new(self){|button|
283
284      # File menu
285      @file_menu = TkMenu.new(button){
286	add "radio",
287	  "label" => "RGB color space",
288	  "variable" => $colorSpace,
289	  "value" => :rgb,
290	  "underline" => "0",
291	  "command" => proc{changeColorSpace(:rgb)}
292	add "radio",
293	  "label" => "CMY color space",
294	  "variable" => $colorSpace,
295	  "value" => :cmy,
296	  "underline" => "0",
297	  "command" => proc{changeColorSpace(:cmy)}
298	add "radio",
299	  "label" => "HSB color space",
300	  "variable" => $colorSpace,
301	  "value" => :hsb,
302	  "underline" => "0",
303	  "command" => proc{changeColorSpace(:hsb)}
304	add "separator"
305	add "radio",
306	  "label" => "Automatic updates",
307	  "variable" => $autoUpdate,
308	  "value" => "1",
309	  "underline" => "0"
310	add "radio",
311	  "label" => "Manual updates",
312	  "variable" => $autoUpdate,
313	  "value" => "0",
314	  "underline" => "0"
315	add "separator"
316	add "command",
317	  "label" => "Exit program",
318	  "underline" => "0",
319	  "command" => proc{exit}
320      }
321
322      # assign File menu to File button
323      menu @file_menu
324
325      text "File"
326      underline "0"
327    }.pack("side"=>"left")
328
329    self
330  end
331end
332
333
334# bottom frame
335class TkColorBotFrame<TkFrame
336  def initialize(parent)
337    super(parent,
338	  "relief"=> "raised",
339	  "borderwidth"=> 2)
340
341    @commandLabel = TkLabel.new(self,
342				"text"=> "Command:")
343    @command = TkEntry.new(self,
344			   "relief"=> "sunken",
345			   "borderwidth"=> "2",
346			   "textvariable"=> $command,
347			   "font"=> "-Adobe-Courier-Medium-R-Normal--*-120-*-*-*-*-*-*")
348    @update = TkButton.new(self,
349			   "text"=> "Update",
350			   "command"=> proc{doUpdate})
351    @commandLabel.pack("side"=>"left")
352    @update.pack("side"=>"right","pady"=>".1c","padx"=>".25c")
353    @command.pack("expand"=>"yes","fill"=>"x","ipadx"=>".25c")
354
355    self
356  end
357end
358
359
360# left side frame of middle level
361class TkColorMiddleLeftFrame<TkFrame
362  def initialize(parent)
363    super(parent)
364
365    for i in ["/usr/local/lib/X11rgb.txt","/usr/lib/X11/rgb.txt",
366	"/X11/R5/lib/X11/rgb.txt","/X11/R4/lib/rgb/rgb.txt",
367	"/usr/openwin/lib/X11/rgb.txt"]
368      if !File.readable?(i)
369	next
370      end
371      f = File.open(i)
372      @scroll = TkScrollbar.new(self,
373				"orient"=>"vertical",
374				"relief"=>"sunken",
375				"borderwidth"=>"2")
376      @scroll.pack("side"=>"right","fill"=>"y")
377      @names = TkListbox.new(self,
378			     "width"=>"20",
379			     "height"=>"12",
380			     "yscrollcommand"=> proc{|first,last| @scroll.set first,last},
381			     "relief"=>"sunken",
382			     "borderwidth"=>"2",
383			     "exportselection"=>"false")
384      @scroll.command(proc{|*args| @names.yview(*args)})
385      @names.bind("Double-1",proc{
386		    tc_loadNamedColor(@names.get(@names.curselection))})
387      @names.pack("side"=>"left")
388      while (line = f.gets)
389	line.chop!
390	linelist = line.split(/[ \t]+/)
391	if linelist.length == 4
392	  @names.insert("end",linelist[3])
393	end
394      end
395      f.close
396      break
397    end
398
399    self
400  end
401end
402
403
404# middle frame of middle level
405class TkColorMiddleMiddleFrame<TkFrame
406  attr_reader :scale1, :scale2, :scale3
407
408  def initialize(parent)
409    super(parent)
410
411    @f1 = TkFrame.new(self)
412    @f2 = TkFrame.new(self)
413    @f3 = TkFrame.new(self)
414    @f4 = TkFrame.new(self)
415
416    for f in [@f1,@f2,@f3]
417      f.pack("side"=>"top","expand"=>"yes")
418    end
419    @f4.pack("side"=>"top","expand"=>"yes","fill"=>"x")
420
421    @label1 = TkLabel.new(self,"textvariable"=>$label1)
422    @scale1 = TkScale.new(self,"from"=>"0","to"=>"1000","length"=>"6c",
423			  "orient"=>"horizontal",
424			  "command"=>proc{tc_scaleChanged})
425    @scale1.pack("side"=>"top","anchor"=>"w")
426    @label1.pack("side"=>"top","anchor"=>"w")
427
428    @label2 = TkLabel.new(self,"textvariable"=>$label2)
429    @scale2 = TkScale.new(self,"from"=>"0","to"=>"1000","length"=>"6c",
430			  "orient"=>"horizontal",
431			  "command"=>proc{tc_scaleChanged})
432    @scale2.pack("side"=>"top","anchor"=>"w")
433    @label2.pack("side"=>"top","anchor"=>"w")
434
435    @label3 = TkLabel.new(self,"textvariable"=>$label3)
436    @scale3 = TkScale.new(self,"from"=>"0","to"=>"1000","length"=>"6c",
437			  "orient"=>"horizontal",
438			  "command"=>proc{tc_scaleChanged})
439    @scale3.pack("side"=>"top","anchor"=>"w")
440    @label3.pack("side"=>"top","anchor"=>"w")
441
442    @nameLabel = TkLabel.new(self,"text"=>"Name:")
443    @name = TkEntry.new(self,"relief"=>"sunken","borderwidth"=>"2",
444			"textvariable"=>$name,"width"=>"10",
445			"font"=>"-Adobe-Courier-Medium-R-Normal--*-120-*-*-*-*-*-*")
446    @nameLabel.pack("side"=>"left")
447    @name.pack("side"=>"right", "expand"=>"1", "fill"=>"x")
448    @name.bind("Return",proc{tc_loadNamedColor $name.to_s})
449
450    self
451  end
452end
453
454
455class TkColorMiddleRightFrame<TkFrame
456  attr_reader :swatch
457
458  def initialize(parent)
459    super(parent)
460    @swatch = TkFrame.new(self, "width"=>"2c", "height"=>"5c",
461			  "background"=>$color)
462    @value = TkLabel.new(self,
463			 "text"=>$color,
464			 "width"=>"13",
465			 "font"=>"-Adobe-Courier-Medium-R-Normal--*-120-*-*-*-*-*-*")
466    @swatch.pack("side"=>"top","expand"=>"yes","fill"=>"both")
467    @value.pack("side"=>"bottom","pady"=>".25c")
468
469    self
470  end
471
472  def set_color(color)
473    @swatch["background"] = color
474    @value["text"] = color
475  end
476end
477
478
479
480# middle level frame
481class TkColorMiddleFrame<TkFrame
482  attr_reader :left, :middle, :right
483
484  def initialize(parent)
485    super(parent,
486	  "relief"=> "raised",
487	  "borderwidth"=> "2")
488
489    @left = TkColorMiddleLeftFrame.new(self)
490    @left.pack("side"=>"left","padx"=>".25c","pady"=>".25c")
491
492    @middle = TkColorMiddleMiddleFrame.new(self)
493    @middle.pack("side"=>"left","expand"=>"yes","fill"=>"y")
494
495    @right = TkColorMiddleRightFrame.new(self)
496    @right.pack("side"=>"left","padx"=>".25c","pady"=>".25c","anchor"=>"s")
497
498    self
499  end
500end
501
502
503class TkColor<TkRoot
504  attr_reader :menu, :bottom, :middle
505
506  def initialize(*args)
507    super(*args)
508    @menu = TkColorMenuFrame.new(self)
509    @menu.pack("side"=>"top", "fill"=>"x")
510
511    @bottom = TkColorBotFrame.new(self)
512    @bottom.pack("side"=>"bottom","fill"=>"x")
513
514    @middle = TkColorMiddleFrame.new(self)
515    @middle.pack("side"=>"top","fill"=>"both")
516
517    self
518  end
519end
520
521
522$root = TkColor.new
523changeColorSpace :rgb
524
525# start eventloop
526Tk.mainloop
527