1<?php 2 3namespace Mpdf\Image; 4 5use Mpdf\Mpdf; 6 7class Bmp 8{ 9 10 /** 11 * @var Mpdf 12 */ 13 private $mpdf; 14 15 public function __construct(Mpdf $mpdf) 16 { 17 $this->mpdf = $mpdf; 18 } 19 20 public function _getBMPimage($data, $file) 21 { 22 // Adapted from script by Valentin Schmidt 23 // http://staff.dasdeck.de/valentin/fpdf/fpdf_bmp/ 24 $bfOffBits = $this->_fourbytes2int_le(substr($data, 10, 4)); 25 $width = $this->_fourbytes2int_le(substr($data, 18, 4)); 26 $height = $this->_fourbytes2int_le(substr($data, 22, 4)); 27 $flip = ($height < 0); 28 if ($flip) { 29 $height = -$height; 30 } 31 $biBitCount = $this->_twobytes2int_le(substr($data, 28, 2)); 32 $biCompression = $this->_fourbytes2int_le(substr($data, 30, 4)); 33 $info = ['w' => $width, 'h' => $height]; 34 if ($biBitCount < 16) { 35 $info['cs'] = 'Indexed'; 36 $info['bpc'] = $biBitCount; 37 $palStr = substr($data, 54, $bfOffBits - 54); 38 $pal = ''; 39 $cnt = strlen($palStr) / 4; 40 for ($i = 0; $i < $cnt; $i++) { 41 $n = 4 * $i; 42 $pal .= $palStr[$n + 2] . $palStr[$n + 1] . $palStr[$n]; 43 } 44 $info['pal'] = $pal; 45 } else { 46 $info['cs'] = 'DeviceRGB'; 47 $info['bpc'] = 8; 48 } 49 50 if ($this->mpdf->restrictColorSpace == 1 || $this->mpdf->PDFX || $this->mpdf->restrictColorSpace == 3) { 51 if (($this->mpdf->PDFA && !$this->mpdf->PDFAauto) || ($this->mpdf->PDFX && !$this->mpdf->PDFXauto)) { 52 $this->mpdf->PDFAXwarnings[] = "Image cannot be converted to suitable colour space for PDFA or PDFX file - $file - (Image replaced by 'no-image'.)"; 53 } 54 return ['error' => "BMP Image cannot be converted to suitable colour space - $file - (Image replaced by 'no-image'.)"]; 55 } 56 57 $biXPelsPerMeter = $this->_fourbytes2int_le(substr($data, 38, 4)); // horizontal pixels per meter, usually set to zero 58 //$biYPelsPerMeter=$this->_fourbytes2int_le(substr($data,42,4)); // vertical pixels per meter, usually set to zero 59 $biXPelsPerMeter = round($biXPelsPerMeter / 1000 * 25.4); 60 //$biYPelsPerMeter=round($biYPelsPerMeter/1000 *25.4); 61 $info['set-dpi'] = $biXPelsPerMeter; 62 63 switch ($biCompression) { 64 case 0: 65 $str = substr($data, $bfOffBits); 66 break; 67 case 1: # BI_RLE8 68 $str = $this->rle8_decode(substr($data, $bfOffBits), $width); 69 break; 70 case 2: # BI_RLE4 71 $str = $this->rle4_decode(substr($data, $bfOffBits), $width); 72 break; 73 } 74 $bmpdata = ''; 75 $padCnt = (4 - ceil($width / (8 / $biBitCount)) % 4) % 4; 76 switch ($biBitCount) { 77 case 1: 78 case 4: 79 case 8: 80 $w = floor($width / (8 / $biBitCount)) + ($width % (8 / $biBitCount) ? 1 : 0); 81 $w_row = $w + $padCnt; 82 if ($flip) { 83 for ($y = 0; $y < $height; $y++) { 84 $y0 = $y * $w_row; 85 for ($x = 0; $x < $w; $x++) { 86 $bmpdata .= $str[$y0 + $x]; 87 } 88 } 89 } else { 90 for ($y = $height - 1; $y >= 0; $y--) { 91 $y0 = $y * $w_row; 92 for ($x = 0; $x < $w; $x++) { 93 $bmpdata .= $str[$y0 + $x]; 94 } 95 } 96 } 97 break; 98 99 case 16: 100 $w_row = $width * 2 + $padCnt; 101 if ($flip) { 102 for ($y = 0; $y < $height; $y++) { 103 $y0 = $y * $w_row; 104 for ($x = 0; $x < $width; $x++) { 105 $n = (ord($str[$y0 + 2 * $x + 1]) * 256 + ord($str[$y0 + 2 * $x])); 106 $b = ($n & 31) << 3; 107 $g = ($n & 992) >> 2; 108 $r = ($n & 31744) >> 7; 109 $bmpdata .= chr($r) . chr($g) . chr($b); 110 } 111 } 112 } else { 113 for ($y = $height - 1; $y >= 0; $y--) { 114 $y0 = $y * $w_row; 115 for ($x = 0; $x < $width; $x++) { 116 $n = (ord($str[$y0 + 2 * $x + 1]) * 256 + ord($str[$y0 + 2 * $x])); 117 $b = ($n & 31) << 3; 118 $g = ($n & 992) >> 2; 119 $r = ($n & 31744) >> 7; 120 $bmpdata .= chr($r) . chr($g) . chr($b); 121 } 122 } 123 } 124 break; 125 126 case 24: 127 case 32: 128 $byteCnt = $biBitCount / 8; 129 $w_row = $width * $byteCnt + $padCnt; 130 131 if ($flip) { 132 for ($y = 0; $y < $height; $y++) { 133 $y0 = $y * $w_row; 134 for ($x = 0; $x < $width; $x++) { 135 $i = $y0 + $x * $byteCnt; # + 1 136 $bmpdata .= $str[$i + 2] . $str[$i + 1] . $str[$i]; 137 } 138 } 139 } else { 140 for ($y = $height - 1; $y >= 0; $y--) { 141 $y0 = $y * $w_row; 142 for ($x = 0; $x < $width; $x++) { 143 $i = $y0 + $x * $byteCnt; # + 1 144 $bmpdata .= $str[$i + 2] . $str[$i + 1] . $str[$i]; 145 } 146 } 147 } 148 break; 149 150 default: 151 return ['error' => 'Error parsing BMP image - Unsupported image biBitCount']; 152 } 153 if ($this->mpdf->compress) { 154 $bmpdata = gzcompress($bmpdata); 155 $info['f'] = 'FlateDecode'; 156 } 157 $info['data'] = $bmpdata; 158 $info['type'] = 'bmp'; 159 return $info; 160 } 161 162 /** 163 * Read a 4-byte integer from string 164 * 165 * @param $s 166 * @return int 167 */ 168 private function _fourbytes2int_le($s) 169 { 170 return (ord($s[3]) << 24) + (ord($s[2]) << 16) + (ord($s[1]) << 8) + ord($s[0]); 171 } 172 173 /** 174 * Read a 2-byte integer from string 175 * 176 * @param $s 177 * @return int 178 */ 179 private function _twobytes2int_le($s) 180 { 181 return (ord(substr($s, 1, 1)) << 8) + ord(substr($s, 0, 1)); 182 } 183 184 /** 185 * Decoder for RLE8 compression in windows bitmaps 186 * 187 * @see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_6x0u.asp 188 * @param $str 189 * @param $width 190 * @return string 191 */ 192 private function rle8_decode($str, $width) 193 { 194 $lineWidth = $width + (3 - ($width - 1) % 4); 195 $out = ''; 196 $cnt = strlen($str); 197 for ($i = 0; $i < $cnt; $i++) { 198 $o = ord($str[$i]); 199 if ($o === 0) { # ESCAPE 200 $i++; 201 switch (ord($str[$i])) { 202 case 0: # NEW LINE 203 $padCnt = $lineWidth - strlen($out) % $lineWidth; 204 if ($padCnt < $lineWidth) { 205 $out .= str_repeat(chr(0), $padCnt);# pad line 206 } 207 break; 208 case 1: # END OF FILE 209 $padCnt = $lineWidth - strlen($out) % $lineWidth; 210 if ($padCnt < $lineWidth) { 211 $out .= str_repeat(chr(0), $padCnt);# pad line 212 } 213 break 2; 214 case 2: # DELTA 215 $i += 2; 216 break; 217 default: # ABSOLUTE MODE 218 $num = ord($str[$i]); 219 for ($j = 0; $j < $num; $j++) { 220 $out .= $str[++$i]; 221 } 222 if ($num % 2) { 223 $i++; 224 } 225 } 226 } else { 227 $out .= str_repeat($str[++$i], $o); 228 } 229 } 230 return $out; 231 } 232 233 /** 234 * Decoder for RLE4 compression in windows bitmaps 235 * 236 * @see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_6x0u.asp 237 * @param $str 238 * @param $width 239 * @return string 240 */ 241 private function rle4_decode($str, $width) 242 { 243 $w = floor($width / 2) + ($width % 2); 244 $lineWidth = $w + (3 - ( ($width - 1) / 2) % 4); 245 $pixels = []; 246 $cnt = strlen($str); 247 for ($i = 0; $i < $cnt; $i++) { 248 $o = ord($str[$i]); 249 if ($o === 0) { # ESCAPE 250 $i++; 251 switch (ord($str[$i])) { 252 case 0: # NEW LINE 253 while (count($pixels) % $lineWidth !== 0) { 254 $pixels[] = 0; 255 } 256 break; 257 case 1: # END OF FILE 258 while (count($pixels) % $lineWidth !== 0) { 259 $pixels[] = 0; 260 } 261 break 2; 262 case 2: # DELTA 263 $i += 2; 264 break; 265 default: # ABSOLUTE MODE 266 $num = ord($str[$i]); 267 for ($j = 0; $j < $num; $j++) { 268 if ($j % 2 === 0) { 269 $c = ord($str[++$i]); 270 $pixels[] = ($c & 240) >> 4; 271 } else { 272 $pixels[] = $c & 15; //FIXME: undefined var 273 } 274 } 275 if ($num % 2) { 276 $i++; 277 } 278 } 279 } else { 280 $c = ord($str[++$i]); 281 for ($j = 0; $j < $o; $j++) { 282 $pixels[] = ($j % 2 === 0 ? ($c & 240) >> 4 : $c & 15); 283 } 284 } 285 } 286 287 $out = ''; 288 if (count($pixels) % 2) { 289 $pixels[] = 0; 290 } 291 $cnt = count($pixels) / 2; 292 for ($i = 0; $i < $cnt; $i++) { 293 $out .= chr(16 * $pixels[2 * $i] + $pixels[2 * $i + 1]); 294 } 295 return $out; 296 } 297} 298