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

    #!/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".
    	'  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'."\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.