PageRenderTime 35ms CodeModel.GetById 1ms app.highlight 25ms RepoModel.GetById 1ms app.codeStats 0ms

/framework/vendor/zend/Zend/Pdf/Resource/Image/Png.php

http://zoop.googlecode.com/
PHP | 374 lines | 242 code | 51 blank | 81 comment | 21 complexity | 8052cbf7c403ac1400589b935e195279 MD5 | raw file
  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 20096 2010-01-06 02:05:09Z bkarwin $
 20 */
 21
 22
 23/** Internally used classes */
 24require_once 'Zend/Pdf/Element/Array.php';
 25require_once 'Zend/Pdf/Element/Dictionary.php';
 26require_once 'Zend/Pdf/Element/Name.php';
 27require_once 'Zend/Pdf/Element/Numeric.php';
 28require_once 'Zend/Pdf/Element/String/Binary.php';
 29
 30
 31/** Zend_Pdf_Resource_Image */
 32require_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 */
 41class 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(($chunkLengthBytes = fread($imageFile, 4)) !== false) {
127            $chunkLengthtmp         = unpack('Ni', $chunkLengthBytes);
128            $chunkLength            = $chunkLengthtmp['i'];
129            $chunkType                      = fread($imageFile, 4);
130            switch($chunkType) {
131                case 'IDAT': //Image Data
132                    /*
133                     * Reads the actual image data from the PNG file. Since we know at this point that the compression
134                     * strategy is the default strategy, we also know that this data is Zip compressed. We will either copy
135                     * the data directly to the PDF and provide the correct FlateDecode predictor, or decompress the data
136                     * decode the filters and output the data as a raw pixel map.
137                     */
138                    $imageData .= fread($imageFile, $chunkLength);
139                    fseek($imageFile, 4, SEEK_CUR);
140                    break;
141
142                case 'PLTE': //Palette
143                    $paletteData = fread($imageFile, $chunkLength);
144                    fseek($imageFile, 4, SEEK_CUR);
145                    break;
146
147                case 'tRNS': //Basic (non-alpha channel) transparency.
148                    $trnsData = fread($imageFile, $chunkLength);
149                    switch ($color) {
150                        case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_GRAY:
151                            $baseColor = ord(substr($trnsData, 1, 1));
152                            $transparencyData = array(new Zend_Pdf_Element_Numeric($baseColor),
153                                                      new Zend_Pdf_Element_Numeric($baseColor));
154                            break;
155
156                        case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_RGB:
157                            $red = ord(substr($trnsData,1,1));
158                            $green = ord(substr($trnsData,3,1));
159                            $blue = ord(substr($trnsData,5,1));
160                            $transparencyData = array(new Zend_Pdf_Element_Numeric($red),
161                                                      new Zend_Pdf_Element_Numeric($red),
162                                                      new Zend_Pdf_Element_Numeric($green),
163                                                      new Zend_Pdf_Element_Numeric($green),
164                                                      new Zend_Pdf_Element_Numeric($blue),
165                                                      new Zend_Pdf_Element_Numeric($blue));
166                            break;
167
168                        case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_INDEXED:
169                            //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).
170                            if(($trnsIdx = strpos($trnsData, chr(0))) !== false) {
171                                $transparencyData = array(new Zend_Pdf_Element_Numeric($trnsIdx),
172                                                          new Zend_Pdf_Element_Numeric($trnsIdx));
173                            }
174                            break;
175
176                        case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_GRAY_ALPHA:
177                            // Fall through to the next case
178
179                        case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_RGB_ALPHA:
180                            require_once 'Zend/Pdf/Exception.php';
181                            throw new Zend_Pdf_Exception( "tRNS chunk illegal for Alpha Channel Images" );
182                            break;
183                    }
184                    fseek($imageFile, 4, SEEK_CUR); //4 Byte Ending Sequence
185                    break;
186
187                case 'IEND';
188                    break 2; //End the loop too
189
190                default:
191                    fseek($imageFile, $chunkLength + 4, SEEK_CUR); //Skip the section
192                    break;
193            }
194        }
195        fclose($imageFile);
196
197        $compressed = true;
198        $imageDataTmp = '';
199        $smaskData = '';
200        switch ($color) {
201            case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_RGB:
202                $colorSpace = new Zend_Pdf_Element_Name('DeviceRGB');
203                break;
204
205            case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_GRAY:
206                $colorSpace = new Zend_Pdf_Element_Name('DeviceGray');
207                break;
208
209            case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_INDEXED:
210                if(empty($paletteData)) {
211                    require_once 'Zend/Pdf/Exception.php';
212                    throw new Zend_Pdf_Exception( "PNG Corruption: No palette data read for indexed type PNG." );
213                }
214                $colorSpace = new Zend_Pdf_Element_Array();
215                $colorSpace->items[] = new Zend_Pdf_Element_Name('Indexed');
216                $colorSpace->items[] = new Zend_Pdf_Element_Name('DeviceRGB');
217                $colorSpace->items[] = new Zend_Pdf_Element_Numeric((strlen($paletteData)/3-1));
218                $paletteObject = $this->_objectFactory->newObject(new Zend_Pdf_Element_String_Binary($paletteData));
219                $colorSpace->items[] = $paletteObject;
220                break;
221
222            case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_GRAY_ALPHA:
223                /*
224                 * To decode PNG's with alpha data we must create two images from one. One image will contain the Gray data
225                 * the other will contain the Gray transparency overlay data. The former will become the object data and the latter
226                 * will become the Shadow Mask (SMask).
227                 */
228                if($bits > 8) {
229                    require_once 'Zend/Pdf/Exception.php';
230                    throw new Zend_Pdf_Exception("Alpha PNGs with bit depth > 8 are not yet supported");
231                }
232
233                $colorSpace = new Zend_Pdf_Element_Name('DeviceGray');
234
235                require_once 'Zend/Pdf/ElementFactory.php';
236                $decodingObjFactory = Zend_Pdf_ElementFactory::createFactory(1);
237                $decodingStream = $decodingObjFactory->newStreamObject($imageData);
238                $decodingStream->dictionary->Filter      = new Zend_Pdf_Element_Name('FlateDecode');
239                $decodingStream->dictionary->DecodeParms = new Zend_Pdf_Element_Dictionary();
240                $decodingStream->dictionary->DecodeParms->Predictor        = new Zend_Pdf_Element_Numeric(15);
241                $decodingStream->dictionary->DecodeParms->Columns          = new Zend_Pdf_Element_Numeric($width);
242                $decodingStream->dictionary->DecodeParms->Colors           = new Zend_Pdf_Element_Numeric(2);   //GreyAlpha
243                $decodingStream->dictionary->DecodeParms->BitsPerComponent = new Zend_Pdf_Element_Numeric($bits);
244                $decodingStream->skipFilters();
245
246                $pngDataRawDecoded = $decodingStream->value;
247
248                //Iterate every pixel and copy out gray data and alpha channel (this will be slow)
249                for($pixel = 0, $pixelcount = ($width * $height); $pixel < $pixelcount; $pixel++) {
250                    $imageDataTmp .= $pngDataRawDecoded[($pixel*2)];
251                    $smaskData .= $pngDataRawDecoded[($pixel*2)+1];
252                }
253                $compressed = false;
254                $imageData  = $imageDataTmp; //Overwrite image data with the gray channel without alpha
255                break;
256
257            case Zend_Pdf_Resource_Image_Png::PNG_CHANNEL_RGB_ALPHA:
258                /*
259                 * To decode PNG's with alpha data we must create two images from one. One image will contain the RGB data
260                 * the other will contain the Gray transparency overlay data. The former will become the object data and the latter
261                 * will become the Shadow Mask (SMask).
262                 */
263                if($bits > 8) {
264                    require_once 'Zend/Pdf/Exception.php';
265                    throw new Zend_Pdf_Exception("Alpha PNGs with bit depth > 8 are not yet supported");
266                }
267
268                $colorSpace = new Zend_Pdf_Element_Name('DeviceRGB');
269
270                require_once 'Zend/Pdf/ElementFactory.php';
271                $decodingObjFactory = Zend_Pdf_ElementFactory::createFactory(1);
272                $decodingStream = $decodingObjFactory->newStreamObject($imageData);
273                $decodingStream->dictionary->Filter      = new Zend_Pdf_Element_Name('FlateDecode');
274                $decodingStream->dictionary->DecodeParms = new Zend_Pdf_Element_Dictionary();
275                $decodingStream->dictionary->DecodeParms->Predictor        = new Zend_Pdf_Element_Numeric(15);
276                $decodingStream->dictionary->DecodeParms->Columns          = new Zend_Pdf_Element_Numeric($width);
277                $decodingStream->dictionary->DecodeParms->Colors           = new Zend_Pdf_Element_Numeric(4);   //RGBA
278                $decodingStream->dictionary->DecodeParms->BitsPerComponent = new Zend_Pdf_Element_Numeric($bits);
279                $decodingStream->skipFilters();
280
281                $pngDataRawDecoded = $decodingStream->value;
282
283                //Iterate every pixel and copy out rgb data and alpha channel (this will be slow)
284                for($pixel = 0, $pixelcount = ($width * $height); $pixel < $pixelcount; $pixel++) {
285                    $imageDataTmp .= $pngDataRawDecoded[($pixel*4)+0] . $pngDataRawDecoded[($pixel*4)+1] . $pngDataRawDecoded[($pixel*4)+2];
286                    $smaskData .= $pngDataRawDecoded[($pixel*4)+3];
287                }
288
289                $compressed = false;
290                $imageData  = $imageDataTmp; //Overwrite image data with the RGB channel without alpha
291                break;
292
293            default:
294                require_once 'Zend/Pdf/Exception.php';
295                throw new Zend_Pdf_Exception( "PNG Corruption: Invalid color space." );
296        }
297
298        if(empty($imageData)) {
299            require_once 'Zend/Pdf/Exception.php';
300            throw new Zend_Pdf_Exception( "Corrupt PNG Image. Mandatory IDAT chunk not found." );
301        }
302
303        $imageDictionary = $this->_resource->dictionary;
304        if(!empty($smaskData)) {
305            /*
306             * Includes the Alpha transparency data as a Gray Image, then assigns the image as the Shadow Mask for the main image data.
307             */
308            $smaskStream = $this->_objectFactory->newStreamObject($smaskData);
309            $smaskStream->dictionary->Type             = new Zend_Pdf_Element_Name('XObject');
310            $smaskStream->dictionary->Subtype          = new Zend_Pdf_Element_Name('Image');
311            $smaskStream->dictionary->Width            = new Zend_Pdf_Element_Numeric($width);
312            $smaskStream->dictionary->Height           = new Zend_Pdf_Element_Numeric($height);
313            $smaskStream->dictionary->ColorSpace       = new Zend_Pdf_Element_Name('DeviceGray');
314            $smaskStream->dictionary->BitsPerComponent = new Zend_Pdf_Element_Numeric($bits);
315            $imageDictionary->SMask = $smaskStream;
316
317            // Encode stream with FlateDecode filter
318            $smaskStreamDecodeParms = array();
319            $smaskStreamDecodeParms['Predictor']        = new Zend_Pdf_Element_Numeric(15);
320            $smaskStreamDecodeParms['Columns']          = new Zend_Pdf_Element_Numeric($width);
321            $smaskStreamDecodeParms['Colors']           = new Zend_Pdf_Element_Numeric(1);
322            $smaskStreamDecodeParms['BitsPerComponent'] = new Zend_Pdf_Element_Numeric(8);
323            $smaskStream->dictionary->DecodeParms  = new Zend_Pdf_Element_Dictionary($smaskStreamDecodeParms);
324            $smaskStream->dictionary->Filter       = new Zend_Pdf_Element_Name('FlateDecode');
325        }
326
327        if(!empty($transparencyData)) {
328            //This is experimental and not properly tested.
329            $imageDictionary->Mask = new Zend_Pdf_Element_Array($transparencyData);
330        }
331
332        $imageDictionary->Width            = new Zend_Pdf_Element_Numeric($width);
333        $imageDictionary->Height           = new Zend_Pdf_Element_Numeric($height);
334        $imageDictionary->ColorSpace       = $colorSpace;
335        $imageDictionary->BitsPerComponent = new Zend_Pdf_Element_Numeric($bits);
336        $imageDictionary->Filter       = new Zend_Pdf_Element_Name('FlateDecode');
337
338        $decodeParms = array();
339        $decodeParms['Predictor']        = new Zend_Pdf_Element_Numeric(15); // Optimal prediction
340        $decodeParms['Columns']          = new Zend_Pdf_Element_Numeric($width);
341        $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)));
342        $decodeParms['BitsPerComponent'] = new Zend_Pdf_Element_Numeric($bits);
343        $imageDictionary->DecodeParms  = new Zend_Pdf_Element_Dictionary($decodeParms);
344
345        //Include only the image IDAT section data.
346        $this->_resource->value = $imageData;
347
348        //Skip double compression
349        if ($compressed) {
350            $this->_resource->skipFilters();
351        }
352    }
353
354    /**
355     * Image width
356     */
357    public function getPixelWidth() {
358    return $this->_width;
359    }
360
361    /**
362     * Image height
363     */
364    public function getPixelHeight() {
365        return $this->_height;
366    }
367
368    /**
369     * Image properties
370     */
371    public function getProperties() {
372        return $this->_imageProperties;
373    }
374}