#/usr/bin/perl -w # GenerateBarcode.pl # by ntheory # # Generates barcodes and the CRC digit for CoinStar receipts. # # June 26th, 2003 - Started development. # June 30th, 2003 - After a few day haitus I resumed work and finished the code. # # Notes: Put the barcodes in a text file and redirect the file into STDIN. # You should only have the 12-digits of the barcode on each line and # nothing else. Alternatively you can enter them by hand. use Image::Magick; # Set up some constants that we'll need in the binary string generation phase. $StartEndSentinel = "101"; # This goes at the beginning and end. $CenterGuardPattern = "01010"; #This goes in the middle. $StartEndSentinelLength = length($StartEndSentinel); $CenterGuardPatternLength = length($CenterGuardPattern); # These are just defined for readability purposes. $OddParity = 0; $EvenParity = 1; $ManufacturerCodeLength = 5; $WholeLeftSideLength = $ManufacturerCodeLength + 1; $RequiredInputLength = 12; $RightSideLength = 5; $WholeRightSideLength = $RightSideLength + 1; $BarcodeHeight = 50; $SingleDigitLength = 7; # Get the parity map, the left hand coding table, and the right hand coding table. $ParityMap = GetParityMap(); $LeftHandCoding = GetLeftHandCodingTable(); $RightHandCoding = GetRightHandCodingTable(); $BarcodeCounter = 0; # We'll do this in a loop so you can generate many barcodes in a row. while (<>) { # Get the unprocessed data and remove the trail ing newl i ne . $Unprocessed = $_; chomp($Unprocessed); # Check to make sure that it makes sense. if ( LooksValid($Unprocessed) ) { # Now convert it to a string of binary digits using some EAN-13 magic. # (http://www.barcodeisland.com/ean13.phtml) $Processed = $StartEndSentinel; # Get the first digit (determines the parity of the manufacturer code). $FirstDigit = int( substr( $Unprocessed, 0, 1 ) ); # Encode the second digit (always odd parity). $Offset = 1; $CurrentDigit = int( substr( $Unprocessed, $Offset, 1 ) ); $Processed .= $LeftHandCoding[$CurrentDigit][$OddParity]; # Next encode the manufacturer code. for ( $Loop = 0 ; $Loop < $ManufacturerCodeLength ; $Loop++ ) { # Move to the next character . $Offset++; # Get the parity. $CurrentParity = substr( $ParityMap[$FirstDigit], $Loop, 1 ) eq "0" ? $OddParity : $EvenParity; $CurrentDigit = int( substr( $Unprocessed, $Offset, 1 ) ); $Processed .= $LeftHandCoding[$CurrentDigit][$CurrentParity]; } # Slap the center guard pattern in there. $Processed .= $CenterGuardPattern; # Encode the right hand side. for ( $Loop = 0 ; $Loop < $RightSideLength ; $Loop++ ) { # Move to the next character. $Offset++; $CurrentDigit = int( substr( $Unprocessed, $Offset, 1 ) ); $Processed .= $RightHandCoding[$CurrentDigit]; } # Finally encode the check digit and slap the end sentinel in there. We also tack # the check digit onto the unprocessed string so we can draw it onto the image below in a loop. $CheckDigit = CalculateCheckDigit($Unprocessed); $Unprocessed .= $CheckDigit; $Processed .= $RightHandCoding[$CheckDigit]; $Processed .= $StartEndSentinel; # Now generate the image $XOffset = 100; $YOffset = 100; $BarcodeLength = length($Processed); $BarcodeImage = new Image::Magick; $BarcodeGeometry = ( $BarcodeLength + $XOffset * 2 ) . "x" . ( $BarcodeHeight + $YOffset * 2 ); $BarcodeImage->set( size => $BarcodeGeometry ); $BarcodeImage->Read("gradient:white-white"); # Don't ask me why I always do this. #Draw the barcode. $YMin = $YOffset; $YMax = $YOffset + $BarcodeHeight; for ( $Loop = 0 ; $Loop < $BarcodeLength ; $Loop++ ) { $X = $Loop + $XOffset; $PointsString = "$X,$YMin,$X,$YMax"; $StrokeColor = substr( $Processed, $Loop, 1 ) eq "0" ? "White" : "Black"; $BarcodeImage->Draw( primitive => "Line", points => "$PointsString", stoke => "$StrokeColor" ); } # Add in the digits below the barcode. # First clear out some space for the left digits. $BoxUpperLeftX = $StartEndSentinelLength + $XOffset; $BoxUpperLeftY = $BarcodeHeight - 10 + $YOffset; $BoxLowerRightX = $BoxUpperLeftX + $SingleDigitLength * $WholeLeftSideLength; $BoxLowerRightY = $BarcodeHeight + $YOffset; $PointsString = "$BoxUpperLeftX,$BoxUpperLeftY,$BoxLowerRightX,$BoxLowerRightY"; $BarcodeImage->Draw( primitive => "Rectangle", points => "$PointsString", fill => "White", stroke => "White" ); # Draw the left side digits $Y = $YOffset + $BarcodeHeight; for ( $Loop = 1 ; $Loop < ( $WholeLeftSideLength + 1 ) ; $Loop++ ) { $X = $BoxUpperLeftX + 1 + ( ( $Loop - 1 ) * $SingleDigitLength ); $Character = substr( $Unprocessed, $Loop, 1 ); $BarcodeImage->Annotate( text => $Character, pointsize->11, antialias => "true", x => $X, y => $Y, fill => "Black" ); } # Then clear out some space for the right digits. $BoxUpperLeftX = $BoxLowerRightX + $CenterGuardPatternLength; $BoxLowerRightX = $BoxUpperLeftX + ( $WholeRightSideLength * $SingleDigitLength ) - 1; $PointsString = "$BoxUpperLeftX,$BoxUpperLeftY,$BoxLowerRightX,$BoxLowerRightY"; $BarcodeImage->Draw( primitive => "Rectangle", points => "$PointsString", fill => "White", stroke => "White" ); # Draw the right side digits. for ( $Loop = 1 ; $Loop < ( $WholeRightSideLength + 1 ) ; $Loop++ ) { $X = $BoxUpperLeftX + ( ( $Loop - 1 ) * $SingleDigitlength ); $Character = substr( $Unprocessed, $Loop + $WholeRightSideLength, 1 ); $BarcodeImage->Annotate( text => $Character, pointsize => 11, antialias => "true", x => $X, y => $Y, fill => "Black" ); } # Draw the first very first digit. $X = $XOffset - 12; $Character = substr( $Unprocessed, 0, 1 ); $BarcodeImage->Annotate( text => $Character, pointsize => 11, antialias => "true", x => $X, y => $Y, fill => "Black" ); $BarcodeCounterString = sprintf( "%03d", $BarcodeCounter ); $BarcodeImageName = "CoinStar-$BarcodeCounterString.png"; $BarcodeImage->Write($BarcodeImageName); $BarcodeCounter++; # Let the user know that something happened. $TransactionID = substr( $Unprocessed, 3, 4 ); $Amount = sprintf( "\$%3d.%02d", int( substr( $Unprocessed, 7, 3 ) ), int( substr( $Unprocessed, 10, 2 ) ) ); print "Barcode generation for transaction $Transaction ID ($Amount) was successful.\n"; print "The image was stored as $BarcodeImageName. The CoinStar check digit was $CheckDigit.\n"; } } sub LooksValid { $Data = $_[0]; $ReturnValue = 0; if ( length($Data) != $RequiredInputlength ) { print "Wrong number of characters. $RequiredlnputLength characters are needed to generate a barcode.\n"; } elsif ( $Data =~ m/[^0-9]/ ) { print "There was a non-numeric character in your data. Only numeric data is accepted.\n"; } else { $ReturnValue = 1; } return $ReturnValue; } sub CalculateCheckDigit { $Data = $_[0]; # CoinStar really exhausted their technician's with this one. Below the code # may look very similar to a typical EAN-13 checksum calculation but it has # a surprise ending. $Sum = 0; # Do the weighted sum. for ( $Loop = 0 ; $Loop < $RequiredlnputLength ; $Loop++ ) { $CurrentDigit = int( substr( $Data, $Loop, 1 ) ); # Even digits are added to the sum normally while odd digits are multiplied # by three before they're added. if ( $Loop % 2 == 0 ) { $Sum += $CurrentDigit; } else { $Sum += ( $CurrentDigit * 3 ); } } # Mod the sum by 10. $Sum = $Sum % 10; # Normally here we'd subtract the sum from 10 but CoinStar had to be # different. CoinStar has a Spinal Tap fetish ("We've got 11"). $CheckDigit = 11 - $Sum; # Make sure the check digit is less than 10. $CheckDigit = $CheckDigit % 10; return $CheckDigit; } sub GetParityMap { # This table tells us how to code the manufacturer's code. my $ParityMap; $ParityMap[0] = "00000"; $ParityMap[1] = "0E0EE"; $ParityMap[2] = "0EE0E"; $ParityMap[3] = "0EEE0"; $ParityMap[4] = "E00EE"; $ParityMap[5] = "EE00E"; $ParityMap[6] = "EEE00"; $ParityMap[7] = "E0E0E"; $ParityMap[8] = "E0EE0"; $ParityMap[9] = "EE0E0"; return $ParityMap; } sub GetLeftHandCodingTable { # This table gives us the binary representation of the left hand digits. my $LeftHandCodingTable; $LeftHandCoding[0][$OddParity] = "0001101"; $LeftHandCoding[1][$OddParity] = "0011001"; $LeftHandCoding[2][$OddParity] = "0010011"; $LeftHandCoding[3][$OddParity] = "0111101"; $LeftHandCoding[4][$OddParity] = "0100011"; $LeftHandCoding[5][$OddParity] = "0110001"; $LeftHandCoding[6][$OddParity] = "0101111"; $LeftHandCoding[7][$OddParity] = "0111011"; $LeftHandCoding[8][$OddParity] = "0110111"; $LeftHandCoding[9][$OddParity] = "0001011"; $LeftHandCoding[0][$EvenParity] = "0100111"; $LeftHandCoding[1][$EvenParity] = "0110011"; $LeftHandCoding[2][$EvenParity] = "0011011"; $LeftHandCoding[3][$EvenParity] = "0100001"; $LeftHandCoding[4][$EvenParity] = "0011101"; $LeftHandCoding[5][$EvenParity] = "0111001"; $LeftHandCoding[6][$EvenParity] = "0000101"; $LeftHandCoding[7][$EvenParity] = "0010001"; $LeftHandCoding[8][$EvenParity] = "0001001"; $LeftHandCoding[9][$EvenParity] = "0010111"; return $LeftHandCoding; } sub GetRightHandCodingTable { # This table gives us the binary representation of the right hand digits. my $RightHandCoding; $RightHandCoding[0] = "1110010"; $RightHandCoding[1] = "1100110"; $RightHandCoding[2] = "1101100"; $RightHandCoding[3] = "1000010"; $RightHandCoding[4] = "1011100"; $RightHandCoding[5] = "1001110"; $RightHandCoding[6] = "1010000"; $RightHandCoding[7] = "1000100"; $RightHandCoding[8] = "1001000"; $RightHandCoding[9] = "1110100"; return $RightHandCoding; }