AcePerl
view release on metacpan or search on metacpan
Ace/Graphics/Panel.pm view on Meta::CPAN
package Ace::Graphics::Panel;
# This embodies the logic for drawing multiple tracks.
use Ace::Graphics::Track;
use GD;
use Carp 'croak';
use strict;
use constant KEYLABELFONT => gdSmallFont;
use constant KEYSPACING => 10; # extra space between key columns
use constant KEYPADTOP => 5; # extra padding before the key starts
use constant KEYCOLOR => 'cornsilk';
*push_track = \&add_track;
# package global
my %COLORS;
# Create a new panel of a given width and height, and add lists of features
# one by one
sub new {
my $class = shift;
my %options = @_;
$class->read_colors() unless %COLORS;
my $length = $options{-length} || 0;
my $offset = $options{-offset} || 0;
my $spacing = $options{-spacing} || 5;
my $keycolor = $options{-keycolor} || KEYCOLOR;
my $keyspacing = $options{-keyspacing} || KEYSPACING;
$length ||= $options{-segment}->length if $options{-segment};
$offset ||= $options{-segment}->start-1 if $options{-segment};
return bless {
tracks => [],
width => $options{-width} || 600,
pad_top => $options{-pad_top}||0,
pad_bottom => $options{-pad_bottom}||0,
pad_left => $options{-pad_left}||0,
pad_right => $options{-pad_right}||0,
length => $length,
offset => $offset,
height => 0, # AUTO
spacing => $spacing,
keycolor => $keycolor,
keyspacing => $keyspacing,
},$class;
}
sub width {
my $self = shift;
my $d = $self->{width};
$self->{width} = shift if @_;
$d + $self->pad_left + $self->pad_right;
}
sub spacing {
my $self = shift;
my $d = $self->{spacing};
$self->{spacing} = shift if @_;
$d;
}
sub length {
my $self = shift;
my $d = $self->{length};
if (@_) {
my $l = shift;
$l = $l->length if ref($l) && $l->can('length');
$self->{length} = $l;
}
$d;
}
sub pad_top {
my $self = shift;
my $d = $self->{pad_top};
$self->{pad_top} = shift if @_;
$d || 0;
}
sub pad_bottom {
my $self = shift;
my $d = $self->{pad_bottom};
$self->{pad_bottom} = shift if @_;
$d || 0;
}
sub pad_left {
my $self = shift;
my $d = $self->{pad_left};
$self->{pad_left} = shift if @_;
$d || 0;
}
sub pad_right {
my $self = shift;
my $d = $self->{pad_right};
$self->{pad_right} = shift if @_;
$d || 0;
}
sub add_track {
my $self = shift;
# due to indecision, we accept features
# and/or glyph types in the first two arguments
my ($features,$glyph_name) = ([],'generic');
while ( $_[0] !~ /^-/) {
my $arg = shift;
$features = $arg and next if ref($arg);
$glyph_name = $arg and next unless ref($arg);
}
$self->_add_track($glyph_name,$features,+1,@_);
}
sub unshift_track {
my $self = shift;
# due to indecision, we accept features
# and/or glyph types in the first two arguments
my ($features,$glyph_name) = ([],'generic');
while ( (my $arg = shift) !~ /^-/) {
$features = $arg and next if ref($arg);
$glyph_name = $arg and next unless ref($arg);
}
$self->_add_track($glyph_name,$features,-1,@_);
}
sub _add_track {
my $self = shift;
my ($glyph_type,$features,$direction,@options) = @_;
unshift @options,'-offset' => $self->{offset} if defined $self->{offset};
unshift @options,'-length' => $self->{length} if defined $self->{length};
$features = [$features] unless ref $features eq 'ARRAY';
my $track = Ace::Graphics::Track->new($glyph_type,$features,@options);
$track->set_scale(abs($self->length),$self->{width});
$track->panel($self);
if ($direction >= 0) {
push @{$self->{tracks}},$track;
} else {
unshift @{$self->{tracks}},$track;
}
return $track;
}
sub height {
my $self = shift;
my $spacing = $self->spacing;
my $key_height = $self->format_key;
my $height = 0;
$height += $_->height + $spacing foreach @{$self->{tracks}};
$height + $key_height + $self->pad_top + $self->pad_bottom;
}
sub gd {
my $self = shift;
return $self->{gd} if $self->{gd};
my $width = $self->width;
my $height = $self->height;
my $gd = GD::Image->new($width,$height);
my %translation_table;
for my $name ('white','black',keys %COLORS) {
my $idx = $gd->colorAllocate(@{$COLORS{$name}});
$translation_table{$name} = $idx;
}
$self->{translations} = \%translation_table;
$self->{gd} = $gd;
my $offset = 0;
my $pl = $self->pad_left;
my $pt = $self->pad_top;
for my $track (@{$self->{tracks}}) {
$track->draw($gd,$pl,$offset+$pt);
$offset += $track->height + $self->spacing;
}
$self->draw_key($gd,$pl,$offset);
return $self->{gd} = $gd;
}
sub draw_key {
my $self = shift;
my ($gd,$left,$top) = @_;
my $key_glyphs = $self->{key_glyphs} or return;
my $color = $self->translate($self->{keycolor});
$gd->filledRectangle($left,$top,$self->width,$self->height,$color);
$gd->string(KEYLABELFONT,$left,KEYPADTOP+$top,"KEY:",1);
$top += KEYLABELFONT->height + KEYPADTOP;
$_->draw($gd,$left,$top) foreach @$key_glyphs;
}
# Format the key section, and return its height
sub format_key {
my $self = shift;
return $self->{key_height} if defined $self->{key_height};
my ($height,$width) = (0,0);
my %tracks;
my @glyphs;
# determine how many glyphs become part of the key
# and their max size
for my $track (@{$self->{tracks}}) {
next unless $track->option('key');
my $glyph = $track->keyglyph;
$tracks{$track} = $glyph;
my ($h,$w) = ($glyph->height,
$glyph->right-$glyph->left);
$height = $h if $h > $height;
$width = $w if $w > $width;
push @glyphs,$glyph;
}
$width += $self->{keyspacing};
# no key glyphs, no key
return $self->{key_height} = 0 unless @glyphs;
# now height and width hold the largest glyph, and $glyph_count
# contains the number of glyphs. We will format them into a
# box that is roughly 3 height/4 width (golden mean)
my $rows = 0;
my $cols = 0;
while (++$rows) {
$cols = @glyphs / $rows;
$cols = int ($cols+1) if $cols =~ /\./; # round upward for fractions
my $total_width = $cols * $width;
my $total_height = $rows * $width;
last if $total_width <= $self->width;
}
# move glyphs into row-major format
my $spacing = $self->spacing;
my $i = 0;
for (my $c = 0; $c < $cols; $c++) {
for (my $r = 0; $r < $rows; $r++) {
my $x = $c * ($width + $spacing);
my $y = $r * ($height + $spacing);
next unless defined $glyphs[$i];
$glyphs[$i]->move($x,$y);
$i++;
}
}
$self->{key_glyphs} = \@glyphs; # remember our key glyphs
# remember our key height
return $self->{key_height} = ($height+$spacing) * $rows + KEYLABELFONT->height +KEYPADTOP;
}
# reverse of translate(); given index, return rgb triplet
sub rgb {
my $self = shift;
my $idx = shift;
my $gd = $self->{gd} or return;
return $gd->rgb($idx);
}
sub translate {
my $self = shift;
if (@_ == 3) { # rgb triplet
my $gd = $self->gd or return 1;
return $gd->colorClosest(@_);
}
# otherwise...
my $color = shift;
if ($color =~ /^\#([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i) {
my $gd = $self->gd or return 1;
my ($r,$g,$b) = (hex($1),hex($2),hex($3));
return $gd->colorClosest($r,$g,$b);
} else {
my $table = $self->{translations} or return $self->fgcolor;
return $table->{$color} || 1;
}
}
sub set_pen {
my $self = shift;
my ($linewidth,$color) = @_;
return $self->{pens}{$linewidth} if $self->{pens}{$linewidth};
my $pen = $self->{pens}{$linewidth} = GD::Image->new($linewidth,$linewidth);
my @rgb = $self->rgb($color);
my $bg = $pen->colorAllocate(255,255,255);
my $fg = $pen->colorAllocate(@rgb);
$pen->fill(0,0,$fg);
$self->{gd}->setBrush($pen);
}
sub png {
my $gd = shift->gd;
$gd->png;
}
sub boxes {
my $self = shift;
my @boxes;
my $offset = 0;
my $pl = $self->pad_left;
my $pt = $self->pad_top;
for my $track (@{$self->{tracks}}) {
my $boxes = $track->boxes($pl,$offset+$pt);
push @boxes,@$boxes;
$offset += $track->height + $self->spacing;
}
return wantarray ? @boxes : \@boxes;
}
sub read_colors {
my $class = shift;
while (<DATA>) {
chomp;
last if /^__END__/;
my ($name,$r,$g,$b) = split /\s+/;
$COLORS{$name} = [hex $r,hex $g,hex $b];
}
}
sub color_names {
my $class = shift;
$class->read_colors unless %COLORS;
return wantarray ? keys %COLORS : [keys %COLORS];
}
1;
__DATA__
white FF FF FF
black 00 00 00
aliceblue F0 F8 FF
antiquewhite FA EB D7
aqua 00 FF FF
aquamarine 7F FF D4
azure F0 FF FF
beige F5 F5 DC
bisque FF E4 C4
blanchedalmond FF EB CD
blue 00 00 FF
blueviolet 8A 2B E2
brown A5 2A 2A
burlywood DE B8 87
cadetblue 5F 9E A0
chartreuse 7F FF 00
chocolate D2 69 1E
coral FF 7F 50
cornflowerblue 64 95 ED
cornsilk FF F8 DC
crimson DC 14 3C
cyan 00 FF FF
darkblue 00 00 8B
darkcyan 00 8B 8B
darkgoldenrod B8 86 0B
darkgray A9 A9 A9
darkgreen 00 64 00
darkkhaki BD B7 6B
darkmagenta 8B 00 8B
darkolivegreen 55 6B 2F
darkorange FF 8C 00
darkorchid 99 32 CC
darkred 8B 00 00
darksalmon E9 96 7A
darkseagreen 8F BC 8F
darkslateblue 48 3D 8B
darkslategray 2F 4F 4F
darkturquoise 00 CE D1
darkviolet 94 00 D3
deeppink FF 14 100
deepskyblue 00 BF FF
dimgray 69 69 69
dodgerblue 1E 90 FF
firebrick B2 22 22
floralwhite FF FA F0
forestgreen 22 8B 22
fuchsia FF 00 FF
gainsboro DC DC DC
ghostwhite F8 F8 FF
gold FF D7 00
goldenrod DA A5 20
( run in 0.714 second using v1.01-cache-2.11-cpan-39bf76dae61 )