* @author Rich Sage * @copyright 2009 Y.Swetake * @copyright 2009 R.Sage * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 * @version SVN: $Id$ * @link http://code.google.com/p/pearqrcode/ */ /** * Exception handling */ require_once 'QRCode/Exception.php'; /** * QRCode * * @category Images * @package Image_QRCode * @author Y.Swetake * @author Rich Sage * @license http://www.gnu.org/licenses/lgpl-2.1.txt GNU LGPL * @version Release: 0.1 * @link http://code.google.com/p/pearqrcode/ */ class Image_QRCode { /** * path to the data directory * * @var string */ protected $path; /** * path to the image directory * * @var string */ protected $image_path; /** * upper limit for version * * @var integer */ protected $version_ul; /** * string/data to encode * * @var string */ protected $data_string; /** * output type * - return: return raw PHP GD image object * - display: output to browser * * @var string */ protected $output_type; /** * image_type * - png/jpeg * * @var string */ protected $image_type; /** * base_image * * @var gd_image */ protected $base_image; /** * output image * * @var gd_image */ protected $output_image; /** * total number of data bits * * @var integer */ protected $total_data_bits; /** * Error correction indicator * * @var string */ protected $error_correct; /** * lookup table for alphanumeric characters * Note: the '$' index is in single quotes as otherwise * PHP tries to parse it as a variable (see PEAR bug #17321) * * @var array */ protected $alphanumeric_hash = array( "0"=>0,"1"=>1,"2"=>2,"3"=>3,"4"=>4, "5"=>5,"6"=>6,"7"=>7,"8"=>8,"9"=>9,"A"=>10,"B"=>11,"C"=>12,"D"=>13,"E"=>14, "F"=>15,"G"=>16,"H"=>17,"I"=>18,"J"=>19,"K"=>20,"L"=>21,"M"=>22,"N"=>23, "O"=>24,"P"=>25,"Q"=>26,"R"=>27,"S"=>28,"T"=>29,"U"=>30,"V"=>31, "W"=>32,"X"=>33,"Y"=>34,"Z"=>35," "=>36,'$'=>37,"%"=>38,"*"=>39, "+"=>40,"-"=>41,"."=>42,"/"=>43,":"=>44 ); /** * Maximum data bits lookup * * @var array */ protected $max_data_bits_array = array( 0,128,224,352,512,688,864,992,1232,1456,1728, 2032,2320,2672,2920,3320,3624,4056,4504,5016,5352, 5712,6256,6880,7312,8000,8496,9024,9544,10136,10984, 11640,12328,13048,13800,14496,15312,15936,16816,17728,18672, 152,272,440,640,864,1088,1248,1552,1856,2192, 2592,2960,3424,3688,4184,4712,5176,5768,6360,6888, 7456,8048,8752,9392,10208,10960,11744,12248,13048,13880, 14744,15640,16568,17528,18448,19472,20528,21616,22496,23648, 72,128,208,288,368,480,528,688,800,976, 1120,1264,1440,1576,1784,2024,2264,2504,2728,3080, 3248,3536,3712,4112,4304,4768,5024,5288,5608,5960, 6344,6760,7208,7688,7888,8432,8768,9136,9776,10208, 104,176,272,384,496,608,704,880,1056,1232, 1440,1648,1952,2088,2360,2600,2936,3176,3560,3880, 4096,4544,4912,5312,5744,6032,6464,6968,7288,7880, 8264,8920,9368,9848,10288,10832,11408,12016,12656,13328 ); /** * Maximum number of codewords, dependant on barcode version * * @var array */ protected $max_codewords_array = array( 0,26,44,70,100,134,172,196,242, 292,346,404,466,532,581,655,733,815,901,991,1085,1156, 1258,1364,1474,1588,1706,1828,1921,2051,2185,2323,2465, 2611,2761,2876,3034,3196,3362,3532,3706 ); /** * Formatting data for the final barcode * * @var array */ protected $format_array = array( "101010000010010","101000100100101", "101111001111100","101101101001011","100010111111001","100000011001110", "100111110010111","100101010100000","111011111000100","111001011110011", "111110110101010","111100010011101","110011000101111","110001100011000", "110110001000001","110100101110110","001011010001001","001001110111110", "001110011100111","001100111010000","000011101100010","000001001010101", "000110100001100","000100000111011","011010101011111","011000001101000", "011111100110001","011101000000110","010010010110100","010000110000011", "010111011011010","010101111101101" ); /** * Maximum number of data bits * * @var integer */ protected $max_data_bits; /** * Size of the module * * @var integer */ protected $module_size; /** * Data values * * @var array */ protected $data_value; /** * Incremental counter, pointer into the data arrays * * @var integer */ protected $data_counter; /** * Data bits array * * @var array */ protected $data_bits; /** * Codeword pointer, incremental * * @var integer */ protected $codeword_num_counter_value; /** * Codeword details * * @var array */ protected $codeword_num_plus; /** * RS-ECC codewords * * @var string */ protected $rs_ecc_codewords; /** * Codewords calculated prior to matrix generation * * @var array */ protected $codewords; /** * Maximum number of data codewords * * @var integer */ protected $max_data_codewords; /** * Final matrix details for plotting barcode * * @var array */ protected $matrix_content; /** * Matrix X array * * @var array */ protected $matrix_x_array; /** * Matrix Y array * * @var array */ protected $matrix_y_array; /** * Masking array - used in final matrix generation * * @var array */ protected $mask_array; /** * RS Block order array * * @var array */ protected $rs_block_order; /** * RS Calculation table array * * @var array */ protected $rs_cal_table_array; /** * Byte number counter * * @var integer */ protected $byte_num; /** * Class constructor * * @param array $options An array of options for the class */ public function __construct($options = array()) { $this->path = "@data_dir@" . DIRECTORY_SEPARATOR . "Image_QRCode" . DIRECTORY_SEPARATOR . "data"; if ("@data_dir@" == "@" . "data_dir@") { // development path $this->path = dirname(__FILE__) . "/../data"; } $this->image_path = "@data_dir@" . DIRECTORY_SEPARATOR . "Image_QRCode" . DIRECTORY_SEPARATOR . "image"; if ("@data_dir@" == "@" . "data_dir@") { // development path $this->image_path = dirname(__FILE__) . "/../image"; } $this->version_ul = 40; $this->data_string = ""; $this->output_type = "display"; $this->image_type = "png"; $this->total_data_bits = 0; $this->error_correct = "M"; $this->module_size = 0; $this->matrix_content = array(); // handle options if (array_key_exists("version", $options)) { $this->setVersion($options["version"]); } if (array_key_exists("path", $options)) { $this->path = $options["path"]; } if (array_key_exists("image_path", $options)) { $this->image_path = $options["image_path"]; } } /** * Sets the data to encode * * @param string $str the data to encode into the barcode * * @return void * @throws Image_QRCode_Exception */ protected function setData($str) { $str = trim($str); if (strlen($str) == 0) { throw new Image_QRCode_Exception("Data cannot be empty"); } $this->data_string = $str; $this->data_length = strlen($str); } /** * Sets the error correction level. * One of: * L: 7% error level * M: 15% error level * Q: 25% error level * H: 30% error level * * @param char $e the error level to use * * @return void * @throws Image_QRCode_Exception */ protected function setErrorCorrect($e) { $e = strtoupper(trim($e)); if (!in_array($e, array("L", "M", "Q", "H"))) { throw new Image_QRCode_Exception("Error correction level not supported"); } $this->error_correct = $e; } /** * Sets the default module size * Defaults are PNG: 4, JPEG: 8 * * @param integer $size the module size to use * * @return void * @throws Image_QRCode_Exception */ protected function setModuleSize($size) { $size = intval($size); if ($size < 1) { throw new Image_QRCode_Exception("Module size is invalid"); } $this->module_size = $size; } /** * Sets the version number to use * (between 1 and 40) * * Version 1 is 21*21 matrix * and 4 modules increases whenever 1 version increases. * So version 40 is 177*177 matrix. * * @param integer $v the version number to use for this barcode * * @return void * @throws Image_QRCode_Exception */ protected function setVersion($v) { $v = intval($v); if ($v < 1 || $v > $this->version_ul) { throw new Image_QRCode_Exception("Version is invalid"); } $this->version = $v; } /** * Defines the return image type * (from 'jpeg', or 'png') * * Default is PNG format * * @param string $t the image type to use * * @return void * @throws Image_QRCode_Exception */ protected function setImageType($t) { $t = strtolower($t); if (!in_array($t, array("jpeg", "png"))) { throw new Image_QRCode_Exception("Image format not supported"); } $this->image_type = $t; } /** * Sets what to do once the code has been generated * ('display' or 'return') * * Default is to display the image, complete with headers. * * @param string $t what to do with the image once it's been created * * @return void * @throws Image_QRCode_Exception */ protected function setOutputType($t) { $t = strtolower($t); if (!in_array($t, array("display", "return"))) { throw new Image_QRCode_Exception("Output type not supported"); } $this->output_type = $t; } /** * Sets the data to append to the code (experimental) * * @param array $data an array of elements (n, m, parity, originaldata) * * @return void */ protected function setStructureAppend($data = array()) { if (!array_key_exists("n", $data) && !array_key_exists("m", $data) && !array_key_exists("parity", $data) && !array_key_exists("originaldata", $data) ) { throw new Image_QRCode_Exception("Appended structure data is not valid"); } $this->structureappend_n = $data["n"]; $this->structureappend_m = $data["m"]; $this->structureappend_parity = $data["parity"]; $this->structureappend_originaldata = $data["originaldata"]; } /** * Performs all necessary calculations and returns/outputs an image * as defined by the configuration * * @param string $data the string/data to encode into the barcode * @param array $options an array of options for the barcode * @param array $append additional data to append (experimental) * * @return resource * @throws Image_QRCode_Exception */ public function makeCode($data = "", $options = array(), $append = array()) { // apply any passed options if (array_key_exists("image_type", $options)) { $this->setImageType($options["image_type"]); } if (array_key_exists("output_type", $options)) { $this->setOutputType($options["output_type"]); } if (array_key_exists("error_correct", $options)) { $this->setErrorCorrect($options["error_correct"]); } if (array_key_exists("module_size", $options)) { $this->setModuleSize($options["module_size"]); } // set our data value $this->setData($data); // if we have an extra set of data to append, set this now if (!empty($append)) { $this->setStructureAppend($append); } // some checks to start with if ($this->module_size <= 0) { $this->module_size = 4; } $this->data_counter = 0; // apply parity and m/n structure append data $this->applyStructureAppend(); $this->data_bits[$this->data_counter] = 4; // Determine the encoding mode, based on the input data $this->determineEncoding(); if (@$this->data_bits[$this->data_counter] > 0) { $this->data_counter++; } $i = 0; $this->total_data_bits = 0; while ($i < $this->data_counter) { $this->total_data_bits += $this->data_bits[$i]; $i++; } $ecc_character_hash = array( "L"=>"1", "l"=>"1", "M"=>"0", "m"=>"0", "Q"=>"3", "q"=>"3", "H"=>"2", "h"=>"2" ); $this->ec = @$ecc_character_hash[$this->error_correct]; if (!$this->ec) { $this->ec = 0; } // Calculate the version of the code we're generating $this->checkVersion(); $this->total_data_bits += $this->codeword_num_plus[$this->version]; $this->data_bits[$this->codeword_num_counter_value] += $this->codeword_num_plus[$this->version]; $max_codewords = $this->max_codewords_array[$this->version]; $max_modules_1side = 17 + ($this->version << 2); $matrix_remain_bit = array( 0,0,7,7,7,7,7,0,0,0,0,0,0,0,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,3,3,3,3,3,3,3,0,0,0,0,0,0 ); /* Create our base clean matrix data, ready for our own data */ $this->emptyMatrix($max_modules_1side); /* ECC -this is the heart of the codeword calculations */ $format_info = $this->performECCOperation( $matrix_remain_bit, $max_codewords ); /* Attach calculated codeword data to the matrix */ $this->attachCodewordData($max_codewords, $matrix_remain_bit); /* Mask selection */ $mask_number = $this->maskSelection($max_modules_1side); $mask_content = 1 << $mask_number; /* Calculate our format information */ $this->calculateFormatInformation($format_info, $mask_number); /* Create base image based on calculated size */ $mib = $this->createBaseImage($max_modules_1side); // Add actual matrix data to the base image $this->addMatrixToImage($max_modules_1side, $mask_content); // create output image, saved in $this->output_image $this->createOutputImage($mib); // what are we doing with the created output image? if ($this->output_type == "return") { return $this->output_image; } else { switch ($this->image_type) { case "jpeg": header("Content-type: image/jpeg"); imagejpeg($this->output_image); break; case "png": header("Content-type: image/png"); imagepng($this->output_image); break; } } } /** * Performs version checking/calculation * * @return void * @throws Image_QRCode_Exception */ protected function checkVersion() { if (!is_numeric($this->version)) { $this->version = 0; } if (!$this->version) { $i = 1 + 40 * $this->ec; $j = $i + 39; $this->version = 1; while ($i <= $j) { $cw = $this->codeword_num_plus[$this->version]; $max = $this->max_data_bits_array[$i]; $tdb = $this->total_data_bits + $cw; if ($max >= $tdb) { $this->max_data_bits = $this->max_data_bits_array[$i]; break; } $i++; $this->version++; } } else { $b = $this->max_data_bits_array[$this->version + 40 * $this->ec]; $this->max_data_bits = $b; } if ($this->version > $this->version_ul) { throw new Image_QRCode_Exception("Version number is too large"); } } /** * Determines the encoding needed for the data provided * * @return void */ protected function determineEncoding() { if (preg_match("/[^0-9]/", $this->data_string) != 0) { $expr = "/[^0-9A-Z \$\*\%\+\.\/\:\-]/"; if (preg_match($expr, $this->data_string) != 0) { /* 8-bit byte mode */ $this->codeword_num_plus = array( 0,0,0,0,0,0,0,0,0,0, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8 ); $this->data_value[$this->data_counter] = 4; $this->data_counter++; $this->data_value[$this->data_counter] = $this->data_length; $this->data_bits[$this->data_counter] = 8; /* version 1-9 */ $this->codeword_num_counter_value = $this->data_counter; $this->data_counter++; $i = 0; while ($i < $this->data_length) { $this->data_value[$this->data_counter] = ord( substr( $this->data_string, $i, 1 ) ); $this->data_bits[$this->data_counter] = 8; $this->data_counter++; $i++; } } else { /* Alphanumeric mode */ $this->codeword_num_plus = array( 0,0,0,0,0,0,0,0,0,0, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 4,4,4,4,4,4,4,4,4,4,4,4,4,4 ); $this->data_value[$this->data_counter] = 2; $this->data_counter++; $this->data_value[$this->data_counter] = $this->data_length; $this->data_bits[$this->data_counter] = 9; /* version 1-9 */ $this->codeword_num_counter_value = $this->data_counter; $i = 0; $this->data_counter++; while ($i < $this->data_length) { if (($i % 2) == 0) { $c = substr($this->data_string, $i, 1); $h = $this->alphanumeric_hash[$c]; $this->data_value[$this->data_counter] = $h; $this->data_bits[$this->data_counter] = 6; } else { $c = substr($this->data_string, $i, 1); $h = $this->alphanumeric_hash[$c]; $this->data_value[$this->data_counter] = $this->data_value[$this->data_counter] * 45 + $h; $this->data_bits[$this->data_counter] = 11; $this->data_counter++; } $i++; } } } else { /* Numeric mode */ $this->codeword_num_plus = array( 0,0,0,0,0,0,0,0,0,0, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 4,4,4,4,4,4,4,4,4,4,4,4,4,4 ); $this->data_value[$this->data_counter] = 1; $this->data_counter++; $this->data_value[$this->data_counter] = $this->data_length; $this->data_bits[$this->data_counter] = 10; /* #version 1-9 */ $this->codeword_num_counter_value = $this->data_counter; $i = 0; $this->data_counter++; while ($i < $this->data_length) { if (($i % 3) == 0) { $this->data_value[$this->data_counter] = substr($this->data_string, $i, 1); $this->data_bits[$this->data_counter] = 4; } else { $c = substr($this->data_string, $i, 1); $this->data_value[$this->data_counter] = $this->data_value[$this->data_counter] * 10 + $c; if (($i % 3) == 1) { $this->data_bits[$this->data_counter] = 7; } else { $this->data_bits[$this->data_counter] = 10; $this->data_counter++; } } $i++; } } } /** * Performs the error correction and code generation operations * * @param array $matrix_remain_bit array of remaining bits for data matrix * @param integer $max_codewords number of maximum codewords * * @return array */ protected function performECCOperation($matrix_remain_bit, $max_codewords) { // get our ECC data in from the relevant file $format_info = $this->readECCData($matrix_remain_bit, $max_codewords); $codewordsCounter = $this->divideBy8Bit(); $codewordsCounter = $this->setPaddingCharacter($codewordsCounter); // Do the actual RS-ECC magic $this->doRSCalculations(); return $format_info; // used in main makeCode() } /** * Read in correct baseline data from ECC/RS files * * @param array $matrix_remain_bit array of remaining bits for data matrix * @param integer $max_codewords number of maximum codewords * * @return array */ protected function readECCData($matrix_remain_bit, $max_codewords) { $this->byte_num = $matrix_remain_bit[$this->version] + ($max_codewords << 3); $filename = $this->path . "/qrv" . $this->version . "_" . $this->ec . ".dat"; if (!file_exists($filename)) { throw new Image_QRCode_Exception("Can't open ECC data file"); } $fp1 = fopen($filename, "rb"); $matx = fread($fp1, $this->byte_num); $maty = fread($fp1, $this->byte_num); $masks = fread($fp1, $this->byte_num); $fi_x = fread($fp1, 15); $fi_y = fread($fp1, 15); $this->rs_ecc_codewords = ord(fread($fp1, 1)); $rso = fread($fp1, 128); fclose($fp1); $this->matrix_x_array = unpack("C*", $matx); $this->matrix_y_array = unpack("C*", $maty); $this->mask_array = unpack("C*", $masks); $this->rs_block_order = unpack("C*", $rso); $format_information_x2 = unpack("C*", $fi_x); $format_information_y2 = unpack("C*", $fi_y); $format_information_x1 = array(0,1,2,3,4,5,7,8,8,8,8,8,8,8,8); $format_information_y1 = array(8,8,8,8,8,8,8,8,7,5,4,3,2,1,0); $format_info = array( "x1" => $format_information_x1, "x2" => $format_information_x2, "y1" => $format_information_y1, "y2" => $format_information_y2 ); $this->max_data_codewords = ($this->max_data_bits >> 3); $filename = $this->path . "/rsc" . $this->rs_ecc_codewords . ".dat"; if (!file_exists($filename)) { throw new Image_QRCode_Exception("Can't open rsc data file"); } $fp0 = fopen($filename, "rb"); $i = 0; while ($i < 256) { $this->rs_cal_table_array[$i] = fread($fp0, $this->rs_ecc_codewords); $i++; } fclose($fp0); /* Set terminator for data */ if ($this->total_data_bits <= $this->max_data_bits-4) { $this->data_value[$this->data_counter] = 0; $this->data_bits[$this->data_counter] = 4; } else { if ($this->total_data_bits < $this->max_data_bits) { $this->data_value[$this->data_counter] = 0; $this->data_bits[$this->data_counter] = $this->max_data_bits-$this->total_data_bits; } else { if ($this->total_data_bits > $this->max_data_bits) { throw new Image_QRCode_Exception("Overflow exception"); } } } return $format_info; } /** * Gets data into 8-bit format * * @return integer */ protected function divideBy8Bit() { $i = 0; $codewords_counter = 0; $this->codewords[0] = 0; $remaining_bits = 8; while ($i <= $this->data_counter) { $buffer = @$this->data_value[$i]; $buffer_bits = @$this->data_bits[$i]; $flag = 1; while ($flag) { if ($remaining_bits > $buffer_bits) { $this->codewords[$codewords_counter] = ((@$this->codewords[$codewords_counter] << $buffer_bits) | $buffer); $remaining_bits -= $buffer_bits; $flag = 0; } else { $buffer_bits -= $remaining_bits; $this->codewords[$codewords_counter] = (($this->codewords[$codewords_counter] << $remaining_bits) | ($buffer >> $buffer_bits)); if ($buffer_bits == 0) { $flag = 0; } else { $buffer = ($buffer & ((1 << $buffer_bits)-1)); $flag = 1; } $codewords_counter++; if ($codewords_counter < $this->max_data_codewords-1) { $this->codewords[$codewords_counter] = 0; } $remaining_bits = 8; } } $i++; } if ($remaining_bits != 8) { $this->codewords[$codewords_counter] = $this->codewords[$codewords_counter] << $remaining_bits; } else { $codewords_counter--; } return $codewords_counter; } /** * Sets the padding character to pad out data * * @param integer $codewordsCounter counter for current no. of codewords * * @return integer */ protected function setPaddingCharacter($codewordsCounter) { if ($codewordsCounter < $this->max_data_codewords-1) { $flag = 1; while ($codewordsCounter < $this->max_data_codewords-1) { $codewordsCounter++; if ($flag == 1) { $this->codewords[$codewordsCounter] = 236; } else { $this->codewords[$codewordsCounter] = 17; } $flag = $flag * -1; } } return $codewordsCounter; } /** * Calculates the format information for the barcode * * @param array $format_info format information from data files * @param integer $mask_number mask integer to use * * @return void */ protected function calculateFormatInformation($format_info, $mask_number) { $format_value = (($this->ec << 3) | $mask_number); $i = 0; while ($i < 15) { $content = substr($this->format_array[$format_value], $i, 1); $x = $format_info["x1"][$i]; $y = $format_info["y1"][$i]; $this->matrix_content[$x][$y] = $content * 255; $x = $format_info["x2"][$i+1]; $y = $format_info["y2"][$i+1]; $this->matrix_content[$x][$y] = $content * 255; $i++; } } /** * Perform the actual RS calculations on the data * * @return void */ protected function doRSCalculations() { // Preparation $i = 0; $j = 0; $rs_block_number = 0; $rs_temp[0] = ""; while ($i < $this->max_data_codewords) { $rs_temp[$rs_block_number] .= chr($this->codewords[$i]); $j++; $v = $this->rs_block_order[$rs_block_number+1] - $this->rs_ecc_codewords; if ($j >= $v) { $j = 0; $rs_block_number++; $rs_temp[$rs_block_number] = ""; } $i++; } // RS-ECC main calculation $rs_block_number = 0; $this->rs_block_order_num = count($this->rs_block_order); while ($rs_block_number < $this->rs_block_order_num) { $rs_codewords = $this->rs_block_order[$rs_block_number+1]; $rs_data_codewords = $rs_codewords-$this->rs_ecc_codewords; $rstemp = $rs_temp[$rs_block_number] . str_repeat(chr(0), $this->rs_ecc_codewords); $padding_data = str_repeat(chr(0), $rs_data_codewords); $j = $rs_data_codewords; while ($j > 0) { $first = ord(substr($rstemp, 0, 1)); if ($first) { $left_chr = substr($rstemp, 1); $cal = $this->rs_cal_table_array[$first] . $padding_data; $rstemp = $left_chr ^ $cal; } else { $rstemp=substr($rstemp, 1); } $j--; } $this->codewords = array_merge( $this->codewords, unpack("C*", $rstemp) ); $rs_block_number++; } } /** * Creates the final GD output image block * ready for sending wherever the user desires * * @param integer $mib destination image width/height * * @return void */ protected function createOutputImage($mib) { imagecopyresized( $this->output_image, $this->base_image, 0, 0, 0, 0, $this->image_size, $this->image_size, $mib, $mib ); } /** * Adds m/n and parity data for structure append (if supplied) * * @return void */ protected function applyStructureAppend() { if ($this->structureappend_n > 1 && $this->structureappend_n <= 16 && $this->structureappend_m > 0 && $this->structureappend_m <= 16 ) { $this->data_value[0] = 3; $this->data_bits[0] = 4; $this->data_value[1] = $this->structureappend_m - 1; $this->data_bits[1] = 4; $this->data_value[2] = $this->structureappend_n - 1; $this->data_bits[2] = 4; $originaldata_length = strlen($this->structureappend_originaldata); if ($originaldata_length > 1) { $this->structureappend_parity = 0; $i = 0; while ($i < $originaldata_length) { $ord = ord( substr( $this->structureappend_originaldata, $i, 1 ) ); $this->structureappend_parity = $this->structureappend_parity ^ $ord; $i++; } } $this->data_value[3] = $this->structureappend_parity; $this->data_bits[3] = 8; $this->data_counter = 4; } } /** * Attaches the calculated codeword data to the data matrix * * @param integer $max_codewords Maximum number of codewords for this code * @param integer $matrix_remain_bit Dependant on the version of the code * * @return void */ protected function attachCodewordData($max_codewords, $matrix_remain_bit) { $i = 0; while ($i < $max_codewords) { $codeword_i=$this->codewords[$i]; $j = 8; while ($j >= 1) { $codeword_bits_number = ($i << 3) + $j; $x = $this->matrix_x_array[$codeword_bits_number]; $y = $this->matrix_y_array[$codeword_bits_number]; $this->matrix_content[$x][$y] = ((255*($codeword_i & 1)) ^ $this->mask_array[$codeword_bits_number]); $codeword_i= $codeword_i >> 1; $j--; } $i++; } $matrix_remain = $matrix_remain_bit[$this->version]; while ($matrix_remain) { $remain_bit_temp = $matrix_remain + ( $max_codewords << 3); $x = $this->matrix_x_array[$remain_bit_temp]; $y = $this->matrix_y_array[$remain_bit_temp]; $this->matrix_content[$x][$y] = (255 ^ $this->mask_array[$remain_bit_temp]); $matrix_remain--; } } /** * Selects the mask to use * * @param integer $max_modules_1side Max number of modules (single side) * * @return integer */ protected function maskSelection($max_modules_1side) { $min_demerit_score = 0; $hor_master = ""; $ver_master = ""; $k = 0; while ($k < $max_modules_1side) { $l = 0; while ($l < $max_modules_1side) { $hor_master = $hor_master . chr($this->matrix_content[$l][$k]); $ver_master = $ver_master . chr($this->matrix_content[$k][$l]); $l++; } $k++; } $i = 0; $all_matrix = $max_modules_1side * $max_modules_1side; while ($i < 8) { $demerit_n1 = 0; $ptn_temp = array(); $bit = 1 << $i; $bit_r = (~$bit) & 255; $bit_mask = str_repeat(chr($bit), $all_matrix); $hor = $hor_master & $bit_mask; $ver = $ver_master & $bit_mask; $ver_shift1 = $ver . str_repeat(chr(170), $max_modules_1side); $ver_shift2 = str_repeat(chr(170), $max_modules_1side) . $ver; $ver_shift1_0 = $ver . str_repeat(chr(0), $max_modules_1side); $ver_shift2_0 = str_repeat(chr(0), $max_modules_1side) . $ver; $ver_or = chunk_split( ~($ver_shift1 | $ver_shift2), $max_modules_1side, chr(170) ); $ver_and = chunk_split( ~($ver_shift1_0 & $ver_shift2_0), $max_modules_1side, chr(170) ); $hor = chunk_split(~$hor, $max_modules_1side, chr(170)); $ver = chunk_split(~$ver, $max_modules_1side, chr(170)); $hor = $hor . chr(170) . $ver; $n1_search = "/" . str_repeat(chr(255), 5) . "+|" . str_repeat(chr($bit_r), 5) . "+/"; $n3_search = chr($bit_r) . chr(255) . chr($bit_r) . chr($bit_r) . chr($bit_r) . chr(255) . chr($bit_r); $demerit_n3 = substr_count($hor, $n3_search) * 40; $sc = substr_count($ver, chr($bit_r)); $demerit_n4 = floor(abs(((100 * ($sc / ($this->byte_num))) - 50) / 5)); $demerit_n4 *= 10; $n2_search1 = "/" . chr($bit_r) . chr($bit_r) . "+/"; $n2_search2 = "/" . chr(255) . chr(255) . "+/"; $demerit_n2 = 0; preg_match_all($n2_search1, $ver_and, $ptn_temp); foreach ($ptn_temp[0] as $str_temp) { $demerit_n2 += (strlen($str_temp) - 1); } $ptn_temp = array(); preg_match_all($n2_search2, $ver_or, $ptn_temp); foreach ($ptn_temp[0] as $str_temp) { $demerit_n2 += (strlen($str_temp) - 1); } $demerit_n2*=3; $ptn_temp=array(); preg_match_all($n1_search, $hor, $ptn_temp); foreach ($ptn_temp[0] as $str_temp) { $demerit_n1 += (strlen($str_temp) - 2); } $demerit_score = $demerit_n1 + $demerit_n2; $demerit_score += $demerit_n3 + $demerit_n4; if ($demerit_score <= $min_demerit_score || $i == 0) { $mask_number = $i; $min_demerit_score = $demerit_score; } $i++; } return $mask_number; } /** * Clears the output matrix array, ready for new data * * @param integer $max_modules_1side Max number of modules (single side) * * @return void */ protected function emptyMatrix($max_modules_1side) { $i = 0; while ($i < $max_modules_1side) { $j = 0; while ($j < $max_modules_1side) { $this->matrix_content[$j][$i] = 0; $j++; } $i++; } } /** * Creates the base square image, based on the module size * * @param integer $max_modules_1side Max number of modules (single side) * * @return integer */ protected function createBaseImage($max_modules_1side) { $mib = $max_modules_1side+8; $this->image_size = $mib * $this->module_size; if ($this->image_size > 1480) { throw new Image_QRCode_Exception("Image size is too large"); } // image is square $this->output_image = imagecreate( $this->image_size, $this->image_size ); // load our base image in $image_path = $this->image_path . "/qrv" . $this->version . ".png"; if (!file_exists($image_path)) { throw new Image_QRCode_Exception("Base image not found"); } $this->base_image = imagecreatefrompng($image_path); if (!$this->base_image) { throw new Image_QRCode_Exception("Couldn't load base image"); } return $mib; } /** * Adds the calculated matrix data to the base image. * * @param integer $max_modules_1side Max number of modules (single side) * @param integer $mask_content Mask based on original data * * @return void */ protected function addMatrixToImage($max_modules_1side, $mask_content) { // Create colours that we need to use // NB if you want your barcode to be in a different colour, // simply changing these colours will not work, as the base image // is in black and white to start with. $col[1] = imagecolorallocate($this->base_image, 0, 0, 0); $col[0] = imagecolorallocate($this->base_image, 255, 255, 255); $i = 4; $mxe = 4 + $max_modules_1side; $ii = 0; while ($i < $mxe) { $j = 4; $jj = 0; while ($j < $mxe) { if ($this->matrix_content[$ii][$jj] & $mask_content) { imagesetpixel($this->base_image, $i, $j, $col[1]); } $j++; $jj++; } $i++; $ii++; } } } ?>