Gradients

Informations

Author: Andreas Würmser
License: FPDF

Description

Paints linear and radial gradients as well as multi-color gradients (coons patch meshes) inside a rectangle.

LinearGradient(float x, float y, float w, float h, array col1, array col2 [, array coords])

x: abscissa of the top left corner of the rectangle.
y: ordinate of the top left corner of the rectangle.
w: width of the rectangle.
h: height of the rectangle.
col1: first color (RGB components).
col2: second color (RGB components).
coords: array of the form (x1, y1, x2, y2) which defines the gradient vector (see linear_gradient_coords.jpg). The default value is from left to right (x1=0, y1=0, x2=1, y2=0).

RadialGradient(float x, float y, float w, float h, array col1, array col2 [, array coords])

x: abscissa of the top left corner of the rectangle.
y: ordinate of the top left corner of the rectangle.
w: width of the rectangle.
h: height of the rectangle.
col1: first color (RGB components).
col2: second color (RGB components).
coords: array of the form (fx, fy, cx, cy, r) where (fx, fy) is the starting point of the gradient with color1, (cx, cy) is the center of the circle with color2, and r is the radius of the circle (see radial_gradient_coords.jpg). (fx, fy) should be inside the circle, otherwise some areas will not be defined.

CoonsPatchMesh(float x, float y, float w, float h, array col1, array col2, array col3, array col4 [, array coords [, float coords_min [, float coords_max]]])

x: abscissa of the top left corner of the rectangle.
y: ordinate of the top left corner of the rectangle.
w: width of the rectangle.
h: height of the rectangle.
col1: first color (lower left corner) (RGB components).
col2: second color (lower right corner) (RGB components).
col3: third color (upper right corner) (RGB components).
col4: fourth color (upper left corner) (RGB components).
coords:

- for one patch mesh

array(float x1, float y1, .... float x12, float y12): 12 pairs of coordinates (normally from 0 to 1) which specify the Bézier control points that define the patch. First pair is the lower left edge point, next is its right control point (control point 2). Then the other points are defined in the order: control point 1, edge point, control point 2 going counter-clockwise around the patch. Last (x12, y12) is the first edge point's left control point (control point 1).

- for two or more patch meshes

