Alpha-Channels / Masks

This script allows to use images (PNGs or JPGs) with alpha-channels

Informations

Author: Valentin Schmidt
License: Freeware
Version: 1.4
Requirements: FPDF 1.6

Description

This script allows to use images (PNGs or JPGs) with alpha-channels. The alpha-channel can be either supplied as separate 8-bit PNG ("mask"), or, for PNGs, also an internal alpha-channel can be used. For the latter, the GD 2.x extension is required.

Specifying a separate mask image has several advantages:
- no GD required.
- Better quality (full 8-bit alpha channel, whereas GD internally only supports 7-bit alpha channels)
- much faster (extraction of embedded alpha-channel has to be done pixel-wise)

function Image(string file, float x, float y [, float w [, float h [, string type [, mixed link [, boolean isMask [, int maskImg]]]]]])

Same parameters as for original Image()-method, with 2 additional (optional) parameters:
isMask: if specified and true, the image is used as mask for other images. In this case, the parameters x, y, w and h will be ignored and the mask image itself is not visible on the page.
maskImg: number of image resource (as returned by previously called Image() with isMask parameter set to true) that will be used as mask for this image.

function ImagePngWithAlpha(string file, float x, float y [, float w [, float h [, mixed link]]])

Same parameters as for original Image()-method, but without a type parameter.

Source


<?php
/*******************************************************************************
* Software: PDF_ImageAlpha
* Version:  1.4
* Date:     2009-12-28
* Author:   Valentin Schmidt 
*
* Requirements: FPDF 1.6
*
* This script allows to use images (PNGs or JPGs) with alpha-channels. 
* The alpha-channel can be either supplied as separate 8-bit PNG ("mask"),  
* or,  for PNGs,  also an internal alpha-channel can be used. 
* For the latter the GD 2.x extension is required.
*******************************************************************************/ 

require('../.inc/fpdf.php');

class PDF_ImageAlpha extends FPDF{

//Private properties
var $tmpFiles = array(); 

/*******************************************************************************
*                                                                              *
*                               Public methods                                 *
*                                                                              *
*******************************************************************************/
function Image($file, $x, $y, $w=0, $h=0, $type='', $link='',  $isMask=false,  $maskImg=0)
{
    //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 ($info=='alpha') return $this->ImagePngWithAlpha($file, $x, $y, $w, $h, $link);
        }
        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'];
    
    // embed hidden,  ouside the canvas
    if ((float)FPDF_VERSION>=1.7){
        if ($isMask) $x = ($this->CurOrientation=='P'?$this->CurPageSize[0]:$this->CurPageSize[1]) + 10;
    }else{
        if ($isMask) $x = ($this->CurOrientation=='P'?$this->CurPageFormat[0]:$this->CurPageFormat[1]) + 10;
    }
        
    $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']));
    if($link)
        $this->Link($x, $y, $w, $h, $link);
        
    return $info['i'];
}

// needs GD 2.x extension
// pixel-wise operation,  not very fast
function ImagePngWithAlpha($file, $x, $y, $w=0, $h=0, $link='')
{
    $tmp_alpha = tempnam('.',  'mska');
    $this->tmpFiles[] = $tmp_alpha;
    $tmp_plain = tempnam('.',  '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 = 255-($color_index>>24)*255/127; // GD alpha component: 7 bit only,  0..127!
            imagesetpixel($alpha_img,  $xpx,  $ypx,  $alpha);
        ++$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); 
    
    //embed image,  masked with previously embedded mask
    $this->Image($tmp_plain, $x, $y, $w, $h, 'PNG', $link,  false,  $maskImg);
}

function Close()
{
    parent::Close();
    // clean up tmp files
    foreach($this->tmpFiles as $tmp) @unlink($tmp);
}

/*******************************************************************************
*                                                                              *
*                               Private methods                                *
*                                                                              *
*******************************************************************************/
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');
        }
    }
}

// this method overwriing the original version is only needed to make the Image method support PNGs with alpha channels.
// if you only use the ImagePngWithAlpha method for such PNGs,  you can remove it from this script.
function _parsepng($file)
{
    //Extract info from a PNG 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->_readint($f);
    $h=$this->_readint($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 {
        fclose($f);      // the only changes are 
        return 'alpha';  // made in those 2 lines
    }
    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->_readint($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);
}

}

?>


Example

Here's an example that demonstrates both specifying alpha channels as separate "mask" images and using PNG's with internal alpha channels.


<?php

define('FPDF_FONTPATH',  '/var/www/user/valentin/home/php/fpdf/.inc/font');
require('fpdf_alpha.php');

$pdf=new PDF_ImageAlpha();

$pdf->AddPage();

$pdf->SetFont('arial', '', 16);
$pdf->MultiCell(0, 8,  str_repeat('Hello World! ',  180));

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

// A) provide image + separate 8-bit mask (best quality!)

//first embed mask image (w,  h,  x and y will be ignored,  the image will be scaled to the target image's size)
$maskImg = $pdf->Image('alpha.png',  0, 0, 0, 0,  '',  '',  true); 

//embed image,  masked with previously embedded mask
$pdf->Image('img.png', 5, 10, 100, 0, '', '',  false,  $maskImg);

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

// B) same as A),  but using a JPG file (+ mask defined as PNG)

// Notice: although B) uses the same mask as A),  we have to embed a copy of the original mask,  
// in other words: each mask can only be used once (TODO: this propably could be avoided)

//first embed mask image (w,  h,  x and y will be ignored,  the image will be scaled to the target image's size)
$maskImg2 = $pdf->Image('alpha2.png',  0, 0, 0, 0,  '',  '',  true);

//embed image,  masked with previously embedded mask
$pdf->Image('img.jpg', 105, 10, 100, 0, '', '',  false,  $maskImg2);

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

// C) use alpha channel from PNG (alpha channel converted to 7-bit by GD,  lower quality)
$pdf->ImagePngWithAlpha('image_with_alpha.png', 55, 100, 100, 0);

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

// D) same as C),  but using Image()-method that recognizes the alpha channel
$pdf->Image('image_with_alpha.png', 55, 190, 100, 0, 'PNG');

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

// send PDF to browser
$pdf->Output();

?>


View the result here.

Download

ZIP

Quelle

http://dasdeck.de/staff/valentin/fpdf/fpdf_alpha/, zuletzt abgerufen 02.10.2016 14:57