- Removed unused model keys and functions from properties.py. - Deleted projection.py as its functions were no longer needed. - Cleaned up model_catalog.py by removing unused model directory function. - Updated imports in various test files to reflect new module structure. - Removed deprecated test cases related to image editing tools. - Added new tests for ONNX runtime provider selection logic.
229 lines
10 KiB
Python
229 lines
10 KiB
Python
import sys
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
from unittest.mock import patch
|
|
import bpy
|
|
import numpy as np
|
|
import pytest
|
|
from PIL import Image
|
|
|
|
sys.path.insert(0, str(Path(__file__).parents[1] / "src"))
|
|
|
|
from anyimage.common import image as images
|
|
from anyimage.operators.convert_to_plane.object import create_color_image
|
|
from anyimage.operators.cutout_tool.object import create_cropped_color_image
|
|
|
|
|
|
@pytest.mark.parametrize("floating", [False, True])
|
|
def test_owned_color_images_preserve_source_and_use_premul(floating):
|
|
original = bpy.data.images.new("Alpha source", width=2, height=2, alpha=True, float_buffer=floating)
|
|
rgba = np.tile(np.array([0.2, 0.4, 0.8, 0.25], dtype=np.float32), (2, 2, 1))
|
|
original.pixels.foreach_set(rgba.ravel())
|
|
source = SimpleNamespace(data=original)
|
|
expected = images.image_rgba(original)
|
|
copied = create_color_image(source)
|
|
cropped = create_cropped_color_image(source, (0, 0, 2, 2), expected)
|
|
edited = images.create_image_edit_result(original, rgba.ravel(), (2, 2))
|
|
try:
|
|
assert original.alpha_mode == "STRAIGHT"
|
|
np.testing.assert_allclose(images.image_rgba(original), expected)
|
|
for image in (copied, cropped, edited):
|
|
assert image.alpha_mode == "PREMUL"
|
|
np.testing.assert_allclose(images.image_rgba(image), expected, atol=1 / 255)
|
|
finally:
|
|
for image in (copied, cropped, edited, original):
|
|
bpy.data.images.remove(image)
|
|
|
|
|
|
@pytest.mark.parametrize("loader", [images.load_image_edit_result, images.load_color_result_image])
|
|
def test_job_color_loader_defaults_to_premul(tmp_path, loader):
|
|
rgba = np.array([[[51, 102, 204, 64]]], dtype=np.uint8)
|
|
Image.fromarray(rgba).save(tmp_path / "image.0001.png")
|
|
original = bpy.data.images.new("Job source", width=1, height=1, alpha=True)
|
|
result = loader(SimpleNamespace(data=original), tmp_path)
|
|
try:
|
|
assert result.alpha_mode == "PREMUL"
|
|
assert result.packed_file is not None
|
|
assert original.alpha_mode == "STRAIGHT"
|
|
finally:
|
|
bpy.data.images.remove(result)
|
|
bpy.data.images.remove(original)
|
|
|
|
|
|
def test_failed_empty_replacement_restores_blending_and_timing():
|
|
original = SimpleNamespace(name="Original", use_fake_user=False, users=1)
|
|
timing = SimpleNamespace(frame_start=3, frame_offset=2, frame_duration=7, use_cyclic=True)
|
|
source = SimpleNamespace(data=original, use_empty_image_alpha=False, image_user=timing)
|
|
result = SimpleNamespace(source="SEQUENCE", users=1)
|
|
|
|
def fail_configuration(*args, **kwargs):
|
|
timing.frame_start = 99
|
|
raise RuntimeError("Failed configuration")
|
|
|
|
with patch.object(images, "has_other_image_empty", return_value=True), patch.object(images, "video_total_frames", return_value=3), patch.object(
|
|
images, "configure_image_user", side_effect=fail_configuration
|
|
):
|
|
with pytest.raises(RuntimeError, match="Failed configuration"):
|
|
images.replace_empty_image(source, result)
|
|
assert source.data is original
|
|
assert not source.use_empty_image_alpha
|
|
assert not original.use_fake_user
|
|
assert images.image_user_settings(source) == (3, 2, 7, True)
|
|
|
|
|
|
def test_business_pixels_ignore_premul_read_interpretation():
|
|
original = bpy.data.images.new("Premul read source", width=1, height=1, alpha=True)
|
|
expected = np.array([0.4, 0.25, 0.1, 0.5], dtype=np.float32)
|
|
result = images.create_image_edit_result(original, expected, (1, 1))
|
|
try:
|
|
result.reload()
|
|
interpreted = np.empty(4, dtype=np.float32)
|
|
result.pixels.foreach_get(interpreted)
|
|
assert interpreted[0] > expected[0]
|
|
np.testing.assert_allclose(images.image_pixels(result), expected, atol=1 / 255)
|
|
assert result.alpha_mode == "PREMUL"
|
|
finally:
|
|
bpy.data.images.remove(result)
|
|
bpy.data.images.remove(original)
|
|
|
|
|
|
@pytest.mark.parametrize("dirty", [False, True])
|
|
def test_business_read_preserves_shared_packed_source(dirty):
|
|
original = bpy.data.images.new("Read integrity", width=1, height=1, alpha=True)
|
|
result = images.create_image_edit_result(original, np.array([0.2, 0.4, 0.8, 0.25], dtype=np.float32), (1, 1))
|
|
owner = bpy.data.objects.new("Read owner", None)
|
|
owner.empty_display_type = "IMAGE"
|
|
owner.data = result
|
|
try:
|
|
result.reload()
|
|
if dirty:
|
|
result.pixels.foreach_set([0.9, 0.1, 0.3, 0.5])
|
|
result.update()
|
|
before = np.array(result.pixels[:])
|
|
packed = bytes(result.packed_file.data)
|
|
state = (result.alpha_mode, result.is_dirty, result.users)
|
|
count = len(bpy.data.images)
|
|
for _ in range(3):
|
|
pixels = images.image_pixels(result)
|
|
np.testing.assert_allclose(pixels, before if dirty else [0.2, 0.4, 0.8, 0.25], atol=1 / 255)
|
|
np.testing.assert_array_equal(result.pixels[:], before)
|
|
assert (result.alpha_mode, result.is_dirty, result.users) == state
|
|
assert bytes(result.packed_file.data) == packed
|
|
assert owner.data == result
|
|
assert len(bpy.data.images) == count
|
|
finally:
|
|
bpy.data.objects.remove(owner)
|
|
bpy.data.images.remove(result)
|
|
bpy.data.images.remove(original)
|
|
|
|
|
|
@pytest.mark.parametrize("floating", [False, True])
|
|
def test_generated_premul_reads_current_pixels(floating):
|
|
source = bpy.data.images.new("Generated read", width=1, height=1, alpha=True, float_buffer=floating)
|
|
try:
|
|
source.alpha_mode = "PREMUL"
|
|
source.pixels.foreach_set([0.2, 0.4, 0.8, 0.25])
|
|
before = np.array(source.pixels[:])
|
|
np.testing.assert_array_equal(images.image_pixels(source), before)
|
|
np.testing.assert_array_equal(source.pixels[:], before)
|
|
assert source.alpha_mode == "PREMUL"
|
|
finally:
|
|
bpy.data.images.remove(source)
|
|
|
|
|
|
@pytest.mark.parametrize("packed", [False, True])
|
|
@pytest.mark.parametrize("floating", [False, True])
|
|
@pytest.mark.parametrize("dirty", [False, True])
|
|
def test_file_read_preserves_byte_and_native_float_cache(tmp_path, packed, floating, dirty):
|
|
path = tmp_path / ("source.exr" if floating else "source.png")
|
|
if floating:
|
|
generated = bpy.data.images.new("EXR source", width=1, height=1, alpha=True, float_buffer=True)
|
|
try:
|
|
generated.pixels.foreach_set([0.2, 0.4, 0.8, 0.25])
|
|
generated.file_format = "OPEN_EXR"
|
|
generated.filepath_raw = str(path)
|
|
generated.save()
|
|
finally:
|
|
bpy.data.images.remove(generated)
|
|
else:
|
|
Image.fromarray(np.array([[[51, 102, 204, 64]]], dtype=np.uint8)).save(path)
|
|
source = bpy.data.images.load(str(path), check_existing=False)
|
|
try:
|
|
if packed:
|
|
source.pack()
|
|
source.alpha_mode = "PREMUL"
|
|
if dirty:
|
|
source.pixels.foreach_set([0.9, 0.1, 0.3, 0.5])
|
|
source.update()
|
|
before = np.array(source.pixels[:])
|
|
assert source.is_dirty == dirty
|
|
assert source.is_float == floating
|
|
expected = before if floating or dirty else np.array([51, 102, 204, 64]) / 255
|
|
count = len(bpy.data.images)
|
|
np.testing.assert_allclose(images.image_pixels(source), expected, atol=1e-7)
|
|
np.testing.assert_array_equal(source.pixels[:], before)
|
|
assert source.is_dirty == dirty and source.alpha_mode == "PREMUL"
|
|
assert len(bpy.data.images) == count
|
|
finally:
|
|
bpy.data.images.remove(source)
|
|
|
|
|
|
def test_isolated_decode_failure_releases_copy():
|
|
class FailedReader:
|
|
@property
|
|
def alpha_mode(self):
|
|
return "PREMUL"
|
|
|
|
@alpha_mode.setter
|
|
def alpha_mode(self, value):
|
|
raise RuntimeError("Decode failed")
|
|
|
|
reader = FailedReader()
|
|
source = SimpleNamespace(alpha_mode="PREMUL", is_dirty=False, is_float=False,
|
|
source="FILE", copy=lambda: reader)
|
|
with patch.object(images, "bpy", SimpleNamespace(data=SimpleNamespace(images=SimpleNamespace(remove=lambda value: removed.append(value))))):
|
|
removed = []
|
|
with pytest.raises(RuntimeError, match="Decode failed"):
|
|
images.image_pixels(source)
|
|
assert removed == [reader]
|
|
assert source.alpha_mode == "PREMUL"
|
|
|
|
|
|
def test_mask_pack_reopen_add_and_repeat_frame_preserve_hidden_color(tmp_path):
|
|
from anyimage.common.selection import SelectionMask
|
|
from anyimage.operators.mask_tool import apply_alpha_mask
|
|
from anyimage.operators import frame_tool as frame
|
|
|
|
source = bpy.data.images.new("Roundtrip source", width=2, height=2, alpha=True)
|
|
rgba = np.tile(np.array([0.2, 0.4, 0.8, 1], dtype=np.float32), (2, 2, 1))
|
|
full = SelectionMask(np.ones((2, 2)), (0, 0, 2, 2))
|
|
result = images.create_image_edit_result(source, apply_alpha_mask(rgba, full, "SUBTRACT"), (2, 2))
|
|
result.use_fake_user = True
|
|
name = result.name
|
|
bpy.data.images.remove(source)
|
|
path = str(tmp_path / "mask.blend")
|
|
bpy.ops.wm.save_as_mainfile(filepath=path)
|
|
bpy.ops.wm.open_mainfile(filepath=path)
|
|
result = bpy.data.images[name]
|
|
try:
|
|
restored = images.image_rgba(result)
|
|
np.testing.assert_allclose(restored[..., :3], rgba[..., :3], atol=1 / 255)
|
|
np.testing.assert_array_equal(restored[..., 3], 0)
|
|
added = images.create_image_edit_result(result, apply_alpha_mask(restored, full, "ADD"), (2, 2))
|
|
bpy.data.images.remove(result)
|
|
result = added
|
|
for _ in range(2):
|
|
source = frame.frame_source_projection(np.eye(4), np.eye(4), np.eye(4), (-1, 1, -1, 1), (2, 2), perspective=False)
|
|
rgba_read = images.image_rgba(result)
|
|
source.update(rgba=rgba_read, pixels=images.premultiplied_rgba(images.image_pixels(result), (2, 2)),
|
|
image_size=(2, 2), active=True, name=result.name)
|
|
pixels = frame.composite_frame_pixels((source,), ((0, 2), (2, 2), (2, 0), (0, 0)), (2, 2))
|
|
next_result = images.create_image_edit_result(result, pixels, (2, 2))
|
|
bpy.data.images.remove(result)
|
|
result = next_result
|
|
result.reload()
|
|
assert result.alpha_mode == "PREMUL" and result.packed_file is not None
|
|
np.testing.assert_allclose(images.image_rgba(result), rgba, atol=1 / 255)
|
|
finally:
|
|
bpy.data.images.remove(result)
|