array[number of patches]: arrays with the following keys for each patch:
f: where to put that patch (0 = first patch, 1, 2, 3 = right, top and left of precedent patch - I didn't figure this out completely - just try and error ;-)
points: 12 pairs of coordinates of the Bézier control points as above for the first patch, 8 pairs of coordinates for the following patches, ignoring the coordinates already defined by the precedent patch (I also didn't figure out the order of these - also: try and see what's happening)
colors: must be 4 colors for the first patch, 2 colors for the following patches

coords_min: minimum value used by the coordinates. If a coordinate's value is smaller than this it will be cut to coords_min. default: 0
coords_max: maximum value used by the coordinates. If a coordinate's value is greater than this it will be cut to coords_max. default: 1

Source

<?php
require('fpdf.php');

class PDF_Gradients extends FPDF{

    protected $gradients = array();

    function LinearGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0,0,1,0)){
        $this->Clip($x,$y,$w,$h);
        $this->Gradient(2,$col1,$col2,$coords);
    }

    function RadialGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0.5,0.5,0.5,0.5,1)){
        $this->Clip($x,$y,$w,$h);
        $this->Gradient(3,$col1,$col2,$coords);
    }

    function CoonsPatchMesh($x, $y, $w, $h, $col1=array(), $col2=array(), $col3=array(), $col4=array(), $coords=array(0.00,0.0,0.33,0.00,0.67,0.00,1.00,0.00,1.00,0.33,1.00,0.67,1.00,1.00,0.67,1.00,0.33,1.00,0.00,1.00,0.00,0.67,0.00,0.33), $coords_min=0, $coords_max=1){
        $this->Clip($x,$y,$w,$h);        
        $n = count($this->gradients)+1;
        $this->gradients[$n]['type']=6; //coons patch mesh
        //check the coords array if it is the simple array or the multi patch array
        if(!isset($coords[0]['f'])){
            //simple array -> convert to multi patch array
            if(!isset($col1[1]))
                $col1[1]=$col1[2]=$col1[0];
            if(!isset($col2[1]))
                $col2[1]=$col2[2]=$col2[0];
            if(!isset($col3[1]))
                $col3[1]=$col3[2]=$col3[0];
            if(!isset($col4[1]))
                $col4[1]=$col4[2]=$col4[0];
            $patch_array[0]['f']=0;
            $patch_array[0]['points']=$coords;
            $patch_array[0]['colors'][0]['r']=$col1[0];
            $patch_array[0]['colors'][0]['g']=$col1[1];
            $patch_array[0]['colors'][0]['b']=$col1[2];
            $patch_array[0]['colors'][1]['r']=$col2[0];
            $patch_array[0]['colors'][1]['g']=$col2[1];
            $patch_array[0]['colors'][1]['b']=$col2[2];
            $patch_array[0]['colors'][2]['r']=$col3[0];
            $patch_array[0]['colors'][2]['g']=$col3[1];
            $patch_array[0]['colors'][2]['b']=$col3[2];
            $patch_array[0]['colors'][3]['r']=$col4[0];
            $patch_array[0]['colors'][3]['g']=$col4[1];
            $patch_array[0]['colors'][3]['b']=$col4[2];
        }
        else{
            //multi patch array
            $patch_array=$coords;
        }
        $bpcd=65535; //16 BitsPerCoordinate
        //build the data stream
        $this->gradients[$n]['stream']='';
        for($i=0;$i<count($patch_array);$i++){
            $this->gradients[$n]['stream'].=chr($patch_array[$i]['f']); //start with the edge flag as 8 bit
            for($j=0;$j<count($patch_array[$i]['points']);$j++){
                //each point as 16 bit
                $patch_array[$i]['points'][$j]=(($patch_array[$i]['points'][$j]-$coords_min)/($coords_max-$coords_min))*$bpcd;
                if($patch_array[$i]['points'][$j]<0) $patch_array[$i]['points'][$j]=0;
                if($patch_array[$i]['points'][$j]>$bpcd) $patch_array[$i]['points'][$j]=$bpcd;
                $val=(int)floor($patch_array[$i]['points'][$j]);
                $this->gradients[$n]['stream'].=chr(intdiv($val,256));
                $this->gradients[$n]['stream'].=chr($val%256);
            }
            for($j=0;$j<count($patch_array[$i]['colors']);$j++){
                //each color component as 8 bit
                $this->gradients[$n]['stream'].=chr($patch_array[$i]['colors'][$j]['r']);
                $this->gradients[$n]['stream'].=chr($patch_array[$i]['colors'][$j]['g']);
                $this->gradients[$n]['stream'].=chr($patch_array[$i]['colors'][$j]['b']);
            }
        }
        //paint the gradient
        $this->_out('/Sh'.$n.' sh');
        //restore previous Graphic State
        $this->_out('Q');
    }

    function Clip($x,$y,$w,$h){
        //save current Graphic State
        $s='q';
        //set clipping area
        $s.=sprintf(' %.2F %.2F %.2F %.2F re W n', $x*$this->k, ($this->h-$y)*$this->k, $w*$this->k, -$h*$this->k);
        //set up transformation matrix for gradient
        $s.=sprintf(' %.3F 0 0 %.3F %.3F %.3F cm', $w*$this->k, $h*$this->k, $x*$this->k, ($this->h-($y+$h))*$this->k);
        $this->_out($s);
    }

    function Gradient($type, $col1, $col2, $coords){
        $n = count($this->gradients)+1;
        $this->gradients[$n]['type']=$type;
        if(!isset($col1[1]))
            $col1[1]=$col1[2]=$col1[0];
        $this->gradients[$n]['col1']=sprintf('%.3F %.3F %.3F',($col1[0]/255),($col1[1]/255),($col1[2]/255));
        if(!isset($col2[1]))
            $col2[1]=$col2[2]=$col2[0];
        $this->gradients[$n]['col2']=sprintf('%.3F %.3F %.3F',($col2[0]/255),($col2[1]/255),($col2[2]/255));
        $this->gradients[$n]['coords']=$coords;
        //paint the gradient
        $this->_out('/Sh'.$n.' sh');
        //restore previous Graphic State
        $this->_out('Q');
    }

    function _putshaders(){
        foreach($this->gradients as $id=>$grad){  
            if($grad['type']==2 || $grad['type']==3){
                $this->_newobj();
                $this->_put('<<');
                $this->_put('/FunctionType 2');
                $this->_put('/Domain [0.0 1.0]');
                $this->_put('/C0 ['.$grad['col1'].']');
                $this->_put('/C1 ['.$grad['col2'].']');
                $this->_put('/N 1');
                $this->_put('>>');
                $this->_put('endobj');
                $f1=$this->n;
            }
            
            $this->_newobj();
            $this->_put('<<');
            $this->_put('/ShadingType '.$grad['type']);
            $this->_put('/ColorSpace /DeviceRGB');
            if($grad['type']=='2'){
                $this->_put(sprintf('/Coords [%.3F %.3F %.3F %.3F]',$grad['coords'][0],$grad['coords'][1],$grad['coords'][2],$grad['coords'][3]));
                $this->_put('/Function '.$f1.' 0 R');
                $this->_put('/Extend [true true] ');
                $this->_put('>>');
            }
            elseif($grad['type']==3){
                //x0, y0, r0, x1, y1, r1
                //at this time radius of inner circle is 0
                $this->_put(sprintf('/Coords [%.3F %.3F 0 %.3F %.3F %.3F]',$grad['coords'][0],$grad['coords'][1],$grad['coords'][2],$grad['coords'][3],$grad['coords'][4]));
                $this->_put('/Function '.$f1.' 0 R');
                $this->_put('/Extend [true true] ');
                $this->_put('>>');
            }
            elseif($grad['type']==6){
                $this->_put('/BitsPerCoordinate 16');
                $this->_put('/BitsPerComponent 8');
                $this->_put('/Decode[0 1 0 1 0 1 0 1 0 1]');
                $this->_put('/BitsPerFlag 8');
                $this->_put('/Length '.strlen($grad['stream']));
                $this->_put('>>');
                $this->_putstream($grad['stream']);
            }
            $this->_put('endobj');
            $this->gradients[$id]['id']=$this->n;
        }
    }

    function _putresourcedict(){
        parent::_putresourcedict();
        $this->_put('/Shading <<');
        foreach($this->gradients as $id=>$grad)
             $this->_put('/Sh'.$id.' '.$grad['id'].' 0 R');
        $this->_put('>>');
    }

    function _putresources(){
        $this->_putshaders();
        parent::_putresources();
    }
}
?>

