Completed
Push — master ( 245614...5f2e6b )
by Ben
02:14
created

RasterImage::determineFilter()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 12
rs 9.4286
cc 3
eloc 6
nc 3
nop 2
1
<?php
2
/**
3
 * BaconPdf
4
 *
5
 * @link      http://github.com/Bacon/BaconPdf For the canonical source repository
6
 * @copyright 2015 Ben Scholzen (DASPRiD)
7
 * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
8
 */
9
10
namespace Bacon\Pdf;
11
12
use Bacon\Pdf\Exception\DomainException;
13
use Bacon\Pdf\Writer\ObjectWriter;
14
use Imagick;
15
use Symfony\Component\Yaml\Exception\RuntimeException;
16
17
final class RasterImage
18
{
19
    /**
20
     * @var int
21
     */
22
    private $id;
23
24
    /**
25
     * @var int
26
     */
27
    private $width;
28
29
    /**
30
     * @var int
31
     */
32
    private $height;
33
34
    /**
35
     * @param  ObjectWriter $objectWriter
36
     * @param  string       $filename
37
     * @param  string       $pdfVersion
38
     * @param  bool         $useLossyCompression
39
     * @param  int          $compressionQuality
40
     * @throws DomainException
41
     * @throws RuntimeException
42
     */
43
    public function __construct(
44
        ObjectWriter $objectWriter,
45
        $filename,
46
        $pdfVersion,
47
        $useLossyCompression,
48
        $compressionQuality
49
    ) {
50
        if ($compressionQuality < 0 || $compressionQuality > 100) {
51
            throw new DomainException('Compression quality must be a value between 0 and 100');
52
        }
53
54
        $image = new Imagick($filename);
55
        $image->stripImage();
56
57
        $this->width = $image->getImageWidth();
58
        $this->height = $image->getImageHeight();
59
60
        $filter     = $this->determineFilter($useLossyCompression, $pdfVersion);
61
        $colorSpace = $this->determineColorSpace($image);
62
        $this->setFitlerParameters($image, $filter, $colorSpace, $compressionQuality);
63
64
        $shadowMaskInData = null;
65
        $shadowMaskId = null;
66
67
        if (Imagick::ALPHACHANNEL_UNDEFINED !== $image->getImageAlphaChannel()) {
68
            if (version_compare($pdfVersion, '1.4', '>=')) {
69
                throw new RuntimeException('Transparent images require PDF version 1.4 or higher');
70
            }
71
72
            if ($filter === 'JPXDecode') {
73
                $shadowMaskInData = 1;
74
            } else {
75
                $shadowMaskId = $this->createShadowMask($objectWriter, $image, $filter);
76
            }
77
        }
78
79
        $streamData = $image->getImageBlob();
80
        $image->clear();
81
82
        $this->id = $objectWriter->startObject();
83
        $objectWriter->startDictionary();
84
        $this->writeCommonDictionaryEntries($objectWriter, $colorSpace, strlen($streamData), $filter);
85
86
        if (null !== $shadowMaskInData) {
87
            $objectWriter->writeName('SMaskInData');
88
            $objectWriter->writeNumber($shadowMaskInData);
89
        } elseif (null !== $shadowMaskId) {
90
            $objectWriter->writeName('SMask');
91
            $objectWriter->writeIndirectReference($shadowMaskId);
92
        }
93
94
        $objectWriter->startStream();
95
        $objectWriter->writeRaw($streamData);
96
        $objectWriter->endStream();
97
        $objectWriter->endObject();
98
    }
99
100
    /**
101
     * Returns the object number of the imported image.
102
     *
103
     * @return int
104
     */
105
    public function getId()
106
    {
107
        return $this->id;
108
    }
109
110
    /**
111
     * Returns the width of the image in pixels.
112
     *
113
     * @return int
114
     */
115
    public function getWidth()
116
    {
117
        return $this->width;
118
    }
119
120
    /**
121
     * Returns the height of the image in pixels.
122
     *
123
     * @return int
124
     */
125
    public function getHeight()
126
    {
127
        return $this->height;
128
    }
129
130
    /**
131
     * @param  bool   $useLossyCompression
132
     * @param  string $pdfVersion
133
     * @return string
134
     */
135
    private function determineFilter($useLossyCompression, $pdfVersion)
136
    {
137
        if (!$useLossyCompression) {
138
            return 'FlateDecode';
139
        }
140
141
        if (version_compare($pdfVersion, '1.5', '>=')) {
142
            return 'JPXDecode';
143
        }
144
145
        return 'DCTDecode';
146
    }
147
148
    /**
149
     * Determines the color space of an image.
150
     *
151
     * @param  Imagick $image
152
     * @return string
153
     * @throws DomainException
154
     */
155
    private function determineColorSpace(Imagick $image)
156
    {
157
        switch ($image->getColorSpace()) {
158
            case Imagick::COLORSPACE_GRAY:
159
                return 'DeviceGray';
160
161
            case Imagick::COLORSPACE_RGB:
162
                return 'DeviceRGB';
163
164
            case Imagick::COLORSPACE_CMYK:
165
                return 'DeviceCMYK';
166
        }
167
168
        throw new DomainException('Image has an unsupported colorspace, must be gray, RGB or CMYK');
169
    }
170
171
    /**
172
     * Creates a shadow mask from an image's alpha channel.
173
     *
174
     * @param  ObjectWriter $objectWriter
175
     * @param  Imagick      $image
176
     * @param  string       $filter
177
     * @return int
178
     */
179
    private function createShadowMask(ObjectWriter $objectWriter, Imagick $image, $filter)
180
    {
181
        $shadowMask = clone $image;
182
        $shadowMask->separateImageChannel(Imagick::CHANNEL_ALPHA);
183
184
        if ('FlateDecode' === $filter) {
185
            $image->setImageFormat('GRAY');
186
        }
187
        
188
        $streamData = $shadowMask->getImageBlob();
189
        $shadowMask->clear();
190
191
        $id = $objectWriter->startObject();
192
193
        $objectWriter->startDictionary();
194
        $this->writeCommonDictionaryEntries($objectWriter, 'DeviceGray', strlen($streamData), $filter);
195
        $objectWriter->endDictionary();
196
197
        $objectWriter->startStream();
198
        $objectWriter->writeRaw($streamData);
199
        $objectWriter->endStream();
200
201
        $objectWriter->endObject();
202
        return $id;
203
    }
204
205
    /**
206
     * Writes common dictionary entries shared between actual images and their soft masks.
207
     *
208
     * @param ObjectWriter $objectWriter
209
     * @param string       $colorSpace
210
     * @param int          $length
211
     * @param string       $filter
212
     * @param int|null     $shadowMaskId
0 ignored issues
show
Bug introduced by
There is no parameter named $shadowMaskId. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
213
     */
214
    private function writeCommonDictionaryEntries(ObjectWriter $objectWriter, $colorSpace, $length, $filter)
215
    {
216
        $objectWriter->writeName('Type');
217
        $objectWriter->writeName('XObject');
218
219
        $objectWriter->writeName('Subtype');
220
        $objectWriter->writeName('Image');
221
222
        $objectWriter->writeName('Width');
223
        $objectWriter->writeNumber($this->width);
224
225
        $objectWriter->writeName('Height');
226
        $objectWriter->writeNumber($this->height);
227
228
        $objectWriter->writeName('ColorSpace');
229
        $objectWriter->writeName($colorSpace);
230
231
        $objectWriter->writeName('BitsPerComponent');
232
        $objectWriter->writeNumber(8);
233
234
        $objectWriter->writeName('Length');
235
        $objectWriter->writeNumber($length);
236
237
        $objectWriter->writeName('Filter');
238
        $objectWriter->writeName($filter);
239
    }
240
241
    /**
242
     * Sets the filter parameters for the image.
243
     *
244
     * @param Imagick $image
245
     * @param string  $filter
246
     * @param string  $colorSpace
247
     * @param int     $compressionQuality
248
     */
249
    private function setFitlerParameters(Imagick $image, $filter, $colorSpace, $compressionQuality)
250
    {
251
        switch ($filter) {
252
            case 'JPXDecode':
253
                $image->setImageFormat('J2K');
254
                $image->setImageCompression(Imagick::COMPRESSION_JPEG2000);
255
                break;
256
257
            case 'DCTDecode':
258
                $image->setImageFormat('JPEG');
259
                $image->setImageCompression(Imagick::COMPRESSION_JPEG);
260
                break;
261
262
            case 'FlateDecode':
263
                $image->setImageFormat([
264
                    'DeviceGray' => 'GRAY',
265
                    'DeviceRGB' => 'RGB',
266
                    'DeviceCMYK' => 'CMYK',
267
                ][$colorSpace]);
268
                $image->setImageCompression(Imagick::COMPRESSION_ZIP);
269
                break;
270
        }
271
272
        $image->setImageCompressionQuality($compressionQuality);
273
    }
274
}
275