composer package updates
[openemr.git] / vendor / mpdf / mpdf / src / Image / ImageProcessor.php
blob81f23d625cad572c0b4489022dda802eebee0c34
1 <?php
3 namespace Mpdf\Image;
5 use Mpdf\Cache;
6 use Mpdf\Color\ColorConverter;
7 use Mpdf\Color\ColorModeConverter;
8 use Mpdf\CssManager;
9 use Mpdf\Gif\Gif;
10 use Mpdf\Language\LanguageToFontInterface;
11 use Mpdf\Language\ScriptToLanguageInterface;
12 use Mpdf\Log\Context as LogContext;
13 use Mpdf\Mpdf;
14 use Mpdf\Otl;
15 use Mpdf\SizeConverter;
17 use Psr\Log\LoggerInterface;
19 class ImageProcessor implements \Psr\Log\LoggerAwareInterface
22 /**
23 * @var \Mpdf\Mpdf
25 private $mpdf;
27 /**
28 * @var \Mpdf\Otl
30 private $otl;
32 /**
33 * @var \Mpdf\CssManager
35 private $cssManager;
37 /**
38 * @var \Mpdf\SizeConverter
40 private $sizeConverter;
42 /**
43 * @var \Mpdf\Color\ColorConverter
45 private $colorConverter;
47 /**
48 * @var \Mpdf\Color\ColorModeConverter
50 private $colorModeConverter;
52 /**
53 * @var \Mpdf\Cache
55 private $cache;
57 /**
58 * @var \Mpdf\Image\ImageTypeGuesser
60 private $guesser;
62 /**
63 * @var string[]
65 private $failedImages;
67 /**
68 * @var \Mpdf\Image\Bmp
70 private $bmp;
72 /**
73 * @var \Mpdf\Image\Wmf
75 private $wmf;
77 /**
78 * @var \Mpdf\Language\LanguageToFontInterface
80 private $languageToFont;
82 /**
83 * @var \Mpdf\Language\ScriptToLanguageInterface
85 public $scriptToLanguage;
87 /**
88 * @var \Psr\Log\LoggerInterface
90 public $logger;
92 public function __construct(
93 Mpdf $mpdf,
94 Otl $otl,
95 CssManager $cssManager,
96 SizeConverter $sizeConverter,
97 ColorConverter $colorConverter,
98 ColorModeConverter $colorModeConverter,
99 Cache $cache,
100 LanguageToFontInterface $languageToFont,
101 ScriptToLanguageInterface $scriptToLanguage,
102 LoggerInterface $logger
105 $this->mpdf = $mpdf;
106 $this->otl = $otl;
107 $this->cssManager = $cssManager;
108 $this->sizeConverter = $sizeConverter;
109 $this->colorConverter = $colorConverter;
110 $this->colorModeConverter = $colorModeConverter;
111 $this->cache = $cache;
112 $this->languageToFont = $languageToFont;
113 $this->scriptToLanguage = $scriptToLanguage;
115 $this->logger = $logger;
117 $this->guesser = new ImageTypeGuesser();
119 $this->failedImages = [];
123 * @param \Psr\Log\LoggerInterface
125 * @return \Mpdf\Mpdf
127 public function setLogger(LoggerInterface $logger)
129 $this->logger = $logger;
131 return $this;
134 public function getImage(&$file, $firsttime = true, $allowvector = true, $orig_srcpath = false, $interpolation = false)
136 // mPDF 6
137 // firsttime i.e. whether to add to this->images - use false when calling iteratively
138 // Image Data passed directly as var:varname
139 if (preg_match('/var:\s*(.*)/', $file, $v)) {
140 if (!isset($this->mpdf->imageVars[$v[1]])) {
141 return $this->imageError($file, $firsttime, 'Unknown image variable');
143 $data = $this->mpdf->imageVars[$v[1]];
144 $file = md5($data);
147 if (preg_match('/data:image\/(gif|jpeg|png);base64,(.*)/', $file, $v)) {
148 $type = $v[1];
149 $data = base64_decode($v[2]);
150 $file = md5($data);
153 // mPDF 5.7.4 URLs
154 if ($firsttime && $file && substr($file, 0, 5) != 'data:') {
155 $file = str_replace(" ", "%20", $file);
157 if ($firsttime && $orig_srcpath) {
158 // If orig_srcpath is a relative file path (and not a URL), then it needs to be URL decoded
159 if (substr($orig_srcpath, 0, 5) != 'data:') {
160 $orig_srcpath = str_replace(" ", "%20", $orig_srcpath);
162 if (!preg_match('/^(http|ftp)/', $orig_srcpath)) {
163 $orig_srcpath = $this->urldecodeParts($orig_srcpath);
167 $ppUx = 0;
168 if ($orig_srcpath && isset($this->mpdf->images[$orig_srcpath])) {
169 $file = $orig_srcpath;
170 return $this->mpdf->images[$orig_srcpath];
173 if (isset($this->mpdf->images[$file])) {
174 return $this->mpdf->images[$file];
175 } elseif ($orig_srcpath && isset($this->mpdf->formobjects[$orig_srcpath])) {
176 $file = $orig_srcpath;
177 return $this->mpdf->formobjects[$file];
178 } elseif (isset($this->mpdf->formobjects[$file])) {
179 return $this->mpdf->formobjects[$file];
180 } elseif ($firsttime && isset($this->failedImages[$file])) { // Save re-trying image URL's which have already failed
181 return $this->imageError($file, $firsttime, '');
184 if (empty($data)) {
186 $type = '';
187 $data = '';
189 if ($orig_srcpath && $this->mpdf->basepathIsLocal && $check = @fopen($orig_srcpath, "rb")) {
190 fclose($check);
191 $file = $orig_srcpath;
192 $this->logger->debug(sprintf('Fetching (file_get_contents) content of file "%s" with local basepath', $file), ['context' => LogContext::REMOTE_CONTENT]);
193 $data = file_get_contents($file);
194 $type = $this->guesser->guess($data);
197 if (!$data && $check = @fopen($file, "rb")) {
198 fclose($check);
199 $this->logger->debug(sprintf('Fetching (file_get_contents) content of file "%s" with non-local basepath', $file), ['context' => LogContext::REMOTE_CONTENT]);
200 $data = file_get_contents($file);
201 $type = $this->guesser->guess($data);
204 if ((!$data || !$type) && function_exists('curl_init')) { // mPDF 5.7.4
205 $this->mpdf->getFileContentsByCurl($file, $data); // needs full url?? even on local (never needed for local)
206 if ($data) {
207 $type = $this->guesser->guess($data);
211 if ((!$data || !$type) && !ini_get('allow_url_fopen')) { // only worth trying if remote file and !ini_get('allow_url_fopen')
212 $this->mpdf->getFileContentsBySocket($file, $data); // needs full url?? even on local (never needed for local)
213 if ($data) {
214 $type = $this->guesser->guess($data);
219 if (!$data) {
220 return $this->imageError($file, $firsttime, 'Could not find image file');
223 if (empty($type)) {
224 $type = $this->guesser->guess($data);
227 if (($type == 'wmf' || $type == 'svg') && !$allowvector) {
228 return $this->imageError($file, $firsttime, 'WMF or SVG image file not supported in this context');
231 // SVG
232 if ($type == 'svg') {
233 $svg = new Svg($this->mpdf, $this->otl, $this->cssManager, $this, $this->sizeConverter, $this->colorConverter, $this->languageToFont, $this->scriptToLanguage);
234 $family = $this->mpdf->FontFamily;
235 $style = $this->mpdf->FontStyle;
236 $size = $this->mpdf->FontSizePt;
237 $info = $svg->ImageSVG($data);
238 // Restore font
239 if ($family) {
240 $this->mpdf->SetFont($family, $style, $size, false);
242 if (!$info) {
243 return $this->imageError($file, $firsttime, 'Error parsing SVG file');
245 $info['type'] = 'svg';
246 $info['i'] = count($this->mpdf->formobjects) + 1;
247 $this->mpdf->formobjects[$file] = $info;
249 return $info;
252 // JPEG
253 if ($type == 'jpeg' || $type == 'jpg') {
254 $hdr = $this->jpgHeaderFromString($data);
255 if (!$hdr) {
256 return $this->imageError($file, $firsttime, 'Error parsing JPG header');
258 $a = $this->jpgDataFromHeader($hdr);
259 $channels = intval($a[4]);
260 $j = strpos($data, 'JFIF');
261 if ($j) {
262 //Read resolution
263 $unitSp = ord(substr($data, ($j + 7), 1));
264 if ($unitSp > 0) {
265 $ppUx = $this->twoBytesToInt(substr($data, ($j + 8), 2)); // horizontal pixels per meter, usually set to zero
266 if ($unitSp == 2) { // = dots per cm (if == 1 set as dpi)
267 $ppUx = round($ppUx / 10 * 25.4);
271 if ($a[2] == 'DeviceCMYK' && (($this->mpdf->PDFA && $this->mpdf->restrictColorSpace != 3) || $this->mpdf->restrictColorSpace == 2)) {
273 // convert to RGB image
274 if (!function_exists("gd_info")) {
275 throw new \Mpdf\MpdfException("JPG image may not use CMYK color space (" . $file . ").");
278 if ($this->mpdf->PDFA && !$this->mpdf->PDFAauto) {
279 $this->mpdf->PDFAXwarnings[] = "JPG image may not use CMYK color space - " . $file . " - (Image converted to RGB. NB This will alter the colour profile of the image.)";
282 $im = @imagecreatefromstring($data);
283 if ($im) {
284 $tempfile = $this->cache->tempFilename('_tempImgPNG' . md5($file) . random_int(1, 10000) . '.png');
285 imageinterlace($im, false);
286 $check = @imagepng($im, $tempfile);
287 if (!$check) {
288 return $this->imageError($file, $firsttime, 'Error creating temporary file (' . $tempfile . ') whilst using GD library to parse JPG(CMYK) image');
290 $info = $this->getImage($tempfile, false);
291 if (!$info) {
292 return $this->imageError($file, $firsttime, 'Error parsing temporary file (' . $tempfile . ') created with GD library to parse JPG(CMYK) image');
294 imagedestroy($im);
295 unlink($tempfile);
296 $info['type'] = 'jpg';
297 if ($firsttime) {
298 $info['i'] = count($this->mpdf->images) + 1;
299 $info['interpolation'] = $interpolation; // mPDF 6
300 $this->mpdf->images[$file] = $info;
302 return $info;
303 } else {
304 return $this->imageError($file, $firsttime, 'Error creating GD image file from JPG(CMYK) image');
307 } elseif ($a[2] == 'DeviceRGB' && ($this->mpdf->PDFX || $this->mpdf->restrictColorSpace == 3)) {
308 // Convert to CMYK image stream - nominally returned as type='png'
309 $info = $this->convImage($data, $a[2], 'DeviceCMYK', $a[0], $a[1], $ppUx, false);
310 if (($this->mpdf->PDFA && !$this->mpdf->PDFAauto) || ($this->mpdf->PDFX && !$this->mpdf->PDFXauto)) {
311 $this->mpdf->PDFAXwarnings[] = "JPG image may not use RGB color space - " . $file . " - (Image converted to CMYK. NB This will alter the colour profile of the image.)";
314 } elseif (($a[2] == 'DeviceRGB' || $a[2] == 'DeviceCMYK') && $this->mpdf->restrictColorSpace == 1) {
315 // Convert to Grayscale image stream - nominally returned as type='png'
316 $info = $this->convImage($data, $a[2], 'DeviceGray', $a[0], $a[1], $ppUx, false);
318 } else {
319 // mPDF 6 Detect Adobe APP14 Tag
320 //$pos = strpos($data, "\xFF\xEE\x00\x0EAdobe\0");
321 //if ($pos !== false) {
323 // mPDF 6 ICC profile
324 $offset = 0;
325 $icc = [];
326 while (($pos = strpos($data, "ICC_PROFILE\0", $offset)) !== false) {
327 // get ICC sequence length
328 $length = $this->twoBytesToInt(substr($data, ($pos - 2), 2)) - 16;
329 $sn = max(1, ord($data[($pos + 12)]));
330 $nom = max(1, ord($data[($pos + 13)]));
331 $icc[($sn - 1)] = substr($data, ($pos + 14), $length);
332 $offset = ($pos + 14 + $length);
334 // order and compact ICC segments
335 if (count($icc) > 0) {
336 ksort($icc);
337 $icc = implode('', $icc);
338 if (substr($icc, 36, 4) != 'acsp') {
339 // invalid ICC profile
340 $icc = false;
342 $input = substr($icc, 16, 4);
343 $output = substr($icc, 20, 4);
344 // Ignore Color profiles for conversion to other colorspaces e.g. CMYK/Lab
345 if ($input != 'RGB ' || $output != 'XYZ ') {
346 $icc = false;
348 } else {
349 $icc = false;
352 $info = ['w' => $a[0], 'h' => $a[1], 'cs' => $a[2], 'bpc' => $a[3], 'f' => 'DCTDecode', 'data' => $data, 'type' => 'jpg', 'ch' => $channels, 'icc' => $icc];
353 if ($ppUx) {
354 $info['set-dpi'] = $ppUx;
358 if (!$info) {
359 return $this->imageError($file, $firsttime, 'Error parsing or converting JPG image');
362 if ($firsttime) {
363 $info['i'] = count($this->mpdf->images) + 1;
364 $info['interpolation'] = $interpolation; // mPDF 6
365 $this->mpdf->images[$file] = $info;
368 return $info;
369 } elseif ($type == 'png') {
371 // Check signature
372 if (substr($data, 0, 8) != chr(137) . 'PNG' . chr(13) . chr(10) . chr(26) . chr(10)) {
373 return $this->imageError($file, $firsttime, 'Error parsing PNG identifier');
376 // Read header chunk
377 if (substr($data, 12, 4) != 'IHDR') {
378 return $this->imageError($file, $firsttime, 'Incorrect PNG file (no IHDR block found)');
381 $w = $this->fourBytesToInt(substr($data, 16, 4));
382 $h = $this->fourBytesToInt(substr($data, 20, 4));
383 $bpc = ord(substr($data, 24, 1));
384 $errpng = false;
385 $pngalpha = false;
386 $channels = 0;
388 // if($bpc>8) { $errpng = 'not 8-bit depth'; } // mPDF 6 Allow through to be handled as native PNG
390 $ct = ord(substr($data, 25, 1));
391 if ($ct == 0) {
392 $colspace = 'DeviceGray';
393 $channels = 1;
394 } elseif ($ct == 2) {
395 $colspace = 'DeviceRGB';
396 $channels = 3;
397 } elseif ($ct == 3) {
398 $colspace = 'Indexed';
399 $channels = 1;
400 } elseif ($ct == 4) {
401 $colspace = 'DeviceGray';
402 $channels = 1;
403 $errpng = 'alpha channel';
404 $pngalpha = true;
405 } else {
406 $colspace = 'DeviceRGB';
407 $channels = 3;
408 $errpng = 'alpha channel';
409 $pngalpha = true;
412 if ($ct < 4 && strpos($data, 'tRNS') !== false) {
413 $errpng = 'transparency';
414 $pngalpha = true;
415 } // mPDF 6
417 if ($ct == 3 && strpos($data, 'iCCP') !== false) {
418 $errpng = 'indexed plus ICC';
419 } // mPDF 6
420 // $pngalpha is used as a FLAG of any kind of transparency which COULD be tranferred to an alpha channel
421 // incl. single-color tarnsparency, depending which type of handling occurs later
423 if (ord(substr($data, 26, 1)) != 0) {
424 $errpng = 'compression method';
425 } // only 0 should be specified
426 if (ord(substr($data, 27, 1)) != 0) {
427 $errpng = 'filter method';
428 } // only 0 should be specified
429 if (ord(substr($data, 28, 1)) != 0) {
430 $errpng = 'interlaced file';
433 $j = strpos($data, 'pHYs');
434 if ($j) {
435 //Read resolution
436 $unitSp = ord(substr($data, ($j + 12), 1));
437 if ($unitSp == 1) {
438 $ppUx = $this->fourBytesToInt(substr($data, ($j + 4), 4)); // horizontal pixels per meter, usually set to zero
439 $ppUx = round($ppUx / 1000 * 25.4);
443 // mPDF 6 Gamma correction
444 $gamma = 0;
445 $gAMA = 0;
446 $j = strpos($data, 'gAMA');
447 if ($j && strpos($data, 'sRGB') === false) { // sRGB colorspace - overrides gAMA
448 $gAMA = $this->fourBytesToInt(substr($data, ($j + 4), 4)); // Gamma value times 100000
449 $gAMA /= 100000;
451 // http://www.libpng.org/pub/png/spec/1.2/PNG-Encoders.html
452 // "If the source file's gamma value is greater than 1.0, it is probably a display system exponent,..."
453 // ("..and you should use its reciprocal for the PNG gamma.")
454 //if ($gAMA > 1) { $gAMA = 1/$gAMA; }
455 // (Some) Applications seem to ignore it... appearing how it was probably intended
456 // Test Case - image(s) on http://www.w3.org/TR/CSS21/intro.html - PNG has gAMA set as 1.45454
457 // Probably unintentional as mentioned above and should be 0.45454 which is 1 / 2.2
458 // Tested on Windows PC
459 // Firefox and Opera display gray as 234 (correct, but looks wrong)
460 // IE9 and Safari display gray as 193 (incorrect but looks right)
461 // See test different gamma chunks at http://www.libpng.org/pub/png/pngsuite-all-good.html
464 if ($gAMA) {
465 $gamma = 1 / $gAMA;
468 // Don't need to apply gamma correction if == default i.e. 2.2
469 if ($gamma > 2.15 && $gamma < 2.25) {
470 $gamma = 0;
473 // NOT supported at present
474 //$j = strpos($data,'sRGB'); // sRGB colorspace - overrides gAMA
475 //$j = strpos($data,'cHRM'); // Chromaticity and Whitepoint
476 // $firsttime added mPDF 6 so when PNG Grayscale with alpha using resrtictcolorspace to CMYK
477 // the alpha channel is sent through as secondtime as Indexed and should not be converted to CMYK
478 if ($firsttime && ($colspace == 'DeviceRGB' || $colspace == 'Indexed') && ($this->mpdf->PDFX || $this->mpdf->restrictColorSpace == 3)) {
479 // Convert to CMYK image stream - nominally returned as type='png'
480 $info = $this->convImage($data, $colspace, 'DeviceCMYK', $w, $h, $ppUx, $pngalpha, $gamma, $ct); // mPDF 5.7.2 Gamma correction
481 if (($this->mpdf->PDFA && !$this->mpdf->PDFAauto) || ($this->mpdf->PDFX && !$this->mpdf->PDFXauto)) {
482 $this->mpdf->PDFAXwarnings[] = "PNG image may not use RGB color space - " . $file . " - (Image converted to CMYK. NB This will alter the colour profile of the image.)";
484 } // $firsttime added mPDF 6 so when PNG Grayscale with alpha using resrtictcolorspace to CMYK
485 // the alpha channel is sent through as secondtime as Indexed and should not be converted to CMYK
486 elseif ($firsttime && ($colspace == 'DeviceRGB' || $colspace == 'Indexed') && $this->mpdf->restrictColorSpace == 1) {
487 // Convert to Grayscale image stream - nominally returned as type='png'
488 $info = $this->convImage($data, $colspace, 'DeviceGray', $w, $h, $ppUx, $pngalpha, $gamma, $ct); // mPDF 5.7.2 Gamma correction
489 } elseif (($this->mpdf->PDFA || $this->mpdf->PDFX) && $pngalpha) {
490 // Remove alpha channel
491 if ($this->mpdf->restrictColorSpace == 1) { // Grayscale
492 $info = $this->convImage($data, $colspace, 'DeviceGray', $w, $h, $ppUx, $pngalpha, $gamma, $ct); // mPDF 5.7.2 Gamma correction
493 } elseif ($this->mpdf->restrictColorSpace == 3) { // CMYK
494 $info = $this->convImage($data, $colspace, 'DeviceCMYK', $w, $h, $ppUx, $pngalpha, $gamma, $ct); // mPDF 5.7.2 Gamma correction
495 } elseif ($this->mpdf->PDFA) { // RGB
496 $info = $this->convImage($data, $colspace, 'DeviceRGB', $w, $h, $ppUx, $pngalpha, $gamma, $ct); // mPDF 5.7.2 Gamma correction
498 if (($this->mpdf->PDFA && !$this->mpdf->PDFAauto) || ($this->mpdf->PDFX && !$this->mpdf->PDFXauto)) {
499 $this->mpdf->PDFAXwarnings[] = "Transparency (alpha channel) not permitted in PDFA or PDFX files - " . $file . " - (Image converted to one without transparency.)";
501 } elseif ($firsttime && ($errpng || $pngalpha || $gamma)) { // mPDF 5.7.2 Gamma correction
502 if (function_exists('gd_info')) {
503 $gd = gd_info();
504 } else {
505 $gd = [];
507 if (!isset($gd['PNG Support'])) {
508 return $this->imageError($file, $firsttime, 'GD library required for PNG image (' . $errpng . ')');
510 $im = imagecreatefromstring($data);
512 if (!$im) {
513 return $this->imageError($file, $firsttime, 'Error creating GD image from PNG file (' . $errpng . ')');
515 $w = imagesx($im);
516 $h = imagesy($im);
517 if ($im) {
518 $tempfile = $this->cache->tempFilename('_tempImgPNG' . md5($file) . random_int(1, 10000) . '.png');
520 // Alpha channel set (including using tRNS for Paletted images)
521 if ($pngalpha) {
522 if ($this->mpdf->PDFA) {
523 throw new \Mpdf\MpdfException("PDFA1-b does not permit images with alpha channel transparency (" . $file . ").");
526 $imgalpha = imagecreate($w, $h);
527 // generate gray scale pallete
528 for ($c = 0; $c < 256; ++$c) {
529 imagecolorallocate($imgalpha, $c, $c, $c);
532 // mPDF 6
533 if ($colspace == 'Indexed') { // generate Alpha channel values from tRNS
534 // Read transparency info
535 $p = strpos($data, 'tRNS');
536 if ($p) {
537 $n = $this->fourBytesToInt(substr($data, ($p - 4), 4));
538 $transparency = substr($data, ($p + 4), $n);
539 // ord($transparency{$index}) = the alpha value for that index
540 // generate alpha channel
541 for ($ypx = 0; $ypx < $h; ++$ypx) {
542 for ($xpx = 0; $xpx < $w; ++$xpx) {
543 $colorindex = imagecolorat($im, $xpx, $ypx);
544 if ($colorindex >= $n) {
545 $alpha = 255;
546 } else {
547 $alpha = ord($transparency{$colorindex});
548 } // 0-255
549 if ($alpha > 0) {
550 imagesetpixel($imgalpha, $xpx, $ypx, $alpha);
555 } elseif ($ct === 0 || $ct == 2) { // generate Alpha channel values from tRNS
556 // Get transparency as array of RGB
557 $p = strpos($data, 'tRNS');
558 if ($p) {
559 $trns = '';
560 $n = $this->fourBytesToInt(substr($data, ($p - 4), 4));
561 $t = substr($data, ($p + 4), $n);
562 if ($colspace == 'DeviceGray') { // ct===0
563 $trns = [$this->translateValue(substr($t, 0, 2), $bpc)];
564 } else /* $colspace=='DeviceRGB' */ { // ct==2
565 $trns = [];
566 $trns[0] = $this->translateValue(substr($t, 0, 2), $bpc);
567 $trns[1] = $this->translateValue(substr($t, 2, 2), $bpc);
568 $trns[2] = $this->translateValue(substr($t, 4, 2), $bpc);
571 // generate alpha channel
572 for ($ypx = 0; $ypx < $h; ++$ypx) {
573 for ($xpx = 0; $xpx < $w; ++$xpx) {
574 $rgb = imagecolorat($im, $xpx, $ypx);
575 $r = ($rgb >> 16) & 0xFF;
576 $g = ($rgb >> 8) & 0xFF;
577 $b = $rgb & 0xFF;
578 if ($colspace == 'DeviceGray' && $b == $trns[0]) {
579 $alpha = 0;
580 } elseif ($r == $trns[0] && $g == $trns[1] && $b == $trns[2]) {
581 $alpha = 0;
582 } // ct==2
583 else {
584 $alpha = 255;
586 if ($alpha > 0) {
587 imagesetpixel($imgalpha, $xpx, $ypx, $alpha);
592 } else {
593 // extract alpha channel
594 for ($ypx = 0; $ypx < $h; ++$ypx) {
595 for ($xpx = 0; $xpx < $w; ++$xpx) {
596 $alpha = (imagecolorat($im, $xpx, $ypx) & 0x7F000000) >> 24;
597 if ($alpha < 127) {
598 imagesetpixel($imgalpha, $xpx, $ypx, (255 - ($alpha * 2)));
604 // NB This must happen after the Alpha channel is extracted
605 // imagegammacorrect() removes the alpha channel data in $im - (I think this is a bug in PHP)
606 if ($gamma) {
607 imagegammacorrect($im, $gamma, 2.2);
610 $tempfile_alpha = $this->cache->tempFilename('_tempMskPNG' . md5($file) . random_int(1, 10000) . '.png');
612 $check = @imagepng($imgalpha, $tempfile_alpha);
614 if (!$check) {
615 return $this->imageError($file, $firsttime, 'Failed to create temporary image file (' . $tempfile_alpha . ') parsing PNG image with alpha channel (' . $errpng . ')');
618 imagedestroy($imgalpha);
619 // extract image without alpha channel
620 $imgplain = imagecreatetruecolor($w, $h);
621 imagealphablending($imgplain, false); // mPDF 5.7.2
622 imagecopy($imgplain, $im, 0, 0, 0, 0, $w, $h);
624 // create temp image file
625 $check = @imagepng($imgplain, $tempfile);
626 if (!$check) {
627 return $this->imageError($file, $firsttime, 'Failed to create temporary image file (' . $tempfile . ') parsing PNG image with alpha channel (' . $errpng . ')');
629 imagedestroy($imgplain);
630 // embed mask image
631 $minfo = $this->getImage($tempfile_alpha, false);
632 unlink($tempfile_alpha);
634 if (!$minfo) {
635 return $this->imageError($file, $firsttime, 'Error parsing temporary file (' . $tempfile_alpha . ') created with GD library to parse PNG image');
638 $imgmask = count($this->mpdf->images) + 1;
639 $minfo['cs'] = 'DeviceGray';
640 $minfo['i'] = $imgmask;
641 $this->mpdf->images[$tempfile_alpha] = $minfo;
642 // embed image, masked with previously embedded mask
643 $info = $this->getImage($tempfile, false);
644 unlink($tempfile);
646 if (!$info) {
647 return $this->imageError($file, $firsttime, 'Error parsing temporary file (' . $tempfile . ') created with GD library to parse PNG image');
650 $info['masked'] = $imgmask;
651 if ($ppUx) {
652 $info['set-dpi'] = $ppUx;
654 $info['type'] = 'png';
655 if ($firsttime) {
656 $info['i'] = count($this->mpdf->images) + 1;
657 $info['interpolation'] = $interpolation; // mPDF 6
658 $this->mpdf->images[$file] = $info;
661 return $info;
662 } else { // No alpha/transparency set (but cannot read directly because e.g. bit-depth != 8, interlaced etc)
663 // ICC profile
664 $icc = false;
665 $p = strpos($data, 'iCCP');
666 if ($p && $colspace == "Indexed") { // Cannot have ICC profile and Indexed together
667 $p += 4;
668 $n = $this->fourBytesToInt(substr($data, ($p - 8), 4));
669 $nullsep = strpos(substr($data, $p, 80), chr(0));
670 $icc = substr($data, ($p + $nullsep + 2), ($n - ($nullsep + 2)));
671 $icc = @gzuncompress($icc); // Ignored if fails
672 if ($icc) {
673 if (substr($icc, 36, 4) != 'acsp') {
674 $icc = false;
675 } // invalid ICC profile
676 else {
677 $input = substr($icc, 16, 4);
678 $output = substr($icc, 20, 4);
679 // Ignore Color profiles for conversion to other colorspaces e.g. CMYK/Lab
680 if ($input != 'RGB ' || $output != 'XYZ ') {
681 $icc = false;
685 // Convert to RGB colorspace so can use ICC Profile
686 if ($icc) {
687 imagepalettetotruecolor($im);
688 $colspace = 'DeviceRGB';
689 $channels = 3;
693 if ($gamma) {
694 imagegammacorrect($im, $gamma, 2.2);
697 imagealphablending($im, false);
698 imagesavealpha($im, false);
699 imageinterlace($im, false);
701 $check = @imagepng($im, $tempfile);
702 if (!$check) {
703 return $this->imageError($file, $firsttime, 'Failed to create temporary image file (' . $tempfile . ') parsing PNG image (' . $errpng . ')');
705 imagedestroy($im);
706 $info = $this->getImage($tempfile, false);
707 unlink($tempfile);
708 if (!$info) {
709 return $this->imageError($file, $firsttime, 'Error parsing temporary file (' . $tempfile . ') created with GD library to parse PNG image');
712 if ($ppUx) {
713 $info['set-dpi'] = $ppUx;
715 $info['type'] = 'png';
716 if ($firsttime) {
717 $info['i'] = count($this->mpdf->images) + 1;
718 $info['interpolation'] = $interpolation; // mPDF 6
719 if ($icc) {
720 $info['ch'] = $channels;
721 $info['icc'] = $icc;
723 $this->mpdf->images[$file] = $info;
725 return $info;
728 } else { // PNG image with no need to convert alph channels, bpc <> 8 etc.
729 $parms = '/DecodeParms <</Predictor 15 /Colors ' . $channels . ' /BitsPerComponent ' . $bpc . ' /Columns ' . $w . '>>';
730 //Scan chunks looking for palette, transparency and image data
731 $pal = '';
732 $trns = '';
733 $pngdata = '';
734 $icc = false;
735 $p = 33;
736 do {
737 $n = $this->fourBytesToInt(substr($data, $p, 4));
738 $p += 4;
739 $type = substr($data, $p, 4);
740 $p += 4;
741 if ($type == 'PLTE') {
742 //Read palette
743 $pal = substr($data, $p, $n);
744 $p += $n;
745 $p += 4;
746 } elseif ($type == 'tRNS') {
747 //Read transparency info
748 $t = substr($data, $p, $n);
749 $p += $n;
750 if ($ct == 0) {
751 $trns = [ord(substr($t, 1, 1))];
752 } elseif ($ct == 2) {
753 $trns = [ord(substr($t, 1, 1)), ord(substr($t, 3, 1)), ord(substr($t, 5, 1))];
754 } else {
755 $pos = strpos($t, chr(0));
756 if (is_int($pos)) {
757 $trns = [$pos];
760 $p += 4;
761 } elseif ($type == 'IDAT') {
762 $pngdata.=substr($data, $p, $n);
763 $p += $n;
764 $p += 4;
765 } elseif ($type == 'iCCP') {
766 $nullsep = strpos(substr($data, $p, 80), chr(0));
767 $icc = substr($data, ($p + $nullsep + 2), ($n - ($nullsep + 2)));
768 $icc = @gzuncompress($icc); // Ignored if fails
769 if ($icc) {
770 if (substr($icc, 36, 4) != 'acsp') {
771 $icc = false;
772 } // invalid ICC profile
773 else {
774 $input = substr($icc, 16, 4);
775 $output = substr($icc, 20, 4);
776 // Ignore Color profiles for conversion to other colorspaces e.g. CMYK/Lab
777 if ($input != 'RGB ' || $output != 'XYZ ') {
778 $icc = false;
782 $p += $n;
783 $p += 4;
784 } elseif ($type == 'IEND') {
785 break;
786 } elseif (preg_match('/[a-zA-Z]{4}/', $type)) {
787 $p += $n + 4;
788 } else {
789 return $this->imageError($file, $firsttime, 'Error parsing PNG image data');
791 } while ($n);
792 if (!$pngdata) {
793 return $this->imageError($file, $firsttime, 'Error parsing PNG image data - no IDAT data found');
795 if ($colspace == 'Indexed' && empty($pal)) {
796 return $this->imageError($file, $firsttime, 'Error parsing PNG image data - missing colour palette');
799 if ($colspace == 'Indexed' && $icc) {
800 $icc = false;
801 } // mPDF 6 cannot have ICC profile and Indexed in a PDF document as both use the colorspace tag.
803 $info = ['w' => $w, 'h' => $h, 'cs' => $colspace, 'bpc' => $bpc, 'f' => 'FlateDecode', 'parms' => $parms, 'pal' => $pal, 'trns' => $trns, 'data' => $pngdata, 'ch' => $channels, 'icc' => $icc];
804 $info['type'] = 'png';
805 if ($ppUx) {
806 $info['set-dpi'] = $ppUx;
810 if (!$info) {
811 return $this->imageError($file, $firsttime, 'Error parsing or converting PNG image');
814 if ($firsttime) {
815 $info['i'] = count($this->mpdf->images) + 1;
816 $info['interpolation'] = $interpolation; // mPDF 6
817 $this->mpdf->images[$file] = $info;
820 return $info;
822 } elseif ($type == 'gif') { // GIF
824 if (function_exists('gd_info')) {
825 $gd = gd_info();
826 } else {
827 $gd = [];
830 if (isset($gd['GIF Read Support']) && $gd['GIF Read Support']) {
832 $im = @imagecreatefromstring($data);
833 if ($im) {
834 $tempfile = $this->cache->tempFilename('_tempImgPNG' . md5($file) . random_int(1, 10000) . '.png');
835 imagealphablending($im, false);
836 imagesavealpha($im, false);
837 imageinterlace($im, false);
838 if (!is_writable($tempfile)) {
839 ob_start();
840 $check = @imagepng($im);
841 if (!$check) {
842 return $this->imageError($file, $firsttime, 'Error creating temporary image object whilst using GD library to parse GIF image');
844 $this->mpdf->imageVars['tempImage'] = ob_get_contents();
845 $tempimglnk = 'var:tempImage';
846 ob_end_clean();
847 $info = $this->getImage($tempimglnk, false);
848 if (!$info) {
849 return $this->imageError($file, $firsttime, 'Error parsing temporary file image object created with GD library to parse GIF image');
851 imagedestroy($im);
852 } else {
853 $check = @imagepng($im, $tempfile);
854 if (!$check) {
855 return $this->imageError($file, $firsttime, 'Error creating temporary file (' . $tempfile . ') whilst using GD library to parse GIF image');
857 $info = $this->getImage($tempfile, false);
858 if (!$info) {
859 return $this->imageError($file, $firsttime, 'Error parsing temporary file (' . $tempfile . ') created with GD library to parse GIF image');
861 imagedestroy($im);
862 unlink($tempfile);
864 $info['type'] = 'gif';
865 if ($firsttime) {
866 $info['i'] = count($this->mpdf->images) + 1;
867 $info['interpolation'] = $interpolation; // mPDF 6
868 $this->mpdf->images[$file] = $info;
870 return $info;
871 } else {
872 return $this->imageError($file, $firsttime, 'Error creating GD image file from GIF image');
876 $gif = new Gif();
878 $h = 0;
879 $w = 0;
880 $gif->loadFile($data, 0);
882 if (isset($gif->m_img->m_gih->m_bLocalClr) && $gif->m_img->m_gih->m_bLocalClr) {
883 $nColors = $gif->m_img->m_gih->m_nTableSize;
884 $pal = $gif->m_img->m_gih->m_colorTable->toString();
885 if ((isset($bgColor)) and $bgColor != -1) { // mPDF 5.7.3
886 $bgColor = $gif->m_img->m_gih->m_colorTable->colorIndex($bgColor);
888 $colspace = 'Indexed';
889 } elseif (isset($gif->m_gfh->m_bGlobalClr) && $gif->m_gfh->m_bGlobalClr) {
890 $nColors = $gif->m_gfh->m_nTableSize;
891 $pal = $gif->m_gfh->m_colorTable->toString();
892 if ((isset($bgColor)) and $bgColor != -1) {
893 $bgColor = $gif->m_gfh->m_colorTable->colorIndex($bgColor);
895 $colspace = 'Indexed';
896 } else {
897 $nColors = 0;
898 $bgColor = -1;
899 $colspace = 'DeviceGray';
900 $pal = '';
903 $trns = '';
904 if (isset($gif->m_img->m_bTrans) && $gif->m_img->m_bTrans && ($nColors > 0)) {
905 $trns = [$gif->m_img->m_nTrans];
907 $gifdata = $gif->m_img->m_data;
908 $w = $gif->m_gfh->m_nWidth;
909 $h = $gif->m_gfh->m_nHeight;
910 $gif->ClearData();
912 if ($colspace == 'Indexed' and empty($pal)) {
913 return $this->imageError($file, $firsttime, 'Error parsing GIF image - missing colour palette');
916 if ($this->mpdf->compress) {
917 $gifdata = $this->gzCompress($gifdata);
918 $info = ['w' => $w, 'h' => $h, 'cs' => $colspace, 'bpc' => 8, 'f' => 'FlateDecode', 'pal' => $pal, 'trns' => $trns, 'data' => $gifdata];
919 } else {
920 $info = ['w' => $w, 'h' => $h, 'cs' => $colspace, 'bpc' => 8, 'pal' => $pal, 'trns' => $trns, 'data' => $gifdata];
923 $info['type'] = 'gif';
924 if ($firsttime) {
925 $info['i'] = count($this->mpdf->images) + 1;
926 $info['interpolation'] = $interpolation; // mPDF 6
927 $this->mpdf->images[$file] = $info;
930 return $info;
932 } elseif ($type == 'bmp') {
934 if (empty($this->bmp)) {
935 $this->bmp = new Bmp($this->mpdf);
937 $info = $this->bmp->_getBMPimage($data, $file);
938 if (isset($info['error'])) {
939 return $this->imageError($file, $firsttime, $info['error']);
941 if ($firsttime) {
942 $info['i'] = count($this->mpdf->images) + 1;
943 $info['interpolation'] = $interpolation; // mPDF 6
944 $this->mpdf->images[$file] = $info;
946 return $info;
948 } elseif ($type == 'wmf') {
950 if (empty($this->wmf)) {
951 $this->wmf = new Wmf($this->mpdf, $this->colorConverter);
954 $wmfres = $this->wmf->_getWMFimage($data);
956 if ($wmfres[0] == 0) {
957 if ($wmfres[1]) {
958 return $this->imageError($file, $firsttime, $wmfres[1]);
960 return $this->imageError($file, $firsttime, 'Error parsing WMF image');
963 $info = ['x' => $wmfres[2][0], 'y' => $wmfres[2][1], 'w' => $wmfres[3][0], 'h' => $wmfres[3][1], 'data' => $wmfres[1]];
964 $info['i'] = count($this->mpdf->formobjects) + 1;
965 $info['type'] = 'wmf';
966 $this->mpdf->formobjects[$file] = $info;
968 return $info;
970 } else { // UNKNOWN TYPE - try GD imagecreatefromstring
972 if (function_exists('gd_info')) {
973 $gd = gd_info();
974 } else {
975 $gd = [];
978 if (isset($gd['PNG Support']) && $gd['PNG Support']) {
980 $im = @imagecreatefromstring($data);
982 if (!$im) {
983 return $this->imageError($file, $firsttime, 'Error parsing image file - image type not recognised, and not supported by GD imagecreate');
986 $tempfile = $this->cache->tempFilename('_tempImgPNG' . md5($file) . random_int(1, 10000) . '.png');
988 imagealphablending($im, false);
989 imagesavealpha($im, false);
990 imageinterlace($im, false);
992 $check = @imagepng($im, $tempfile);
994 if (!$check) {
995 return $this->imageError($file, $firsttime, 'Error creating temporary file (' . $tempfile . ') whilst using GD library to parse unknown image type');
998 $info = $this->getImage($tempfile, false);
1000 imagedestroy($im);
1001 unlink($tempfile);
1003 if (!$info) {
1004 return $this->imageError($file, $firsttime, 'Error parsing temporary file (' . $tempfile . ') created with GD library to parse unknown image type');
1007 $info['type'] = 'png';
1008 if ($firsttime) {
1009 $info['i'] = count($this->mpdf->images) + 1;
1010 $info['interpolation'] = $interpolation; // mPDF 6
1011 $this->mpdf->images[$file] = $info;
1014 return $info;
1018 return $this->imageError($file, $firsttime, 'Error parsing image file - image type not recognised');
1021 private function convImage(&$data, $colspace, $targetcs, $w, $h, $dpi, $mask, $gamma_correction = false, $pngcolortype = false)
1023 if ($this->mpdf->PDFA || $this->mpdf->PDFX) {
1024 $mask = false;
1027 $im = @imagecreatefromstring($data);
1028 $info = [];
1029 $bpc = ord(substr($data, 24, 1));
1031 if ($im) {
1032 $imgdata = '';
1033 $mimgdata = '';
1034 $minfo = [];
1036 // mPDF 6 Gamma correction
1037 // Need to extract alpha channel info before imagegammacorrect (which loses the data)
1038 if ($mask) { // i.e. $pngalpha for PNG
1039 // mPDF 6
1040 if ($colspace == 'Indexed') { // generate Alpha channel values from tRNS - only from PNG
1041 //Read transparency info
1042 $transparency = '';
1043 $p = strpos($data, 'tRNS');
1044 if ($p) {
1045 $n = $this->fourBytesToInt(substr($data, ($p - 4), 4));
1046 $transparency = substr($data, ($p + 4), $n);
1047 // ord($transparency{$index}) = the alpha value for that index
1048 // generate alpha channel
1049 for ($ypx = 0; $ypx < $h; ++$ypx) {
1050 for ($xpx = 0; $xpx < $w; ++$xpx) {
1051 $colorindex = imagecolorat($im, $xpx, $ypx);
1052 if ($colorindex >= $n) {
1053 $alpha = 255;
1054 } else {
1055 $alpha = ord($transparency{$colorindex});
1056 } // 0-255
1057 $mimgdata .= chr($alpha);
1061 } elseif ($pngcolortype === 0 || $pngcolortype == 2) { // generate Alpha channel values from tRNS
1062 // Get transparency as array of RGB
1063 $p = strpos($data, 'tRNS');
1064 if ($p) {
1065 $trns = '';
1066 $n = $this->fourBytesToInt(substr($data, ($p - 4), 4));
1067 $t = substr($data, ($p + 4), $n);
1068 if ($colspace == 'DeviceGray') { // ct===0
1069 $trns = [$this->translateValue(substr($t, 0, 2), $bpc)];
1070 } else /* $colspace=='DeviceRGB' */ { // ct==2
1071 $trns = [];
1072 $trns[0] = $this->translateValue(substr($t, 0, 2), $bpc);
1073 $trns[1] = $this->translateValue(substr($t, 2, 2), $bpc);
1074 $trns[2] = $this->translateValue(substr($t, 4, 2), $bpc);
1077 // generate alpha channel
1078 for ($ypx = 0; $ypx < $h; ++$ypx) {
1079 for ($xpx = 0; $xpx < $w; ++$xpx) {
1080 $rgb = imagecolorat($im, $xpx, $ypx);
1081 $r = ($rgb >> 16) & 0xFF;
1082 $g = ($rgb >> 8) & 0xFF;
1083 $b = $rgb & 0xFF;
1084 if ($colspace == 'DeviceGray' && $b == $trns[0]) {
1085 $alpha = 0;
1086 } elseif ($r == $trns[0] && $g == $trns[1] && $b == $trns[2]) {
1087 $alpha = 0;
1088 } // ct==2
1089 else {
1090 $alpha = 255;
1092 $mimgdata .= chr($alpha);
1096 } else {
1097 for ($i = 0; $i < $h; $i++) {
1098 for ($j = 0; $j < $w; $j++) {
1099 $rgb = imagecolorat($im, $j, $i);
1100 $alpha = ($rgb & 0x7F000000) >> 24;
1101 if ($alpha < 127) {
1102 $mimgdata .= chr(255 - ($alpha * 2));
1103 } else {
1104 $mimgdata .= chr(0);
1111 // mPDF 6 Gamma correction
1112 if ($gamma_correction) {
1113 imagegammacorrect($im, $gamma_correction, 2.2);
1116 //Read transparency info
1117 $trns = [];
1118 $trnsrgb = false;
1119 if (!$this->mpdf->PDFA && !$this->mpdf->PDFX && !$mask) { // mPDF 6 added NOT mask
1120 $p = strpos($data, 'tRNS');
1121 if ($p) {
1122 $n = $this->fourBytesToInt(substr($data, ($p - 4), 4));
1123 $t = substr($data, ($p + 4), $n);
1124 if ($colspace == 'DeviceGray') { // ct===0
1125 $trns = [$this->translateValue(substr($t, 0, 2), $bpc)];
1126 } elseif ($colspace == 'DeviceRGB') { // ct==2
1127 $trns[0] = $this->translateValue(substr($t, 0, 2), $bpc);
1128 $trns[1] = $this->translateValue(substr($t, 2, 2), $bpc);
1129 $trns[2] = $this->translateValue(substr($t, 4, 2), $bpc);
1130 $trnsrgb = $trns;
1131 if ($targetcs == 'DeviceCMYK') {
1132 $col = $this->colorModeConverter->rgb2cmyk([3, $trns[0], $trns[1], $trns[2]]);
1133 $c1 = intval($col[1] * 2.55);
1134 $c2 = intval($col[2] * 2.55);
1135 $c3 = intval($col[3] * 2.55);
1136 $c4 = intval($col[4] * 2.55);
1137 $trns = [$c1, $c2, $c3, $c4];
1138 } elseif ($targetcs == 'DeviceGray') {
1139 $c = intval(($trns[0] * .21) + ($trns[1] * .71) + ($trns[2] * .07));
1140 $trns = [$c];
1142 } else { // Indexed
1143 $pos = strpos($t, chr(0));
1144 if (is_int($pos)) {
1145 $pal = imagecolorsforindex($im, $pos);
1146 $r = $pal['red'];
1147 $g = $pal['green'];
1148 $b = $pal['blue'];
1149 $trns = [$r, $g, $b]; // ****
1150 $trnsrgb = $trns;
1151 if ($targetcs == 'DeviceCMYK') {
1152 $col = $this->colorModeConverter->rgb2cmyk([3, $r, $g, $b]);
1153 $c1 = intval($col[1] * 2.55);
1154 $c2 = intval($col[2] * 2.55);
1155 $c3 = intval($col[3] * 2.55);
1156 $c4 = intval($col[4] * 2.55);
1157 $trns = [$c1, $c2, $c3, $c4];
1158 } elseif ($targetcs == 'DeviceGray') {
1159 $c = intval(($r * .21) + ($g * .71) + ($b * .07));
1160 $trns = [$c];
1167 for ($i = 0; $i < $h; $i++) {
1168 for ($j = 0; $j < $w; $j++) {
1169 $rgb = imagecolorat($im, $j, $i);
1170 $r = ($rgb >> 16) & 0xFF;
1171 $g = ($rgb >> 8) & 0xFF;
1172 $b = $rgb & 0xFF;
1173 if ($colspace == 'Indexed') {
1174 $pal = imagecolorsforindex($im, $rgb);
1175 $r = $pal['red'];
1176 $g = $pal['green'];
1177 $b = $pal['blue'];
1180 if ($targetcs == 'DeviceCMYK') {
1181 $col = $this->colorModeConverter->rgb2cmyk([3, $r, $g, $b]);
1182 $c1 = intval($col[1] * 2.55);
1183 $c2 = intval($col[2] * 2.55);
1184 $c3 = intval($col[3] * 2.55);
1185 $c4 = intval($col[4] * 2.55);
1186 if ($trnsrgb) {
1187 // original pixel was not set as transparent but processed color does match
1188 if ($trnsrgb != [$r, $g, $b] && $trns == [$c1, $c2, $c3, $c4]) {
1189 if ($c4 == 0) {
1190 $c4 = 1;
1191 } else {
1192 $c4--;
1196 $imgdata .= chr($c1) . chr($c2) . chr($c3) . chr($c4);
1197 } elseif ($targetcs == 'DeviceGray') {
1198 $c = intval(($r * .21) + ($g * .71) + ($b * .07));
1199 if ($trnsrgb) {
1200 // original pixel was not set as transparent but processed color does match
1201 if ($trnsrgb != [$r, $g, $b] && $trns == [$c]) {
1202 if ($c == 0) {
1203 $c = 1;
1204 } else {
1205 $c--;
1209 $imgdata .= chr($c);
1210 } elseif ($targetcs == 'DeviceRGB') {
1211 $imgdata .= chr($r) . chr($g) . chr($b);
1216 if ($targetcs == 'DeviceGray') {
1217 $ncols = 1;
1218 } elseif ($targetcs == 'DeviceRGB') {
1219 $ncols = 3;
1220 } elseif ($targetcs == 'DeviceCMYK') {
1221 $ncols = 4;
1224 $imgdata = $this->gzCompress($imgdata);
1225 $info = ['w' => $w, 'h' => $h, 'cs' => $targetcs, 'bpc' => 8, 'f' => 'FlateDecode', 'data' => $imgdata, 'type' => 'png',
1226 'parms' => '/DecodeParms <</Colors ' . $ncols . ' /BitsPerComponent 8 /Columns ' . $w . '>>'];
1227 if ($dpi) {
1228 $info['set-dpi'] = $dpi;
1230 if ($mask) {
1231 $mimgdata = $this->gzCompress($mimgdata);
1232 $minfo = ['w' => $w, 'h' => $h, 'cs' => 'DeviceGray', 'bpc' => 8, 'f' => 'FlateDecode', 'data' => $mimgdata, 'type' => 'png',
1233 'parms' => '/DecodeParms <</Colors ' . $ncols . ' /BitsPerComponent 8 /Columns ' . $w . '>>'];
1234 if ($dpi) {
1235 $minfo['set-dpi'] = $dpi;
1237 $tempfile = '_tempImgPNG' . md5($data) . random_int(1, 10000) . '.png';
1238 $imgmask = count($this->mpdf->images) + 1;
1239 $minfo['i'] = $imgmask;
1240 $this->mpdf->images[$tempfile] = $minfo;
1241 $info['masked'] = $imgmask;
1242 } elseif ($trns) {
1243 $info['trns'] = $trns;
1245 imagedestroy($im);
1247 return $info;
1250 private function jpgHeaderFromString(&$data)
1252 $p = 4;
1253 $p += $this->twoBytesToInt(substr($data, $p, 2)); // Length of initial marker block
1254 $marker = substr($data, $p, 2);
1256 while ($marker != chr(255) . chr(192) && $marker != chr(255) . chr(194) && $p < strlen($data)) {
1257 // Start of frame marker (FFC0) or (FFC2) mPDF 4.4.004
1258 $p += ($this->twoBytesToInt(substr($data, $p + 2, 2))) + 2; // Length of marker block
1259 $marker = substr($data, $p, 2);
1262 if ($marker != chr(255) . chr(192) && $marker != chr(255) . chr(194)) {
1263 return false;
1265 return substr($data, $p + 2, 10);
1268 private function jpgDataFromHeader($hdr)
1270 $bpc = ord(substr($hdr, 2, 1));
1272 if (!$bpc) {
1273 $bpc = 8;
1276 $h = $this->twoBytesToInt(substr($hdr, 3, 2));
1277 $w = $this->twoBytesToInt(substr($hdr, 5, 2));
1279 $channels = ord(substr($hdr, 7, 1));
1281 if ($channels == 3) {
1282 $colspace = 'DeviceRGB';
1283 } elseif ($channels == 4) {
1284 $colspace = 'DeviceCMYK';
1285 } else {
1286 $colspace = 'DeviceGray';
1289 return [$w, $h, $colspace, $bpc, $channels];
1293 * Corrects 2-byte integer to 8-bit depth value
1294 * If original image is bpc != 8, tRNS will be in this bpc
1295 * $im from imagecreatefromstring will always be in bpc=8
1296 * So why do we only need to correct 16-bit tRNS and NOT 2 or 4-bit???
1298 private function translateValue($s, $bpc)
1300 $n = $this->twoBytesToInt($s);
1302 if ($bpc == 16) {
1303 $n = ($n >> 8);
1306 //elseif ($bpc==4) { $n = ($n << 2); }
1307 //elseif ($bpc==2) { $n = ($n << 4); }
1309 return $n;
1313 * Read a 4-byte integer from string
1315 private function fourBytesToInt($s)
1317 return (ord($s[0]) << 24) + (ord($s[1]) << 16) + (ord($s[2]) << 8) + ord($s[3]);
1321 * Equivalent to _get_ushort
1322 * Read a 2-byte integer from string
1324 private function twoBytesToInt($s)
1326 return (ord(substr($s, 0, 1)) << 8) + ord(substr($s, 1, 1));
1329 private function gzCompress($data)
1331 if (!function_exists('gzcompress')) {
1332 throw new \Mpdf\MpdfException('gzcompress is not available. install ext-zlib extension.');
1335 return gzcompress($data);
1339 * Throw an exception and save re-trying image URL's which have already failed
1341 private function imageError($file, $firsttime, $msg)
1343 $this->failedImages[$file] = true;
1345 if ($firsttime && ($this->mpdf->showImageErrors || $this->mpdf->debug)) {
1346 throw new \Mpdf\MpdfImageException(sprintf('%s (%s)', $msg, $file));
1349 $this->logger->warning(sprintf('%s (%s)', $msg, $file), ['context' => LogContext::IMAGES]);
1353 * @since mPDF 5.7.4
1354 * @param string $url
1355 * @return string
1357 private function urldecodeParts($url)
1359 $file = $url;
1360 $query = '';
1361 if (preg_match('/[?]/', $url)) {
1362 $bits = preg_split('/[?]/', $url, 2);
1363 $file = $bits[0];
1364 $query = '?' . $bits[1];
1366 $file = rawurldecode($file);
1367 $query = urldecode($query);
1369 return $file . $query;