web/lib/Zend/Pdf/Resource/Image/Png.php
changeset 64 162c1de6545a
parent 19 1c2f13fd785c
child 68 ecaf28ffe26e
equal deleted inserted replaced
63:5b37998e522e 64:162c1de6545a
       
     1 <?php
       
     2 /**
       
     3  * Zend Framework
       
     4  *
       
     5  * LICENSE
       
     6  *
       
     7  * This source file is subject to the new BSD license that is bundled
       
     8  * with this package in the file LICENSE.txt.
       
     9  * It is also available through the world-wide-web at this URL:
       
    10  * http://framework.zend.com/license/new-bsd
       
    11  * If you did not receive a copy of the license and are unable to
       
    12  * obtain it through the world-wide-web, please send an email
       
    13  * to license@zend.com so we can send you a copy immediately.
       
    14  *
       
    15  * @category   Zend
       
    16  * @package    Zend_Pdf
       
    17  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    18  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    19  * @version    $Id: Png.php 23395 2010-11-19 15:30:47Z alexander $
       
    20  */
       
    21 
       
    22 
       
    23 /** Internally used classes */
       
    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 
       
    31 /** Zend_Pdf_Resource_Image */
       
    32 require_once 'Zend/Pdf/Resource/Image.php';
       
    33 
       
    34 /**
       
    35  * PNG image
       
    36  *
       
    37  * @package    Zend_Pdf
       
    38  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    39  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    40  */
       
    41 class Zend_Pdf_Resource_Image_Png extends Zend_Pdf_Resource_Image
       
    42 {
       
    43     const PNG_COMPRESSION_DEFAULT_STRATEGY = 0;
       
    44     const PNG_COMPRESSION_FILTERED = 1;
       
    45     const PNG_COMPRESSION_HUFFMAN_ONLY = 2;
       
    46     const PNG_COMPRESSION_RLE = 3;
       
    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 
       
    54     const PNG_INTERLACING_DISABLED = 0;
       
    55     const PNG_INTERLACING_ENABLED = 1;
       
    56 
       
    57     const PNG_CHANNEL_GRAY = 0;
       
    58     const PNG_CHANNEL_RGB = 2;
       
    59     const PNG_CHANNEL_INDEXED = 3;
       
    60     const PNG_CHANNEL_GRAY_ALPHA = 4;
       
    61     const PNG_CHANNEL_RGB_ALPHA = 6;
       
    62 
       
    63     protected $_width;
       
    64     protected $_height;
       
    65     protected $_imageProperties;
       
    66 
       
    67     /**
       
    68      * Object constructor
       
    69      *
       
    70      * @param string $imageFileName
       
    71      * @throws Zend_Pdf_Exception
       
    72      * @todo Add compression conversions to support compression strategys other than PNG_COMPRESSION_DEFAULT_STRATEGY.
       
    73      * @todo Add pre-compression filtering.
       
    74      * @todo Add interlaced image handling.
       
    75      * @todo Add support for 16-bit images. Requires PDF version bump to 1.5 at least.
       
    76      * @todo Add processing for all PNG chunks defined in the spec. gAMA etc.
       
    77      * @todo Fix tRNS chunk support for Indexed Images to a SMask.
       
    78      */
       
    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) {
       
   156                         case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_GRAY:
       
   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 
       
   162                         case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_RGB:
       
   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 
       
   174                         case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_INDEXED:
       
   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 
       
   182                         case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_GRAY_ALPHA:
       
   183                             // Fall through to the next case
       
   184 
       
   185                         case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_RGB_ALPHA:
       
   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) {
       
   207             case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_RGB:
       
   208                 $colorSpace = new Zend_Pdf_Element_Name('DeviceRGB');
       
   209                 break;
       
   210 
       
   211             case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_GRAY:
       
   212                 $colorSpace = new Zend_Pdf_Element_Name('DeviceGray');
       
   213                 break;
       
   214 
       
   215             case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_INDEXED:
       
   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 
       
   228             case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_GRAY_ALPHA:
       
   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 
       
   263             case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_RGB_ALPHA:
       
   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);
       
   347         $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)));
       
   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 
       
   360     /**
       
   361      * Image width
       
   362      */
       
   363     public function getPixelWidth() {
       
   364     return $this->_width;
       
   365     }
       
   366 
       
   367     /**
       
   368      * Image height
       
   369      */
       
   370     public function getPixelHeight() {
       
   371         return $this->_height;
       
   372     }
       
   373 
       
   374     /**
       
   375      * Image properties
       
   376      */
       
   377     public function getProperties() {
       
   378         return $this->_imageProperties;
       
   379     }
       
   380 }