blob: d4e1a0a0685d8aef850320fdd593836a987f699e [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2022 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
"""PNG to rgb565 cc file converter."""
import argparse
import importlib.resources
import logging
import math
from pathlib import Path
from PIL import Image # type: ignore
from jinja2 import Environment, DictLoader, make_logging_undefined
jinja_templates = {
f.name: f.read_text()
for f in importlib.resources.files('pw_graphics.templates').iterdir()
if f.name.endswith('.jinja')
}
jinja_env = Environment(
# Load templates automatically from pw_graphics/templates
loader=DictLoader(jinja_templates),
# Raise errors if variables are undefined in templates
undefined=make_logging_undefined(
logger=logging.getLogger(__package__),
),
# Trim whitespace in templates
trim_blocks=True,
lstrip_blocks=True,
)
def _arg_parser():
"""Setup argparse."""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('IMAGE', type=Path, help="Input image. ")
parser.add_argument(
'-W', '--sprite-width', type=int, required=True, help='Sprite width.'
)
parser.add_argument(
'-H', '--sprite-height', type=int, required=True, help='Sprite height.'
)
parser.add_argument(
'--output-mode', default='rgb565', choices=['rgb565', 'font'], help=''
)
parser.add_argument(
'--transparent-color',
default='255,0,255',
help='Comma separated r,g,b values to use as transparent.',
)
return parser.parse_args()
def rgb8888to565(rgba8888):
red = (rgba8888 & 0xFF0000) >> 16
green = (rgba8888 & 0xFF00) >> 8
blue = rgba8888 & 0xFF
return rgb565(red, green, blue)
def rgb565(red, green, blue) -> int:
return ((red & 0xF8) << 8) | ((green & 0xFC) << 3) | ((blue & 0xF8) >> 3)
def render_rgb565_header(
alpha_composite: Image,
tile_width: int,
tile_height: int,
sprite_width: int,
sprite_height: int,
) -> list:
sprite_data: list[list[str]] = []
for theight in range(0, tile_height):
for twidth in range(0, tile_width):
sprite_data.append([])
for currenty in range(0, sprite_height):
for currentx in range(0, sprite_width):
globalx = twidth * sprite_width + currentx
globaly = theight * sprite_height + currenty
pix = alpha_composite.getpixel((globalx, globaly))
hex_rgb565 = rgb565(pix[0], pix[1], pix[2])
hex_r = f"{pix[0]:02x}"
hex_g = f"{pix[1]:02x}"
hex_b = f"{pix[2]:02x}"
sprite_data[-1].append(
f"{hex_rgb565:#04x}, // #{hex_r}{hex_g}{hex_b}\n"
)
return sprite_data
def render_font_header(
alpha_composite: Image,
tile_width: int,
tile_height: int,
sprite_width: int,
sprite_height: int,
) -> list:
sprite_data: list[list[str]] = []
for theight in range(0, tile_height):
for twidth in range(0, tile_width):
sprite_data.append([])
for currenty in range(0, sprite_height):
line = '0b'
for currentx in range(0, sprite_width):
globalx = twidth * sprite_width + currentx
globaly = theight * sprite_height + currenty
pix = alpha_composite.getpixel((globalx, globaly))
if pix[0] + pix[1] + pix[2] == 0:
line += '0'
else:
line += '1'
line += ',\n'
sprite_data[-1].append(line)
return sprite_data
def main() -> None:
"""Main."""
args = _arg_parser()
image_path = args.IMAGE
file_name = image_path.stem.lower().replace(' ', '_')
sprite_width = args.sprite_width
sprite_height = args.sprite_height
if not image_path.is_file():
raise FileNotFoundError
transparent_color = (255, 0, 255)
if args.transparent_color:
rgb = args.transparent_color.split(',')
assert len(rgb) == 3
transparent_color = (int(rgb[0]), int(rgb[1]), int(rgb[2]))
out_path = Path(image_path.stem + '.h')
img = Image.open(image_path).convert('RGBA')
background = Image.new('RGBA', img.size, transparent_color)
alpha_composite = Image.alpha_composite(background, img)
transparent_color_int = rgb565(*transparent_color)
image_width = img.size[0]
image_height = img.size[1]
tile_width = math.floor(image_width / sprite_width)
tile_height = math.floor(image_height / sprite_height)
tile_width = max(tile_width, 1)
tile_height = max(tile_height, 1)
if args.output_mode == 'rgb565':
sprite_data = render_rgb565_header(
alpha_composite,
tile_width,
tile_height,
sprite_width,
sprite_height,
)
elif args.output_mode == 'font':
sprite_data = render_font_header(
alpha_composite,
tile_width,
tile_height,
sprite_width,
sprite_height,
)
template = jinja_env.get_template(args.output_mode + '.jinja')
out_path.write_text(
template.render(
file_name=file_name,
sprite_data=sprite_data,
sprite_width=sprite_width,
sprite_height=sprite_height,
transparent_color=f"{transparent_color_int:#04x}",
count=len(sprite_data),
)
)
if __name__ == '__main__':
main()