xref: /plugin/totext/vendor/prinsfrank/pdfparser/src/Document/Image/RasterizedImage.php (revision b4ecfc82b02c9a76205381a28b47006474ccf569)
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