canvasexport

canvasexport mvg $canvas
returns canvas content coded as ImageMagickʼs vector language.
canvasexport svg $canvas
returns canvas content coded as Scalable Vector Graphics.
canvasToBitmap
executes ImageMagickʼs tool convert to turn the code above to a PNG image.

namespace eval canvasexport {
  
  namespace import ::tcl::mathop::* ::tcl::mathfunc::*
  
  proc mvg canvas {
    set col2hex {color {
        if {[winfo exists $color] && [winfo class $color] eq "Canvas"} then {
          set color [$color cget -bg]
        }
        if {$color eq ""} then {
          set result none
        } else {
          set result #
          foreach x [winfo rgb . $color] {
            append result [format %02x [expr {int($x / 256)}]]
          }
        }
        set result
      }}
    set splinecoords2mvg {{coords {canBeClosed yes}} {
        set closed [expr {$canBeClosed &&
            [lindex $coords 0] == [lindex $coords end-1] &&
            [lindex $coords 1] == [lindex $coords end]}]
        if {$closed} then {
          lassign [lrange $coords end-3 end] x0 y0 x1 y1
          set x [expr {($x0+$x1)/2.0}]
          set y [expr {($y0+$y1)/2.0}]
          lset coords end-1 $x
          lset coords end $y
          set coords [concat $x $y $coords]
        }
        if {[llength $coords] == 6} then {
          lreplace $coords 2 1 Q
        } else {
          lappend result {*}[lrange $coords 0 1]
          set co1 [lrange $coords 2 end-4]
          set co2 [lrange $coords 4 end-2]
          foreach {x1 y1} $co1 {x2 y2} $co2 {
            lappend result $x1 $y1 [expr {($x1+$x2)/2.0}] [expr {($y1+$y2)/2.0}]
          }
          lappend result {*}[lrange $coords end-3 end]
          lreplace $result 2 1 Q
        }
      }}
    array set mode {fill "" 
      stroke "" 
      strokewidth "" 
      joinstyle "" 
      capstyle ""
      fontfamily "" 
      fontsize ""}
    lappend result [list viewbox 0 0 [winfo width $canvas] [winfo height $canvas]]
    # lappend result [list stroke none]\
      [list fill [apply $col2hex $canvas]]\
      [list rectangle 0 0 [winfo width $canvas] [winfo height $canvas]]
    foreach item [$canvas find all] {
      if {[$canvas itemcget $item -state] eq "hidden"} continue
      set type [$canvas type $item]
      lappend result "# $type ... [$canvas gettags $item]"
      # outline width
      if {$type in {polygon oval arc rectangle line}} then {
        set width [$canvas itemcget $item -width]
        if {$width != $mode(strokewidth)} then {
          set mode(strokewidth) $width
          lappend result [list stroke-width $width]
        }
      }
      # fill, stroke
      if {$type in {polygon oval arc rectangle}} then {
        set fill [apply $col2hex [$canvas itemcget $item -fill]]
        if {$mode(fill) ne $fill} then {
          set mode(fill) $fill
          lappend result [list fill $fill]
        }
        set stroke [apply $col2hex [$canvas itemcget $item -outline]]
        if {$mode(stroke) ne $stroke} then {
          set mode(stroke) $stroke
          lappend result [list stroke $stroke]
        }
      }
      # joinstyle
      if {$type in {polygon line}} then {
        set joinstyle [$canvas itemcget $item -joinstyle]
        if {$mode(joinstyle) ne $joinstyle} then {
          set mode(joinstyle) $joinstyle
          lappend result [list stroke-linejoin $joinstyle]
        }
      }
      # line color, capstyle
      if {$type in {line}} then {
        if {$mode(fill) ne "none"} then {
          set mode(fill) none
          lappend result [list fill none]
        }
        set stroke [apply $col2hex [$canvas itemcget $item -fill]]
        if {$mode(stroke) ne $stroke} then {
          set mode(stroke) $stroke
          lappend result [list stroke $stroke]
        }
        set capstyle [dict get {butt butt projecting square round round}\
            [$canvas itemcget $item -capstyle]]
        if {$mode(capstyle) ne $capstyle} then {
          set mode(capstyle) $capstyle
          lappend result [list stroke-linecap $capstyle]
        }
      }
      # text color, font, size
      if {$type in {text}} then {
        if {$mode(stroke) ne "none"} then {
          set mode(stroke) none
          lappend result [list stroke none]
        }
        set fill [apply $col2hex [$canvas itemcget $item -fill]]
        if {$mode(fill) ne $fill} then {
          set mode(fill) $fill
          lappend result [list fill $fill]
        }
        set font [$canvas itemcget $item -font]
        # font-family, font-size
        if {$font in font names} then {
          set fontsize [font configure $font -size]
          set fontfamily [font configure $font -family]
        } else {
          if {[llength $font] == 1} then {
            set fontsize 12
          } else {
            set fontsize [lindex $font 1]
          }
          set fontfamily [lindex $font 0]
        }
        if {$fontsize < 0} then {
          set fontsize [expr {int(-$fontsize / [tk scaling])}]
        }
        if {$mode(fontsize) ne $fontsize} then {
          set mode(fontsize) $fontsize
          lappend result [list font-size $fontsize]
        }
        #
        # Attention! In some cases, IM assumes 72dpi,
        # where 90dpi is necessary.
        # Then, on cmd line, use switch -density as follows:
        # convert -density 90 test.mvg test.png
        #
        if {$mode(fontfamily) ne $fontfamily} then {
          set mode(fontfamily) $fontfamily
          lappend result [list font $fontfamily]
        }
        #
        # Attention! Care that IM has access to fonts.
        # If not, an error msg is shown,
        # then the default font is used silently.
        #
      }
      set line {}
      set coords [$canvas coords $item]
      switch -exact -- $type {
        line {
          # start of path
          lappend line path 'M
          set smooth [$canvas itemcget $item -smooth]
          if {[string is true -strict $smooth]} then {
            if {[$canvas itemcget $item -arrow] eq "none"} then {
              lappend line {*}[apply $splinecoords2mvg $coords]
            } else {
              lappend line {*}[apply $splinecoords2mvg $coords false]
            }
          } elseif {[string is false -strict $smooth]} then {
            lappend line {*}[lrange $coords 0 1] L {*}[lrange $coords 2 end]
          } else {
            lappend line {*}[lrange $coords 0 1] C {*}[lrange $coords 2 end]
          }
          append line '
          lappend result $line
        }
        polygon {
          lappend line path 'M
          set smooth [$canvas itemcget $item -smooth]
          if {[string is false -strict $smooth]} then {
            # line
            lassign $coords x0 y0
            lassign [lrange $coords end-1 end] x1 y1
            set x [expr {($x0+$x1)/2.0}]
            set y [expr {($y0+$y1)/2.0}]
            lappend line $x $y L {*}$coords $x $y Z
          } elseif {[string is true -strict $smooth]} then {
            # spline
            if {[lindex $coords 0] != [lindex $coords end-1] ||
              [lindex $coords 1] != [lindex $coords end]} then {
              lappend coords {*}[lrange $coords 0 1]
            }
            lappend line {*}[apply $splinecoords2mvg $coords]
          } else {
            # raw = bezier
            lappend line {*}[lrange $coords 0 1]\
              C {*}[lrange $coords 2 end] {*}[lrange $coords 0 1]
          }
          append line '
          lappend result $line
        }
        oval {
          lassign $coords x0 y0 x1 y1
          lappend line ellipse [expr {($x0+$x1)/2.0}] [expr {($y0+$y1)/2.0}]\
            [expr {$x1-($x0+$x1)/2.0}] [expr {$y1-($y0+$y1)/2.0}] 0 360
          lappend result $line
        }
        arc {
          lappend result [list push graphic-context]
          lappend result [list stroke-linejoin miter]
          # lappend result [list stroke-linejoin bevel]
          # lappend result [list stroke-linejoin round]
          lappend line path 'M
          lassign $coords x0 y0 x1 y1
          set rx [expr {($x1-$x0)/2.0}]
          set ry [expr {($y1-$y0)/2.0}]
          set x [expr {($x0+$x1)/2.0}]
          set y [expr {($y0+$y1)/2.0}]
          set f [expr {acos(0)/90}]
          set start [$canvas itemcget $item -start]
          set startx [expr {cos($start*$f)*$rx+$x}]
          set starty [expr {sin(-$start*$f)*$ry+$y}]
          set angle [expr {$start+[$canvas itemcget $item -extent]}]
          set endx [expr {cos($angle*$f)*$rx+$x}]
          set endy [expr {sin(-$angle*$f)*$ry+$y}]
          # start point
          lappend line\
            [expr {($startx+$x)/2.0}] [expr {($starty+$y)/2.0}]\
            $startx $starty
          lappend line A
          # radiusx, radiusy
          lappend line $rx $ry
          # angle -- always 0
          lappend line 0
          # "big" or "small"?
          lappend line [expr {$angle-$start > 180}]
          # right side (always)
          lappend line 0
          # end point
          lappend line $endx $endy
          # close path
          lappend line L $x $y Z
          append line '
          lappend result $line
          lappend result [list pop graphic-context]
        }
        rectangle {
          lappend result\
            [list push graphic-context]\
            [list stroke-linejoin miter]\
            [concat rectangle $coords]\
            [list pop graphic-context]
        }
        text {
          lassign [$canvas bbox $item] x0 y0 x1 y1
          lappend line text $x0 $y1
          append line " '[$canvas itemcget $item -text]'"
          lappend result $line
        }
        image - bitmap {
          set img [$canvas itemcget $item -image]
          set file [$img cget -file]
          lassign [$canvas bbox $item] x0 y0
          lappend result [list image over $x0 $y0 0 0 '$file']       
        }
        default {
          lappend result "# not yet done:\
              [$canvas type $item] [$canvas coords $item]\
              ([$canvas gettags $item])"
        }
      }
    }
    join $result \n
  }

