qrmask.php (12584B)
1 <?php 2 /* 3 * PHP QR Code encoder 4 * 5 * Masking 6 * 7 * Based on libqrencode C library distributed under LGPL 2.1 8 * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi <fukuchi@megaui.net> 9 * 10 * PHP QR Code is distributed under LGPL 3 11 * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm> 12 * 13 * This library is free software; you can redistribute it and/or 14 * modify it under the terms of the GNU Lesser General Public 15 * License as published by the Free Software Foundation; either 16 * version 3 of the License, or any later version. 17 * 18 * This library is distributed in the hope that it will be useful, 19 * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 21 * Lesser General Public License for more details. 22 * 23 * You should have received a copy of the GNU Lesser General Public 24 * License along with this library; if not, write to the Free Software 25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 26 */ 27 28 define('N1', 3); 29 define('N2', 3); 30 define('N3', 40); 31 define('N4', 10); 32 33 class QRmask { 34 35 public $runLength = array(); 36 37 //---------------------------------------------------------------------- 38 public function __construct() 39 { 40 $this->runLength = array_fill(0, QRSPEC_WIDTH_MAX + 1, 0); 41 } 42 43 //---------------------------------------------------------------------- 44 public function writeFormatInformation($width, &$frame, $mask, $level) 45 { 46 $blacks = 0; 47 $format = QRspec::getFormatInfo($mask, $level); 48 49 for($i=0; $i<8; $i++) { 50 if($format & 1) { 51 $blacks += 2; 52 $v = 0x85; 53 } else { 54 $v = 0x84; 55 } 56 57 $frame[8][$width - 1 - $i] = chr($v); 58 if($i < 6) { 59 $frame[$i][8] = chr($v); 60 } else { 61 $frame[$i + 1][8] = chr($v); 62 } 63 $format = $format >> 1; 64 } 65 66 for($i=0; $i<7; $i++) { 67 if($format & 1) { 68 $blacks += 2; 69 $v = 0x85; 70 } else { 71 $v = 0x84; 72 } 73 74 $frame[$width - 7 + $i][8] = chr($v); 75 if($i == 0) { 76 $frame[8][7] = chr($v); 77 } else { 78 $frame[8][6 - $i] = chr($v); 79 } 80 81 $format = $format >> 1; 82 } 83 84 return $blacks; 85 } 86 87 //---------------------------------------------------------------------- 88 public function mask0($x, $y) { return ($x+$y)&1; } 89 public function mask1($x, $y) { return ($y&1); } 90 public function mask2($x, $y) { return ($x%3); } 91 public function mask3($x, $y) { return ($x+$y)%3; } 92 public function mask4($x, $y) { return (((int)($y/2))+((int)($x/3)))&1; } 93 public function mask5($x, $y) { return (($x*$y)&1)+($x*$y)%3; } 94 public function mask6($x, $y) { return ((($x*$y)&1)+($x*$y)%3)&1; } 95 public function mask7($x, $y) { return ((($x*$y)%3)+(($x+$y)&1))&1; } 96 97 //---------------------------------------------------------------------- 98 private function generateMaskNo($maskNo, $width, $frame) 99 { 100 $bitMask = array_fill(0, $width, array_fill(0, $width, 0)); 101 102 for($y=0; $y<$width; $y++) { 103 for($x=0; $x<$width; $x++) { 104 if(ord($frame[$y][$x]) & 0x80) { 105 $bitMask[$y][$x] = 0; 106 } else { 107 $maskFunc = call_user_func(array($this, 'mask'.$maskNo), $x, $y); 108 $bitMask[$y][$x] = ($maskFunc == 0)?1:0; 109 } 110 111 } 112 } 113 114 return $bitMask; 115 } 116 117 //---------------------------------------------------------------------- 118 public static function serial($bitFrame) 119 { 120 $codeArr = array(); 121 122 foreach ($bitFrame as $line) 123 $codeArr[] = join('', $line); 124 125 return gzcompress(join("\n", $codeArr), 9); 126 } 127 128 //---------------------------------------------------------------------- 129 public static function unserial($code) 130 { 131 $codeArr = array(); 132 133 $codeLines = explode("\n", gzuncompress($code)); 134 foreach ($codeLines as $line) 135 $codeArr[] = str_split($line); 136 137 return $codeArr; 138 } 139 140 //---------------------------------------------------------------------- 141 public function makeMaskNo($maskNo, $width, $s, &$d, $maskGenOnly = false) 142 { 143 $b = 0; 144 $bitMask = array(); 145 146 $fileName = QR_CACHE_DIR.'mask_'.$maskNo.DIRECTORY_SEPARATOR.'mask_'.$width.'_'.$maskNo.'.dat'; 147 148 if (QR_CACHEABLE) { 149 if (file_exists($fileName)) { 150 $bitMask = self::unserial(file_get_contents($fileName)); 151 } else { 152 $bitMask = $this->generateMaskNo($maskNo, $width, $s, $d); 153 if (!file_exists(QR_CACHE_DIR.'mask_'.$maskNo)) 154 mkdir(QR_CACHE_DIR.'mask_'.$maskNo); 155 file_put_contents($fileName, self::serial($bitMask)); 156 } 157 } else { 158 $bitMask = $this->generateMaskNo($maskNo, $width, $s, $d); 159 } 160 161 if ($maskGenOnly) 162 return; 163 164 $d = $s; 165 166 for($y=0; $y<$width; $y++) { 167 for($x=0; $x<$width; $x++) { 168 if($bitMask[$y][$x] == 1) { 169 $d[$y][$x] = chr(ord($s[$y][$x]) ^ (int)$bitMask[$y][$x]); 170 } 171 $b += (int)(ord($d[$y][$x]) & 1); 172 } 173 } 174 175 return $b; 176 } 177 178 //---------------------------------------------------------------------- 179 public function makeMask($width, $frame, $maskNo, $level) 180 { 181 $masked = array_fill(0, $width, str_repeat("\0", $width)); 182 $this->makeMaskNo($maskNo, $width, $frame, $masked); 183 $this->writeFormatInformation($width, $masked, $maskNo, $level); 184 185 return $masked; 186 } 187 188 //---------------------------------------------------------------------- 189 public function calcN1N3($length) 190 { 191 $demerit = 0; 192 193 for($i=0; $i<$length; $i++) { 194 195 if($this->runLength[$i] >= 5) { 196 $demerit += (N1 + ($this->runLength[$i] - 5)); 197 } 198 if($i & 1) { 199 if(($i >= 3) && ($i < ($length-2)) && ($this->runLength[$i] % 3 == 0)) { 200 $fact = (int)($this->runLength[$i] / 3); 201 if(($this->runLength[$i-2] == $fact) && 202 ($this->runLength[$i-1] == $fact) && 203 ($this->runLength[$i+1] == $fact) && 204 ($this->runLength[$i+2] == $fact)) { 205 if(($this->runLength[$i-3] < 0) || ($this->runLength[$i-3] >= (4 * $fact))) { 206 $demerit += N3; 207 } else if((($i+3) >= $length) || ($this->runLength[$i+3] >= (4 * $fact))) { 208 $demerit += N3; 209 } 210 } 211 } 212 } 213 } 214 return $demerit; 215 } 216 217 //---------------------------------------------------------------------- 218 public function evaluateSymbol($width, $frame) 219 { 220 $head = 0; 221 $demerit = 0; 222 223 for($y=0; $y<$width; $y++) { 224 $head = 0; 225 $this->runLength[0] = 1; 226 227 $frameY = $frame[$y]; 228 229 if ($y>0) 230 $frameYM = $frame[$y-1]; 231 232 for($x=0; $x<$width; $x++) { 233 if(($x > 0) && ($y > 0)) { 234 $b22 = ord($frameY[$x]) & ord($frameY[$x-1]) & ord($frameYM[$x]) & ord($frameYM[$x-1]); 235 $w22 = ord($frameY[$x]) | ord($frameY[$x-1]) | ord($frameYM[$x]) | ord($frameYM[$x-1]); 236 237 if(($b22 | ($w22 ^ 1))&1) { 238 $demerit += N2; 239 } 240 } 241 if(($x == 0) && (ord($frameY[$x]) & 1)) { 242 $this->runLength[0] = -1; 243 $head = 1; 244 $this->runLength[$head] = 1; 245 } else if($x > 0) { 246 if((ord($frameY[$x]) ^ ord($frameY[$x-1])) & 1) { 247 $head++; 248 $this->runLength[$head] = 1; 249 } else { 250 $this->runLength[$head]++; 251 } 252 } 253 } 254 255 $demerit += $this->calcN1N3($head+1); 256 } 257 258 for($x=0; $x<$width; $x++) { 259 $head = 0; 260 $this->runLength[0] = 1; 261 262 for($y=0; $y<$width; $y++) { 263 if($y == 0 && (ord($frame[$y][$x]) & 1)) { 264 $this->runLength[0] = -1; 265 $head = 1; 266 $this->runLength[$head] = 1; 267 } else if($y > 0) { 268 if((ord($frame[$y][$x]) ^ ord($frame[$y-1][$x])) & 1) { 269 $head++; 270 $this->runLength[$head] = 1; 271 } else { 272 $this->runLength[$head]++; 273 } 274 } 275 } 276 277 $demerit += $this->calcN1N3($head+1); 278 } 279 280 return $demerit; 281 } 282 283 284 //---------------------------------------------------------------------- 285 public function mask($width, $frame, $level) 286 { 287 $minDemerit = PHP_INT_MAX; 288 $bestMaskNum = 0; 289 $bestMask = array(); 290 291 $checked_masks = array(0,1,2,3,4,5,6,7); 292 293 if (QR_FIND_FROM_RANDOM !== false) { 294 295 $howManuOut = 8-(QR_FIND_FROM_RANDOM % 9); 296 for ($i = 0; $i < $howManuOut; $i++) { 297 $remPos = rand (0, count($checked_masks)-1); 298 unset($checked_masks[$remPos]); 299 $checked_masks = array_values($checked_masks); 300 } 301 302 } 303 304 $bestMask = $frame; 305 306 foreach($checked_masks as $i) { 307 $mask = array_fill(0, $width, str_repeat("\0", $width)); 308 309 $demerit = 0; 310 $blacks = 0; 311 $blacks = $this->makeMaskNo($i, $width, $frame, $mask); 312 $blacks += $this->writeFormatInformation($width, $mask, $i, $level); 313 $blacks = (int)(100 * $blacks / ($width * $width)); 314 $demerit = (int)((int)(abs($blacks - 50) / 5) * N4); 315 $demerit += $this->evaluateSymbol($width, $mask); 316 317 if($demerit < $minDemerit) { 318 $minDemerit = $demerit; 319 $bestMask = $mask; 320 $bestMaskNum = $i; 321 } 322 } 323 324 return $bestMask; 325 } 326 327 //---------------------------------------------------------------------- 328 }