diff --git a/tool/src/aurora_iconset_builder/__init__.py b/tool/src/aurora_iconset_builder/__init__.py index ff283f9..09aa587 100644 --- a/tool/src/aurora_iconset_builder/__init__.py +++ b/tool/src/aurora_iconset_builder/__init__.py @@ -28,5 +28,5 @@ def main() -> None: def _build_icons(ctx: AdvancedContext) -> None: - build_standard_icons(ctx) build_non_standard_icons(ctx) + #build_standard_icons(ctx) diff --git a/tool/src/aurora_iconset_builder/assemblicon/artio.py b/tool/src/aurora_iconset_builder/assemblicon/artio.py index 8301393..efd0ff3 100644 --- a/tool/src/aurora_iconset_builder/assemblicon/artio.py +++ b/tool/src/aurora_iconset_builder/assemblicon/artio.py @@ -5,6 +5,15 @@ import PIL.Image from .utils import Artifact, ArtifactFile +def new_artifact(size: tuple[int, int]) -> Artifact: + """ + Create new RGBA empty (#ffffff00) artifact with given size. + """ + return PIL.Image.new( + "RGBA", size, "#ffffff00" + ) + + def load_input_artifact(p: Path) -> ArtifactFile: """ Load input artifact used for building temporary and output artifact. diff --git a/tool/src/aurora_iconset_builder/assemblicon/artproc.py b/tool/src/aurora_iconset_builder/assemblicon/artproc.py index 82d01a0..286877c 100644 --- a/tool/src/aurora_iconset_builder/assemblicon/artproc.py +++ b/tool/src/aurora_iconset_builder/assemblicon/artproc.py @@ -15,9 +15,7 @@ class VerticalAnchor(enum.IntEnum): Bottom = enum.auto() -def _anchor_offset( - oversize: int, anchor: HorizontalAnchor | VerticalAnchor -) -> int: +def _anchor_offset(oversize: int, anchor: HorizontalAnchor | VerticalAnchor) -> int: match anchor: case HorizontalAnchor.Left | VerticalAnchor.Top: return 0 @@ -47,12 +45,69 @@ def aspect_ratio_scale_and_crop( scaled_height = math.ceil(art.height * scale) scaled = art.resize((scaled_width, scaled_height), scale_resample) - crop_x = _anchor_offset( - scaled_width - target_width, crop_horizontal_anchor + crop_x = _anchor_offset(scaled_width - target_width, crop_horizontal_anchor) + crop_y = _anchor_offset(scaled_height - target_height, crop_vertical_anchor) + return scaled.crop((crop_x, crop_y, crop_x + target_width, crop_y + target_height)) + + +def anchor_to_pos( + art: Artifact, + horizontal_anchor: HorizontalAnchor, + vertical_anchor: VerticalAnchor, +) -> tuple[int, int]: + """ + Resolve the anchor point position inside given artifact. + + :param art: The artifact whose anchor point should be resolved. + :param horizontal_anchor: The anchor selection at horizontal direction. + :param vertical_anchor: The anchor selection at vertical direction. + :return: The (x, y) coordinate of the anchor point. For Left/Top + anchors this is the corresponding edge; for Right/Bottom anchors + this is just outside the artifact (the exact corner coordinate). + """ + return ( + _anchor_offset(art.width, horizontal_anchor), + _anchor_offset(art.height, vertical_anchor), ) - crop_y = _anchor_offset( - scaled_height - target_height, crop_vertical_anchor + + +def align_paste( + art: Artifact, + clipboard: Artifact, + art_pos: tuple[int, int], + clipboard_pos: tuple[int, int], +) -> None: + """ + Paste clipboard onto given artifact with two anchor points aligned. + + The clipboard is pasted in the way that the point indicated by + clipboard_pos on the clipboard exactly overlaps the point indicated + by art_pos on the artifact. The art is modified in place. Positions + resolving to negative paste origins are allowed and clipped by PIL + just like a normal paste call. + + :param art: The artifact pasted onto. Modified in place. + :param clipboard: The artifact pasted as the clipboard. + :param art_pos: The (x, y) point on the art to be aligned, usually + resolved by anchor_to_pos. + :param clipboard_pos: The (x, y) point on the clipboard to be + aligned, usually resolved by anchor_to_pos. + """ + art.paste( + clipboard, + (art_pos[0] - clipboard_pos[0], art_pos[1] - clipboard_pos[1]), ) - return scaled.crop( - (crop_x, crop_y, crop_x + target_width, crop_y + target_height) + + +def align_alpha_composite( + art: Artifact, + clipboard: Artifact, + art_pos: tuple[int, int], + clipboard_pos: tuple[int, int], +) -> None: + """ + Alpha-supported ``align_paste``. + """ + art.alpha_composite( + clipboard, (art_pos[0] - clipboard_pos[0], art_pos[1] - clipboard_pos[1]) ) diff --git a/tool/src/aurora_iconset_builder/assemblicon/context.py b/tool/src/aurora_iconset_builder/assemblicon/context.py index ef4fd87..19e816b 100644 --- a/tool/src/aurora_iconset_builder/assemblicon/context.py +++ b/tool/src/aurora_iconset_builder/assemblicon/context.py @@ -102,7 +102,7 @@ _DEFAULT_BLD_CONFIG = artrender.BlenderConfig(VANILLA_ARTIFACT_HW, VANILLA_ARTIF # TODO: For easy testing, we only build 32x32 and 48x48. # Enable full generation after testing. -_FD_THUMBNAIL_HWS: tuple[int, ...] = (32, 48) +_FD_THUMBNAIL_HWS: tuple[int, ...] = (32, 48, 1024) # _FD_THUMBNAIL_HWS: tuple[int, ...] = (16, 32, 48, 64, 128, 256) @@ -148,6 +148,16 @@ class AdvancedContext: ) -> None: self.__context.__exit__(exc_type, exc_value, traceback) + # region: New Artifact + + def new_artifact(self) -> Artifact: + """ + Create new RGBA vanilla size (1024x1024) empty (#ffffff00) artifact. + """ + return artio.new_artifact((VANILLA_ARTIFACT_HW, VANILLA_ARTIFACT_HW)) + + # endregion + # region: Input Artifact def input_artifact(self, *args: str) -> Path: diff --git a/tool/src/aurora_iconset_builder/nostd_icons/__init__.py b/tool/src/aurora_iconset_builder/nostd_icons/__init__.py index aae6e85..68ea84f 100644 --- a/tool/src/aurora_iconset_builder/nostd_icons/__init__.py +++ b/tool/src/aurora_iconset_builder/nostd_icons/__init__.py @@ -1,6 +1,8 @@ from ..assemblicon.context import AdvancedContext from .mimetype_icons import build_mimetype_icons +from .application_icons import build_application_icons def build_non_standard_icons(ctx: AdvancedContext) -> None: build_mimetype_icons(ctx) + build_application_icons(ctx) diff --git a/tool/src/aurora_iconset_builder/nostd_icons/application_icons.py b/tool/src/aurora_iconset_builder/nostd_icons/application_icons.py new file mode 100644 index 0000000..6f05c57 --- /dev/null +++ b/tool/src/aurora_iconset_builder/nostd_icons/application_icons.py @@ -0,0 +1,35 @@ +import PIL.Image +from ..assemblicon.context import AdvancedContext, FdContext, WinCategory, MacCategory +from ..assemblicon.artproc import ( + anchor_to_pos, + align_paste, + VerticalAnchor, + HorizontalAnchor, +) + + +def build_application_icons(ctx: AdvancedContext) -> None: + _build_sarasacw_picture_icons(ctx) + + +def _build_sarasacw_picture_icons(ctx: AdvancedContext) -> None: + app = ctx.new_artifact() + frame = ctx.render_svg_then_load( + ctx.input_artifact("app", "sarasacw-picture", "app-frame.svg") + ) + + inner = ctx.load_temporary_artifact("image-jpeg.png") + resized_inner = inner.resize((990, 990), PIL.Image.Resampling.LANCZOS) + + align_paste( + app, + resized_inner, + anchor_to_pos(app, HorizontalAnchor.Center, VerticalAnchor.Center), + anchor_to_pos(resized_inner, HorizontalAnchor.Center, VerticalAnchor.Center), + ) + app.alpha_composite(frame, (0, 0)) + + # save artifact + ctx.save_fd_artifact(app, FdContext.Applications, "org.sarasachipworkshop.picture") + ctx.save_win_artifact(app, WinCategory.Application, "sarasacw-picture") + ctx.save_mac_artifact(app, MacCategory.Application, "sarasacw-picture") diff --git a/tool/src/aurora_iconset_builder/nostd_icons/mimetype_icons.py b/tool/src/aurora_iconset_builder/nostd_icons/mimetype_icons.py index a50811d..76749e9 100644 --- a/tool/src/aurora_iconset_builder/nostd_icons/mimetype_icons.py +++ b/tool/src/aurora_iconset_builder/nostd_icons/mimetype_icons.py @@ -62,6 +62,8 @@ def _build_jpg_icon(ctx: AdvancedContext, base: Artifact) -> None: ) # build artifacts + # save as temporary for future use + ctx.save_temporary_artifact(jpg_image, "image-jpeg.png") # save for FD ctx.save_fd_artifact(jpg_image, FdContext.MimeTypes, "image-jpeg") # use image/jpeg as the default image icon 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 5e65beb..fbeb4cd 100644 --- a/tool/src/aurora_iconset_builder/std_icons/action_icons.py +++ b/tool/src/aurora_iconset_builder/std_icons/action_icons.py @@ -36,13 +36,17 @@ def _build_go_icons(ctx: AdvancedContext) -> None: def _build_object_flip_icons(ctx: AdvancedContext) -> None: art = ctx.render_svg_then_load(ctx.input_artifact("object-flip-horizontal.svg")) - ctx.save_fd_artifact(art, FdContext.Actions, "object-flip-horizontal") + with art: + ctx.save_fd_artifact(art, FdContext.Actions, "object-flip-horizontal") art = ctx.render_svg_then_load(ctx.input_artifact("object-flip-vertical.svg")) - ctx.save_fd_artifact(art, FdContext.Actions, "object-flip-vertical") + with art: + ctx.save_fd_artifact(art, FdContext.Actions, "object-flip-vertical") def _build_object_rotate_icons(ctx: AdvancedContext) -> None: art = ctx.render_svg_then_load(ctx.input_artifact("object-rotate-left.svg")) - ctx.save_fd_artifact(art, FdContext.Actions, "object-rotate-left") + with art: + ctx.save_fd_artifact(art, FdContext.Actions, "object-rotate-left") art = ctx.render_svg_then_load(ctx.input_artifact("object-rotate-right.svg")) - ctx.save_fd_artifact(art, FdContext.Actions, "object-rotate-right") + with art: + ctx.save_fd_artifact(art, FdContext.Actions, "object-rotate-right")