Fix #8052 - fixing pdf
[pear] / FpdfAlt / ImageAlpha.php
1  <?php
2 /*******************************************************************************
3 * Software: PDF_ImageAlpha
4 * Version:  1.4
5 * Date:     2009-12-28
6 * Author:   Valentin Schmidt 
7 *
8 * Requirements: FPDF 1.6
9 *
10 * This script allows to use images (PNGs or JPGs) with alpha-channels. 
11 * The alpha-channel can be either supplied as separate 8-bit PNG ("mask"), 
12 * or, for PNGs, also an internal alpha-channel can be used. 
13 * For the latter the GD 2.x extension is required.
14 *******************************************************************************/ 
15
16 require_once 'Fpdf.php';
17
18 class Fpdf_ImageAlpha extends Fpdf{
19
20 //Private properties
21 var $tmpFiles = array(); 
22
23 /*******************************************************************************
24 *                                                                              *
25 *                               Public methods                                 *
26 *                                                                              *
27 *******************************************************************************/
28 function Image($file,$x,$y,$w=0,$h=0,$type='',$link='', $isMask=false, $maskImg=0)
29 {
30     //Put an image on the page
31     if(!isset($this->images[$file]))
32     {
33         //First use of image, get info
34         if($type=='')
35         {
36             $pos=strrpos($file,'.');
37             if(!$pos)
38                 $this->Error('Image file has no extension and no type was specified: '.$file);
39             $type=substr($file,$pos+1);
40         }
41         $type=strtolower($type);
42         //$mqr=get_magic_quotes_runtime();
43         //@set_magic_quotes_runtime(0);
44         if($type=='jpg' || $type=='jpeg')
45             $info=$this->_parsejpg($file);
46         elseif($type=='png'){
47             $info=$this->_parsepng($file);
48             if ($info=='alpha') return $this->ImagePngWithAlpha($file,$x,$y,$w,$h,$link);
49         }
50         else
51         {
52             //Allow for additional formats
53             $mtd='_parse'.$type;
54             if(!method_exists($this,$mtd))
55                 $this->Error('Unsupported image type: '.$type);
56             $info=$this->$mtd($file);
57         }
58         //set_magic_quotes_runtime($mqr);
59         
60         if ($isMask){
61       $info['cs']="DeviceGray"; // try to force grayscale (instead of indexed)
62     }
63         $info['i']=count($this->images)+1;
64         if ($maskImg>0) $info['masked'] = $maskImg;###
65         $this->images[$file]=$info;
66     }
67     else
68         $info=$this->images[$file];
69     //Automatic width and height calculation if needed
70     if($w==0 && $h==0)
71     {
72         //Put image at 72 dpi
73         $w=$info['w']/$this->k;
74         $h=$info['h']/$this->k;
75     }
76     if($w==0)
77         $w=$h*$info['w']/$info['h'];
78     if($h==0)
79         $h=$w*$info['h']/$info['w'];
80     
81     // embed hidden, ouside the canvas
82     if ((float)FPDF_VERSION>=1.7){
83         if ($isMask) $x = ($this->CurOrientation=='P'?$this->CurPageSize[0]:$this->CurPageSize[1]) + 10;
84     }else{
85         if ($isMask) $x = ($this->CurOrientation=='P'?$this->CurPageFormat[0]:$this->CurPageFormat[1]) + 10;
86     }
87         
88     $this->_out(sprintf('q %.2f 0 0 %.2f %.2f %.2f cm /I%d Do Q',$w*$this->k,$h*$this->k,$x*$this->k,($this->h-($y+$h))*$this->k,$info['i']));
89     if($link)
90         $this->Link($x,$y,$w,$h,$link);
91         
92     return $info['i'];
93 }
94
95 // needs GD 2.x extension
96 // pixel-wise operation, not very fast
97 function ImagePngWithAlpha($file,$x,$y,$w=0,$h=0,$link='')
98 {
99     $tmp_alpha = tempnam('.', 'mska');
100     $this->tmpFiles[] = $tmp_alpha;
101     $tmp_plain = tempnam('.', 'mskp');
102     $this->tmpFiles[] = $tmp_plain;
103     
104     list($wpx, $hpx) = getimagesize($file);
105     $img = imagecreatefrompng($file);
106     $alpha_img = imagecreate( $wpx, $hpx );
107     
108     // generate gray scale pallete
109     for($c=0;$c<256;$c++) ImageColorAllocate($alpha_img, $c, $c, $c);
110     
111     // extract alpha channel
112     $xpx=0;
113     while ($xpx<$wpx){
114         $ypx = 0;
115         while ($ypx<$hpx){
116             $color_index = imagecolorat($img, $xpx, $ypx);
117             $alpha = 255-($color_index>>24)*255/127; // GD alpha component: 7 bit only, 0..127!
118             imagesetpixel($alpha_img, $xpx, $ypx, $alpha);
119         ++$ypx;
120         }
121         ++$xpx;
122     }
123
124     imagepng($alpha_img, $tmp_alpha);
125     imagedestroy($alpha_img);
126     
127     // extract image without alpha channel
128     $plain_img = imagecreatetruecolor ( $wpx, $hpx );
129     imagecopy ($plain_img, $img, 0, 0, 0, 0, $wpx, $hpx );
130     imagepng($plain_img, $tmp_plain);
131     imagedestroy($plain_img);
132     
133     //first embed mask image (w, h, x, will be ignored)
134     $maskImg = $this->Image($tmp_alpha, 0,0,0,0, 'PNG', '', true); 
135     
136     //embed image, masked with previously embedded mask
137     $this->Image($tmp_plain,$x,$y,$w,$h,'PNG',$link, false, $maskImg);
138 }
139
140 function Close()
141 {
142     parent::Close();
143     // clean up tmp files
144     foreach($this->tmpFiles as $tmp) @unlink($tmp);
145 }
146
147 /*******************************************************************************
148 *                                                                              *
149 *                               Private methods                                *
150 *                                                                              *
151 *******************************************************************************/
152 function _putimages()
153 {
154     $filter=($this->compress) ? '/Filter /FlateDecode ' : '';
155     reset($this->images);
156     while(list($file,$info)=each($this->images))
157     {
158         $this->_newobj();
159         $this->images[$file]['n']=$this->n;
160         $this->_out('<</Type /XObject');
161         $this->_out('/Subtype /Image');
162         $this->_out('/Width '.$info['w']);
163         $this->_out('/Height '.$info['h']);
164         
165         if (isset($info["masked"])) $this->_out('/SMask '.($this->n-1).' 0 R'); ###
166         
167         if($info['cs']=='Indexed')
168             $this->_out('/ColorSpace [/Indexed /DeviceRGB '.(strlen($info['pal'])/3-1).' '.($this->n+1).' 0 R]');
169         else
170         {
171             $this->_out('/ColorSpace /'.$info['cs']);
172             if($info['cs']=='DeviceCMYK')
173                 $this->_out('/Decode [1 0 1 0 1 0 1 0]');
174         }
175         $this->_out('/BitsPerComponent '.$info['bpc']);
176         if(isset($info['f']))
177             $this->_out('/Filter /'.$info['f']);
178         if(isset($info['parms']))
179             $this->_out($info['parms']);
180         if(isset($info['trns']) && is_array($info['trns']))
181         {
182             $trns='';
183             for($i=0;$i<count($info['trns']);$i++)
184                 $trns.=$info['trns'][$i].' '.$info['trns'][$i].' ';
185             $this->_out('/Mask ['.$trns.']');
186         }
187         $this->_out('/Length '.strlen($info['data']).'>>');
188         $this->_putstream($info['data']);
189         unset($this->images[$file]['data']);
190         $this->_out('endobj');
191         //Palette
192         if($info['cs']=='Indexed')
193         {
194             $this->_newobj();
195             $pal=($this->compress) ? gzcompress($info['pal']) : $info['pal'];
196             $this->_out('<<'.$filter.'/Length '.strlen($pal).'>>');
197             $this->_putstream($pal);
198             $this->_out('endobj');
199         }
200     }
201 }
202
203 // this method overwriing the original version is only needed to make the Image method support PNGs with alpha channels.
204 // if you only use the ImagePngWithAlpha method for such PNGs, you can remove it from this script.
205 function _parsepng($file)
206 {
207     //Extract info from a PNG file
208     $f=fopen($file,'rb');
209     if(!$f)
210         $this->Error('Can\'t open image file: '.$file);
211     //Check signature
212     if(fread($f,8)!=chr(137).'PNG'.chr(13).chr(10).chr(26).chr(10))
213         $this->Error('Not a PNG file: '.$file);
214     //Read header chunk
215     fread($f,4);
216     if(fread($f,4)!='IHDR')
217         $this->Error('Incorrect PNG file: '.$file);
218     $w=$this->_readint($f);
219     $h=$this->_readint($f);
220     $bpc=ord(fread($f,1));
221     if($bpc>8)
222         $this->Error('16-bit depth not supported: '.$file);
223     $ct=ord(fread($f,1));
224     if($ct==0)
225         $colspace='DeviceGray';
226     elseif($ct==2)
227         $colspace='DeviceRGB';
228     elseif($ct==3)
229         $colspace='Indexed';
230     else {
231         fclose($f);      // the only changes are 
232         return 'alpha';  // made in those 2 lines
233     }
234     if(ord(fread($f,1))!=0)
235         $this->Error('Unknown compression method: '.$file);
236     if(ord(fread($f,1))!=0)
237         $this->Error('Unknown filter method: '.$file);
238     if(ord(fread($f,1))!=0)
239         $this->Error('Interlacing not supported: '.$file);
240     fread($f,4);
241     $parms='/DecodeParms <</Predictor 15 /Colors '.($ct==2 ? 3 : 1).' /BitsPerComponent '.$bpc.' /Columns '.$w.'>>';
242     //Scan chunks looking for palette, transparency and image data
243     $pal='';
244     $trns='';
245     $data='';
246     do
247     {
248         $n=$this->_readint($f);
249         $type=fread($f,4);
250         if($type=='PLTE')
251         {
252             //Read palette
253             $pal=fread($f,$n);
254             fread($f,4);
255         }
256         elseif($type=='tRNS')
257         {
258             //Read transparency info
259             $t=fread($f,$n);
260             if($ct==0)
261                 $trns=array(ord(substr($t,1,1)));
262             elseif($ct==2)
263                 $trns=array(ord(substr($t,1,1)),ord(substr($t,3,1)),ord(substr($t,5,1)));
264             else
265             {
266                 $pos=strpos($t,chr(0));
267                 if($pos!==false)
268                     $trns=array($pos);
269             }
270             fread($f,4);
271         }
272         elseif($type=='IDAT')
273         {
274             //Read image data block
275             $data.=fread($f,$n);
276             fread($f,4);
277         }
278         elseif($type=='IEND')
279             break;
280         else
281             fread($f,$n+4);
282     }
283     while($n);
284     if($colspace=='Indexed' && empty($pal))
285         $this->Error('Missing palette in '.$file);
286     fclose($f);
287     return array('w'=>$w,'h'=>$h,'cs'=>$colspace,'bpc'=>$bpc,'f'=>'FlateDecode','parms'=>$parms,'pal'=>$pal,'trns'=>$trns,'data'=>$data);
288 }
289
290 function _readstream($f, $n)
291 {
292         // Read n bytes from stream
293         $res = '';
294         while($n>0 && !feof($f))
295         {
296                 $s = fread($f,$n);
297                 if($s===false)
298                         $this->Error('Error while reading stream');
299                 $n -= strlen($s);
300                 $res .= $s;
301         }
302         if($n>0)
303                 $this->Error('Unexpected end of stream');
304         return $res;
305 }
306
307 function _readint($f)
308 {
309         // Read a 4-byte integer from stream
310         $a = unpack('Ni',$this->_readstream($f,4));
311         return $a['i'];
312 }
313 }
314