#########
# Author: rmp@sanger.ac.uk
# Maintainer: webmaster@sanger.ac.uk
# Created: 2001
# Last Modified: dj3 2005-09-01 add chevron line style a la UCSC (ticket 25769)
# dj3 2005-08-31 add tiling ability to Polys (was just Rects)
# rmp 2005-08-09 hatched fill-pattern support (subs tile and render_Rect): set $glyph->{'hatched'} = true|false and $glyph->{'hatchcolour'} = 'darkgrey';
# rmp 2004-12-14 initial stringFT support
#
package Sanger::Graphics::Renderer::gif;
use strict;
#use warnings;
use base qw(Sanger::Graphics::Renderer);
use GD;
## use GD::Text::Align;
# use Math::Bezier;
sub init_canvas {
my ($self, $config, $im_width, $im_height) = @_;
$self->{'im_width'} = $im_width;
$self->{'im_height'} = $im_height;
if( $self->{'config'}->can('species_defs') ) {
my $ST = $self->{'config'}->species_defs->ENSEMBL_STYLE || {};
$self->{'ttf_path'} ||= $ST->{'GRAPHIC_TTF_PATH'};
}
$self->{'ttf_path'} ||= '/usr/local/share/fonts/ttfonts/';
my $canvas = GD::Image->new(
$im_width * $self->{'sf'},
$im_height * $self->{'sf'}
);
$canvas->colorAllocate($config->colourmap->rgb_by_name($config->bgcolor()));
$self->canvas($canvas);
}
sub add_canvas_frame {
my ($self, $config, $im_width, $im_height) = @_;
return;
return if (defined $config->{'no_image_frame'});
# custom || default image frame colour
my $imageframecol = $config->{'image_frame_colour'} || 'black';
my $framecolour = $self->colour($imageframecol);
# for contigview bottom box we need an extra thick border...
if ($config->script() eq 'contigviewbottom'){
$self->{'canvas'}->rectangle(1, 1, $im_width * $self->{sf} -2, $im_height * $self->{sf}-2, $framecolour);
}
$self->{'canvas'}->rectangle(
0, 0, $im_width * $self->{sf} -1, $im_height * $self->{sf} -1, $framecolour
);
}
sub canvas {
my ($self, $canvas) = @_;
if(defined $canvas) {
$self->{'canvas'} = $canvas;
} else {
return $self->{'canvas'}->gif();
}
}
#########
# colour caching routine.
# GD can only store 256 colours, so need to cache the ones we colorAllocate. (Doh!)
#
sub colour {
my ($self, $id) = @_;
$id ||= 'black';
$self->{'_GDColourCache'}->{$id} ||= $self->{'canvas'}->colorAllocate($self->{'colourmap'}->rgb_by_name($id));
return $self->{'_GDColourCache'}->{$id};
}
#########
# build mini GD images which can be used as fill patterns
# should probably support different density hatching too
#
sub tile {
my ($self, $id, $pattern) = @_;
my $bg_color = 'white';
$id ||= 'darkgrey';
$pattern ||= 'hatch_ne';
my $key = join ':', $bg_color, $id, $pattern;
unless($self->{'_GDTileCache'}->{$key}) {
my $tile;
my $pattern_def = $Sanger::Graphics::Renderer::patterns->{$pattern};
if( $pattern_def ) {
$tile = GD::Image->new(@{ $pattern_def->{'size'}} );
my $bg = $tile->colorAllocate($self->{'colourmap'}->rgb_by_name($bg_color));
my $fg = $tile->colorAllocate($self->{'colourmap'}->rgb_by_name($id));
$tile->transparent($bg);
$tile->line(@$_, $fg ) foreach( @{$pattern_def->{'lines'}||[]});
foreach my $poly_def ( @{$pattern_def->{'polys'}||[]} ) {
my $poly = new GD::Polygon;
foreach( @$poly_def ) {
$poly->addPt( map { $_ } @$_ );
}
$tile->filledPolygon($poly,$fg);
}
}
$self->{'_GDTileCache'}->{$key} = $tile;
}
return $self->{'_GDTileCache'}->{$key};
}
sub render_Rect {
my ($self, $glyph) = @_;
my $canvas = $self->{'canvas'};
my $gcolour = $glyph->{'colour'};
my $gbordercolour = $glyph->{'bordercolour'};
# (avc)
# this is a no-op to let us define transparent glyphs
# and which can still have an imagemap area BUT make
# sure it is smaller than the carrent largest glyph in
# this glyphset because its height is not recorded!
if (defined $gcolour && $gcolour eq 'transparent') {
return;
}
my $bordercolour = $self->colour($gbordercolour);
my $colour = $self->colour($gcolour);
my $x1 = $self->{sf} * $glyph->{'pixelx'};
my $x2 = $self->{sf} * ( $glyph->{'pixelx'} + $glyph->{'pixelwidth'} );
my $y1 = $self->{sf} * $glyph->{'pixely'};
my $y2 = $self->{sf} * ( $glyph->{'pixely'} + $glyph->{'pixelheight'} );
$canvas->filledRectangle($x1, $y1, $x2, $y2, $colour) if(defined $gcolour);
if($glyph->{'pattern'}) {
$canvas->setTile($self->tile($glyph->{'patterncolour'}, $glyph->{'pattern'}));
$canvas->filledRectangle($x1, $y1, $x2, $y2, gdTiled);
}
$canvas->rectangle($x1, $y1, $x2, $y2, $bordercolour) if(defined $gbordercolour);
}
sub render_Text {
my ($self, $glyph) = @_;
return unless $glyph->{'text'};
my $font = $glyph->font();
my $colour = $self->colour($glyph->{'colour'});
########## Stock GD fonts
my $left = $self->{sf} * $glyph->{'pixelx'} || 0;
my $textwidth = $self->{sf} * $glyph->{'textwidth'} || 0;
my $top = $self->{sf} * $glyph->{'pixely'} || 0;
my $textheight = $self->{sf} * $glyph->{'pixelheight'} || 0;
my $halign = $glyph->{'halign'} || '';
if($halign eq 'right' ) {
$left += $glyph->{'pixelwidth'} * $self->{sf} - $textwidth;
} elsif($halign ne 'left' ) {
$left += ($glyph->{'pixelwidth'} * $self->{sf} - $textwidth)/2;
}
if($font eq 'Tiny') {
$self->{'canvas'}->string(gdTinyFont, $left, $top, $glyph->text(), $colour);
} elsif($font eq 'Small') {
$self->{'canvas'}->string(gdSmallFont, $left, $top, $glyph->text(), $colour);
} elsif($font eq 'MediumBold') {
$self->{'canvas'}->string(gdMediumBoldFont, $left, $top, $glyph->text(), $colour);
} elsif($font eq 'Large') {
$self->{'canvas'}->string(gdLargeFont, $left, $top, $glyph->text(), $colour);
} elsif($font eq 'Giant') {
$self->{'canvas'}->string(gdGiantFont, $left, $top, $glyph->text(), $colour);
} elsif($font) {
#########
# If we didn't recognise it already, assume it's a TrueType font
$self->{'canvas'}->stringTTF( $colour, $self->{'ttf_path'}.$font.'.ttf', $self->{sf} * $glyph->ptsize, 0, $left, $top+$textheight, $glyph->{'text'} );
### my ($cx, $cy) = $glyph->pixelcentre();
### my $xpt = $glyph->{'pixelx'} +
### ( $glyph->{'halign'} eq 'left' ? 0 : $glyph->{'halign'} eq 'right' ? 1 : 0.5 ) * $glyph->{'pixelwidth'};
### my $X = GD::Text::Align->new( $self->{'canvas'},
### 'valign' => $glyph->{'valign'} || 'center', 'halign' => $glyph->{'halign'} || 'center',
### 'colour' => $colour, 'font' => "$self->{'ttf_path'}$font.ttf",
### 'ptsize' => $glyph->ptsize(), 'text' => $glyph->text()
### );
### $X->draw( $xpt, $cy, $glyph->angle()||0 );
}
}
sub render_Circle {
my ($self, $glyph) = @_;
my $canvas = $self->{'canvas'};
my $gcolour = $glyph->{'colour'};
my $colour = $self->colour($gcolour);
my $filled = $glyph->filled();
my ($cx, $cy) = $glyph->pixelcentre();
my $method = $filled ? 'filledEllipse' : 'ellipse';
$canvas->$method(
$self->{sf} * ($cx-$glyph->{'pixelwidth'}/2),
$self->{sf} * ($cy-$glyph->{'pixelheight'}/2),
$self->{sf} * $glyph->{'pixelwidth'},
$self->{sf} * $glyph->{'pixelheight'},
$colour
);
# $canvas->fillToBorder($cx, $cy, $colour, $colour) if ($filled && $cx <= $self->{'im_width'});
}
sub render_Ellipse {
my ($self, $glyph) = @_;
my $canvas = $self->{'canvas'};
my $gcolour = $glyph->{'colour'};
my $colour = $self->colour($gcolour);
my $filled = $glyph->filled();
my ($cx, $cy) = $glyph->pixelcentre();
my $method = $filled ? 'filledEllipse' : 'ellipse';
$canvas->$method(
$self->{sf} * ($cx-$glyph->{'pixelwidth'}/2),
$self->{sf} * ($cy-$glyph->{'pixelheight'}/2),
$self->{sf} * $glyph->{'pixelwidth'},
$self->{sf} * $glyph->{'pixelheight'},
$colour
);
}
sub render_Intron {
my ($self, $glyph) = @_;
my ($colour, $xstart, $xmiddle, $xend, $ystart, $ymiddle, $yend, $strand, $gy);
$colour = $self->colour($glyph->{'colour'});
$gy = $self->{sf} * $glyph->{'pixely'};
$strand = $glyph->{'strand'};
$xstart = $self->{sf} * $glyph->{'pixelx'};
$xend = $xstart + $self->{sf} * $glyph->{'pixelwidth'};
$xmiddle = $xstart + $self->{sf} * $glyph->{'pixelwidth'} / 2;
$ystart = $gy + $self->{sf} * $glyph->{'pixelheight'}/2;
$yend = $ystart;
$ymiddle = $ystart + $self->{sf} * ( $strand == 1 ? -1 : 1 ) * $glyph->{'pixelheight'} * 3/8;
$self->{'canvas'}->line($xstart, $ystart, $xmiddle, $ymiddle, $colour);
$self->{'canvas'}->line($xmiddle, $ymiddle, $xend, $yend, $colour);
}
sub render_Line {
my ($self, $glyph) = @_;
my $colour = $self->colour($glyph->{'colour'});
my $x1 = $self->{sf} * $glyph->{'pixelx'} + 0;
my $y1 = $self->{sf} * $glyph->{'pixely'} + 0;
my $x2 = $x1 + $self->{sf} * $glyph->{'pixelwidth'};
my $y2 = $y1 + $self->{sf} * $glyph->{'pixelheight'};
if(defined $glyph->dotted() && $glyph->dotted ) {
$self->{'canvas'}->setStyle(gdTransparent,gdTransparent,gdTransparent,$colour,$colour,$colour);
$self->{'canvas'}->line($x1, $y1, $x2, $y2, gdStyled);
} else {
$self->{'canvas'}->line($x1, $y1, $x2, $y2, $colour);
}
if($glyph->chevron()) {
my $flip = ($glyph->{'strand'}<0);
my $len = $glyph->chevron(); $len=4 if $len<4;
my $n = int($self->{sf} * ($glyph->{'pixelwidth'} + $glyph->{'pixelheight'})/$len);
my $dx = $self->{sf} * $glyph->{'pixelwidth'} / $n; $dx*=-1 if $flip;
my $dy = $self->{sf} * $glyph->{'pixelheight'} / $n; $dy*=-1 if $flip;
my $ix = int($dx);
my $iy = int($dy);
my $i1x = int(-0.5*($ix-$iy));
my $i1y = int(-0.5*($iy+$ix));
my $i2x = int(-0.5*($ix+$iy));
my $i2y = int(-0.5*($iy-$ix));
for (;$n;$n--) {
my $tx = int($n*$dx)+($flip ? $x2 : $x1);
my $ty = int($n*$dy)+($flip ? $y2 : $y1);
$self->{'canvas'}->line($tx, $ty, $tx+$i1x, $ty+$i1y, $colour);
$self->{'canvas'}->line($tx, $ty, $tx+$i2x, $ty+$i2y, $colour);
}
}
}
sub render_Poly {
my ($self, $glyph) = @_;
my $canvas = $self->{'canvas'};
my $bordercolour = $self->colour($glyph->{'bordercolour'});
my $colour = $self->colour($glyph->{'colour'});
my $poly = new GD::Polygon;
return unless(defined $glyph->pixelpoints());
my @points = @{$glyph->pixelpoints()};
my $pairs_of_points = (scalar @points)/ 2;
for(my $i=0;$i<$pairs_of_points;$i++) {
my $x = shift @points;
my $y = shift @points;
$poly->addPt($self->{sf} * $x,$self->{sf} * $y);
}
if($glyph->{colour}) {
$canvas->filledPolygon($poly, $colour);
}
if($glyph->{'pattern'}) {
$canvas->setTile($self->tile($glyph->{'patterncolour'}, $glyph->{'pattern'}));
$canvas->filledPolygon($poly, gdTiled);
}
if($glyph->{bordercolour}) {
$canvas->polygon($poly, $bordercolour);
}
}
sub render_Composite {
my ($self, $glyph, $Ta) = @_;
#########
# draw & colour the fill area if specified
#
$self->render_Rect($glyph) if(defined $glyph->{'colour'});
#########
# now loop through $glyph's children
#
$self->SUPER::render_Composite($glyph,$Ta);
#########
# draw & colour the bounding area if specified
#
$glyph->{'colour'} = undef;
$self->render_Rect($glyph) if(defined $glyph->{'bordercolour'});
}
#sub render_Bezier {
# my ($self, $glyph) = @_;
#
# my $colour = $self->colour($glyph->{'colour'});
#
# return unless(defined $glyph->pixelpoints());
#
# my @coords = @{$glyph->pixelpoints()};
# my $bezier = Math::Bezier->new(\@coords);
# my $points = $bezier->curve($glyph->{'samplesize'}||20);
#
# my ($lx,$ly);
# while (@$points) {
# my ($x, $y) = splice(@$points, 0, 2);
#
# $self->{'canvas'}->line($lx, $ly, $x, $y, $colour) if(defined($lx) && defined($ly));
# ($lx, $ly) = ($x, $y);
# }
#}
sub render_Sprite {
my ($self, $glyph) = @_;
my $spritename = $glyph->{'sprite'} || 'unknown';
my $config = $self->config();
unless(exists $config->{'_spritecache'}->{$spritename}) {
my $libref = $config->get_parameter( 'spritelib');
my $lib = $libref->{$glyph->{'spritelib'} || 'default'};
my $fn = "$lib/$spritename.gif";
unless( -r $fn ){
warn( "$fn is unreadable by uid/gid" );
return;
}
$config->{'_spritecache'}->{$spritename} = GD::Image->newFromGif($fn);
if( !$config->{'_spritecache'}->{$spritename} ) {
$config->{'_spritecache'}->{$spritename} = GD::Image->newFromGif("$lib/missing.gif");
}
}
my $sprite = $config->{'_spritecache'}->{$spritename};
return unless $sprite;
my ($width, $height) = $sprite->getBounds();
my $METHOD = $self->{'canvas'}->can('copyRescaled') ? 'copyRescaled' : 'copyResized' ;
$self->{'canvas'}->$METHOD($sprite,
$self->{sf} * $glyph->{'pixelx'},
$self->{sf} * $glyph->{'pixely'},
0,
0,
$self->{sf} * $glyph->{'pixelwidth'} || 1,
$self->{sf} * $glyph->{'pixelheight'} || 1,
$width,
$height);
}
1;