1<?php declare(strict_types=1); 2 3namespace PrinsFrank\PdfParser\Document\Image; 4 5use PrinsFrank\PdfParser\Document\Document; 6use PrinsFrank\PdfParser\Document\Image\ColorSpace\ColorSpace; 7use PrinsFrank\PdfParser\Document\Image\ColorSpace\Components; 8use PrinsFrank\PdfParser\Exception\ParseFailureException; 9use PrinsFrank\PdfParser\Stream\InMemoryStream; 10use PrinsFrank\PdfParser\Stream\Stream; 11 12class RasterizedImage { 13 /** 14 * @internal 15 * 16 * @param int<1, max> $width 17 * @param int<1, max> $height 18 * @throws ParseFailureException 19 */ 20 public static function toPNG(ColorSpace $colorSpace, int $width, int $height, int $bitsPerComponent, Stream $content, Document $document): Stream { 21 $image = imagecreatetruecolor($width, $height); 22 if ($image === false) { 23 throw new ParseFailureException('Unable to create image'); 24 } 25 26 if ($bitsPerComponent === 1) { 27 $streamLength = $content->getSizeInBytes(); 28 if ($streamLength < ceil($width * $height * $bitsPerComponent / 8)) { 29 throw new ParseFailureException('Stream content is smaller than expected'); 30 } 31 32 $byteIndex = $bitsRemaining = 0; 33 $currentByte = null; 34 for ($y = 0; $y < $height; $y++) { 35 for ($x = 0; $x < $width; $x++) { 36 if ($bitsRemaining === 0) { 37 $currentByte = ord($content->read($byteIndex, 1)); 38 $bitsRemaining = 8; 39 $byteIndex++; 40 } 41 42 $bitPosition = --$bitsRemaining; 43 $bit = ($currentByte >> $bitPosition) & 1; 44 if (($color = $bit === 0 ? imagecolorallocate($image, 0, 0, 0) : imagecolorallocate($image, 255, 255, 255)) === false) { 45 throw new ParseFailureException('Unable to allocate color'); 46 } 47 48 imagesetpixel($image, $x, $y, $color); 49 } 50 51 $endOfRowBits = $width % 8; 52 if ($endOfRowBits !== 0) { 53 $bitsRemaining = max(0, $bitsRemaining - (8 - $endOfRowBits)); 54 } 55 } 56 } elseif ($bitsPerComponent === 8) { 57 $pixelIndex = 0; 58 for ($y = 0; $y < $height; $y++) { 59 for ($x = 0; $x < $width; $x++) { 60 if ($colorSpace->isIndexed && $colorSpace->LUTObj !== null) { 61 $indexInLUT = ord($content->read($pixelIndex, 1)); 62 if ($indexInLUT > $colorSpace->maxIndexLUT) { 63 throw new ParseFailureException('Index in LUT is too large'); 64 } 65 66 $color = match ($colorSpace->getComponents($document)) { 67 Components::RGB => imagecolorallocate($image, ord($colorSpace->LUTObj->getStream()->read($indexInLUT, 1)), ord($colorSpace->LUTObj->getStream()->read($indexInLUT + 1, 1)), ord($colorSpace->LUTObj->getStream()->read($indexInLUT + 2, 1))), 68 Components::Gray => imagecolorallocate($image, $value = ord($colorSpace->LUTObj->getStream()->read($indexInLUT, 1)), $value, $value), 69 Components::CMYK => imagecolorallocate( 70 $image, 71 min(255, max(0, (int) (255 * (1 - (ord($colorSpace->LUTObj->getStream()->read($indexInLUT, 1)) / 255)) * (1 - (ord($colorSpace->LUTObj->getStream()->read($indexInLUT + 3, 1)) / 255))))), 72 min(255, max(0, (int) (255 * (1 - (ord($colorSpace->LUTObj->getStream()->read($indexInLUT + 1, 1)) / 255)) * (1 - (ord($colorSpace->LUTObj->getStream()->read($indexInLUT + 3, 1)) / 255))))), 73 min(255, max(0, (int) (255 * (1 - (ord($colorSpace->LUTObj->getStream()->read($indexInLUT + 2, 1)) / 255)) * (1 - (ord($colorSpace->LUTObj->getStream()->read($indexInLUT + 3, 1)) / 255))))), 74 ), 75 }; 76 $pixelIndex++; 77 } else { 78 $color = match ($colorSpace->getComponents($document)) { 79 Components::RGB => imagecolorallocate($image, ord($content->read($pixelIndex, 1)), ord($content->read($pixelIndex + 1, 1)), ord($content->read($pixelIndex + 2, 1))), 80 Components::Gray => imagecolorallocate($image, $value = ord($content->read($pixelIndex, 1)), $value, $value), 81 Components::CMYK => imagecolorallocate( 82 $image, 83 min(255, max(0, (int) (255 * (1 - (ord($content->read($pixelIndex, 1)) / 255)) * (1 - (ord($content->read($pixelIndex + 3, 1)) / 255))))), 84 min(255, max(0, (int) (255 * (1 - (ord($content->read($pixelIndex + 1, 1)) / 255)) * (1 - (ord($content->read($pixelIndex + 3, 1)) / 255))))), 85 min(255, max(0, (int) (255 * (1 - (ord($content->read($pixelIndex + 2, 1)) / 255)) * (1 - (ord($content->read($pixelIndex + 3, 1)) / 255))))), 86 ), 87 }; 88 $pixelIndex += $colorSpace->getComponents($document)->value; 89 } 90 91 if ($color === false) { 92 throw new ParseFailureException('Unable to allocate color'); 93 } 94 95 imagesetpixel($image, $x, $y, $color); 96 } 97 } 98 } else { 99 throw new ParseFailureException(sprintf('Unsupported BitsPerComponent %d', $bitsPerComponent)); 100 } 101 102 ob_start(); 103 imagepng($image); 104 $imageContent = ob_get_clean(); 105 if ($imageContent === false) { 106 throw new ParseFailureException('Unable to decode image'); 107 } 108 109 return new InMemoryStream($imageContent); 110 } 111} 112