feat: first commit
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
|||||||
|
# No VS Code
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# No backup files
|
||||||
|
*.blend1
|
||||||
|
*.kra~
|
||||||
|
|
||||||
|
# No binary assets should be submitted in Git
|
||||||
|
# We may use Git LFS in future.
|
||||||
|
# We need delete these in that day.
|
||||||
|
*.png
|
||||||
|
*.jpg
|
||||||
|
*.jpeg
|
||||||
|
*.svg
|
||||||
|
*.svgz
|
||||||
|
*.kra
|
||||||
|
*.blend
|
||||||
|
*.fspy
|
||||||
|
*.ico
|
||||||
|
*.icns
|
||||||
|
*.dll
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Aurora Icon Set
|
||||||
|
|
||||||
|
Yet Another Aero Icon Set.
|
||||||
|
|
||||||
|
## Repository Notes
|
||||||
|
|
||||||
|
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.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Ignore all files except this.
|
||||||
|
*
|
||||||
|
!.gitignore
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
[[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'
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Python-generated files
|
||||||
|
__pycache__/
|
||||||
|
*.py[oc]
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
wheels/
|
||||||
|
*.egg-info
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
3.13
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Aurora Icon Set Builder
|
||||||
|
|
||||||
|
WIP
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
[project]
|
||||||
|
name = "aurora-iconset-builder"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "The Programmatic Icon Assembly Workflow for Aurora Icon Set."
|
||||||
|
readme = "README.md"
|
||||||
|
authors = [
|
||||||
|
{ name = "yyc12345", email = "yyc12321@outlook.com" }
|
||||||
|
]
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
dependencies = [
|
||||||
|
"pillow>=12.3.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
aurora-iconset-builder = "aurora_iconset_builder:main"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["uv_build>=0.8.0,<0.9"]
|
||||||
|
build-backend = "uv_build"
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
setup_logging()
|
||||||
|
|
||||||
|
repository_root = Path(__file__).resolve().parent.parent.parent.parent
|
||||||
|
iconset_input = repository_root / "src"
|
||||||
|
iconset_output = repository_root / "artifact"
|
||||||
|
ctx = Context(iconset_input, iconset_output)
|
||||||
|
|
||||||
|
try:
|
||||||
|
_build_icons(ctx)
|
||||||
|
except BaseException as e:
|
||||||
|
logging.fatal("Runtime error: %s", e)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_icons(ctx: Context) -> None:
|
||||||
|
build_standard_icons(ctx)
|
||||||
|
#build_image_icons(ctx)
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
from . import main
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
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")
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
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)
|
||||||
|
)
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
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()
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
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
|
||||||
|
)
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
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)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import logging
|
||||||
|
from PIL.Image import Image
|
||||||
|
|
||||||
|
type Artifact = Image
|
||||||
|
|
||||||
|
def setup_logging() -> None:
|
||||||
|
logging.basicConfig(format="[%(levelname)s] %(message)s", level=logging.INFO)
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
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,6 @@
|
|||||||
|
from ..assemblicon.context import Context
|
||||||
|
from .action_icons import build_action_icons
|
||||||
|
|
||||||
|
|
||||||
|
def build_standard_icons(ctx: Context) -> None:
|
||||||
|
build_action_icons(ctx)
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
import PIL.Image
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_action_icons(ctx: Context) -> None:
|
||||||
|
_build_go_icons(ctx)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_go_icons(ctx: Context) -> None:
|
||||||
|
# build 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(
|
||||||
|
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"))
|
||||||
|
|
||||||
|
|
||||||
|
def _build_go_icon(ctx: Context, base: Artifact, shining: Artifact, mask: Path) -> None:
|
||||||
|
go = base.copy()
|
||||||
|
|
||||||
|
|
||||||
Generated
+76
@@ -0,0 +1,76 @@
|
|||||||
|
version = 1
|
||||||
|
revision = 2
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aurora-iconset-builder"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { editable = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "pillow" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [{ name = "pillow", specifier = ">=12.3.0" }]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pillow"
|
||||||
|
version = "12.3.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9d/ac/31fb64e1e7efb5a4b50cd3d92049ba89ac6e4d8d3bb6a74e15048ca3353e/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89", size = 4161684, upload-time = "2026-07-01T11:54:25.934Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/87/b4/9805e23d2b4d77842b468513841fda254ee42f0289d25088340e4ff46e2d/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace", size = 4255487, upload-time = "2026-07-01T11:54:27.935Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/df/39/ecf519435a200c693fe053a6ee4d835b41cf963a4dfc2551c4e637cb2a71/pillow-12.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec", size = 3696433, upload-time = "2026-07-01T11:54:29.813Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/42/92/2fc3ffad878ae8dd5469ec1bc8eb83b71f48e13efdf68f02709003982a32/pillow-12.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66", size = 5345889, upload-time = "2026-07-01T11:54:31.97Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35", size = 4780109, upload-time = "2026-07-01T11:54:34.026Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1f/01/e18aff37cb0b4aac47ac90f016d347a49aca667ef97f190b06ac2aabc928/pillow-12.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65", size = 6263736, upload-time = "2026-07-01T11:54:36.131Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3", size = 6937129, upload-time = "2026-07-01T11:54:38.216Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/70/4d/105627a13300c5e0df1d174230b32fd1273062c96f7745fd552b945d1e1d/pillow-12.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a", size = 6339562, upload-time = "2026-07-01T11:54:40.354Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6b/1d/f13de01a553988ab895ba1c722e06cf3144d4f57656fd5b81b6d881f1179/pillow-12.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e", size = 7049439, upload-time = "2026-07-01T11:54:42.489Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c9/f9/066794cca041b969964f779ee5fa66a9498bbf34248ac39c5d7954e4198f/pillow-12.3.0-cp313-cp313-win32.whl", hash = "sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f", size = 6473287, upload-time = "2026-07-01T11:54:44.9Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a6/9b/7a58e61d62be561da3a356fe2384d4059a6345fc130e23ef1c36a5b81d24/pillow-12.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8", size = 7239691, upload-time = "2026-07-01T11:54:47.141Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/b0/c4ed4f0ef8f8fa5ee8351537db6650bb8189f7e118842978dd6589065692/pillow-12.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b", size = 2568185, upload-time = "2026-07-01T11:54:49.137Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dc/01/001f65b68192f0228cc1dbbc8d2530ab5d58b61037ba0587f946fea607cd/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330", size = 4161736, upload-time = "2026-07-01T11:54:51.156Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1a/d2/0219746d0fd16fc8a84498e79452375be3797d3ce4044596ce565164b84f/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217", size = 4255435, upload-time = "2026-07-01T11:54:53.414Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c8/02/8d0bc62ef0302318c46ff2a512822d2610e81c7aa46c9b3abe6cbaca5ad0/pillow-12.3.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930", size = 3696262, upload-time = "2026-07-01T11:54:55.739Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/85/e2/73c77d218410b14f5f2d565e8a998d5317b7b9c75368d29985139f7a46f0/pillow-12.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8", size = 5350344, upload-time = "2026-07-01T11:54:57.657Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/da/32c752228ae345f489e3a42499d817b6c3996da7e8a3bc7a04fc806b243b/pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e158cb00350dc278f3b91551101aa7d12415a66ebf2c91d8d5ac14e56ddd3ad0", size = 4780131, upload-time = "2026-07-01T11:54:59.713Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b1/9d/8b2c807dbef61a5197c047afe99823787eb66f63daf9fb2432f91d6f0462/pillow-12.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321", size = 6263757, upload-time = "2026-07-01T11:55:01.778Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/44/c85361f65dbe00eea8576ee467c768d25129989efb76e94f205e9ca9bb46/pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b", size = 6936962, upload-time = "2026-07-01T11:55:03.93Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/7e/e483414b35800b86b6f08dbbc7803fb5cd52c4d6f897f47d53ea2c7e6f65/pillow-12.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fe3cca2e4e8a592be0f269a1ca4835c25199d9f3ce815c8491048f785b0a0198", size = 6339171, upload-time = "2026-07-01T11:55:05.989Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f0/f4/68c491844841ede6bed70189546b3ee9731cf9f2cbad396faff5e1ccba45/pillow-12.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:23aceaa007d6172b02c277f0cd359c79492bbb14f7072b4ede9fbcaf20648130", size = 7048116, upload-time = "2026-07-01T11:55:08.131Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a3/34/77f3f793fed8efc7d243f21b33c5a3f0d1c97ee70346d3db855587e155ff/pillow-12.3.0-cp314-cp314-win32.whl", hash = "sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a", size = 6467209, upload-time = "2026-07-01T11:55:10.408Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f1/e0/492879f69d94f91f60fc8cd05ba03650e9520afebb2fb7aa12777d7c7f38/pillow-12.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d", size = 7237707, upload-time = "2026-07-01T11:55:12.745Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c9/ac/6b11f2875f1c2ac040d84e1bbf9cf22a88038f901ca1037898b280b38365/pillow-12.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838", size = 2565995, upload-time = "2026-07-01T11:55:14.736Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/52/69/c2208e56af9bfc1913afb24020297a691eb1d4ef688474c8a04913f65e04/pillow-12.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:164b31cd1a0490ab6efae01aa5df49da7061be0af1b30e035b6e9a1bfe34ee6e", size = 5352503, upload-time = "2026-07-01T11:55:17.076Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/07/70/e5686d753e898a45d778ff1718dba8516ead6ab6b95d85fc8c4b70650cf2/pillow-12.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5afb51d599ea772b8365ae807ae557f18bccfe46ab261fd1c2a9ed700fc6eb17", size = 4782956, upload-time = "2026-07-01T11:55:19.448Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d5/37/25c6692f06927ee973ff18c8d9ee98ad0b4d84ee67a09610c2dd1447958e/pillow-12.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3edce1d53195db527e0191f84b71d02022de0540bf43a16ed734ed7537b07385", size = 6322855, upload-time = "2026-07-01T11:55:21.613Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cc/91/420637fcb8f1bc11029e403b4538e6694744428d8246118e45719f944556/pillow-12.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf16ba1b4d0b6b7c8e534936632270cf70eb00dbe09005bc345b2677b726855c", size = 6989642, upload-time = "2026-07-01T11:55:24.006Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/10/08/b94d7811281ccf0d143a1cf768d1c49e1e54af63e7b708ab2ee3eb87face/pillow-12.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:24870b09b224f7ae3c39ed07d10e819d06f8720bc551847b1d623832b5b0e28d", size = 6391281, upload-time = "2026-07-01T11:55:26.252Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d2/87/24233f785f55474dc02ce3e739c5528a77e3a862e9333d1dd7a25cc31f70/pillow-12.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:30f2aa603c41533cc25c05acd0da21636e84a315768feb631c937177db558931", size = 7096716, upload-time = "2026-07-01T11:55:28.318Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/23/26/fcb2f6e37175b04f53570b59937867e2b80ee1685e744023153028fc14f9/pillow-12.3.0-cp314-cp314t-win32.whl", hash = "sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7", size = 6474125, upload-time = "2026-07-01T11:55:30.956Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/90/de/3634abee5f1c9e13c56787b7d5517b0ba8d6de51700b95578cf338349c9f/pillow-12.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c", size = 7242939, upload-time = "2026-07-01T11:55:34.044Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/2a/fd13f8eb24de5714a6eb444a3d67e2842c6c576e159a43793adf23051351/pillow-12.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45", size = 2567506, upload-time = "2026-07-01T11:55:35.988Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5d/dc/8fdce34ec725a33c81c6ba122b904d6b9024e50ea9ac7bede62fab54506c/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0feb2e9d6ad6c9e3c06effe9d00f3f1e618a6643273576b016f591e9315a7139", size = 4162063, upload-time = "2026-07-01T11:55:37.941Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/76/66/2044b9a63d3b84ff048228dfcb7cd9bf0df983e8470971bf7d4c57b693de/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9e881fca225083806662a5c43d627d215f258ff43c890f831966c7d7ba9c7402", size = 4255549, upload-time = "2026-07-01T11:55:40.022Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/52/7e/1f67e6f4ece6b582ee4b539decbcc9f848dc245a93ed8cd7338bafef72f1/pillow-12.3.0-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:4998562bf62a445225f22e07c896bb04b35b1b1f2eb6d760584c9c51d7a5f78c", size = 3696331, upload-time = "2026-07-01T11:55:41.98Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/12/40/d306fc2c8e4d45d7f175c77edca7063be7b86fe7fe6e68f4353bf71d808c/pillow-12.3.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:dc624f6bc473dacdf7ef7eb8678d0d08edf15cd94fad6ae5c7d6cc67a4e4902f", size = 5350370, upload-time = "2026-07-01T11:55:44.028Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dd/44/668fb1437e8ce420f62d6106eb66e44a5971602a4d794615bdf79315d82d/pillow-12.3.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:71d6097b330eea8fd15097780c8e89cb1a8ce7838669f48c5bacd6f663dd4701", size = 4780147, upload-time = "2026-07-01T11:55:46.073Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/08/93fa2e70e30a2d81547e481b6ee2bb9522117221fb1e0ce4b5df70967677/pillow-12.3.0-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:28ce87c5ab450a9dd970b52e5aca5fe63ed432d18a2eaddd1979a00a1ba24ace", size = 6273659, upload-time = "2026-07-01T11:55:48.264Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f8/6d/043e96ff814fc31a33077e4cba86082167db520c93632afdf2042febbb0c/pillow-12.3.0-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b02afb9b97f65fbca5f31db6a2a3ba21aa93030225f150fa3f249717e938fb4", size = 6947439, upload-time = "2026-07-01T11:55:50.503Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/af/92/ba71d2ee2ac0edf3fa33bd9d5ee9ee080da70b1766f3ca3934f9938ddac9/pillow-12.3.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:1182d52bc2d5e5d7d0949503aa7e36d12f42205dc287e4883f407b1988820d39", size = 6353577, upload-time = "2026-07-01T11:55:52.697Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0f/ce/e63064e2122923ff687c8ad792d0d736a7b3920a56a46982e81a7fdd25d6/pillow-12.3.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e795b7eb908249c4e43c7c99fac7c2c75dab0c43566e37db472a355f63693d71", size = 7060394, upload-time = "2026-07-01T11:55:55.149Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/54/76/a09cc3ccc8d773a7283d34c38bec1708f9e3cc932093cbc4c5e71ac4060b/pillow-12.3.0-cp315-cp315-win32.whl", hash = "sha256:57b3d78c95ba9059768b10e28b813002261d3f3dfc55cc48b0c988f625175827", size = 6467375, upload-time = "2026-07-01T11:55:57.769Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/03/1846c49ba3b1d5550392a4bbd06d6fb4578e1cd91a803198b5c90f5f7d53/pillow-12.3.0-cp315-cp315-win_amd64.whl", hash = "sha256:fa4ecea169a355be7a3ade2c783e2ed12f0e40d2c5621cda8b3297faf7fbb9f5", size = 7237048, upload-time = "2026-07-01T11:55:59.975Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fb/bb/89f35dcc79610423f9f195504d7def7f0d1416a711541b42867e25fe3412/pillow-12.3.0-cp315-cp315-win_arm64.whl", hash = "sha256:877c3f311ff35410f690861c4409e7ccbf0cd2f878e50628a28e5a0bb689e658", size = 2566006, upload-time = "2026-07-01T11:56:02.143Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/30/88/707027ba09942dfa2c28759b5c222d769290a41c6d20ea60ec250801941f/pillow-12.3.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:e9871b1ffbfa9656b60aeee92ed5136a5742696006fa322b29ea3d8da0ecc9cf", size = 5352509, upload-time = "2026-07-01T11:56:04.2Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b0/6d/00352fa25332c2569cd387851f568cc5a4b75a9adbfb37ac4fbce4c02eec/pillow-12.3.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:53aa02d20d10c3d814d536aa4e5ac9b84ca0ff5a88377963b085ad6822f93e64", size = 4783167, upload-time = "2026-07-01T11:56:06.631Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/13/4f/9e049dfa21af7c22427275720e2490267ba8138120add5c4c574deb69782/pillow-12.3.0-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:446c34dcc4324b084a53b705127dc15717b22c5e140ae0a3c38349d4efec071e", size = 6329237, upload-time = "2026-07-01T11:56:08.868Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/36/16/cf6eeaae8d0fce8dd390a33437cf68c5d5bd73834a2bc6e2f14efda0ab45/pillow-12.3.0-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf1845d02ad822a369a49f2bb9345b1614744267682e7a03527dc3bf6eea1777", size = 6997047, upload-time = "2026-07-01T11:56:11.379Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1e/69/dbf769bdd55f48bf5733cac28edc6364ffaa072ec9ba336266e4fe66be55/pillow-12.3.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:186941b6aef820ad110fb01fb06eb925374dc3a21b17e37ec9a53b250c6fe2d1", size = 6400440, upload-time = "2026-07-01T11:56:13.908Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a0/e1/ffc9cfc2eea0d178da8018e18e959301ad9d6bc9f3edb7181e748a474b97/pillow-12.3.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f13c32a3abd6079a66d9526e18dad9b6d280384d49d7c54040cd57b6424041d9", size = 7105895, upload-time = "2026-07-01T11:56:16.575Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/f0/a5595c1e8c3ae44b9828cb2f0fa8155e5095ef04d6327b8f61cf44a3df85/pillow-12.3.0-cp315-cp315t-win32.whl", hash = "sha256:1657923d2d45afb66526e5b933e5b3052e6bdea196c90d3abb2424e18c77dae8", size = 6474384, upload-time = "2026-07-01T11:56:18.855Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e4/04/62bcd9f844984c5938d3b05264a61d797a29d3e0812341a8204af70bbdee/pillow-12.3.0-cp315-cp315t-win_amd64.whl", hash = "sha256:8cd2f7bdda092d99c9fc2fb7391354f306d01443d22785d0cbfafa2e2c8bb418", size = 7243537, upload-time = "2026-07-01T11:56:21.214Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3d/68/1f3066acedf37673694a7141381d8f811ae97f30d34413d236abe7d489f1/pillow-12.3.0-cp315-cp315t-win_arm64.whl", hash = "sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59", size = 2567491, upload-time = "2026-07-01T11:56:23.506Z" },
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user