- Implemented a preview tool for rectifying images with interactive aspect controls in `preview.py`. - Added tests for depth metadata loading to ensure consistent schema usage in `test_depth_metadata.py`. - Developed comprehensive tests for cutout calibration processes in `test_cutout_calibration.py`, including stability checks and depth calibration. - Created tests for frame compositing operations in `test_frame_compositing.py`, ensuring correct handling of depth and transparency. - Introduced material slot inheritance functionality in `material_slots.py` to manage geometry material assignments. - Built shared shader nodes for material processing in `shader.py`, including roughness and bump mapping setups.
176 lines
8.5 KiB
Python
176 lines
8.5 KiB
Python
from types import SimpleNamespace
|
|
from unittest.mock import patch
|
|
import bpy
|
|
import numpy as np
|
|
import pytest
|
|
from PIL import Image
|
|
|
|
|
|
from anyimage import preferences
|
|
from anyimage.common import image as images
|
|
from anyimage.common import material
|
|
from anyimage.operators.clipboard_image import actions as clipboard
|
|
from tests.support.color_image import color_image
|
|
|
|
|
|
@pytest.fixture
|
|
def color_data():
|
|
original_images = set(bpy.data.images)
|
|
original_materials = set(bpy.data.materials)
|
|
original_objects = set(bpy.data.objects)
|
|
yield
|
|
for obj in set(bpy.data.objects) - original_objects:
|
|
bpy.data.objects.remove(obj, do_unlink=True)
|
|
for item in set(bpy.data.materials) - original_materials:
|
|
bpy.data.materials.remove(item, do_unlink=True)
|
|
for item in set(bpy.data.images) - original_images:
|
|
bpy.data.images.remove(item, do_unlink=True)
|
|
|
|
|
|
def view_scene(view):
|
|
return SimpleNamespace(view_settings=SimpleNamespace(view_transform=view))
|
|
|
|
|
|
def color_texture(result):
|
|
return next(node.image for node in result.node_tree.nodes if node.type == "TEX_IMAGE")
|
|
|
|
|
|
@pytest.mark.parametrize("enabled", [False, True])
|
|
@pytest.mark.parametrize("view,space", [*material.VIEW_TRANSFORM_COLOR_SPACES, ("Standard", "sRGB"), ("Unknown", "sRGB")])
|
|
def test_color_space_and_alpha_follow_preference(color_data, enabled, view, space):
|
|
image = bpy.data.images.new("Material policy", width=2, height=1, alpha=True)
|
|
expected = np.array([0.2, 0.4, 0.8, 0.25, 0.6, 0.3, 0.1, 0], dtype=np.float32)
|
|
image.pixels.foreach_set(expected)
|
|
settings = SimpleNamespace(adapt_material_to_view_transform=enabled)
|
|
# The installed bpy may not include newer view transforms such as ACES 2.0.
|
|
try:
|
|
image.colorspace_settings.name = space
|
|
except TypeError:
|
|
space = "sRGB"
|
|
image.colorspace_settings.name = "sRGB"
|
|
image.pixels.foreach_set(expected)
|
|
with patch.object(preferences, "addon_preferences", return_value=settings):
|
|
result = material.configure_material_color_image(image, view_scene(view))
|
|
selected = space if enabled else "sRGB"
|
|
assert result.colorspace_settings.name == selected
|
|
assert result.alpha_mode == ("PREMUL" if selected == "sRGB" else "STRAIGHT")
|
|
assert settings.adapt_material_to_view_transform == enabled
|
|
np.testing.assert_allclose(images.image_pixels(result), expected, atol=1 / 255)
|
|
|
|
|
|
@pytest.mark.parametrize("enabled", [False, True])
|
|
@pytest.mark.parametrize("kind", [
|
|
"byte", "packed", "file", "dirty",
|
|
"float",
|
|
])
|
|
def test_material_pixels_survive_pack_and_library_reload(color_data, tmp_path, enabled, kind):
|
|
expected = np.array([[[51, 102, 204, 64], [153, 76, 25, 0]]], dtype=np.uint8)
|
|
if kind in {"file", "dirty"}:
|
|
path = tmp_path / "source.png"
|
|
Image.fromarray(expected).save(path)
|
|
source = bpy.data.images.load(str(path), check_existing=False)
|
|
if kind == "dirty":
|
|
source.pixels.foreach_set((expected.astype(np.float32) / 255).ravel())
|
|
else:
|
|
source = bpy.data.images.new("Material pixels", width=2, height=1, alpha=True, float_buffer=kind == "float")
|
|
source.alpha_mode = "PREMUL"
|
|
source.pixels.foreach_set((expected.astype(np.float32) / 255).ravel())
|
|
if kind == "packed":
|
|
source.pack()
|
|
source.reload()
|
|
before = images.image_pixels(source)
|
|
owner = bpy.data.objects.new("Color source owner", None)
|
|
owner.empty_display_type = "IMAGE"
|
|
owner.data = source
|
|
with patch.object(material, "configured_material_view_adaptation", return_value=enabled):
|
|
result = material.configure_material_color_image(source, view_scene("AgX"))
|
|
assert result == source
|
|
np.testing.assert_allclose(images.image_pixels(source), before, atol=1 / 255)
|
|
np.testing.assert_allclose(images.image_pixels(result), before, atol=1 / 255)
|
|
if result.packed_file is None or result.is_dirty:
|
|
result.pack()
|
|
selected = (result.colorspace_settings.name, result.alpha_mode)
|
|
path = tmp_path / "material.blend"
|
|
bpy.data.libraries.write(str(path), {result})
|
|
with bpy.data.libraries.load(str(path)) as (available, loaded):
|
|
loaded.images = available.images
|
|
restored = loaded.images[0]
|
|
assert (restored.colorspace_settings.name, restored.alpha_mode) == selected
|
|
np.testing.assert_allclose(images.image_pixels(restored), before, atol=1 / 255)
|
|
edited = images.create_image_edit_result(restored, images.image_pixels(restored), (2, 1))
|
|
assert edited.alpha_mode == selected[1]
|
|
np.testing.assert_allclose(images.image_pixels(edited), before, atol=1 / 255)
|
|
|
|
|
|
@pytest.mark.parametrize("shadeless", [False, True])
|
|
@pytest.mark.parametrize("entry", ["plane", "cutout", "depth", "clipboard"])
|
|
def test_material_configures_the_image_selected_by_its_caller(color_data, entry, shadeless):
|
|
source = bpy.data.images.new("Shared source", width=2, height=2, alpha=True)
|
|
source.pixels.foreach_set(np.tile(np.array([0.2, 0.4, 0.8, 0.25], dtype=np.float32), 4))
|
|
owner = bpy.data.objects.new("Shared Empty", None)
|
|
owner.empty_display_type = "IMAGE"
|
|
owner.data = source
|
|
before = images.image_pixels(source)
|
|
scene = view_scene("AgX")
|
|
settings = SimpleNamespace(adapt_material_to_view_transform=True)
|
|
data_textures = {}
|
|
if entry == "depth":
|
|
for key in ("displacement_image", "normal_image"):
|
|
image = bpy.data.images.new(key, width=2, height=2, float_buffer=True)
|
|
image.colorspace_settings.name = "Non-Color"
|
|
data_textures[key] = image
|
|
data_states = {
|
|
key: (image.colorspace_settings.name, image.alpha_mode, images.image_pixels(image))
|
|
for key, image in data_textures.items()
|
|
}
|
|
with patch.object(preferences, "addon_preferences", return_value=settings):
|
|
if entry == "clipboard":
|
|
first = clipboard.create_image_material(source, source, shadeless=shadeless, scene=scene)
|
|
else:
|
|
if entry == "cutout":
|
|
color = color_image(owner.data, (0, 0, 2, 2), images.image_rgba(source))
|
|
else:
|
|
color = source
|
|
first = material.create_image_material(owner.data, color, 0, shadeless=shadeless, scene=scene, **data_textures)
|
|
original_color = color_texture(first)
|
|
assert original_color.colorspace_settings.name == "AgX Base sRGB"
|
|
assert original_color.alpha_mode == "STRAIGHT"
|
|
settings.adapt_material_to_view_transform = False
|
|
assert original_color.alpha_mode == "STRAIGHT"
|
|
if entry == "clipboard":
|
|
second = clipboard.create_image_material(original_color, original_color, shadeless=shadeless, scene=scene)
|
|
else:
|
|
second = material.create_image_material(owner.data, original_color, 0, shadeless=shadeless, scene=scene)
|
|
new_color = color_texture(second)
|
|
assert new_color == original_color
|
|
assert new_color.colorspace_settings.name == "sRGB"
|
|
assert new_color.alpha_mode == "PREMUL"
|
|
assert original_color.colorspace_settings.name == "sRGB"
|
|
assert original_color.alpha_mode == "PREMUL"
|
|
assert not settings.adapt_material_to_view_transform
|
|
assert owner.data == source
|
|
assert source.colorspace_settings.name == "sRGB"
|
|
assert source.alpha_mode == ("STRAIGHT" if entry == "cutout" else "PREMUL")
|
|
for image in (source, original_color, new_color):
|
|
np.testing.assert_allclose(images.image_pixels(image), before, atol=1 / 255)
|
|
for key, image in data_textures.items():
|
|
space, alpha, pixels = data_states[key]
|
|
assert image.colorspace_settings.name == space
|
|
assert image.alpha_mode == alpha
|
|
np.testing.assert_array_equal(images.image_pixels(image), pixels)
|
|
|
|
|
|
def test_edit_results_preserve_source_alpha_with_adaptation_enabled(color_data, tmp_path):
|
|
source = bpy.data.images.new("Editing source", width=1, height=1, alpha=True)
|
|
rgba = np.array([0.2, 0.4, 0.8, 0.25], dtype=np.float32)
|
|
Image.fromarray(np.array([[[51, 102, 204, 64]]], dtype=np.uint8)).save(tmp_path / "foreground.png")
|
|
with patch.object(preferences, "addon_preferences", return_value=SimpleNamespace(adapt_material_to_view_transform=True)):
|
|
edited = images.create_image_edit_result(source, rgba, (1, 1))
|
|
loaded = images.load_image_edit_result(source, tmp_path / "foreground.png")
|
|
for result in (edited, loaded):
|
|
assert result.colorspace_settings.name == "sRGB"
|
|
assert result.alpha_mode == source.alpha_mode
|
|
np.testing.assert_allclose(images.image_pixels(result), rgba, atol=1 / 255)
|
|
assert source.colorspace_settings.name == "sRGB"
|
|
assert source.alpha_mode == "STRAIGHT"
|