CheapEasy DIY Barcodes in R

I couldn’t believe how expensive the software was for writing barcodes, so I wrote a short program in R to do it for FREE. And, frankly it should be faster and easier if you already have your labels in an Excel file. You don’t really need to understand the program or even R functions to use it, as long as you know how to run an R program.

Setup and Overview:

[UPDATED (see notes below)] – R-code. Start with this (Note I could not upload a .R file, so this is .txt but still an R program).

Input – barcodes128.csv – You need this file to run the program. Save it in your working directory (see comments in R code for how to set this). AND labels.csv – This is a sample file showing the format for your labels. Even though it’s a .csv, it is a single column with each label as a separate row, so there are no actual commas

Output – BarcodesOut.pdf – A sample output: a pdf file for the 0.5″x1.75″ Worth Poly Label WP0517 (Polyester Label Stock), currently in the lab

That’s really all you need to know, everything that follows is extraneous info. If you have any problems, check out the Detailed Instructions, Troubleshooting Tips, or add a comment below.

I’m doing this for a few reasons: First, it looks like the labelRIGHT software in Kathryn’s Blog post is gone or not working properly on the lab computer. Second, I want to print barcodes from my own computer. Third, I want to have something that I can integrate with other automated software down the line for dealing with large numbers of seed samples. Fourth, it seemed like a fun challenge, but turned out not to be so much fun or challenging. But a little R-exercise is never a bad thing.

There are a number of different barcode formats, but I’m going to start with the relatively simple ‘Code 128’, which is a 1-dimensional code that can use any of the 128 ASCII characters, which should work well for plant tags. After reading the Wikipedia entry it seemed relatively simple. The hardest part was calculating the check character. At some point in the future I might write a 2D version to integrate with Android software… maybe…

How it works:

Details of this method should be evident when you read through the Wikipedia link and then the R code. Briefly, each character is defined by a series of wide and narrow lines. From there, it’s just a matter of translating an input code to a series of these lines, and then outputting them in a size that works for plant tags.

It’s not as hard as it seems because the Wikipedia page provides a binary code for each ASCII character. This binary represents black and white lines, so for each label it’s just a matter of:

  1. Read the label.
  2. Break-up the label string into individual characters.
  3. Translate each character into its corresponding binary code.
  4. Combine the binaries for each character into a long string of 1s and 0s.
  5. Calculate the check code character and translate to its corresponding binary.
  6. Add ‘quiet zones’ of 10 zeros, start code, check code and stop code.
  7. Set up the pdf and graphics output.
  8. Draw the barcode using the binary code. This is essentially just a graph: Moving from 0 to N where N=number of binary digits in the barcode, draw a black (1) or white (0) vertical line.
  9. Add the label name in regular ASCII since most of us are not fluent in Code128.
  10. Close the pdf file

Detailed instructions (follow along in the R comments):

1. Save the ASCII barcode conversion file “barcodes128.csv” in your R working directory. Here are a few lines of the file:

  • ASCII,Barcode
  • 32,11011001100
  • 33,11001101100

ASCII holds the ASCII code for each character (e.g. 32 is the ASCII number for space, 33 is !, etc.)

Barcode is a binary code that will be used to convert each ASCII character into the corresponding lines (1) and spaces (0).

2. Create a .csv file for your barcode labels. If you are using Excel, just create a single column with each label in a separate row, then choose “Save As” and then in the pull-down tab in the save window choose “.csv”. You can save this as “labels.csv” in you R working directory if you don’t want to bother changing the file name in the R code.

3. Change the ‘setwd()’ from “C:/Users/Alliaria/Documents/Barcode Generator” to the path of your working directory (containing ‘barcodes.csv’ and ‘labels.csv’). This is where the pdf will be saved.

4. Run the script.

5. Open the pdf and print!

Troubleshooting Tips:

Barcodes not lining up correctly with the labels. Make sure you choose ‘Actual Size’ in the Print Options. The output is a paper of EXACTLY 8.5”x 11” dimension. Unfortunately, when I initially printed from Acrobat Reader, the settings default to ‘fit’ (sometimes called ‘scale to fit page’). This messed up all the margins.

