buildimage 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. #! /usr/bin/perl -w
  2. #
  3. # Render SVG files containing one or more images into an ICO or BMP.
  4. #
  5. # Copyright (C) 2010 Joel Holdsworth
  6. #
  7. # This library is free software; you can redistribute it and/or
  8. # modify it under the terms of the GNU Lesser General Public
  9. # License as published by the Free Software Foundation; either
  10. # version 2.1 of the License, or (at your option) any later version.
  11. #
  12. # This library is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. # Lesser General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU Lesser General Public
  18. # License along with this library; if not, write to the Free Software
  19. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  20. use strict;
  21. use warnings;
  22. use XML::LibXML;
  23. use MIME::Base64;
  24. use File::Copy;
  25. # Parse the parameters
  26. my $svgFileName = $ARGV[0];
  27. my $outFileName = $ARGV[1];
  28. die "Cannot open SVG file" unless defined($svgFileName);
  29. die "Cannot open output file" unless defined($outFileName);
  30. $outFileName =~ m/(.*)\.(.*)/;
  31. my $outName = $1;
  32. my $ext = lc($2);
  33. die "Only BMP, ICO and CUR outputs are supported" unless $ext eq "bmp" or $ext eq "ico" or $ext eq "cur";
  34. my $renderedSVGFileName = "$svgFileName.png";
  35. my @pngFiles;
  36. my @hotspot;
  37. $ENV{"SOURCE_DATE_EPOCH"} ||= "0"; # for reproducible builds
  38. # Get the programs from the environment variables
  39. my $convert = $ENV{"CONVERT"} || "convert";
  40. my $rsvg = $ENV{"RSVG"} || "rsvg-convert";
  41. my @icotool_args = ($ENV{"ICOTOOL"} || "icotool", "--create",
  42. $ext eq "cur" ? "--cursor" : "--icon", "-o", $outFileName);
  43. # Be ready to abort
  44. sub cleanup()
  45. {
  46. unlink $renderedSVGFileName;
  47. unlink $_ foreach(@pngFiles);
  48. }
  49. $SIG{"INT"} = "cleanup";
  50. $SIG{"HUP"} = "cleanup";
  51. $SIG{"TERM"} = "cleanup";
  52. $SIG{"__DIE__"} = "cleanup";
  53. my %label =
  54. (
  55. 'ico' => 'icon:(\d*)-(\d*)',
  56. 'cur' => 'cursor:(\d*)-(\d*)',
  57. 'bmp' => 'bitmap:(\d*)-(\d*)',
  58. );
  59. # run a shell command and die on error
  60. sub shell(@)
  61. {
  62. my @args = @_;
  63. system(@args) == 0 or die "@args failed: $?";
  64. }
  65. # add an image to the icon/cursor
  66. sub add_image($$$)
  67. {
  68. my ($file, $size, $depth) = @_;
  69. if (defined $hotspot[$size])
  70. {
  71. my @coords = @{$hotspot[$size]};
  72. push @icotool_args, "--hotspot-x=$coords[0]", "--hotspot-y=$coords[1]";
  73. }
  74. push @icotool_args, ($size >= 128 && $depth >= 24) ? "--raw=$file" : $file;
  75. push @pngFiles, $file;
  76. }
  77. # Render the SVG image
  78. my @rsvgCmd;
  79. push(@rsvgCmd, $rsvg);
  80. push(@rsvgCmd, $svgFileName);
  81. push(@rsvgCmd, "-o") if ($rsvg eq "rsvg-convert");
  82. push(@rsvgCmd, $renderedSVGFileName);
  83. shell @rsvgCmd;
  84. # Render the images in the SVG
  85. my $xml = XML::LibXML->load_xml( location => $svgFileName );
  86. my $xc = XML::LibXML::XPathContext->new($xml);
  87. $xc->registerNs('x', 'http://www.w3.org/2000/svg');
  88. if ($ext eq "bmp")
  89. {
  90. foreach my $element ($xc->findnodes("/x:svg"))
  91. {
  92. next unless $element->{id} =~ /bitmap:(\d*)-(\d*)/;
  93. my $size = $1;
  94. my $depth = $2;
  95. if ($depth == 24) {
  96. shell $convert, $renderedSVGFileName, "+matte", $outFileName;
  97. } else {
  98. shell $convert, $renderedSVGFileName, $outFileName;
  99. }
  100. cleanup();
  101. exit(0);
  102. }
  103. }
  104. # fetch hotspot rectangles for the various sizes
  105. if ($ext eq "cur")
  106. {
  107. foreach my $element ($xc->findnodes("/x:svg/x:rect"))
  108. {
  109. next unless $element->{id} =~ /hotspot:(\d*)/;
  110. $hotspot[$1] = [ $element->{x}, $element->{y} ];
  111. }
  112. }
  113. # extract rectangles from the rendered svg
  114. foreach my $element ($xc->findnodes("/x:svg/*[\@id]"))
  115. {
  116. next unless $element->{id} =~ /$label{$ext}/;
  117. my $size = $1;
  118. my $depth = $2;
  119. warn "Unexpected depth" unless
  120. $depth == 1 or $depth == 4 or $depth == 8 or $depth == 24 or $depth == 32;
  121. my $file = "$outName-$size-$depth.png";
  122. my $x = $element->{x};
  123. my $y = $element->{y};
  124. my $width = $element->{width};
  125. my $height = $element->{height};
  126. if ($element->{'xlink:href'})
  127. {
  128. # extract base64-encoded png image
  129. (my $data = $element->{'xlink:href'}) =~ s/data:image\/png;base64//;
  130. open FILE, ">$file" or die "$!";
  131. print FILE decode_base64($data);
  132. close FILE;
  133. }
  134. else
  135. {
  136. shell $convert, $renderedSVGFileName, "-crop", "${width}x${height}+$x+$y", "-depth", $depth, $file;
  137. }
  138. add_image( $file, $size, $depth );
  139. }
  140. die "no render directive found in $svgFileName" unless @pngFiles;
  141. shell @icotool_args;
  142. # Delete the intermediate images
  143. cleanup();