File indexing completed on 2025-01-19 05:21:23
0001 <?php 0002 /** 0003 * Zend Framework 0004 * 0005 * LICENSE 0006 * 0007 * This source file is subject to the new BSD license that is bundled 0008 * with this package in the file LICENSE.txt. 0009 * It is also available through the world-wide-web at this URL: 0010 * http://framework.zend.com/license/new-bsd 0011 * If you did not receive a copy of the license and are unable to 0012 * obtain it through the world-wide-web, please send an email 0013 * to license@zend.com so we can send you a copy immediately. 0014 * 0015 * @category Zend 0016 * @package Zend_Pdf 0017 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0018 * @license http://framework.zend.com/license/new-bsd New BSD License 0019 * @version $Id$ 0020 */ 0021 0022 0023 /** Internally used classes */ 0024 // require_once 'Zend/Pdf/Element/Array.php'; 0025 // require_once 'Zend/Pdf/Element/Dictionary.php'; 0026 // require_once 'Zend/Pdf/Element/Name.php'; 0027 // require_once 'Zend/Pdf/Element/Numeric.php'; 0028 // require_once 'Zend/Pdf/Element/String/Binary.php'; 0029 0030 0031 /** Zend_Pdf_Resource_Image */ 0032 // require_once 'Zend/Pdf/Resource/Image.php'; 0033 0034 /** 0035 * PNG image 0036 * 0037 * @package Zend_Pdf 0038 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0039 * @license http://framework.zend.com/license/new-bsd New BSD License 0040 */ 0041 class Zend_Pdf_Resource_Image_Png extends Zend_Pdf_Resource_Image 0042 { 0043 const PNG_COMPRESSION_DEFAULT_STRATEGY = 0; 0044 const PNG_COMPRESSION_FILTERED = 1; 0045 const PNG_COMPRESSION_HUFFMAN_ONLY = 2; 0046 const PNG_COMPRESSION_RLE = 3; 0047 0048 const PNG_FILTER_NONE = 0; 0049 const PNG_FILTER_SUB = 1; 0050 const PNG_FILTER_UP = 2; 0051 const PNG_FILTER_AVERAGE = 3; 0052 const PNG_FILTER_PAETH = 4; 0053 0054 const PNG_INTERLACING_DISABLED = 0; 0055 const PNG_INTERLACING_ENABLED = 1; 0056 0057 const PNG_CHANNEL_GRAY = 0; 0058 const PNG_CHANNEL_RGB = 2; 0059 const PNG_CHANNEL_INDEXED = 3; 0060 const PNG_CHANNEL_GRAY_ALPHA = 4; 0061 const PNG_CHANNEL_RGB_ALPHA = 6; 0062 0063 protected $_width; 0064 protected $_height; 0065 protected $_imageProperties; 0066 0067 /** 0068 * Object constructor 0069 * 0070 * @param string $imageFileName 0071 * @throws Zend_Pdf_Exception 0072 * @todo Add compression conversions to support compression strategys other than PNG_COMPRESSION_DEFAULT_STRATEGY. 0073 * @todo Add pre-compression filtering. 0074 * @todo Add interlaced image handling. 0075 * @todo Add support for 16-bit images. Requires PDF version bump to 1.5 at least. 0076 * @todo Add processing for all PNG chunks defined in the spec. gAMA etc. 0077 * @todo Fix tRNS chunk support for Indexed Images to a SMask. 0078 */ 0079 public function __construct($imageFileName) 0080 { 0081 if (($imageFile = @fopen($imageFileName, 'rb')) === false ) { 0082 // require_once 'Zend/Pdf/Exception.php'; 0083 throw new Zend_Pdf_Exception( "Can not open '$imageFileName' file for reading." ); 0084 } 0085 0086 parent::__construct(); 0087 0088 //Check if the file is a PNG 0089 fseek($imageFile, 1, SEEK_CUR); //First signature byte (%) 0090 if ('PNG' != fread($imageFile, 3)) { 0091 // require_once 'Zend/Pdf/Exception.php'; 0092 throw new Zend_Pdf_Exception('Image is not a PNG'); 0093 } 0094 fseek($imageFile, 12, SEEK_CUR); //Signature bytes (Includes the IHDR chunk) IHDR processed linerarly because it doesnt contain a variable chunk length 0095 $wtmp = unpack('Ni',fread($imageFile, 4)); //Unpack a 4-Byte Long 0096 $width = $wtmp['i']; 0097 $htmp = unpack('Ni',fread($imageFile, 4)); 0098 $height = $htmp['i']; 0099 $bits = ord(fread($imageFile, 1)); //Higher than 8 bit depths are only supported in later versions of PDF. 0100 $color = ord(fread($imageFile, 1)); 0101 0102 $compression = ord(fread($imageFile, 1)); 0103 $prefilter = ord(fread($imageFile,1)); 0104 0105 if (($interlacing = ord(fread($imageFile,1))) != Zend_Pdf_Resource_Image_Png::PNG_INTERLACING_DISABLED) { 0106 // require_once 'Zend/Pdf/Exception.php'; 0107 throw new Zend_Pdf_Exception( "Only non-interlaced images are currently supported." ); 0108 } 0109 0110 $this->_width = $width; 0111 $this->_height = $height; 0112 $this->_imageProperties = array(); 0113 $this->_imageProperties['bitDepth'] = $bits; 0114 $this->_imageProperties['pngColorType'] = $color; 0115 $this->_imageProperties['pngFilterType'] = $prefilter; 0116 $this->_imageProperties['pngCompressionType'] = $compression; 0117 $this->_imageProperties['pngInterlacingType'] = $interlacing; 0118 0119 fseek($imageFile, 4, SEEK_CUR); //4 Byte Ending Sequence 0120 $imageData = ''; 0121 0122 /* 0123 * The following loop processes PNG chunks. 4 Byte Longs are packed first give the chunk length 0124 * followed by the chunk signature, a four byte code. IDAT and IEND are manditory in any PNG. 0125 */ 0126 while (!feof($imageFile)) { 0127 $chunkLengthBytes = fread($imageFile, 4); 0128 if ($chunkLengthBytes === false) { 0129 // require_once 'Zend/Pdf/Exception.php'; 0130 throw new Zend_Pdf_Exception('Error ocuured while image file reading.'); 0131 } 0132 0133 $chunkLengthtmp = unpack('Ni', $chunkLengthBytes); 0134 $chunkLength = $chunkLengthtmp['i']; 0135 $chunkType = fread($imageFile, 4); 0136 switch($chunkType) { 0137 case 'IDAT': //Image Data 0138 /* 0139 * Reads the actual image data from the PNG file. Since we know at this point that the compression 0140 * strategy is the default strategy, we also know that this data is Zip compressed. We will either copy 0141 * the data directly to the PDF and provide the correct FlateDecode predictor, or decompress the data 0142 * decode the filters and output the data as a raw pixel map. 0143 */ 0144 $imageData .= fread($imageFile, $chunkLength); 0145 fseek($imageFile, 4, SEEK_CUR); 0146 break; 0147 0148 case 'PLTE': //Palette 0149 $paletteData = fread($imageFile, $chunkLength); 0150 fseek($imageFile, 4, SEEK_CUR); 0151 break; 0152 0153 case 'tRNS': //Basic (non-alpha channel) transparency. 0154 $trnsData = fread($imageFile, $chunkLength); 0155 switch ($color) { 0156 case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_GRAY: 0157 $baseColor = ord(substr($trnsData, 1, 1)); 0158 $transparencyData = array(new Zend_Pdf_Element_Numeric($baseColor), 0159 new Zend_Pdf_Element_Numeric($baseColor)); 0160 break; 0161 0162 case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_RGB: 0163 $red = ord(substr($trnsData,1,1)); 0164 $green = ord(substr($trnsData,3,1)); 0165 $blue = ord(substr($trnsData,5,1)); 0166 $transparencyData = array(new Zend_Pdf_Element_Numeric($red), 0167 new Zend_Pdf_Element_Numeric($red), 0168 new Zend_Pdf_Element_Numeric($green), 0169 new Zend_Pdf_Element_Numeric($green), 0170 new Zend_Pdf_Element_Numeric($blue), 0171 new Zend_Pdf_Element_Numeric($blue)); 0172 break; 0173 0174 case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_INDEXED: 0175 //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). 0176 if(($trnsIdx = strpos($trnsData, "\0")) !== false) { 0177 $transparencyData = array(new Zend_Pdf_Element_Numeric($trnsIdx), 0178 new Zend_Pdf_Element_Numeric($trnsIdx)); 0179 } 0180 break; 0181 0182 case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_GRAY_ALPHA: 0183 // Fall through to the next case 0184 0185 case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_RGB_ALPHA: 0186 // require_once 'Zend/Pdf/Exception.php'; 0187 throw new Zend_Pdf_Exception( "tRNS chunk illegal for Alpha Channel Images" ); 0188 break; 0189 } 0190 fseek($imageFile, 4, SEEK_CUR); //4 Byte Ending Sequence 0191 break; 0192 0193 case 'IEND'; 0194 break 2; //End the loop too 0195 0196 default: 0197 fseek($imageFile, $chunkLength + 4, SEEK_CUR); //Skip the section 0198 break; 0199 } 0200 } 0201 fclose($imageFile); 0202 0203 $compressed = true; 0204 $imageDataTmp = ''; 0205 $smaskData = ''; 0206 switch ($color) { 0207 case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_RGB: 0208 $colorSpace = new Zend_Pdf_Element_Name('DeviceRGB'); 0209 break; 0210 0211 case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_GRAY: 0212 $colorSpace = new Zend_Pdf_Element_Name('DeviceGray'); 0213 break; 0214 0215 case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_INDEXED: 0216 if(empty($paletteData)) { 0217 // require_once 'Zend/Pdf/Exception.php'; 0218 throw new Zend_Pdf_Exception( "PNG Corruption: No palette data read for indexed type PNG." ); 0219 } 0220 $colorSpace = new Zend_Pdf_Element_Array(); 0221 $colorSpace->items[] = new Zend_Pdf_Element_Name('Indexed'); 0222 $colorSpace->items[] = new Zend_Pdf_Element_Name('DeviceRGB'); 0223 $colorSpace->items[] = new Zend_Pdf_Element_Numeric((strlen($paletteData)/3-1)); 0224 $paletteObject = $this->_objectFactory->newObject(new Zend_Pdf_Element_String_Binary($paletteData)); 0225 $colorSpace->items[] = $paletteObject; 0226 break; 0227 0228 case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_GRAY_ALPHA: 0229 /* 0230 * To decode PNG's with alpha data we must create two images from one. One image will contain the Gray data 0231 * the other will contain the Gray transparency overlay data. The former will become the object data and the latter 0232 * will become the Shadow Mask (SMask). 0233 */ 0234 if($bits > 8) { 0235 // require_once 'Zend/Pdf/Exception.php'; 0236 throw new Zend_Pdf_Exception("Alpha PNGs with bit depth > 8 are not yet supported"); 0237 } 0238 0239 $colorSpace = new Zend_Pdf_Element_Name('DeviceGray'); 0240 0241 // require_once 'Zend/Pdf/ElementFactory.php'; 0242 $decodingObjFactory = Zend_Pdf_ElementFactory::createFactory(1); 0243 $decodingStream = $decodingObjFactory->newStreamObject($imageData); 0244 $decodingStream->dictionary->Filter = new Zend_Pdf_Element_Name('FlateDecode'); 0245 $decodingStream->dictionary->DecodeParms = new Zend_Pdf_Element_Dictionary(); 0246 $decodingStream->dictionary->DecodeParms->Predictor = new Zend_Pdf_Element_Numeric(15); 0247 $decodingStream->dictionary->DecodeParms->Columns = new Zend_Pdf_Element_Numeric($width); 0248 $decodingStream->dictionary->DecodeParms->Colors = new Zend_Pdf_Element_Numeric(2); //GreyAlpha 0249 $decodingStream->dictionary->DecodeParms->BitsPerComponent = new Zend_Pdf_Element_Numeric($bits); 0250 $decodingStream->skipFilters(); 0251 0252 $pngDataRawDecoded = $decodingStream->value; 0253 0254 //Iterate every pixel and copy out gray data and alpha channel (this will be slow) 0255 for($pixel = 0, $pixelcount = ($width * $height); $pixel < $pixelcount; $pixel++) { 0256 $imageDataTmp .= $pngDataRawDecoded[($pixel*2)]; 0257 $smaskData .= $pngDataRawDecoded[($pixel*2)+1]; 0258 } 0259 $compressed = false; 0260 $imageData = $imageDataTmp; //Overwrite image data with the gray channel without alpha 0261 break; 0262 0263 case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_RGB_ALPHA: 0264 /* 0265 * To decode PNG's with alpha data we must create two images from one. One image will contain the RGB data 0266 * the other will contain the Gray transparency overlay data. The former will become the object data and the latter 0267 * will become the Shadow Mask (SMask). 0268 */ 0269 if($bits > 8) { 0270 // require_once 'Zend/Pdf/Exception.php'; 0271 throw new Zend_Pdf_Exception("Alpha PNGs with bit depth > 8 are not yet supported"); 0272 } 0273 0274 $colorSpace = new Zend_Pdf_Element_Name('DeviceRGB'); 0275 0276 // require_once 'Zend/Pdf/ElementFactory.php'; 0277 $decodingObjFactory = Zend_Pdf_ElementFactory::createFactory(1); 0278 $decodingStream = $decodingObjFactory->newStreamObject($imageData); 0279 $decodingStream->dictionary->Filter = new Zend_Pdf_Element_Name('FlateDecode'); 0280 $decodingStream->dictionary->DecodeParms = new Zend_Pdf_Element_Dictionary(); 0281 $decodingStream->dictionary->DecodeParms->Predictor = new Zend_Pdf_Element_Numeric(15); 0282 $decodingStream->dictionary->DecodeParms->Columns = new Zend_Pdf_Element_Numeric($width); 0283 $decodingStream->dictionary->DecodeParms->Colors = new Zend_Pdf_Element_Numeric(4); //RGBA 0284 $decodingStream->dictionary->DecodeParms->BitsPerComponent = new Zend_Pdf_Element_Numeric($bits); 0285 $decodingStream->skipFilters(); 0286 0287 $pngDataRawDecoded = $decodingStream->value; 0288 0289 //Iterate every pixel and copy out rgb data and alpha channel (this will be slow) 0290 for($pixel = 0, $pixelcount = ($width * $height); $pixel < $pixelcount; $pixel++) { 0291 $imageDataTmp .= $pngDataRawDecoded[($pixel*4)+0] . $pngDataRawDecoded[($pixel*4)+1] . $pngDataRawDecoded[($pixel*4)+2]; 0292 $smaskData .= $pngDataRawDecoded[($pixel*4)+3]; 0293 } 0294 0295 $compressed = false; 0296 $imageData = $imageDataTmp; //Overwrite image data with the RGB channel without alpha 0297 break; 0298 0299 default: 0300 // require_once 'Zend/Pdf/Exception.php'; 0301 throw new Zend_Pdf_Exception( "PNG Corruption: Invalid color space." ); 0302 } 0303 0304 if(empty($imageData)) { 0305 // require_once 'Zend/Pdf/Exception.php'; 0306 throw new Zend_Pdf_Exception( "Corrupt PNG Image. Mandatory IDAT chunk not found." ); 0307 } 0308 0309 $imageDictionary = $this->_resource->dictionary; 0310 if(!empty($smaskData)) { 0311 /* 0312 * Includes the Alpha transparency data as a Gray Image, then assigns the image as the Shadow Mask for the main image data. 0313 */ 0314 $smaskStream = $this->_objectFactory->newStreamObject($smaskData); 0315 $smaskStream->dictionary->Type = new Zend_Pdf_Element_Name('XObject'); 0316 $smaskStream->dictionary->Subtype = new Zend_Pdf_Element_Name('Image'); 0317 $smaskStream->dictionary->Width = new Zend_Pdf_Element_Numeric($width); 0318 $smaskStream->dictionary->Height = new Zend_Pdf_Element_Numeric($height); 0319 $smaskStream->dictionary->ColorSpace = new Zend_Pdf_Element_Name('DeviceGray'); 0320 $smaskStream->dictionary->BitsPerComponent = new Zend_Pdf_Element_Numeric($bits); 0321 $imageDictionary->SMask = $smaskStream; 0322 0323 // Encode stream with FlateDecode filter 0324 $smaskStreamDecodeParms = array(); 0325 $smaskStreamDecodeParms['Predictor'] = new Zend_Pdf_Element_Numeric(15); 0326 $smaskStreamDecodeParms['Columns'] = new Zend_Pdf_Element_Numeric($width); 0327 $smaskStreamDecodeParms['Colors'] = new Zend_Pdf_Element_Numeric(1); 0328 $smaskStreamDecodeParms['BitsPerComponent'] = new Zend_Pdf_Element_Numeric(8); 0329 $smaskStream->dictionary->DecodeParms = new Zend_Pdf_Element_Dictionary($smaskStreamDecodeParms); 0330 $smaskStream->dictionary->Filter = new Zend_Pdf_Element_Name('FlateDecode'); 0331 } 0332 0333 if(!empty($transparencyData)) { 0334 //This is experimental and not properly tested. 0335 $imageDictionary->Mask = new Zend_Pdf_Element_Array($transparencyData); 0336 } 0337 0338 $imageDictionary->Width = new Zend_Pdf_Element_Numeric($width); 0339 $imageDictionary->Height = new Zend_Pdf_Element_Numeric($height); 0340 $imageDictionary->ColorSpace = $colorSpace; 0341 $imageDictionary->BitsPerComponent = new Zend_Pdf_Element_Numeric($bits); 0342 $imageDictionary->Filter = new Zend_Pdf_Element_Name('FlateDecode'); 0343 0344 $decodeParms = array(); 0345 $decodeParms['Predictor'] = new Zend_Pdf_Element_Numeric(15); // Optimal prediction 0346 $decodeParms['Columns'] = new Zend_Pdf_Element_Numeric($width); 0347 $decodeParms['Colors'] = new Zend_Pdf_Element_Numeric((($color==Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_RGB || $color==Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_RGB_ALPHA)?(3):(1))); 0348 $decodeParms['BitsPerComponent'] = new Zend_Pdf_Element_Numeric($bits); 0349 $imageDictionary->DecodeParms = new Zend_Pdf_Element_Dictionary($decodeParms); 0350 0351 //Include only the image IDAT section data. 0352 $this->_resource->value = $imageData; 0353 0354 //Skip double compression 0355 if ($compressed) { 0356 $this->_resource->skipFilters(); 0357 } 0358 } 0359 0360 /** 0361 * Image width 0362 */ 0363 public function getPixelWidth() { 0364 return $this->_width; 0365 } 0366 0367 /** 0368 * Image height 0369 */ 0370 public function getPixelHeight() { 0371 return $this->_height; 0372 } 0373 0374 /** 0375 * Image properties 0376 */ 0377 public function getProperties() { 0378 return $this->_imageProperties; 0379 } 0380 }