# -*- perl -*- # # $Id: GD.pm,v 1.48 2005/08/08 22:51:47 eserte Exp $ # Author: Slaven Rezic # # Copyright (C) 1998-2003 Slaven Rezic. All rights reserved. # This program is free software; you can redistribute it and/or # modify it under the same terms as Perl itself. # # Mail: slaven@rezic.de # WWW: http://bbbike.sourceforge.net/ # package BBBikeDraw::GD; use strict; use base qw(BBBikeDraw); use Strassen; # Strassen benutzt FindBin benutzt Carp, also brauchen wir hier nicht zu # sparen: use Carp qw(confess); use vars qw($gd_version $VERSION $DEBUG @colors %color %outline_color %width $TTF_STREET $TTF_CITY); BEGIN { @colors = qw($grey_bg $white $yellow $red $green $middlegreen $darkgreen $darkblue $lightblue $rose $black); } use vars @colors; use vars qw($AUTOLOAD); sub AUTOLOAD { warn "Loading BBBikeDraw::GDHeavy for $AUTOLOAD ...\n"; require BBBikeDraw::GDHeavy; if (defined &$AUTOLOAD) { goto &$AUTOLOAD; } else { die "Cannot find $AUTOLOAD in ". __PACKAGE__; } } $DEBUG = 0; $VERSION = sprintf("%d.%02d", q$Revision: 1.48 $ =~ /(\d+)\.(\d+)/); my(%brush, %outline_brush, %thickness, %outline_thickness); # REPO BEGIN # REPO NAME pi /home/e/eserte/src/repository # REPO MD5 bb2103b1f2f6d4c047c4f6f5b3fa77cd sub pi () { 4 * atan2(1, 1) } # 3.141592653 # REPO END sub init { my $self = shift; $self->SUPER::init(); # XXX provide fallbacks (by using an array?)! $TTF_STREET = '/usr/X11R6/lib/X11/fonts/ttf/LucidaSansRegular.ttf' if !defined $TTF_STREET; $TTF_CITY = '/usr/X11R6/lib/X11/fonts/Type1/lcdxsr.pfa' if !defined $TTF_CITY; local $^W = 0; #XXX revamp this code... maybe separate between in/out (newFrom... and ...) eval q{ local $SIG{'__DIE__'}; if (defined $self->{ImageType} && $self->{ImageType} eq 'svg') { require GD::SVG; $self->{GD_Image} = 'GD::SVG::Image'; $self->{GD_Polygon} = 'GD::SVG::Polygon'; $self->{GD_Font} = 'GD::SVG::Font'; $self->{GD} = 'GD::SVG'; package GD::SVG::Image; sub imageOut { shift->svg(@_); } } else { require GD; $self->{GD_Image} = 'GD::Image'; $self->{GD_Polygon} = 'GD::Polygon'; $self->{GD_Font} = 'GD::Font'; $self->{GD} = 'GD'; $self->{ImageType} = 'gif' if !defined $self->{ImageType}; # if ($GD::VERSION < 1.27 && $self->{ImageType} eq 'wbmp') { # $self->{ImageType} = 'gif'; # } if ($self->{ImageType} eq 'gif' && $GD::VERSION >= 1.20) { # XXX automatic detection does not seem to work with GD 1.41 ? #XXX if ($GD::VERSION < 1.37 || !GD::Image->can("gif")) { if (!eval { require GD::Convert; GD::Convert->import("gif=any", "newFromGif=any"); 1}) { warn "Can't create gif files, fallback to png: $@"; $self->{ImageType} = 'png'; } else { warn "OK, using GD::Convert for gif conversion" if $DEBUG; } #XXX } } if ($self->{ImageType} eq 'png' && $GD::VERSION < 1.20) { $self->{ImageType} = 'gif'; } package GD::Image; sub imageOut { my $image = shift; if ($self->imagetype eq 'gif' && $image->can('gif')) { $image->gif(@_); } elsif ($self->imagetype eq 'png' && $image->can('png')) { $image->png(@_); } elsif ($self->imagetype eq 'jpeg' && $image->can('jpeg')) { $image->jpeg(@_); } elsif ($self->imagetype eq 'wbmp') { if (!$image->can('wbmp')) { require GD::Convert; GD::Convert->import('wbmp'); } $image->wbmp($BBBikeDraw::black); } else { die "Fatal error: no gif or png methods"; } } sub newFromImage { my $image = shift; if ($self->imagetype eq 'gif' && $image->can('newFromGif')) { $image->newFromGif(@_); } elsif ($self->imagetype eq 'png' && $image->can('newFromPng')) { $image->newFromPng(@_); } elsif ($self->imagetype eq 'jpeg' && $image->can('newFromJpeg')) { $image->newFromJpeg(@_); } else { die "Fatal error: no newFrom" . ucfirst($self->imagetype) . " method available"; } } } }; warn $@ if $@; return undef if ($@); eval q{local $SIG{'__DIE__'}; $gd_version = $GD::VERSION; }; $self->{Width} ||= 640; $self->{Height} ||= 480; my $im; if ($self->{OldImage}) { $im = $self->{OldImage}; } else { my $use_truecolor = 0; # with 1 segfaults!!! $im = $self->{GD_Image}->new($self->{Width},$self->{Height}, $use_truecolor); } $self->{Image} = $im; $self->{GD_use_thickness} = $im->can("setThickness"); $self->{GD_use_aa} = 0 && $im->can("setAntiAliased"); # no effect seen without truecolor if (!$self->{OldImage}) { $self->allocate_colors; } $self->set_category_colors; $self->set_category_outline_colors; $self->set_category_widths; $im->interlaced('true'); $self->set_draw_elements; if ($self->imagetype eq 'wbmp' && !defined $self->{RouteWidth}) { $self->{RouteWidth} = $width{HH} + 4; #$self->{RouteDotted} = 3; } $self; } sub pre_draw { my $self = shift; $self->SUPER::pre_draw; my $im = $self->{Image}; my $scale = ($self->{Xk} > 1 ? 10 : $self->{Xk} * 10); $scale = 0.5 if $scale < 0.5; # create brushes foreach my $cat (keys %width) { next if $cat eq 'Route'; my $width = $width{$cat} * $scale; $width = 1 if $width < 1; $width = int($width); if ($self->{GD_use_thickness}) { $thickness{$cat} = $width; } else { my $brush = $self->{GD_Image}->new($width, $width); $brush->colorAllocate($im->rgb($color{$cat})); $brush{$cat} = $brush; } } # create outline brushes foreach my $cat (keys %width) { next unless $outline_color{$cat}; my $width = $width{$cat} * $scale; $width = 1 if $width < 1; $width = int($width); $width += 2; if ($self->{GD_use_thickness}) { $outline_thickness{$cat} = $width; } else { my $brush = $self->{GD_Image}->new($width, $width); $brush->colorAllocate($im->rgb($outline_color{$cat})); $outline_brush{$cat} = $brush; } } } sub allocate_colors { my $self = shift; my $im = $self->{Image}; my($c, $c_order) = $self->get_color_values; no strict 'refs'; for my $color (@$c_order) { my $value = $c->{$color}; if (defined $value) { $ {$color} = $im->colorAllocate(@{$value}[0..2]); if (defined $value->[3] && $value->[3] == 1) { $im->transparent($ {$color}); } } } } sub draw_map { my $self = shift; $self->pre_draw if !$self->{PreDrawCalled}; my $im = $self->{Image}; my $transpose = $self->{Transpose}; $self->_get_nets; $self->{FlaechenPass} = 1; my @netz = @{ $self->{_Net} }; my @outline_netz = @{ $self->{_OutlineNet} }; my @netz_name = @{ $self->{_NetName} }; my %str_draw = %{ $self->{_StrDraw} }; my %p_draw = %{ $self->{_PDraw} }; my $title_draw = $self->{_TitleDraw}; my($restrict, $restrict_code); if (defined $self->{Restrict}) { if (UNIVERSAL::isa($self->{Restrict}, 'ARRAY')) { $restrict = { map { ($_ => 1) } @{ $self->{Restrict} } }; } elsif (UNIVERSAL::isa($self->{Restrict}, 'CODE')) { $restrict_code = $self->{Restrict}; } } my $draw_outline_sub = sub { my($strecke) = @_; $strecke->init; while(1) { my $s = $strecke->next; last if !@{$s->[1]}; my $cat = $s->[2]; # XXX what about outlined areas? # if ($cat =~ /^F:(.*)/) { # if ($1 eq 'I') { # next; # Inseln vorerst ignorieren # } # my $c = $color{$1} || $white; # my $poly = GD::Polygon->new(); # for(my $i = 0; $i <= $#{$s->[1]}; $i++) { # $poly->addPt(&$transpose # (@{Strassen::to_koord1($s->[1][$i])})); # } # $im->filledPolygon($poly, $c); # } elsif ($cat !~ /^[SRU]0$/) { # Ausnahmen: in Bau next if $restrict && !exists $restrict->{$cat}; next if (!$outline_brush{$cat} && !defined $outline_thickness{$cat}); my $color; if (defined $outline_thickness{$cat}) { $im->setThickness($outline_thickness{$cat}); $color = $outline_color{$cat}; } else { $im->setBrush($outline_brush{$cat}); $color = $self->{GD}->gdBrushed(); } for(my $i = 0; $i < $#{$s->[1]}; $i++) { my($x1, $y1, $x2, $y2) = (@{Strassen::to_koord1($s->[1][$i])}, @{Strassen::to_koord1($s->[1][$i+1])}); # XXX evtl. aus Performancegründen testen, ob # überhaupt im Zeichenbereich. # Evtl. eine XS-Funktion für diese Schleife # schreiben? my($x1t, $y1t, $x2t, $y2t) = (&$transpose($x1, $y1), &$transpose($x2, $y2)); $im->line($x1t, $y1t, $x2t, $y2t, $color); } } }; my $draw_normal_sub = sub { my($strecke, $strecke_name, $flaechen_pass) = @_; for my $s ($self->get_street_records_in_bbox($strecke)) { my $cat = $s->[Strassen::CAT]; if ($cat =~ /^F:(.*)/) { my $cat = $1; next if ($strecke_name eq 'flaechen' && (($flaechen_pass == 1 && $cat eq 'Pabove') || ($flaechen_pass == 2 && $cat ne 'Pabove')) ); my $c = defined $color{$cat} ? $color{$cat} : $white; if ($self->{GD_use_aa}) { $im->setAntiAliased($c); $c = $self->{GD}->gdAntiAliased(); } my $poly = $self->{GD_Polygon}->new(); for my $coord (@{ $s->[Strassen::COORDS] }) { $poly->addPt($transpose->(split /,/, $coord)); } if ($self->{GD_use_thickness}) { $im->setThickness(1); } $im->filledPolygon($poly, $c); } elsif ($cat !~ /^[SRU]0$/) { # Ausnahmen: in Bau next if $restrict && !exists $restrict->{$cat}; my $color; if (defined $thickness{$cat}) { $im->setThickness($thickness{$cat}); $color = $color{$cat}; } elsif ($brush{$cat}) { $im->setBrush($brush{$cat}); $color = $self->{GD}->gdBrushed(); } else { if ($self->{GD_use_thickness}) { $im->setThickness(1); } $color = defined $color{$cat} ? $color{$cat} : $white; } if ($self->{GD_use_aa} && $color ne $self->{GD}->gdBrushed()) { $im->setAntiAliased($color); $color = $self->{GD}->gdAntiAliased(); } my @txy = map { $transpose->(split/,/, $_) } @{ $s->[Strassen::COORDS] }; next if @txy < 4; # ignore points for my $i (0 .. $#txy/2-1) { $im->line(@txy[$i*2 .. $i*2+3], $color); } } } }; my @layers; use constant LAYER_TYPE_OUTLINE => 1; use constant LAYER_TYPE_NORMAL => 2; if ($self->{Outline}) { push @layers, map { +{type => LAYER_TYPE_OUTLINE, str => $_, } } @outline_netz; } foreach my $strecke_i (0 .. $#netz) { my $strecke = $netz[$strecke_i]; my $strecke_name = $netz_name[$strecke_i]; my $flaechen_pass = $self->{FlaechenPass}; push @layers, +{type => LAYER_TYPE_NORMAL, str => $strecke, name => $strecke_name, flaechen_pass => $flaechen_pass, }; if ($strecke_name eq 'flaechen') { $self->{FlaechenPass}++; } } { # Sort layers: # At bottom: flaechen+wasser # then: outlines # then: remaining layers # Preserve original sorting by using $micro_level my $micro_level = 0; @layers = (map { $_->[1] } sort { $a->[0] <=> $b->[0]; } map { my $level = 0; $micro_level+=0.0001; if ($_->{type} eq LAYER_TYPE_OUTLINE) { $level = -1; } elsif ($_->{name} =~ /^(flaechen|wasser)$/) { $level = -2; } [$level + $micro_level, $_]; } @layers); } for my $layer_def (@layers) { my($type,$str) = @{$layer_def}{qw(type str)}; if ($type eq LAYER_TYPE_OUTLINE) { $draw_outline_sub->($str); } else { my($name,$flaechen_pass) = @{$layer_def}{qw(name flaechen_pass)}; $draw_normal_sub->($str, $name, $flaechen_pass); } } # $self->{Xk} bezeichnet den Vergrößerungsfaktor # bis etwa 0.02 ist es zu unübersichtlich, Ampeln zu zeichnen, # ab etwa 0.05 kann man die mittelgroße Variante nehmen if ($p_draw{'ampel'} && $self->{Xk} >= 0.02) { my $lsa = new Strassen "ampeln"; my $suf = ($self->{Xk} >= 0.05 ? '' : '2'); my($kl_ampel, $w_lsa, $h_lsa) = $self->read_image("ampel_klein$suf"); my($kl_andreas, $w_and, $h_and) = $self->read_image("andreaskr_klein$suf"); my($kl_zugbruecke, $w_zbr, $h_zbr) = $self->read_image($self->{Xk} >= 0.05 ? "zugbruecke" : "zugbruecke_klein"); my($kl_ampelf, $w_lsaf, $h_lsaf) = $self->read_image("ampelf_klein$suf"); if ($kl_andreas || $kl_ampel || $kl_zugbruecke || $kl_ampelf) { $lsa->init; while(1) { my $s = $lsa->next_obj; last if $s->is_empty; my $cat = $s->category; my($x, $y) = &$transpose(@{$s->coord_as_list(0)}); if ($cat eq 'B') { if ($kl_andreas) { $im->copy($kl_andreas, $x-$w_and/2, $y-$h_and/2, 0, 0, $w_and, $h_and); } } elsif ($cat eq 'Zbr') { if ($kl_zugbruecke) { $im->copy($kl_zugbruecke, $x-$w_zbr/2, $y-$h_zbr/2, 0, 0, $w_zbr, $h_zbr); } } elsif (0 && $cat eq 'F') { # XXX Wegen des Alpha-Channels in ampelf* nicht nutzbar. # Es würde funktionieren, wenn das truecolor-Bit sowohl # auf $im als auch auf $kl_ampelf gesetzt wäre. # Leider bekomme ich einen Absturz bei $im mit truecolor. if ($kl_ampelf) { $im->copy($kl_ampelf, $x-$w_lsa/2, $y-$h_lsa/2, 0, 0, $w_lsa, $h_lsa); } } else { if ($kl_ampel) { $im->copy($kl_ampel, $x-$w_lsa/2, $y-$h_lsa/2, 0, 0, $w_lsa, $h_lsa); } } } } } my($xw_u, $yw_u); my($xw_s, $yw_s); my $small_display = 0; if ($self->{Width} < 200 || $self->{Height} < 150) { ($xw_u, $yw_u) = (1, 1); ($xw_s, $yw_s) = (1, 1); $small_display = 1; } else { my($xw1, $yw1) = &$transpose(0, 0); my($xw2, $yw2) = &$transpose(60, 60); my($xw, $yw) = ($xw2-$xw1, $yw2-$yw1); if ($xw < 2) { ($xw_s, $yw_s) = (5,5); ($xw_u, $yw_u) = (1,1); } else { ($xw_s, $yw_s) = (7,7); ($xw_u, $yw_u) = (2,2); } } my $min_ort_category = $self->{MinPlaceCat}; if (!defined $min_ort_category) { $min_ort_category = ($self->{Xk} < 0.005 ? 4 : ($self->{Xk} < 0.01 ? 3 : ($self->{Xk} < 0.02 ? 2 : ($self->{Xk} < 0.03 ? 1 : 0)))); } my %ort_font = %{ $self->get_ort_font_mapping }; my %seen_bahnhof; my $strip_bhf = sub { my $bhf = shift; require Strassen::Strasse; $bhf =~ s/\s+\(.*\)$//; # strip text in parenthesis $bhf = Strasse::short($bhf, 1); $bhf; }; foreach my $points (['ubahn', 'ubahnhof', 'u'], ['sbahn', 'sbahnhof', 's'], ['rbahn', 'rbahnhof', 'r'], ['ort', 'orte', 'o'], ['orte_city', 'orte_city', 'oc'], ) { # check if it is advisable to draw stations... next if ($points->[0] =~ /bahn$/ && $self->{Xk} < 0.004); my $do_bahnhof = grep { $_ eq $points->[0]."name" } @{$self->{Draw}}; if ($self->{Xk} < 0.06) { $do_bahnhof = 0; } # Skip drawing if !ubahnhof, !sbahnhof or !rbahnhof is specified next if $str_draw{"!" . $points->[1]}; if ($str_draw{$points->[0]}) { my $brush; if ($points->[2] =~ /^[sr]$/) { $brush = $self->{GD_Image}->new($xw_s,$yw_s); $brush->transparent($brush->colorAllocate(255,255,255)); my $col = $brush->colorAllocate($im->rgb($color{'SA'})); $brush->arc($xw_s/2,$yw_s/2,$xw_s,$yw_s,0,360,$col); $brush->fill($xw_s/2,$yw_s/2,$col); $im->setBrush($brush); } my $p = ($points->[0] eq 'ort' ? $self->_get_orte : new Strassen $points->[1]); my $type = $points->[2]; my($image, $image_w, $image_h); if ($points->[1] =~ /^([us])bahnhof$/ && $self->{Xk} > 0.01) { my $type = $1; my $basename = "${type}bahn" . ($self->{Xk} > 0.12 ? "" : $self->{Xk} > 0.07 ? "_klein" : "_mini"); ($image, $image_w, $image_h) = $self->read_image($basename); } for my $s ($self->get_street_records_in_bbox($p)) { my $cat = $s->[Strassen::CAT]; next if $cat =~ /0$/; my($x0,$y0) = split /,/, $s->[Strassen::COORDS][0]; my $draw_symbol = 1; my $pad_left; if ($type =~ /^[us]$/ && $image) { my($x, $y) = &$transpose($x0, $y0); $im->copy($image, $x-$image_w/2, $y-$image_h/2, 0, 0, $image_w, $image_h); $draw_symbol = 0; $pad_left = int($image_w/2) + 2; } if ($type eq 'u' || ($type eq 's' && $small_display)) { my($x1,$x2,$y1,$y2); if (0 && !$small_display) { ($x1, $y1) = &$transpose($x0-20, $y0-20); ($x2, $y2) = &$transpose($x0+20, $y0+20); } else { ($x1, $y2) = &$transpose($x0, $y0); ($x2, $y1) = ($x1+$xw_u, $y2+$yw_u); ($x1, $y2) = ($x1-$xw_u, $y2-$yw_u); } # Achtung: y2 und y1 vertauschen! # XXX Farbe bei small_display && s-bahn if ($draw_symbol) { $im->filledRectangle($x1, $y2, $x2, $y1, $darkblue); } if ($do_bahnhof) { my $name = $strip_bhf->($s->[Strassen::NAME]); if (!$seen_bahnhof{$name}) { $pad_left = 4 if !defined $pad_left; $self->outline_text($ort_font{'bhf'}, $x1+$pad_left, $y1, $self->patch_string($name), $darkblue, $grey_bg, # $white, $darkblue ); $seen_bahnhof{$name}++; } } } elsif ($type =~ /^[sr]$/) { # XXX ausgefüllten Kreis zeichnen my($x, $y) = &$transpose(split /,/, $s->[Strassen::COORDS][0]); #$im->arc($x, $y, $xw_s, $yw_s, 0, 360, $darkgreen); if ($draw_symbol) { $im->line($x,$y,$x,$y,$self->{GD}->gdBrushed()); } if ($do_bahnhof) { my $name = $strip_bhf->($s->[Strassen::NAME]); if (!$seen_bahnhof{$name}) { $pad_left = 4 if !defined $pad_left; $self->outline_text($ort_font{'bhf'}, $x+$pad_left, $y, $self->patch_string($name), $darkgreen, $grey_bg, # $white, $darkgreen ); $seen_bahnhof{$name}++; } } } else { if ($cat >= $min_ort_category && (!$restrict_code || $restrict_code->($s, $type))) { my($x, $y) = &$transpose(split /,/, $s->[Strassen::COORDS][0]); my $ort = $s->[Strassen::NAME]; # Anhängsel löschen (z.B. "b. Berlin") $ort =~ s/\|.*$//; if ($type eq 'oc') { $self->outline_text ($ort_font{$cat} || $self->{GD_Font}->Small, $x, $y, $self->patch_string($ort), $black, $grey_bg, -anchor => "c", ); } else { $im->arc($x, $y, 3, 3, 0, 360, $black); $self->outline_text ($ort_font{$cat} || $self->{GD_Font}->Small, $x, $y, $self->patch_string($ort), $black, $grey_bg, -padx => 4, -pady => 4, ); } } } } } } if (ref $self->{StrLabel} && (defined &GD::Image::stringFT || defined &GD::Image::stringTTF)) { eval { my $ttf = $TTF_STREET; my $fontsize = 10; $Tk::RotFont::NO_X11 = 1; require Tk::RotFont; my $ft_method = defined &GD::Image::stringFT ? 'stringFT' : 'stringTTF'; my $draw_sub = sub { my($x,$y) = &$transpose($_[0], $_[1]); if (defined $_[4] and defined $_[5]) { $x -= $_[4]; $y -= $_[5]; } # correct base point of text to middle: my $rad = -$_[3]; my $cx = sin($rad)*$fontsize/2; my $cy = cos($rad)*$fontsize/2; $x += $cx; $y += $cy; warn "correct $cx/$cy\n" if $DEBUG; $im->$ft_method($black, $ttf, $fontsize, $rad, $x, $y, $_[2]); }; my $extent_sub = sub { my(@b) = $self->{GD_Image}->$ft_method ($black, $ttf, $fontsize, -$_[3], &$transpose($_[0], $_[1]), $_[2]); ($b[2]-$b[0], $b[3]-$b[1]); }; #my $strecke = $multistr; my $strecke = $self->_get_strassen(Strdraw => \%str_draw); # XXX Übergabe von %str_draw notwendig? $strecke->init; while(1) { my $s = $strecke->next; last if !@{$s->[1]}; my $cat = $s->[2]; next unless $cat eq 'HH' || $cat eq 'H'; my($x1, $y1, $xe, $ye) = (@{Strassen::to_koord1($s->[1][0])}, @{Strassen::to_koord1($s->[1][-1])}); next if (!(($x1 >= $self->{Min_x} and $xe <= $self->{Max_x}) and ($y1 >= $self->{Min_y} and $ye <= $self->{Max_y}))); my $str = Strassen::strip_bezirk($s->[0]); my $coordref = [ map { (split(/,/, $_)) } @{ $s->[1] } ]; Tk::RotFont::rot_text_smart($str, $coordref, -drawsub => $draw_sub, -extentsub => $extent_sub, -transpose => $transpose, ); } }; warn $@ if $@; } $self->{TitleDraw} = $title_draw; $self->draw_scale unless $self->{NoScale}; } sub get_ort_font_mapping { my $self = shift; my %ort_font; my $ttf = $TTF_CITY; if (defined $ttf && defined &GD::Image::stringFT && -r $ttf) { my $sc = $self->{FontSizeScale} || ($self->{Xk} < 0.1 ? 1 : $self->{Xk} < 0.2 ? 1.2 : $self->{Xk} < 0.5 ? 1.5 : 2); %ort_font = (0 => [$ttf, 6*$sc], 1 => [$ttf, 7*$sc], 2 => [$ttf, 8*$sc], 3 => [$ttf, 9*$sc], 4 => [$ttf, 10*$sc], 5 => [$ttf, 11*$sc], 6 => [$ttf, 12*$sc], bhf => [$ttf, 7*$sc], strname => [$ttf, 9*$sc], ); } else { %ort_font = (0 => $self->{GD_Font}->Tiny, 1 => $self->{GD_Font}->Small, 2 => $self->{GD_Font}->Small, 3 => $self->{GD_Font}->Large, # MediumBold sieht fetter aus 4 => $self->{GD_Font}->Large, 5 => $self->{GD_Font}->Giant, 6 => $self->{GD_Font}->Giant, bhf => $self->{GD_Font}->Small, strname => $self->{GD_Font}->Small, ); } \%ort_font; } # Zeichnen des Maßstabs sub draw_scale { my $self = shift; my $im = $self->{Image}; my $transpose = $self->{Transpose}; my $x_margin = 10; my $y_margin = 10; my $color = $black; my $bar_width = 4; my($x0,$y0) = $transpose->(0,0); my($x1,$y1, $strecke, $strecke_label); for $strecke (1000, 5000, 10000, 20000, 50000, 100000) { ($x1,$y1) = $transpose->($strecke,0); if ($x1-$x0 > 30) { $strecke_label = $strecke/1000 . "km"; last; } } if ($self->{GD_use_thickness}) { $im->setThickness(1); } $im->line($self->{Width}-($x1-$x0)-$x_margin, $self->{Height}-$y_margin, $self->{Width}-$x_margin, $self->{Height}-$y_margin, $color); $im->line($self->{Width}-($x1-$x0)-$x_margin, $self->{Height}-$y_margin-$bar_width, $self->{Width}-$x_margin, $self->{Height}-$y_margin-$bar_width, $color); $im->filledRectangle ($self->{Width}-($x1-$x0)/2-$x_margin, $self->{Height}-$y_margin-$bar_width, $self->{Width}-$x_margin, $self->{Height}-$y_margin, $color); $im->line($self->{Width}-($x1-$x0)/2-$x_margin, $self->{Height}-$y_margin, $self->{Width}-($x1-$x0)/2-$x_margin, $self->{Height}-$y_margin-$bar_width, $color); $im->line($self->{Width}-($x1-$x0)-$x_margin, $self->{Height}-$y_margin+2, $self->{Width}-($x1-$x0)-$x_margin, $self->{Height}-$y_margin-$bar_width-2, $color); $im->line($self->{Width}-$x_margin, $self->{Height}-$y_margin+2, $self->{Width}-$x_margin, $self->{Height}-$y_margin-$bar_width-2, $color); $self->_draw_scale_label($self->{Width}-($x1-$x0)-$x_margin-3, $self->{Height}-$y_margin-$bar_width-2-12, "0", $color); $self->_draw_scale_label($self->{Width}-$x_margin+8-6*length($strecke_label), $self->{Height}-$y_margin-$bar_width-2-12, $strecke_label, $color); } sub _draw_scale_label { my($self, $x, $y, $string, $color) = @_; my $ttf = "/usr/X11R6/lib/X11/fonts/TTF/luxirr.ttf"; # XXX do not hardcode! my $im = $self->{Image}; if ($self->{GD_Image}->can("stringFT") && -r $ttf) { # XXX why +8? $im->stringFT($color, $ttf, 8, 0, $x, $y+8, $string); } else { $im->string($self->{GD_Font}->Small, $x, $y, $string, $color); } } sub draw_route { my $self = shift; $self->pre_draw if !$self->{PreDrawCalled}; my $im = $self->{Image}; my $transpose = $self->{Transpose}; my(@c1) = @{ $self->{C1} }; my $strnet; # StrassenNetz-Objekt foreach (@{$self->{Draw}}) { if ($_ eq 'strname' && $self->{'MakeNet'}) { $strnet = $self->{MakeNet}->('lite'); } } my $brush; # should be *outside* the next block!!! my $line_style; my $width; if ($self->{RouteDotted}) { # gepunktete Routen für die WAP-Ausgabe (B/W) $im->setStyle(($white)x$self->{RouteDotted}, ($black)x$self->{RouteDotted}); $line_style = $self->{GD}->gdStyled(); # $width = $width{Route}; } elsif ($self->{RouteWidth}) { # fette Routen für die WAP-Ausgabe (B/W) $brush = $self->{GD_Image}->new($self->{RouteWidth}, $self->{RouteWidth}); $brush->colorAllocate($im->rgb($white)); $im->setBrush($brush); $line_style = $self->{GD}->gdBrushed(); } elsif ($brush{Route}) { $im->setBrush($brush{Route}); $line_style = $self->{GD}->gdBrushed(); } else { # Vorschlag von Rainer Scheunemann: die Route in blau zu zeichnen, # damit Rot-Grün-Blinde sie auch erkennen können. Vielleicht noch # besser: rot-grün-gestrichelt $im->setStyle(($darkblue)x3, ($self->{GD}->gdTransparent)x3, ($red)x3, ($self->{GD}->gdTransparent)x3, ); $line_style = $self->{GD}->gdStyled(); $width = $width{Route}; } # Route # Don't use setThickness here, because the dashed effect looks # strange (schraffiert). for(my $i = 0; $i < $#c1; $i++) { my($x1, $y1, $x2, $y2) = (@{$c1[$i]}, @{$c1[$i+1]}); my($tx1,$ty1, $tx2,$ty2) = (&$transpose($x1, $y1), &$transpose($x2, $y2)); $im->line($tx1,$ty1, $tx2,$ty2, $line_style); if (defined $width) { my $alpha = atan2($ty2-$ty1, $tx2-$tx1); my $beta = $alpha - pi()/2; for my $delta (-int($width/2) .. int($width/2)) { next if $delta == 0; my($dx, $dy) = ($delta*cos($beta), $delta*sin($beta)); $im->line($tx1+$dx,$ty1+$dy,$tx2+$dx,$ty2+$dy, $line_style); } } } # Flags if (@c1 > 1) { if ($self->{UseFlags} && $self->{GD_Image}->can("copyMerge") && $self->imagetype ne 'wbmp') { my $images_dir = $self->get_images_dir; my $imgfile; $imgfile = "$images_dir/flag2_bl." . $self->suffix; if (open(GIF, $imgfile)) { binmode GIF; my $start_flag = $self->{GD_Image}->newFromImage(\*GIF); close GIF; if ($start_flag) { my($w, $h) = $start_flag->getBounds; my($x, $y) = &$transpose(@{ $c1[0] }); # workaround: newFromPNG vergisst die Transparency-Information #$start_flag->transparent($start_flag->colorClosest(192,192,192)); $im->copyMerge($start_flag, $x-5, $y-15, 0, 0, $w, $h, 50); } else { warn "$imgfile exists, but can't be read by GD"; } } else { warn "Can't open $imgfile: $!"; } $imgfile = "$images_dir/flag_ziel." . $self->suffix; if (open(GIF, $imgfile)) { binmode GIF; my $end_flag = $self->{GD_Image}->newFromImage(\*GIF); close GIF; if ($end_flag) { my($w, $h) = $end_flag->getBounds; my($x, $y) = &$transpose(@{ $c1[-1] }); # workaround: newFromPNG vergisst die Transparency-Information #$end_flag->transparent($end_flag->colorClosest(192,192,192)); $im->copyMerge($end_flag, $x-5, $y-15, 0, 0, $w, $h, 50); } else { warn "$imgfile exists, but can't be read by GD"; } } else { warn "Can't open $imgfile: $!"; } } elsif ($self->{UseFlags} && $self->imagetype eq 'wbmp' && $self->{RouteWidth}) { my($x, $y) = &$transpose(@{ $c1[0] }); for my $w ($self->{RouteWidth}+5 .. $self->{RouteWidth}+6) { $im->arc($x,$y,$w,$w,0,360,$black); } } } # Ausgabe der Straßennnamen if ($strnet) { my %ort_font = %{ $self->get_ort_font_mapping }; my($text_inner, $text_outer); # if ($self->{Bg} eq 'white') { # ($text_inner, $text_outer) = ($darkblue, $white); # } else { # ($text_inner, $text_outer) = ($white, $darkblue); # } ($text_inner, $text_outer) = ($black, $grey_bg); my(@strnames) = $strnet->route_to_name ([ map { [split ','] } @{ $self->{Coords} } ]); require VectorUtil; my @rectangles; foreach my $e (@strnames) { my $name = Strassen::strip_bezirk($e->[0]); my $f_i = $e->[4][0]; my($x,$y) = &$transpose(split ',', $self->{Coords}[$f_i]); my @args = ($ort_font{strname}, $x, $y, $self->patch_string($name), $text_inner, $text_outer ); my(@bounds) = $self->check_outline_text(@args); if (!@bounds) { $self->outline_text(@args); } else { CHECK_FOR_INTERSECT: { for my $rect (@rectangles) { if (VectorUtil::intersect_rectangles(@bounds[0,1,4,5], @$rect)) { last CHECK_FOR_INTERSECT; } } $self->outline_text(@args); push @rectangles, [@bounds[0,1,4,5]]; } } } } if ($self->{TitleDraw}) { my $s = $self->make_default_title; my $gdfont; if (7*length($s) <= $self->{Width}) { $gdfont = $self->{GD_Font}->MediumBold; } elsif (6*length($s) <= $self->{Width}) { $gdfont = $self->{GD_Font}->Small; } else { $gdfont = $self->{GD_Font}->Tiny; } # my $inner = $white; # my $outer = $darkblue; # if ($self->{Bg} =~ /^white/) { # ($inner, $outer) = ($outer, $inner); # } my($inner, $outer) = ($darkblue, $grey_bg); $self->outline_text($gdfont, 1, 1, $s, $inner, $outer); } } sub outline_text { my($self, $gdfont, $x, $y, $s, $inner, $outer, %args) = @_; return if $x < 0 || $y < 0 || $x > $self->{Width} || $y > $self->{Height}; if (ref $gdfont eq 'ARRAY') { # check for ft font spec return outline_ft_text(@_); } # XXX anchor handling missing $x += $args{-padx} if defined $args{-padx}; my $im = $self->{Image}; for ([-1, 0], [1, 0], [0, 1], [0, -1]) { $im->string($gdfont, $x+$_->[0], $y+$_->[1], $s, $outer); } $im->string($gdfont, $x, $y, $s, $inner); } # $fontspec = [$fontname,$ptsize] sub outline_ft_text { my($self, $fontspec, $x, $y, $s, $inner, $outer, %args) = @_; my $im = $self->{Image}; return if !$im->can("stringFT"); # for example for GD::SVG operation if ($args{-anchor}) { ($x, $y) = _adjust_anchor ($x, $y, $args{-anchor}, $args{-padx}||0, $args{-pady}||0, sub { my @bounds = $self->{GD_Image}->stringFT($inner, @$fontspec, 0, 0, 0, $s); ($bounds[2]-$bounds[0], $bounds[5]-$bounds[3]); } ); } else { $x += $args{-padx} if defined $args{-padx}; } for ([-1, 0], [1, 0], [0, 1], [0, -1]) { $im->stringFT($outer, @$fontspec, 0, $x+$_->[0], $y+$_->[1], $s); } $im->stringFT($inner, @$fontspec, 0, $x, $y, $s); } sub check_outline_text { if (ref $_[1] eq 'ARRAY') { # check for ft font spec return &check_outline_ft_text; } (); } # XXX rough (without outline, no non-true-type support) # return bounds sub check_outline_ft_text { my($self, $fontspec, $x, $y, $s, $inner, $outer, %args) = @_; $self->{GD_Image}->stringFT($inner, @$fontspec, 0, $x, $y, $s); } sub _adjust_anchor { my($x, $y, $anchor, $padx, $pady, $get_bounds) = @_; my($dx,$dy); if ($anchor =~ / ( ^[ns]|[cew]$ ) /x) { ($dx,$dy) = $get_bounds->(); } if ($anchor =~ /^n/) { $y -= ($dy + $pady); } elsif ($anchor =~ /^s/) { $y += $pady; } #XXX this is not quite right: anchor !~ /e$/ should be -= $dx/2, however, #all calls to outline_text should specify -anchor => "w" then if ($anchor =~ /e$/) { $x -= ($dx + $padx); } elsif ($anchor =~ /c$/) { # XXX hack, see above $x -= $dx/2; } else { $x += $padx; } ($x, $y); } # Comment only valid for older GDs without filledArc: # Draw this first, otherwise the filling of the circle won't work! sub draw_wind { my $self = shift; return unless $self->{Wind}; require BBBikeCalc; BBBikeCalc::init_wind(); my $richtung = lc($self->{Wind}{Windrichtung}); if ($richtung =~ /o$/) { $richtung =~ s/o$/e/; } my $staerke = $self->{Wind}{Windstaerke}; my $im = $self->{Image}; if ($self->{GD_use_thickness}) { $im->setThickness(1); } my($radx, $rady) = (10, 10); my $col = $darkblue; if ($im->can("filledArc")) { $im->filledArc($self->{Width}-20, 20, $radx, $rady, 0, 360, $col); } else { $im->arc($self->{Width}-20, 20, $radx, $rady, 0, 360, $col); $im->fill($self->{Width}-20, 20, $col); } if ($staerke > 0) { my %senkrecht = # im Uhrzeigersinn ('-1,-1' => [-1,+1], '-1,0' => [ 0,+1], '-1,1' => [+1,+1], '0,1' => [+1, 0], '1,1' => [+1,-1], '1,0' => [ 0,-1], '1,-1' => [-1,-1], '0,-1' => [-1, 0], ); my($ydir, $xdir) = @{$BBBikeCalc::wind_dir{$richtung}}; if (exists $senkrecht{"$xdir,$ydir"}) { my($x2dir, $y2dir) = @{ $senkrecht{"$xdir,$ydir"} }; my($yadd, $xadd) = map { -$_*15 } ($ydir, $xdir); $xadd = -$xadd; # korrigieren $im->line($self->{Width}-20, 20, $self->{Width}-20+$xadd, 20+$yadd, $col); my $this_tic = 15; my $i = $staerke; my $last_is_half = 0; if ($i%2 == 1) { $last_is_half++; $i--; } while ($i >= 0) { my($yadd, $xadd) = map { -$_*$this_tic } ($ydir, $xdir); $xadd = -$xadd; my $stroke_len; if ($i == 0) { if ($last_is_half) { # half halbe Strichlänge $stroke_len = 3; } else { last; } } else { # full; volle Strichlänge $stroke_len = 6; } my($yadd2, $xadd2) = map { -$_*$stroke_len } ($y2dir, $x2dir); $xadd2 = -$xadd2; $im->line($self->{Width}-20+$xadd, 20+$yadd, $self->{Width}-20+$xadd+$xadd2, 20+$yadd+$yadd2, $col); $this_tic -= 3; last if $this_tic <= 0; $i-=2; } } } } sub make_imagemap { my $self = shift; my $fh = shift || confess "No file handle supplied"; my(%args) = @_; if (!defined $self->{Width} && !defined $self->{Height}) { if ($self->{Geometry} =~ /^(\d+)x(\d+)$/) { ($self->{Width}, $self->{Height}) = ($1, $2); } } my $transpose = $self->{Transpose}; my $multistr = $self->_get_strassen; # XXX Übergabe von %str_draw? # keine Javascript-Abfrage, damit der Code generell bleibt und # gecachet werden kann... if ($args{'-generate_javascript'}) { print $fh < EOF } print $fh ""; $multistr->init; while(1) { my $s = $multistr->next_obj; last if $s->is_empty; if ($s->category !~ /^F/ && $#{$s->coords} > 0) { my(@polygon1, @polygon2); my($dx, $dy, $c); my($x1, $y1, $x2, $y2); for(my $i = 0; $i < $#{$s->coords}; $i++) { ($x1, $y1, $x2, $y2) = (&$transpose(@{$s->coord_as_list($i)}), &$transpose(@{$s->coord_as_list($i+1)})); $dx = $x2-$x1; $dy = $y2-$y1; $c = CORE::sqrt($dx*$dx + $dy*$dy)/2; if ($c == 0) { $c = 0.00001; } $dx /= $c; $dy /= $c; push @polygon1, int($x1-$dy), int($y1+$dx); unshift @polygon2, int($x1+$dy), int($y1-$dx); } # letzter Punkt push @polygon1, int($x2-$dy), int($y2+$dx); unshift @polygon2, int($x2+$dy), int($y2-$dx); # Optimierung: nur die eine Seite des Polygons wird überprüft next unless $self->is_in_map(@polygon1); my $coordstr = join(",", @polygon1, @polygon2, $polygon1[0], $polygon1[1]); print $fh # XXX folgendes: AREA ONMOUSEOVER funktioniert für # FreeBSD-Netscape # bei Win-MSIE wird es ignoriert # und bei WIn-NS wird ein falscher Link erzeugt # title= wird noch nicht von NS und IE unterstützt (aber vom Galeon) # evtl. AREA ganz weglassen # XXX check mit onclick. evtl. onclick so patchen, dass submit mit # richtigen Werten aufgerufen wird. # "name . "\" ", "name . "\" ", "onmouseover=\"return s('" . $s->name . "')\" ", #XXX"onclick=\"return false\" ", ">\n"; #XXXXXXXXXXXXX # Geht jetzt auch nicht mehr mit NS4?! } } print $fh ""; } sub flush { my $self = shift; my %args = @_; my $fh = $args{Fh} || $self->{Fh}; binmode $fh; print $fh $self->{Image}->imageOut; } sub patch_string { my $self = shift; if (defined $gd_version and $gd_version >= 1.16) { shift; } else { $_ = shift; s/ä/ae/g; s/ö/oe/g; s/ü/ue/g; s/Ä/Ae/g; s/Ö/Oe/g; s/Ü/Ue/g; s/ß/ss/g; s/[éè]/e/g; $_; } } sub empty_image_error { my $self = shift; my $im = $self->{Image}; my $fh = $self->{Fh}; $im->string($self->{GD}->gdLargeFont, 10, 10, "Karte kann nicht gezeichnet werden!", $darkblue); $im->string($self->{GD}->gdLargeFont, 10, 30, "Cannot draw map!", $darkblue); $im->string($self->{GD}->gdLargeFont, 10, 50, scalar(localtime), $darkblue); binmode $fh if $fh; if ($fh) { print $fh $im->imageOut; } else { print $im->imageOut; } confess "Empty image"; } sub can_multiple_passes { my($self, $type) = @_; return $type eq 'flaechen'; } sub read_image { my($self, $basename) = @_; my($img, $w, $h); my $images_dir = $self->get_images_dir; my $imgfile = "$images_dir/$basename." . $self->suffix; if (open(GIF, $imgfile)) { binmode GIF; $img = $self->{GD_Image}->newFromImage(\*GIF); if ($img) { ($w, $h) = $img->getBounds; } else { warn "$imgfile exists, but can't be read by GD"; } close GIF; } ($img, $w, $h); } # To avoid loading of GDHeavy sub DESTROY { } 1;