Want to use a different paper/size. This is possible but you will have to mess around with the ‘par’ and ‘pdf’ settings to get all the margins to line up.

Long label name not scanning properly. There is no strict limit to the number of characters you can use. However, the line width scales down as the number of characters increases to fit on the page, so at some point the lines will be too thin for the printer. Shorten the label name or buy a better printer.

Moderate/short label not scanning properly. Make sure you are using ASCII characters and the correct ‘start code’ if you use anything other than basic alphanumeric characters. See the StartCode variable and the Wikipedia link for more info about this.

———————————————————————————————-

[UPDATE OCTOBER 30, 2012]

Here is updated R-code (DIYBarcodes.R), with a couple small additions:

1. User-defined “blankgraphs” – sets N blank labels before adding the “labels.csv” labels. This is useful for printing on partially-used pages. Simply count the number of used/empty labels and input that to “blankgraphs”. Note that the numbering is across columns, then down rows, so for example the top-left is #1, and top-right is #4, and the first label in the second row is #5.

2. Limit to label length – “MaxLength” variable was added and set to 15. I have found out (the hard way) that this is the maximum number of characters that can be scanned by our WORTHData Tricoders. Labels can still exceed this length but will be printed without a barcode and instead will have an error message “CODE TOO LONG”. For this reason you should inspect the PDF output file before printing.

