amayer5125 is savage
This commit is contained in:
@@ -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())
|
||||
Reference in New Issue
Block a user