- 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.
253 lines
10 KiB
Python
253 lines
10 KiB
Python
import sys
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
import bpy
|
|
import numpy as np
|
|
from mathutils import Matrix
|
|
|
|
SOURCE_ROOT = Path(__file__).parents[1] / "src"
|
|
|
|
sys.path.insert(0, str(SOURCE_ROOT))
|
|
|
|
|
|
from anyimage.common import image as image_data
|
|
from anyimage.common.selection import SelectionMask
|
|
from anyimage.operators import mask_tool as edit_mask
|
|
from anyimage.operators import frame_tool as edit_frame
|
|
|
|
|
|
def test_image_edit_result_uses_complete_shared_source_name():
|
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
|
source_image = bpy.data.images.new("Poster.png", width=2, height=2, alpha=True)
|
|
source = bpy.data.objects.new("Poster", None)
|
|
source.empty_display_type = "IMAGE"
|
|
source.data = source_image
|
|
shared = bpy.data.objects.new("Poster Shared", None)
|
|
shared.empty_display_type = "IMAGE"
|
|
shared.data = source_image
|
|
bpy.context.collection.objects.link(source)
|
|
bpy.context.collection.objects.link(shared)
|
|
|
|
result_image = image_data.create_image_edit_result(
|
|
source_image,
|
|
np.zeros(4 * 4 * 4, dtype=np.float32),
|
|
(4, 4),
|
|
)
|
|
result_image = image_data.replace_empty_image(source, result_image)
|
|
|
|
assert shared.data == source_image
|
|
assert source_image.alpha_mode == "STRAIGHT"
|
|
assert result_image.alpha_mode == "PREMUL"
|
|
assert source.use_empty_image_alpha
|
|
assert source.data == result_image
|
|
assert result_image.name == "Poster.png.001"
|
|
assert "_color" not in result_image.name
|
|
|
|
|
|
def test_image_edit_result_recovers_unshared_source_name():
|
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
|
source_image = bpy.data.images.new("Poster.png", width=2, height=2, alpha=True)
|
|
source = bpy.data.objects.new("Poster", None)
|
|
source.empty_display_type = "IMAGE"
|
|
source.data = source_image
|
|
bpy.context.collection.objects.link(source)
|
|
|
|
result_image = image_data.create_image_edit_result(
|
|
source_image,
|
|
np.zeros(4 * 4 * 4, dtype=np.float32),
|
|
(4, 4),
|
|
)
|
|
result_image = image_data.replace_empty_image(source, result_image)
|
|
|
|
assert source.data == result_image
|
|
assert result_image.name == "Poster.png"
|
|
assert bpy.data.images.get("Poster.png") == result_image
|
|
|
|
|
|
def test_mask_frame_mask_add_preserves_packed_hidden_rgb():
|
|
from io import BytesIO
|
|
from PIL import Image
|
|
|
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
|
source_image = bpy.data.images.new("Frame RGB", width=4, height=4, alpha=True)
|
|
rgba = np.ones((4, 4, 4), dtype=np.float32)
|
|
rgba[..., :3] = np.arange(48).reshape((4, 4, 3)) / 47.0
|
|
source_image.pixels.foreach_set(np.flipud(rgba).ravel())
|
|
source_image.update()
|
|
expected = image_data.image_rgba(source_image).copy()
|
|
source = bpy.data.objects.new("Frame RGB", None)
|
|
source.empty_display_type = "IMAGE"
|
|
source.empty_display_size = 2.0
|
|
source.empty_image_offset = (-0.5, -0.5)
|
|
source.data = source_image
|
|
bpy.context.collection.objects.link(source)
|
|
selection = SelectionMask(np.ones((4, 4), dtype=np.float32), (0, 0, 4, 4))
|
|
operator = SimpleNamespace(mode="SUBTRACT", report=lambda *_args: None)
|
|
assert edit_mask.EditImageAlpha._apply_mask(
|
|
operator, SimpleNamespace(), source, selection
|
|
) == {"FINISHED"}
|
|
descriptor = edit_frame._frame_source(
|
|
source, SimpleNamespace(width=4, height=4),
|
|
SimpleNamespace(perspective_matrix=Matrix.Identity(4), is_perspective=False),
|
|
Matrix.Identity(4), active=True,
|
|
)
|
|
np.testing.assert_allclose(descriptor["rgba"][..., :3], expected[..., :3])
|
|
np.testing.assert_array_equal(descriptor["pixels"], 0)
|
|
framed = edit_frame.composite_frame_pixels(
|
|
(descriptor,), ((0, 4), (4, 4), (4, 0), (0, 0)), (4, 4)
|
|
)
|
|
result = image_data.create_image_edit_result(source.data, framed, (4, 4))
|
|
result = image_data.replace_empty_image(source, result)
|
|
assert result.alpha_mode == "PREMUL"
|
|
assert source.use_empty_image_alpha
|
|
assert result.packed_file is not None
|
|
packed = np.asarray(Image.open(BytesIO(result.packed_file.data)).convert("RGBA")) / 255.0
|
|
np.testing.assert_allclose(packed[..., :3], expected[..., :3], atol=1 / 255)
|
|
np.testing.assert_array_equal(packed[..., 3], 0)
|
|
result.reload()
|
|
assert result.alpha_mode == "PREMUL"
|
|
operator.mode = "ADD"
|
|
assert edit_mask.EditImageAlpha._apply_mask(
|
|
operator, SimpleNamespace(), source, selection
|
|
) == {"FINISHED"}
|
|
restored = image_data.image_rgba(source.data)
|
|
np.testing.assert_allclose(restored[..., :3], expected[..., :3], atol=1 / 255)
|
|
np.testing.assert_array_equal(restored[..., 3], 1)
|
|
|
|
|
|
def test_frame_restored_alpha_keeps_foreground_blended_into_white():
|
|
from io import BytesIO
|
|
from PIL import Image
|
|
|
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
|
sources = []
|
|
for name, color, depth in (("Background", (1, 1, 1, 0), 2),
|
|
("Foreground", (0.2, 0.4, 0.6, 1), 1)):
|
|
image = bpy.data.images.new(name, width=6, height=2, alpha=True)
|
|
rgba = np.tile(color, (2, 6, 1)).astype(np.float32)
|
|
if name == "Foreground":
|
|
rgba[..., 3] = (0, 0.01, 0.1, 0.25, 0.5, 1)
|
|
rgba[:, 0, :3] = 0
|
|
image.pixels.foreach_set(np.flipud(rgba).ravel())
|
|
image.update()
|
|
source = bpy.data.objects.new(name, None)
|
|
source.empty_display_type = "IMAGE"
|
|
source.empty_display_size = 2
|
|
source.empty_image_offset = (-0.5, -0.5)
|
|
source.data = image
|
|
source.matrix_world = Matrix.Translation((0, 0, -depth))
|
|
bpy.context.collection.objects.link(source)
|
|
sources.append(edit_frame._frame_source(
|
|
source, SimpleNamespace(width=6, height=2),
|
|
SimpleNamespace(perspective_matrix=Matrix.Identity(4), is_perspective=False),
|
|
Matrix.Identity(4), active=name == "Foreground",
|
|
))
|
|
front = sources[1]
|
|
source_alpha = front["rgba"][..., 3:4]
|
|
expected = front["rgba"][..., :3] * source_alpha + 1 - source_alpha
|
|
# The image aspect makes its orthographic screen projection two thirds of a pixel high.
|
|
pixels = edit_frame.composite_frame_pixels(
|
|
sources, ((0, 4 / 3), (6, 4 / 3), (6, 2 / 3), (0, 2 / 3)), (6, 2)
|
|
)
|
|
result = image_data.create_image_edit_result(front["object"].data, pixels, (6, 2))
|
|
result = image_data.replace_empty_image(front["object"], result)
|
|
encoded = np.asarray(Image.open(BytesIO(result.packed_file.data)).convert("RGBA")) / 255
|
|
np.testing.assert_allclose(encoded[..., :3], expected, atol=1 / 255)
|
|
np.testing.assert_allclose(encoded[..., 3:4], source_alpha, atol=1 / 255)
|
|
selection = SelectionMask(np.ones((2, 6), dtype=np.float32), (0, 0, 6, 2))
|
|
operator = SimpleNamespace(mode="ADD", report=lambda *_args: None)
|
|
assert edit_mask.EditImageAlpha._apply_mask(
|
|
operator, SimpleNamespace(), front["object"], selection
|
|
) == {"FINISHED"}
|
|
restored = image_data.image_rgba(front["object"].data)
|
|
np.testing.assert_allclose(restored[..., :3], expected, atol=1 / 255)
|
|
np.testing.assert_array_equal(restored[..., 3], 1)
|
|
assert np.all(np.diff(restored[0, :, 0]) <= 0)
|
|
np.testing.assert_array_equal(restored[0, 0, :3], 1)
|
|
|
|
|
|
def test_brush_release_commits_one_packed_image_without_touching_shared_source():
|
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
|
source_image = bpy.data.images.new("Mask Source", width=8, height=6, alpha=True)
|
|
source_rgba = np.ones((6, 8, 4), dtype=np.float32)
|
|
source_rgba[:, :, :3] = (0.2, 0.4, 0.6)
|
|
source_image.pixels.foreach_set(np.flipud(source_rgba).ravel())
|
|
source_image.update()
|
|
source = bpy.data.objects.new("Mask Source", None)
|
|
source.empty_display_type = "IMAGE"
|
|
source.data = source_image
|
|
source.empty_display_size = 3.0
|
|
source.empty_image_offset = (-0.25, -0.75)
|
|
source.matrix_world = Matrix.Translation((1.0, 2.0, 3.0))
|
|
shared = bpy.data.objects.new("Mask Shared", None)
|
|
shared.empty_display_type = "IMAGE"
|
|
shared.data = source_image
|
|
bpy.context.collection.objects.link(source)
|
|
bpy.context.collection.objects.link(shared)
|
|
matrix = source.matrix_world.copy()
|
|
|
|
selection_mask = SelectionMask(
|
|
np.ones((2, 3), dtype=np.float32),
|
|
(2, 1, 5, 3),
|
|
)
|
|
operator = SimpleNamespace(mode="SUBTRACT", report=lambda *_args: None)
|
|
status = edit_mask.EditImageAlpha._apply_mask(
|
|
operator,
|
|
SimpleNamespace(),
|
|
source,
|
|
selection_mask,
|
|
)
|
|
|
|
result = source.data
|
|
assert status == {"FINISHED"}
|
|
assert source.data == result
|
|
assert result != source_image
|
|
assert result.packed_file is not None
|
|
assert shared.data == source_image
|
|
np.testing.assert_allclose(image_data.image_rgba(source_image), source_rgba)
|
|
result_rgba = image_data.image_rgba(result)
|
|
np.testing.assert_array_equal(result_rgba[1:3, 2:5, 3], 0.0)
|
|
assert source.matrix_world == matrix
|
|
assert source.empty_display_size == 3.0
|
|
assert tuple(source.empty_image_offset) == (-0.25, -0.75)
|
|
|
|
|
|
def test_brush_drag_does_not_create_or_update_an_image():
|
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
|
source_image = bpy.data.images.new("Mask Cancel", width=4, height=3, alpha=True)
|
|
source = bpy.data.objects.new("Mask Cancel", None)
|
|
source.empty_display_type = "IMAGE"
|
|
source.data = source_image
|
|
bpy.context.collection.objects.link(source)
|
|
image_count = len(bpy.data.images)
|
|
shared = bpy.data.objects.new("Mask Cancel shared", None)
|
|
shared.empty_display_type = "IMAGE"
|
|
shared.data = source_image
|
|
before = np.array(source_image.pixels[:])
|
|
operator = edit_mask.ImageGesture()
|
|
operator.active_tool_id = "anyimage.mask"
|
|
operator.gesture = "BRUSH"
|
|
operator._path = [(10.0, 10.0)]
|
|
operator._path_samples = list(operator._path)
|
|
operator._cursor = (10.0, 10.0)
|
|
context = SimpleNamespace(area=SimpleNamespace(tag_redraw=lambda: None),
|
|
workspace=SimpleNamespace(status_text_set=lambda value: None))
|
|
event = SimpleNamespace(
|
|
type="MOUSEMOVE",
|
|
value="NOTHING",
|
|
mouse_region_x=20,
|
|
mouse_region_y=25,
|
|
)
|
|
|
|
result = edit_mask.ImageGesture.modal(operator, context, event)
|
|
|
|
assert result == {"RUNNING_MODAL"}
|
|
assert source.data == source_image
|
|
assert len(bpy.data.images) == image_count
|
|
event.type, event.value = "ESC", "PRESS"
|
|
assert operator.modal(context, event) == {"CANCELLED"}
|
|
assert source.data == shared.data == source_image
|
|
np.testing.assert_array_equal(source_image.pixels[:], before)
|
|
assert len(bpy.data.images) == image_count
|
|
assert operator._path is operator._path_samples is operator._brush_preview is None
|