2 thoughts on “CheapEasy DIY Barcodes in R

  1. This is great, Rob! I wrote something similar a while back for the fun and challenge, and also found it to be not-so-fun or challenging. Let me know if you decide to tackle the 2D barcodes, as I started to work on a QR code generator, but gave up after spending too much time trying to learn the Reed-Solomon error correction. To any readers, I recommend using Rob’s code, as it’s more user friendly, portable, prints labels of the proper size, and designed to run on Excel output. The perl script is posted below. It takes two parmeters, and prints SVG (an awesome format; you can paint the screen using plain text!) to standard out. If you save it as drawSVGbarcodes.pl and make it executable can run it like this:

    ./drawSVGbarcodes.pl DATA ENCODING >OUT.svg

    Where:
    DATA = the string you want to encode
    ENCODING = 128 or 39 or UPC

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    #!/usr/bin/perl
    use warnings;
    use strict;
    my $id = shift;
    my $encoding = shift; # 128, 39, and UPC are currently supported
     
    my $codeUPC = {
        START => '101',
        MID   => '01010',
        STOP   => '101',
        '0' => {'leftA' => '0001101', 'leftB' => '0100111', 'right' => '1110010'},
        '1' => {'leftA' => '0011001', 'leftB' => '0110011', 'right' => '1100110'},
        '2' => {'leftA' => '0010011', 'leftB' => '0011011', 'right' => '1101100'},
        '3' => {'leftA' => '0111101', 'leftB' => '0100001', 'right' => '1000010'},
        '4' => {'leftA' => '0100011', 'leftB' => '0011101', 'right' => '1011100'},
        '5' => {'leftA' => '0110001', 'leftB' => '0111001', 'right' => '1001110'},
        '6' => {'leftA' => '0101111', 'leftB' => '0000101', 'right' => '1010000'},
        '7' => {'leftA' => '0111011', 'leftB' => '0010001', 'right' => '1000100'},
        '8' => {'leftA' => '0110111', 'leftB' => '0001001', 'right' => '1001000'},
        '9' => {'leftA' => '0001011', 'leftB' => '0010111', 'right' => '1110100'}
    };
     
     
    my %code128 = (
     
        START_A => '11010000100',
        START_B => '11010010000',
        START_C => '11010011100',
        STOP    => '1100011101011',
         
        '0' => '11011001100', 1    => '11001101100', 2    => '11001100110', 3  => '10010011000', 4  => '10010001100',
        5   => '10001001100', 6    => '10011001000', 7    => '10011000100', 8  => '10001100100', 9  => '11001001000',
        10  => '11001000100', 11  => '11000100100', 12    => '10110011100', 13 => '10011011100', 14 => '10011001110',
        15  => '10111001100', 16  => '10011101100', 17    => '10011100110', 18 => '11001110010', 19 => '11001011100',
        20  => '11001001110', 21  => '11011100100', 22    => '11001110100', 23 => '11101101110', 24 => '11101001100',
        25  => '11100101100', 26  => '11100100110', 27    => '11101100100', 28 => '11100110100', 29 => '11100110010',
        30  => '11011011000', 31  => '11011000110', 32    => '11000110110', 33 => '10100011000', 34 => '10001011000',
        35  => '10001000110', 36  => '10110001000', 37    => '10001101000', 38 => '10001100010', 39 => '11010001000',
        40  => '11000101000', 41  => '11000100010', 42    => '10110111000', 43 => '10110001110', 44 => '10001101110',
        45  => '10111011000', 46  => '10111000110', 47    => '10001110110', 48 => '11101110110', 49 => '11010001110',
        50  => '11000101110', 51  => '11011101000', 52    => '11011100010', 53 => '11011101110', 54 => '11101011000',
        55  => '11101000110', 56  => '11100010110', 57    => '11101101000', 58 => '11101100010', 59 => '11100011010',
        60  => '11101111010', 61  => '11001000010', 62    => '11110001010', 63 => '10100110000', 64 => '10100001100',
        65  => '10010110000', 66  => '10010000110', 67    => '10000101100', 68 => '10000100110', 69 => '10110010000',
        70  => '10110000100', 71  => '10011010000', 72    => '10011000010', 73 => '10000110100', 74 => '10000110010',
        75  => '11000010010', 76  => '11001010000', 77    => '11110111010', 78 => '11000010100', 79 => '10001111010',
        80  => '10100111100', 81  => '10010111100', 82    => '10010011110', 83 => '10111100100', 84 => '10011110100',
        85  => '10011110010', 86  => '11110100100', 87    => '11110010100', 88 => '11110010010', 89 => '11011011110',
        90  => '11011110110', 91  => '11110110110', 92    => '10101111000', 93 => '10100011110', 94 => '10001011110',
        95  => '10111101000', 96  => '10111100010', 97    => '11110101000', 98 => '11110100010', 99 => '10111011110',
        100 => '10111101110', 101 => '11101011110', 102   => '11110101110'
     
    );
     
     
     
     
    my %code39 = (
     
        PAD   => '0',             ## required between characters
        START => '100101101101'##
        STOP  => '100101101101'## START and STOP are encoded by the same pattern.
        '*'   => '100101101101'## "*" is used in printing.
         
        '0' => {CODE => '101001101101', NR => 0 }, 'M' => {CODE => '110110101001', NR => 22},
        '1' => {CODE => '110100101011', NR => 1 }, 'N' => {CODE => '101011010011', NR => 23},
        '2' => {CODE => '101100101011', NR => 2 }, 'O' => {CODE => '110101101001', NR => 24},
        '3' => {CODE => '110110010101', NR => 3 }, 'P' => {CODE => '101101101001', NR => 25},
        '4' => {CODE => '101001101011', NR => 4 }, 'Q' => {CODE => '101010110011', NR => 26},
        '5' => {CODE => '110100110101', NR => 5 }, 'R' => {CODE => '110101011001', NR => 27},
        '6' => {CODE => '101100110101', NR => 6 }, 'S' => {CODE => '101101011001', NR => 28},
        '7' => {CODE => '101001011011', NR => 7 }, 'T' => {CODE => '101011011001', NR => 29},
        '8' => {CODE => '110100101101', NR => 8 }, 'U' => {CODE => '110010101011', NR => 30},
        '9' => {CODE => '101100101101', NR => 9 }, 'V' => {CODE => '100110101011', NR => 31},
        'A' => {CODE => '110101001011', NR => 10}, 'W' => {CODE => '110011010101', NR => 32},
        'B' => {CODE => '101101001011', NR => 11}, 'X' => {CODE => '100101101011', NR => 33},
        'C' => {CODE => '110110100101', NR => 12}, 'Y' => {CODE => '110010110101', NR => 34},
        'D' => {CODE => '101011001011', NR => 13}, 'Z' => {CODE => '100110110101', NR => 35},
        'E' => {CODE => '110101100101', NR => 14}, '-' => {CODE => '100101011011', NR => 36},
        'F' => {CODE => '101101100101', NR => 15}, '.' => {CODE => '110010101101', NR => 37},
        'G' => {CODE => '101010011011', NR => 16}, ' ' => {CODE => '100110101101', NR => 38},
        'H' => {CODE => '110101001101', NR => 17}, '$' => {CODE => '100100100101', NR => 39},
        'I' => {CODE => '101101001101', NR => 18}, '/' => {CODE => '100100101001', NR => 40},
        'J' => {CODE => '101011001101', NR => 19}, '+' => {CODE => '100101001001', NR => 41},
        'K' => {CODE => '110101010011', NR => 20}, '%' => {CODE => '101001001001', NR => 42}
     
    );
     
    my $binary;
    if($encoding eq 'UPC'){
        if(($id=~m/[^0-9]/) || (length $id != 11)){
            die "ERROR: cannot convert to UPC!\n"
        }
        $binary = &string2UPC($id);
    }
    elsif($encoding == 39){
        $id = uc $id;
        $id =~ s/[^A-Z0-9\-\$%.\/+]/\$/g;
        $binary = &string2code39($id);
    }
    else{
        $binary = &string2code128($id);
    }
    my $image = &generate_svg("$binary", "$id");
    print "$image";
     
     
     
     
     
    sub string2UPC {
        my $digits = shift;
        my $pattern = $codeUPC->{START};
        print STDERR "***\t$pattern\n";
        my @atoms = split //,$digits;
        my $i = 0;
        my $check_digit;
        my $sum_odd = 0;
        my $sum_even = 0;
        foreach(@atoms){
            my $atom = "$_";
            print STDERR "$atom\n";
            if($i<6){
                $pattern = "$pattern".$codeUPC->{"$atom"}->{'leftA'};
            }
            elsif($i==6){
                $pattern = "$pattern".$codeUPC->{MID}.$codeUPC->{$atom}->{'right'};
     
            }
            else{
                $pattern = "$pattern".$codeUPC->{$atom}->{'right'};
     
            }
            ++$i;
            my $is_odd = &even_or_odd($i);
            if($is_odd==1){
                $sum_odd = $sum_odd + $atom;
            }
            elsif($is_odd==0){
                $sum_even = $sum_even + $atom;
            }
        }
        $check_digit = 0;
        my $result_modulo = (($sum_odd * 3) + $sum_even) % 10;
        if($result_modulo>0){
            $check_digit = 10 - $result_modulo;
        }
        $pattern = "$pattern".$codeUPC->{"$check_digit"}->{'right'}.$codeUPC->{STOP};
        return $pattern;
    }
     
    sub string2code39 {
        my $string = shift;
        my $pattern = $code39{START};
        my @atoms = split //,$string;
        foreach(@atoms){
            my $atom = "$_";
            $pattern = "$pattern".'0'.$code39{$atom}{CODE};
        }
        $pattern = "$pattern".'0'.$code39{STOP};
        return $pattern;
          
    }
    sub string2code128{
        my $string = shift;
        my $pattern = $code128{START_B}; # support for other/mixed codes forthcoming
        my $checksum = 104;             # startB value
        my @atoms = split //,$string;
        my %B = (
        ' ' => '0', '!'=> 1,  '"' => 2,  '#' => 3,  '$' => 4,  '%' => 5,
        '&' => 6, '\'' => 7,  '(' => 8,  ')' => 9,  '*' => 10, '+' => 11,
        ',' => 12, '-' => 13, '.' => 14, '/' => 15, '0' => 16, '1' => 17,
        '2' => 18, '3' => 19, '4' => 20, '5' => 21, '6' => 22, '7' => 23,
        '8' => 24, '9' => 25, ':' => 26, ';' => 27, '<' => 28, '=' => 29,
        '>' => 30, '?' => 31, '@' => 32, 'A' => 33, 'B' => 34, 'C' => 35,
        'D' => 36, 'E' => 37, 'F' => 38, 'G' => 39, 'H' => 40, 'I' => 41,
        'J' => 42, 'K' => 43, 'L' => 44, 'M' => 45, 'N' => 46, 'O' => 47,
        'P' => 48, 'Q' => 49, 'R' => 50, 'S' => 51, 'T' => 52, 'U' => 53,
        'V' => 54, 'W' => 55, 'X' => 56, 'Y' => 57, 'Z' => 58, '[' => 59,
        '\\' => 60,']' => 61, '^' => 62, '_' => 63, '`' => 64, 'a' => 65,
        'b' => 66, 'c' => 67, 'd' => 68, 'e' => 69, 'f' => 70, 'g' => 71,
        'h' => 72, 'i' => 73, 'j' => 74, 'k' => 75, 'l' => 76, 'm' => 77,
        'n' => 78, 'o' => 79, 'p' => 80, 'q' => 81, 'r' => 82, 's' => 83,
        't' => 84, 'u' => 85, 'v' => 86, 'w' => 87, 'x' => 88, 'y' => 89,
        'z' => 90, '{' => 91, '|' => 92, '}' => 93, '~' => 94);
         
        my $i = 1;
        foreach(@atoms){
            my $atom = "$_";
            my $value = 4; # replace non-codeB-encoded characters with "$", for now.
            if(exists $B{$atom}){
                $value = $B{$atom};
            }
            $pattern = "$pattern".$code128{$value};
            $checksum = $checksum + ($i * $value);
            ++$i;
         
        }
         
        my $checkvalue = $checksum % 103;
        $pattern = "$pattern".$code128{$checkvalue};
        $pattern = "$pattern".$code128{STOP};
        return "$pattern";
    }
     
    sub even_or_odd {
        my $n = shift;
        my $is_odd = 1;
        if($n == 0){
            $is_odd = '0';
        }
        elsif($n % 2 == 0){
            $is_odd = '0';
        }
        else{
            $is_odd = '1';
        }
        return "$is_odd";
    }  
     
     
    sub generate_svg {
        my $pattern = shift;
        my $name    = shift;
        my $quiet_width = 10;
        my $scale = 2;
        my $base_width    =  (length $pattern) + (2 * $quiet_width);
        my $outline_width = $scale * ($base_width + 2);
        my $image_width   = $scale * ($base_width + 4);
        my $quiet_zone = '0' x $quiet_width;
        my $full_pattern = "$quiet_zone"."$pattern"."$quiet_zone";
        my @bars = split //, $full_pattern;
     
        my $svg =
        '<?xml version="1.0" standalone="no"?>'."\n".
        '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"'."\n".
        '<svg width="'."$image_width".'px" height="'."$image_width".'px"'."\n".
        '     xmlns="http://www.w3.org/2000/svg" version="1.1">'."\n".
        '  <desc>'."$name".'</desc>'."\n".
        ''."\n".
        '  <!-- Show outline of canvas using \'rect\' element -->'."\n".
        '  <rect x="'."$scale".'" y="'."$scale".'" width="'."$outline_width".'" height="'."$outline_width".'"'."\n".
        '        fill="none" stroke="blue" stroke-width="'."$scale".'" />'."\n".
        ''."\n"
        ;
     
        my $i = 0;
        my $x1 = ($scale * 2);
        my $y1 = ($scale * 2);
        my $x2 = ($scale * 2);
        my $y2 = ($scale * (2 + $base_width));
        foreach(@bars){
            my $bar = "$_";
            my $color = 'white';
            if($bar>0){
                $color = 'black';
            }
            my $svg_part =
            '  <g stroke="'."$color".'" >'."\n".
            '    <line x1="'."$x1".'" y1="'."$y1".'" x2="'."$x2".'" y2="'."$y2".'"'."\n".
            '            stroke-width="'."$scale".'"  />'."\n".
            '  </g>'."\n"
            ;
            $svg = "$svg"."$svg_part";
            ++$i;
            $x1 = $x1+($scale);
            $x2 = $x2+($scale);
        }
        $svg="$svg".'</svg>'."\n";
         
        return $svg;
    }
    • Thanks Chris, great addition!

      I used QR barcodes at Duke but I found they took longer to scan compared to the code128. Maybe we had a crappy scanner or printer. It might be a fun challenge at some point, I’ll let you know if I try to tackle it.

Comments are closed.