What I've learned about gradients (keywords: blend, shading, pattern)
Rick Measham <rick-6wk1kIbWGY9Wo+R/V/U2/[email protected]> Sat, 07 Feb 2009 22:37:59 +1100
| Newsgroups | gmane.comp.lang.perl.modules.pdfapi2 |
|---|---|
| Message-ID | <[email protected]> |
I've asked a couple of times about doing gradients with PDF::API2, and have had a couple of useful responses. I've taken the examples from Alan Fry, looked up what was happening in the PDF documentation and decided to capture what I have learned. There's some code attached that might be useful, though it's tied to DeviceRGB and doesn't let you do anything other than vertical gradients. Though it does let you blend between any number of colors .. 0. None of this has an interface in PDF::API2, so you have to deal with some ugly code. 1. Shading defines how a gradient works, it isn't something you can fill a shape with. 2. There are 7 shading types in PDF. Two are useful (to me): Linear (axial) and Radial. 3. A pattern turns a shading into something you can fill a shape with. 4. A shading runs from 0 to 1. A pattern defines a transformation matrix that extends it to the size you need. 5. A shading is defined as a function. The 0-to-1 value is the input. The color is the output. 6. There are four function types in PDF, but to do shading you just need one (or two .. see #8) 7. Type 2, Exponential Interpolation, is the most useful. Give it a start color and an end color and it interpolates every value in between. 8. Type 3, The stitching function type, lets you stitch together other functions. This is useful if you want to blend from Color A to Color B to Color C. The first type-2 function blends from Color A to Color B. The second type-2 function blends from Color B to Color C and the type-3 function joins these two together. 9. You set the stitching position for each function with the Bounds array. If you have the two functions as above, you just have one value in the Bounds array. If set to 0.2, then the first function blends from Color A to Color B between 0 and 0.2. The second function blends from Color B to Color C between 0.2 and 1. 10. Read the PDF Spec while looking at the code examples I've attached if the comments don't explain enough. 11. One day I might write a new tutorial on this to go with my 'basics' tutorial. Cheers! Rick Measham
gradient_example.pl
(application/x-perl, 3.9 KB)
#!/usr/bin/perl
# Blended fill
use strict;
use warnings;
use PDF::API2;
use PDF::API2::Basic::PDF::Utils;
use constant mm => 25.4 / 72;
use constant in => 1 / 72;
use constant pt => 1;
my $pdf = PDF::API2->new();
my $page = $pdf->page;
$page->mediabox('A4');
my $gfx=$page->gfx;
my $shade = linear_gradient(
$pdf,
[ 0.0000 => 1, 0, 0 ], # At zero, it's red
[ 0.7500 => 0, 1, 0 ], # At 75%, it's green
[ 1.0000 => 0, 0, 1 ], # At 100% it's blue
);
$gfx->strokecolor( (0.65) x 3 );
$gfx->linewidth(1);
$gfx->fillcolor( gradient_shape($pdf, $shade, 10/mm, 10/mm, 190/mm, 277/mm) );
$gfx->rect( 10/mm, 10/mm, 190/mm, 277/mm );
$gfx->fillstroke;
$pdf->saveas("$0.pdf");
# Arguments for gradient_shape are:
# * The PDF Object
# * The Shading Object (from linear_gradient below)
# * The X, Y, W and H of the shape (in relation to the page) to fill with the shading
# If you're filling a rect(), use the same values here as for the rect()
sub gradient_shape{
my $pdf = shift;
my $sh = shift;
my ($x, $y, $w, $h) = @_;
my $pt=$pdf->pattern();
$pt->{Type}=PDFName('Pattern');
$pt->{PatternType}=PDFNum(2); # 2 = Shading
# See section 4.2 of the spec for discussion of the matrix:
$pt->{Matrix}=PDFArray(PDFNum($w),PDFNum(0),PDFNum(0),PDFNum($h),PDFNum($x),PDFNum($y));
$pt->{Shading}=$sh;
return $pt
}
# This function creates the shading object you'll need for 'gradient_shape'
# Arguments for linear_gradient are:
# * The PDF Object
# * A list of points to blend between
# * Each point is an array ref comprised of the point (between 0 and 1) that the point represents
# * Followed by three values (for R, G, B).
# Example: [ 0 => 1, 0, 0 ] means that at the zero point, make it red.
# You need at least two points to blend between.
sub linear_gradient{
my( $pdf, @points ) = @_;
# Sort the points by their position
@points = sort { $a->[0] <=> $b->[0] } @points;
my $sh = $pdf->shading;
$sh->{ShadingType}=PDFNum(2); # 2 = Linear, 3 = Radial
$sh->{ColorSpace}=PDFName('DeviceRGB');
# If you want to mess with the angle, here's where you do it.
# This runs up the Y axis between the first and last point.
$sh->{Coords}=PDFArray(
PDFNum(0),PDFNum( $points[0][0] ), # X1, Y1
PDFNum(0),PDFNum( $points[$#points][0]) # X2, Y2
);
# If there's just two points, then use the exponential extrapolation function
if( @points == 2 ){
$sh->{Function} = PDFDict();
$sh->{Function}->{FunctionType}=PDFNum(2);
$sh->{Function}->{Domain}=PDFArray(PDFNum(0),PDFNum(1));
$sh->{Function}->{C0}=PDFArray(PDFNum($points[0][1]),PDFNum($points[0][2]),PDFNum($points[0][3]));
$sh->{Function}->{C1}=PDFArray(PDFNum($points[1][1]),PDFNum($points[1][2]),PDFNum($points[1][3]));
$sh->{Function}->{N}=PDFNum(1);
}
# If there's more than two points, we need to stitch
else {
# Make a part to blend between each of the points. It's an exponential extrapolation function as above.
my @part = ();
for my $p ( 0 .. $#points - 1 ){
$part[$p] = PDFDict();
$part[$p]->{FunctionType}=PDFNum(2);
$part[$p]->{Domain}=PDFArray(PDFNum(0),PDFNum(1));
$part[$p]->{C0}=PDFArray(PDFNum($points[$p][1]),PDFNum($points[$p][2]),PDFNum($points[$p][3]));
$part[$p]->{C1}=PDFArray(PDFNum($points[$p+1][1]),PDFNum($points[$p+1][2]),PDFNum($points[$p+1][3]));
$part[$p]->{N}=PDFNum(1);
}
# Now stitch them all together
$sh->{Function}=PDFDict();
$sh->{Function}->{FunctionType} = PDFNum(3); # 3 = Stitching
$sh->{Function}->{Functions} = PDFArray( @part );
# Set the bounds (the points at which each part starts/stops) according to the input points.
$sh->{Function}->{Bounds} = PDFArray( map { PDFNum( $_->[0] ) } @points[1 .. $#points-1] );
# Each part goes from 0 to 1
$sh->{Function}->{Encode} = PDFArray( ( PDFNum( 0 ), PDFNum( 1 ) ) x @part );
# As does the whole thing
$sh->{Function}->{Domain}=PDFArray(PDFNum(0),PDFNum(1));
}
# Extend infinitely in both directions
$sh->{Extend} = PDFArray( PDFBool(1), PDFBool(1) );
return $sh;
}
smime.p7s
(application/x-pkcs7-signature, 3.2 KB) - not displayed