Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
Png.php
Go to the documentation of this file.
1 <?php
24 #require_once 'Zend/Pdf/Element/Array.php';
25 #require_once 'Zend/Pdf/Element/Dictionary.php';
26 #require_once 'Zend/Pdf/Element/Name.php';
27 #require_once 'Zend/Pdf/Element/Numeric.php';
28 #require_once 'Zend/Pdf/Element/String/Binary.php';
29 
30 
32 #require_once 'Zend/Pdf/Resource/Image.php';
33 
42 {
47 
48  const PNG_FILTER_NONE = 0;
49  const PNG_FILTER_SUB = 1;
50  const PNG_FILTER_UP = 2;
51  const PNG_FILTER_AVERAGE = 3;
52  const PNG_FILTER_PAETH = 4;
53 
56 
57  const PNG_CHANNEL_GRAY = 0;
58  const PNG_CHANNEL_RGB = 2;
62 
63  protected $_width;
64  protected $_height;
65  protected $_imageProperties;
66 
79  public function __construct($imageFileName)
80  {
81  if (($imageFile = @fopen($imageFileName, 'rb')) === false ) {
82  #require_once 'Zend/Pdf/Exception.php';
83  throw new Zend_Pdf_Exception( "Can not open '$imageFileName' file for reading." );
84  }
85 
86  parent::__construct();
87 
88  //Check if the file is a PNG
89  fseek($imageFile, 1, SEEK_CUR); //First signature byte (%)
90  if ('PNG' != fread($imageFile, 3)) {
91  #require_once 'Zend/Pdf/Exception.php';
92  throw new Zend_Pdf_Exception('Image is not a PNG');
93  }
94  fseek($imageFile, 12, SEEK_CUR); //Signature bytes (Includes the IHDR chunk) IHDR processed linerarly because it doesnt contain a variable chunk length
95  $wtmp = unpack('Ni',fread($imageFile, 4)); //Unpack a 4-Byte Long
96  $width = $wtmp['i'];
97  $htmp = unpack('Ni',fread($imageFile, 4));
98  $height = $htmp['i'];
99  $bits = ord(fread($imageFile, 1)); //Higher than 8 bit depths are only supported in later versions of PDF.
100  $color = ord(fread($imageFile, 1));
101 
102  $compression = ord(fread($imageFile, 1));
103  $prefilter = ord(fread($imageFile,1));
104 
105  if (($interlacing = ord(fread($imageFile,1))) != Zend_Pdf_Resource_Image_Png::PNG_INTERLACING_DISABLED) {
106  #require_once 'Zend/Pdf/Exception.php';
107  throw new Zend_Pdf_Exception( "Only non-interlaced images are currently supported." );
108  }
109 
110  $this->_width = $width;
111  $this->_height = $height;
112  $this->_imageProperties = array();
113  $this->_imageProperties['bitDepth'] = $bits;
114  $this->_imageProperties['pngColorType'] = $color;
115  $this->_imageProperties['pngFilterType'] = $prefilter;
116  $this->_imageProperties['pngCompressionType'] = $compression;
117  $this->_imageProperties['pngInterlacingType'] = $interlacing;
118 
119  fseek($imageFile, 4, SEEK_CUR); //4 Byte Ending Sequence
120  $imageData = '';
121 
122  /*
123  * The following loop processes PNG chunks. 4 Byte Longs are packed first give the chunk length
124  * followed by the chunk signature, a four byte code. IDAT and IEND are manditory in any PNG.
125  */
126  while (!feof($imageFile)) {
127  $chunkLengthBytes = fread($imageFile, 4);
128  if ($chunkLengthBytes === false) {
129  #require_once 'Zend/Pdf/Exception.php';
130  throw new Zend_Pdf_Exception('Error ocuured while image file reading.');
131  }
132 
133  $chunkLengthtmp = unpack('Ni', $chunkLengthBytes);
134  $chunkLength = $chunkLengthtmp['i'];
135  $chunkType = fread($imageFile, 4);
136  switch($chunkType) {
137  case 'IDAT': //Image Data
138  /*
139  * Reads the actual image data from the PNG file. Since we know at this point that the compression
140  * strategy is the default strategy, we also know that this data is Zip compressed. We will either copy
141  * the data directly to the PDF and provide the correct FlateDecode predictor, or decompress the data
142  * decode the filters and output the data as a raw pixel map.
143  */
144  $imageData .= fread($imageFile, $chunkLength);
145  fseek($imageFile, 4, SEEK_CUR);
146  break;
147 
148  case 'PLTE': //Palette
149  $paletteData = fread($imageFile, $chunkLength);
150  fseek($imageFile, 4, SEEK_CUR);
151  break;
152 
153  case 'tRNS': //Basic (non-alpha channel) transparency.
154  $trnsData = fread($imageFile, $chunkLength);
155  switch ($color) {
157  $baseColor = ord(substr($trnsData, 1, 1));
158  $transparencyData = array(new Zend_Pdf_Element_Numeric($baseColor),
159  new Zend_Pdf_Element_Numeric($baseColor));
160  break;
161 
163  $red = ord(substr($trnsData,1,1));
164  $green = ord(substr($trnsData,3,1));
165  $blue = ord(substr($trnsData,5,1));
166  $transparencyData = array(new Zend_Pdf_Element_Numeric($red),
167  new Zend_Pdf_Element_Numeric($red),
168  new Zend_Pdf_Element_Numeric($green),
169  new Zend_Pdf_Element_Numeric($green),
170  new Zend_Pdf_Element_Numeric($blue),
171  new Zend_Pdf_Element_Numeric($blue));
172  break;
173 
175  //Find the first transparent color in the index, we will mask that. (This is a bit of a hack. This should be a SMask and mask all entries values).
176  if(($trnsIdx = strpos($trnsData, "\0")) !== false) {
177  $transparencyData = array(new Zend_Pdf_Element_Numeric($trnsIdx),
178  new Zend_Pdf_Element_Numeric($trnsIdx));
179  }
180  break;
181 
183  // Fall through to the next case
184 
186  #require_once 'Zend/Pdf/Exception.php';
187  throw new Zend_Pdf_Exception( "tRNS chunk illegal for Alpha Channel Images" );
188  break;
189  }
190  fseek($imageFile, 4, SEEK_CUR); //4 Byte Ending Sequence
191  break;
192 
193  case 'IEND';
194  break 2; //End the loop too
195 
196  default:
197  fseek($imageFile, $chunkLength + 4, SEEK_CUR); //Skip the section
198  break;
199  }
200  }
201  fclose($imageFile);
202 
203  $compressed = true;
204  $imageDataTmp = '';
205  $smaskData = '';
206  switch ($color) {
208  $colorSpace = new Zend_Pdf_Element_Name('DeviceRGB');
209  break;
210 
212  $colorSpace = new Zend_Pdf_Element_Name('DeviceGray');
213  break;
214 
216  if(empty($paletteData)) {
217  #require_once 'Zend/Pdf/Exception.php';
218  throw new Zend_Pdf_Exception( "PNG Corruption: No palette data read for indexed type PNG." );
219  }
220  $colorSpace = new Zend_Pdf_Element_Array();
221  $colorSpace->items[] = new Zend_Pdf_Element_Name('Indexed');
222  $colorSpace->items[] = new Zend_Pdf_Element_Name('DeviceRGB');
223  $colorSpace->items[] = new Zend_Pdf_Element_Numeric((strlen($paletteData)/3-1));
224  $paletteObject = $this->_objectFactory->newObject(new Zend_Pdf_Element_String_Binary($paletteData));
225  $colorSpace->items[] = $paletteObject;
226  break;
227 
229  /*
230  * To decode PNG's with alpha data we must create two images from one. One image will contain the Gray data
231  * the other will contain the Gray transparency overlay data. The former will become the object data and the latter
232  * will become the Shadow Mask (SMask).
233  */
234  if($bits > 8) {
235  #require_once 'Zend/Pdf/Exception.php';
236  throw new Zend_Pdf_Exception("Alpha PNGs with bit depth > 8 are not yet supported");
237  }
238 
239  $colorSpace = new Zend_Pdf_Element_Name('DeviceGray');
240 
241  #require_once 'Zend/Pdf/ElementFactory.php';
242  $decodingObjFactory = Zend_Pdf_ElementFactory::createFactory(1);
243  $decodingStream = $decodingObjFactory->newStreamObject($imageData);
244  $decodingStream->dictionary->Filter = new Zend_Pdf_Element_Name('FlateDecode');
245  $decodingStream->dictionary->DecodeParms = new Zend_Pdf_Element_Dictionary();
246  $decodingStream->dictionary->DecodeParms->Predictor = new Zend_Pdf_Element_Numeric(15);
247  $decodingStream->dictionary->DecodeParms->Columns = new Zend_Pdf_Element_Numeric($width);
248  $decodingStream->dictionary->DecodeParms->Colors = new Zend_Pdf_Element_Numeric(2); //GreyAlpha
249  $decodingStream->dictionary->DecodeParms->BitsPerComponent = new Zend_Pdf_Element_Numeric($bits);
250  $decodingStream->skipFilters();
251 
252  $pngDataRawDecoded = $decodingStream->value;
253 
254  //Iterate every pixel and copy out gray data and alpha channel (this will be slow)
255  for($pixel = 0, $pixelcount = ($width * $height); $pixel < $pixelcount; $pixel++) {
256  $imageDataTmp .= $pngDataRawDecoded[($pixel*2)];
257  $smaskData .= $pngDataRawDecoded[($pixel*2)+1];
258  }
259  $compressed = false;
260  $imageData = $imageDataTmp; //Overwrite image data with the gray channel without alpha
261  break;
262 
264  /*
265  * To decode PNG's with alpha data we must create two images from one. One image will contain the RGB data
266  * the other will contain the Gray transparency overlay data. The former will become the object data and the latter
267  * will become the Shadow Mask (SMask).
268  */
269  if($bits > 8) {
270  #require_once 'Zend/Pdf/Exception.php';
271  throw new Zend_Pdf_Exception("Alpha PNGs with bit depth > 8 are not yet supported");
272  }
273 
274  $colorSpace = new Zend_Pdf_Element_Name('DeviceRGB');
275 
276  #require_once 'Zend/Pdf/ElementFactory.php';
277  $decodingObjFactory = Zend_Pdf_ElementFactory::createFactory(1);
278  $decodingStream = $decodingObjFactory->newStreamObject($imageData);
279  $decodingStream->dictionary->Filter = new Zend_Pdf_Element_Name('FlateDecode');
280  $decodingStream->dictionary->DecodeParms = new Zend_Pdf_Element_Dictionary();
281  $decodingStream->dictionary->DecodeParms->Predictor = new Zend_Pdf_Element_Numeric(15);
282  $decodingStream->dictionary->DecodeParms->Columns = new Zend_Pdf_Element_Numeric($width);
283  $decodingStream->dictionary->DecodeParms->Colors = new Zend_Pdf_Element_Numeric(4); //RGBA
284  $decodingStream->dictionary->DecodeParms->BitsPerComponent = new Zend_Pdf_Element_Numeric($bits);
285  $decodingStream->skipFilters();
286 
287  $pngDataRawDecoded = $decodingStream->value;
288 
289  //Iterate every pixel and copy out rgb data and alpha channel (this will be slow)
290  for($pixel = 0, $pixelcount = ($width * $height); $pixel < $pixelcount; $pixel++) {
291  $imageDataTmp .= $pngDataRawDecoded[($pixel*4)+0] . $pngDataRawDecoded[($pixel*4)+1] . $pngDataRawDecoded[($pixel*4)+2];
292  $smaskData .= $pngDataRawDecoded[($pixel*4)+3];
293  }
294 
295  $compressed = false;
296  $imageData = $imageDataTmp; //Overwrite image data with the RGB channel without alpha
297  break;
298 
299  default:
300  #require_once 'Zend/Pdf/Exception.php';
301  throw new Zend_Pdf_Exception( "PNG Corruption: Invalid color space." );
302  }
303 
304  if(empty($imageData)) {
305  #require_once 'Zend/Pdf/Exception.php';
306  throw new Zend_Pdf_Exception( "Corrupt PNG Image. Mandatory IDAT chunk not found." );
307  }
308 
309  $imageDictionary = $this->_resource->dictionary;
310  if(!empty($smaskData)) {
311  /*
312  * Includes the Alpha transparency data as a Gray Image, then assigns the image as the Shadow Mask for the main image data.
313  */
314  $smaskStream = $this->_objectFactory->newStreamObject($smaskData);
315  $smaskStream->dictionary->Type = new Zend_Pdf_Element_Name('XObject');
316  $smaskStream->dictionary->Subtype = new Zend_Pdf_Element_Name('Image');
317  $smaskStream->dictionary->Width = new Zend_Pdf_Element_Numeric($width);
318  $smaskStream->dictionary->Height = new Zend_Pdf_Element_Numeric($height);
319  $smaskStream->dictionary->ColorSpace = new Zend_Pdf_Element_Name('DeviceGray');
320  $smaskStream->dictionary->BitsPerComponent = new Zend_Pdf_Element_Numeric($bits);
321  $imageDictionary->SMask = $smaskStream;
322 
323  // Encode stream with FlateDecode filter
324  $smaskStreamDecodeParms = array();
325  $smaskStreamDecodeParms['Predictor'] = new Zend_Pdf_Element_Numeric(15);
326  $smaskStreamDecodeParms['Columns'] = new Zend_Pdf_Element_Numeric($width);
327  $smaskStreamDecodeParms['Colors'] = new Zend_Pdf_Element_Numeric(1);
328  $smaskStreamDecodeParms['BitsPerComponent'] = new Zend_Pdf_Element_Numeric(8);
329  $smaskStream->dictionary->DecodeParms = new Zend_Pdf_Element_Dictionary($smaskStreamDecodeParms);
330  $smaskStream->dictionary->Filter = new Zend_Pdf_Element_Name('FlateDecode');
331  }
332 
333  if(!empty($transparencyData)) {
334  //This is experimental and not properly tested.
335  $imageDictionary->Mask = new Zend_Pdf_Element_Array($transparencyData);
336  }
337 
338  $imageDictionary->Width = new Zend_Pdf_Element_Numeric($width);
339  $imageDictionary->Height = new Zend_Pdf_Element_Numeric($height);
340  $imageDictionary->ColorSpace = $colorSpace;
341  $imageDictionary->BitsPerComponent = new Zend_Pdf_Element_Numeric($bits);
342  $imageDictionary->Filter = new Zend_Pdf_Element_Name('FlateDecode');
343 
344  $decodeParms = array();
345  $decodeParms['Predictor'] = new Zend_Pdf_Element_Numeric(15); // Optimal prediction
346  $decodeParms['Columns'] = new Zend_Pdf_Element_Numeric($width);
348  $decodeParms['BitsPerComponent'] = new Zend_Pdf_Element_Numeric($bits);
349  $imageDictionary->DecodeParms = new Zend_Pdf_Element_Dictionary($decodeParms);
350 
351  //Include only the image IDAT section data.
352  $this->_resource->value = $imageData;
353 
354  //Skip double compression
355  if ($compressed) {
356  $this->_resource->skipFilters();
357  }
358  }
359 
363  public function getPixelWidth() {
364  return $this->_width;
365  }
366 
370  public function getPixelHeight() {
371  return $this->_height;
372  }
373 
377  public function getProperties() {
379  }
380 }
const PNG_COMPRESSION_DEFAULT_STRATEGY
Definition: Png.php:43
const PNG_COMPRESSION_HUFFMAN_ONLY
Definition: Png.php:45
__construct($imageFileName)
Definition: Png.php:79
const PNG_CHANNEL_GRAY_ALPHA
Definition: Png.php:60
const PNG_INTERLACING_ENABLED
Definition: Png.php:55
const PNG_INTERLACING_DISABLED
Definition: Png.php:54
static createFactory($objCount)
const PNG_COMPRESSION_FILTERED
Definition: Png.php:44