Skip to content

Commit

Permalink
Add generator and parser for bitmap.hpp (#2313)
Browse files Browse the repository at this point in the history
* Combined the converter from <ico>.png to bitmap.hpp and reverse in one script: pp_png2hpp.py

* Minor change ficed variables from testing.

* Cleanup output for parser. Add description to readme.md

* Update pp_png2hpp.py

Added the suggested and much cleaner argparse code from zxkmm.
Added a icon-name handling, to convert one or a comma seperated subset of icons by name.

* Update pp_bitmap_parser.py

Updated the handler with dynamic in/outputs, in parallel to the pp_png2hpp.py script ... But I think I'll delete this one, after I decide how to handle the alpha (transparent) code.
  • Loading branch information
LupusE authored Oct 20, 2024
1 parent c90f094 commit a4c2e15
Show file tree
Hide file tree
Showing 3 changed files with 339 additions and 1 deletion.
26 changes: 25 additions & 1 deletion firmware/tools/bitmap_tools/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,25 @@
bitmap is for icons, it's colorless and headless; splash and modal isn't using bitmap
bitmap is for icons, it's colorless and headless; splash and modal isn't
using bitmap

## Bitmap helper folder
The folder `bitmap_tools` contains scripts to convert icons in png format to
the PortaPack readable format in `bitmap.hpp`.

### Convert a folder contains one or more icon.png to one bitmap.hpp
The `make_bitmap.py` is the traditional helper, well tested. The folder with
the icons is given as argument and generates the `./bitmap.hpp`. This file
needs to be copied to `mayhem-firmware/firmware/application/`.
The icon size needs to be a multiple of 8. The generated icon is black/white.

### Convert bitmap array to icon.png
The `bitmap_arr_reverse_decode.py` takes an array from the bitmap.hpp and
convert it back to a png.

### Convert both ways
The `pp_png2hpp.py` is based on the privious scripts, as all in one solution.
With the `--hpp bitmap.hpp` file and the `--graphics /folder_to/png_icons/`
arguments, theis script will generate a `bitmap.hpp`.
Add the `--reverse` argument to generate png icons from the given `bitmap.hpp`.

Especially the reverse function got a parser, to automatic get the filename
and size of the image.
77 changes: 77 additions & 0 deletions firmware/tools/bitmap_tools/pp_bitmap_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/usr/bin/env python3

## Note: This helper was just a POC for pp_png3hpp.py to test the regex.

import argparse
import numpy as np
import re
from PIL import Image


def parse_bitmaphpp(bitmaphpp_file, icon_name):
ico_pattern = re.compile(r"static constexpr uint8_t bitmap_(.*)_data\[\] = {\n((?:\s+(?:.*)\n)+)};\nstatic constexpr Bitmap bitmap_.*\{\n\s+\{(.*)\},", re.MULTILINE)
ico_data = []

# read file to buffer, to find multiline regex
readfile = open(bitmaphpp_file,'r')
buff = readfile.read()
readfile.close()

if icon_name == 'all':
for match in ico_pattern.finditer(buff):
ico_data.append([match.group(1), match.group(2), match.group(3)])
else:
for match in ico_pattern.finditer(buff):
if match.group(1) in icon_name:
ico_data.append([match.group(1), match.group(2), match.group(3)])

return (ico_data)

def convert_hpp(icon_name,bitmap_array,iconsize_str):
iconsize = iconsize_str.split(", ")
bitmap_size = (int(iconsize[0]),int(iconsize[1]))
bitmap_data=[]

image_data = np.zeros((bitmap_size[1], bitmap_size[0]), dtype=np.uint8)

#print(bitmap_array)
for value in bitmap_array.split(",\n"):
if (value):
#print(int(value, 0))
bitmap_data.append(int(value, 0))

print(f"Count {len(bitmap_data)} Size: {bitmap_size[0]}x{bitmap_size[1]} ({bitmap_size[0]*bitmap_size[1]})")

for y in range(bitmap_size[1]):
for x in range(bitmap_size[0]):
byte_index = (y * bitmap_size[0] + x) // 8
bit_index = x % 8
# bit_index = 7 - (x % 8)
pixel_value = (bitmap_data[byte_index] >> bit_index) & 1
image_data[y, x] = pixel_value * 255

image = Image.fromarray(image_data, 'L')
imagea = image.copy()
imagea.putalpha(image)
imagea.save(icon_name+".png")


if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("hpp", help="Path for bitmap.hpp")
parser.add_argument("--icon", help="Name of the icon from bitmap.hpp, Use 'All' for all icons in file", default = 'titlebar_image')

args = parser.parse_args()

if args.icon:
icon_name = args.icon
else:
icon_name = 'titlebar_image'

print("parse", icon_name)
icons = parse_bitmaphpp(args.hpp, icon_name)

for icon in icons:
convert_hpp(icon[0],icon[1],icon[2])


237 changes: 237 additions & 0 deletions firmware/tools/bitmap_tools/pp_png2hpp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
#!/usr/bin/env python3

# Convert png icons to bitmap.hpp inspired by
# make_bitmap.py - Copyright (C) 2016 Furrtek
# Convert bitmap.hpp to icons inspyred by
# bitmap_arr_reverse_decode.py - Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
# bitmap_arr_reverse_decode.py - Copyleft (ɔ) 2024 zxkmm with the GPL license
# Copysomething (c) 2024 LupusE with the license, needed by the PortaPack project
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; see the file COPYING. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
#

import argparse
import numpy as np
import re
import sys
import os
from PIL import Image

### Convert a directory of icons in png format to one bitmap.hpp file.
######################################################################

def pp_bitmaphpp_header():
return ("""/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
// This file was generated by make_bitmap.py
#ifndef __BITMAP_HPP__
#define __BITMAP_HPP__
#include \"ui.hpp\"
namespace ui {
""")


def convert_png(file):
bmp_data = []
data = 0

rgb_im = Image.open(file).convert('RGBA')

if rgb_im.size[0] % 8 or rgb_im.size[1] % 8:
print((file + ": Size is not a multiple of 8. Image is not included in bitmap.hpp."))
sys.exit(-1)

name = os.path.basename(file).split(".")[0].lower()

bmp_data.append(f"\nstatic constexpr uint8_t bitmap_{name}_data[] = {{\n")

for i in range(rgb_im.size[1]):
for j in range(rgb_im.size[0]):
r, g, b, a = rgb_im.getpixel((j, i))

data >>= 1

if r > 127 and g > 127 and b > 127 and a > 127:
data += 128

if j % 8 == 7:
bmp_data.append(" 0x%0.2X,\n" % data)
data = 0

bmp_data.append(f"""}};
static constexpr Bitmap bitmap_{name}{{
{{{str(rgb_im.size[0])}, {str(rgb_im.size[1])}}},
bitmap_{name}_data}};
""")

out_bmpdata = ''.join(map(str, bmp_data))

return (out_bmpdata)

def pp_bitmaphpp_data(pngicons_path):
count = 0
bitmaphpp_data = []
for file in os.listdir(pngicons_path):
if os.path.isfile(pngicons_path + file) and file.endswith(".png"):
bitmaphpp_data.append(convert_png(pngicons_path + file))
count += 1

return bitmaphpp_data


def pp_bitmaphpp_footer():
return("""
} /* namespace ui */
#endif /*__BITMAP_HPP__*/
""")


def pp_write_bitmaphpp(pngicons_path, hpp_outpath):
bitmaphpp_file = []

bitmaphpp_file.append(pp_bitmaphpp_header())
bitmaphpp_file.append("".join(str(x) for x in pp_bitmaphpp_data(pngicons_path)))
bitmaphpp_file.append(pp_bitmaphpp_footer())

out_file = "".join(str(x) for x in bitmaphpp_file)

with open(hpp_outpath, "w", encoding="utf-8") as fd:
fd.writelines(out_file)

print("Find your bitmap.hpp at", out_file)


### Convert from a bitmap.hpp file one or all icons in png.
###########################################################

def parse_bitmaphpp(bitmaphpp_file,icon_name):
ico_pattern = re.compile(r"static constexpr uint8_t bitmap_(.*)_data\[\] = {\n((?:\s+(?:.*)\n)+)};\nstatic constexpr Bitmap bitmap_.*\{\n\s+\{(.*)\},", re.MULTILINE)
ico_data = []

# read file to buffer, to find multiline regex
readfile = open(bitmaphpp_file,'r')
buff = readfile.read()
readfile.close()

if icon_name == 'all':
for match in ico_pattern.finditer(buff):
ico_data.append([match.group(1), match.group(2), match.group(3)])
else:
for match in ico_pattern.finditer(buff):
if match.group(1) in icon_name:
ico_data.append([match.group(1), match.group(2), match.group(3)])

return (ico_data)


def convert_hpp(icon_name,bitmap_array,iconsize_str,png_outdir):
iconsize = iconsize_str.split(", ")
bitmap_size = (int(iconsize[0]),int(iconsize[1]))
bitmap_data=[]

image_data = np.zeros((bitmap_size[1], bitmap_size[0]), dtype=np.uint8)

for value in bitmap_array.split(",\n"):
if (value):
bitmap_data.append(int(value, 0))

for y in range(bitmap_size[1]):
for x in range(bitmap_size[0]):
byte_index = (y * bitmap_size[0] + x) // 8
bit_index = x % 8
# bit_index = 7 - (x % 8)
pixel_value = (bitmap_data[byte_index] >> bit_index) & 1
image_data[y, x] = pixel_value * 255

image = Image.fromarray(image_data, 'L')
icon_out = os.path.join(png_outdir,icon_name+".png")
image.save(icon_out)


### Processing
##############

if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("hpp", help="Path of the bitmap.hpp")
parser.add_argument("graphics", help="Path of <icon>.png files to convert", default=os.path.join(os.getcwd(),"graphics"))
parser.add_argument("--icon","-i", help="Name of the icon to convert in reverse. Use 'all' to convert all. More names can be comma seperated.")
parser.add_argument("--reverse","-r", help="Convert icon from bitmap.hpp to <name>.png", action="store_true")

args = parser.parse_args()

if args.reverse:
print("Reverse: Converting from hpp to png")

# filter hpp arg is a file
hpp_file_path = args.hpp
if not os.path.isfile(hpp_file_path):
print(f"Error: {hpp_file_path} is not a valid file.")
sys.exit(1)

# filter graph arg is a path
if args.graphics:
graphics_path = os.path.join(args.graphics, '')
else:
graphics_path = os.path.join(os.getcwd(),"graphics", '')
if not os.path.exists(graphics_path):
os.makedirs(graphics_path) # create if not exist

# define icons to convert
if args.icon:
icon_name = args.icon
else:
icon_name = 'titlebar_image'

icons = parse_bitmaphpp(hpp_file_path, icon_name)

for icon in icons:
print("Converting icon", icon[0])
convert_hpp(icon[0], icon[1], icon[2], graphics_path)
sys.exit()
else:
print("Converting from png to hpp")
if args.graphics:
graphics_path = os.path.join(args.graphics, '')
else:
graphics_path = os.path.join(os.getcwd(),"graphics", '')
print("Path", graphics_path, "hpp", args.hpp)
pp_write_bitmaphpp(graphics_path, args.hpp)
sys.exit()

0 comments on commit a4c2e15

Please sign in to comment.