2004Dec17 PSThis 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
}
}