Example

<?php
require('gradients.php');

$pdf = new PDF_Gradients();

//first page
$pdf->AddPage();
$pdf->SetFont('Arial','',14);
$pdf->Cell(0,5,'Page 1',0,1,'C');
$pdf->Ln();

//set colors for gradients (r,g,b) or (grey 0-255)
$red=array(255,0,0);
$blue=array(0,0,200);
$yellow=array(255,255,0);
$green=array(0,255,0);
$white=array(255);
$black=array(0);

//set the coordinates x1,y1,x2,y2 of the gradient (see linear_gradient_coords.jpg)
$coords=array(0,0,1,0);

//paint a linear gradient
$pdf->LinearGradient(20,25,80,80,$red,$blue,$coords);

//set the coordinates fx,fy,cx,cy,r of the gradient (see radial_gradient_coords.jpg)
$coords=array(0.5,0.5,1,1,1.2);

//paint a radial gradient
$pdf->RadialGradient(110,25,80,80,$white,$black,$coords);

//paint a coons patch mesh with default coordinates
$pdf->CoonsPatchMesh(20,115,80,80,$yellow,$blue,$green,$red);

//set the coordinates for the cubic Bézier points x1,y1 ... x12, y12 of the patch (see coons_patch_mesh_coords.jpg)
$coords=array(0.00,0.00, 0.33,0.20,             //lower left
              0.67,0.00, 1.00,0.00, 0.80,0.33,  //lower right
              0.80,0.67, 1.00,1.00, 0.67,0.80,  //upper right
              0.33,1.00, 0.00,1.00, 0.20,0.67,  //upper left
              0.00,0.33);                       //lower left
