2 /*******************************************************************************
3 * Software: PDF_ImageAlpha
6 * Author: Valentin Schmidt
8 * Requirements: FPDF 1.6
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 *******************************************************************************/
16 require_once 'Fpdf.php';
18 class Fpdf_ImageAlpha extends Fpdf{
21 var $tmpFiles = array();
23 /*******************************************************************************
27 *******************************************************************************/
28 function Image($file,$x,$y,$w=0,$h=0,$type='',$link='', $isMask=false, $maskImg=0)
30 //Put an image on the page
31 if(!isset($this->images[$file]))
33 //First use of image, get info
36 $pos=strrpos($file,'.');
38 $this->Error('Image file has no extension and no type was specified: '.$file);
39 $type=substr($file,$pos+1);
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);
47 $info=$this->_parsepng($file);
48 if ($info=='alpha') return $this->ImagePngWithAlpha($file,$x,$y,$w,$h,$link);
52 //Allow for additional formats
54 if(!method_exists($this,$mtd))
55 $this->Error('Unsupported image type: '.$type);
56 $info=$this->$mtd($file);
58 //set_magic_quotes_runtime($mqr);
61 $info['cs']="DeviceGray"; // try to force grayscale (instead of indexed)
63 $info['i']=count($this->images)+1;
64 if ($maskImg>0) $info['masked'] = $maskImg;###
65 $this->images[$file]=$info;
68 $info=$this->images[$file];
69 //Automatic width and height calculation if needed
73 $w=$info['w']/$this->k;
74 $h=$info['h']/$this->k;
77 $w=$h*$info['w']/$info['h'];
79 $h=$w*$info['h']/$info['w'];
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;
85 if ($isMask) $x = ($this->CurOrientation=='P'?$this->CurPageFormat[0]:$this->CurPageFormat[1]) + 10;
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']));
90 $this->Link($x,$y,$w,$h,$link);
95 // needs GD 2.x extension
96 // pixel-wise operation, not very fast
97 function ImagePngWithAlpha($file,$x,$y,$w=0,$h=0,$link='')
99 $tmp_alpha = tempnam('.', 'mska');
100 $this->tmpFiles[] = $tmp_alpha;
101 $tmp_plain = tempnam('.', 'mskp');
102 $this->tmpFiles[] = $tmp_plain;
104 list($wpx, $hpx) = getimagesize($file);
105 $img = imagecreatefrompng($file);
106 $alpha_img = imagecreate( $wpx, $hpx );
108 // generate gray scale pallete
109 for($c=0;$c<256;$c++) ImageColorAllocate($alpha_img, $c, $c, $c);
111 // extract alpha channel
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);
124 imagepng($alpha_img, $tmp_alpha);
125 imagedestroy($alpha_img);
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);
133 //first embed mask image (w, h, x, will be ignored)
134 $maskImg = $this->Image($tmp_alpha, 0,0,0,0, 'PNG', '', true);
136 //embed image, masked with previously embedded mask
137 $this->Image($tmp_plain,$x,$y,$w,$h,'PNG',$link, false, $maskImg);
143 // clean up tmp files
144 foreach($this->tmpFiles as $tmp) @unlink($tmp);
147 /*******************************************************************************
151 *******************************************************************************/
152 function _putimages()
154 $filter=($this->compress) ? '/Filter /FlateDecode ' : '';
155 reset($this->images);
156 while(list($file,$info)=each($this->images))
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']);
165 if (isset($info["masked"])) $this->_out('/SMask '.($this->n-1).' 0 R'); ###
167 if($info['cs']=='Indexed')
168 $this->_out('/ColorSpace [/Indexed /DeviceRGB '.(strlen($info['pal'])/3-1).' '.($this->n+1).' 0 R]');
171 $this->_out('/ColorSpace /'.$info['cs']);
172 if($info['cs']=='DeviceCMYK')
173 $this->_out('/Decode [1 0 1 0 1 0 1 0]');
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']))
183 for($i=0;$i<count($info['trns']);$i++)
184 $trns.=$info['trns'][$i].' '.$info['trns'][$i].' ';
185 $this->_out('/Mask ['.$trns.']');
187 $this->_out('/Length '.strlen($info['data']).'>>');
188 $this->_putstream($info['data']);
189 unset($this->images[$file]['data']);
190 $this->_out('endobj');
192 if($info['cs']=='Indexed')
195 $pal=($this->compress) ? gzcompress($info['pal']) : $info['pal'];
196 $this->_out('<<'.$filter.'/Length '.strlen($pal).'>>');
197 $this->_putstream($pal);
198 $this->_out('endobj');
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)
207 //Extract info from a PNG file
208 $f=fopen($file,'rb');
210 $this->Error('Can\'t open image file: '.$file);
212 if(fread($f,8)!=chr(137).'PNG'.chr(13).chr(10).chr(26).chr(10))
213 $this->Error('Not a PNG file: '.$file);
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));
222 $this->Error('16-bit depth not supported: '.$file);
223 $ct=ord(fread($f,1));
225 $colspace='DeviceGray';
227 $colspace='DeviceRGB';
231 fclose($f); // the only changes are
232 return 'alpha'; // made in those 2 lines
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);
241 $parms='/DecodeParms <</Predictor 15 /Colors '.($ct==2 ? 3 : 1).' /BitsPerComponent '.$bpc.' /Columns '.$w.'>>';
242 //Scan chunks looking for palette, transparency and image data
248 $n=$this->_readint($f);
256 elseif($type=='tRNS')
258 //Read transparency info
261 $trns=array(ord(substr($t,1,1)));
263 $trns=array(ord(substr($t,1,1)),ord(substr($t,3,1)),ord(substr($t,5,1)));
266 $pos=strpos($t,chr(0));
272 elseif($type=='IDAT')
274 //Read image data block
278 elseif($type=='IEND')
284 if($colspace=='Indexed' && empty($pal))
285 $this->Error('Missing palette in '.$file);
287 return array('w'=>$w,'h'=>$h,'cs'=>$colspace,'bpc'=>$bpc,'f'=>'FlateDecode','parms'=>$parms,'pal'=>$pal,'trns'=>$trns,'data'=>$data);
290 function _readstream($f, $n)
292 // Read n bytes from stream
294 while($n>0 && !feof($f))
298 $this->Error('Error while reading stream');
303 $this->Error('Unexpected end of stream');
307 function _readint($f)
309 // Read a 4-byte integer from stream
310 $a = unpack('Ni',$this->_readstream($f,4));