dRonin  adbada4
dRonin firmware
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
encode_fonts.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 
3 """
4 **
5  ******************************************************************************
6  * @file encode_fonts.py
7  * @author dRonin, http://dRonin.org/, Copyright (C) 2016
8  * @addtogroup dRonin Modules
9  * @{
10  * @addtogroup OnScreenDisplay Module
11  * @{
12  * @brief Font converter script for OSD
13  *****************************************************************************/
14 /*
15  * This program is free software; you can redistribute it and/or modify
16  * it under the terms of the GNU General Public License as published by
17  * the Free Software Foundation; either version 3 of the License, or
18  * (at your option) any later version.
19  *
20  * This program is distributed in the hope that it will be useful, but
21  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
22  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
23  * for more details.
24  *
25  * You should have received a copy of the GNU General Public License along
26  * with this program; if not, see <http://www.gnu.org/licenses/>
27  *
28  * Additional note on redistribution: The copyright and license notices above
29  * must be maintained in each individual source file that is a derivative work
30  * of this source file; otherwise redistribution is prohibited.
31  """
32 
33 
34 import os.path as op
35 
36 from string import Template
37 from itertools import chain, repeat
38 
39 import numpy as np
40 import matplotlib.pyplot as plt
41 import matplotlib.image as mpimg
42 
43 file_header = Template("""
44 /**
45  ******************************************************************************
46  * @file $FILENAME
47  * @author dRonin, http://dRonin.org/, Copyright (C) 2016
48  * @author The OpenPilot Team, http://www.openpilot.org, Copyright (C) 2012
49  * @author Thomas Oldbury Copyright (C) 2010
50  * @addtogroup dRonin Modules
51  * @{
52  * @addtogroup OnScreenDisplay Module
53  * @{
54  * @brief Fonts for OSD
55  *****************************************************************************/
56 /*
57  * This program is free software; you can redistribute it and/or modify
58  * it under the terms of the GNU General Public License as published by
59  * the Free Software Foundation; either version 3 of the License, or
60  * (at your option) any later version.
61  *
62  * This program is distributed in the hope that it will be useful, but
63  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
64  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
65  * for more details.
66  *
67  * You should have received a copy of the GNU General Public License along
68  * with this program; if not, see <http://www.gnu.org/licenses/>
69  *
70  * Additional note on redistribution: The copyright and license notices above
71  * must be maintained in each individual source file that is a derivative work
72  * of this source file; otherwise redistribution is prohibited.
73  *
74  * NOTE: This file is generated by encode_fonts.py DO NOT EDIT!
75  * NOTE: Some fonts are CC 3.0 BY-SA and note GPL licensed. See FONT_LICENSE.txt
76  */
77 
78 
79 """)
80 
81 
82 font_header_template = Template("""
83 #ifndef FONTS_H
84 #define FONTS_H
85 
86 #include <openpilot.h>
87 
88 
89 struct FontEntry {
90 \tuint8_t width;
91 \tuint8_t height;
92 \tconst uint8_t* lookup;
93 \tconst uint16_t* data;
94 };
95 
96 #define NUM_FONTS $NUM_FONTS
97 
98 $names
99 
100 #endif /* FONTS_H */
101 """)
102 
103 font_c_template = Template("""
104 static const struct FontEntry font_$name = {
105 \t.width = $width,
106 \t.height = $height,
107 \t.lookup = lookup_$name,
108 \t.data = (uint16_t*)data_$name
109 };
110 
111 """)
112 
113 
114 font_c_table = Template("""
115 const struct FontEntry* fonts[NUM_FONTS] = {$CONTENT};
116 
117 """)
118 
119 def grouper(n, iterable, padvalue=None):
120  "grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"
121  return zip(*[chain(iterable, repeat(padvalue, n-1))]*n)
122 
123 
124 def encode_font(fname, start, last):
125  img = img=mpimg.imread(fname)
126  height, width, depth = img.shape
127  font_height = int(height / 16)
128  font_width = int(width / 16)
129  if font_width * 16 != width or font_height * 16 != height:
130  raise RuntimeError("width or height of image is not divisable by 16!")
131  gray = np.max(img[:, :, :3], axis=-1)
132  bw = gray > 0.9
133 
134  if depth == 4:
135  alpha = img[:, :, -1]
136  mask = alpha > 0.5
137  elif depth == 3:
138  mask = np.logical_or(gray < 0.1, gray > 0.9)
139 
140  # determine which chraracters are present, ceate lookup table
141  pos = 0
142  lookup = np.zeros(256, dtype=np.uint8)
143  lookup.fill(255)
144  for ii in range(start, last + 1):
145  row = int(ii / 16) * font_height
146  col = (ii % 16) * font_width
147  if np.sum(mask[row:row + font_height, col:col + font_width]) > 0:
148  lookup[ii] = pos
149  pos += 1
150  n_active = pos + 1
151  print('Encoding %s: %d x %d pixels / character. Active characters: %d' % (fname, font_width, font_height, n_active))
152 
153  out_height = n_active * font_height
154  # width in bytes
155  byte_width_split = int(np.ceil(font_width / float(8)))
156  # buffers for OSDs with split level/mask buffers
157  mask_buffer = np.zeros((out_height, byte_width_split), dtype=np.uint8)
158  level_buffer = np.zeros((out_height, byte_width_split), dtype=np.uint8)
159  # buffer for OSDs with a single buffer
160  byte_width = int(np.ceil(font_width / float(4)))
161  data_buffer = np.zeros((out_height, byte_width), dtype=np.uint8)
162 
163  row_out = 0
164  for ii, entry in enumerate(lookup):
165  if entry == 255:
166  continue
167  for row in range(0, font_height):
168  row_in = int(ii / 16) * font_height + row
169  for col in range(font_width):
170  col_in = (ii % 16) * font_width + col
171  if mask[row_in, col_in]:
172  pos = int(col / 8)
173  mask_buffer[row_out, pos] |= 0x01 << (7 - (col % 8))
174  level_buffer[row_out, pos] |= ~bw[row_in, col_in] << (7 - (col % 8))
175  pos = int(col / 4)
176  data_buffer[row_out, pos] |= (bw[row_in, col_in] << (7 - 2 * (col % 4))) | (0x01 << (6 - 2 * (col % 4)))
177  row_out += 1
178 
179  if data_buffer.shape[1] == 3:
180  data_buffer = np.c_[data_buffer, np.zeros((out_height, 1), dtype=np.uint8)]
181 
182  data_split_buffer = np.c_[level_buffer, mask_buffer]
183 
184  font = dict(width=font_width, height=font_height,
185  data=data_buffer, data_split=data_split_buffer, lookup=lookup)
186  return font
187 
188 
189 def c_format_font(fid, name, font, buffer_names, buffers, row_width=16):
190  """Format image as a C header"""
191  for buf_name, buf in zip(buffer_names, buffers):
192  if len(font[buf].shape) == 1 or font[buf].shape[1] == 1:
193  word_size = 8
194  elif font[buf].shape[1] == 2:
195  word_size = 16
196  else:
197  word_size = 32
198  fid.write('const uint%d_t %s_%s[] = {\n' % (word_size, buf_name, name))
199 
200  n_rows = np.ceil(len(font[buf].ravel()) / float(row_width))
201  for ii, row in enumerate(grouper(row_width, font[buf].ravel())):
202  row = [val for val in row if val is not None]
203  fid.write('\t')
204 
205  if word_size == 8:
206  for byte in row:
207  fid.write('0x%0.2X, ' % byte)
208  elif word_size == 16:
209  for pos in range(int(len(row) / 2)):
210  fid.write('0x%0.2X%0.2X, ' % (row[2 * pos], row[2 * pos + 1]))
211  elif word_size == 32:
212  for pos in range(int(len(row) / 4)):
213  value = (row[4 * pos], row[4 * pos + 1], row[4 * pos + 2], row[4 * pos + 3])
214  fid.write('0x%0.2X%0.2X%0.2X%0.2X, ' % value)
215  if ii < n_rows - 1:
216  fid.write('\n')
217  else:
218  fid.write('};\n\n')
219 
220 
221 if __name__ == '__main__':
222  from optparse import OptionParser
223  parser = OptionParser()
224  parser.add_option('-o', '--out', dest='filename_out',
225  help='Output file', metavar='FILE')
226  parser.add_option('-d', '--header', dest='filename_header_out',
227  help='Output header file', metavar='FILE')
228  parser.add_option('-s', '--start', dest='start',
229  help='First ASCII character to encode',
230  type="int", default=0)
231  parser.add_option('-l', '--last', dest='last',
232  help='Last ASCII character to encode',
233  type="int", default=255)
234 
235  options, args = parser.parse_args()
236 
237  in_files = args
238  names = [fname[:-4] for fname in in_files]
239  print('Input fonts: %s' % str(in_files))
240  fonts = dict()
241  for name,fname in zip(names, in_files):
242  fonts[name] = encode_font(fname, options.start, options.last)
243 
244  n_fonts = len(fonts)
245  with open(options.filename_header_out, 'w') as fid:
246  font_name_str = ''
247  for ii, name in enumerate(names):
248  font_name_str += '#define %s %d\n' % (name.upper(), ii)
249  fid.write(file_header.substitute(FILENAME=op.basename(options.filename_header_out)))
250  fid.write(font_header_template.substitute(NUM_FONTS=n_fonts, names=font_name_str))
251 
252 
253  with open(options.filename_out, 'w') as fid:
254  fid.write(file_header.substitute(FILENAME=op.basename(options.filename_header_out)))
255  fid.write('#include "fonts.h"\n')
256  fid.write('#if defined(PIOS_VIDEO_SPLITBUFFER)\n')
257  for name in names:
258  font = fonts[name]
259  c_format_font(fid, name, font, buffer_names=('data',), buffers=('data_split',))
260  fid.write('#else /* defined(PIOS_VIDEO_SPLITBUFFER) */\n')
261  for name in names:
262  font = fonts[name]
263  c_format_font(fid, name, font, buffer_names=('data',), buffers=('data',))
264  fid.write('#endif /* defined(PIOS_VIDEO_SPLITBUFFER) */\n')
265  for name in names:
266  font = fonts[name]
267  c_format_font(fid, name, font, buffer_names=('lookup',), buffers=('lookup',))
268 
269  for name in names:
270  font = fonts[name]
271  fid.write(font_c_template.substitute(width=font['width'],
272  height=font['height'], name=name))
273  # write the font table
274  content = ''
275  for name in names:
276  content += '&font_' + name + ', '
277  content = content[:-2]
278  fid.write(font_c_table.substitute(CONTENT=content))
static int print(char **out, const char *format, va_list args)