#!/usr/bin/perl # -*-Perl-*- package MGM::Xpm; # read/write XPM format, simple transformations; grayscale for the moment my@hex; for(my$i=0;$i<256;$i++){$hex[$i]=sprintf "%02x", $i} sub rgb{ my$val=shift; if($val=~m/#/){ $val=~m/\#(.)(.)(.)/ if(length($val)==4); $val=~m/\#(..)(..)(..)/ if(length($val)==7); $val=~m/\#(..)..(..)..(..)/ if(length($val)==13); my$r=hex$1; my$g=hex$2; my$b=hex$3; if(length($val)==4){$r*=16;$g*=16;$b*=16}; ($r,$g,$b); }else{ my($r,$g,$b)=$main::toplevel->rgb($val); ($r/256,$g/256,$b/256); } } sub read{ my($file,$transp)=@_; die "Couldn't open $file for reading: $!" unless open(XPM,"$file"); undef $/; my$data=; $/="\n"; close XPM; &data($data,$transp); } sub data{ my($data,$transp)=@_; $transp="#ffffff"if(!defined($transp)); my@lines=split "\n", $data; $_=shift @lines; if(m{/\* XPM \*/}){ $_=shift @lines; $_=shift @lines; m/^\"(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/; if($4==1){ my%xpm; my$vector; my$width=$xpm{"width"}=$1; my$height=$xpm{"height"}=$2; my$colors=$3; my$entries; my$gentries; my$type='g'; # graymap until upgraded for(my$i=0;$i<$colors;$i++){ $_=shift @lines;;m/^\"(.)\s+(\S)\s+([^\"\s]+)/; $type='c' if($2 eq 'c'); $entries.=sprintf "\\%03o", ord $1; $val=$3; $val=$transp if(ucfirst($val) eq 'None'); ($red,$green,$blue)=MGM::Xpm::rgb($val); $val=$red*.3+$green*.5+$blue*.2; $gentries.=sprintf "\\%03o", $val; } # read the image bits for(my$y=0;$y<$height;$y++){ my$line=shift @lines; $line=substr $line, 1, $width; # sometimes Perl makes me positively weep for joy: eval "\$line=~tr[$entries][$gentries];"; $vector.=$line; } $xpm{"gray"}=$vector; my$ref=\%xpm; bless($ref,"MGM::Xpm"); $ref; }else{ print STDERR "This code can only handle Pixmaps with single ". "byte color indicies\n"; undef; } }else{ print STDERR "Input file is not in XPM format ". "(must begin with /* XPM */)\n"; undef; } } sub copy{ my$xpm=shift; my%copy; $copy{"width"}=$xpm->{"width"}; my$height=$copy{"height"}=$xpm->{"height"}; $copy{"gray"}=$xpm->{"gray"}; my$ret=\%copy; bless $ret, "MGM::Xpm"; } sub write{ my($xpm,$black,$white,$quant,$transp)=@_; # easier than reading, but harder to do efficiently $transp=-1 if(!defined($transp)); my$width=$xpm->{"width"}; my$height=$xpm->{"height"}; my$colors=0; my$type='c'; my%entries; my$char=' '; my$buffer; my$out; my($br,$bg,$bb)=&rgb($black); my($wr,$wg,$wb)=&rgb($white); $wr=$wr-$br; $wg=$wg-$bg; $wb=$wb-$bb; for(my$i=0;$i<$height;$i++){ my@g=unpack 'C*', substr $xpm->{"gray"}, $i*$width, $width; $buffer.="\",\n\""; for(my$j=0;$j<$width;$j++){ $buffer.=chr(int($g[$j]*($quant/255))+65); } } $out="/* XPM */\nstatic char * mgm_xpm[] = {\n". "\"$width $height ".($quant+1)." 1"; foreach $key (0..$quant){ $c=chr($key+65); $out.="\",\n\"$c $type "; if($key eq $transp){ $out.="None"; }else{ my$r=($key/$quant)*$wr+$br; my$g=($key/$quant)*$wg+$bg; my$b=($key/$quant)*$wb+$bb; my$val=$hex[$r].$hex[$g].$hex[$b]; $out.="#$val"; } } $out.=$buffer."\"};\n"; $out; } sub new{ my($width,$height,$val)=@_; $width=int($width); $height=int($height); my($red,$green,$blue)=MGM::Xpm::rgb($val); my$gray=chr($red*.3+$green*.5+$blue*.2); my%xpm; $xpm{"width"}=$width; $xpm{"height"}=$height; $xpm{"gray"}= ($gray) x ($width*$height); my$ret=\%xpm; bless $ret, "MGM::Xpm"; } sub width{ my$xpm=shift; $xpm->{"width"}; } sub height{ my$xpm=shift; $xpm->{"height"}; } sub merge{ my($to,$tox,$toy,$from,$x,$y,$w,$h)=@_; my$twidth=$to->{"width"}; my$fwidth=$from->{"width"}; $w=$twidth-$tox if($w+$tox>$twidth); $w=$fwidth-$x if($w+$x>$fwidth); for(my$i=0;$i<$h;$i++){ substr($to->{"gray"}, $tox+($i+$toy)*$twidth, $w) = substr $from->{"gray"}, $x+($i+$y)*$fwidth, $w; } $to; } sub rot90{ my$xpm=shift; $w=$xpm->width; $h=$xpm->height; # build new grid my$gray; for(my$i=$w-1;$i>=0;$i--){ for(my$j=0;$j<$h;$j++){ $gray.=substr $xpm->{"gray"}, $j*$w+$i, 1; } } $xpm->{"gray"}=$gray; $xpm->{"width"}=$h; $xpm->{"height"}=$w; $xpm; } sub reduceonex{ my($w,$nw,$v)=@_; my$del=(1.0*$w)/($nw?$nw:1); my$prev=0; my$iprev=0; my$oldval=$v->[0]; for(my$nx=0;$nx<$nw;$nx++){ my$x=$del*($nx+1); my$ix=int($x); $x-=$ix; my$val=$oldval*(1-$prev); for(my$i=$iprev+1;$i<$ix;$i++){$val+=$v->[$i]}; $oldval=$v->[$ix]; $val+=$oldval*$x if($x>.0001); $v->[$nx]=$val/$del+.5; $prev=$x; $iprev=$ix; } } sub enlargeonex{ my($w,$nw,$v)=@_; my$del=$w/$nw; my$prev=$w; my$iprev=$w-1; my$oldval=$v->[$w-1]; for(my$nx=$nw-1;$nx>=0;$nx--){ my$x=$del*($nx); my$ix=int($x); if($ix==$iprev){ $v->[$nx]=$v->[$ix]; }else{ my$val=$oldval*($prev-$iprev); $val+=($oldval=$v->[$ix])*(1-$x+$ix); $v->[$nx]=$val/$del+.5; } $prev=$x; $iprev=$ix; } } sub reduceoney{ my($w,$h,$nh,$vv)=@_; my$del=$h/$nh; my$prev=0; my$iprev=0; my@nv; $#nv=$w; for(my$ny=0;$ny<$nh;$ny++){ my$y=$del*($ny+1); my$iy=int($y); # initial row my$idel=(1-$prev+$iprev)/$del; my@v=unpack 'C*', substr $$vv, $iprev*$w, $w; for(my$x=0;$x<$w;$x++){$nv[$x]=$v[$x]*$idel+.5;} #intevening rows $iprev++; while($iprev<$iy){ @v=unpack 'C*', substr $$vv, $iprev*$w, $w; for(my$x=0;$x<$w;$x++){$nv[$x]+=$v[$x]/$del;} $iprev++; } # terminal row $idel=($y-$iy)/$del; if($idel>.0001){ @v=unpack 'C*', substr $$vv, $iy*$w, $w; for(my$x=0;$x<$w;$x++){$nv[$x]+=$v[$x]*$idel;} } $iprev=$iy; $prev=$y; substr($$vv, $ny*$w, $w)=pack 'C*', @nv[0..$w-1]; } } sub enlargeoney{ my($w,$h,$nh,$vv)=@_; my$del=$h/$nh; my$prev=$h; my$iprev=$h-1; my@nv; $#nv=$w; for(my$ny=$nh-1;$ny>=0;$ny--){ my$y=$del*$ny; my$iy=int($y); if($iprev==$iy){ substr($$vv, $ny*$w, $w)= substr($$vv, $iprev*$w, $w); }else{ # initial row my$idel=($prev-$iprev)/$del; @v=unpack 'C*', substr $$vv, $iprev*$w, $w; for(my$x=0;$x<$w;$x++){$nv[$x]=$v[$x]*$idel;} # terminal row $idel=(1-$y+$iy)/$del; @v=unpack 'C*', substr $$vv, $iy*$w, $w; for(my$x=0;$x<$w;$x++){$nv[$x]+=$v[$x]*$idel+.5;} substr($$vv, $ny*$w, $w)=pack 'C*', @nv[0..$w-1]; } $iprev=$iy; $prev=$y; } } sub scale{ my($xpm,$nw,$nh)=@_; $nw=int($nw); $nh=int($nh); $w=$xpm->width; $h=$xpm->height; if($nw<$w){ for(my$y=0;$y<$h;$y++){ my@temp=unpack 'C*', substr ($xpm->{"gray"}, $y*$w, $w); &reduceonex($w,$nw,\@temp); substr ($xpm->{"gray"}, $y*$nw, $nw) = pack 'C*', @temp[0..$nw-1]; } $w=$nw; $xpm->{"width"}=$w; } if($nh<$h){ &reduceoney($w,$h,$nh,\$xpm->{"gray"}); $h=$nh; $xpm->{"height"}=$h; } if($nw>$w){ substr($xpm->{"gray"}, $h*$w)= 'Xpm Xpm ' x ($h*($nw-$w)/8); for(my$y=$h-1;$y>=0;$y--){ my@temp=unpack 'C*', substr ($xpm->{"gray"}, $y*$w, $w); &enlargeonex($w,$nw,\@temp); substr ($xpm->{"gray"}, $y*$nw, $nw) = pack 'C*', @temp[0..$nw-1]; } $w=$nw; $xpm->{"width"}=$w; } if($nh>$h){ substr($xpm->{"gray"}, $h*$w)= 'Xpm Xpm ' x ($w*($nh-$h)/8); &enlargeoney($w,$h,$nh,\$xpm->{"gray"}); $h=$nh; $xpm->{"height"}=$h; } $xpm; } sub getpixel{ my($xpm,$x,$y)=@_; my$w=$xpm->{"width"}; ord (substr ($xpm->{"gray"}, $y*$w+$x, 1)); } package MGM::Font; sub new{ my($xpm)=@_; my%font; my$count=32; my$pos=0; $font{"bitmap"}=$xpm; $font{"height"}=$xpm->height-1; while($pos<$xpm->width){ $font{"$count"}=$pos; # find the width while(++$pos<$xpm->width){ last if (!$xpm->getpixel($pos,0)); } $font{($count)."w"}=$pos-$font{"$count"}; $count++; } my$ref=\%font; bless $ref, "MGM::Font"; } sub textsize{ my($font,$text,$height)=@_; my$acc=0; my$pop; while($pop=ord chop($text)){ $acc+=$font->{($pop)."w"}; } if(!defined($height)){ ($acc,$font->{"height"}); }else{ (int($height/$font->{"height"}*$acc),$height); } } my%textcache; sub maketext{ my($font,$text,$height,$widthlimit,$stretch,$rot90,$cache)=@_; my($bigw,$bigh); my$bigtext; if(!defined($textcache{$text})){ # how big? ($bigw,$bigh)=$font->textsize($text); # make a pixmap frame $bigtext=MGM::Xpm::new($bigw+2,$bigh+2,'#ffffff'); $textcache{$text}=$bigtext if(defined($cache)); # paste in characters my$pos=$bigw+1; while(my$pop=ord chop($text)){ my$w=$font->{($pop)."w"}; $pos-=$w; $bigtext->merge($pos,1,$font->{"bitmap"},$font->{$pop},1, $w,$bigh); } }else{ $bigtext=$textcache{$text}; $bigw=$bigtext->width; $bigh=$bigtext->height; } # scale it. $width=int($height/$bigh*$bigw*$stretch+.9); $width=$widthlimit if (defined($widthlimit) && $width>$widthlimit); my$ret=$bigtext->copy; $ret->scale($width,$height); $ret->rot90 if ($rot90); $ret; } #$xpm=MGM::Xpm::read("/home/xiphmont/SnotfishCVS/mgm/lib/helvetica.xpm","#ffffff"); #$xpm->scale(200,31); #$xpm->rot90; #$m=MGM::Xpm::new(100,100,'#6542ee'); #$m->merge(2,2,$xpm,4,4,50,50); #print $m->write('#161262','#ffffff','#ffffff'); 1;