gnocl::pixbuf -drawing on pixbufs using the actions sub-command

WJG (16/09/25) The latest pre-release versions of Gnocl have support for Cairo function calls through the use of "actions". Adding this functionality allows direct drawing and manipulation of the pixbuf and drawing area objects without the need to modify the C source library modules.

For those applications requiring the use of a fully features canvas object, bindings exist for the Goocanvas widget which, in turn, is built upon Cairo.

The follow sample source script demonstrates how the Cairo "code samples" can be implemented using the pixbuf actions sub-command. Each line of the drawing script is parsed and executes the corresponding Cairo API call.

gnocl::pixBuf -action:gradient

# !/bin/sh
# the next line restarts using tclsh \
exec tclsh "$0" "$@"

package require Gnocl

# set colours and draw primitives
proc test-draw-1 { im pb } {

        set xc 250 ; set yc 250                                                                                                                                                                       
        set rad [expr 500/4]
        set yc [expr $rad+$rad/6]
        set frac 0


        for {set frac 0} {$frac <= 360} {incr frac 15} { 
                set actions "
rectangle {0 0 500 500}
set_source_rgba {0 0 0 1}
fill                
set_source_rgba  { 1 1 1 1 }
set_line_width 3
arc  { $xc $yc $rad 360 }
close_path  
fill
set_source_rgb  { 1 0 0 1}
arc { $xc $yc [expr $rad-4] -90 [expr -90+$frac] }
line_to {$xc $yc}
fill
set_source_rgba  { 1 1 1 1}
arc  { $xc $yc $rad 0 360 }
stroke
select_font_face { Sans }
set_font_size {25}
move_to { $xc [expr $yc+$rad+40]}
show_text $frac
stroke
set_source_rgba  { 0 1 1 1}
set_line_width 5
move_to {100 200}
curve_to {200 100 300 300 400 100}
stroke
surface_destroy
"
        $pb actions $actions
        $im configure -image %?$pb
        gnocl::update
        $pb save myfile-$frac.png
        }
        
}


# load images
proc test-draw-2 { im pb } {
        
set actions "
# {some comment}
        image_surface_create_from_png romedalen.png
        set_source_surface { 10 10}
        paint
        
# {another comment}
        image_surface_create_from_png hatch.png
        rotate 45
        set_source_surface { 10 10}
        paint
"
        $pb actions $actions
        $im configure -image %?$pb
        gnocl::update
        #$pb save myfile-$frac.png        
        
}

# draw text
proc test-draw-3 {im pb} {

set actions "
# {create text}
set_source_rgba  { 1 1 1 1 }
select_font_face { Sans normal bold}
set_font_size {25}
move_to {20 100}
show_text Hidehi
stroke

# {create text}
move_to {20 150}
show_text Hodeho
stroke

# {create text}

set_font_size {90}
move_to {20 250}
text_path Campers!
set_source_rgba {0 1 1 1}
fill_preserve
set_source_rgba {1 0 1 1}
set_line_width 3
stroke
surface_destroy
"
        $pb actions $actions
        $im configure -image %?$pb
        gnocl::update
        
}

# CAIRO SAMPLES ~~~~~~~~~~~~~~~~~~~~~~~~~

proc action:arc {} {
        
set xc 128.0
set yc 128.0
set radius 100.0
set angle1 45.0 
set angle2 180.0

set actions "
set_source_rgba {0 0 0 1}
set_line_width {10.0}
arc {$xc $yc $radius $angle1 $angle2}
stroke

# {draw helping lines}
set_source_rgba {1 0.2 0.2 0.6}
set_line_width {6.0}

arc {$xc $yc 10.0 0 360}
fill

arc {$xc $yc $radius $angle1 $angle1}
line_to {$xc $yc}
arc {$xc $yc $radius $angle2 $angle2}
line_to {$xc $yc}
stroke

"
return $actions
}

