source: branches/blt4/gui/scripts/imageresult.tcl @ 2287

Last change on this file since 2287 was 2048, checked in by gah, 13 years ago

tool.xml

File size: 25.0 KB
Line 
1# ----------------------------------------------------------------------
2#  COMPONENT: imageresult - picture image in a ResultSet
3#
4#  This widget displays an image found in the output of a Rappture
5#  tool run.  Use the "add" and "delete" methods to control the images
6#  showing in the widget.
7# ======================================================================
8#  AUTHOR:  Michael McLennan, Purdue University
9#  Copyright (c) 2004-2005  Purdue Research Foundation
10#
11#  See the file "license.terms" for information on usage and
12#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
13# ======================================================================
14package require Itk
15package require BLT
16#package require Img
17
18option add *ImageResult.width 3i widgetDefault
19option add *ImageResult.height 3i widgetDefault
20option add *ImageResult.controlBackground gray widgetDefault
21option add *ImageResult.font \
22    -*-helvetica-medium-r-normal-*-12-* widgetDefault
23
24itcl::class Rappture::ImageResult {
25    inherit itk::Widget
26
27    constructor {args} { # defined below }
28    destructor { # defined below }
29
30    public method add {image {settings ""}}
31    public method get {}
32    public method delete {args}
33    public method scale {args}
34    public method snap {x y}
35    public method parameters {title args} { # do nothing }
36    public method download {option args}
37
38    protected method _rebuild {args}
39    protected method _top {what}
40    protected method _zoom {option args}
41    protected method _move {option args}
42
43    private method AddImageControls { frame widget }
44    private method SetWaitVariable { value } {
45        set _getimage $value
46    }
47    private method GetWaitVariable {} {
48        return $_getimage
49    }
50    private method WaitForImage {} {
51        tkwait variable [itcl::scope _getimage]
52        return $_getimage
53    }
54
55    private variable _dispatcher "" ;# dispatcher for !events
56    private variable _dlist ""      ;# list of data objects
57    private variable _topmost ""    ;# topmost image in _dlist
58    private variable _max           ;# max size of all images
59    private variable _scale         ;# info related to zoom
60    private variable _image         ;# image buffers used for scaling
61    private variable _downloadPopup
62}
63                                                                               
64itk::usual ImageResult {
65    keep -background -foreground -cursor -font
66}
67itk::usual Scrollset {
68}
69
70# ----------------------------------------------------------------------
71# CONSTRUCTOR
72# ----------------------------------------------------------------------
73itcl::body Rappture::ImageResult::constructor {args} {
74    Rappture::dispatcher _dispatcher
75    $_dispatcher register !rebuild
76    $_dispatcher dispatch $this !rebuild [itcl::code $this _rebuild]
77
78    array set _scale {
79        max 1.0
80        current 1.0
81        default 1
82        x 0
83        y 0
84    }
85    array set _downloadPopup {
86        format image
87        image_controls ""
88    }
89
90    option add hull.width hull.height
91    pack propagate $itk_component(hull) no
92
93    Rappture::Panes $itk_interior.panes -sashwidth 1 -sashrelief solid -sashpadding 2
94    pack $itk_interior.panes -expand yes -fill both
95    set main [$itk_interior.panes pane 0]
96    $itk_interior.panes fraction 0 1
97
98    itk_component add controls {
99        frame $main.cntls
100    } {
101        usual
102        rename -background -controlbackground controlBackground Background
103    }
104    pack $itk_component(controls) -side right -fill y
105
106    itk_component add reset {
107        button $itk_component(controls).reset \
108            -borderwidth 1 -padx 1 -pady 1 \
109            -bitmap [Rappture::icon reset] \
110            -command [itcl::code $this _zoom reset]
111    } {
112        usual
113        ignore -borderwidth
114        rename -highlightbackground -controlbackground controlBackground Background
115    }
116    pack $itk_component(reset) -padx 4 -pady 4
117    Rappture::Tooltip::for $itk_component(reset) "Reset the view to the default zoom level"
118
119    itk_component add zoomin {
120        button $itk_component(controls).zin \
121            -borderwidth 1 -padx 1 -pady 1 \
122            -bitmap [Rappture::icon zoomin] \
123            -command [itcl::code $this _zoom in]
124    } {
125        usual
126        ignore -borderwidth
127        rename -highlightbackground -controlbackground controlBackground Background
128    }
129    pack $itk_component(zoomin) -padx 4 -pady 4
130    Rappture::Tooltip::for $itk_component(zoomin) "Zoom in"
131
132    itk_component add zoomout {
133        button $itk_component(controls).zout \
134            -borderwidth 1 -padx 1 -pady 1 \
135            -bitmap [Rappture::icon zoomout] \
136            -command [itcl::code $this _zoom out]
137    } {
138        usual
139        ignore -borderwidth
140        rename -highlightbackground -controlbackground controlBackground Background
141    }
142    pack $itk_component(zoomout) -padx 4 -pady 4
143    Rappture::Tooltip::for $itk_component(zoomout) "Zoom out"
144
145
146    set _image(zoom) [image create picture]
147    set _image(final) [image create picture]
148
149    itk_component add image {
150        label $main.image -image $_image(final) -anchor c
151    } {
152        keep -background -foreground -cursor -font
153    }
154    pack $itk_component(image) -expand yes -fill both
155
156    #
157    # Add bindings for resize/move
158    #
159    bind $itk_component(image) <Configure> \
160        [list $_dispatcher event -idle !rebuild resize 1]
161
162    bind $itk_component(image) <ButtonPress-1> \
163        [itcl::code $this _move click %x %y]
164    bind $itk_component(image) <B1-Motion> \
165        [itcl::code $this _move drag %x %y]
166    bind $itk_component(image) <ButtonRelease-1> \
167        [itcl::code $this _move release %x %y]
168
169    #
170    # Add area at the bottom for notes.
171    #
172    set notes [$itk_interior.panes insert end -fraction 0.15]
173    $itk_interior.panes visibility 1 off
174    blt::scrollset $notes.scr -xscrollbar $notes.scr.xs \
175        -yscrollbar $notes.scr.ys -window $notes.scr.html
176    blt::tk::scrollbar $notes.scr.xs
177    blt::tk::scrollbar $notes.scr.ys
178    pack $notes.scr -expand yes -fill both
179    itk_component add notes {
180        Rappture::HTMLviewer $notes.scr.html
181    }
182    eval itk_initialize $args
183}
184
185# ----------------------------------------------------------------------
186# DESTRUCTOR
187# ----------------------------------------------------------------------
188itcl::body Rappture::ImageResult::destructor {} {
189    foreach name [array names _image] {
190        image delete $_image($name)
191    }
192}
193
194# ----------------------------------------------------------------------
195# USAGE: add <image> ?<settings>?
196#
197# Clients use this to add an image to the plot.  The optional <settings>
198# are used to configure the image.  Allowed settings are -color,
199# -brightness, -width, -linestyle and -raise.
200# ----------------------------------------------------------------------
201itcl::body Rappture::ImageResult::add {image {settings ""}} {
202    array set params {
203        -color auto
204        -brightness 0
205        -width 1
206        -raise 0
207        -linestyle solid
208        -description ""
209        -param ""
210    }
211    foreach {opt val} $settings {
212        if {![info exists params($opt)]} {
213            error "bad setting \"$opt\": should be [join [lsort [array names params]] {, }]"
214        }
215        set params($opt) $val
216    }
217
218    if {$params(-raise)} {
219        set _topmost $image
220        $_dispatcher event -idle !rebuild
221    }
222
223    set pos [lsearch -exact $image $_dlist]
224    if {$pos < 0} {
225        lappend _dlist $image
226        $_dispatcher event -idle !rebuild
227    }
228}
229
230# ----------------------------------------------------------------------
231# USAGE: get
232#
233# Clients use this to query the list of images being displayed, in
234# order from bottom to top of this result.
235# ----------------------------------------------------------------------
236itcl::body Rappture::ImageResult::get {} {
237    # put the dataobj list in order according to -raise options
238    set dlist $_dlist
239
240    set i [lsearch $_dlist $_topmost]
241    if {$i >= 0} {
242        set dlist [lreplace $dlist $i $i]
243        set dlist [linsert $dlist 0 $_topmost]
244    }
245    return $dlist
246}
247
248# ----------------------------------------------------------------------
249# USAGE: delete ?<image1> <image2> ...?
250#
251# Clients use this to delete an image from the plot.  If no images
252# are specified, then all images are deleted.
253# ----------------------------------------------------------------------
254itcl::body Rappture::ImageResult::delete {args} {
255    if {[llength $args] == 0} {
256        set args $_dlist
257    }
258
259    # delete all specified curves
260    set changed 0
261    foreach image $args {
262        set pos [lsearch -exact $_dlist $image]
263        if {$pos >= 0} {
264            set _dlist [lreplace $_dlist $pos $pos]
265            set changed 1
266
267            if {$image == $_topmost} {
268                set _topmost ""
269            }
270        }
271    }
272
273    # if anything changed, then rebuild the plot
274    if {$changed} {
275        $_dispatcher event -idle !rebuild
276    }
277}
278
279# ----------------------------------------------------------------------
280# USAGE: scale ?<image1> <image2> ...?
281#
282# Sets the default limits for the overall plot according to the
283# limits of the data for all of the given <image> objects.  This
284# accounts for all images--even those not showing on the screen.
285# Because of this, the limits are appropriate for all images as
286# the user scans through data in the ResultSet viewer.
287# ----------------------------------------------------------------------
288itcl::body Rappture::ImageResult::scale {args} {
289    set _max(w) 0
290    set _max(h) 0
291    foreach image $args {
292        set imh [$image tkimage]
293
294        set w [image width $imh]
295        if {$w > $_max(w)} { set _max(w) $w }
296
297        set h [image height $imh]
298        if {$h > $_max(h)} { set _max(h) $h }
299    }
300
301    # scale is unknown for now... scale later at next _rebuild
302    set _scale(max) "?"
303    set _scale(current) "?"
304
305    $_dispatcher event -idle !rebuild
306}
307
308# ----------------------------------------------------------------------
309# USAGE: download coming
310# USAGE: download controls <downloadCommand>
311# USAGE: download now
312#
313# Clients use this method to create a downloadable representation
314# of the plot.  Returns a list of the form {ext string}, where
315# "ext" is the file extension (indicating the type of data) and
316# "string" is the data itself.
317# ----------------------------------------------------------------------
318itcl::body Rappture::ImageResult::download {option args} {
319    switch $option {
320        coming {
321            # nothing to do
322        }
323        controls {
324            set popup .imageresultdownload
325            if {![winfo exists $popup]} {
326                # If we haven't created the popup yet, do it now
327                Rappture::Balloon $popup \
328                    -title "[Rappture::filexfer::label downloadWord] as..."
329                set inner [$popup component inner]
330                label $inner.summary -text "" -anchor w
331                pack $inner.summary -side top
332                radiobutton $inner.image -text "Image (PNG/JPEG/GIF)" \
333                    -variable \
334                    ::Rappture::ImageResult::_downloadPopup(format) \
335                    -font "Arial 10 " \
336                    -value image
337                Rappture::Tooltip::for $inner.image "Save as image."
338                pack $inner.image -anchor w
339                button $inner.go -text [Rappture::filexfer::label download] \
340                    -command [lindex $args 0]
341                pack $inner.go -side bottom -pady 4
342                $inner.image select
343            } else {
344                set inner [$popup component inner]
345            }
346            set num [llength [get]]
347            set num [expr {($num == 1) ? "1 result" : "$num results"}]
348            set word [Rappture::filexfer::label downloadWord]
349            $inner.summary configure -text "$word $num in the following format:"
350            update idletasks ;          # Fix initial sizes
351            return $popup
352        }
353        now {
354            set popup .imageresultdownload
355            if { [winfo exists $popup] } {
356                $popup deactivate
357            }
358            switch -- $_downloadPopup(format) {
359                "image" {
360                    set top [_top image]
361                    set top [_top image]
362                    if {$top == ""} {
363                        return ""
364                    }
365                    set popup .imageresultimage
366                    if { ![winfo exists $popup] } {
367                        # Create the balloon popup and and the print image
368                        # dialog widget to it.
369                        Rappture::Balloon $popup -title "Save as image..." \
370                            -deactivatecommand \
371                            [itcl::code $this SetWaitVariable 0]
372                        set inner [$popup component inner]
373                        AddImageControls $inner [lindex $args 0]
374                    } else {
375                        set inner [$popup component inner]
376                    }                   
377                    set _downloadPopup(image_controls) $inner
378                    update
379                    # Activate the popup and call for the output.
380                    foreach { widget toolName plotName } $args break
381                    SetWaitVariable 0
382                    $popup activate $widget left
383                    set bool [WaitForImage]
384                    $popup deactivate
385                    if { $bool } {
386                        set inner $_downloadPopup(image_controls)
387                        set fmt [$inner.format translate [$inner.format value]]
388                        switch $fmt {
389                            "jpg" {
390                                $top export jpg -quality 100 -data bytes
391                            }
392                            "png" {
393                                $top export png -data bytes
394                            }
395                            "gif" {
396                                $top export gif -data bytes
397                            }
398                            default {
399                                return ""
400                            }
401                        }
402                        return [list .$fmt $bytes]
403                    }
404                }
405            }
406            return ""
407        }
408        default {
409            error "bad option \"$option\": should be coming, controls, now"
410        }
411    }
412}
413
414# ----------------------------------------------------------------------
415# USAGE: _rebuild ?<eventData>...?
416#
417# Called automatically whenever something changes that affects the
418# data in the widget.  Clears any existing data and rebuilds the
419# widget to display new data.
420# ----------------------------------------------------------------------
421itcl::body Rappture::ImageResult::_rebuild {args} {
422    array set event $args
423    if {[info exists event(resize)] && $event(resize)} {
424        # window changed size -- recompute max scale below
425        set _scale(max) "?"
426    }
427
428    if {$_scale(max) == "?"} {
429        if {![_zoom rescale]} {
430            return
431        }
432    }
433    if {$_scale(current) == "?" || $_scale(default)} {
434        set _scale(current) $_scale(max)
435        set _scale(x) 0.5
436        set _scale(y) 0.5
437    }
438
439    set w [winfo width $itk_component(image)]
440    set h [winfo height $itk_component(image)]
441    set bg [$itk_component(image) cget -background]
442
443    set imh [_top image]
444    if {$imh != ""} {
445        set iw [image width $imh]
446        set ih [image height $imh]
447        set wz [expr {round($w*$_scale(current))}]
448        set hz [expr {round($h*$_scale(current))}]
449
450        if {$wz < $iw || $hz < $ih} {
451            #
452            # Scale the image up by creating a "zoom" image which
453            # is smaller than the current image.  Sample a small
454            # part of the original image by copying into the "zoom"
455            # image, then scale that part up to the full "view" area.
456            #
457            if {$wz > $iw} {
458                set wz $iw
459            }
460            if {$hz > $ih} {
461                set hz $ih
462            }
463
464            set sx [expr {round($_scale(x)*$_max(w)-0.5*$wz)}]
465            if {$sx+$wz > $iw} {
466                set sx [expr {$iw-$wz}]
467            }
468            if {$sx < 0} {
469                set sx 0
470            }
471
472            set sy [expr {round($_scale(y)*$_max(h)-0.5*$hz)}]
473            if {$sy+$hz > $ih} {
474                set sy [expr {$ih-$hz}]
475            }
476            if {$sy < 0} {
477                set sy 0
478            }
479
480            if {$wz > 1 && $hz > 1} {
481                $_image(zoom) configure -width $wz -height $hz -maxpect no
482                $_image(zoom) copy $imh -from [list $sx $sy]
483                set wf [expr {round(double($wz)/$_scale(current))}]
484                set hf [expr {round(double($hz)/$_scale(current))}]
485                $_image(final) configure -width $wf -height $hf -maxpect no
486                $_image(final) resample $_image(zoom) -filter sinc
487            }
488        } else {
489            #
490            # Scale the image down by creating a "zoom" image which
491            # is smaller than the current image.  Resize the original
492            # image to the smaller size, then copy into the current
493            # view.
494            #
495            set wz [expr {round(double($iw)/$_scale(current))}]
496            set hz [expr {round(double($ih)/$_scale(current))}]
497            if {$wz > 1 && $hz > 1} {
498                $_image(zoom) configure -width $wz -height $hz
499                $_image(zoom) blank $bg
500                $_image(zoom) resample $imh -filter sinc
501                $_image(final) configure -width $wz -height $hz
502                $_image(final) copy $_image(zoom) -from "0 0"
503            }
504        }
505    }
506
507    set note [_top note]
508    if {[string length $note] > 0} {
509        if {[regexp {^html://} $note]} {
510            set note [string range $note 7 end]
511        } else {
512            regexp {&} $note {\007} note
513            regexp {<} $note {\&lt;} note
514            regexp {>} $note {\&gt;} note
515            regexp {\007} $note {\&amp;} note
516            regexp "\n\n" $note {<br/>} note
517            set note "<html><body>$note</body></html>"
518        }
519        set notes [$itk_interior.panes pane 1]
520        $itk_component(notes) load $note -in [file join [_top tooldir] docs]
521        $itk_interior.panes visibility 1 on
522    } else {
523        $itk_interior.panes visibility 1 off
524    }
525}
526
527# ----------------------------------------------------------------------
528# USAGE: _top image|note|tooldir
529#
530# Used internally to get the topmost image currently being displayed.
531# ----------------------------------------------------------------------
532itcl::body Rappture::ImageResult::_top {option} {
533    set top $_topmost
534    if {"" == $top} {
535        set top [lindex $_dlist 0]
536    }
537    if {"" != $top} {
538        switch -- $option {
539            image   { return [$top tkimage] }
540            note    { return [$top hints note] }
541            tooldir { return [$top hints tooldir] }
542            default { error "bad option \"$option\": should be image, note, tooldir" }
543        }
544    }
545    return ""
546}
547
548# ----------------------------------------------------------------------
549# USAGE: _zoom reset
550# USAGE: _zoom in
551# USAGE: _zoom out
552#
553# Called automatically when the user clicks on one of the zoom
554# controls for this widget.  Changes the zoom for the current view.
555# ----------------------------------------------------------------------
556itcl::body Rappture::ImageResult::_zoom {option args} {
557    switch -- $option {
558        rescale {
559            # empty list? then reset w/h max size
560            if {[llength $_dlist] == 0} {
561                set _max(w) 0
562                set _max(h) 0
563                set _scale(max) 1.0
564            } else {
565                set w [winfo width $itk_component(image)]
566                set h [winfo height $itk_component(image)]
567                if {$w == 1 && $h == 1} {
568                    return 0
569                }
570
571                if {$w < $h} {
572                    if {$_max(w)/double($_max(h)) > $w/double($h)} {
573                        set _scale(max) [expr {$_max(w)/double($w)}]
574                    } else {
575                        set _scale(max) [expr {$_max(h)/double($h)}]
576                    }
577                } else {
578                    if {$_max(w)/double($_max(h)) < $w/double($h)} {
579                        set _scale(max) [expr {$_max(h)/double($h)}]
580                    } else {
581                        set _scale(max) [expr {$_max(w)/double($w)}]
582                    }
583                }
584            }
585            return 1
586        }
587        reset {
588            set _scale(current) $_scale(max)
589            set _scale(default) 1
590            set _scale(x) 0.5
591            set _scale(y) 0.5
592        }
593        in {
594            set _scale(current) [expr {$_scale(current)*0.8}]
595            set _scale(default) 0
596        }
597        out {
598            set w [winfo width $itk_component(image)]
599            set h [winfo height $itk_component(image)]
600            if {$_max(w)/$_scale(current) > $w
601                  || $_max(h)/$_scale(current) > $h} {
602                # must be room left to zoom -- zoom out, but not beyond max
603                set _scale(current) [expr {$_scale(current)*1.25}]
604                if {$_scale(current) > $_scale(max)} {
605                    set _scale(current) $_scale(max)
606                }
607            } else {
608                # no room left to zoom -- zoom out max
609                set _scale(current) $_scale(max)
610            }
611
612            # fix the center of view, in case it is now out of bounds
613            if {$_scale(current) > 1.0} {
614                set _scale(x) 0.5
615                set _scale(y) 0.5
616            }
617
618            # keep this zoom setting now that we've zoomed out
619            set _scale(default) 0
620        }
621    }
622    $_dispatcher event -idle !rebuild
623}
624
625# ----------------------------------------------------------------------
626# USAGE: _move click <x> <y>
627# USAGE: _move drag <x> <y>
628# USAGE: _move release <x> <y>
629#
630# Called automatically when the user clicks and drags on the image
631# to pan the view.  Adjusts the (x,y) offset for the scaling info
632# and redraws the widget.
633# ----------------------------------------------------------------------
634itcl::body Rappture::ImageResult::_move {option args} {
635    switch -- $option {
636        click {
637            foreach {x y} $args break
638            $itk_component(image) configure -cursor fleur
639            set _scale(x0) $_scale(x)
640            set _scale(y0) $_scale(y)
641            set _scale(xclick) $x
642            set _scale(yclick) $y
643        }
644        drag {
645            foreach {x y} $args break
646            if {[info exists _scale(xclick)] && [info exists _scale(yclick)]} {
647                set w [winfo width $itk_component(image)]
648                set h [winfo height $itk_component(image)]
649                set wx [expr {round($_max(w)/$_scale(current))}]
650                set hy [expr {round($_max(h)/$_scale(current))}]
651                if {$wx > $w || $hy > $h} {
652                    set x [expr {$_scale(x0)-($x-$_scale(xclick))/double($wx)}]
653                    set y [expr {$_scale(y0)-($y-$_scale(yclick))/double($hy)}]
654                    if {$x*$_max(w) < 0.5*$w*$_scale(current)} {
655                        set x [expr {0.5*$w*$_scale(current)/$_max(w)}]
656                    }
657                    if {$x*$_max(w) > $_max(w) - 0.5*$w*$_scale(current)} {
658                        set x [expr {1 - 0.5*$w*$_scale(current)/$_max(w)}]
659                    }
660                    if {$y*$_max(h) < 0.5*$h*$_scale(current)} {
661                        set y [expr {0.5*$h*$_scale(current)/$_max(h)}]
662                    }
663                    if {$y*$_max(h) > $_max(h) - 0.5*$h*$_scale(current)} {
664                        set y [expr {1 - 0.5*$h*$_scale(current)/$_max(h)}]
665                    }
666                    set _scale(x) $x
667                    set _scale(y) $y
668                } else {
669                    set _scale(x) 0.5
670                    set _scale(y) 0.5
671                }
672                $_dispatcher event -idle !rebuild
673            }
674        }
675        release {
676            eval _move drag $args
677            $itk_component(image) configure -cursor ""
678            catch {unset _scale(xclick)}
679            catch {unset _scale(yclick)}
680        }
681        default {
682            error "bad option \"$option\": should be click, drag, release"
683        }
684    }
685}
686
687itcl::body Rappture::ImageResult::snap { w h } {
688    if { $w <= 0 || $h <= 0 } {
689        set w [image width $_image(final)]
690        set h [image height $_image(final)]
691    }
692    set img [image create picture -width $w -height $h]
693    $img resample $_image(final)
694    return $img
695}
696
697itcl::body Rappture::ImageResult::AddImageControls { inner widget } {
698    label $inner.size_l -text "Size:" -font "Arial 9"
699    set _downloadPopup(image_controls) $inner
700    set img $_image(plot)
701    set res "[image width $img]x[image height $img]"
702    Rappture::Combobox $inner.size -width 30 -editable no
703    $inner.size choices insert end \
704        "draft"  "Draft ($res)"         
705
706    label $inner.bgcolor_l -text "Background:" -font "Arial 9"
707    Rappture::Combobox $inner.bgcolor -width 30 -editable no
708    $inner.bgcolor choices insert end \
709        "black"  "Black" \
710        "white"  "White" \
711        "none"  "Transparent (PNG only)"         
712
713    label $inner.format_l -text "Format:" -font "Arial 9"
714    Rappture::Combobox $inner.format -width 30 -editable no
715    $inner.format choices insert end \
716        "png"  "PNG (Portable Network Graphics format)" \
717        "jpg"  "JPEG (Joint Photographic Experts Group format)" \
718        "gif"  "GIF (GIF Graphics Interchange Format)"
719
720    button $inner.go -text [Rappture::filexfer::label download] \
721        -command [itcl::code $this SetWaitVariable 1]
722
723    blt::table $inner \
724        0,0 $inner.format_l -anchor e \
725        0,1 $inner.format -anchor w -fill x  \
726        1,0 $inner.size_l -anchor e \
727        1,1 $inner.size -anchor w -fill x \
728        2,0 $inner.bgcolor_l -anchor e \
729        2,1 $inner.bgcolor -anchor w -fill x \
730        6,0 $inner.go -cspan 2 -pady 5
731    $inner.bgcolor value "Black"
732    $inner.size value "Draft ($res)"
733    $inner.format value  "PNG (Portable Network Graphics format)"
734}
Note: See TracBrowser for help on using the repository browser.