Compare commits

...
38 Commits
Author SHA1 Message Date
yyc12345 6a951c6d11 fix: fix sarasacw-picture name error 2026-09-22 16:38:01 +08:00
yyc12345 890019bc54 feat: adapt to new prologue with custom FreeDesktop thumbnail sizes 2026-09-10 09:28:53 +08:00
yyc12345 0d43fe3962 chore: use REUSE instead of non-standard license declaration 2026-09-09 22:02:14 +08:00
yyc12345 a33c3538f5 refactor: peel assemblicon off as indenpendent project
peel assemblicon off as indenpendent project
in SarasasChipWorkshop/assemblicon
2026-09-09 20:40:21 +08:00
yyc12345 72edad6edc doc: update README and LICENSE 2026-09-09 19:56:09 +08:00
yyc12345 2015ebf925 feat: cleanup bitmaps in references 2026-09-09 14:08:57 +08:00
yyc12345 1e15fb4783 feat: support monitor zoom fit best 2026-09-09 14:02:58 +08:00
yyc12345 42a2b08abf feat: support image-png/bmp/gif 2026-09-09 12:09:33 +08:00
yyc12345 61e003c741 fix: fix fatal source link issue 2026-09-08 22:29:50 +08:00
yyc12345 7b216b8865 doc: update TODO 2026-09-08 22:15:13 +08:00
yyc12345 36adf8029a feat: change src layout and write license infos 2026-09-08 22:13:59 +08:00
yyc12345 e0d6aa63e6 feat: add magnifying glass, zoom in, out, notepad icons 2026-09-08 14:31:15 +08:00
yyc12345 765375f04c feat: add plus/minus icons 2026-09-07 21:02:14 +08:00
yyc12345 a563917aa8 feat: split rotation and flip icon build functions 2026-09-07 20:52:24 +08:00
yyc12345 bb5032d502 feat: refactor (3/3) finish user side adaption 2026-09-06 14:38:04 +08:00
yyc12345 db04b9c809 fix: fix refactor issues again 2026-09-06 14:30:00 +08:00
yyc12345 44ba5fbbee fix: fix refactor issues 2026-09-06 13:59:32 +08:00
yyc12345 f650d3f98a feat: refactor assemblicon (2/3) context job topology 2026-09-06 13:35:47 +08:00
yyc12345 43e3bd39ef feat: refactor assemblicon (1/3) sink and source establishment 2026-09-05 22:56:14 +08:00
yyc12345 49edad7d33 feat: add todo 2026-09-03 19:33:07 +08:00
yyc12345 28fa01992e feat: bring source and sink back for future development 2026-09-03 19:13:35 +08:00
yyc12345 d13f18cc90 feat: finish image-zoom-original 2026-09-03 14:59:06 +08:00
yyc12345 da52ab5028 refactor: rename some job names 2026-09-02 17:08:35 +08:00
yyc12345 988a9fc04f refactor: fix aurora icon build error 2026-09-02 14:07:57 +08:00
yyc12345 63ba6d44f7 refactor: basically finish core refactor 2026-09-02 13:36:41 +08:00
yyc12345 bcac59ceb1 refactor: refactor assemblicon 2026-09-01 22:32:00 +08:00
yyc12345 54af04a48f feat: add zoom-original 2026-09-01 17:09:20 +08:00
yyc12345 e43029ea47 feat: add dialog icons and image properties 2026-09-01 15:27:11 +08:00
yyc12345 3a9a6d99cb feat: add app icons 2026-08-28 20:20:55 +08:00
yyc12345 892a946998 feat: add license info for all assets 2026-08-27 15:09:50 +08:00
yyc12345 9d20e60adb feat: add some action icons 2026-08-27 14:57:09 +08:00
yyc12345 80738d1254 feat: pre-create all essential directories in advanced context 2026-08-27 13:04:12 +08:00
yyc12345 c54ae438c1 feat: add cli for svg render 2026-08-27 13:03:48 +08:00
yyc12345 f33523259a fix: fix build issue 2026-08-26 20:02:59 +08:00
yyc12345 96e0eab9d2 feat: add dup feature 2026-08-26 19:48:56 +08:00
yyc12345 1d48f54c40 doc: add some docstring for assemblicon context 2026-08-26 16:45:13 +08:00
yyc12345 55a922aafe refactor: refactor assemblicon context 2026-08-26 16:39:10 +08:00
yyc12345 c9740206d4 feat: add go icon support 2026-08-24 13:25:03 +08:00
34 changed files with 1292 additions and 488 deletions
+14
View File
@@ -0,0 +1,14 @@
# License
This project has mixed licencing.
You are free to copy, redistribute and/or modify aspects of this work under the terms of each licence accordingly.
## Icon Set Builder
The builder of this icon set is under **MIT License**.
The content of these part is under `tool` directory.
## Icon Set Assets
The assets for generating this icon set is under different licenses.
This project follows the [REUSE Specification](https://reuse.software/). You can collect the licensing information using the REUSE tool, or browse the `REUSE.toml` files located in their directory manually.
+9 -1
View File
@@ -5,4 +5,12 @@ Yet Another Aero Icon Set.
## Repository Notes ## Repository Notes
The content submitted in Git repository doesn't include any binary assets. The content submitted in Git repository doesn't include any binary assets.
It only includes code part of this icon set for tracing the change of build code. It only includes the code part of this repository for tracing the changes of code building this icon set.
## Status
Work in progress...
## License
See [LICENSES](LICENSE.md) for more details.
-15
View File
@@ -1,15 +0,0 @@
Ivar Leidus, CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0>, via Wikimedia Commons
https://commons.wikimedia.org/wiki/File:Niinsaare_j%C3%A4rv.jpg
Pascalou petit, CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0>, via Wikimedia Commons
https://commons.wikimedia.org/wiki/File:Aster_Tataricus.JPG
https://commons.wikimedia.org/wiki/File:Great_pershing_balloon_derby_2005_09_04.jpg
Balloon launch at the Great Pershing Balloon Derby near Brookfield, Missouri on September 4, 2005. Photo taken by Joe DeShon.
This file is licensed under the Creative Commons Attribution 2.5 Generic license.
https://commons.wikimedia.org/wiki/File:Wild_Petunia_Blue_Flower_1.JPG
I, Jonathan Zander, CC BY-SA 3.0 <http://creativecommons.org/licenses/by-sa/3.0/>, via Wikimedia Commons
https://commons.wikimedia.org/wiki/File:Daisy_(Argyranthemum_frutescens).jpg
Fir0002, CC BY-SA 3.0 <http://creativecommons.org/licenses/by-sa/3.0/>, via Wikimedia Commons
+8
View File
@@ -0,0 +1,8 @@
version = 1
[[annotations]]
path = "Nycticorax_nycticorax12.jpg"
precedence = "override"
SPDX-FileComment = "Source: https://commons.wikimedia.org/wiki/File:Nycticorax_nycticorax12.jpg"
SPDX-FileCopyrightText = "Calibas"
SPDX-License-Identifier = "CC-BY-SA-3.0"
+75
View File
@@ -0,0 +1,75 @@
version = 1
[[annotations]]
path = "go-base.svg"
precedence = "override"
SPDX-FileCopyrightText = "2026 yyc12345"
SPDX-License-Identifier = "CC-BY-SA-4.0"
[[annotations]]
path = "go-inner-shining.svg"
precedence = "override"
SPDX-FileCopyrightText = "2026 yyc12345"
SPDX-License-Identifier = "CC-BY-SA-4.0"
[[annotations]]
path = "go-inner.svg"
precedence = "override"
SPDX-FileComment = "Source: https://github.com/google/material-design-icon"
SPDX-FileCopyrightText = ["2014-2026 Google", "2026 yyc12345"]
SPDX-License-Identifier = "Apache-2.0"
[[annotations]]
path = "object-flip-horizontal.svg"
precedence = "override"
SPDX-FileComment = "Source: https://invent.kde.org/frameworks/oxygen-icons"
SPDX-FileCopyrightText = ["2015-2026 KDE Oxygen Icon Theme Team", "2026 yyc12345"]
SPDX-License-Identifier = "LGPL-2.1-only"
[[annotations]]
path = "object-flip-vertical.svg"
precedence = "override"
SPDX-FileComment = "Source: https://invent.kde.org/frameworks/oxygen-icons"
SPDX-FileCopyrightText = ["2015-2026 KDE Oxygen Icon Theme Team", "2026 yyc12345"]
SPDX-License-Identifier = "LGPL-2.1-only"
[[annotations]]
path = "object-rotate-left.svg"
precedence = "override"
SPDX-FileComment = "Source: https://invent.kde.org/frameworks/oxygen-icons"
SPDX-FileCopyrightText = ["2015-2026 KDE Oxygen Icon Theme Team", "2026 yyc12345"]
SPDX-License-Identifier = "LGPL-2.1-only"
[[annotations]]
path = "object-rotate-right.svg"
precedence = "override"
SPDX-FileComment = "Source: https://invent.kde.org/frameworks/oxygen-icons"
SPDX-FileCopyrightText = ["2015-2026 KDE Oxygen Icon Theme Team", "2026 yyc12345"]
SPDX-License-Identifier = "LGPL-2.1-only"
[[annotations]]
path = "image-png-checkerboard.svg"
precedence = "override"
SPDX-FileCopyrightText = "2026 yyc12345"
SPDX-License-Identifier = "CC-BY-SA-4.0"
[[annotations]]
path = "dialog-infomation.svg"
precedence = "override"
SPDX-FileComment = "Source: https://invent.kde.org/frameworks/oxygen-icons"
SPDX-FileCopyrightText = ["2015-2026 KDE Oxygen Icon Theme Team", "2026 yyc12345"]
SPDX-License-Identifier = "LGPL-2.1-only"
[[annotations]]
path = "dialog-warning.svg"
precedence = "override"
SPDX-FileComment = "Source: https://invent.kde.org/frameworks/oxygen-icons"
SPDX-FileCopyrightText = ["2015-2026 KDE Oxygen Icon Theme Team", "2026 yyc12345"]
SPDX-License-Identifier = "LGPL-2.1-only"
[[annotations]]
path = "dialog-error.svg"
precedence = "override"
SPDX-FileComment = "Source: https://invent.kde.org/frameworks/oxygen-icons"
SPDX-FileCopyrightText = "2015-2026 KDE Oxygen Icon Theme Team"
SPDX-License-Identifier = "LGPL-2.1-only"
+13
View File
@@ -0,0 +1,13 @@
version = 1
[[annotations]]
path = "sarasacw-picture-frame.svg"
precedence = "override"
SPDX-FileCopyrightText = "2026 yyc12345"
SPDX-License-Identifier = "CC-BY-SA-4.0"
[[annotations]]
path = "notepad.blend"
precedence = "override"
SPDX-FileCopyrightText = "2026 yyc12345"
SPDX-License-Identifier = "CC-BY-SA-4.0"
+29
View File
@@ -0,0 +1,29 @@
version = 1
[[annotations]]
path = "Niinsaare_järv.jpg"
precedence = "override"
SPDX-FileComment = "Source: https://commons.wikimedia.org/wiki/File:Niinsaare_j%C3%A4rv.jpg"
SPDX-FileCopyrightText = "2013 Ivar Leidus"
SPDX-License-Identifier = "CC-BY-SA-3.0"
[[annotations]]
path = "Tulipa_suaveolens_floriade_to_Canberra.jpg"
precedence = "override"
SPDX-FileComment = "Source: https://commons.wikimedia.org/wiki/File:Tulipa_suaveolens_floriade_to_Canberra.jpg"
SPDX-FileCopyrightText = "2005 John O'Neill"
SPDX-License-Identifier = "CC-BY-SA-3.0"
[[annotations]]
path = "Hot-air_balloon_in_Tambov_-_02.jpg"
precedence = "override"
SPDX-FileComment = "Source: https://commons.wikimedia.org/wiki/File:Hot-air_balloon_in_Tambov_-_02.jpg"
SPDX-FileCopyrightText = "2022 Alexander Novikov"
SPDX-License-Identifier = "CC-BY-SA-4.0"
[[annotations]]
path = "Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg"
precedence = "override"
SPDX-FileComment = "Source: https://commons.wikimedia.org/wiki/File:Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg"
SPDX-FileCopyrightText = ["1889 Vincent van Gogh", "Google Arts & Culture"]
SPDX-License-Identifier = "CC0-1.0" # Public Domain actually.
Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

+5
View File
@@ -0,0 +1,5 @@
Blue Background Node 1,#001fbe
Blue Background Node 2,#1439ff
Paper Background Node 1,#ffffff
Paper Backgroundr Node 2,#e3e3e3
1 Blue Background Node 1 #001fbe
2 Blue Background Node 2 #1439ff
3 Paper Background Node 1 #ffffff
4 Paper Backgroundr Node 2 #e3e3e3
+40
View File
@@ -0,0 +1,40 @@
version = 1
[[annotations]]
path = "image-base.svg"
precedence = "override"
SPDX-FileCopyrightText = "2026 yyc12345"
SPDX-License-Identifier = "CC-BY-SA-4.0"
[[annotations]]
path = "generic-background.svg"
precedence = "override"
SPDX-FileCopyrightText = "2026 yyc12345"
SPDX-License-Identifier = "CC-BY-SA-4.0"
[[annotations]]
path = "triangle.svg"
precedence = "override"
SPDX-FileComment = "Source: https://github.com/Templarian/MaterialDesign"
SPDX-FileCopyrightText = ["2014-2025 Austin Andrews", "2026 yyc12345"]
SPDX-License-Identifier = "Apache-2.0"
[[annotations]]
path = "magnifying-glass.blend"
precedence = "override"
SPDX-FileCopyrightText = "2026 yyc12345"
SPDX-License-Identifier = "CC-BY-SA-4.0"
[[annotations]]
path = "minus.svg"
precedence = "override"
SPDX-FileComment = "Source: https://github.com/google/material-design-icon"
SPDX-FileCopyrightText = ["2014-2026 Google", "2026 yyc12345"]
SPDX-License-Identifier = "Apache-2.0"
[[annotations]]
path = "plus.svg"
precedence = "override"
SPDX-FileComment = "Source: https://github.com/google/material-design-icon"
SPDX-FileCopyrightText = ["2014-2026 Google", "2026 yyc12345"]
SPDX-License-Identifier = "Apache-2.0"
-5
View File
@@ -1,5 +0,0 @@
[[licenses]]
filename = 'Niinsaare_järv.jpg'
license = "CC-BY-SA-3.0"
author = ["Ivar Leidus", "Wikimedia Commons"]
source = 'https://commons.wikimedia.org/wiki/File:Niinsaare_j%C3%A4rv.jpg'
+7
View File
@@ -0,0 +1,7 @@
version = 1
[[annotations]]
path = "**"
precedence = "override"
SPDX-FileCopyrightText = "2026 Sarasa's Chip Workshop"
SPDX-License-Identifier = "MIT"
+4 -1
View File
@@ -8,7 +8,7 @@ authors = [
] ]
requires-python = ">=3.13" requires-python = ">=3.13"
dependencies = [ dependencies = [
"pillow>=12.3.0", "assemblicon",
] ]
[project.scripts] [project.scripts]
@@ -17,3 +17,6 @@ aurora-iconset-builder = "aurora_iconset_builder:main"
[build-system] [build-system]
requires = ["uv_build>=0.8.0,<0.9"] requires = ["uv_build>=0.8.0,<0.9"]
build-backend = "uv_build" build-backend = "uv_build"
[tool.uv.sources]
assemblicon = { git = "https://github.com/SarasasChipWorkshop/assemblicon" }
+31 -12
View File
@@ -1,25 +1,44 @@
import logging
from pathlib import Path from pathlib import Path
from .assemblicon.context import Context from assemblicon.highlevel.utils import DEFAULT_FD_THUMBNAIL_HWS
from .assemblicon.utils import setup_logging from assemblicon.highlevel.environment import Environment
from .image_icons import build_image_icons from assemblicon.highlevel.context import Context
from .std_icons import build_standard_icons from assemblicon.logger import APP_LOGGER as LOGGER
from .cli import parse
from .types import AuroraPrologue, AuroraContext
from . import std_icons, nostd_icons
def main() -> None: def main() -> None:
setup_logging() opts = parse()
def tempdir_initializer(p: Path) -> None:
# create our special temporary subdirectory
comp = p / "component"
comp.mkdir(parents=True, exist_ok=True)
repository_root = Path(__file__).resolve().parent.parent.parent.parent repository_root = Path(__file__).resolve().parent.parent.parent.parent
iconset_input = repository_root / "src" iconset_input = repository_root / "src"
iconset_output = repository_root / "artifact" iconset_output = repository_root / "artifact"
ctx = Context(iconset_input, iconset_output) prologue = AuroraPrologue(
iconset_input,
iconset_output,
opts.svg_render,
# TODO: For easy testing, we only build 32x32 and 48x48.
# Enable full generation after testing.
# DEFAULT_FD_THUMBNAIL_HWS,
(32, 48, 256),
tempdir_initializer,
)
env = Environment(prologue)
ctx = Context(env)
try: try:
_build_icons(ctx) register(ctx)
ctx.run(opts.selection)
except BaseException as e: except BaseException as e:
logging.fatal("Runtime error: %s", e) LOGGER.fatal("Runtime error: %s", e)
def _build_icons(ctx: Context) -> None: def register(ctx: AuroraContext) -> None:
build_standard_icons(ctx) std_icons.register(ctx)
#build_image_icons(ctx) nostd_icons.register(ctx)
@@ -1,10 +0,0 @@
from pathlib import Path
import PIL.Image
def load_artifact(p: Path) -> PIL.Image.ImageFile.ImageFile:
return PIL.Image.open(p, "r", ["PNG"])
def save_artifact(art: PIL.Image.Image, p: Path) -> None:
art.save(p, format="PNG")
@@ -1,58 +0,0 @@
import enum
import math
from .utils import Artifact
class HorizontalAnchor(enum.IntEnum):
Left = enum.auto()
Center = enum.auto()
Right = enum.auto()
class VerticalAnchor(enum.IntEnum):
Top = enum.auto()
Center = enum.auto()
Bottom = enum.auto()
def _anchor_offset(
oversize: int, anchor: HorizontalAnchor | VerticalAnchor
) -> int:
match anchor:
case HorizontalAnchor.Left | VerticalAnchor.Top:
return 0
case HorizontalAnchor.Center | VerticalAnchor.Center:
return oversize // 2
case HorizontalAnchor.Right | VerticalAnchor.Bottom:
return oversize
def aspect_ratio_scale_and_crop(
art: Artifact,
target_width: int,
target_height: int,
scale_resample: int,
crop_horizontal_anchor: HorizontalAnchor,
crop_vertical_anchor: VerticalAnchor,
) -> Artifact:
if target_width <= 0:
raise ValueError("Target width must be a positive integer")
if target_height <= 0:
raise ValueError("Target height must be positive integer")
scale = max(target_width / art.width, target_height / art.height)
# use math.ceil to ensure that resized image at least has required size
# (floating point value error may cause this)
scaled_width = math.ceil(art.width * scale)
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_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)
)
@@ -1,44 +0,0 @@
import tempfile
from pathlib import Path
from types import TracebackType
class Context:
__input_dir: Path
__output_dir: Path
__temp_dir: tempfile.TemporaryDirectory[str]
def __init__(self, input_dir: Path, output_dir: Path) -> None:
self.__input_dir = input_dir
self.__output_dir = output_dir
self.__temp_dir = tempfile.TemporaryDirectory()
def input_artifact(self, *args: str) -> Path:
return self.__input_dir / Path(*args)
def output_artifact(self, *args: str) -> Path:
p = self.__output_dir / Path(*args)
p.parent.mkdir(parents=True, exist_ok=True)
return p
def temporary_artifact(self, *args: str) -> Path:
p = Path(self.__temp_dir.name) / "user" / Path(*args)
p.parent.mkdir(parents=True, exist_ok=True)
return p
def _intern_temporary_artifact(self, *args: str) -> Path:
p = Path(self.__temp_dir.name) / "assemblicon" / Path(*args)
p.parent.mkdir(parents=True, exist_ok=True)
return p
def __enter__(self) -> "Context":
return self
def __exit__(
self,
exc_type: type[BaseException] | None,
exc_value: BaseException | None,
traceback: TracebackType | None,
) -> None:
self.__temp_dir.cleanup()
@@ -1,60 +0,0 @@
from dataclasses import dataclass
@dataclass
class MinMaxRect:
x1: int
y1: int
x2: int
y2: int
def to_pos_size_rect(self) -> "PosSizeRect":
return PosSizeRect(
x=self.x1, y=self.y1, width=self.x2 - self.x1, height=self.y2 - self.y1
)
def to_pillow_tuple(self) -> tuple[int, int, int, int]:
return (self.x1, self.y1, self.x2, self.y2)
@dataclass
class PosSizeRect:
x: int
y: int
width: int
height: int
def to_min_max_rect(self) -> MinMaxRect:
return MinMaxRect(
x1=self.x, y1=self.y, x2=self.x + self.width, y2=self.y + self.height
)
def to_pillow_tuple(self) -> tuple[int, int]:
return (self.x, self.y)
@dataclass
class MarginRect:
top: int
right: int
bottom: int
left: int
def to_min_max_rect(self, width: int, height: int) -> MinMaxRect:
return MinMaxRect(
x1=self.left,
y1=self.top,
x2=width - self.right,
y2=height - self.bottom,
)
@dataclass
class PaddingRect:
top: int
right: int
bottom: int
left: int
def to_min_max_rect(self, x: int, y: int) -> MinMaxRect:
return MinMaxRect(
x1=x - self.left, y1=y - self.top, x2=x + self.right, y2=y + self.bottom
)
@@ -1,168 +0,0 @@
import logging
import os
import subprocess
from pathlib import Path
from dataclasses import dataclass
import PIL.Image
from .context import Context
from .utils import Artifact
@dataclass
class InkscapeConfig:
width: int
height: int
def __post_init__(self) -> None:
if self.width <= 0:
raise ValueError(
f"The image width in Inkscape config must be a positive integer"
)
if self.height <= 0:
raise ValueError(
f"The image height in Inkscape config must be a positive integer"
)
def inkscape_render(src: Path, dst: Path, cfg: InkscapeConfig) -> None:
logging.info("Rendering Inkscape artifact: %s -> %s", src, dst)
inkscape_bin = os.getenv("ASSEMBLICON_INKSCAPE", "inkscape")
cmd = [
inkscape_bin,
"--export-area-page",
"--export-width",
str(cfg.width),
"--export-height",
str(cfg.height),
"--export-type",
"png",
"--export-png-color-mode",
"RGBA_8",
"--export-background-opacity",
"0.0",
"--export-png-use-dithering",
"false",
"--export-filename",
str(dst),
str(src),
]
proc = subprocess.run(cmd, capture_output=False)
if proc.returncode != 0:
raise RuntimeError(
f"Fail to execute Inkscape. Return code is {proc.returncode}"
)
@dataclass
class BlenderConfig:
width: int
height: int
def __post_init__(self) -> None:
if self.width <= 0:
raise ValueError(
f"The image width in Blender config must be a positive integer"
)
if self.height <= 0:
raise ValueError(
f"The image height in Blender config must be a positive integer"
)
_BLENDER_RENDER_SCRIPT = """\
import sys
import traceback
try:
import bpy
render = bpy.context.scene.render
render.resolution_x = {width}
render.resolution_y = {height}
render.resolution_percentage = 100
render.image_settings.file_format = "PNG"
render.image_settings.color_mode = "RGBA"
render.image_settings.color_depth = "8"
render.film_transparent = True
render.use_file_extension = False
render.filepath = {dst}
bpy.ops.render.render(write_still=True)
except BaseException:
traceback.print_exc()
sys.exit(1)
"""
def blender_render(ctx: Context, src: Path, dst: Path, cfg: BlenderConfig) -> None:
logging.info("Rendering Blender artifact: %s -> %s", src, dst)
script = _BLENDER_RENDER_SCRIPT.format(
width=cfg.width, height=cfg.height, dst=repr(str(dst))
)
script_path = ctx._intern_temporary_artifact("blender_render.py")
script_path.write_text(script, encoding="utf-8")
blender_bin = os.getenv("ASSEMBLICON_BLENDER", "blender")
cmd = [
blender_bin,
"--background",
str(src),
"--python",
str(script_path),
]
proc = subprocess.run(cmd, capture_output=False)
if proc.returncode != 0:
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"
)
_ICON_ART_SIZE = 1024
def _ensure_1024_art(art: Artifact) -> None:
if art.size != (_ICON_ART_SIZE, _ICON_ART_SIZE):
raise ValueError(
f"Icon art must be {_ICON_ART_SIZE}x{_ICON_ART_SIZE}, "
f"got {art.width}x{art.height} instead"
)
def _scaled_frame(art: Artifact, size: int) -> Artifact:
_ensure_1024_art(art)
frame = art.copy()
frame.thumbnail((size, size), PIL.Image.Resampling.LANCZOS)
return frame
def ico_render(ctx: Context, art: Artifact, dst: Path) -> None:
logging.info("Rendering .ICO artifact via Pillow: -> %s", dst)
_ensure_1024_art(art)
sizes = [16, 32, 48, 64, 128, 256]
# provide every frame explicitly so that we never depend on
# undocumented plugin-side resizing behavior
frames = [_scaled_frame(art, size) for size in sizes]
art.save(
dst,
format="ICO",
sizes=[(size, size) for size in sizes],
append_images=frames,
)
def icns_render(ctx: Context, art: Artifact, dst: Path) -> None:
logging.info("Rendering .ICNS artifact via Pillow: -> %s", dst)
_ensure_1024_art(art)
# provide every smaller frame explicitly; the 1024x1024 entry is
# covered by the art itself
frames = [_scaled_frame(art, size) for size in (512, 256, 128, 64, 32)]
art.save(dst, format="ICNS", append_images=frames)
@@ -1,7 +0,0 @@
import logging
from PIL.Image import Image
type Artifact = Image
def setup_logging() -> None:
logging.basicConfig(format="[%(levelname)s] %(message)s", level=logging.INFO)
+31
View File
@@ -0,0 +1,31 @@
from dataclasses import dataclass
from typing import Optional
from assemblicon.cli import (
SvgRenderKind,
StdOptsSetup,
create_argparser,
parse_standard_opts,
)
@dataclass(frozen=True)
class AuroraOpts:
selection: Optional[list[str]]
"""The list holding all name of jobs for building."""
svg_render: SvgRenderKind
"""The render kind for SVG file."""
def parse() -> AuroraOpts:
setup = StdOptsSetup(require_input=False, require_output=False)
parser = create_argparser(setup)
parser.prog = "Aurora Icon Set Builder"
parser.description = "The Programmatic Icon Assembly Workflow for Aurora Icon Set."
ns = parser.parse_args()
opts = parse_standard_opts(setup, ns)
return AuroraOpts(
selection=opts.selection, svg_render=opts.svg_render.to_assemblicon_type()
)
@@ -1,67 +0,0 @@
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 (
aspect_ratio_scale_and_crop,
VerticalAnchor,
HorizontalAnchor,
)
IMAGE_INNER_RECT_MINMAX = MinMaxRect(20 * 4, 52 * 4, 236 * 4, 204 * 4)
IMAGE_INNER_RECT_POSSIZE = IMAGE_INNER_RECT_MINMAX.to_pos_size_rect()
def build_image_icons(ctx: Context) -> None:
# build 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:
_build_jpg_icon(ctx, image_base)
def _build_jpg_icon(ctx: Context, base: Artifact) -> None:
jpg_image = base.copy()
with PIL.Image.open(
ctx.input_artifact("ext", "bitmap", "Niinsaare_järv.jpg")
) as 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())
# do a flip
flip_jpg_image_inner = crop_jpg_image_inner.transpose(
PIL.Image.Transpose.FLIP_LEFT_RIGHT
)
# enhance its saturation
enhancer = PIL.ImageEnhance.Color(flip_jpg_image_inner)
saturated_jpg_image_inner = enhancer.enhance(1.4)
# fit to inner rect
bestfit_jpg_image_inner = aspect_ratio_scale_and_crop(
saturated_jpg_image_inner,
IMAGE_INNER_RECT_POSSIZE.width,
IMAGE_INNER_RECT_POSSIZE.height,
PIL.Image.Resampling.NEAREST,
HorizontalAnchor.Center,
VerticalAnchor.Center,
)
# paste into inner rect
jpg_image.paste(
bestfit_jpg_image_inner, IMAGE_INNER_RECT_POSSIZE.to_pillow_tuple()
)
# build artifacts
jpg_image.save(ctx.temporary_artifact("ext", "jpg-image.png"))
ico_render(
ctx,
jpg_image,
ctx.output_artifact("ext", "jpg-image.ico"),
)
@@ -0,0 +1,8 @@
from ..types import AuroraContext
from . import action_icons, application_icons, mimetype_icons
def register(ctx: AuroraContext) -> None:
action_icons.register(ctx)
application_icons.register(ctx)
mimetype_icons.register(ctx)
@@ -0,0 +1,243 @@
from ..types import AuroraContext, AuroraEnvironment
from assemblicon.highlevel.utils import FdContext, VART_HW
from assemblicon.highlevel.source import TempArtSource, NewArtSource
from assemblicon.highlevel.sink import FdArtSink
from assemblicon.geometry import Rectangle
from assemblicon.lowlevel.artproc import (
art_anchor_to_offset,
anchor_to_offset,
default_art_resize,
align_paste,
align_alpha_composite,
VerticalAnchor,
HorizontalAnchor,
)
def register(ctx: AuroraContext) -> None:
ctx.register(
"nostd/action/image-properties",
(
TempArtSource("image-jpeg.png"),
TempArtSource("dialog-infomation.png"),
FdArtSink(FdContext.Actions, "image-properties"),
),
build_image_properties_icon,
)
ctx.register(
"nostd/action/image-zoom-original",
(
TempArtSource("component", "triangle-left.png"),
TempArtSource("component", "triangle-down.png"),
TempArtSource("component", "triangle-right.png"),
TempArtSource("component", "triangle-up.png"),
TempArtSource("image-jpeg.png"),
NewArtSource(),
FdArtSink(FdContext.Actions, "image-zoom-original"),
),
build_image_zoom_original_icon,
)
ctx.register(
"nostd/action/monitor-zoom-fit-best",
(
TempArtSource("component", "triangle-left.png"),
TempArtSource("component", "triangle-down.png"),
TempArtSource("component", "triangle-right.png"),
TempArtSource("component", "triangle-up.png"),
TempArtSource("component", "monitor.png"),
NewArtSource(),
FdArtSink(FdContext.Actions, "monitor-zoom-fit-best"),
),
build_monitor_zoom_fit_best_icon,
)
def build_image_properties_icon(
env: AuroraEnvironment,
jpeg_src: TempArtSource,
infomation_src: TempArtSource,
fd_sink: FdArtSink,
) -> None:
art = jpeg_src.pull(env)
info_mark = infomation_src.pull(env)
resized_info_mark = default_art_resize(info_mark, 500, 500)
align_alpha_composite(
art,
resized_info_mark,
art_anchor_to_offset(art, HorizontalAnchor.Right, VerticalAnchor.Bottom),
art_anchor_to_offset(
resized_info_mark, HorizontalAnchor.Right, VerticalAnchor.Bottom
),
)
# save artifact
fd_sink.push(env, art)
def build_image_zoom_original_icon(
env: AuroraEnvironment,
tri_left_src: TempArtSource,
tri_down_src: TempArtSource,
tri_right_src: TempArtSource,
tri_up_src: TempArtSource,
inner_src: TempArtSource,
new_art: NewArtSource,
fd_sink: FdArtSink,
) -> None:
# load triangle assets
TRI_SIZE = (200, 200)
tri_left = default_art_resize(tri_left_src.pull(env), *TRI_SIZE)
tri_down = default_art_resize(tri_down_src.pull(env), *TRI_SIZE)
tri_right = default_art_resize(tri_right_src.pull(env), *TRI_SIZE)
tri_up = default_art_resize(tri_up_src.pull(env), *TRI_SIZE)
# load inner image
inner = inner_src.pull(env)
resized_inner = default_art_resize(inner, 680, 680)
# composite this icon
icon = new_art.pull()
align_paste(
icon,
resized_inner,
art_anchor_to_offset(icon, HorizontalAnchor.Center, VerticalAnchor.Center),
art_anchor_to_offset(
resized_inner, HorizontalAnchor.Center, VerticalAnchor.Center
),
)
tri_rect = Rectangle.from_margin_rect(VART_HW, VART_HW, 100, 0, 100, 0)
align_alpha_composite(
icon,
tri_left,
anchor_to_offset(
tri_rect.width,
tri_rect.height,
HorizontalAnchor.Left,
VerticalAnchor.Center,
)
+ tri_rect.pos1,
art_anchor_to_offset(tri_left, HorizontalAnchor.Left, VerticalAnchor.Center),
)
align_alpha_composite(
icon,
tri_right,
anchor_to_offset(
tri_rect.width,
tri_rect.height,
HorizontalAnchor.Right,
VerticalAnchor.Center,
)
+ tri_rect.pos1,
art_anchor_to_offset(tri_right, HorizontalAnchor.Right, VerticalAnchor.Center),
)
align_alpha_composite(
icon,
tri_up,
anchor_to_offset(
tri_rect.width, tri_rect.height, HorizontalAnchor.Center, VerticalAnchor.Top
)
+ tri_rect.pos1,
art_anchor_to_offset(tri_up, HorizontalAnchor.Center, VerticalAnchor.Top),
)
align_alpha_composite(
icon,
tri_down,
anchor_to_offset(
tri_rect.width,
tri_rect.height,
HorizontalAnchor.Center,
VerticalAnchor.Bottom,
)
+ tri_rect.pos1,
art_anchor_to_offset(tri_down, HorizontalAnchor.Center, VerticalAnchor.Bottom),
)
# save it
fd_sink.push(env, icon)
def build_monitor_zoom_fit_best_icon(
env: AuroraEnvironment,
tri_left_src: TempArtSource,
tri_down_src: TempArtSource,
tri_right_src: TempArtSource,
tri_up_src: TempArtSource,
inner_src: TempArtSource,
new_art: NewArtSource,
fd_sink: FdArtSink,
) -> None:
# load triangle assets
TRI_SIZE = (200, 200)
tri_left = default_art_resize(tri_left_src.pull(env), *TRI_SIZE)
tri_down = default_art_resize(tri_down_src.pull(env), *TRI_SIZE)
tri_right = default_art_resize(tri_right_src.pull(env), *TRI_SIZE)
tri_up = default_art_resize(tri_up_src.pull(env), *TRI_SIZE)
# load inner image
inner = inner_src.pull(env)
resized_inner = default_art_resize(inner, 620, 620)
# composite this icon
icon = new_art.pull()
align_paste(
icon,
resized_inner,
art_anchor_to_offset(icon, HorizontalAnchor.Center, VerticalAnchor.Center),
art_anchor_to_offset(
resized_inner, HorizontalAnchor.Center, VerticalAnchor.Center
),
)
tri_rect = Rectangle.from_margin_rect(VART_HW, VART_HW, 100, 0, 100, 0)
align_alpha_composite(
icon,
tri_right,
anchor_to_offset(
tri_rect.width,
tri_rect.height,
HorizontalAnchor.Left,
VerticalAnchor.Center,
)
+ tri_rect.pos1,
art_anchor_to_offset(tri_right, HorizontalAnchor.Left, VerticalAnchor.Center),
)
align_alpha_composite(
icon,
tri_left,
anchor_to_offset(
tri_rect.width,
tri_rect.height,
HorizontalAnchor.Right,
VerticalAnchor.Center,
)
+ tri_rect.pos1,
art_anchor_to_offset(tri_left, HorizontalAnchor.Right, VerticalAnchor.Center),
)
align_alpha_composite(
icon,
tri_down,
anchor_to_offset(
tri_rect.width, tri_rect.height, HorizontalAnchor.Center, VerticalAnchor.Top
)
+ tri_rect.pos1,
art_anchor_to_offset(tri_down, HorizontalAnchor.Center, VerticalAnchor.Top),
)
align_alpha_composite(
icon,
tri_up,
anchor_to_offset(
tri_rect.width,
tri_rect.height,
HorizontalAnchor.Center,
VerticalAnchor.Bottom,
)
+ tri_rect.pos1,
art_anchor_to_offset(tri_up, HorizontalAnchor.Center, VerticalAnchor.Bottom),
)
# save it
fd_sink.push(env, icon)
@@ -0,0 +1,82 @@
from ..types import AuroraContext, AuroraEnvironment
from assemblicon.highlevel.utils import FdContext, WinCategory, MacCategory
from assemblicon.highlevel.source import (
SvgSource,
TempArtSource,
NewArtSource,
BlenderSource,
)
from assemblicon.highlevel.sink import FdArtSink, WinArtSink, MacArtSink, TempArtSink
from assemblicon.lowlevel.artproc import (
art_anchor_to_offset,
default_art_resize,
align_paste,
VerticalAnchor,
HorizontalAnchor,
)
def register(ctx: AuroraContext) -> None:
ctx.register(
"nostd/application/org.sarasaschipworkshop.picture",
(
SvgSource("apps", "sarasacw-picture-frame.svg"),
TempArtSource("image-jpeg.png"),
NewArtSource(),
FdArtSink(FdContext.Applications, "org.sarasaschipworkshop.picture"),
WinArtSink(WinCategory.Application, "sarasacw-picture"),
MacArtSink(MacCategory.Application, "sarasacw-picture"),
),
build_sarasacw_picture_icon,
)
ctx.register(
"nostd/application/notepad",
(
BlenderSource("apps", "notepad.blend"),
TempArtSink("notepad.png"),
WinArtSink(WinCategory.Application, "notepad"),
),
build_notepad_icon,
)
def build_sarasacw_picture_icon(
env: AuroraEnvironment,
frame_src: SvgSource,
inner_src: TempArtSource,
new_art: NewArtSource,
fd_sink: FdArtSink,
win_sink: WinArtSink,
mac_sink: MacArtSink,
) -> None:
app = new_art.pull()
frame = frame_src.pull(env)
inner = inner_src.pull(env)
resized_inner = default_art_resize(inner, 990, 990)
align_paste(
app,
resized_inner,
art_anchor_to_offset(app, HorizontalAnchor.Center, VerticalAnchor.Center),
art_anchor_to_offset(
resized_inner, HorizontalAnchor.Center, VerticalAnchor.Center
),
)
app.alpha_composite(frame, (0, 0))
# save artifact
fd_sink.push(env, app)
win_sink.push(env, app)
mac_sink.push(env, app)
def build_notepad_icon(
env: AuroraEnvironment,
src: BlenderSource,
temp_sink: TempArtSink,
win_sink: WinArtSink,
) -> None:
src.pull_to_temp(env, temp_sink)
with temp_sink.repull(env) as art:
win_sink.push(env, art)
@@ -0,0 +1,8 @@
from ..types import AuroraContext, AuroraEnvironment
from assemblicon.highlevel.utils import FdContext, WinCategory, MacCategory
from assemblicon.highlevel.source import SvgSource, TempArtSource
from assemblicon.highlevel.sink import TempArtSink, FdArtSink, WinArtSink, MacArtSink
def register(ctx: AuroraContext) -> None:
pass
@@ -1,6 +1,9 @@
from ..assemblicon.context import Context from ..types import AuroraContext
from .action_icons import build_action_icons from . import components, action_icons, status_icons, mimetype_icons
def build_standard_icons(ctx: Context) -> None: def register(ctx: AuroraContext) -> None:
build_action_icons(ctx) components.register(ctx)
action_icons.register(ctx)
mimetype_icons.register(ctx)
status_icons.register(ctx)
@@ -1,43 +1,176 @@
from pathlib import Path
import PIL.Image import PIL.Image
from ..assemblicon.render import inkscape_render, ico_render, InkscapeConfig from ..types import AuroraContext, AuroraEnvironment
from ..assemblicon.context import Context from assemblicon.geometry import Rectangle, Point
from ..assemblicon.utils import Artifact from assemblicon.lowlevel.artproc import default_art_resize, align_alpha_composite
from ..assemblicon.geometry import MinMaxRect, PosSizeRect from assemblicon.highlevel.utils import FdContext
from ..assemblicon.artproc import ( from assemblicon.highlevel.source import SvgSource, TempArtSource
aspect_ratio_scale_and_crop, from assemblicon.highlevel.sink import FdArtSink
VerticalAnchor,
HorizontalAnchor,
)
def build_action_icons(ctx: Context) -> None: def register(ctx: AuroraContext) -> None:
_build_go_icons(ctx) ctx.register(
"std/action/go-[previous|down|next|up]",
(
def _build_go_icons(ctx: Context) -> None: SvgSource("go-base.svg"),
# build go-* base SvgSource("go-inner-shining.svg"),
inkscape_render( SvgSource("go-inner.svg"),
ctx.input_artifact("component", "go-base.svg"), FdArtSink(FdContext.Actions, "go-previous"),
ctx.temporary_artifact("component", "go-base.png"), FdArtSink(FdContext.Actions, "go-down"),
InkscapeConfig(1024, 1024), FdArtSink(FdContext.Actions, "go-next"),
) FdArtSink(FdContext.Actions, "go-up"),
# build go-* shining ),
inkscape_render( build_go_icons,
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: ctx.register(
with PIL.Image.open( "std/action/object-flip-horizontal",
ctx.temporary_artifact("component", "go-inner-shining.png") (
) as go_shining: SvgSource("object-flip-horizontal.svg"),
_build_go_icon(ctx, go_base, go_shining, ctx.input_artifact("go-previous.svg")) FdArtSink(FdContext.Actions, "object-flip-horizontal"),
_build_go_icon(ctx, go_base, go_shining, ctx.input_artifact("go-next.svg")) ),
build_object_flip_horizontal_icon,
)
ctx.register(
"std/action/object-flip-vertical",
(
SvgSource("object-flip-vertical.svg"),
FdArtSink(FdContext.Actions, "object-flip-vertical"),
),
build_object_flip_vertical_icon,
)
ctx.register(
"std/action/object-rotate-left",
(
SvgSource("object-rotate-left.svg"),
FdArtSink(FdContext.Actions, "object-rotate-left"),
),
build_object_rotate_left_icon,
)
ctx.register(
"std/action/object-rotate-right",
(
SvgSource("object-rotate-right.svg"),
FdArtSink(FdContext.Actions, "object-rotate-right"),
),
build_object_rotate_right_icon,
)
ctx.register(
"std/action/zoom-[original|fit-best]",
(
TempArtSource("component", "triangle-left.png"),
TempArtSource("component", "triangle-down.png"),
TempArtSource("component", "triangle-right.png"),
TempArtSource("component", "triangle-up.png"),
FdArtSink(FdContext.Actions, "zoom-original"),
FdArtSink(FdContext.Actions, "zoom-fit-best"),
),
build_zoom_origin_and_fit_best_icons,
)
ctx.register(
"std/action/zoom-[in|out]",
(
TempArtSource("component", "magnifying-glass.png"),
TempArtSource("component", "plus.png"),
TempArtSource("component", "minus.png"),
FdArtSink(FdContext.Actions, "zoom-in"),
FdArtSink(FdContext.Actions, "zoom-out"),
),
build_zoom_in_out_icons,
)
def _build_go_icon(ctx: Context, base: Artifact, shining: Artifact, mask: Path) -> None: def build_go_icons(
go = base.copy() env: AuroraEnvironment,
base_src: SvgSource,
shining_src: SvgSource,
inner_src: SvgSource,
sink_previous: FdArtSink,
sink_down: FdArtSink,
sink_next: FdArtSink,
sink_up: FdArtSink,
) -> None:
# build go-* base, shining and inner
go_base = base_src.pull(env)
go_inner_shining = shining_src.pull(env)
go_inner = inner_src.pull(env)
with go_base, go_inner_shining, go_inner:
go_mask = go_inner
for sink in (sink_previous, sink_down, sink_next, sink_up):
go = go_base.copy()
# render shining with inner mask
go.paste(go_inner_shining, (0, 0), go_mask)
# save as FD action
sink.push(env, go)
# rotate it for next calling
go_mask = go_mask.transpose(PIL.Image.Transpose.ROTATE_90)
def build_object_flip_horizontal_icon(
env: AuroraEnvironment, src: SvgSource, sink: FdArtSink
) -> None:
with src.pull(env) as art:
sink.push(env, art)
def build_object_flip_vertical_icon(
env: AuroraEnvironment, src: SvgSource, sink: FdArtSink
) -> None:
with src.pull(env) as art:
sink.push(env, art)
def build_object_rotate_left_icon(
env: AuroraEnvironment, src: SvgSource, sink: FdArtSink
) -> None:
with src.pull(env) as art:
sink.push(env, art)
def build_object_rotate_right_icon(
env: AuroraEnvironment, src: SvgSource, sink: FdArtSink
) -> None:
with src.pull(env) as art:
sink.push(env, art)
def build_zoom_origin_and_fit_best_icons(
env: AuroraEnvironment,
tri_left_src: TempArtSource,
tri_down_src: TempArtSource,
tri_right_src: TempArtSource,
tri_up_src: TempArtSource,
zoom_original_sink: FdArtSink,
zoom_fit_best_sink: FdArtSink,
) -> None:
# TODO: finish this in future
pass
def build_zoom_in_out_icons(
env: AuroraEnvironment,
magnifying_glass_source: TempArtSource,
plus_source: TempArtSource,
minus_source: TempArtSource,
zoom_in_sink: FdArtSink,
zoom_out_sink: FdArtSink,
) -> None:
magnifying_glass = magnifying_glass_source.pull(env)
magnifying_glass_rect = Rectangle.from_padding_rect(
Point(665, 355), 200, 200, 200, 200
)
inner_size = (magnifying_glass_rect.width, magnifying_glass_rect.height)
plus = default_art_resize(plus_source.pull(env), *inner_size)
minus = default_art_resize(minus_source.pull(env), *inner_size)
zoom_in = magnifying_glass.copy()
align_alpha_composite(zoom_in, plus, magnifying_glass_rect.pos1, Point(0, 0))
zoom_in_sink.push(env, zoom_in)
zoom_out = magnifying_glass.copy()
align_alpha_composite(zoom_out, minus, magnifying_glass_rect.pos1, Point(0, 0))
zoom_out_sink.push(env, zoom_out)
@@ -0,0 +1,138 @@
import PIL.Image
from ..types import AuroraContext, AuroraEnvironment
from assemblicon.highlevel.source import (
SvgSource,
TempArtSource,
NewArtSource,
BlenderSource,
)
from assemblicon.highlevel.sink import TempArtSink
def register(ctx: AuroraContext) -> None:
ctx.register(
"component/generic-background",
(
SvgSource("component", "generic-background.svg"),
TempArtSink("component", "generic-background.png"),
),
build_generic_background,
)
ctx.register(
"component/triangles",
(
SvgSource("component", "triangle.svg"),
TempArtSource("component", "generic-background.png"),
NewArtSource(),
TempArtSink("component", "triangle-left.png"),
TempArtSink("component", "triangle-down.png"),
TempArtSink("component", "triangle-right.png"),
TempArtSink("component", "triangle-up.png"),
),
build_triangle_icons,
)
ctx.register(
"component/plus_minus",
(
TempArtSource("component", "generic-background.png"),
SvgSource("component", "plus.svg"),
SvgSource("component", "minus.svg"),
NewArtSource(),
TempArtSink("component", "plus.png"),
TempArtSink("component", "minus.png"),
),
build_plus_minus_icons,
)
ctx.register(
"component/magnifying-glass",
(
BlenderSource("component", "magnifying-glass.blend"),
TempArtSink("component", "magnifying-glass.png"),
),
build_magnifying_glass_icon,
)
ctx.register(
"component/monitor",
(
SvgSource("component", "monitor.svg"),
TempArtSink("component", "monitor.png"),
),
build_generic_background,
)
def build_generic_background(
env: AuroraEnvironment, src: SvgSource, sink: TempArtSink
) -> None:
src.pull_to_temp(env, sink)
def build_triangle_icons(
env: AuroraEnvironment,
tri_src: SvgSource,
bg_src: TempArtSource,
new_art: NewArtSource,
sink_left: TempArtSink,
sink_down: TempArtSink,
sink_right: TempArtSink,
sink_up: TempArtSink,
) -> None:
# load triangle and background assets
tri = tri_src.pull(env)
bg = bg_src.pull(env)
# create 4 triangle icons for following building
tri_left = new_art.pull()
tri_left.paste(bg, (0, 0), tri)
tri = tri.transpose(PIL.Image.Transpose.ROTATE_90)
tri_down = new_art.pull()
tri_down.paste(bg, (0, 0), tri)
tri = tri.transpose(PIL.Image.Transpose.ROTATE_90)
tri_right = new_art.pull()
tri_right.paste(bg, (0, 0), tri)
tri = tri.transpose(PIL.Image.Transpose.ROTATE_90)
tri_up = new_art.pull()
tri_up.paste(bg, (0, 0), tri)
# save them for reuse
sink_left.push(env, tri_left)
sink_down.push(env, tri_down)
sink_right.push(env, tri_right)
sink_up.push(env, tri_up)
def build_plus_minus_icons(
env: AuroraEnvironment,
bg_src: TempArtSource,
plus_src: SvgSource,
minus_src: SvgSource,
new_art: NewArtSource,
plus_sink: TempArtSink,
minus_sink: TempArtSink,
) -> None:
bg = bg_src.pull(env)
plus = plus_src.pull(env)
art = new_art.pull()
art.paste(bg, (0, 0), plus)
plus_sink.push(env, art)
minus = minus_src.pull(env)
art = new_art.pull()
art.paste(bg, (0, 0), minus)
minus_sink.push(env, art)
def build_magnifying_glass_icon(
env: AuroraEnvironment, src: BlenderSource, sink: TempArtSink
) -> None:
src.pull_to_temp(env, sink)
def build_monitor_icon(
env: AuroraEnvironment, src: SvgSource, sink: TempArtSink
) -> None:
src.pull_to_temp(env, sink)
@@ -0,0 +1,270 @@
import PIL.Image
import PIL.ImageEnhance
from ..types import AuroraContext, AuroraEnvironment
from assemblicon.highlevel.utils import FdContext, WinCategory, MacCategory
from assemblicon.highlevel.source import (
SvgSource,
BitmapSource,
TempArtSource,
FdArtSource,
)
from assemblicon.highlevel.sink import TempArtSink, FdArtSink, WinArtSink, MacArtSink
from assemblicon.geometry import Rectangle, Point
from assemblicon.lowlevel.artio import new_artifact
from assemblicon.lowlevel.artproc import (
aspect_ratio_scale_and_crop,
VerticalAnchor,
HorizontalAnchor,
)
def register(ctx: AuroraContext) -> None:
ctx.register(
"component/image-base",
(
SvgSource("component", "image-base.svg"),
TempArtSink("component", "image-base.png"),
),
build_image_base_icon,
)
ctx.register(
"std/mimetype/image-x-generic",
(
FdArtSource(FdContext.MimeTypes, "image-jpeg"),
FdArtSink(FdContext.MimeTypes, "image-x-generic"),
),
build_image_x_generic,
)
ctx.register(
"std/mimetype/image-jpeg",
(
TempArtSource("component", "image-base.png"),
BitmapSource("bitmap", "Niinsaare_järv.jpg"),
TempArtSink("image-jpeg.png"),
FdArtSink(FdContext.MimeTypes, "image-jpeg"),
WinArtSink(WinCategory.Extension, "jpg"),
MacArtSink(MacCategory.Extension, "jpg"),
),
build_image_jpeg_icon,
)
ctx.register(
"std/mimetype/image-png",
(
TempArtSource("component", "image-base.png"),
BitmapSource("bitmap", "Tulipa_suaveolens_floriade_to_Canberra.jpg"),
BitmapSource("bitmap", "Tulipa_suaveolens_floriade_to_Canberra-mask.png"),
SvgSource("image-png-checkerboard.svg"),
FdArtSink(FdContext.MimeTypes, "image-png"),
WinArtSink(WinCategory.Extension, "png"),
MacArtSink(MacCategory.Extension, "png"),
),
build_image_png_icon,
)
ctx.register(
"std/mimetype/image-bmp",
(
TempArtSource("component", "image-base.png"),
BitmapSource("bitmap", "Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg"),
FdArtSink(FdContext.MimeTypes, "image-bmp"),
WinArtSink(WinCategory.Extension, "bmp"),
MacArtSink(MacCategory.Extension, "bmp"),
),
build_image_bmp_icon,
)
ctx.register(
"std/mimetype/image-gif",
(
TempArtSource("component", "image-base.png"),
BitmapSource("bitmap", "Hot-air_balloon_in_Tambov_-_02.jpg"),
FdArtSink(FdContext.MimeTypes, "image-gif"),
WinArtSink(WinCategory.Extension, "gif"),
MacArtSink(MacCategory.Extension, "gif"),
),
build_image_gif_icon,
)
def build_image_base_icon(
env: AuroraEnvironment, src: SvgSource, sink: TempArtSink
) -> None:
src.pull_to_temp(env, sink)
def build_image_x_generic(
env: AuroraEnvironment, src: FdArtSource, sink: FdArtSink
) -> None:
# use image/jpeg as the default image icon
src.link_to(env, sink)
IMAGE_INNER_RECT = Rectangle(Point(20 * 4, 52 * 4), Point(236 * 4, 204 * 4))
"""The valid region of image-base for putting inner image."""
def build_image_jpeg_icon(
env: AuroraEnvironment,
base_src: TempArtSource,
bitmap_src: BitmapSource,
temp_sink: TempArtSink,
fd_sink: FdArtSink,
win_sink: WinArtSink,
mac_sink: MacArtSink,
) -> None:
jpg_image = base_src.pull(env)
jpg_image_inner = bitmap_src.pull(env)
with jpg_image_inner:
# crop the area which is interested by ourselves
interest_area = Rectangle.from_pos_size_rect(Point(2700, 0), 5016, 3888)
crop_jpg_image_inner = jpg_image_inner.crop(interest_area.to_tuple())
# do a flip
flip_jpg_image_inner = crop_jpg_image_inner.transpose(
PIL.Image.Transpose.FLIP_LEFT_RIGHT
)
# enhance its saturation
enhancer = PIL.ImageEnhance.Color(flip_jpg_image_inner)
saturated_jpg_image_inner = enhancer.enhance(1.4)
# fit to inner rect
bestfit_jpg_image_inner = aspect_ratio_scale_and_crop(
saturated_jpg_image_inner,
IMAGE_INNER_RECT.width,
IMAGE_INNER_RECT.height,
PIL.Image.Resampling.NEAREST,
HorizontalAnchor.Center,
VerticalAnchor.Center,
)
# paste into inner rect
jpg_image.paste(bestfit_jpg_image_inner, IMAGE_INNER_RECT.to_tuple())
# build artifacts
# save as temporary for future use
temp_sink.push(env, jpg_image)
# save for FD
fd_sink.push(env, jpg_image)
# save for windows and mac
win_sink.push(env, jpg_image)
mac_sink.push(env, jpg_image)
def build_image_png_icon(
env: AuroraEnvironment,
base_src: TempArtSource,
bitmap_src: BitmapSource,
bitmap_mask_src: BitmapSource,
checkerboard_src: SvgSource,
fd_sink: FdArtSink,
win_sink: WinArtSink,
mac_sink: MacArtSink,
) -> None:
png_image = base_src.pull(env)
bitmap = bitmap_src.pull(env)
bitmap_mask = bitmap_mask_src.pull(env)
checkerboard = checkerboard_src.pull(env)
with bitmap, bitmap_mask, checkerboard:
# mask bitmap first
# force mask as grayscale format to apply black-white mask instead of alpha channel mask
bitmap_masked = new_artifact(bitmap.size)
bitmap_masked.paste(bitmap, (0, 0), bitmap_mask.convert("L"))
# we use the whole image as the inner,
# so we directly fit it to inner.
bestfit_bitmap = aspect_ratio_scale_and_crop(
bitmap_masked,
IMAGE_INNER_RECT.width,
IMAGE_INNER_RECT.height,
PIL.Image.Resampling.NEAREST,
HorizontalAnchor.Center,
VerticalAnchor.Top,
)
# TODO: When bestfit this checkerboard, the border of this checkerboard aer lost.
# We may need resolve this in future but currently it is so subtle to notice.
bestfit_checkerboard = aspect_ratio_scale_and_crop(
checkerboard,
IMAGE_INNER_RECT.width,
IMAGE_INNER_RECT.height,
PIL.Image.Resampling.NEAREST,
HorizontalAnchor.Center,
VerticalAnchor.Center,
)
# composite them together
png_image.alpha_composite(
bestfit_checkerboard, IMAGE_INNER_RECT.pos1.to_tuple()
)
png_image.alpha_composite(bestfit_bitmap, IMAGE_INNER_RECT.pos1.to_tuple())
# build artifacts
# save for FD
fd_sink.push(env, png_image)
# save for windows and mac
win_sink.push(env, png_image)
mac_sink.push(env, png_image)
def build_image_bmp_icon(
env: AuroraEnvironment,
base_src: TempArtSource,
bitmap_src: BitmapSource,
fd_sink: FdArtSink,
win_sink: WinArtSink,
mac_sink: MacArtSink,
) -> None:
bmp_image = base_src.pull(env)
bmp_image_inner = bitmap_src.pull(env)
with bmp_image_inner:
# we use the whole image as the inner,
# so we directly fit it to inner.
bestfit_inner = aspect_ratio_scale_and_crop(
bmp_image_inner,
IMAGE_INNER_RECT.width,
IMAGE_INNER_RECT.height,
PIL.Image.Resampling.NEAREST,
HorizontalAnchor.Center,
VerticalAnchor.Center,
)
# paste into inner rect
bmp_image.paste(bestfit_inner, IMAGE_INNER_RECT.to_tuple())
# build artifacts
# save for FD
fd_sink.push(env, bmp_image)
# save for windows and mac
win_sink.push(env, bmp_image)
mac_sink.push(env, bmp_image)
def build_image_gif_icon(
env: AuroraEnvironment,
base_src: TempArtSource,
bitmap_src: BitmapSource,
fd_sink: FdArtSink,
win_sink: WinArtSink,
mac_sink: MacArtSink,
) -> None:
gif_image = base_src.pull(env)
gif_image_inner = bitmap_src.pull(env)
with gif_image_inner:
# we use the whole image as the inner,
# so we directly fit it to inner.
bestfit_inner = aspect_ratio_scale_and_crop(
gif_image_inner,
IMAGE_INNER_RECT.width,
IMAGE_INNER_RECT.height,
PIL.Image.Resampling.NEAREST,
HorizontalAnchor.Center,
VerticalAnchor.Center,
)
# paste into inner rect
gif_image.paste(bestfit_inner, IMAGE_INNER_RECT.to_tuple())
# build artifacts
# save for FD
fd_sink.push(env, gif_image)
# save for windows and mac
win_sink.push(env, gif_image)
mac_sink.push(env, gif_image)
@@ -0,0 +1,79 @@
from ..types import AuroraContext, AuroraEnvironment
from assemblicon.highlevel.utils import FdContext
from assemblicon.highlevel.source import SvgSource
from assemblicon.highlevel.sink import TempArtSink, FdArtSink
def register(ctx: AuroraContext) -> None:
ctx.register(
"std/status/dialog-infomation",
(
SvgSource("dialog-infomation.svg"),
TempArtSink("dialog-infomation.png"),
FdArtSink(FdContext.Status, "dialog-infomation"),
),
build_dialog_infomation_icon,
)
ctx.register(
"std/status/dialog-warning",
(
SvgSource("dialog-warning.svg"),
TempArtSink("dialog-warning.png"),
FdArtSink(FdContext.Status, "dialog-warning"),
),
build_dialog_warning_icon,
)
ctx.register(
"std/status/dialog-error",
(
SvgSource("dialog-error.svg"),
TempArtSink("dialog-error.png"),
FdArtSink(FdContext.Status, "dialog-error"),
),
build_dialog_error_icon,
)
ctx.register(
"std/status/dialog-question",
(
SvgSource("dialog-question.svg"),
TempArtSink("dialog-question.png"),
FdArtSink(FdContext.Status, "dialog-question"),
),
build_dialog_question_icon,
)
def build_dialog_infomation_icon(
env: AuroraEnvironment, src: SvgSource, temp_sink: TempArtSink, fd_sink: FdArtSink
) -> None:
# save dialog icon with FreeDesktop icon and temp icon for reuse.
src.pull_to_temp(env, temp_sink)
with temp_sink.repull(env) as art:
fd_sink.push(env, art)
def build_dialog_warning_icon(
env: AuroraEnvironment, src: SvgSource, temp_sink: TempArtSink, fd_sink: FdArtSink
) -> None:
# save dialog icon with FreeDesktop icon and temp icon for reuse.
src.pull_to_temp(env, temp_sink)
with temp_sink.repull(env) as art:
fd_sink.push(env, art)
def build_dialog_error_icon(
env: AuroraEnvironment, src: SvgSource, temp_sink: TempArtSink, fd_sink: FdArtSink
) -> None:
# TODO: update this icon from rectangle to circle
# save dialog icon with FreeDesktop icon and temp icon for reuse.
src.pull_to_temp(env, temp_sink)
with temp_sink.repull(env) as art:
fd_sink.push(env, art)
def build_dialog_question_icon(
env: AuroraEnvironment, src: SvgSource, temp_sink: TempArtSink, fd_sink: FdArtSink
) -> None:
# TODO: add dialog-question generation
pass
+14
View File
@@ -0,0 +1,14 @@
from dataclasses import dataclass
from assemblicon.highlevel.environment import Environment, Prologue
from assemblicon.highlevel.context import Context
@dataclass(frozen=True)
class AuroraPrologue(Prologue):
"""Aurora icon set builder specific prologue"""
pass
type AuroraEnvironment = Environment[AuroraPrologue]
type AuroraContext = Context[AuroraPrologue]
Generated
+10 -2
View File
@@ -2,16 +2,24 @@ version = 1
revision = 2 revision = 2
requires-python = ">=3.13" requires-python = ">=3.13"
[[package]]
name = "assemblicon"
version = "0.1.0"
source = { git = "https://github.com/SarasasChipWorkshop/assemblicon#0073298d45d6c44fc164456ebdec5f0d3ada5dff" }
dependencies = [
{ name = "pillow" },
]
[[package]] [[package]]
name = "aurora-iconset-builder" name = "aurora-iconset-builder"
version = "0.1.0" version = "0.1.0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "pillow" }, { name = "assemblicon" },
] ]
[package.metadata] [package.metadata]
requires-dist = [{ name = "pillow", specifier = ">=12.3.0" }] requires-dist = [{ name = "assemblicon", git = "https://github.com/SarasasChipWorkshop/assemblicon" }]
[[package]] [[package]]
name = "pillow" name = "pillow"