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