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 = \&copyResized;
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 )