unleashed-firmware/scripts/imglint.py
Sean Skyhawk 41fcead710
Images linting: ensure that all images conform specification (#3802)
* Change all icons to be white background
* assets: re-processed all *.png images to 1-bit
* assets: also stripped profile data from .pngs
* assets: also stripped datetime from metadata (`-define png:exclude-chunks=date,time`)
* scripts: added image linter and formatter; fbt: added `lint_img` && `format_img` targets; github: integrated image lint step into CI
* scripts: imglint: fixed deprecation warning
* images: applied `format_img`
* fbt: added `lint_all` and `format_all` targets; docs: updated for new targets

Co-authored-by: hedger <hedger@nanode.su>
Co-authored-by: hedger <hedger@users.noreply.github.com>
2024-08-07 11:57:32 +09:00

97 lines
3 KiB
Python

import logging
import multiprocessing
import os
from pathlib import Path
from flipper.app import App
from PIL import Image, ImageOps
_logger = logging.getLogger(__name__)
def _check_image(image, do_fixup=False):
failed_checks = []
with Image.open(image) as img:
# check that is's pure 1-bit B&W
if img.mode != "1":
failed_checks.append(f"not 1-bit B&W, but {img.mode}")
if do_fixup:
img = img.convert("1")
# ...and does not have any metadata or ICC profile
if img.info:
failed_checks.append(f"has metadata")
if do_fixup:
img.info = {}
if do_fixup:
img.save(image)
_logger.info(f"Fixed image {image}")
if failed_checks:
_logger.warning(f"Image {image} issues: {'; '.join(failed_checks)}")
return len(failed_checks) == 0
class ImageLint(App):
ICONS_SUPPORTED_FORMATS = [".png"]
def init(self):
self.subparsers = self.parser.add_subparsers(help="sub-command help")
self.parser_check = self.subparsers.add_parser(
"check", help="Check image format and file names"
)
self.parser_check.add_argument("input", nargs="+")
self.parser_check.set_defaults(func=self.check)
self.parser_format = self.subparsers.add_parser(
"format", help="Format image and fix file names"
)
self.parser_format.add_argument(
"input",
nargs="+",
)
self.parser_format.set_defaults(func=self.format)
def _gather_images(self, folders):
images = []
for folder in folders:
for dirpath, _, filenames in os.walk(folder):
for filename in filenames:
if self.is_file_an_icon(filename):
images.append(os.path.join(dirpath, filename))
return images
def is_file_an_icon(self, filename):
extension = Path(filename).suffix.lower()
return extension in self.ICONS_SUPPORTED_FORMATS
def _process_images(self, images, do_fixup):
with multiprocessing.Pool() as pool:
image_checks = pool.starmap(
_check_image, [(image, do_fixup) for image in images]
)
return all(image_checks)
def check(self):
images = self._gather_images(self.args.input)
self.logger.info(f"Found {len(images)} images")
if not self._process_images(images, False):
self.logger.error("Some images are not in the correct format")
return 1
self.logger.info("All images are in the correct format")
return 0
def format(self):
images = self._gather_images(self.args.input)
self.logger.info(f"Found {len(images)} images")
if not self._process_images(images, True):
self.logger.warning("Applied fixes to some images")
else:
self.logger.info("All images were in the correct format")
return 0
if __name__ == "__main__":
ImageLint()()