From c9740206d4356871fb2aa98f631f6fe0ac21448e Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Mon, 24 Aug 2026 13:25:03 +0800 Subject: [PATCH] feat: add go icon support --- tool/src/aurora_iconset_builder/__init__.py | 4 +- .../assemblicon/artio.py | 11 ++++- .../assemblicon/render.py | 24 ++++++---- .../ext_icons/__init__.py | 6 +++ .../{ => ext_icons}/image_icons.py | 24 +++++----- .../std_icons/action_icons.py | 46 ++++++++++++------- 6 files changed, 75 insertions(+), 40 deletions(-) create mode 100644 tool/src/aurora_iconset_builder/ext_icons/__init__.py rename tool/src/aurora_iconset_builder/{ => ext_icons}/image_icons.py (78%) diff --git a/tool/src/aurora_iconset_builder/__init__.py b/tool/src/aurora_iconset_builder/__init__.py index ca7ce5e..d0a8862 100644 --- a/tool/src/aurora_iconset_builder/__init__.py +++ b/tool/src/aurora_iconset_builder/__init__.py @@ -2,8 +2,8 @@ import logging from pathlib import Path from .assemblicon.context import Context from .assemblicon.utils import setup_logging -from .image_icons import build_image_icons from .std_icons import build_standard_icons +from .ext_icons import build_ext_icons def main() -> None: @@ -22,4 +22,4 @@ def main() -> None: def _build_icons(ctx: Context) -> None: build_standard_icons(ctx) - #build_image_icons(ctx) + #build_ext_icons(ctx) diff --git a/tool/src/aurora_iconset_builder/assemblicon/artio.py b/tool/src/aurora_iconset_builder/assemblicon/artio.py index 03caa65..63b433c 100644 --- a/tool/src/aurora_iconset_builder/assemblicon/artio.py +++ b/tool/src/aurora_iconset_builder/assemblicon/artio.py @@ -1,10 +1,19 @@ +import logging from pathlib import Path import PIL.Image +import PIL.ImageFile -def load_artifact(p: Path) -> PIL.Image.ImageFile.ImageFile: +def load_asset(p: Path) -> PIL.ImageFile.ImageFile: + logging.info("Loading asset: %s", p) + return PIL.Image.open(p, "r", ["PNG", "JPEG", "BMP"]) + + +def load_artifact(p: Path) -> PIL.ImageFile.ImageFile: + logging.info("Loading artifact: %s", p) return PIL.Image.open(p, "r", ["PNG"]) def save_artifact(art: PIL.Image.Image, p: Path) -> None: + logging.info("Saving artifact: %s", p) art.save(p, format="PNG") diff --git a/tool/src/aurora_iconset_builder/assemblicon/render.py b/tool/src/aurora_iconset_builder/assemblicon/render.py index afb4619..2b94927 100644 --- a/tool/src/aurora_iconset_builder/assemblicon/render.py +++ b/tool/src/aurora_iconset_builder/assemblicon/render.py @@ -3,11 +3,11 @@ import os import subprocess from pathlib import Path from dataclasses import dataclass - import PIL.Image - +import PIL.ImageFile from .context import Context from .utils import Artifact +from .artio import load_artifact @dataclass @@ -25,8 +25,12 @@ class InkscapeConfig: f"The image height in Inkscape config must be a positive integer" ) +# TODO: Add "resvg" alternative for Inkscape +# because Inkscape is too slow. -def inkscape_render(src: Path, dst: Path, cfg: InkscapeConfig) -> None: +def inkscape_render( + src: Path, dst: Path, cfg: InkscapeConfig +) -> PIL.ImageFile.ImageFile: logging.info("Rendering Inkscape artifact: %s -> %s", src, dst) inkscape_bin = os.getenv("ASSEMBLICON_INKSCAPE", "inkscape") @@ -55,6 +59,8 @@ def inkscape_render(src: Path, dst: Path, cfg: InkscapeConfig) -> None: f"Fail to execute Inkscape. Return code is {proc.returncode}" ) + return load_artifact(dst) + @dataclass class BlenderConfig: @@ -96,9 +102,11 @@ except BaseException: """ -def blender_render(ctx: Context, src: Path, dst: Path, cfg: BlenderConfig) -> None: +def blender_render( + ctx: Context, src: Path, dst: Path, cfg: BlenderConfig +) -> PIL.ImageFile.ImageFile: logging.info("Rendering Blender artifact: %s -> %s", src, dst) - + script = _BLENDER_RENDER_SCRIPT.format( width=cfg.width, height=cfg.height, dst=repr(str(dst)) ) @@ -115,14 +123,14 @@ def blender_render(ctx: Context, src: Path, dst: Path, cfg: BlenderConfig) -> No ] proc = subprocess.run(cmd, capture_output=False) if proc.returncode != 0: - raise RuntimeError( - f"Fail to execute Blender. Return code is {proc.returncode}" - ) + raise RuntimeError(f"Fail to execute Blender. Return code is {proc.returncode}") if not dst.exists(): raise RuntimeError( f"Blender exited successfully but output file {dst} is missing" ) + return load_artifact(dst) + _ICON_ART_SIZE = 1024 diff --git a/tool/src/aurora_iconset_builder/ext_icons/__init__.py b/tool/src/aurora_iconset_builder/ext_icons/__init__.py new file mode 100644 index 0000000..e2fcef8 --- /dev/null +++ b/tool/src/aurora_iconset_builder/ext_icons/__init__.py @@ -0,0 +1,6 @@ +from ..assemblicon.context import Context +from .image_icons import build_image_icons + + +def build_ext_icons(ctx: Context) -> None: + build_image_icons(ctx) diff --git a/tool/src/aurora_iconset_builder/image_icons.py b/tool/src/aurora_iconset_builder/ext_icons/image_icons.py similarity index 78% rename from tool/src/aurora_iconset_builder/image_icons.py rename to tool/src/aurora_iconset_builder/ext_icons/image_icons.py index f290e12..2146e66 100644 --- a/tool/src/aurora_iconset_builder/image_icons.py +++ b/tool/src/aurora_iconset_builder/ext_icons/image_icons.py @@ -1,10 +1,11 @@ import PIL.Image import PIL.ImageEnhance -from .assemblicon.render import inkscape_render, ico_render, InkscapeConfig -from .assemblicon.context import Context -from .assemblicon.utils import Artifact -from .assemblicon.geometry import MinMaxRect, PosSizeRect -from .assemblicon.artproc import ( +from ..assemblicon.render import inkscape_render, ico_render, InkscapeConfig +from ..assemblicon.context import Context +from ..assemblicon.utils import Artifact +from ..assemblicon.geometry import MinMaxRect, PosSizeRect +from ..assemblicon.artio import load_asset, save_artifact +from ..assemblicon.artproc import ( aspect_ratio_scale_and_crop, VerticalAnchor, HorizontalAnchor, @@ -16,24 +17,23 @@ IMAGE_INNER_RECT_POSSIZE = IMAGE_INNER_RECT_MINMAX.to_pos_size_rect() def build_image_icons(ctx: Context) -> None: # build image base - inkscape_render( + image_base = inkscape_render( ctx.input_artifact("component", "image-base.svg"), ctx.temporary_artifact("component", "image-base.png"), InkscapeConfig(1024, 1024), ) # load image base - with PIL.Image.open( - ctx.temporary_artifact("component", "image-base.png") - ) as image_base: + with image_base: _build_jpg_icon(ctx, image_base) def _build_jpg_icon(ctx: Context, base: Artifact) -> None: jpg_image = base.copy() - with PIL.Image.open( + jpg_image_inner = load_asset( ctx.input_artifact("ext", "bitmap", "Niinsaare_järv.jpg") - ) as jpg_image_inner: + ) + with jpg_image_inner: # crop the area which is interested by ourselves interest_area = PosSizeRect(2700, 0, 5016, 3888).to_min_max_rect() crop_jpg_image_inner = jpg_image_inner.crop(interest_area.to_pillow_tuple()) @@ -59,7 +59,7 @@ def _build_jpg_icon(ctx: Context, base: Artifact) -> None: bestfit_jpg_image_inner, IMAGE_INNER_RECT_POSSIZE.to_pillow_tuple() ) # build artifacts - jpg_image.save(ctx.temporary_artifact("ext", "jpg-image.png")) + save_artifact(jpg_image, ctx.temporary_artifact("ext", "jpg-image.png")) ico_render( ctx, jpg_image, diff --git a/tool/src/aurora_iconset_builder/std_icons/action_icons.py b/tool/src/aurora_iconset_builder/std_icons/action_icons.py index d151540..b80cf7b 100644 --- a/tool/src/aurora_iconset_builder/std_icons/action_icons.py +++ b/tool/src/aurora_iconset_builder/std_icons/action_icons.py @@ -1,14 +1,9 @@ from pathlib import Path import PIL.Image -from ..assemblicon.render import inkscape_render, ico_render, InkscapeConfig +from ..assemblicon.render import inkscape_render, InkscapeConfig from ..assemblicon.context import Context from ..assemblicon.utils import Artifact -from ..assemblicon.geometry import MinMaxRect, PosSizeRect -from ..assemblicon.artproc import ( - aspect_ratio_scale_and_crop, - VerticalAnchor, - HorizontalAnchor, -) +from ..assemblicon.artio import save_artifact def build_action_icons(ctx: Context) -> None: @@ -17,27 +12,44 @@ def build_action_icons(ctx: Context) -> None: def _build_go_icons(ctx: Context) -> None: # build go-* base - inkscape_render( + go_base = inkscape_render( ctx.input_artifact("component", "go-base.svg"), ctx.temporary_artifact("component", "go-base.png"), InkscapeConfig(1024, 1024), ) # build go-* shining - inkscape_render( + go_shining = inkscape_render( ctx.input_artifact("component", "go-inner-shining.svg"), ctx.temporary_artifact("component", "go-inner-shining.png"), InkscapeConfig(1024, 1024), ) - with PIL.Image.open(ctx.temporary_artifact("component", "go-base.png")) as go_base: - with PIL.Image.open( - ctx.temporary_artifact("component", "go-inner-shining.png") - ) as go_shining: - _build_go_icon(ctx, go_base, go_shining, ctx.input_artifact("go-previous.svg")) - _build_go_icon(ctx, go_base, go_shining, ctx.input_artifact("go-next.svg")) + with go_base, go_shining: + _build_go_icon( + ctx, + go_base, + go_shining, + ctx.input_artifact("go-previous.svg"), + ctx.temporary_artifact("go-previous.png"), + ctx.output_artifact("go-previous.png") + ) + _build_go_icon( + ctx, + go_base, + go_shining, + ctx.input_artifact("go-next.svg"), + ctx.temporary_artifact("go-next.png"), + ctx.output_artifact("go-next.png") + ) -def _build_go_icon(ctx: Context, base: Artifact, shining: Artifact, mask: Path) -> None: +def _build_go_icon( + ctx: Context, base: Artifact, shining: Artifact, mask: Path, mask_dst: Path, dst: Path +) -> None: go = base.copy() - + # render shining with mask + go_mask = inkscape_render(mask, mask_dst, InkscapeConfig(1024, 1024)) + go.paste(shining, (0, 0), go_mask) + # save as image + save_artifact(go, dst)