amayer5125 is savage

This commit is contained in:
Frank Schwenk
2026-05-30 11:33:07 +02:00
commit e7cdb8dd6f
55 changed files with 4339 additions and 0 deletions
+298
View File
@@ -0,0 +1,298 @@
from __future__ import annotations
import shutil
from pathlib import Path
import pytest
from imagepipeline.core.params import validate_params
from imagepipeline.modules.composite import CompositeModule
from imagepipeline.modules.crop_square import CropSquareModule
from imagepipeline.modules.darktable_style import DarktableStyleModule
from imagepipeline.modules.imagemagick_fill import (
ImageMagickFillModule,
build_fill_arguments,
)
from imagepipeline.modules.imagemagick_grayscale import ImageMagickGrayscale
from imagepipeline.modules.gmic_grayscale import GmicGrayscale
from imagepipeline.modules.registry import get_module, list_modules
from imagepipeline.modules.rembg import RembgModule
has_magick = bool(shutil.which("magick") or shutil.which("convert"))
class TestModuleRegistration:
def test_workflow_modules_registered(self) -> None:
names = list_modules()
for name in (
"rembg",
"gmic_grayscale",
"composite",
"darktable_style",
"imagemagick_grayscale",
"imagemagick_fill",
"crop_square",
):
assert name in names
def test_get_module_returns_class(self) -> None:
assert get_module("rembg") is RembgModule
assert get_module("gmic_grayscale") is GmicGrayscale
assert get_module("composite") is CompositeModule
assert get_module("darktable_style") is DarktableStyleModule
assert get_module("crop_square") is CropSquareModule
assert get_module("imagemagick_fill") is ImageMagickFillModule
class TestImageMagickFill:
def test_solid_fill_arguments(self) -> None:
args = build_fill_arguments(
800,
600,
color1="#d7fd00",
color2=None,
gradient=False,
radial=False,
angle=None,
)
assert args == ["-size", "800x600", "xc:#d7fd00"]
def test_linear_gradient_arguments(self) -> None:
args = build_fill_arguments(
100,
50,
color1="#d7fd00",
color2="#fc0ade",
gradient=True,
radial=False,
angle=45.0,
)
assert args == [
"-size",
"100x50",
"-define",
"gradient:angle=45.0",
"gradient:#d7fd00-#fc0ade",
]
def test_radial_gradient_arguments(self) -> None:
args = build_fill_arguments(
100,
50,
color1="d7fd00",
color2="fc0ade",
gradient=True,
radial=True,
angle=90.0,
)
assert args == ["-size", "100x50", "radial-gradient:#d7fd00-#fc0ade"]
def test_requires_color1(self) -> None:
with pytest.raises(ValueError, match="required"):
ImageMagickFillModule.validate_module_params({})
@pytest.mark.skipif(not has_magick, reason="ImageMagick not installed")
def test_output_matches_input_size(self, tmp_path: Path) -> None:
from imagepipeline.core.context import ModuleContext
from imagepipeline.utils.subprocess import run_command
from tests.conftest import make_png
src = tmp_path / "ref.png"
make_png(src, width=40, height=20)
output_dir = tmp_path / "out"
output_dir.mkdir()
ctx = ModuleContext(
input_paths=[src],
matched_groups=[],
output_dir=output_dir,
params=ImageMagickFillModule.validate_module_params(
{
"color1": "#d7fd00",
"color2": "#fc0ade",
"gradient": True,
"angle": 45,
}
),
pipeline_output_root=tmp_path,
step_id="imagemagick_fill_01",
logger=None,
)
ImageMagickFillModule().run(ctx)
dst = output_dir / "ref.png"
assert dst.is_file()
magick = shutil.which("magick") or shutil.which("convert")
result = run_command([magick, "-format", "%w %h", str(dst), "info:"])
assert result.stdout.strip() == "40 20"
class TestCropSquare:
@pytest.mark.skipif(not has_magick, reason="ImageMagick not installed")
def test_center_crops_to_square(self, tmp_path: Path) -> None:
from imagepipeline.core.context import ModuleContext
from imagepipeline.utils.subprocess import run_command
from tests.conftest import make_png
src = tmp_path / "wide.png"
make_png(src, width=40, height=20)
output_dir = tmp_path / "out"
output_dir.mkdir()
ctx = ModuleContext(
input_paths=[src],
matched_groups=[],
output_dir=output_dir,
params={},
pipeline_output_root=tmp_path,
step_id="crop_square_01",
logger=None,
)
CropSquareModule().run(ctx)
dst = output_dir / "wide.png"
assert dst.is_file()
magick = shutil.which("magick") or shutil.which("convert")
result = run_command(
[magick, "-format", "%w %h", str(dst), "info:"],
)
width, height = map(int, result.stdout.strip().split())
assert width == height == 20
class TestModuleParameters:
def test_darktable_style_requires_style(self) -> None:
with pytest.raises(ValueError, match="required"):
DarktableStyleModule.validate_module_params({})
def test_darktable_style_accepts_style(self) -> None:
params = DarktableStyleModule.validate_module_params(
{"style": "Watermark F12.rocks"}
)
assert params["style"] == "Watermark F12.rocks"
assert params["style_overwrite"] is True
def test_composite_mode_choices(self) -> None:
with pytest.raises(ValueError, match="must be one of"):
CompositeModule.validate_module_params({"mode": "invalid"})
def test_rembg_defaults(self) -> None:
params = RembgModule.validate_module_params({})
assert params["model"] == "birefnet-general"
assert params["alpha_matting"] is True
def test_darktable_export_conf_jpeg(self) -> None:
from imagepipeline.modules.darktable_style import export_conf_options
assert export_conf_options("jpeg") == [
"plugins/imageio/format/jpeg/quality=90"
]
def test_darktable_export_conf_png(self) -> None:
from imagepipeline.modules.darktable_style import export_conf_options
assert export_conf_options("png") == [
"plugins/imageio/format/png/bpp=8",
"plugins/imageio/format/png/compression=5",
]
def test_darktable_resolve_format_from_input(self) -> None:
from imagepipeline.modules.darktable_style import resolve_export_format
format_name, conf = resolve_export_format(Path("photo.jpg"), "")
assert format_name == "jpeg"
assert conf == ["plugins/imageio/format/jpeg/quality=90"]
format_name, conf = resolve_export_format(Path("photo.png"), "")
assert format_name == "png"
assert "plugins/imageio/format/png/compression=5" in conf
def test_darktable_rejects_unsupported_format(self) -> None:
from imagepipeline.modules.darktable_style import resolve_export_format
with pytest.raises(ValueError, match="Unsupported output format"):
resolve_export_format(Path("photo.tiff"), "tiff")
@pytest.mark.skipif(not has_magick, reason="ImageMagick not installed")
class TestCompositeColor:
def test_preserves_color_over_grayscale_background(self, tmp_path: Path) -> None:
from imagepipeline.core.context import ModuleContext
from imagepipeline.utils.subprocess import run_command
from tests.conftest import make_png
src = tmp_path / "src.png"
make_png(src, width=40, height=40, rgb=(200, 50, 50))
background = tmp_path / "bg.png"
foreground = tmp_path / "fg.png"
output_dir = tmp_path / "composite_out"
output_dir.mkdir()
magick = shutil.which("magick") or shutil.which("convert")
run_command([magick, str(src), "-colorspace", "Gray", str(background)])
run_command(
[
magick,
"-size",
"40x40",
"xc:none",
"-fill",
"rgba(255,0,0,0.8)",
"-draw",
"circle 20,20 20,2",
str(foreground),
]
)
module = CompositeModule()
ctx = ModuleContext(
input_paths=[background, foreground],
output_dir=output_dir,
params=CompositeModule.validate_module_params({}),
pipeline_output_root=tmp_path,
step_id="composite_01",
matched_groups=[[background, foreground]],
)
module.run(ctx)
output = output_dir / "fg.png"
assert output.is_file()
result = run_command(
[magick, "identify", "-format", "%[type]", str(output)]
)
assert result.stdout.strip() != "Grayscale"
has_workflow_tools = all(
shutil.which(name)
for name in ("rembg", "gmic", "magick", "darktable-cli")
) or all(shutil.which(name) for name in ("rembg", "gmic", "convert", "darktable-cli"))
@pytest.mark.skipif(not has_workflow_tools, reason="Workflow CLI tools not installed")
class TestWorkflowIntegration:
def test_watermark_pipeline(self, input_dir, output_base) -> None:
from imagepipeline import Pipeline
with Pipeline(
name="workflow_test",
input_dir=input_dir,
output_base=output_base,
verbose=False,
) as p:
rembg_out = p.step("rembg", inputs="input")
grayscale = p.step("gmic_grayscale", inputs="input")
combined = p.step("composite", inputs=[grayscale, rembg_out])
p.step(
"darktable_style",
inputs=combined,
style="Watermark F12.rocks",
)
root = p.run()
assert (root / "rembg_01").is_dir()
assert (root / "gmic_grayscale_01").is_dir()
assert (root / "composite_01").is_dir()
assert (root / "darktable_style_01").is_dir()
assert list((root / "darktable_style_01").iterdir())