  proc svg {{canvas .c} {title tk}} {
    lappend result [subst {<svg xmlns="http://www.w3.org/2000/svg"\
          width="[$canvas cget -width]px"\
          height="[$canvas cget -height]px"\
          viewbox="0 0 [$canvas cget -width] [$canvas cget -height]">}]
    foreach item [$canvas find all] {
      if {[$canvas itemcget $item -state] eq "hidden"} continue
      if {[$canvas itemcget $item -fill] eq ""} continue
      switch -exact -- [$canvas type $item] {
        line {
          lappend result [lineToSVG $item]
        }
        polygon {
          lappend result [polyToSVG $item]
        }
        default {
          lappend result "<!-- [$canvas type $item] not yet done -->"
        }
      }
    }
    string cat \n [join $result "\n  "] \n </svg>
  }

  proc hex color {
    lassign [winfo rgb . $color] r g b
    string cat #\
      [string range [format %04x $r] 0 1]\
      [string range [format %04x $g] 0 1]\
      [string range [format %04x $b] 0 1]
  }
  
  proc lineToSVG {item {canvas .c}} {
    set coords [$canvas coords $item]
    set style "fill:none;\
        fill-rule:evenodd;\
        stroke:[hex [$canvas itemcget $item -fill]];\
        stroke-width:[$canvas itemcget $item -width]px;\
        stroke-linecap:[$canvas itemcget $item -capstyle];\
        stroke-linejoin:[$canvas itemcget $item -joinstyle];\
        stroke-opacity:1"
    set path [lmap {x y} $coords {string cat $x , $y}]
    if {[$canvas itemcget $item -smooth] eq "raw"} then {
      # bezier 
      while {[llength $path] % 3 != 1} {
        # gemäß Canvas-Regeln für Bezier
        lappend path [lindex $path end]
      }
      string cat <path\
        " d=" \" "M [lindex $path 0] C [lrange $path 1 end]" \"\
        " class=" \" [concat [$canvas type $item] [$canvas gettags $item]] \"\
        " style=" \" $style \"\
        " id=" \" [$canvas type $item]-[$canvas find withtag $item] \"\
        " />"
    } elseif {[$canvas itemcget $item -smooth]} then {
      # spline
      # not yet done
    } else {
      # straight
      string cat <path\
        " d=" \" "M $path" \"\
        " class=" \" [concat [$canvas type $item] [$canvas gettags $item]] \"\
        " style=" \" $style \"\
        " id=" \" [$canvas type $item]-[$canvas find withtag $item] \"\
        " />"
    }
  }
  
