'''2004Dec17 [PS]''' This code is used by [Scan an EAN-13 barcode from an image], save it as ean13.tcl in the same directory. ---- ====== #EAN 13 generation and decode routines namespace eval ean13 { #Digit bar/space widths #Lefthand digits start with spaces, righthand with bars #Lefthand-even are pattern inverse of odd: zero: 3211 ==even==> 1123 set digits { {3 2 1 1}. {2 2 2 1} {2 1 2 2} {1 4 1 1} {1 1 3 2} {1 2 3 1} {1 1 1 4} {1 3 1 2} {1 2 1 3} {3 1 1 2} } set rdigits { {1 1 2 3} {1 2 2 2} {2 2 1 2} {1 1 4 1} {2 3 1 1} {1 3 2 1} {4 1 1 1} {2 1 3 1} {3 1 2 1} {2 1 1 3} } #parity encoding, the odd-even patterns of digits 2 through 7 set parity_table { ooooo oeoee oeeoe oeeeo eooee eeooe eeeoo eoeoe eoeeo eeoeo } proc lreverse { list } { set l {} for {set i [expr {[llength $list]-1}]} {$i> -1} {incr i -1} { lappend l [lindex $list $i] } return $l } proc scanline { line } { variable digits variable rdigits variable parity_table #Start scanning! #Line is: {{pixel0 width0 isbar0} {pixel1 . .} ... } set barcnt [llength $line] #try to locate a valid barcode (1=single bar, 0=single space): # 101 (6*4 bars/spaces) 01010 (6*4 bars/spaces) 101 # there are 30 bars and 29 spaces in a barcode # the width of the entire barcode has 95 element width units for {set i 0} {$i <$barcnt-59} {incr i} { #.t insert end "Offset $i\n" #Barcodes start with a bar... foreach {c isbar width} [lindex $line $i] {} if { !$isbar } { continue } #Calculate X, the single item width. foreach {c_end isbar width} [lindex $line [expr {$i+59}]] {} set X [expr {($c_end-$c)/95.0}] #Now translate to integer values: set widths {} for { set j $i } { $j < $i+59 } { incr j } { foreach {c isbar width} [lindex $line $j] {} lappend widths [expr { round( $width/$X ) } ] } #So, if this is valid EAN13, it should start with three ones: if { [lrange $widths 0 2] ne "1 1 1" } { #.t insert end "Bad start guard\n" continue } #It should also end with three ones: if { [lrange $widths end-2 end] ne "1 1 1" } { #.t insert end "Bad end guard\n" continue } #And the center pattern is five ones: if { [lrange $widths 27 31] ne "1 1 1 1 1" } { #.t insert end "Bad center guard\n" continue } #Got it. Try to decode. #.t insert end "Found guards\n" #Maybe reverse? #Is the first digit left or righthand? set d [lrange $widths 3 6] #.t insert end "First $d >> [lsearch $digits $d] [lsearch $rdigits $d]\n" if { [lsearch $rdigits $d] > -1 } { #yes. #.t insert end "Reverse! \n$widths ..\n" set widths [lreverse $widths] #.t insert end "$widths ..\n" } #Now decode: #First six digits: set number {} for {set j 0} {$j<6} {incr j} { set d [lrange $widths [expr {3+$j*4}] [expr {3+$j*4+3}] ] if { $j == 0 } { set n [lsearch $digits $d] } else { set n [lsearch $digits $d] if { $n > -1 } { append parity o } else { set n [lsearch $rdigits $d] append parity e } } #.t insert end "Left digits: $j == $d >> $n \n" if { $n == -1 } { break } append number $n } if { [string length $number] < 4 } { return "" } if { [string length $number] < 6 } { #decode error. return "partial $number" } set number [lsearch $parity_table $parity]$number #Last six digits: for {set j 0} {$j<6} {incr j} { set d [lrange $widths [expr {32+$j*4}] [expr {32+$j*4+3}] ] set n [lsearch $digits $d] #.t insert end "Right digits: $j == $d >> $n \n" if { $n == -1 } { break } append number $n } #.t insert end "All digits $number\n" if { [string length $number] == 13 } { set c [ean13_csum [string range $number 0 11]] if { $c ne [string index $number 12] } { return "partial/csum $number" } return $number } return "partial $number" } } proc ean13 { number } { set digits { {0 0001101 0100111 1110010} {1 0011001 0110011 1100110} {2 0010011 0011011 1101100} {3 0111101 0100001 1000010} {4 0100011 0011101 1011100} {5 0110001 0111001 1001110} {6 0101111 0000101 1010000} {7 0111011 0010001 1000100} {8 0110111 0001001 1001000} {9 0001011 0010111 1110100} } array set parity_enc { 0 {1 1 1 1 1} 1 {1 2 1 2 2} 2 {1 2 2 1 2} 3 {1 2 2 2 1} 4 {2 1 1 2 2} 5 {2 2 1 1 2} 6 {2 2 2 1 1} 7 {2 1 2 1 2} 8 {2 1 2 2 1} 9 {2 2 1 2 1} } if { [string length $number] == 12 } { set number $number[ean13_csum $number] } #left guard bars: lappend bars 101 #second system char: lappend bars [lindex [lindex $digits [string index $number 1]] 1] #the five digits that encode the first digit in their parity: foreach digit [split [string range $number 2 6] ""] \ enc $parity_enc([string index $number 0]) { lappend bars [lindex [lindex $digits $digit] $enc] } #center guard bars: lappend bars 01010 #the right hand chars: for {set i 7} {$i<13} {incr i} { lappend bars [lindex [lindex $digits [string index $number $i]] 3] } #and the final guards: lappend bars 101 return [list $bars $number] } proc ean13_csum { number } { set odd 1 set sum 0 foreach digit [split $number ""] { set odd [expr {!$odd}] #puts "$sum += ($odd*2+1)*$digit :: [expr {($odd*2+1)*$digit}]" incr sum [expr {($odd*2+1)*$digit}] } set check [expr {$sum % 10}] if { $check > 0 } { return [expr {10 - $check}] } return $check } } ====== <> Barcode