package Buttoner; $VERSION = "1.00"; =head1 NAME Buttoner.pm - Make button GIFs via GD =head1 AUTHOR David M. Chess =head1 SYNOPSIS use Buttoner; # create a simple button and save as a GIF $button = new Buttoner::Button(120,30,"Push Here"); print OUTH $button->gif(); # Exercising more control... $button = new Buttoner::Button(140,40,"Continue",0,128,0); $button->setCenterText(1); # Center the text $button->setEdgeWidth(2); # Mess with the shape $button->setBlur(1); # and fuzz it a little print GIFNORMAL $button->gif(); # Save that $button->setTextColor(255,0,0); # Red-text version print GIFACTIVE $button->gif(); # Save that $button->setPressed(1); # Pressed-button version $button->setTextStyle("raise"); # with "raised" text print GIFPUSHED $button->gif(); # and save that =head1 HISTORY 1999/08/31 - First release version, 1.00 =head1 BUGS None currently known; see Plans and Limitations =head1 PLANS AND LIMITATIONS The text font is always gdGiantFont which, despite the name, is a mere 9x15 pels. Should use a smaller font for teensy buttons, and (to begin with) use copyResized() to fake a larger font for larger buttons. Ideally, should access the system fonts and use those instead of the gdFont(s). Buttons are always just rectangles; would be nice to allow rounded-corner buttons, at least. =cut use GD(); use strict; 1; =head1 Creating a new object =head2 new(width,height,text,red,green,blue) returns a new button instance, of the given height and width, the given text label, and the given RGB color. Defaults will be provided for any missing arguments; the default is something like (200,50,"Hello, sailor!",128,128,128). Methods exist to change all these before or between rendering. =cut sub Buttoner::Button::new { my $class = shift; my $self = {}; bless $self, $class; $self->{width} = shift() || 200; $self->{height} = shift() || ($self->{width} / 4); $self->setText(shift()); my ($red,$green,$blue) = @_; $self->setColor($red,$green,$blue); $self->setTextColor(); $self->{style} = "bevel"; $self->setTextStyle(); # default $self->{edgewidth} = 4; $self->{centertext} = 0; $self->{blur} = 0; $self->{contrast} = 0.5; $self->{font} = GD::gdGiantFont; # more flexible? return $self; } =head1 Adjusting its appearance =head2 Buttoner::Button::setText("your message here") Sets/changes the text for the button label. =cut sub Buttoner::Button::setText { my $self = shift; $self->{text} = shift() || "Hello, sailor!"; } =head2 Buttoner::Button::setCenterText(boolean) Turns on/off centering of the button label; default is off. =cut sub Buttoner::Button::setCenterText { my $self = shift; $self->{centertext} = shift(); } =head2 Buttoner::Button::setEdgeWidth(new_width) Sets the width, in pels, of the vaguely-3D-looking border around the button. Setting this to zero produces a "flat" button. Default is like four or something. =cut sub Buttoner::Button::setEdgeWidth { my $self = shift; $self->{edgewidth} = shift(); } =head2 Buttoner::Button::setPressed(boolean) Turns on/off the display of the button in "pressed" state. Press and unpressed states assume, of course, that the button is lit from the upper left. Default is off (false). =cut sub Buttoner::Button::setPressed { my $self = shift; $self->{pressed} = shift(); } =head2 Buttoner::Button::setBlur(level) Specifies the blur level of the button; default is zero. The button image is blurred (softened) before the text is applied; the text doesn't get blurred. Images with non-zero blur take somewhat longer to produce, and will tend to be larger (because they use more colors). =cut sub Buttoner::Button::setBlur { my $self = shift; $self->{blur} = shift(); } =head2 Buttoner::Button::setContrast(level) Specifies the contrast level (a number between 0 and 2ish) of the brightened and shadowed colors used for the quasi-3D effects. The default is 0.5. A contrast of 0 produces a flat button; a contrast of 1 makes the colors considerably brighter/darker. =cut sub Buttoner::Button::setContrast { my $self = shift; $self->{contrast} = shift(); } =head2 Buttoner::Button::setColor(red,green,blue) sets or changes the button's main color, which is used on the face of the button. (And used at rendering time, along with the contrast, to derive the other colors used in the quasi-3D effects.) =cut sub Buttoner::Button::setColor { my $self = shift; $self->{red} = shift(); $self->{green} = shift(); $self->{blue} = shift(); $self->{red} = 0x80 if not defined $self->{red}; $self->{green} = 0x80 if not defined $self->{green}; $self->{blue} = 0x80 if not defined $self->{blue}; } =head2 Buttoner::Button::setTextColor(red,green,blue) sets or changes the color to be used for the button label. =cut sub Buttoner::Button::setTextColor { my $self = shift; $self->{textred} = shift() || 0; $self->{textgreen} = shift() || 0; $self->{textblue} = shift() || 0; } =head2 Buttoner::Button::setTextStyle(stylename) sets or changes the style of the button label. Valid choices are "incise" (the default; looks vaguely recessed into the face of the button), "raise" (looks vaguely raised), and "flat" (just sitting there). =cut sub Buttoner::Button::setTextStyle { my $self = shift; $self->{textstyle} = shift || "incise"; } =head1 Rendering a button =head2 Buttoner::Button::gif() returns, as a potentially rather long binary string, a GIF-format representation of the button with the current appearance settings, for writing to a file, piping to a display program, or whatever. This method may be called multiple times, with appearance-adjusting calls in between, to render different versions of the "same" button (see SYNPOSIS). =cut sub Buttoner::Button::gif { my $self = shift; my $image = new GD::Image($self->{width},$self->{height}); $self->{lightred} = $self->{red} + 128*$self->{contrast}; $self->{lightgreen} = $self->{green} + 128*$self->{contrast}; $self->{lightblue} = $self->{blue} + 128*$self->{contrast}; $self->{lightred} = 255 if $self->{lightred}>255; $self->{lightgreen} = 255 if $self->{lightgreen}>255; $self->{lightblue} = 255 if $self->{lightblue}>255; $self->{halflightred} = ($self->{red} + $self->{lightred})/2; $self->{halflightgreen} = ($self->{green} + $self->{lightgreen})/2; $self->{halflightblue} = ($self->{blue} + $self->{lightblue})/2; $self->{darkred} = $self->{red}*(1-$self->{contrast}); $self->{darkgreen} = $self->{green}*(1-$self->{contrast}); $self->{darkblue} = $self->{blue}*(1-$self->{contrast}); $self->{darkred} = 0 if $self->{darkred}<0; $self->{darkgreen} = 0 if $self->{darkgreen}<0; $self->{darkblue} = 0 if $self->{darkblue}<0; my $face = $image->colorAllocate($self->{red},$self->{green},$self->{blue}); my $dark = $image->colorAllocate($self->{darkred}, $self->{darkgreen}, $self->{darkblue}); my $halflight = $image->colorAllocate($self->{halflightred}, $self->{halflightgreen}, $self->{halflightblue}); my $light = $image->colorAllocate($self->{lightred}, $self->{lightgreen}, $self->{lightblue}); my $topcolor = $light; my $leftcolor = $halflight; my $bottomcolor = $dark; my $rightcolor = $dark; my ($i,$j); if ($self->{pressed}) { $topcolor = $dark; $leftcolor = $dark; $bottomcolor = $light; $rightcolor = $halflight; } if ($self->{blur}) { my @tops = $image->rgb($topcolor); my @bottoms = $image->rgb($bottomcolor); my @lefts = $image->rgb($leftcolor); my @rights = $image->rgb($rightcolor); my @faces = $image->rgb($face); my $bitmap = []; my $whichcolor; for ($i=0;$i<$self->{edgewidth};$i++) { for ($j=$i;$j<($self->{width}-$i);$j++) { $bitmap->[$j][$i] = \@tops; $bitmap->[$j][$self->{height}-($i+1)] = \@bottoms; } for ($j=$i;$j<($self->{height}-$i);$j++) { $bitmap->[$i][$j] = \@lefts; $bitmap->[$self->{width}-($i+1)][$j] = \@rights; } } for ($i=$self->{edgewidth};$i<($self->{width}-$self->{edgewidth});$i++) { for ($j=$self->{edgewidth};$j<($self->{height}-$self->{edgewidth});$j++) { $bitmap->[$i][$j] = \@faces; } } for (1..$self->{blur}) { $bitmap = $self->_blur($bitmap); } for ($i=0;$i<$self->{width};$i++) { for ($j=0;$j<$self->{height};$j++) { $whichcolor = $image->colorExact($bitmap->[$i][$j][0], $bitmap->[$i][$j][1], $bitmap->[$i][$j][2]); $whichcolor = $image->colorAllocate($bitmap->[$i][$j][0], $bitmap->[$i][$j][1], $bitmap->[$i][$j][2]) if $whichcolor == -1; $whichcolor = $image->colorClosest($bitmap->[$i][$j][0], $bitmap->[$i][$j][1], $bitmap->[$i][$j][2]) if $whichcolor == -1; $image->setPixel($i,$j,$whichcolor) if ($whichcolor != $face); } } } else { for ($i=0;$i<$self->{edgewidth};$i++) { for ($j=$i;$j<($self->{width}-$i);$j++) { $image->setPixel($j,$i,$topcolor); $image->setPixel($j,$self->{height}-($i+1),$bottomcolor); } for ($j=$i;$j<($self->{height}-$i);$j++) { $image->setPixel($i,$j,$leftcolor); $image->setPixel($self->{width}-($i+1),$j,$rightcolor); } } } my $textheight = $self->{font}->height; my $textwidth = $self->{font}->width * length($self->{text}); my $textx = $self->{centertext} ? ($self->{width} - $textwidth)/2 : $self->{font}->width * 1.4; my $texty = ($self->{height} - $textheight)/2; my $textcolor = $image->colorAllocate($self->{textred}, $self->{textgreen}, $self->{textblue}); if ($self->{textstyle} ne "flat") { my ($ulcolor,$lrcolor) = ($dark,$halflight); ($ulcolor,$lrcolor) = ($halflight,$dark) if $self->{textstyle} eq "raise"; $image->string($self->{font},$textx+1,$texty+1,$self->{text},$lrcolor); $image->string($self->{font},$textx-1,$texty-1,$self->{text},$ulcolor); } $image->string($self->{font},$textx,$texty,$self->{text},$textcolor); return $image->gif(); } # # Internal utility routine that actually does the blurring. Takes # a reference to a three-dimensional array (you know) each of whose # components is a three-element RGB array (ref) itself. # sub Buttoner::Button::_blur { my $self = shift; my $inmap = shift; my $outmap = []; my ($c,$i,$j,$t,$n); my $xt = $self->{width} - 1; my $yt = $self->{height} - 1; foreach $c (0..2) { # for each color plane for ($i=0;$i<$self->{width};$i++) { for ($j=0;$j<$self->{height};$j++) { $n=0; $t=0; if ($i) { if ($j) { $n++; $t += $inmap->[$i-1][$j-1][$c]; } $n += 2; $t += $inmap->[$i-1][$j][$c] * 2; if ($j != $yt) { $n++; $t += $inmap->[$i-1][$j+1][$c]; } } if ($j) { $n+=2; $t += $inmap->[$i][$j-1][$c] * 2; } $n += 4; $t += $inmap->[$i][$j][$c] * 4; if ($j != $yt) { $n+=2; $t += $inmap->[$i][$j+1][$c] * 2; } if ($i != $xt) { if ($j) { $n++; $t += $inmap->[$i+1][$j-1][$c]; } $n += 2; $t += $inmap->[$i+1][$j][$c] * 2; if ($j != $yt) { $n++; $t += $inmap->[$i+1][$j+1][$c]; } } $outmap->[$i][$j][$c] = int(0.5+$t/$n); } } } return $outmap; } =head1 Copyright This code is hereby dedicated to the Public Domain. Anyone in the world can do anything they want with it, for all of me. Of course, anyone who sticks their own name on it and tries to submit it to CPAN or sell it for big bucks or anything, will just be making a fool of themself... David M. Chess, 1999 =cut