bmpblk: Introduce dpi option to boards.yaml
Add a dpi option to boards.yaml to control the resolution of the
generated text bitmaps. The font size passed to pango-view is calculated
from the 'height' config in format.yaml, so that the bitmap resolution
will be roughly proportional to the height as before. Therefore,
FONT_SIZE can be removed from Makefile. See go/bmpblk-scale for details.
The image generation flow is also changed in this patch. For text
bitmaps (both generic and localized bitmaps), we use pango-view to
generate PNG files directly, so no SVGs are involved anymore. As a
result, 'num_lines' is calculated by comparing the height of multi-line
and one-line PNGs, both of which are generated by the text_to_png_svg
script.
When the size of a bitmap is larger than what will be displayed at
runtime, we downscale it based on the 'screen' config in boards.yaml. To
achieve that, we revert the changes in CL:2291719 so that the config can
match the physical panel resolution. As a direct consequence, the size
of glyph bitmaps will become the same as they are rendered.
BUG=b:147123923, b:168810876
TEST=emerge-asurada chromeos-bmpblk chromeos-bootimage
TEST=emerge-puff chromeos-bmpblk chromeos-bootimage
TEST=emerge-volteer chromeos-bmpblk chromeos-bootimage
BRANCH=none
Change-Id: I9b192ae9e8eb6d8c2be150958bb763e09f41b179
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/bmpblk/+/2549365
Commit-Queue: Yu-Ping Wu <yupingso@chromium.org>
Tested-by: Yu-Ping Wu <yupingso@chromium.org>
Reviewed-by: Joel Kitching <kitching@chromium.org>
diff --git a/build.py b/build.py
index b183899..e0dda08 100755
--- a/build.py
+++ b/build.py
@@ -63,9 +63,10 @@
PANEL_KEY = 'panel'
SDCARD_KEY = 'sdcard'
BAD_USB3_KEY = 'bad_usb3'
+DPI_KEY = 'dpi'
+TEXT_COLORS_KEY = 'text_colors'
LOCALES_KEY = 'locales'
RTL_KEY = 'rtl'
-TEXT_COLORS_KEY = 'text_colors'
RW_OVERRIDE_KEY = 'rw_override'
BMP_HEADER_OFFSET_NUM_LINES = 6
@@ -103,7 +104,8 @@
def convert_text_to_png(locale, input_file, font, output_dir, height=None,
- max_width=None, bgcolor='#000000', fgcolor='#ffffff',
+ max_width=None, dpi=None, bgcolor='#000000',
+ fgcolor='#ffffff',
**options):
"""Converts text files into PNG image files.
@@ -125,18 +127,22 @@
command.append('--lan=%s' % locale)
if font:
command.append("--font='%s'" % font)
- font_size = os.getenv('FONT_SIZE')
- if font_size:
+ if height:
+ # Font size should be proportional to the height. Here we use 2 as the
+ # divisor so that setting dpi to 96 (pango-view's default) in boards.yaml
+ # will be roughly equivalent to setting the screen resolution to 1366x768.
+ font_size = height / 2
command.append('--point=%r' % font_size)
if max_width:
- # Without the --width option set, the minimum height of the output SVG
- # image is roughly 22px (for locale 'en'). With --width=WIDTH passed to
- # pango-view, the width of the output seems to always be (WIDTH * 4 / 3),
- # regardless of the font being used. Therefore, set the max_width in
- # points as follows to prevent drawing from exceeding canvas boundary in
- # depthcharge runtime.
- max_width_pt = int(22 * max_width / height / (4 / 3))
+ # When converting text to PNG by pango-view, the ratio of image height to
+ # the font size is usually no more than 1.1875 (with Roboto). Therefore,
+ # set the `max_width_pt` as follows to prevent UI drawing from exceeding
+ # the canvas boundary in depthcharge runtime. The divisor 2 is the same in
+ # the calculation of `font_size` above.
+ max_width_pt = int(max_width / 2 * 1.1875)
command.append('--width=%d' % max_width_pt)
+ if dpi:
+ command.append('--dpi=%d' % dpi)
command.append('--margin=0')
command.append('--bgcolor="%s"' % bgcolor)
command.append('--color="%s"' % fgcolor)
@@ -234,7 +240,7 @@
f.write(inputs[name] + '\n')
-def convert_localized_strings(formats):
+def convert_localized_strings(formats, dpi):
"""Converts localized strings."""
# Make a copy of formats to avoid modifying it
formats = copy.deepcopy(formats)
@@ -301,6 +307,7 @@
kwargs = {
'height': style[KEY_HEIGHT],
'max_width': style[KEY_MAX_WIDTH],
+ 'dpi': dpi,
'bgcolor': style[KEY_BGCOLOR],
'fgcolor': style[KEY_FGCOLOR],
}
@@ -322,8 +329,10 @@
exit('Failed to render some locales')
-def build_strings(formats):
+def build_strings(formats, board_config):
"""Builds text strings."""
+ dpi = board_config[DPI_KEY]
+
# Convert glyphs
print('Converting glyphs...')
convert_glyphs()
@@ -341,12 +350,13 @@
if not convert_text_to_png(None, input_file, default_font, STAGE_DIR,
height=style[KEY_HEIGHT],
max_width=style[KEY_MAX_WIDTH],
+ dpi=dpi,
bgcolor=style[KEY_BGCOLOR],
fgcolor=style[KEY_FGCOLOR]):
exit('Failed to convert text %s' % input_file)
# Convert localized strings
- convert_localized_strings(formats)
+ convert_localized_strings(formats, dpi)
def load_boards_config(filename):
@@ -542,25 +552,21 @@
self.locales = [LocaleInfo(code, code in rtl_locales)
for code in locales]
- def _get_svg_height(self, svg_file):
- tree = ElementTree.parse(svg_file)
- height = tree.getroot().attrib['height']
- m = re.match('([0-9]+)pt', height)
- if not m:
- raise BuildImageError('Cannot get height from %s' % svg_file)
- return int(m.group(1))
+ def _get_png_height(self, png_file):
+ with Image.open(png_file) as image:
+ return image.size[1]
def get_num_lines(self, file, one_line_dir):
"""Gets the number of lines of text in `file`."""
name, _ = os.path.splitext(os.path.basename(file))
- svg_name = name + '.svg'
- multi_line_file = os.path.join(os.path.dirname(file), svg_name)
- one_line_file = os.path.join(one_line_dir, svg_name)
- # The number of lines id determined by comparing the height of
+ png_name = name + '.png'
+ multi_line_file = os.path.join(os.path.dirname(file), png_name)
+ one_line_file = os.path.join(one_line_dir, png_name)
+ # The number of lines is determined by comparing the height of
# `multi_line_file` with `one_line_file`, where the latter is generated
# without the '--width' option passed to pango-view.
- height = self._get_svg_height(multi_line_file)
- line_height = self._get_svg_height(one_line_file)
+ height = self._get_png_height(multi_line_file)
+ line_height = self._get_png_height(one_line_file)
return int(round(height / line_height))
def convert_svg_to_png(self, svg_file, png_file, height, num_lines,
@@ -586,7 +592,7 @@
command.append(svg_file)
subprocess.check_call(' '.join(command), shell=True)
- def convert_to_bitmap(self, input_file, num_lines, background, output,
+ def convert_to_bitmap(self, input_file, height, num_lines, background, output,
max_colors):
"""Converts an image file `input_file` to a BMP file `output`."""
image = Image.open(input_file)
@@ -604,10 +610,18 @@
else:
target = image
- # Stretch image horizontally for stretched display
+ width_px, height_px = image.size
+ max_height_px = int(self.canvas_px * height / self.SCALE_BASE) * num_lines
+ # If the image size is larger than what will be displayed at runtime,
+ # downscale it.
+ if height_px > max_height_px:
+ height_px = max_height_px
+ width_px = height_px * image.size[0] // image.size[1]
+ # Stretch image horizontally for stretched display.
if self.panel_stretch != 1:
- new_width_px = int(image.size[0] * self.panel_stretch)
- new_size = (new_width_px, image.size[1])
+ width_px = int(width_px * self.panel_stretch)
+ new_size = width_px, height_px
+ if new_size != image.size:
target = target.resize(new_size, Image.BICUBIC)
# Export and downsample color space.
@@ -650,7 +664,8 @@
self.convert_svg_to_png(file, png_file, height, num_lines, background)
file = png_file
- self.convert_to_bitmap(file, num_lines, background, output, max_colors)
+ self.convert_to_bitmap(file, height, num_lines, background, output,
+ max_colors)
def convert_sprite_images(self):
"""Converts sprite images."""
@@ -684,7 +699,7 @@
heights[name] = style[KEY_HEIGHT]
max_widths[name] = style[KEY_MAX_WIDTH]
- files = glob.glob(os.path.join(self.stage_dir, SVG_FILES))
+ files = glob.glob(os.path.join(self.stage_dir, PNG_FILES))
self.convert(files, self.output_dir, heights, max_widths,
self.text_max_colors)
@@ -710,7 +725,7 @@
print(' ' + locale, end='', file=sys.stderr, flush=True)
os.makedirs(ro_locale_dir)
self.convert(
- glob.glob(os.path.join(stage_locale_dir, SVG_FILES)),
+ glob.glob(os.path.join(stage_locale_dir, PNG_FILES)),
ro_locale_dir, heights, max_widths, self.text_max_colors,
one_line_dir=os.path.join(stage_locale_dir, ONE_LINE_DIR))
print(file=sys.stderr)
@@ -806,24 +821,22 @@
self.copy_images_to_rw()
-def build_images(board, formats):
- """Builds images for `board`."""
- configs = load_boards_config(BOARDS_CONFIG_FILE)
- print('Building for ' + board)
- converter = Converter(board, formats, configs[board], OUTPUT_DIR)
- converter.build()
-
-
def main():
"""Builds bitmaps for firmware screens."""
parser = argparse.ArgumentParser()
parser.add_argument('board', help='Target board')
args = parser.parse_args()
+ board = args.board
with open(FORMAT_FILE, encoding='utf-8') as f:
formats = yaml.load(f)
- build_strings(formats)
- build_images(args.board, formats)
+ board_config = load_boards_config(BOARDS_CONFIG_FILE)[board]
+
+ # TODO(yupingso): Put everything into Converter class
+ print('Building for ' + board)
+ build_strings(formats, board_config)
+ converter = Converter(board, formats, board_config, OUTPUT_DIR)
+ converter.build()
if __name__ == '__main__':