GD-Cairo
view release on metacpan or search on metacpan
lib/GD/Cairo.pm view on Meta::CPAN
};
use Cairo;
use Data::Dumper;
our $EXTENTS_SELF;
our $TRUECOLOR = 0;
our $ANTIALIAS = 0;
use vars qw( $AUTOLOAD );
# Preloaded methods go here.
sub _new
{
my( $class, @opts ) = @_;
my $self = bless {
background_color => undef,
colors => [],
operations => [],
transparent => undef,
thickness => 1,
brush => undef,
style => {},
}, $class;
}
sub newFromSurface
{
my( $class, $surface ) = @_;
my $self = $class->_new();
$self->{surface} = $surface;
$self->{context} = Cairo::Context->create( $surface );
$self->{context}->set_line_width( $self->{thickness} );
$self->{width} = $surface->get_width;
$self->{height} = $surface->get_height;
$EXTENTS_SELF = $self;
return $self;
}
sub new
{
my( $class, $w, $h, $truecolor ) = @_;
$truecolor = $TRUECOLOR if scalar(@_) == 3;
my $format = $truecolor ? 'argb32' : 'a8';
$format = 'argb32';
my $surface = Cairo::ImageSurface->create( $format, $w, $h );
return $class->newFromSurface( $surface );
}
sub newFromPngData
{
my( $class, $data, $truecolor ) = @_;
pos($data) = 0;
my $surface = Cairo::ImageSurface->create_from_png_stream(sub {
my( $closure, $length ) = @_;
use bytes;
my $buffer = substr($data,pos($data),$length);
pos($data) += $length;
return $buffer;
});
return $class->newFromSurface( $surface );
}
sub getCairoContext
{
$_[0]->{context};
}
sub getCairoImageSurface
{
$_[0]->{surface};
}
sub getCairoPattern
{
$_[0]->{brush};
}
sub trueColor
{
my( $self, $truecolor ) = @_;
$TRUECOLOR = $truecolor;
}
sub newPalette
{
my( $class, $w, $h ) = @_;
# my $surface = Cairo::ImageSurface->create( 'a8', $w, $h );
my $surface = Cairo::ImageSurface->create( 'argb32', $w, $h );
return $class->newFromSurface( $surface );
}
sub newTrueColor
{
my( $class, $w, $h ) = @_;
my $surface = Cairo::ImageSurface->create( 'argb32', $w, $h );
return $class->newFromSurface( $surface );
}
sub ignoreMissing
{
my( $warn ) = @_;
lib/GD/Cairo.pm view on Meta::CPAN
$color = $self->{colors}->[0];
}
return $self->_color_to_index( $color );
}
sub setPixel
{
my( $self, $x, $y, $color ) = @_;
if( $color == gdBrushed )
{
my $w = $self->{brush}->width;
my $h = $self->{brush}->height;
$self->copy( $self->{brush}, $x - $w/2, $y - $h/2, 0, 0, $w, $h );
}
else
{
$self->{pixels}->{"${x}x${y}"} = $color;
push @{$self->{operations}}, [
set_source_rgba => $self->_color( $color ),
set_line_width => [1],
set_antialias => ['none'],
move_to => [$x-1,$y],
line_to => [$x,$y],
stroke => []
];
}
}
sub rgb
{
my( $self, $index ) = @_;
return map { sprintf("%.0f", $_ * 255) } @{$self->{colors}->[$index]}[0..2];
}
sub transparent
{
my( $self, $index ) = @_;
if( 1 == @_ )
{
return defined $self->{transparent} ?
$self->_color_to_index( $self->{transparent} ) :
-1;
}
return $self->{transparent} = $index > -1 ?
$self->{colors}->[$index] :
-1;
}
*setTile = \&setBrush;
sub setBrush
{
my( $self, $image ) = @_;
unless( $image->isa( 'GD::Cairo' ) )
{
$image = GD::Cairo->newFromPngData( $image->png );
}
$self->{brush} = $image;
}
sub setStyle
{
my( $self, @colors ) = @_;
my %lines = $self->_convert_style_to_dashes( @colors );
$self->{style} = \%lines;
}
sub setThickness
{
my( $self, $thickness ) = @_;
$self->{thickness} = $thickness;
}
sub setAntiAliased
{
my( $self, $color ) = @_;
$self->{antialiased} = $self->_color( $color );
}
sub rectangle
{
my( $self, $x, $y, $x2, $y2, $color ) = @_;
my $shape = [
rectangle => [$x, $y, $x2-$x, $y2-$y],
];
$self->_stroke_shape( $shape, $color,
x => $x,
y => $y,
antialias => 'none'
);
}
sub filledRectangle
{
my( $self, $x, $y, $x2, $y2, $color ) = @_;
my $shape = [
rectangle => [$x, $y, $x2-$x, $y2-$y],
];
$self->_fill_shape( $shape, $color,
x => $x,
y => $y,
antialias => 'none'
);
}
sub _polygon
{
my( $self, $polygon, $color ) = @_;
lib/GD/Cairo.pm view on Meta::CPAN
my $shape = _arc( @_ );
$self->_stroke_shape( $shape, $color,
x => $x,
y => $y,
);
}
sub filledArc
{
my( $self, $x, $y, $w, $h, $s, $e, $color, $arc_style ) = @_;
return unless $w > 0 and $h > 0;
$arc_style ||= 0;
my $shape = [];
# Cairo doesn't support chords
if( $arc_style & gdChord )
{
$s = $s/180*PI;
$e = $e/180*PI;
my $x1 = $x + ($w/2) * cos($s);
my $y1 = $y + ($h/2) * sin($s);
my $x2 = $x + ($w/2) * cos($e);
my $y2 = $y + ($h/2) * sin($e);
push @$shape,
move_to => [$x1,$y1],
line_to => [$x2,$y2];
}
else
{
$shape = _arc( @_ );
}
push @$shape,
line_to => [$x, $y],
close_path => [];
if( $arc_style & gdNoFill )
{
$self->_stroke_shape( $shape, $color );
}
else
{
$self->_fill_shape( $shape, $color );
}
}
sub copy
{
my( $self, $sourceImage, $dstX, $dstY, $srcX, $srcY, $width, $height ) = @_;
unless( $sourceImage->isa( 'GD::Cairo' ) )
{
$sourceImage = GD::Cairo->newFromPngData( $sourceImage->png );
}
push @{$self->{operations}}, [
set_source_surface => [$sourceImage->{surface}, $dstX-$srcX, $dstY-$srcY],
rectangle => [$dstX,$dstY,$width,$height],
fill => []
];
}
*copyResampled = \©Resized;
sub copyResized
{
my( $self, $sourceImage, $dstX, $dstY, $srcX, $srcY, $destW, $destH, $srcW, $srcH ) = @_;
unless( $sourceImage->isa( 'GD::Cairo' ) )
{
$sourceImage = GD::Cairo->newFromPngData( $sourceImage->png );
}
my $scaleX = $destW / $srcW;
my $scaleY = $destH / $srcH;
push @{$self->{operations}}, [
set_source_surface => [$sourceImage->{surface}, 0, 0],
sub {
my( $cr ) = @_;
my $pattern = $cr->get_source;
$pattern->set_filter( 'bilinear' );
my $matrix = $pattern->get_matrix;
$matrix->translate( $srcX, $srcY );
$matrix->scale( 1/$scaleX, 1/$scaleY );
$matrix->translate( -1*$dstX, -1*$dstY );
$pattern->set_matrix( $matrix );
} => [],
translate => [$dstX,$dstY],
scale => [$scaleX,$scaleY],
rectangle => [0,0,$srcW,$srcH],
fill => [],
];
}
sub copyRotated
{
my( $self, $sourceImage, $dstX, $dstY, $srcX, $srcY, $width, $height, $angle ) = @_;
$angle = $angle/180*PI;
unless( $sourceImage->isa( 'GD::Cairo' ) )
{
$sourceImage = GD::Cairo->newFromPngData( $sourceImage->png );
}
my $w = $sourceImage->width;
my $h = $sourceImage->height;
push @{$self->{operations}}, [
set_source_surface => [$sourceImage->{surface}, 0, 0],
sub {
my( $cr ) = @_;
my $pattern = $cr->get_source;
$pattern->set_filter( 'bilinear' );
my $matrix = $pattern->get_matrix;
$matrix->translate( $w/2, $h/2 );
$matrix->rotate( $angle );
$matrix->translate( -1*$dstX, -1*$dstY );
$pattern->set_matrix( $matrix );
} => [],
translate => [$dstX, $dstY],
rotate => [$angle],
rectangle => [$width/-2,$height/-2,$width,$height],
fill => [],
];
}
sub _rotate_point
{
my( $x, $y, $ox, $oy, $angle ) = @_;
$x -= $ox;
$y -= $oy;
my $xx = $x * cos($angle) + $y * sin($angle);
my $yy = -1 * $x * sin($angle) + $y * cos($angle);
return( $xx + $ox, $yy + $oy );
}
sub _extents
{
my( $self, $font, $ptsize, $angle, $x, $y, $string ) = @_;
my $cr = $self->{context};
$cr->save;
$cr->select_font_face( $font, GC_FONT_SLANT_NORMAL, GC_FONT_SLANT_NORMAL );
$cr->set_font_size( $ptsize );
# $cr->rotate( $angle );
my $extents = $cr->text_extents( $string );
$cr->restore;
return (
_rotate_point( $x + $extents->{x_bearing},
$y + $extents->{y_bearing}, $x, $y, $angle ),
_rotate_point( $x + $extents->{x_bearing} + $extents->{width},
$y + $extents->{y_bearing}, $x, $y, $angle ),
_rotate_point( $x + $extents->{x_bearing} + $extents->{width},
$y + $extents->{y_bearing} + $extents->{height}, $x, $y, $angle ),
_rotate_point( $x + $extents->{x_bearing},
$y + $extents->{y_bearing} + $extents->{height}, $x, $y, $angle ),
);
lib/GD/Cairo.pm view on Meta::CPAN
$cr->restore;
}
foreach my $shape (@{$self->{operations}})
{
$cr->save;
for(my $i = 0; $i < @$shape; $i+=2)
{
my( $f, $opts ) = @$shape[$i,$i+1];
if( ref($f) eq 'CODE' )
{
&$f( $cr, @$opts );
}
else
{
$cr->$f( @$opts );
}
}
$cr->restore;
}
$cr->show_page;
}
sub _write_buffer
{
my( $self, $class ) = @_;
my $buffer = '';
my $surface = $class->create_from_stream( sub { $buffer .= $_[1] }, '', $self->width, $self->height );
my $context = Cairo::Context->create( $surface );
$self->{context} = $context;
$self->_render_operations;
return $buffer;
}
sub _write_file
{
my( $self, $filename, $class ) = @_;
my $surface = $class->create( $filename, $self->width, $self->height );
my $context = Cairo::Context->create( $surface );
$self->{context} = $context;
$self->_render_operations;
}
sub png
{
my( $self ) = @_;
$self->_render_operations;
my $buffer = '';
$self->{surface}->write_to_png_stream(sub { $buffer .= $_[1] }, '');
return $buffer;
}
sub writePng
{
my( $self, $filename ) = @_;
open(my $fh, ">", $filename) or die "Error writing to $filename: $!";
binmode($fh);
print $fh $self->png;
close($fh);
}
sub pdf
{
_write_buffer( $_[0], 'Cairo::PdfSurface' );
}
sub writePdf
{
_write_file( $_[0], $_[1], 'Cairo::PdfSurface' );
}
sub svg
{
_write_buffer( $_[0], 'Cairo::SvgSurface' );
}
sub writeSvg
{
_write_file( $_[0], $_[1], 'Cairo::SvgSurface' );
}
package GD::Cairo::Font;
# Utility class to create GD::Font stub classes that work with GD::Cairo
use strict;
our %GD_FONTS = (
gdTinyFont => {
nchars => 256,
offset => 0,
width => 5,
height => 8
},
gdSmallFont => {
nchars => 256,
offset => 0,
width => 6,
height => 13
},
gdMediumBoldFont => {
nchars => 256,
offset => 0,
width => 7,
height => 13
},
gdLargeFont => {
nchars => 256,
offset => 0,
width => 8,
height => 16
},
gdGiantFont => {
nchars => 256,
lib/GD/Cairo.pm view on Meta::CPAN
=head1 SYNOPSIS
use GD; # Needed for constants and GD::Polygon
use GD::Cairo;
# use GD;
use GD::Cairo qw( :gd ); # Import GD constants and fonts
# my $img = GD::Image->new( 400, 300, 1 );
my $img = GD::Cairo->new( 400, 300, 1 );
print $fh $img->svg;
=head1 DESCRIPTION
This module provides a GD API emulation for the Cairo graphics library. Cairo is a vector-based drawing package that aims to provide consistent output to many graphics contexts/formats.
=head1 METHODS
See <GD>.
=head2 GD::Cairo-specific methods
=over 4
=item GD::Cairo->new( WIDTH, HEIGHT [, TRUECOLOR ] )
Create a new image of WIDTH by HEIGHT. WIDTH and HEIGHT are in user-space units (e.g. pixels for PNG or points for PDF).
=item GD::Cairo::ignoreMissing( [ WARN ] )
Ignore any missing functionality in GD::Cairo that may be in GD.
=item $data = $img->png
Return the image in PNG format.
=item $data = $img->pdf
Return the image in PDF format.
=item $data = $img->svg
Return the image in SVG format.
=back
=head1 TODO
=over 4
=item new(*FILEHANDLE)
=item new($filename)
=item new($data)
=item newFrom*
(newFromPngData implemented.)
=item colorClosestHWB
=item setAntiAliasedDontBlend($color [,$flag])
=item dashedLine
This is deprecated anyway.
=item fillToBorder
Unlikely to ever work.
=item clone
=item trueColorToPalette
=item alphaBlending
=item saveAlpha
=item interlaced
Ignored.
=item compare($image2)
=item clip($x1,$y1,$x2,$y2)
=item boundsSafe($x,$y)
=item GD::Polygon, GD::Polyline
=item GD::Simple
=head1 BUGS
Patches/suggestions are welcome.
=head2 Images are always true colour
I don't think Cairo supports paletted images, see http://cairographics.org/manual/cairo-Image-Surfaces.html#cairo-format-t.
=head2 Alignment in PNG Output
PngSurface doesn't appear to reliably translate coordinates onto the surface e.g. a point at 0,0 doesn't get rendered at all.
=head2 StringFT/String/StringUp
StringFT* will always render using 'Sans-Serif' and String* using 'Monospace' (which depend on fontconfig). I need an example for loading fonts with Cairo.
=head2 SetBrush
GD renders brushes by repeatedly rendering the brush (an image) along the path the given shape provides. This isn't practically achievable with Cairo (AFAIK), so instead I repeat the image along the path/fill.
=head2 SetStyle
Does not support gdStyledBrushed.
=head2 Memory Usage
In order to support GD::Image::fill GD::Cairo builds a stack of operations, which makes it memory inefficient compared to writing direct to a GD::Image surface.
GD::Cairo also stores a hash entry for every pixel set with setPixel to support getPixel.
=head1 SEE ALSO
L<Cairo>, L<GD>, L<GD::SVG> (includes extensive discussion of why translating GD to a vector library is difficult).
http://cairographics.org/manual/
=head1 AUTHOR
Tim D Brody, E<lt>tdb01r@ecs.soton.ac.ukE<gt>
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2007 by Tim D Brody
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.8 or,
at your option, any later version of Perl 5 you may have available.
=cut
( run in 2.412 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )