rrd graphs: Switch to SVG output format

Message ID 20210804110106.592-1-hofmann@leo-andres.de
State Accepted
Commit fd7a02263035341f85fec624d176fd469290b063
Headers
Series rrd graphs: Switch to SVG output format |

Commit Message

Leo-Andres Hofmann Aug. 4, 2021, 11:01 a.m. UTC
  The vector graphics can be scaled without becoming blurred.

Signed-off-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
---

Hi,

Michael suggested switching to the SVG format for the graphs:
https://lists.ipfire.org/pipermail/development/2021-July/010804.html

I have tested this with firefox and chrome, the graphs and error message look fine to me.
If someone knows a good SVG validator, I would be happy to get the link :)

Regards,
Leo

 config/cfgroot/graphs.pl     |  2 +-
 html/cgi-bin/getrrdimage.cgi | 87 +++++++++++++++++++++---------------
 2 files changed, 52 insertions(+), 37 deletions(-)
  

Comments

Bernhard Bitsch Aug. 4, 2021, 11:46 a.m. UTC | #1
No problems found at a quick test with Firefox.
Did not check errors, yet.

Reviewed-by: Bernhard Bitsch <bbitsch@ipfire.org>
Tested-by: Bernhard Bitsch <bbitsch@ipfire.org>


Am 04.08.2021 um 13:01 schrieb Leo-Andres Hofmann:
> The vector graphics can be scaled without becoming blurred.
> 
> Signed-off-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
> ---
> 
> Hi,
> 
> Michael suggested switching to the SVG format for the graphs:
> https://lists.ipfire.org/pipermail/development/2021-July/010804.html
> 
> I have tested this with firefox and chrome, the graphs and error message look fine to me.
> If someone knows a good SVG validator, I would be happy to get the link :)
> 
> Regards,
> Leo
> 
>   config/cfgroot/graphs.pl     |  2 +-
>   html/cgi-bin/getrrdimage.cgi | 87 +++++++++++++++++++++---------------
>   2 files changed, 52 insertions(+), 37 deletions(-)
> 
> diff --git a/config/cfgroot/graphs.pl b/config/cfgroot/graphs.pl
> index 441d4c483..02341eb45 100644
> --- a/config/cfgroot/graphs.pl
> +++ b/config/cfgroot/graphs.pl
> @@ -40,7 +40,7 @@ my $ERROR;
>   
>   my @GRAPH_ARGS = (
>   	# Output format
> -	"--imgformat", "PNG",
> +	"--imgformat", "SVG",
>   
>   	# No border
>   	"--border", "0",
> diff --git a/html/cgi-bin/getrrdimage.cgi b/html/cgi-bin/getrrdimage.cgi
> index c08247c57..92f8b585d 100644
> --- a/html/cgi-bin/getrrdimage.cgi
> +++ b/html/cgi-bin/getrrdimage.cgi
> @@ -21,8 +21,7 @@
>   
>   use strict;
>   use URI;
> -use GD;
> -use GD::Text::Wrap;
> +use Text::Wrap;
>   use experimental 'smartmatch';
>   
>   # debugging
> @@ -52,7 +51,7 @@ my $range = lc $query{'range'}; # lower case
>   # Check parameters
>   unless(($origin =~ /^\w+?\.cgi$/) && ($graph =~ /^[\w\-.,; ]+?$/) && ($range ~~ @Graphs::time_ranges)) {
>   	# Send HTTP headers
> -	_start_png_output();
> +	_start_svg_output();
>   	
>   	_print_error("URL parameters missing or malformed.");	
>   	exit;
> @@ -76,7 +75,7 @@ unless(($origin ~~ @supported_origins) || ($origin eq "getrrdimage.cgi")) {
>   
>   ### Create graphs ###
>   # Send HTTP headers
> -_start_png_output();
> +_start_svg_output();
>   
>   # Graphs are first grouped by their origin.
>   # This is because some graph categories require special parameter handling.
> @@ -204,46 +203,62 @@ if($graphstatus) {
>   
>   ###--- Internal functions ---###
>   
> -# Send HTTP headers and switch to binary output
> +# Send HTTP headers
>   # (don't print any non-image data to STDOUT afterwards)
> -sub _start_png_output {
> +sub _start_svg_output {
>   	print "Cache-Control: no-cache, no-store\n";
> -	print "Content-Type: image/png\n";
> +	print "Content-Type: image/svg+xml\n";
>   	print "\n"; # End of HTTP headers
> -	binmode(STDOUT);
>   }
>   
> -# Print error message to PNG output
> +# Print error message to SVG output
>   sub _print_error {
>   	my ($message) = @_;
> -	$message = "- Error -\n \n$message";
> -
> -	# Create new image with the same size as a graph
> -	my $img = GD::Image->new($Graphs::image_size{'width'}, $Graphs::image_size{'height'});
> -	$img->interlaced('true');
> -
> -	# Basic colors
> -	my $color_background = $img->colorAllocate(255, 255, 255);
> -	my $color_border = $img->colorAllocate(255, 0, 0);
> -	my $color_text = $img->colorAllocate(0, 0, 0);
>   
> -	# Background and border
> -	$img->setThickness(2);
> -	$img->filledRectangle(0, 0, $img->width, $img->height, $color_background);
> -	$img->rectangle(10, 10, $img->width - 10, $img->height - 10, $color_border);
> -	
> -	# Draw message with line-wrap
> -	my $textbox = GD::Text::Wrap->new($img,
> -		text => $message,
> -		width => ($img->width - 50),
> -		color => $color_text,
> -		align => 'center',
> -		line_space => 5,
> -		preserve_nl => 1
> +	# Prepare image options
> +	my %img = (
> +		'width' => $Graphs::image_size{'width'},
> +		'height' => $Graphs::image_size{'height'},
> +		'text_center' => int($Graphs::image_size{'width'} / 2),
> +		'line_height' => 20,
> +		'font_family' => "DejaVu Sans, Helvetica, sans-serif" # Matching the IPFire theme
>   	);
> -	$textbox->set_font(gdLargeFont);
> -	$textbox->draw(25, 25);
>   
> -	# Get PNG output
> -	print $img->png;
> +	# Line-wrap message to fit image (adjust to font width if necessary)
> +	local($Text::Wrap::columns) = int($img{'width'} / 10);
> +	$message = wrap('', '', $message);
> +
> +	# Create new image with fixed background and border
> +	print <<END
> +<?xml version="1.0" encoding="UTF-8"?>
> +<svg width="$img{'width'}px" height="$img{'height'}px" viewBox="0 0 $img{'width'} $img{'height'}" version="1.1" xmlns="http://www.w3.org/2000/svg">
> +	<!-- Background -->
> +	<rect width="100%" height="100%" fill="white"/>
> +	<rect width="100%" height="100%" fill="none" stroke="red" stroke-width="2" transform="scale(0.95)" transform-origin="center"/>
> +	<!-- Message -->
> +	<text x="$img{'text_center'}" y="50" font-size="20" font-family="$img{'font_family'}" text-anchor="middle">- $Lang::tr{'error'} -</text>
> +	<text x="$img{'text_center'}" y="90" font-size="14" font-family="$img{'font_family'}" text-anchor="middle">
> +END
> +;
> +
> +	# Print message lines
> +	my $shift_y = 0; # Shifts text along y-axis
> +	foreach my $line (split(/\n/, $message)) {
> +		if($line ne "") { # Don't create empty tspan elements
> +			print <<END
> +		<tspan x="$img{'text_center'}" dy="$shift_y">$line</tspan>
> +END
> +;
> +			$shift_y = $img{'line_height'};
> +		} else { # Create blank lines by summing up unused line height
> +			$shift_y += $img{'line_height'};
> +		}
> +	}
> +
> +	# Finish SVG output
> +	print <<END
> +	</text>
> +</svg>
> +END
> +;
>   }
>
  
Michael Tremer Aug. 4, 2021, 2:40 p.m. UTC | #2
Cool. Thank you.

Did you test if the SVG files can be generated quicker than PNG?

-Michael

> On 4 Aug 2021, at 13:01, Leo-Andres Hofmann <hofmann@leo-andres.de> wrote:
> 
> The vector graphics can be scaled without becoming blurred.
> 
> Signed-off-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
> ---
> 
> Hi,
> 
> Michael suggested switching to the SVG format for the graphs:
> https://lists.ipfire.org/pipermail/development/2021-July/010804.html
> 
> I have tested this with firefox and chrome, the graphs and error message look fine to me.
> If someone knows a good SVG validator, I would be happy to get the link :)
> 
> Regards,
> Leo
> 
> config/cfgroot/graphs.pl     |  2 +-
> html/cgi-bin/getrrdimage.cgi | 87 +++++++++++++++++++++---------------
> 2 files changed, 52 insertions(+), 37 deletions(-)
> 
> diff --git a/config/cfgroot/graphs.pl b/config/cfgroot/graphs.pl
> index 441d4c483..02341eb45 100644
> --- a/config/cfgroot/graphs.pl
> +++ b/config/cfgroot/graphs.pl
> @@ -40,7 +40,7 @@ my $ERROR;
> 
> my @GRAPH_ARGS = (
> 	# Output format
> -	"--imgformat", "PNG",
> +	"--imgformat", "SVG",
> 
> 	# No border
> 	"--border", "0",
> diff --git a/html/cgi-bin/getrrdimage.cgi b/html/cgi-bin/getrrdimage.cgi
> index c08247c57..92f8b585d 100644
> --- a/html/cgi-bin/getrrdimage.cgi
> +++ b/html/cgi-bin/getrrdimage.cgi
> @@ -21,8 +21,7 @@
> 
> use strict;
> use URI;
> -use GD;
> -use GD::Text::Wrap;
> +use Text::Wrap;
> use experimental 'smartmatch';
> 
> # debugging
> @@ -52,7 +51,7 @@ my $range = lc $query{'range'}; # lower case
> # Check parameters
> unless(($origin =~ /^\w+?\.cgi$/) && ($graph =~ /^[\w\-.,; ]+?$/) && ($range ~~ @Graphs::time_ranges)) {
> 	# Send HTTP headers
> -	_start_png_output();
> +	_start_svg_output();
> 	
> 	_print_error("URL parameters missing or malformed.");	
> 	exit;
> @@ -76,7 +75,7 @@ unless(($origin ~~ @supported_origins) || ($origin eq "getrrdimage.cgi")) {
> 
> ### Create graphs ###
> # Send HTTP headers
> -_start_png_output();
> +_start_svg_output();
> 
> # Graphs are first grouped by their origin.
> # This is because some graph categories require special parameter handling.
> @@ -204,46 +203,62 @@ if($graphstatus) {
> 
> ###--- Internal functions ---###
> 
> -# Send HTTP headers and switch to binary output
> +# Send HTTP headers
> # (don't print any non-image data to STDOUT afterwards)
> -sub _start_png_output {
> +sub _start_svg_output {
> 	print "Cache-Control: no-cache, no-store\n";
> -	print "Content-Type: image/png\n";
> +	print "Content-Type: image/svg+xml\n";
> 	print "\n"; # End of HTTP headers
> -	binmode(STDOUT);
> }
> 
> -# Print error message to PNG output
> +# Print error message to SVG output
> sub _print_error {
> 	my ($message) = @_;
> -	$message = "- Error -\n \n$message";
> -
> -	# Create new image with the same size as a graph
> -	my $img = GD::Image->new($Graphs::image_size{'width'}, $Graphs::image_size{'height'});
> -	$img->interlaced('true');
> -
> -	# Basic colors
> -	my $color_background = $img->colorAllocate(255, 255, 255);
> -	my $color_border = $img->colorAllocate(255, 0, 0);
> -	my $color_text = $img->colorAllocate(0, 0, 0);
> 
> -	# Background and border
> -	$img->setThickness(2);
> -	$img->filledRectangle(0, 0, $img->width, $img->height, $color_background);
> -	$img->rectangle(10, 10, $img->width - 10, $img->height - 10, $color_border);
> -	
> -	# Draw message with line-wrap
> -	my $textbox = GD::Text::Wrap->new($img,
> -		text => $message,
> -		width => ($img->width - 50),
> -		color => $color_text,
> -		align => 'center',
> -		line_space => 5,
> -		preserve_nl => 1
> +	# Prepare image options
> +	my %img = (
> +		'width' => $Graphs::image_size{'width'},
> +		'height' => $Graphs::image_size{'height'},
> +		'text_center' => int($Graphs::image_size{'width'} / 2),
> +		'line_height' => 20,
> +		'font_family' => "DejaVu Sans, Helvetica, sans-serif" # Matching the IPFire theme
> 	);
> -	$textbox->set_font(gdLargeFont);
> -	$textbox->draw(25, 25);
> 
> -	# Get PNG output
> -	print $img->png;
> +	# Line-wrap message to fit image (adjust to font width if necessary)
> +	local($Text::Wrap::columns) = int($img{'width'} / 10);
> +	$message = wrap('', '', $message);
> +
> +	# Create new image with fixed background and border
> +	print <<END
> +<?xml version="1.0" encoding="UTF-8"?>
> +<svg width="$img{'width'}px" height="$img{'height'}px" viewBox="0 0 $img{'width'} $img{'height'}" version="1.1" xmlns="http://www.w3.org/2000/svg">
> +	<!-- Background -->
> +	<rect width="100%" height="100%" fill="white"/>
> +	<rect width="100%" height="100%" fill="none" stroke="red" stroke-width="2" transform="scale(0.95)" transform-origin="center"/>
> +	<!-- Message -->
> +	<text x="$img{'text_center'}" y="50" font-size="20" font-family="$img{'font_family'}" text-anchor="middle">- $Lang::tr{'error'} -</text>
> +	<text x="$img{'text_center'}" y="90" font-size="14" font-family="$img{'font_family'}" text-anchor="middle">
> +END
> +;
> +
> +	# Print message lines
> +	my $shift_y = 0; # Shifts text along y-axis
> +	foreach my $line (split(/\n/, $message)) {
> +		if($line ne "") { # Don't create empty tspan elements
> +			print <<END
> +		<tspan x="$img{'text_center'}" dy="$shift_y">$line</tspan>
> +END
> +;
> +			$shift_y = $img{'line_height'};
> +		} else { # Create blank lines by summing up unused line height
> +			$shift_y += $img{'line_height'};
> +		}
> +	}
> +
> +	# Finish SVG output
> +	print <<END
> +	</text>
> +</svg>
> +END
> +;
> }
> -- 
> 2.27.0.windows.1
>
  
Bernhard Bitsch Aug. 4, 2021, 3:14 p.m. UTC | #3
Am 04.08.2021 um 16:40 schrieb Michael Tremer:
> Cool. Thank you.
> 
> Did you test if the SVG files can be generated quicker than PNG?
> 

Haven't measured in detail, but the time for display of the graph seems 
equal to the png pictures ( on my small ALIX ). But I didn't check the 
timings of sampling of the data also.

- Bernhard

> -Michael
> 
>> On 4 Aug 2021, at 13:01, Leo-Andres Hofmann <hofmann@leo-andres.de> wrote:
>>
>> The vector graphics can be scaled without becoming blurred.
>>
>> Signed-off-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
>> ---
>>
>> Hi,
>>
>> Michael suggested switching to the SVG format for the graphs:
>> https://lists.ipfire.org/pipermail/development/2021-July/010804.html
>>
>> I have tested this with firefox and chrome, the graphs and error message look fine to me.
>> If someone knows a good SVG validator, I would be happy to get the link :)
>>
>> Regards,
>> Leo
>>
>> config/cfgroot/graphs.pl     |  2 +-
>> html/cgi-bin/getrrdimage.cgi | 87 +++++++++++++++++++++---------------
>> 2 files changed, 52 insertions(+), 37 deletions(-)
>>
>> diff --git a/config/cfgroot/graphs.pl b/config/cfgroot/graphs.pl
>> index 441d4c483..02341eb45 100644
>> --- a/config/cfgroot/graphs.pl
>> +++ b/config/cfgroot/graphs.pl
>> @@ -40,7 +40,7 @@ my $ERROR;
>>
>> my @GRAPH_ARGS = (
>> 	# Output format
>> -	"--imgformat", "PNG",
>> +	"--imgformat", "SVG",
>>
>> 	# No border
>> 	"--border", "0",
>> diff --git a/html/cgi-bin/getrrdimage.cgi b/html/cgi-bin/getrrdimage.cgi
>> index c08247c57..92f8b585d 100644
>> --- a/html/cgi-bin/getrrdimage.cgi
>> +++ b/html/cgi-bin/getrrdimage.cgi
>> @@ -21,8 +21,7 @@
>>
>> use strict;
>> use URI;
>> -use GD;
>> -use GD::Text::Wrap;
>> +use Text::Wrap;
>> use experimental 'smartmatch';
>>
>> # debugging
>> @@ -52,7 +51,7 @@ my $range = lc $query{'range'}; # lower case
>> # Check parameters
>> unless(($origin =~ /^\w+?\.cgi$/) && ($graph =~ /^[\w\-.,; ]+?$/) && ($range ~~ @Graphs::time_ranges)) {
>> 	# Send HTTP headers
>> -	_start_png_output();
>> +	_start_svg_output();
>> 	
>> 	_print_error("URL parameters missing or malformed.");	
>> 	exit;
>> @@ -76,7 +75,7 @@ unless(($origin ~~ @supported_origins) || ($origin eq "getrrdimage.cgi")) {
>>
>> ### Create graphs ###
>> # Send HTTP headers
>> -_start_png_output();
>> +_start_svg_output();
>>
>> # Graphs are first grouped by their origin.
>> # This is because some graph categories require special parameter handling.
>> @@ -204,46 +203,62 @@ if($graphstatus) {
>>
>> ###--- Internal functions ---###
>>
>> -# Send HTTP headers and switch to binary output
>> +# Send HTTP headers
>> # (don't print any non-image data to STDOUT afterwards)
>> -sub _start_png_output {
>> +sub _start_svg_output {
>> 	print "Cache-Control: no-cache, no-store\n";
>> -	print "Content-Type: image/png\n";
>> +	print "Content-Type: image/svg+xml\n";
>> 	print "\n"; # End of HTTP headers
>> -	binmode(STDOUT);
>> }
>>
>> -# Print error message to PNG output
>> +# Print error message to SVG output
>> sub _print_error {
>> 	my ($message) = @_;
>> -	$message = "- Error -\n \n$message";
>> -
>> -	# Create new image with the same size as a graph
>> -	my $img = GD::Image->new($Graphs::image_size{'width'}, $Graphs::image_size{'height'});
>> -	$img->interlaced('true');
>> -
>> -	# Basic colors
>> -	my $color_background = $img->colorAllocate(255, 255, 255);
>> -	my $color_border = $img->colorAllocate(255, 0, 0);
>> -	my $color_text = $img->colorAllocate(0, 0, 0);
>>
>> -	# Background and border
>> -	$img->setThickness(2);
>> -	$img->filledRectangle(0, 0, $img->width, $img->height, $color_background);
>> -	$img->rectangle(10, 10, $img->width - 10, $img->height - 10, $color_border);
>> -	
>> -	# Draw message with line-wrap
>> -	my $textbox = GD::Text::Wrap->new($img,
>> -		text => $message,
>> -		width => ($img->width - 50),
>> -		color => $color_text,
>> -		align => 'center',
>> -		line_space => 5,
>> -		preserve_nl => 1
>> +	# Prepare image options
>> +	my %img = (
>> +		'width' => $Graphs::image_size{'width'},
>> +		'height' => $Graphs::image_size{'height'},
>> +		'text_center' => int($Graphs::image_size{'width'} / 2),
>> +		'line_height' => 20,
>> +		'font_family' => "DejaVu Sans, Helvetica, sans-serif" # Matching the IPFire theme
>> 	);
>> -	$textbox->set_font(gdLargeFont);
>> -	$textbox->draw(25, 25);
>>
>> -	# Get PNG output
>> -	print $img->png;
>> +	# Line-wrap message to fit image (adjust to font width if necessary)
>> +	local($Text::Wrap::columns) = int($img{'width'} / 10);
>> +	$message = wrap('', '', $message);
>> +
>> +	# Create new image with fixed background and border
>> +	print <<END
>> +<?xml version="1.0" encoding="UTF-8"?>
>> +<svg width="$img{'width'}px" height="$img{'height'}px" viewBox="0 0 $img{'width'} $img{'height'}" version="1.1" xmlns="http://www.w3.org/2000/svg">
>> +	<!-- Background -->
>> +	<rect width="100%" height="100%" fill="white"/>
>> +	<rect width="100%" height="100%" fill="none" stroke="red" stroke-width="2" transform="scale(0.95)" transform-origin="center"/>
>> +	<!-- Message -->
>> +	<text x="$img{'text_center'}" y="50" font-size="20" font-family="$img{'font_family'}" text-anchor="middle">- $Lang::tr{'error'} -</text>
>> +	<text x="$img{'text_center'}" y="90" font-size="14" font-family="$img{'font_family'}" text-anchor="middle">
>> +END
>> +;
>> +
>> +	# Print message lines
>> +	my $shift_y = 0; # Shifts text along y-axis
>> +	foreach my $line (split(/\n/, $message)) {
>> +		if($line ne "") { # Don't create empty tspan elements
>> +			print <<END
>> +		<tspan x="$img{'text_center'}" dy="$shift_y">$line</tspan>
>> +END
>> +;
>> +			$shift_y = $img{'line_height'};
>> +		} else { # Create blank lines by summing up unused line height
>> +			$shift_y += $img{'line_height'};
>> +		}
>> +	}
>> +
>> +	# Finish SVG output
>> +	print <<END
>> +	</text>
>> +</svg>
>> +END
>> +;
>> }
>> -- 
>> 2.27.0.windows.1
>>
>
  
Leo-Andres Hofmann Aug. 5, 2021, 8:40 a.m. UTC | #4
I used the Firefox developer tools to check the loading times of the images.

On average, the SVG image is generated 10ms faster: "HTTP Wait" for PNG: ~126ms, for SVG: ~115ms
(My test system: Core 159 on Proxmox VM/Xeon 3,3GHz)

Regards,
Leo

Am 04.08.2021 um 16:40 schrieb Michael Tremer:
> Cool. Thank you.
>
> Did you test if the SVG files can be generated quicker than PNG?
>
> -Michael
>
>> On 4 Aug 2021, at 13:01, Leo-Andres Hofmann <hofmann@leo-andres.de> wrote:
>>
>> The vector graphics can be scaled without becoming blurred.
>>
>> Signed-off-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
>> ---
>>
>> Hi,
>>
>> Michael suggested switching to the SVG format for the graphs:
>> https://lists.ipfire.org/pipermail/development/2021-July/010804.html
>>
>> I have tested this with firefox and chrome, the graphs and error message look fine to me.
>> If someone knows a good SVG validator, I would be happy to get the link :)
>>
>> Regards,
>> Leo
>>
>> config/cfgroot/graphs.pl     |  2 +-
>> html/cgi-bin/getrrdimage.cgi | 87 +++++++++++++++++++++---------------
>> 2 files changed, 52 insertions(+), 37 deletions(-)
>>
>> diff --git a/config/cfgroot/graphs.pl b/config/cfgroot/graphs.pl
>> index 441d4c483..02341eb45 100644
>> --- a/config/cfgroot/graphs.pl
>> +++ b/config/cfgroot/graphs.pl
>> @@ -40,7 +40,7 @@ my $ERROR;
>>
>> my @GRAPH_ARGS = (
>> 	# Output format
>> -	"--imgformat", "PNG",
>> +	"--imgformat", "SVG",
>>
>> 	# No border
>> 	"--border", "0",
>> diff --git a/html/cgi-bin/getrrdimage.cgi b/html/cgi-bin/getrrdimage.cgi
>> index c08247c57..92f8b585d 100644
>> --- a/html/cgi-bin/getrrdimage.cgi
>> +++ b/html/cgi-bin/getrrdimage.cgi
>> @@ -21,8 +21,7 @@
>>
>> use strict;
>> use URI;
>> -use GD;
>> -use GD::Text::Wrap;
>> +use Text::Wrap;
>> use experimental 'smartmatch';
>>
>> # debugging
>> @@ -52,7 +51,7 @@ my $range = lc $query{'range'}; # lower case
>> # Check parameters
>> unless(($origin =~ /^\w+?\.cgi$/) && ($graph =~ /^[\w\-.,; ]+?$/) && ($range ~~ @Graphs::time_ranges)) {
>> 	# Send HTTP headers
>> -	_start_png_output();
>> +	_start_svg_output();
>> 	
>> 	_print_error("URL parameters missing or malformed.");	
>> 	exit;
>> @@ -76,7 +75,7 @@ unless(($origin ~~ @supported_origins) || ($origin eq "getrrdimage.cgi")) {
>>
>> ### Create graphs ###
>> # Send HTTP headers
>> -_start_png_output();
>> +_start_svg_output();
>>
>> # Graphs are first grouped by their origin.
>> # This is because some graph categories require special parameter handling.
>> @@ -204,46 +203,62 @@ if($graphstatus) {
>>
>> ###--- Internal functions ---###
>>
>> -# Send HTTP headers and switch to binary output
>> +# Send HTTP headers
>> # (don't print any non-image data to STDOUT afterwards)
>> -sub _start_png_output {
>> +sub _start_svg_output {
>> 	print "Cache-Control: no-cache, no-store\n";
>> -	print "Content-Type: image/png\n";
>> +	print "Content-Type: image/svg+xml\n";
>> 	print "\n"; # End of HTTP headers
>> -	binmode(STDOUT);
>> }
>>
>> -# Print error message to PNG output
>> +# Print error message to SVG output
>> sub _print_error {
>> 	my ($message) = @_;
>> -	$message = "- Error -\n \n$message";
>> -
>> -	# Create new image with the same size as a graph
>> -	my $img = GD::Image->new($Graphs::image_size{'width'}, $Graphs::image_size{'height'});
>> -	$img->interlaced('true');
>> -
>> -	# Basic colors
>> -	my $color_background = $img->colorAllocate(255, 255, 255);
>> -	my $color_border = $img->colorAllocate(255, 0, 0);
>> -	my $color_text = $img->colorAllocate(0, 0, 0);
>>
>> -	# Background and border
>> -	$img->setThickness(2);
>> -	$img->filledRectangle(0, 0, $img->width, $img->height, $color_background);
>> -	$img->rectangle(10, 10, $img->width - 10, $img->height - 10, $color_border);
>> -	
>> -	# Draw message with line-wrap
>> -	my $textbox = GD::Text::Wrap->new($img,
>> -		text => $message,
>> -		width => ($img->width - 50),
>> -		color => $color_text,
>> -		align => 'center',
>> -		line_space => 5,
>> -		preserve_nl => 1
>> +	# Prepare image options
>> +	my %img = (
>> +		'width' => $Graphs::image_size{'width'},
>> +		'height' => $Graphs::image_size{'height'},
>> +		'text_center' => int($Graphs::image_size{'width'} / 2),
>> +		'line_height' => 20,
>> +		'font_family' => "DejaVu Sans, Helvetica, sans-serif" # Matching the IPFire theme
>> 	);
>> -	$textbox->set_font(gdLargeFont);
>> -	$textbox->draw(25, 25);
>>
>> -	# Get PNG output
>> -	print $img->png;
>> +	# Line-wrap message to fit image (adjust to font width if necessary)
>> +	local($Text::Wrap::columns) = int($img{'width'} / 10);
>> +	$message = wrap('', '', $message);
>> +
>> +	# Create new image with fixed background and border
>> +	print <<END
>> +<?xml version="1.0" encoding="UTF-8"?>
>> +<svg width="$img{'width'}px" height="$img{'height'}px" viewBox="0 0 $img{'width'} $img{'height'}" version="1.1" xmlns="http://www.w3.org/2000/svg">
>> +	<!-- Background -->
>> +	<rect width="100%" height="100%" fill="white"/>
>> +	<rect width="100%" height="100%" fill="none" stroke="red" stroke-width="2" transform="scale(0.95)" transform-origin="center"/>
>> +	<!-- Message -->
>> +	<text x="$img{'text_center'}" y="50" font-size="20" font-family="$img{'font_family'}" text-anchor="middle">- $Lang::tr{'error'} -</text>
>> +	<text x="$img{'text_center'}" y="90" font-size="14" font-family="$img{'font_family'}" text-anchor="middle">
>> +END
>> +;
>> +
>> +	# Print message lines
>> +	my $shift_y = 0; # Shifts text along y-axis
>> +	foreach my $line (split(/\n/, $message)) {
>> +		if($line ne "") { # Don't create empty tspan elements
>> +			print <<END
>> +		<tspan x="$img{'text_center'}" dy="$shift_y">$line</tspan>
>> +END
>> +;
>> +			$shift_y = $img{'line_height'};
>> +		} else { # Create blank lines by summing up unused line height
>> +			$shift_y += $img{'line_height'};
>> +		}
>> +	}
>> +
>> +	# Finish SVG output
>> +	print <<END
>> +	</text>
>> +</svg>
>> +END
>> +;
>> }
>> -- 
>> 2.27.0.windows.1
>>
  
Michael Tremer Aug. 5, 2021, 9:14 a.m. UTC | #5
Hello Leo,

> On 5 Aug 2021, at 10:40, Leo Hofmann <hofmann@leo-andres.de> wrote:
> 
> I used the Firefox developer tools to check the loading times of the images.
> 
> On average, the SVG image is generated 10ms faster: "HTTP Wait" for PNG: ~126ms, for SVG: ~115ms
> (My test system: Core 159 on Proxmox VM/Xeon 3,3GHz)

Okay. That seems to be a good improvement then. Around 10%. Cool.

-Michael

> Regards,
> Leo
> 
> Am 04.08.2021 um 16:40 schrieb Michael Tremer:
>> Cool. Thank you.
>> 
>> Did you test if the SVG files can be generated quicker than PNG?
>> 
>> -Michael
>> 
>>> On 4 Aug 2021, at 13:01, Leo-Andres Hofmann <hofmann@leo-andres.de> wrote:
>>> 
>>> The vector graphics can be scaled without becoming blurred.
>>> 
>>> Signed-off-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
>>> ---
>>> 
>>> Hi,
>>> 
>>> Michael suggested switching to the SVG format for the graphs:
>>> https://lists.ipfire.org/pipermail/development/2021-July/010804.html
>>> 
>>> I have tested this with firefox and chrome, the graphs and error message look fine to me.
>>> If someone knows a good SVG validator, I would be happy to get the link :)
>>> 
>>> Regards,
>>> Leo
>>> 
>>> config/cfgroot/graphs.pl     |  2 +-
>>> html/cgi-bin/getrrdimage.cgi | 87 +++++++++++++++++++++---------------
>>> 2 files changed, 52 insertions(+), 37 deletions(-)
>>> 
>>> diff --git a/config/cfgroot/graphs.pl b/config/cfgroot/graphs.pl
>>> index 441d4c483..02341eb45 100644
>>> --- a/config/cfgroot/graphs.pl
>>> +++ b/config/cfgroot/graphs.pl
>>> @@ -40,7 +40,7 @@ my $ERROR;
>>> 
>>> my @GRAPH_ARGS = (
>>> 	# Output format
>>> -	"--imgformat", "PNG",
>>> +	"--imgformat", "SVG",
>>> 
>>> 	# No border
>>> 	"--border", "0",
>>> diff --git a/html/cgi-bin/getrrdimage.cgi b/html/cgi-bin/getrrdimage.cgi
>>> index c08247c57..92f8b585d 100644
>>> --- a/html/cgi-bin/getrrdimage.cgi
>>> +++ b/html/cgi-bin/getrrdimage.cgi
>>> @@ -21,8 +21,7 @@
>>> 
>>> use strict;
>>> use URI;
>>> -use GD;
>>> -use GD::Text::Wrap;
>>> +use Text::Wrap;
>>> use experimental 'smartmatch';
>>> 
>>> # debugging
>>> @@ -52,7 +51,7 @@ my $range = lc $query{'range'}; # lower case
>>> # Check parameters
>>> unless(($origin =~ /^\w+?\.cgi$/) && ($graph =~ /^[\w\-.,; ]+?$/) && ($range ~~ @Graphs::time_ranges)) {
>>> 	# Send HTTP headers
>>> -	_start_png_output();
>>> +	_start_svg_output();
>>> 	
>>> 	_print_error("URL parameters missing or malformed.");	
>>> 	exit;
>>> @@ -76,7 +75,7 @@ unless(($origin ~~ @supported_origins) || ($origin eq "getrrdimage.cgi")) {
>>> 
>>> ### Create graphs ###
>>> # Send HTTP headers
>>> -_start_png_output();
>>> +_start_svg_output();
>>> 
>>> # Graphs are first grouped by their origin.
>>> # This is because some graph categories require special parameter handling.
>>> @@ -204,46 +203,62 @@ if($graphstatus) {
>>> 
>>> ###--- Internal functions ---###
>>> 
>>> -# Send HTTP headers and switch to binary output
>>> +# Send HTTP headers
>>> # (don't print any non-image data to STDOUT afterwards)
>>> -sub _start_png_output {
>>> +sub _start_svg_output {
>>> 	print "Cache-Control: no-cache, no-store\n";
>>> -	print "Content-Type: image/png\n";
>>> +	print "Content-Type: image/svg+xml\n";
>>> 	print "\n"; # End of HTTP headers
>>> -	binmode(STDOUT);
>>> }
>>> 
>>> -# Print error message to PNG output
>>> +# Print error message to SVG output
>>> sub _print_error {
>>> 	my ($message) = @_;
>>> -	$message = "- Error -\n \n$message";
>>> -
>>> -	# Create new image with the same size as a graph
>>> -	my $img = GD::Image->new($Graphs::image_size{'width'}, $Graphs::image_size{'height'});
>>> -	$img->interlaced('true');
>>> -
>>> -	# Basic colors
>>> -	my $color_background = $img->colorAllocate(255, 255, 255);
>>> -	my $color_border = $img->colorAllocate(255, 0, 0);
>>> -	my $color_text = $img->colorAllocate(0, 0, 0);
>>> 
>>> -	# Background and border
>>> -	$img->setThickness(2);
>>> -	$img->filledRectangle(0, 0, $img->width, $img->height, $color_background);
>>> -	$img->rectangle(10, 10, $img->width - 10, $img->height - 10, $color_border);
>>> -	
>>> -	# Draw message with line-wrap
>>> -	my $textbox = GD::Text::Wrap->new($img,
>>> -		text => $message,
>>> -		width => ($img->width - 50),
>>> -		color => $color_text,
>>> -		align => 'center',
>>> -		line_space => 5,
>>> -		preserve_nl => 1
>>> +	# Prepare image options
>>> +	my %img = (
>>> +		'width' => $Graphs::image_size{'width'},
>>> +		'height' => $Graphs::image_size{'height'},
>>> +		'text_center' => int($Graphs::image_size{'width'} / 2),
>>> +		'line_height' => 20,
>>> +		'font_family' => "DejaVu Sans, Helvetica, sans-serif" # Matching the IPFire theme
>>> 	);
>>> -	$textbox->set_font(gdLargeFont);
>>> -	$textbox->draw(25, 25);
>>> 
>>> -	# Get PNG output
>>> -	print $img->png;
>>> +	# Line-wrap message to fit image (adjust to font width if necessary)
>>> +	local($Text::Wrap::columns) = int($img{'width'} / 10);
>>> +	$message = wrap('', '', $message);
>>> +
>>> +	# Create new image with fixed background and border
>>> +	print <<END
>>> +<?xml version="1.0" encoding="UTF-8"?>
>>> +<svg width="$img{'width'}px" height="$img{'height'}px" viewBox="0 0 $img{'width'} $img{'height'}" version="1.1" xmlns="http://www.w3.org/2000/svg">
>>> +	<!-- Background -->
>>> +	<rect width="100%" height="100%" fill="white"/>
>>> +	<rect width="100%" height="100%" fill="none" stroke="red" stroke-width="2" transform="scale(0.95)" transform-origin="center"/>
>>> +	<!-- Message -->
>>> +	<text x="$img{'text_center'}" y="50" font-size="20" font-family="$img{'font_family'}" text-anchor="middle">- $Lang::tr{'error'} -</text>
>>> +	<text x="$img{'text_center'}" y="90" font-size="14" font-family="$img{'font_family'}" text-anchor="middle">
>>> +END
>>> +;
>>> +
>>> +	# Print message lines
>>> +	my $shift_y = 0; # Shifts text along y-axis
>>> +	foreach my $line (split(/\n/, $message)) {
>>> +		if($line ne "") { # Don't create empty tspan elements
>>> +			print <<END
>>> +		<tspan x="$img{'text_center'}" dy="$shift_y">$line</tspan>
>>> +END
>>> +;
>>> +			$shift_y = $img{'line_height'};
>>> +		} else { # Create blank lines by summing up unused line height
>>> +			$shift_y += $img{'line_height'};
>>> +		}
>>> +	}
>>> +
>>> +	# Finish SVG output
>>> +	print <<END
>>> +	</text>
>>> +</svg>
>>> +END
>>> +;
>>> }
>>> -- 
>>> 2.27.0.windows.1
>>>
  

Patch

diff --git a/config/cfgroot/graphs.pl b/config/cfgroot/graphs.pl
index 441d4c483..02341eb45 100644
--- a/config/cfgroot/graphs.pl
+++ b/config/cfgroot/graphs.pl
@@ -40,7 +40,7 @@  my $ERROR;
 
 my @GRAPH_ARGS = (
 	# Output format
-	"--imgformat", "PNG",
+	"--imgformat", "SVG",
 
 	# No border
 	"--border", "0",
diff --git a/html/cgi-bin/getrrdimage.cgi b/html/cgi-bin/getrrdimage.cgi
index c08247c57..92f8b585d 100644
--- a/html/cgi-bin/getrrdimage.cgi
+++ b/html/cgi-bin/getrrdimage.cgi
@@ -21,8 +21,7 @@ 
 
 use strict;
 use URI;
-use GD;
-use GD::Text::Wrap;
+use Text::Wrap;
 use experimental 'smartmatch';
 
 # debugging
@@ -52,7 +51,7 @@  my $range = lc $query{'range'}; # lower case
 # Check parameters
 unless(($origin =~ /^\w+?\.cgi$/) && ($graph =~ /^[\w\-.,; ]+?$/) && ($range ~~ @Graphs::time_ranges)) {
 	# Send HTTP headers
-	_start_png_output();
+	_start_svg_output();
 	
 	_print_error("URL parameters missing or malformed.");	
 	exit;
@@ -76,7 +75,7 @@  unless(($origin ~~ @supported_origins) || ($origin eq "getrrdimage.cgi")) {
 
 ### Create graphs ###
 # Send HTTP headers
-_start_png_output();
+_start_svg_output();
 
 # Graphs are first grouped by their origin.
 # This is because some graph categories require special parameter handling.
@@ -204,46 +203,62 @@  if($graphstatus) {
 
 ###--- Internal functions ---###
 
-# Send HTTP headers and switch to binary output
+# Send HTTP headers
 # (don't print any non-image data to STDOUT afterwards)
-sub _start_png_output {
+sub _start_svg_output {
 	print "Cache-Control: no-cache, no-store\n";
-	print "Content-Type: image/png\n";
+	print "Content-Type: image/svg+xml\n";
 	print "\n"; # End of HTTP headers
-	binmode(STDOUT);
 }
 
-# Print error message to PNG output
+# Print error message to SVG output
 sub _print_error {
 	my ($message) = @_;
-	$message = "- Error -\n \n$message";
-
-	# Create new image with the same size as a graph
-	my $img = GD::Image->new($Graphs::image_size{'width'}, $Graphs::image_size{'height'});
-	$img->interlaced('true');
-
-	# Basic colors
-	my $color_background = $img->colorAllocate(255, 255, 255);
-	my $color_border = $img->colorAllocate(255, 0, 0);
-	my $color_text = $img->colorAllocate(0, 0, 0);
 
-	# Background and border
-	$img->setThickness(2);
-	$img->filledRectangle(0, 0, $img->width, $img->height, $color_background);
-	$img->rectangle(10, 10, $img->width - 10, $img->height - 10, $color_border);
-	
-	# Draw message with line-wrap
-	my $textbox = GD::Text::Wrap->new($img,
-		text => $message,
-		width => ($img->width - 50),
-		color => $color_text,
-		align => 'center',
-		line_space => 5,
-		preserve_nl => 1
+	# Prepare image options
+	my %img = (
+		'width' => $Graphs::image_size{'width'},
+		'height' => $Graphs::image_size{'height'},
+		'text_center' => int($Graphs::image_size{'width'} / 2),
+		'line_height' => 20,
+		'font_family' => "DejaVu Sans, Helvetica, sans-serif" # Matching the IPFire theme
 	);
-	$textbox->set_font(gdLargeFont);
-	$textbox->draw(25, 25);
 
-	# Get PNG output
-	print $img->png;
+	# Line-wrap message to fit image (adjust to font width if necessary)
+	local($Text::Wrap::columns) = int($img{'width'} / 10);
+	$message = wrap('', '', $message);
+
+	# Create new image with fixed background and border
+	print <<END
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="$img{'width'}px" height="$img{'height'}px" viewBox="0 0 $img{'width'} $img{'height'}" version="1.1" xmlns="http://www.w3.org/2000/svg">
+	<!-- Background -->
+	<rect width="100%" height="100%" fill="white"/>
+	<rect width="100%" height="100%" fill="none" stroke="red" stroke-width="2" transform="scale(0.95)" transform-origin="center"/>
+	<!-- Message -->
+	<text x="$img{'text_center'}" y="50" font-size="20" font-family="$img{'font_family'}" text-anchor="middle">- $Lang::tr{'error'} -</text>
+	<text x="$img{'text_center'}" y="90" font-size="14" font-family="$img{'font_family'}" text-anchor="middle">
+END
+;
+
+	# Print message lines
+	my $shift_y = 0; # Shifts text along y-axis
+	foreach my $line (split(/\n/, $message)) {
+		if($line ne "") { # Don't create empty tspan elements
+			print <<END
+		<tspan x="$img{'text_center'}" dy="$shift_y">$line</tspan>
+END
+;
+			$shift_y = $img{'line_height'};
+		} else { # Create blank lines by summing up unused line height
+			$shift_y += $img{'line_height'};
+		}
+	}
+
+	# Finish SVG output
+	print <<END
+	</text>
+</svg>
+END
+;
 }