proc action:arc_negative {} {
set xc 128.0
set yc 128.0
set radius 100.0
set angle1 45.0 
set angle2 180.0
set actions "
set_source_rgba {0 0 0 1}
set_line_width {10.0}
arc_negative {$xc $yc $radius $angle1 $angle2}
stroke

# {draw helping lines}
set_source_rgba {1 0.2 0.2 0.6}
set_line_width {6.0}

arc {$xc $yc 10.0 0 360}
fill

arc {$xc $yc $radius $angle1 $angle1}
line_to {$xc $yc}
arc {$xc $yc $radius $angle2 $angle2}
line_to {$xc $yc}
stroke

"

return $actions
}


proc action:dash {} {
set dashes {50.0 10.0 10.0 10.0}
set ndash  [llength $dashes]
set offset -100.0

set actions "
set_source_rgb {0 0 0}
set_dash {{$dashes} $ndash $offset}
set_line_width 10.0

move_to {128.0 25.6}
line_to {230.4 230.4}
rel_line_to {-102.4 0.0}
curve_to {51.2 230.4 51.2 128.0 128.0 128.0}

stroke
"
return $actions

}


proc action:fill_stroke_2 {} {
set actions "
move_to {128.0 25.6}
line_to {230.4 230.4}
rel_line_to {-102.4 0.0}
curve_to {51.2 230.4 51.2 128.0 128.0 128.0}
close_path

move_to {64.0 25.6}
rel_line_to {51.2 51.2}
rel_line_to {-51.2 51.2}
rel_line_to {-51.2 -51.2}
close_path 

set_line_width {10.0}
set_source_rgb {0 0 1}
fill_preserve 
set_source_rgb {0 0 0}
stroke
"
return $actions
}

proc action:clip {} {
        
set actions "
set_source_rgb {0 0 0 1}
arc {128.0 128.0 76.8 0 360}
clip 
new_path   

# {current path is not consumed by clip}
rectangle {0 0 256 256}
fill 
set_source_rgb {0 1 0 1}
move_to {0 0}
line_to {256 256}
move_to {256 0}
line_to {0 256}
set_line_width {10.0}
stroke 
"
return $actions
}


proc action:clip_image {} {
set actions "
arc {128.0 128.0 76.8 0 360}
clip
new_path 
# {path not consumed by clip}

image_surface_create_from_png romedalen.png
scale {0.5 0.5}

set_source_surface {0 0}
paint

surface_destroy
"
return $actions

}


proc action:curve_rectangle {} {

# a custom shape that could be wrapped in a function
set x0 25.6   ;# parameters like rectangle
set y0 25.6
set rect_width 204.8
set rect_height 204.8
set radius 102.4 ;# and an approximate curvature radius

set x1 [expr $x0+$rect_width]
set y1 [expr $y0+$rect_height]

# if (!rect_width || !rect_height) return;


set actions ""
if { [expr $rect_width/2] < $radius } {
    if { [expr $rect_height/$2]<$radius } {
        lappend actions \
"move_to {$x0 [expr ($y0+$y1)/2]}
curve_to {$x0 $y0 $x0 $y0 [expr ($x0+$x1)/2] $y0}
curve_to {$x1 $y0 $x1 $y0 $x1 [expr ($y0+$y1)/2]}
curve_to {$x1 $y1 $x1 $y1 [expr ($x1+$x0)/2 $y1)}
curve_to {$x0 $y1 $x0 $y1 $x0 [expr ($y0+$y1)/2)]}"
    } else {
        lappend actions \
"move_to  {$x0 [expr $y0+$radius]}
curve_to {$x0 $y0 $x0 $y0 [expr ($x0 + $x1)/2] $y0}
curve_to {$x1 $y0 $x1 $y0 $x1 $y0+$radius}
line_to  {$x1 [expr $y1-$radius]}
curve_to {$x1 $y1 $x1 $y1 [expr ($x1+$x0)/2] $y1}
curve_to {$x0 $y1 $x0 $y1 $x0 [expr $y1-$radius}"
    }
} else {
    if {[expr $rect_height/2]<$radius} {
        lappend actions \
"move_to  {$x0 [expr ($y0+$y1)/2]}
curve_to {$x0 $y0 $x0 $y0 [expr $x0+$radius] $y0}
line_to  {[expr $x1-$radius] $y0}
curve_to {$x1 $y0 $x1 $y0 $x1 [expr ($y0+$y1)/2]}
curve_to {$x1 $y1 $x1 $y1 [expr $x1-$radius] $y1}
line_to  {[expr $x0+$radius] $y1}
curve_to {$x0 $y1 $x0 $y1 $x0 [expr ($y0+$y1)/2]}"
    } else {
                lappend actions \
"move_to  {$x0 [expr $y0+$radius]}
curve_to {$x0 $y0 $x0 $y0 [expr $x0+$radius] $y0}
line_to  {[expr $x1-$radius] $y0}
curve_to {$x1 $y0 $x1 $y0 $x1 [expr $y0+$radius]}
line_to  {$x1 [expr $y1-$radius]}
curve_to {$x1 $y1 $x1 $y1 [expr $x1-$radius] $y1}
line_to  {[expr $x0+$radius] $y1}
curve_to {$x0 $y1 $x0 $y1 $x0 [expr $y1-$radius]}"
    }
}


lappend actions "
close_path
set_source_rgb {0.5 0.5 1}
fill_preserve
set_source_rgba {0.5 0 0 0.5}
set_line_width {10.0}
stroke"

return [join $actions]

}

