e69481266909de535c1bea5c4a2de1e6f19d4bfe
[unifont.git] / src / unipng2hex
1 #!/usr/bin/perl
2
3 #  unipng2hex - program to turn a .png glyph matrix into a
4 #               GNU Unifont hex glyph set of 256 characters
5 #
6 #  Synopsis: unipng2hex [-i in_file.png] [-o out_file.hex] [-w]
7 #
8 #
9 #  Author: Paul Hardy, unifoundry <at> unifoundry.com, December 2007
10 #
11 #  Perl conversion: Andrew Miller, August 2013
12 #
13 #
14 #   Copyright (C) 2007-2008 Paul Hardy
15 #
16 #   This program is free software: you can redistribute it and/or modify
17 #   it under the terms of the GNU General Public License as published by
18 #   the Free Software Foundation, either version 2 of the License, or
19 #   (at your option) any later version.
20 #
21 #   This program is distributed in the hope that it will be useful,
22 #   but WITHOUT ANY WARRANTY; without even the implied warranty of
23 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24 #   GNU General Public License for more details.
25 #
26 #   You should have received a copy of the GNU General Public License
27 #   along with this program.  If not, see <http://www.gnu.org/licenses/>.
28
29 use Getopt::Long;
30 use GD qw (:DEFAULT :cmp);
31
32 $result = GetOptions (
33         "help|?",
34         "input|i=s" => \$input,
35         "output|o=s" => \$output,
36         "width|w=i" => \$minwidth
37 );
38
39 if ($opt_help) {
40         print << "END";
41
42 Turn a .png glyph matrix into a GNU Unifont hex glyph set of 256 characters
43
44 Syntax:
45
46    unipng2hex -i <Input_File> [-o <Output_File>] [-w <width>]
47
48    -i, --input               the input PNG file
49    -o, --output              the output hex file (write to STDOUT if not
50                              specified)
51    -w, --width               the minimum width of the output glyphs - valid
52                              values are 16, 24 and 32 (default is no minimum
53                              width)
54    -?, --help                display this help and exit
55
56
57 Example:
58
59    unipng2hex -i u83.png -o u83.hex
60 END
61         exit ()
62 }
63
64 if (not $input) {
65         die ("No input file specified\n")
66 }
67
68 #if (not $output) {
69 #       die ("No output file specified\n")
70 #}
71
72 GD::Image->trueColor (1);
73 $im = new GD::Image ("$input") or die ("Cannot open image\n");
74
75 if ($im->isTrueColor ()) {
76         $im->trueColorToPalette ();
77 }
78
79 # Disable transparency if it has been set
80 $im->transparent (-1);
81
82 $black = $im->colorClosest (0, 0, 0);
83 $white = $im->colorClosest (255, 255, 255);
84
85 # Exit if black and white are not in the image's palette
86 if ($white == -1 || $black == -1) {
87         die ("Invalid image - colors do not match\n");
88 } else {
89         ($black_red, $black_green, $black_blue) = $im->rgb ($black);
90         ($white_red, $white_green, $white_blue) = $im->rgb ($white);
91 }
92
93 ($imagewidth, $imageheight) = $im->getBounds();
94
95 $charxoffset = 4;
96 $gridxoffset = 48;
97 $gridyoffset = 32;
98
99 $boxsize = ($imagewidth - $gridxoffset) / 16;
100
101 if ($boxsize == 32) {
102         # try to determine if charheight is 16 or 24 by examining grid
103         $pixel = $im->getPixel ($imagewidth - 1, $imageheight - 5);
104
105         if ($im->rgb ($pixel) == ($black_red, $black_green, $black_blue)) {
106                 $charyoffset = 7;
107                 $charheight = 16;
108                 $charmaxwidth = 3;
109         } elsif ($im->rgb ($pixel) == ($white_red, $white_green, $white_blue)) {
110                 $charyoffset = 4;
111                 $charheight = 24;
112                 $charmaxwidth = 3;
113         } else {
114                 die ("Cannot determine font height\n")
115         }
116 } elsif ($boxsize == 40) {
117         $charyoffset = 4;
118         $charheight = 32;
119         $charmaxwidth = 4;
120 } else {
121         die ("Invalid image - incorrect size\n")
122 }
123
124 if ($minwidth) {
125         if ($charheight == 16 || $charheight == 24) {
126                 if (!($minwidth == 16 || $minwidth == 24)) {
127                         die ("invalid width\n");
128                 }
129         } elsif ($charheight == 32) {
130                 if (!($minwidth == 16 || $minwidth == 24 || $minwidth == 32)) {
131                         die ("Invalid width\n");
132                 }
133         }
134
135         $minwidth /= 8;
136 }
137
138 # Build an array of single digit hex character images
139 for ($count = 0; $count < 16; $count++) {
140         $digit = new GD::Image (8, 16, 0);
141         $black = $digit->colorAllocate ($black_red, $black_green, $black_blue);
142         $white = $digit->colorAllocate ($white_red, $white_green, $white_blue);
143
144         $digit->fill (0, 0, $white);
145
146         $digit->string (gdLargeFont, 0, 0, sprintf ('%X', $count), $black);
147
148         push (@digits, $digit);
149 }
150
151 $codepoint = 0;
152
153 # Get plane
154 for ($d = 0; $d < 2; $d++) {
155         $c = new GD::Image (8, 16);
156         $c->copy ($im, 0, 0, 24 + ($d * 8), 9, 8, 16);
157
158         for ($count = 0; $count < 16; $count++) {
159                 if (!($c->compare ($digits[$count]) & GD_CMP_IMAGE)) {
160                         $codepoint = ($codepoint << 4) + $count;
161                 }
162         }
163 }
164
165 # Get page
166 for ($d = 0; $d < 3; $d++) {
167         $c = new GD::Image (8, 16);
168         $c->copy ($im, 0, 0, (($boxsize - 24) / 2) + $gridxoffset + ($d * 8), 9, 8, 16);
169
170         for ($count = 0; $count < 16; $count++) {
171                 if (!($c->compare ($digits[$count]) & GD_CMP_IMAGE)) {
172                         $codepoint = ($codepoint << 4) + $count;
173                 }
174         }
175 }
176
177 # Calculate the first codepoint and it's display width
178 $codepoint = ($codepoint << 4);
179 $display_width = $codepoint > 0xFFFF ? 6 : 4;
180
181 if ($output) {
182         open (HEXFILE, ">$output") or die ("Cannot save file\n");
183 } else {
184         *HEXFILE = *STDOUT;
185 }
186 binmode HEXFILE;
187
188 for ($col = 0; $col < 16; $col++) {
189         for ($row = 0; $row < 16; $row++) {
190                 # Calculate glyph width
191                 $charwidth = 0;
192
193                 for ($count = 0; $count < $charmaxwidth; $count++) {
194                         $c = new GD::Image (8, $charheight, 0);
195                         $c->copy ($im, 0, 0, ($col * $boxsize) + ($count * 8) + $gridxoffset + $charxoffset, ($row * $boxsize) + $gridyoffset + $charyoffset, 8, $charheight);
196
197                         if ($c->colorsTotal > 1 || $c->colorExact ($white_red, $white_green, $white_blue) == -1) {
198                                 $charwidth = $count + 1
199                         }
200                 }
201
202                 # Force glyphs to minimum width if minwidth is set
203                 if ($charwidth < $minwidth) {
204                         $charwidth = $minwidth;
205                 }
206
207                 if ($charwidth != 0) {
208                         $char = sprintf ("%0*X:", $display_width, $codepoint);
209
210                         # Loop through the glyph and build up it's hex representation
211                         for ($j = 0; $j < $charheight; $j++) {
212                                 $line = 0;
213
214                                 for ($i = 0; $i < $charwidth * 8; $i++) {
215                                         $bit = $im->getPixel (($col * $boxsize)+ $i + $gridxoffset + $charxoffset, ($row * $boxsize) + $j + $gridyoffset + $charyoffset) == $black ? 1 : 0;
216
217                                         $line = ($line << 1) + $bit;
218                                 }
219
220                                 $char = $char . sprintf ("%0*X", $charwidth * 2, $line);
221                         }
222
223                         print HEXFILE "$char\n";
224                 }
225
226                 $codepoint += 1;
227         }
228 }
229
230 # Only close HEXFILE if it isn't mapped to STDOUT.
231 if ($output) {
232         close HEXFILE;
233 }