a60a18a253
Introduce ruff lint/format config, expand .gitignore, and reformat Python sources. Co-authored-by: Cursor <cursoragent@cursor.com>
116 lines
3.5 KiB
Python
116 lines
3.5 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from imagepipeline.core.context import ModuleContext
|
|
from imagepipeline.core.params import Param
|
|
from imagepipeline.modules.base import SubprocessModule
|
|
from imagepipeline.modules.registry import register
|
|
from imagepipeline.utils.subprocess import run_command
|
|
|
|
|
|
def normalize_color(color: str) -> str:
|
|
color = color.strip()
|
|
if not color:
|
|
raise ValueError("Color must not be empty")
|
|
if color.startswith("#"):
|
|
return color
|
|
if color.replace(".", "", 1).isdigit():
|
|
return color
|
|
return f"#{color}"
|
|
|
|
|
|
def build_fill_arguments(
|
|
width: int,
|
|
height: int,
|
|
*,
|
|
color1: str,
|
|
color2: str | None,
|
|
gradient: bool,
|
|
radial: bool,
|
|
angle: float | None,
|
|
) -> list[str]:
|
|
c1 = normalize_color(color1)
|
|
size = f"{width}x{height}"
|
|
|
|
if not gradient:
|
|
return ["-size", size, f"xc:{c1}"]
|
|
|
|
c2 = normalize_color(color2 or color1)
|
|
if radial:
|
|
return ["-size", size, f"radial-gradient:{c1}-{c2}"]
|
|
|
|
args = ["-size", size]
|
|
if angle is not None:
|
|
args.extend(["-define", f"gradient:angle={angle}"])
|
|
args.append(f"gradient:{c1}-{c2}")
|
|
return args
|
|
|
|
|
|
@register
|
|
class ImageMagickFillModule(SubprocessModule):
|
|
name = "imagemagick_fill"
|
|
description = "Create solid-color or gradient images sized to match each input image"
|
|
command_candidates = ("magick", "convert")
|
|
|
|
@classmethod
|
|
def parameters(cls) -> dict[str, Param]:
|
|
return {
|
|
"color1": Param(
|
|
"string",
|
|
required=True,
|
|
help="Primary color (hex, e.g. #d7fd00, or ImageMagick color name)",
|
|
),
|
|
"color2": Param(
|
|
"string",
|
|
default="",
|
|
help="Second gradient color; ignored for solid fills",
|
|
),
|
|
"gradient": Param(
|
|
"bool",
|
|
default=False,
|
|
help="Create a gradient instead of a solid fill",
|
|
),
|
|
"radial": Param(
|
|
"bool",
|
|
default=False,
|
|
help="Use a radial gradient (linear when false)",
|
|
),
|
|
"angle": Param(
|
|
"float",
|
|
default=None,
|
|
help="Linear gradient angle in degrees (ignored for radial/solid)",
|
|
),
|
|
}
|
|
|
|
def run(self, ctx: ModuleContext) -> None:
|
|
command = self.resolve_command()
|
|
color1 = ctx.params["color1"]
|
|
color2 = ctx.params["color2"] or None
|
|
gradient = ctx.params["gradient"]
|
|
radial = ctx.params["radial"]
|
|
angle = ctx.params["angle"]
|
|
ctx.output_dir.mkdir(parents=True, exist_ok=True)
|
|
total = len(ctx.input_paths)
|
|
|
|
for index, src in enumerate(ctx.input_paths, start=1):
|
|
self.log_image(ctx, index, total, src)
|
|
width, height = self._image_size(command, src)
|
|
fill_args = build_fill_arguments(
|
|
width,
|
|
height,
|
|
color1=color1,
|
|
color2=color2,
|
|
gradient=gradient,
|
|
radial=radial,
|
|
angle=angle,
|
|
)
|
|
dst = ctx.output_dir / src.name
|
|
run_command([command, *fill_args, str(dst)])
|
|
|
|
@staticmethod
|
|
def _image_size(command: str, src: Path) -> tuple[int, int]:
|
|
result = run_command([command, "-format", "%w %h", str(src), "info:"])
|
|
width, height = map(int, result.stdout.strip().split())
|
|
return width, height
|