proc action:image_pattern {} { 

puts [expr (512/256.0)*5.0]~[expr (384/256.0)*5.0]

set  actions "
# {load image, create surface and set opts}
image_surface_create_from_png romedalen.png
pattern_create_for_surface
pattern_set_extend repeat

# {this will affect the rectangle}
translate {128 128}
rotate 45
scale {[expr 1 / sqrt (2)] [expr 1 / sqrt (2)]}
translate {-128 -128}

# {
scale x/y to squeeze-fit 5x5 array based upon pattern created from image 
ensure values passed to expr are floats ensure non-integer calculations
eg: [expr (512.0/256.0)*5.0] [expr (384.0/256.0)*5.0]
}

matrix_init_scale {[expr (512.0/256.0)*5.0] [expr (384.0/256.0)*5.0]}

pattern_set_matrix

set_source pattern

# {create and render the rectangle}
rectangle {0 0 256 256}
fill        

# {tidy up}
pattern_destroy
surface_destroy
"
}


proc action:rounded_rectangle {} {
# a custom shape that could be wrapped in a function
set x 25.6 ;#parameters like rectangle
set y 25.6
set width 204.8
set height 204.8
set aspect 1.0 ;# aspect ratio */
set corner_radius [expr $height/10.0]
set radius [expr $corner_radius/$aspect]

set actions "
new_sub_path
arc {[expr $x + $width - $radius] [expr $y + $radius] $radius  -90 0}
arc {[expr $x + $width - $radius] [expr $y + $height - $radius] $radius 0 90}
arc {[expr $x + $radius] [expr $y + $height - $radius] $radius 90 180}
arc {[expr $x + $radius] [expr $y + $radius] $radius 180 270}
close_path

set_source_rgb {0.5 0.5 1}
fill_preserve
set_source_rgba {0.5 0 0 0.5}
set_line_width {10.0}
stroke
"
return $actions
}


proc action:curve_to {} {
set x  25.6  ; set y 128.0
set x1 102.4 ; set y1 230.4
set x2 153.6 ; set y2 25.6
set x3 230.4 ; set y3 128.0

set actions "
set_source_rgba {0 0 0 1}
move_to {$x $y}
curve_to {$x1 $y1 $x2 $y2 $x3 $y3}
set_line_width 10.0
stroke
set_source_rgba {1 0.2 0.2 0.6}
set_line_width 6.0
move_to {$x $y}   
line_to {$x1 $y1}
move_to {$x2 $y2}
line_to {$x3 $y3}
stroke
"
return $actions
}

proc action:fill_style {} {
set actions "
set_line_width 6
rectangle {12 12 232 70}

new_sub_path 
arc {64 64 40 0 360}
new_sub_path 
arc_negative {192 64 40 0 -360}

set_fill_rule even-odd
set_source_rgb {0 0.7 0} 
fill_preserve
set_source_rgb {0 0 0} 
stroke

translate {0 128}
rectangle {12 12 232 70}
new_sub_path
arc {64 64 40 0 360}
new_sub_path
arc_negative {192 64 40 0 -360}

set_fill_rule winding
set_source_rgb {0 0 0.9}
fill_preserve 
set_source_rgb {0 0 0} 
stroke
"
return $actions
}

