|
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 |
|
|
|
|
|
|
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
|
|
|
|
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
$italyis not defined by the methodfinale(...).The most likely cause is that the parameter was removed, but the annotation was not.