Go Back   $5 Script Archive Community Forum > html2ps/pdf > Discussion & Feature Requests
Register Try FlashChat Try FlashBB Search Today's Posts Mark Forums Read

Discussion & Feature Requests Post feature requests, and discussion regarding any aspect of the script that is not a bug.

Reply
 
Thread Tools Display Modes
  #1  
Old 07-02-2008, 11:20 AM
Guud Spelar Guud Spelar is offline
Junior Member
 
Join Date: Jul 2008
Posts: 3
Default How to get PNG Alpha Channel Transparency

This post applies to the FPDF driver only, not pdflib or ghostscript.

Currently with FPDF, if you have a PNG with embedded alpha-channel transparency, the alpha channel is ignored. I found a nice add-on class for FPDF that enables alpha-channel transparency for PNGs here: http://staff.dasdeck.de/valentin/fpdf/fpdf_alpha/. I took it apart and put it in the html2ps code.

Basically, if it runs into a PNG with an embedded alpha layer, it will make two flat PNGs and use one to mask the other. It's pretty groovy. I learned a lot by playing with this code. Both HTML2PS and FPDF are very impressive and well-made. Thanks to Derren and Konstantin and also to Valentin Schmidt, the writer of the FPDF extension I copied from.

( lines that are new have a ### after them, unless the entire function is new.)

File: pdf.fpdf.php
Class: FPDF


All the changes to this file are to the FPDF class (line 1036)

Add a var for keeping track of temp files to the FPDF class. (Around line 1092)
Code:
var $_forms;
var $_form_radios;
var $_pages;
var $tmpFiles = array(); ###
add garbage cleanup to the Close() function:
Code:
function Close() {
	//Terminate document
	if ($this->state == FPDF_STATE_COMPLETED) {
		return;
	};
	
	if ($this->page==0) {
		$this->AddPage();
	};
	
	//Close page
	$this->_endpage();
	//Close document
	$this->_enddoc();
	
	foreach($this->tmpFiles as $tmp) @unlink($tmp);	 ### 
}

Replace Image() function:
Code:
function Image($file,$x,$y,$w=0,$h=0,$typeBogus='',$link='', $isMask=false, $maskImg=0, $type='', $alpha=true)
{
	//Put an image on the page
	if(!isset($this->images[$file]))
	{
		//First use of image, get info
		if($type=='')
		{
			$pos=strrpos($file,'.');
			if(!$pos)
				$this->Error('Image file has no extension and no type was specified: '.$file);
			$type=substr($file,$pos+1);
		}
		$type=strtolower($type);
		$mqr=get_magic_quotes_runtime();
		set_magic_quotes_runtime(0);
		if($type=='jpg' || $type=='jpeg'){
			$info=$this->_parsejpg($file);
		}
		elseif($type=='png'){		
		
			$info=$this->_parsepng($file);		
							
			if ($alpha){
				$this->ImagePngWithAlpha($file,$x,$y,$w,$h,$link);
				return;
			}
		}
		else
		{
			//Allow for additional formats
			$mtd='_parse'.$type;
			if(!method_exists($this,$mtd))
				$this->Error('Unsupported image type: '.$type);
			$info=$this->$mtd($file);
		}
		set_magic_quotes_runtime($mqr);
		
		if ($isMask){
		  $info['cs']="DeviceGray"; // try to force grayscale (instead of indexed)
		}
		
		$info['i']=count($this->images)+1;
		
		if ($maskImg>0){
			$info['masked'] = $maskImg;
		}
		
		$this->images[$file]=$info;
	}
	else
	{
		$info=$this->images[$file];
	}
	
	//Automatic width and height calculation if needed
	if($w==0 && $h==0)
	{
		//Put image at 72 dpi
		$w=$info['w']/$this->k;
		$h=$info['h']/$this->k;
	}
	if($w==0)
		$w=$h*$info['w']/$info['h'];
	if($h==0)
		$h=$w*$info['h']/$info['w'];
		
	if ($isMask){
		$x = $this->fwPt + 10; // embed hidden, ouside the canvas  
	}

	$info = $this->images[$file];
				
	$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']));
					  
	return $info['i'];
}




Add these two new functions after Image(): ImagePngWithAlpha() and _gamma()
Code:
function ImagePngWithAlpha($file,$x,$y,$w=0,$h=0,$link='')
{	
	$tmp_alpha = tempnam(WRITER_TEMPDIR, 'mska');
	$this->tmpFiles[] = $tmp_alpha;
	$tmp_plain = tempnam(WRITER_TEMPDIR, 'mskp');
	$this->tmpFiles[] = $tmp_plain;
	
	list($wpx, $hpx) = getimagesize($file);
	
	$img = imagecreatefrompng($file);
	$alpha_img = imagecreate( $wpx, $hpx );

	// generate gray scale pallete
	for($c=0;$c<256;$c++){
		imagecolorallocate($alpha_img, $c, $c, $c);
	}
					
	// extract alpha channel
	$xpx=0;
	while ($xpx<$wpx){
		$ypx = 0;
		while ($ypx<$hpx){
			$color_index = imagecolorat($img, $xpx, $ypx);
			$alpha = ($color_index & 0x7F000000) >> 24;
			imagesetpixel($alpha_img, $xpx, $ypx, $this->_gamma( (127-$alpha)*255/127)  );
			$ypx++;
		}
		$xpx++;
	}

	imagepng($alpha_img, $tmp_alpha);
	imagedestroy($alpha_img);
	
	// extract image without alpha channel
	$plain_img = imagecreatetruecolor ( $wpx, $hpx );
	imagecopy ($plain_img, $img, 0, 0, 0, 0, $wpx, $hpx );
	imagepng($plain_img, $tmp_plain);
	imagedestroy($plain_img);
	
	//first embed mask image (w, h, x, will be ignored)
	$maskImg = $this->Image($tmp_alpha, 0,0,0,0, 'PNG', '', true, NULL, 'PNG', false); 
	
	//embed image, masked with previously embedded mask
	$this->Image($tmp_plain,$x,$y,$w,$h,'PNG',$link, false, $maskImg, 'PNG', false);
}

// GD seems to use a different gamma, this method is used to correct it again
function _gamma($v){
	return pow ($v/255, 2.2) * 255;
}


Add a new "mask" flag for masked images in _putimages():
Code:
function _putimages()
{
	$filter=($this->compress) ? '/Filter /FlateDecode ' : '';
	reset($this->images);
	while(list($file,$info)=each($this->images))
	{
		$this->_newobj();
		$this->images[$file]['n']=$this->n;
		$this->_out('<</Type /XObject');
		$this->_out('/Subtype /Image');
		$this->_out('/Width '.$info['w']);
		$this->_out('/Height '.$info['h']);
		
		if (isset($info["masked"])) $this->_out('/SMask '.($this->n-1).' 0 R'); ###
		
		if($info['cs']=='Indexed')
			$this->_out('/ColorSpace [/Indexed /DeviceRGB '.(strlen($info['pal'])/3-1).' '.($this->n+1).' 0 R]');
		else
		{
			$this->_out('/ColorSpace /'.$info['cs']);
			if($info['cs']=='DeviceCMYK')
				$this->_out('/Decode [1 0 1 0 1 0 1 0]');
		}
		$this->_out('/BitsPerComponent '.$info['bpc']);
		if(isset($info['f']))
			$this->_out('/Filter /'.$info['f']);
		if(isset($info['parms']))
			$this->_out($info['parms']);
		if(isset($info['trns']) && is_array($info['trns']))
		{
			$trns='';
			for($i=0;$i<count($info['trns']);$i++)
				$trns.=$info['trns'][$i].' '.$info['trns'][$i].' ';
			$this->_out('/Mask ['.$trns.']');
		}
		$this->_out('/Length '.strlen($info['data']).'>>');
		$this->_putstream($info['data']);
		unset($this->images[$file]['data']);
		$this->_out('endobj');
		//Palette
		if($info['cs']=='Indexed')
		{
			$this->_newobj();
			$pal=($this->compress) ? gzcompress($info['pal']) : $info['pal'];
			$this->_out('<<'.$filter.'/Length '.strlen($pal).'>>');
			$this->_putstream($pal);
			$this->_out('endobj');
		}
	}
}
Continued in next post >>

Last edited by Guud Spelar : 03-23-2009 at 08:15 AM. Reason: replaced tempnam('.', 'mska') with tempnam(WRITER_TEMPDIR, 'mska') in ImagePngWithAlpha
Reply With Quote
  #2  
Old 07-02-2008, 11:20 AM
Guud Spelar Guud Spelar is offline
Junior Member
 
Join Date: Jul 2008
Posts: 3
Default

Replace the 'alpha' error message in _parsepng with a string return
Code:
// Extract info from a PNG file
function _parsepng($file) {
  $f = fopen($file,'rb');
  if (!$f) {
	$this->Error('Can\'t open image file: '.$file);
  };

  //Check signature
  if (fread($f,8)!=chr(137).'PNG'.chr(13).chr(10).chr(26).chr(10)) {
	$this->Error('Not a PNG file: '.$file);
  };

  //Read header chunk
  fread($f,4);
  if (fread($f,4)!='IHDR') {
	$this->Error('Incorrect PNG file: '.$file);
  };

  $w = $this->_freadint($f);
  $h = $this->_freadint($f);
  $bpc = ord(fread($f,1));

  if ($bpc>8) {
	$this->Error('16-bit depth not supported: '.$file);
  };

  $ct=ord(fread($f,1));
  if ($ct==0) {
	$colspace='DeviceGray';
  } elseif($ct==2) {
	$colspace='DeviceRGB';
  } elseif($ct==3) {
	$colspace='Indexed';
  } else {
	//$this->Error('Alpha channel not supported: '.$file); ###
	return 'alpha'; ###
  };

  if (ord(fread($f,1))!=0) {
	$this->Error('Unknown compression method: '.$file);
  };

  if (ord(fread($f,1))!=0) {
	$this->Error('Unknown filter method: '.$file);
  };

  if (ord(fread($f,1))!=0) {
	$this->Error('Interlacing not supported: '.$file);
  };

  fread($f,4);
  $parms='/DecodeParms <</Predictor 15 /Colors '.($ct==2 ? 3 : 1).' /BitsPerComponent '.$bpc.' /Columns '.$w.'>>';

  //Scan chunks looking for palette, transparency and image data
  $pal='';
  $trns='';
  $data='';
  do {
	$n=$this->_freadint($f);
	$type=fread($f,4);
	if ($type=='PLTE') {
	  //Read palette
	  $pal=fread($f,$n);
	  fread($f,4);
	} elseif($type=='tRNS') {
	  //Read transparency info
	  $t=fread($f,$n);
	  if ($ct==0) {
		$trns=array(ord(substr($t,1,1)));
	  } elseif($ct==2) {
		$trns=array(ord(substr($t,1,1)),ord(substr($t,3,1)),ord(substr($t,5,1)));
	  } else {
		$pos=strpos($t,chr(0));
		if ($pos!==false) {
		  $trns=array($pos);
		}
	  }
	  fread($f,4);
	} elseif ($type=='IDAT') {
	  //Read image data block
	  $data.=fread($f,$n);
	  fread($f,4);
	} elseif ($type=='IEND') {
	  break;
	} else {
	  fread($f,$n+4);
	};
  } while($n);

  if ($colspace=='Indexed' && empty($pal)) {
	$this->Error('Missing palette in '.$file);
  };
  fclose($f);
  return array('w'     => $w,
			   'h'     => $h,
			   'cs'    => $colspace,
			   'bpc'   => $bpc,
			   'f'     => 'FlateDecode',
			   'parms' => $parms,
			   'pal'   => $pal,
			   'trns'  => $trns,
			   'data'  => $data);
}

File: output.fpdf.class.php
Class: OutputDriverFPDF


Change imagepng() to copy(). I'm not sure if this is the best way to do this, but it worked for me. For some reason, I don't know why, when imagepng is used at this point, it causes the PNG to lose its alpha channel. So instead of using imagepng to create a new image in the temp directory from the cached file, it just copies the file from the cache folder to the temp folder.

Code:
function _mktempimage($image) {   
	$tempnam = tempnam(WRITER_TEMPDIR, WRITER_FILE_PREFIX);

	switch ($image->get_type()) {
		case 'image/png':
			$filename = $tempnam . '.png';
			copy($image->get_filename(),$filename);	  ##
			break;
		
		case 'image/jpeg':
			default:
			$filename = $tempnam . '.jpg';
			imagejpeg($image->get_handle(), $filename);
			break;
    }

    unlink($tempnam);
    return $filename;
}
Reply With Quote
  #3  
Old 07-30-2008, 08:45 AM
TTrek TTrek is offline
Junior Member
 
Join Date: Feb 2006
Posts: 15
Default

Will this be available in future release?
Reply With Quote
  #4  
Old 09-10-2009, 01:13 AM
kevin.mcisaac kevin.mcisaac is offline
Junior Member
 
Join Date: Jun 2009
Location: Sydney, Australia
Posts: 2
Default

Guud thank you so much. This was tremendously helpful
Reply With Quote
Reply


Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off


All times are GMT -7. The time now is 11:02 PM.


Powered by vBulletin® Version 3.6.7
Copyright ©2000 - 2010, Jelsoft Enterprises Ltd.
(c) 1999-2007 TUFaT.com