proc action:gradient {} {
set actions "
pattern_create_linear {0.0 0.0 0.0 256.0}
pattern_add_color_stop_rgba {1 0 0 0 1}
pattern_add_color_stop_rgba {0 1 1 1 1}
rectangle {0 0 256 256}
set_source pattern
fill
pattern_destroy

pattern_create_radial {115.2 102.4 25.6 102.4 102.4 128.0}
pattern_add_color_stop_rgba {0 1 1 1 1}
pattern_add_color_stop_rgba {1 0 0 0 1}
set_source pattern
arc {128.0 128.0 76.8 0 360}
fill
pattern_destroy
"
return $actions
}

proc action:image {} {
        
lassign [gnocl::pixBuf fileInfo romedalen.png] w h t
        
        
set actions "
image_surface_create_from_png romedalen.png
translate {128 128}
rotate 45
scale {[expr 256.0/$w] [expr 256.0/$h]}
translate {[expr -0.5*$h] [expr -0.5*$h]}
set_source_surface {0 0}
paint
"

#puts $actions

return $actions

}


proc action:multi_segment_caps {} {
set actions "

set_source_rgba {0 0 0 1}

move_to {50 75}
line_to {200 75}

move_to {50 125}
line_to {200 125}

move_to {50 175}
line_to {200 175}

set_line_width 30
set_line_cap round
stroke

"
return $actions
}

proc action:line_cap {} {
set actions "
set_source_rgba {0 0 0 1}
set_line_width {30.0}
# {/* default */}
set_line_cap butt
move_to {64.0 50.0} line_to {64.0 200.0}
stroke
set_line_cap round
move_to {128.0 50.0} line_to {128.0 200.0}
stroke 
set_line_cap square
move_to {192.0 50.0} line_to {192.0 200.0}
stroke

# {/*draw helping lines */}
set_source_rgb {1 0.2 0.2}
set_line_width {2.56}
move_to {64.0 50.0} line_to {64.0 200.0}
move_to {128.0 50.0}  line_to {128.0 200.0}
move_to {192.0 50.0} line_to {192.0 200.0}
stroke
"
return $actions
}


proc action:line_join {} {
set actions "
set_source_rgba {0 0 0 1}
set_line_width {40.96}
move_to {76.8 84.48}
rel_line_to {51.2 -51.2}
rel_line_to {51.2 51.2}
set_line_join miter # default
stroke

move_to {76.8 161.28}
rel_line_to {51.2 -51.2}
rel_line_to {51.2 51.2}
set_line_join bevel
stroke

move_to {76.8 238.08}
rel_line_to {51.2 -51.2}
rel_line_to {51.2 51.2}
set_line_join round
stroke

"
return $actions
}

proc action:clear {} {
set actions "
set_source_rgba {0 0 0 0}
rectangle {0 0 256 256}
fill
"
return $actions
}


proc wdg:comboBox {} {

foreach item [lsort [info procs action*]] {
        lappend itemList [list $item $item]
}


set container [gnocl::hBox]
set box [gnocl::vBox]
set txt [gnocl::text -baseFont {Mono 9} -widthRequest 256 ] ;# -baseColor black -textColor green
set pb [gnocl::pixBuf new -width 256 -height 256 -alpha 1]
set im [gnocl::image -image %?$pb ]
$pb actions [[lindex [lindex $itemList 0] 1]]

set cb [gnocl::comboBox  -itemList $itemList -variable comboBoxVar -data_ $pb -data "$pb $im $txt" -onChanged { 
        [lindex %d 0] clear
        [lindex %d 0] actions [%v]
        [lindex %d 1] configure -image %?[lindex %d 0] 
        [lindex %d 2] set [%v]
        } ]
$box add $cb
$box add $im
#$box add [gnocl::label -textVariable comboBoxVar] -expand 0.5

$cb changed

$container add $box  
$container add $txt -fill 1 -expand 1
return "$container -fill 1 -expand 1"

}

gnocl::window -child [wdg:comboBox] -width 750