diff --git a/firmware/tools/bitmap_tools/README.md b/firmware/tools/bitmap_tools/README.md index 2ce9e05e..d994cb6e 100644 --- a/firmware/tools/bitmap_tools/README.md +++ b/firmware/tools/bitmap_tools/README.md @@ -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. \ No newline at end of file diff --git a/firmware/tools/bitmap_tools/pp_bitmap_parser.py b/firmware/tools/bitmap_tools/pp_bitmap_parser.py new file mode 100755 index 00000000..453f6d5b --- /dev/null +++ b/firmware/tools/bitmap_tools/pp_bitmap_parser.py @@ -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]) + + diff --git a/firmware/tools/bitmap_tools/pp_png2hpp.py b/firmware/tools/bitmap_tools/pp_png2hpp.py new file mode 100755 index 00000000..729c659e --- /dev/null +++ b/firmware/tools/bitmap_tools/pp_png2hpp.py @@ -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 .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 .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()