$coords_min=0;   //minimum value of the coordinates
$coords_max=1;   //maximum value of the coordinates

//paint a coons patch gradient with the above coordinates 
$pdf->CoonsPatchMesh(110,115,80,80,$yellow,$blue,$green,$red,$coords,$coords_min,$coords_max);

//second page
$pdf->AddPage();
$pdf->Cell(0,5,'Page 2',0,1,'C');
$pdf->Ln();

//first patch: f = 0
$patch_array[0]['f']=0;
$patch_array[0]['points']=array(0.00,0.00, 0.33,0.00,
                                0.67,0.00, 1.00,0.00, 1.00,0.33,
                                0.8,0.67, 1.00,1.00, 0.67,0.8,
                                0.33,1.80, 0.00,1.00, 0.00,0.67,
                                0.00,0.33);
$patch_array[0]['colors'][0]=array('r'=>255,'g'=>255,'b'=>0);
$patch_array[0]['colors'][1]=array('r'=>0,'g'=>0,'b'=>255);
$patch_array[0]['colors'][2]=array('r'=>0,'g'=>255,'b'=>0);
$patch_array[0]['colors'][3]=array('r'=>255,'g'=>0,'b'=>0);

//second patch - above the other: f = 2
$patch_array[1]['f']=2;
$patch_array[1]['points']=array(0.00,1.33,
                                0.00,1.67, 0.00,2.00, 0.33,2.00,
                                0.67,2.00, 1.00,2.00, 1.00,1.67,
                                1.5,1.33);
$patch_array[1]['colors'][0]=array('r'=>0,'g'=>0,'b'=>0);
$patch_array[1]['colors'][1]=array('r'=>255,'g'=>0,'b'=>255);

//third patch - right of the above: f = 3
$patch_array[2]['f']=3;
$patch_array[2]['points']=array(1.33,0.80,
                                1.67,1.50, 2.00,1.00, 2.00,1.33,
                                2.00,1.67, 2.00,2.00, 1.67,2.00,
                                1.33,2.00);
$patch_array[2]['colors'][0]=array('r'=>0,'g'=>255,'b'=>255);
$patch_array[2]['colors'][1]=array('r'=>0,'g'=>0,'b'=>0);

//fourth patch - below the above, which means left(?) of the above: f = 1
$patch_array[3]['f']=1;
$patch_array[3]['points']=array(2.00,0.67,
                                2.00,0.33, 2.00,0.00, 1.67,0.00,
                                1.33,0.00, 1.00,0.00, 1.00,0.33,
                                0.8,0.67);
$patch_array[3]['colors'][0]=array('r'=>0,'g'=>0,'b'=>0);
$patch_array[3]['colors'][1]=array('r'=>0,'g'=>0,'b'=>255);

$coords_min=0;
$coords_max=2;

$pdf->CoonsPatchMesh(10,25,190,200,'','','','',$patch_array,$coords_min,$coords_max);

$pdf->Output();
?>
View the result here.

Download

ZIP | TGZ