  proc polyToSVG {item {canvas .c}} {
    set coords [$canvas coords $item]
    set style "fill:[hex [$canvas itemcget $item -fill]];\
        fill-rule:evenodd;\
        stroke:[$canvas itemcget $item -outline];\
        stroke-width:[$canvas itemcget $item -width]px;\
        stroke-linejoin:[$canvas itemcget $item -joinstyle];\
        stroke-opacity:1"
    set path [lmap {x y} $coords {string cat $x , $y}]
    if {[$canvas itemcget $item -smooth] eq "raw"} then {
      # bezier
      while {[llength $path] % 3 != 0} {
        # gemäß Canvas-Regeln für Bezier
        lappend path [lindex $path end]
      }
      # gemäß SVG-Regeln für Bezier
      lappend path [lindex $path 0]
      #
      string cat <path\
        " d=" \" "M [lindex $path 0] C [lrange $path 1 end] Z" \"\
        " class=" \" [concat [$canvas type $item] [$canvas gettags $item]] \"\
        " style=" \" $style \"\
        " id=" \" [$canvas type $item]-[$canvas find withtag $item] \"\
        " />"
    } elseif {[$canvas itemcget $item -smooth]} then {
      # spline
      # not yet done
    } else {
      # straight
      # not yet done
    }
  }
  
  namespace export svg mvg
  namespace ensemble create
}
proc canvasToBitmap {{canvas .c} {file X:} {background white} args} {
  variable externalApp
  set ch [open tmp.mvg w]
  puts $ch [canvasexport mvg $canvas]
  close $ch
  exec $externalApp(mvg)\
    -size [$canvas cget -width]x[$canvas cget -height]\
    xc:$background\
    -draw @tmp.mvg\
    $file {*}$args
}

© Wolf-Dieter Busch | Home | Sitemap | Urheber | A-Z