Class: Teek::Photo
- Inherits:
-
Object
- Object
- Teek::Photo
- Defined in:
- lib/teek/photo.rb
Overview
CPU-side RGBA pixel buffer backed by Tk's "photo image" format.
Despite the name, this is really a raw pixel manipulation surface. Tk has two built-in image types: "bitmap" (two colors + transparency) and "photo" (full-color, 32-bit RGBA). The naming reflects Tk's image type system, not the contents — a "photo" is just Tk's term for a full-color pixel buffer.
Think of it as a software framebuffer: you pack RGBA bytes, write them in bulk, read them back, zoom/subsample, and blit to a canvas or label for display. All work is CPU-driven — there is no GPU acceleration.
The C methods (#put_block, #get_image, #get_pixel, etc.) call
Tk_PhotoPutBlock / Tk_PhotoGetImage directly, bypassing Tcl string
parsing for much better performance than the Tcl-level $photo put
command. Designed for games, visualizations, and real-time drawing.
Instance Attribute Summary collapse
-
#app ⇒ Object
readonly
Returns the value of attribute app.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
Class Method Summary collapse
-
.finalizer_for(name, app) ⇒ Object
private
Builds the proc registered as this instance's finalizer.
- .next_name ⇒ Object private
Instance Method Summary collapse
- #==(other) ⇒ Object (also: #eql?)
-
#blank ⇒ self
(also: #clear)
Clear the image to fully transparent.
-
#command(*args, **kwargs) ⇒ String
Invoke a photo subcommand not covered by a dedicated method above (e.g.
copy,read,write). -
#delete ⇒ void
Delete this photo image and free its resources.
-
#exist? ⇒ Boolean
Check if this photo image still exists.
-
#expand(width, height) ⇒ self
Expand image to at least the given dimensions.
-
#get_image(x: nil, y: nil, width: nil, height: nil, unpack: false) ⇒ Hash
Read pixel data from the image.
-
#get_pixel(x, y) ⇒ Array<Integer>
Read a single pixel.
-
#get_size ⇒ Array<Integer>
Get image dimensions.
- #hash ⇒ Object
-
#initialize(app, name: nil, width: nil, height: nil, file: nil, data: nil, format: nil, palette: nil, gamma: nil) ⇒ Photo
constructor
Create a new photo image.
- #inspect ⇒ Object
-
#put_block(pixel_data, width, height, x: 0, y: 0, format: :rgba, composite: :set) ⇒ self
Write RGBA pixel data to the image.
-
#put_zoomed_block(pixel_data, width, height, x: 0, y: 0, zoom_x: 1, zoom_y: 1, subsample_x: 1, subsample_y: 1, format: :rgba, composite: :set) ⇒ self
Write RGBA pixel data with zoom and subsample.
-
#set_size(width, height) ⇒ self
Set image dimensions.
-
#to_s ⇒ String
The Tcl image name.
Constructor Details
#initialize(app, name: nil, width: nil, height: nil, file: nil, data: nil, format: nil, palette: nil, gamma: nil) ⇒ Photo
Create a new photo image.
The underlying Tk image is automatically deleted once this Photo
object is garbage collected and nothing else references it - keep
it around (an ivar, a collection, etc.) for as long as you need the
image, the same contract as File or Socket. If only the image
name is kept (e.g. passed into a widget's image:) and this
wrapper is dropped, the image can be reclaimed out from under that
reference; Tk shows a broken image rather than crashing, but the
image won't come back - call #delete explicitly rather than
relying on GC timing if you need deterministic cleanup.
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/teek/photo.rb', line 101 def initialize(app, name: nil, width: nil, height: nil, file: nil, data: nil, format: nil, palette: nil, gamma: nil) @app = app @name = name || self.class.next_name kwargs = {} kwargs[:width] = width if width kwargs[:height] = height if height kwargs[:file] = file if file kwargs[:data] = data if data kwargs[:format] = format if format kwargs[:palette] = palette if palette kwargs[:gamma] = gamma if gamma @app.command(:image, :create, :photo, @name, **kwargs) ObjectSpace.define_finalizer(self, self.class.finalizer_for(@name, @app)) end |
Instance Attribute Details
#app ⇒ Object (readonly)
Returns the value of attribute app.
43 44 45 |
# File 'lib/teek/photo.rb', line 43 def app @app end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
43 44 45 |
# File 'lib/teek/photo.rb', line 43 def name @name end |
Class Method Details
.finalizer_for(name, app) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Builds the proc registered as this instance's finalizer. Must not
close over self (the Photo instance) or any of its ivars
directly - a finalizer that references its own object keeps that
object permanently reachable, so it would never actually be
collected. +name+/+app+ are plain local captures instead.
Finalizers can run on any thread, so the delete goes through
Interp#queue_for_main (fire-and-forget) rather than #tcl_eval,
which would block on a cross-thread queue wait if called from a
background thread - risky from inside a GC finalizer. The catch
means a concurrent explicit #delete (or an interpreter that's
already torn down by the time this runs) is a silent no-op, not
an error with nowhere to go.
69 70 71 72 73 74 75 76 77 |
# File 'lib/teek/photo.rb', line 69 def finalizer_for(name, app) proc do begin app.interp.queue_for_main(proc { app.tcl_eval("catch {image delete #{name}}") }) rescue Teek::TclError # interpreter already torn down - nothing to clean up end end end |
.next_name ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
49 50 51 52 |
# File 'lib/teek/photo.rb', line 49 def next_name @counter += 1 "teek_photo#{@counter}" end |
Instance Method Details
#==(other) ⇒ Object Also known as: eql?
278 279 280 |
# File 'lib/teek/photo.rb', line 278 def ==(other) other.is_a?(Photo) ? @name == other.name : @name == other.to_s end |
#blank ⇒ self Also known as: clear
Clear the image to fully transparent.
241 242 243 244 |
# File 'lib/teek/photo.rb', line 241 def blank @app.interp.photo_blank(@name) self end |
#command(*args, **kwargs) ⇒ String
Invoke a photo subcommand not covered by a dedicated method above
(e.g. copy, read, write). Prepends the image name as the Tcl
command, exactly like Widget#command does for widgets.
129 130 131 |
# File 'lib/teek/photo.rb', line 129 def command(*args, **kwargs) @app.command(@name, *args, **kwargs) end |
#delete ⇒ void
This method returns an undefined value.
Delete this photo image and free its resources.
Cancels the GC finalizer registered by #initialize - otherwise a
later collection could delete an unrelated image if this name gets
reused (e.g. an explicit name: passed to a later Photo).
255 256 257 258 |
# File 'lib/teek/photo.rb', line 255 def delete ObjectSpace.undefine_finalizer(self) @app.tcl_eval("image delete #{@name}") end |
#exist? ⇒ Boolean
Check if this photo image still exists.
263 264 265 266 267 |
# File 'lib/teek/photo.rb', line 263 def exist? @app.tcl_eval("image type #{@name}") == 'photo' rescue Teek::TclError false end |
#expand(width, height) ⇒ self
Has no effect on photos created with explicit width: / height:
options. Only works on auto-sized photos (those whose size was set by
writing pixel data). This is a Tk limitation.
Expand image to at least the given dimensions. Will not shrink.
233 234 235 236 |
# File 'lib/teek/photo.rb', line 233 def (width, height) @app.interp.(@name, width, height) self end |
#get_image(x: nil, y: nil, width: nil, height: nil, unpack: false) ⇒ Hash
Read pixel data from the image.
189 190 191 192 193 194 195 196 |
# File 'lib/teek/photo.rb', line 189 def get_image(x: nil, y: nil, width: nil, height: nil, unpack: false) opts = { unpack: unpack } opts[:x] = x if x opts[:y] = y if y opts[:width] = width if width opts[:height] = height if height @app.interp.photo_get_image(@name, opts) end |
#get_pixel(x, y) ⇒ Array<Integer>
Read a single pixel.
203 204 205 |
# File 'lib/teek/photo.rb', line 203 def get_pixel(x, y) @app.interp.photo_get_pixel(@name, x, y) end |
#get_size ⇒ Array<Integer>
Get image dimensions.
210 211 212 |
# File 'lib/teek/photo.rb', line 210 def get_size @app.interp.photo_get_size(@name) end |
#hash ⇒ Object
283 284 285 |
# File 'lib/teek/photo.rb', line 283 def hash @name.hash end |
#inspect ⇒ Object
274 275 276 |
# File 'lib/teek/photo.rb', line 274 def inspect "#<Teek::Photo #{@name}>" end |
#put_block(pixel_data, width, height, x: 0, y: 0, format: :rgba, composite: :set) ⇒ self
Write RGBA pixel data to the image.
143 144 145 146 147 |
# File 'lib/teek/photo.rb', line 143 def put_block(pixel_data, width, height, x: 0, y: 0, format: :rgba, composite: :set) opts = { x: x, y: y, format: format, composite: composite } @app.interp.photo_put_block(@name, pixel_data, width, height, opts) self end |
#put_zoomed_block(pixel_data, width, height, x: 0, y: 0, zoom_x: 1, zoom_y: 1, subsample_x: 1, subsample_y: 1, format: :rgba, composite: :set) ⇒ self
Write RGBA pixel data with zoom and subsample.
Zoom replicates each pixel (zoom=3 makes each source pixel 3x3). Subsample skips source pixels (subsample=2 takes every other pixel).
166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/teek/photo.rb', line 166 def put_zoomed_block(pixel_data, width, height, x: 0, y: 0, zoom_x: 1, zoom_y: 1, subsample_x: 1, subsample_y: 1, format: :rgba, composite: :set) opts = { x: x, y: y, zoom_x: zoom_x, zoom_y: zoom_y, subsample_x: subsample_x, subsample_y: subsample_y, format: format, composite: composite } @app.interp.photo_put_zoomed_block(@name, pixel_data, width, height, opts) self end |
#set_size(width, height) ⇒ self
Set image dimensions. May crop or add transparent pixels.
219 220 221 222 |
# File 'lib/teek/photo.rb', line 219 def set_size(width, height) @app.interp.photo_set_size(@name, width, height) self end |
#to_s ⇒ String
Returns the Tcl image name.
270 271 272 |
# File 'lib/teek/photo.rb', line 270 def to_s @name end |