1#
2# tk/image.rb : treat Tk image objects
3#
4
5require 'tk'
6
7class TkImage<TkObject
8  include Tk
9
10  TkCommandNames = ['image'.freeze].freeze
11
12  Tk_IMGTBL = TkCore::INTERP.create_table
13
14  (Tk_Image_ID = ['i'.freeze, TkUtil.untrust('00000')]).instance_eval{
15    @mutex = Mutex.new
16    def mutex; @mutex; end
17    freeze
18  }
19
20  TkCore::INTERP.init_ip_env{
21    Tk_IMGTBL.mutex.synchronize{ Tk_IMGTBL.clear }
22  }
23
24  def self.new(keys=nil)
25    if keys.kind_of?(Hash)
26      name = nil
27      if keys.key?(:imagename)
28        name = keys[:imagename]
29      elsif keys.key?('imagename')
30        name = keys['imagename']
31      end
32      if name
33        if name.kind_of?(TkImage)
34          obj = name
35        else
36          name = _get_eval_string(name)
37          obj = nil
38          Tk_IMGTBL.mutex.synchronize{
39            obj = Tk_IMGTBL[name]
40          }
41        end
42        if obj
43          if !(keys[:without_creating] || keys['without_creating'])
44            keys = _symbolkey2str(keys)
45            keys.delete('imagename')
46            keys.delete('without_creating')
47            obj.instance_eval{
48              tk_call_without_enc('image', 'create',
49                                  @type, @path, *hash_kv(keys, true))
50            }
51          end
52          return obj
53        end
54      end
55    end
56    (obj = self.allocate).instance_eval{
57      Tk_IMGTBL.mutex.synchronize{
58        initialize(keys)
59        Tk_IMGTBL[@path] = self
60      }
61    }
62    obj
63  end
64
65  def initialize(keys=nil)
66    @path = nil
67    without_creating = false
68    if keys.kind_of?(Hash)
69      keys = _symbolkey2str(keys)
70      @path = keys.delete('imagename')
71      without_creating = keys.delete('without_creating')
72    end
73    unless @path
74      Tk_Image_ID.mutex.synchronize{
75        @path = Tk_Image_ID.join(TkCore::INTERP._ip_id_)
76        Tk_Image_ID[1].succ!
77      }
78    end
79    unless without_creating
80      tk_call_without_enc('image', 'create',
81                          @type, @path, *hash_kv(keys, true))
82    end
83  end
84
85  def delete
86    Tk_IMGTBL.mutex.synchronize{
87      Tk_IMGTBL.delete(@id) if @id
88    }
89    tk_call_without_enc('image', 'delete', @path)
90    self
91  end
92  def height
93    number(tk_call_without_enc('image', 'height', @path))
94  end
95  def inuse
96    bool(tk_call_without_enc('image', 'inuse', @path))
97  end
98  def itemtype
99    tk_call_without_enc('image', 'type', @path)
100  end
101  def width
102    number(tk_call_without_enc('image', 'width', @path))
103  end
104
105  def TkImage.names
106    Tk_IMGTBL.mutex.synchronize{
107      Tk.tk_call_without_enc('image', 'names').split.collect!{|id|
108        (Tk_IMGTBL[id])? Tk_IMGTBL[id] : id
109      }
110    }
111  end
112
113  def TkImage.types
114    Tk.tk_call_without_enc('image', 'types').split
115  end
116end
117
118class TkBitmapImage<TkImage
119  def __strval_optkeys
120    super() + ['maskdata', 'maskfile']
121  end
122  private :__strval_optkeys
123
124  def initialize(*args)
125    @type = 'bitmap'
126    super(*args)
127  end
128end
129
130# A photo is an image whose pixels can display any color or be transparent.
131# At present, only GIF and PPM/PGM formats are supported, but an interface
132# exists to allow additional image file formats to be added easily.
133#
134# This class documentation is a copy from the original Tcl/Tk at
135# http://www.tcl.tk/man/tcl8.5/TkCmd/photo.htm with some rewrited parts.
136class TkPhotoImage<TkImage
137  NullArgOptionKeys = [ "shrink", "grayscale" ]
138
139  def _photo_hash_kv(keys)
140    keys = _symbolkey2str(keys)
141    NullArgOptionKeys.collect{|opt|
142      if keys[opt]
143        keys[opt] = None
144      else
145        keys.delete(opt)
146      end
147    }
148    keys.collect{|k,v|
149      ['-' << k, v]
150    }.flatten
151  end
152  private :_photo_hash_kv
153
154  # Create a new image with the given options.
155  # == Examples of use :
156  # === Create an empty image of 300x200 pixels
157  #
158  #		image = TkPhotoImage.new(:height => 200, :width => 300)
159  #
160  # === Create an image from a file
161  #
162  #		image = TkPhotoImage.new(:file: => 'my_image.gif')
163  #
164  # == Options
165  # Photos support the following options:
166  # * :data
167  #   Specifies the contents of the image as a string.
168  # * :format
169  #   Specifies the name of the file format for the data.
170  # * :file
171  #   Gives the name of a file that is to be read to supply data for the image.
172  # * :gamma
173  #   Specifies that the colors allocated for displaying this image in a window
174  #   should be corrected for a non-linear display with the specified gamma
175  #   exponent value.
176  # * height
177  #   Specifies the height of the image, in pixels. This option is useful
178  #   primarily in situations where the user wishes to build up the contents of
179  #   the image piece by piece. A value of zero (the default) allows the image
180  #   to expand or shrink vertically to fit the data stored in it.
181  # * palette
182  #   Specifies the resolution of the color cube to be allocated for displaying
183  #   this image.
184  # * width
185  #   Specifies the width of the image, in pixels. This option is useful
186  #   primarily in situations where the user wishes to build up the contents of
187  #   the image piece by piece. A value of zero (the default) allows the image
188  #   to expand or shrink horizontally to fit the data stored in it.
189  def initialize(*args)
190    @type = 'photo'
191    super(*args)
192  end
193
194  # Blank the image; that is, set the entire image to have no data, so it will
195  # be displayed as transparent, and the background of whatever window it is
196  # displayed in will show through.
197  def blank
198    tk_send_without_enc('blank')
199    self
200  end
201
202  def cget_strict(option)
203    case option.to_s
204    when 'data', 'file'
205      tk_send 'cget', '-' << option.to_s
206    else
207      tk_tcl2ruby(tk_send('cget', '-' << option.to_s))
208    end
209  end
210
211  # Returns the current value of the configuration option given by option.
212  # Example, display name of the file from which <tt>image</tt> was created:
213  # 	puts image.cget :file
214  def cget(option)
215    unless TkConfigMethod.__IGNORE_UNKNOWN_CONFIGURE_OPTION__
216      cget_strict(option)
217    else
218      begin
219        cget_strict(option)
220      rescue => e
221        if current_configinfo.has_key?(option.to_s)
222          # error on known option
223          fail e
224        else
225          # unknown option
226          nil
227        end
228      end
229    end
230  end
231
232  # Copies a region from the image called source to the image called
233  # destination, possibly with pixel zooming and/or subsampling. If no options
234  # are specified, this method copies the whole of source into destination,
235  # starting at coordinates (0,0) in destination. The following options may be
236  # specified:
237  #
238  # * :from [x1, y1, x2, y2]
239  #   Specifies a rectangular sub-region of the source image to be copied.
240  #   (x1,y1) and (x2,y2) specify diagonally opposite corners of the rectangle.
241  #   If x2 and y2 are not specified, the default value is the bottom-right
242  #   corner of the source image. The pixels copied will include the left and
243  #   top edges of the specified rectangle but not the bottom or right edges.
244  #   If the :from option is not given, the default is the whole source image.
245  # * :to [x1, y1, x2, y2]
246  #   Specifies a rectangular sub-region of the destination image to be
247  #   affected. (x1,y1) and (x2,y2) specify diagonally opposite corners of the
248  #   rectangle. If x2 and y2 are not specified, the default value is (x1,y1)
249  #   plus the size of the source region (after subsampling and zooming, if
250  #   specified). If x2 and  y2 are specified, the source region will be
251  #   replicated if necessary to fill the destination region in a tiled fashion.
252  # * :shrink
253  #   Specifies that the size of the destination image should be reduced, if
254  #   necessary, so that the region being copied into is at the bottom-right
255  #   corner of the image. This option will not affect the width or height of
256  #   the image if the user has specified a non-zero value for the :width or
257  #   :height configuration option, respectively.
258  # * :zoom [x, y]
259  #   Specifies that the source region should be magnified by a factor of x
260  #   in the X direction and y in the Y direction. If y is not given, the
261  #   default value is the same as x. With this option, each pixel in the
262  #   source image will be expanded into a block of x x y pixels in the
263  #   destination image, all the same color. x and y must be greater than 0.
264  # * :subsample [x, y]
265  #   Specifies that the source image should be reduced in size by using only
266  #   every xth pixel in the X direction and yth pixel in the Y direction.
267  #   Negative values will cause the image to be flipped about the Y or X axes,
268  #   respectively. If y is not given, the default value is the same as x.
269  # * :compositingrule rule
270  #   Specifies how transparent pixels in the source image are combined with
271  #   the destination image. When a compositing rule of <tt>overlay</tt> is set,
272  #   the old  contents of the destination image are visible, as if the source
273  #   image were  printed on a piece of transparent film and placed over the
274  #   top of the  destination. When a compositing rule of <tt>set</tt> is set,
275  #   the old contents of  the destination image are discarded and the source
276  #   image is used as-is. The default compositing rule is <tt>overlay</tt>.
277  def copy(src, *opts)
278    if opts.size == 0
279      tk_send('copy', src)
280    elsif opts.size == 1 && opts[0].kind_of?(Hash)
281      tk_send('copy', src, *_photo_hash_kv(opts[0]))
282    else
283      # for backward compatibility
284      args = opts.collect{|term|
285        if term.kind_of?(String) && term.include?(?\s)
286          term.split
287        else
288          term
289        end
290      }.flatten
291      tk_send('copy', src, *args)
292    end
293    self
294  end
295
296  # Returns image data in the form of a string. The following options may be
297  # specified:
298  # * :background color
299  #   If the color is specified, the data will not contain any transparency
300  #   information. In all transparent pixels the color will be replaced by the
301  #   specified color.
302  # * :format format-name
303  #   Specifies the name of the image file format handler to be used.
304  #   Specifically, this subcommand searches for the first handler whose name
305  #   matches an initial substring of format-name and which has the capability
306  #   to read this image data. If this option is not given, this subcommand
307  #   uses the first handler that has the capability to read the image data.
308  # * :from [x1, y1, x2, y2]
309  #   Specifies a rectangular region of imageName to be returned. If only x1
310  #   and y1 are specified, the region extends from (x1,y1) to the bottom-right
311  #   corner of imageName. If all four coordinates are given, they specify
312  #   diagonally opposite corners of the rectangular region, including x1,y1
313  #   and excluding x2,y2. The default, if this option is not given, is the
314  #   whole image.
315  # * :grayscale
316  #   If this options is specified, the data will not contain color information.
317  #   All pixel data will be transformed into grayscale.
318  def data(keys={})
319    tk_split_list(tk_send('data', *_photo_hash_kv(keys)))
320  end
321
322  # Returns the color of the pixel at coordinates (x,y) in the image as a list
323  # of three integers between 0 and 255, representing the red, green and blue
324  # components respectively.
325  def get(x, y)
326    tk_send('get', x, y).split.collect{|n| n.to_i}
327  end
328
329  def put(data, *opts)
330    if opts.empty?
331      tk_send('put', data)
332    elsif opts.size == 1 && opts[0].kind_of?(Hash)
333      tk_send('put', data, *_photo_hash_kv(opts[0]))
334    else
335      # for backward compatibility
336      tk_send('put', data, '-to', *opts)
337    end
338    self
339  end
340
341  def read(file, *opts)
342    if opts.size == 0
343      tk_send('read', file)
344    elsif opts.size == 1 && opts[0].kind_of?(Hash)
345      tk_send('read', file, *_photo_hash_kv(opts[0]))
346    else
347      # for backward compatibility
348      args = opts.collect{|term|
349        if term.kind_of?(String) && term.include?(?\s)
350          term.split
351        else
352          term
353        end
354      }.flatten
355      tk_send('read', file, *args)
356    end
357    self
358  end
359
360  def redither
361    tk_send 'redither'
362    self
363  end
364
365  # Returns a boolean indicating if the pixel at (x,y) is transparent.
366  def get_transparency(x, y)
367    bool(tk_send('transparency', 'get', x, y))
368  end
369
370  # Makes the pixel at (x,y) transparent if <tt>state</tt> is true, and makes
371  # that pixel opaque otherwise.
372  def set_transparency(x, y, state)
373    tk_send('transparency', 'set', x, y, state)
374    self
375  end
376
377  def write(file, *opts)
378    if opts.size == 0
379      tk_send('write', file)
380    elsif opts.size == 1 && opts[0].kind_of?(Hash)
381      tk_send('write', file, *_photo_hash_kv(opts[0]))
382    else
383      # for backward compatibility
384      args = opts.collect{|term|
385        if term.kind_of?(String) && term.include?(?\s)
386          term.split
387        else
388          term
389        end
390      }.flatten
391      tk_send('write', file, *args)
392    end
393    self
394  end
395end
396