diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index 20a7e2e000a..55c727673c7 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -933,6 +933,49 @@ if(WITH_CODEC_FFMPEG) ) endif() +if(NOT OPENIMAGEIO_IDIFF) + message(STATUS "Disabling ImBuf image format tests because OIIO idiff does not exist") +else() + SET(OPTIONAL_FORMATS "") + if(WITH_IMAGE_CINEON) + set(OPTIONAL_FORMATS "${OPTIONAL_FORMATS} CINEON") + endif() + if(WITH_IMAGE_HDR) + set(OPTIONAL_FORMATS "${OPTIONAL_FORMATS} HDR") + endif() + if(WITH_IMAGE_OPENEXR) + set(OPTIONAL_FORMATS "${OPTIONAL_FORMATS} OPENEXR") + endif() + if(WITH_IMAGE_OPENJPEG) + set(OPTIONAL_FORMATS "${OPTIONAL_FORMATS} OPENJPEG") + endif() + if(WITH_IMAGE_TIFF) + set(OPTIONAL_FORMATS "${OPTIONAL_FORMATS} TIFF") + endif() + if(WITH_IMAGE_WEBP) + set(OPTIONAL_FORMATS "${OPTIONAL_FORMATS} WEBP") + endif() + + add_blender_test( + bf_imbuf_save + --python ${CMAKE_CURRENT_LIST_DIR}/bl_imbuf_save.py + -- + -test_dir "${TEST_SRC_DIR}/imbuf_io" + -output_dir "${TEST_OUT_DIR}/imbuf_io/save" + -idiff "${OPENIMAGEIO_IDIFF}" + -optional_formats "${OPTIONAL_FORMATS}" + ) + + add_blender_test( + bf_imbuf_load + --python ${CMAKE_CURRENT_LIST_DIR}/bl_imbuf_load.py + -- + -test_dir "${TEST_SRC_DIR}/imbuf_io" + -output_dir "${TEST_OUT_DIR}/imbuf_io/load" + -idiff "${OPENIMAGEIO_IDIFF}" + -optional_formats "${OPTIONAL_FORMATS}" + ) +endif() # ------------------------------------------------------------------------------ # SEQUENCER RENDER TESTS diff --git a/tests/python/bl_imbuf_load.py b/tests/python/bl_imbuf_load.py new file mode 100644 index 00000000000..5e0559aaef5 --- /dev/null +++ b/tests/python/bl_imbuf_load.py @@ -0,0 +1,179 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +import os +import pathlib +import sys +import unittest + +import bpy + +sys.path.append(str(pathlib.Path(__file__).parent.absolute())) +from modules.colored_print import print_message +from modules.imbuf_test import AbstractImBufTest + + +args = None + + +class ImBufTest(AbstractImBufTest): + @classmethod + def setUpClass(cls): + AbstractImBufTest.init(args) + + if cls.update: + os.makedirs(cls.reference_load_dir, exist_ok=True) + + def _get_image_files(self, file_pattern): + return [f for f in pathlib.Path(self.reference_dir).glob(file_pattern)] + + def _validate_metadata(self, img, ref_metadata_path, out_metadata_path): + channels = img.channels + is_float = img.is_float + colorspace = img.colorspace_settings.name + alpha_mode = img.alpha_mode + actual_metadata = f"{channels=} {is_float=} {colorspace=} {alpha_mode=}" + + # Save actual metadata + out_metadata_path.write_text(actual_metadata, encoding="utf-8") + + if ref_metadata_path.exists(): + # Compare with expected + try: + expected_metadata = ref_metadata_path.read_text(encoding="utf-8") + + failed = not (actual_metadata == expected_metadata) + except BaseException as e: + if self.verbose: + print_message(e.output.decode("utf-8", 'ignore')) + failed = True + else: + if not self.update: + return False + + failed = True + + if failed and self.update: + # Update reference if requested. + ref_metadata_path.write_text(actual_metadata, encoding="utf-8") + failed = False + + return not failed + + def _save_exr(self, img, out_exr_path): + scene = bpy.data.scenes[0] + image_settings = scene.render.image_settings + image_settings.file_format = "OPEN_EXR" + image_settings.color_mode = "RGBA" + image_settings.color_depth = "32" + image_settings.exr_codec = "ZIP" + + img.save_render(str(out_exr_path), scene=scene) + + def _validate_pixels(self, img, ref_exr_path, out_exr_path): + self._save_exr(img, out_exr_path) + + return self.call_idiff(ref_exr_path, out_exr_path) + + def check(self, file_pattern): + image_files = self._get_image_files(file_pattern) + if len(image_files) == 0: + self.fail(f"No images found for pattern {file_pattern}") + + for image_path in image_files: + print_message(image_path.name, 'SUCCESS', 'RUN') + + # Load the image under test + bpy.ops.image.open(filepath=str(image_path)) + img = bpy.data.images[image_path.name] + + # Compare the image with our exr/metadata references + exr_filename = image_path.with_suffix(".exr").name + metadata_filename = image_path.with_suffix(".txt").name + + ref_exr_path = self.reference_load_dir.joinpath(exr_filename) + ref_metadata_path = self.reference_load_dir.joinpath(metadata_filename) + out_exr_path = self.output_dir.joinpath(exr_filename) + out_metadata_path = self.output_dir.joinpath(metadata_filename) + + res1 = self._validate_metadata(img, ref_metadata_path, out_metadata_path) + res2 = self._validate_pixels(img, ref_exr_path, out_exr_path) + + if not res1 or not res2: + self.errors += 1 + print_message("Results are different from reference data") + print_message(image_path.name, 'FAILURE', 'FAILED') + else: + print_message(image_path.name, 'SUCCESS', 'OK') + + +class ImBufLoadTest(ImBufTest): + def test_load_bmp(self): + self.check("*.bmp") + + def test_load_png(self): + self.check("*.png") + + def test_load_exr(self): + self.skip_if_format_missing("OPENEXR") + + self.check("*.exr") + + def test_load_hdr(self): + self.skip_if_format_missing("HDR") + + self.check("*.hdr") + + def test_load_targa(self): + self.check("*.tga") + + def test_load_tiff(self): + self.skip_if_format_missing("TIFF") + + self.check("*.tif") + + def test_load_jpeg(self): + self.check("*.jpg") + + def test_load_jpeg2000(self): + self.skip_if_format_missing("OPENJPEG") + + self.check("*.jp2") + self.check("*.j2c") + + def test_load_dpx(self): + self.skip_if_format_missing("CINEON") + + self.check("*.dpx") + + def test_load_cineon(self): + self.skip_if_format_missing("CINEON") + + self.check("*.cin") + + def test_load_webp(self): + self.skip_if_format_missing("WEBP") + + self.check("*.webp") + + +def main(): + global args + import argparse + + if '--' in sys.argv: + argv = [sys.argv[0]] + sys.argv[sys.argv.index('--') + 1:] + else: + argv = sys.argv + + parser = argparse.ArgumentParser() + parser.add_argument('-test_dir', required=True, type=pathlib.Path) + parser.add_argument('-output_dir', required=True, type=pathlib.Path) + parser.add_argument('-idiff', required=True, type=pathlib.Path) + parser.add_argument('-optional_formats', required=True) + args, remaining = parser.parse_known_args(argv) + + unittest.main(argv=remaining) + + +if __name__ == '__main__': + main() diff --git a/tests/python/bl_imbuf_save.py b/tests/python/bl_imbuf_save.py new file mode 100644 index 00000000000..6ae0bfa75e3 --- /dev/null +++ b/tests/python/bl_imbuf_save.py @@ -0,0 +1,272 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +import os +import pathlib +import sys +import unittest + +import bpy + +sys.path.append(str(pathlib.Path(__file__).parent.absolute())) +from modules.colored_print import print_message +from modules.imbuf_test import AbstractImBufTest + + +args = None + +TEMPLATE_RGBA08 = "template-rgba08.png" +TEMPLATE_RGBA32 = "template-rgba32.exr" + + +class ImBufTest(AbstractImBufTest): + @classmethod + def setUpClass(cls): + AbstractImBufTest.init(args) + + if cls.update: + os.makedirs(cls.reference_dir, exist_ok=True) + + def _load_template_image(self, name, template_name): + image_path = str(self.test_dir.joinpath(template_name)) + bpy.ops.image.open(filepath=image_path) + img = bpy.data.images[template_name] + img.name = name + return img + + def _setup_image(self, src, ext, settings): + scene = bpy.data.scenes[0] + image_settings = scene.render.image_settings + + # Make an appropriate filename which embeds all relevant settings and + # set the file output parameters for the exact configuration we want + name = "" + for s in settings: + if s == "color_depth": + name += str(settings[s]).rjust(2, '0') + "-" + else: + name += str(settings[s]) + "-" + + setattr(image_settings, s, settings[s]) + + image_name = name[:-1].lower() + "__from__" + src + "." + ext + + return image_name + + def _save_image(self, src, image_name): + loaders = { + "rgba08": lambda name: self._load_template_image(name, TEMPLATE_RGBA08), + "rgba32": lambda name: self._load_template_image(name, TEMPLATE_RGBA32), + } + + # Load the template image and assign it the image name + img = loaders[src](image_name) + + # Save the image in the desired format with the desired settings + scene = bpy.data.scenes[0] + ref_image_path = self.reference_dir.joinpath(img.name) + out_image_path = self.output_dir.joinpath(img.name) + img.save_render(str(out_image_path), scene=scene) + + # Completely remove image in case it was modified during save + img.user_clear() + bpy.data.images.remove(img) + return ref_image_path, out_image_path + + def _validate(self, ref_image_path, out_image_path): + return self.call_idiff(ref_image_path, out_image_path) + + def check(self, src, ext, settings): + image_name = self._setup_image(src, ext, settings) + print_message(image_name, 'SUCCESS', 'RUN') + + ref_image_path, out_image_path = self._save_image(src, image_name) + + if not self._validate(ref_image_path, out_image_path): + self.errors += 1 + print_message("Save result is different from reference image") + print_message(ref_image_path.name, 'FAILURE', 'FAILED') + else: + print_message(ref_image_path.name, 'SUCCESS', 'OK') + + +# autopep8: off +class ImBufSaveTest(ImBufTest): + def test_save_bmp(self): + self.check(src="rgba08", ext="bmp", settings={"file_format": "BMP", "color_mode": "BW"}) + self.check(src="rgba08", ext="bmp", settings={"file_format": "BMP", "color_mode": "RGB"}) + + self.check(src="rgba32", ext="bmp", settings={"file_format": "BMP", "color_mode": "BW"}) + self.check(src="rgba32", ext="bmp", settings={"file_format": "BMP", "color_mode": "RGB"}) + + def test_save_png(self): + self.check(src="rgba08", ext="png", settings={"file_format": "PNG", "color_mode": "BW", "color_depth": "8", "compression": 15}) + self.check(src="rgba08", ext="png", settings={"file_format": "PNG", "color_mode": "RGB", "color_depth": "8", "compression": 15}) + self.check(src="rgba08", ext="png", settings={"file_format": "PNG", "color_mode": "RGBA", "color_depth": "8", "compression": 15}) + self.check(src="rgba08", ext="png", settings={"file_format": "PNG", "color_mode": "BW", "color_depth": "16", "compression": 25}) + self.check(src="rgba08", ext="png", settings={"file_format": "PNG", "color_mode": "RGB", "color_depth": "16", "compression": 25}) + self.check(src="rgba08", ext="png", settings={"file_format": "PNG", "color_mode": "RGBA", "color_depth": "16", "compression": 25}) + + self.check(src="rgba32", ext="png", settings={"file_format": "PNG", "color_mode": "BW", "color_depth": "8", "compression": 15}) + self.check(src="rgba32", ext="png", settings={"file_format": "PNG", "color_mode": "RGB", "color_depth": "8", "compression": 15}) + self.check(src="rgba32", ext="png", settings={"file_format": "PNG", "color_mode": "RGBA", "color_depth": "8", "compression": 15}) + self.check(src="rgba32", ext="png", settings={"file_format": "PNG", "color_mode": "BW", "color_depth": "16", "compression": 25}) + self.check(src="rgba32", ext="png", settings={"file_format": "PNG", "color_mode": "RGB", "color_depth": "16", "compression": 25}) + self.check(src="rgba32", ext="png", settings={"file_format": "PNG", "color_mode": "RGBA", "color_depth": "16", "compression": 25}) + + def test_save_exr(self): + self.skip_if_format_missing("OPENEXR") + + self.check(src="rgba08", ext="exr", settings={"file_format": "OPEN_EXR", "color_mode": "BW", "color_depth": "16", "exr_codec": "ZIP"}) + self.check(src="rgba08", ext="exr", settings={"file_format": "OPEN_EXR", "color_mode": "RGB", "color_depth": "16", "exr_codec": "DWAA"}) + self.check(src="rgba08", ext="exr", settings={"file_format": "OPEN_EXR", "color_mode": "RGBA", "color_depth": "16", "exr_codec": "DWAB"}) + self.check(src="rgba08", ext="exr", settings={"file_format": "OPEN_EXR", "color_mode": "BW", "color_depth": "32", "exr_codec": "DWAB"}) + self.check(src="rgba08", ext="exr", settings={"file_format": "OPEN_EXR", "color_mode": "RGB", "color_depth": "32", "exr_codec": "DWAA"}) + self.check(src="rgba08", ext="exr", settings={"file_format": "OPEN_EXR", "color_mode": "RGBA", "color_depth": "32", "exr_codec": "ZIP"}) + + self.check(src="rgba32", ext="exr", settings={"file_format": "OPEN_EXR", "color_mode": "BW", "color_depth": "16", "exr_codec": "ZIP"}) + self.check(src="rgba32", ext="exr", settings={"file_format": "OPEN_EXR", "color_mode": "RGB", "color_depth": "16", "exr_codec": "DWAA"}) + self.check(src="rgba32", ext="exr", settings={"file_format": "OPEN_EXR", "color_mode": "RGBA", "color_depth": "16", "exr_codec": "DWAB"}) + self.check(src="rgba32", ext="exr", settings={"file_format": "OPEN_EXR", "color_mode": "BW", "color_depth": "32", "exr_codec": "DWAB"}) + self.check(src="rgba32", ext="exr", settings={"file_format": "OPEN_EXR", "color_mode": "RGB", "color_depth": "32", "exr_codec": "DWAA"}) + self.check(src="rgba32", ext="exr", settings={"file_format": "OPEN_EXR", "color_mode": "RGBA", "color_depth": "32", "exr_codec": "ZIP"}) + + def test_save_hdr(self): + self.skip_if_format_missing("HDR") + + self.check(src="rgba08", ext="hdr", settings={"file_format": "HDR", "color_mode": "BW"}) + self.check(src="rgba08", ext="hdr", settings={"file_format": "HDR", "color_mode": "RGB"}) + + self.check(src="rgba32", ext="hdr", settings={"file_format": "HDR", "color_mode": "BW"}) + self.check(src="rgba32", ext="hdr", settings={"file_format": "HDR", "color_mode": "RGB"}) + + def test_save_targa(self): + self.check(src="rgba08", ext="tga", settings={"file_format": "TARGA", "color_mode": "BW"}) + self.check(src="rgba08", ext="tga", settings={"file_format": "TARGA", "color_mode": "RGB"}) + self.check(src="rgba08", ext="tga", settings={"file_format": "TARGA", "color_mode": "RGBA"}) + + self.check(src="rgba32", ext="tga", settings={"file_format": "TARGA", "color_mode": "BW"}) + self.check(src="rgba32", ext="tga", settings={"file_format": "TARGA", "color_mode": "RGB"}) + self.check(src="rgba32", ext="tga", settings={"file_format": "TARGA", "color_mode": "RGBA"}) + + def test_save_targa_raw(self): + self.check(src="rgba08", ext="tga", settings={"file_format": "TARGA_RAW", "color_mode": "BW"}) + self.check(src="rgba08", ext="tga", settings={"file_format": "TARGA_RAW", "color_mode": "RGB"}) + self.check(src="rgba08", ext="tga", settings={"file_format": "TARGA_RAW", "color_mode": "RGBA"}) + + self.check(src="rgba32", ext="tga", settings={"file_format": "TARGA_RAW", "color_mode": "BW"}) + self.check(src="rgba32", ext="tga", settings={"file_format": "TARGA_RAW", "color_mode": "RGB"}) + self.check(src="rgba32", ext="tga", settings={"file_format": "TARGA_RAW", "color_mode": "RGBA"}) + + def test_save_tiff(self): + self.skip_if_format_missing("TIFF") + + self.check(src="rgba08", ext="tif", settings={"file_format": "TIFF", "color_mode": "BW", "color_depth": "8", "tiff_codec": "DEFLATE"}) + self.check(src="rgba08", ext="tif", settings={"file_format": "TIFF", "color_mode": "RGB", "color_depth": "8", "tiff_codec": "LZW"}) + self.check(src="rgba08", ext="tif", settings={"file_format": "TIFF", "color_mode": "RGBA", "color_depth": "8", "tiff_codec": "PACKBITS"}) + self.check(src="rgba08", ext="tif", settings={"file_format": "TIFF", "color_mode": "BW", "color_depth": "16", "tiff_codec": "PACKBITS"}) + self.check(src="rgba08", ext="tif", settings={"file_format": "TIFF", "color_mode": "RGB", "color_depth": "16", "tiff_codec": "LZW"}) + self.check(src="rgba08", ext="tif", settings={"file_format": "TIFF", "color_mode": "RGBA", "color_depth": "16", "tiff_codec": "DEFLATE"}) + + self.check(src="rgba32", ext="tif", settings={"file_format": "TIFF", "color_mode": "BW", "color_depth": "8", "tiff_codec": "DEFLATE"}) + self.check(src="rgba32", ext="tif", settings={"file_format": "TIFF", "color_mode": "RGB", "color_depth": "8", "tiff_codec": "LZW"}) + self.check(src="rgba32", ext="tif", settings={"file_format": "TIFF", "color_mode": "RGBA", "color_depth": "8", "tiff_codec": "PACKBITS"}) + self.check(src="rgba32", ext="tif", settings={"file_format": "TIFF", "color_mode": "BW", "color_depth": "16", "tiff_codec": "PACKBITS"}) + self.check(src="rgba32", ext="tif", settings={"file_format": "TIFF", "color_mode": "RGB", "color_depth": "16", "tiff_codec": "LZW"}) + self.check(src="rgba32", ext="tif", settings={"file_format": "TIFF", "color_mode": "RGBA", "color_depth": "16", "tiff_codec": "DEFLATE"}) + + def test_save_jpeg(self): + self.check(src="rgba08", ext="jpg", settings={"file_format": "JPEG", "color_mode": "BW", "quality": 90}) + self.check(src="rgba08", ext="jpg", settings={"file_format": "JPEG", "color_mode": "RGB", "quality": 90}) + + self.check(src="rgba32", ext="jpg", settings={"file_format": "JPEG", "color_mode": "BW", "quality": 70}) + self.check(src="rgba32", ext="jpg", settings={"file_format": "JPEG", "color_mode": "RGB", "quality": 70}) + + def test_save_jpeg2000(self): + self.skip_if_format_missing("OPENJPEG") + + # Is there a better combination of settings we can use so there's not so many? + # Is this a good mix? + self.check(src="rgba08", ext="jp2", settings={"file_format": "JPEG2000", "color_mode": "BW", "color_depth": "8", "jpeg2k_codec": "JP2", "quality": 90}) + self.check(src="rgba08", ext="jp2", settings={"file_format": "JPEG2000", "color_mode": "BW", "color_depth": "12", "jpeg2k_codec": "JP2", "quality": 90}) + self.check(src="rgba08", ext="j2c", settings={"file_format": "JPEG2000", "color_mode": "BW", "color_depth": "16", "jpeg2k_codec": "J2K", "quality": 90}) + self.check(src="rgba08", ext="jp2", settings={"file_format": "JPEG2000", "color_mode": "RGB", "color_depth": "8", "jpeg2k_codec": "JP2", "quality": 90}) + self.check(src="rgba08", ext="jp2", settings={"file_format": "JPEG2000", "color_mode": "RGB", "color_depth": "12", "jpeg2k_codec": "JP2", "quality": 90}) + self.check(src="rgba08", ext="j2c", settings={"file_format": "JPEG2000", "color_mode": "RGB", "color_depth": "16", "jpeg2k_codec": "J2K", "quality": 90}) + self.check(src="rgba08", ext="jp2", settings={"file_format": "JPEG2000", "color_mode": "RGBA", "color_depth": "8", "jpeg2k_codec": "JP2", "quality": 90}) + self.check(src="rgba08", ext="jp2", settings={"file_format": "JPEG2000", "color_mode": "RGBA", "color_depth": "12", "jpeg2k_codec": "JP2", "quality": 90}) + self.check(src="rgba08", ext="j2c", settings={"file_format": "JPEG2000", "color_mode": "RGBA", "color_depth": "16", "jpeg2k_codec": "J2K", "quality": 90}) + + self.check(src="rgba32", ext="jp2", settings={"file_format": "JPEG2000", "color_mode": "BW", "color_depth": "8", "jpeg2k_codec": "JP2", "quality": 70}) + self.check(src="rgba32", ext="jp2", settings={"file_format": "JPEG2000", "color_mode": "BW", "color_depth": "12", "jpeg2k_codec": "JP2", "quality": 70}) + self.check(src="rgba32", ext="j2c", settings={"file_format": "JPEG2000", "color_mode": "BW", "color_depth": "16", "jpeg2k_codec": "J2K", "quality": 70}) + self.check(src="rgba32", ext="jp2", settings={"file_format": "JPEG2000", "color_mode": "RGB", "color_depth": "8", "jpeg2k_codec": "JP2", "quality": 70}) + self.check(src="rgba32", ext="jp2", settings={"file_format": "JPEG2000", "color_mode": "RGB", "color_depth": "12", "jpeg2k_codec": "JP2", "quality": 70}) + self.check(src="rgba32", ext="j2c", settings={"file_format": "JPEG2000", "color_mode": "RGB", "color_depth": "16", "jpeg2k_codec": "J2K", "quality": 70}) + self.check(src="rgba32", ext="jp2", settings={"file_format": "JPEG2000", "color_mode": "RGBA", "color_depth": "8", "jpeg2k_codec": "JP2", "quality": 70}) + self.check(src="rgba32", ext="jp2", settings={"file_format": "JPEG2000", "color_mode": "RGBA", "color_depth": "12", "jpeg2k_codec": "JP2", "quality": 70}) + self.check(src="rgba32", ext="j2c", settings={"file_format": "JPEG2000", "color_mode": "RGBA", "color_depth": "16", "jpeg2k_codec": "J2K", "quality": 70}) + + # Note: The 'use_jpeg2k_cinema_preset' option mandates very large images + # self.check(src="rgba08", ext="jpg", settings={"file_format":"JPEG2000", "color_mode":"RGBA", "color_depth":"8", "jpeg2k_codec":"JP2", "use_jpeg2k_cinema_preset":True, "use_jpeg2k_cinema_48":False, "use_jpeg2k_ycc":False, "quality":70}) + # self.check(src="rgba32", ext="jpg", settings={"file_format":"JPEG2000", "color_mode":"RGBA", "color_depth":"8", "jpeg2k_codec":"JP2", "use_jpeg2k_cinema_preset":True, "use_jpeg2k_cinema_48":False, "use_jpeg2k_ycc":False, "quality":70}) + + self.check(src="rgba08", ext="jp2", settings={"file_format": "JPEG2000", "color_mode": "RGBA", "color_depth": "12", "jpeg2k_codec": "JP2", "use_jpeg2k_cinema_preset": False, "use_jpeg2k_cinema_48": True, "use_jpeg2k_ycc": False, "quality": 70}) + self.check(src="rgba32", ext="jp2", settings={"file_format": "JPEG2000", "color_mode": "RGBA", "color_depth": "12", "jpeg2k_codec": "JP2", "use_jpeg2k_cinema_preset": False, "use_jpeg2k_cinema_48": True, "use_jpeg2k_ycc": False, "quality": 70}) + + self.check(src="rgba08", ext="jp2", settings={"file_format": "JPEG2000", "color_mode": "RGBA", "color_depth": "16", "jpeg2k_codec": "JP2", "use_jpeg2k_cinema_preset": False, "use_jpeg2k_cinema_48": False, "use_jpeg2k_ycc": True, "quality": 70}) + self.check(src="rgba32", ext="jp2", settings={"file_format": "JPEG2000", "color_mode": "RGBA", "color_depth": "16", "jpeg2k_codec": "JP2", "use_jpeg2k_cinema_preset": False, "use_jpeg2k_cinema_48": False, "use_jpeg2k_ycc": True, "quality": 70}) + + def test_save_dpx(self): + self.skip_if_format_missing("CINEON") + + self.check(src="rgba08", ext="dpx", settings={"file_format": "DPX", "color_mode": "RGB", "color_depth": "8", "use_cineon_log": False}) + self.check(src="rgba08", ext="dpx", settings={"file_format": "DPX", "color_mode": "RGB", "color_depth": "12", "use_cineon_log": False}) + self.check(src="rgba08", ext="dpx", settings={"file_format": "DPX", "color_mode": "RGB", "color_depth": "16", "use_cineon_log": False}) + self.check(src="rgba08", ext="dpx", settings={"file_format": "DPX", "color_mode": "RGBA", "color_depth": "10", "use_cineon_log": True}) + self.check(src="rgba08", ext="dpx", settings={"file_format": "DPX", "color_mode": "RGBA", "color_depth": "12", "use_cineon_log": True}) + self.check(src="rgba08", ext="dpx", settings={"file_format": "DPX", "color_mode": "RGBA", "color_depth": "16", "use_cineon_log": True}) + + self.check(src="rgba32", ext="dpx", settings={"file_format": "DPX", "color_mode": "RGB", "color_depth": "8", "use_cineon_log": False}) + self.check(src="rgba32", ext="dpx", settings={"file_format": "DPX", "color_mode": "RGB", "color_depth": "12", "use_cineon_log": False}) + self.check(src="rgba32", ext="dpx", settings={"file_format": "DPX", "color_mode": "RGB", "color_depth": "16", "use_cineon_log": False}) + self.check(src="rgba32", ext="dpx", settings={"file_format": "DPX", "color_mode": "RGBA", "color_depth": "10", "use_cineon_log": True}) + self.check(src="rgba32", ext="dpx", settings={"file_format": "DPX", "color_mode": "RGBA", "color_depth": "12", "use_cineon_log": True}) + self.check(src="rgba32", ext="dpx", settings={"file_format": "DPX", "color_mode": "RGBA", "color_depth": "16", "use_cineon_log": True}) + + def test_save_cineon(self): + self.skip_if_format_missing("CINEON") + + self.check(src="rgba08", ext="cin", settings={"file_format": "CINEON", "color_mode": "RGB"}) + self.check(src="rgba32", ext="cin", settings={"file_format": "CINEON", "color_mode": "RGB"}) + + def test_save_webp(self): + self.skip_if_format_missing("WEBP") + + self.check(src="rgba08", ext="webp", settings={"file_format": "WEBP", "color_mode": "RGB", "quality": 90}) + self.check(src="rgba08", ext="webp", settings={"file_format": "WEBP", "color_mode": "RGBA", "quality": 90}) + + self.check(src="rgba32", ext="webp", settings={"file_format": "WEBP", "color_mode": "RGB", "quality": 70}) + self.check(src="rgba32", ext="webp", settings={"file_format": "WEBP", "color_mode": "RGBA", "quality": 70}) +# autopep8: on + + +def main(): + global args + import argparse + + if '--' in sys.argv: + argv = [sys.argv[0]] + sys.argv[sys.argv.index('--') + 1:] + else: + argv = sys.argv + + parser = argparse.ArgumentParser() + parser.add_argument('-test_dir', required=True, type=pathlib.Path) + parser.add_argument('-output_dir', required=True, type=pathlib.Path) + parser.add_argument('-idiff', required=True, type=pathlib.Path) + parser.add_argument('-optional_formats', required=True) + args, remaining = parser.parse_known_args(argv) + + unittest.main(argv=remaining) + + +if __name__ == '__main__': + main() diff --git a/tests/python/modules/colored_print.py b/tests/python/modules/colored_print.py new file mode 100644 index 00000000000..f1b9b56df35 --- /dev/null +++ b/tests/python/modules/colored_print.py @@ -0,0 +1,46 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +import sys + + +class COLORS_ANSI: + RED = '\033[00;31m' + GREEN = '\033[00;32m' + ENDC = '\033[0m' + + +class COLORS_NONE: + RED = '' + GREEN = '' + ENDC = '' + + +COLORS = COLORS_NONE + + +def use_message_colors(): + global COLORS, COLORS_ANSI + COLORS = COLORS_ANSI + + +def print_message(message, type=None, status=''): + if type == 'SUCCESS': + print(COLORS.GREEN, end="") + elif type == 'FAILURE': + print(COLORS.RED, end="") + status_text = ... + if status == 'RUN': + status_text = " RUN " + elif status == 'OK': + status_text = " OK " + elif status == 'PASSED': + status_text = " PASSED " + elif status == 'FAILED': + status_text = " FAILED " + else: + status_text = status + if status_text: + print("[{}]" . format(status_text), end="") + print(COLORS.ENDC, end="") + print(" {}" . format(message)) + sys.stdout.flush() diff --git a/tests/python/modules/imbuf_test.py b/tests/python/modules/imbuf_test.py new file mode 100644 index 00000000000..378c7500cb3 --- /dev/null +++ b/tests/python/modules/imbuf_test.py @@ -0,0 +1,92 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +import os +import pathlib +import shutil +import subprocess +import unittest + +from .colored_print import (print_message, use_message_colors) + + +class AbstractImBufTest(unittest.TestCase): + @classmethod + def init(cls, args): + cls.test_dir = pathlib.Path(args.test_dir) + cls.reference_dir = pathlib.Path(args.test_dir).joinpath("reference") + cls.reference_load_dir = pathlib.Path(args.test_dir).joinpath("reference_load") + cls.output_dir = pathlib.Path(args.output_dir) + cls.diff_dir = pathlib.Path(args.output_dir).joinpath("diff") + cls.idiff = pathlib.Path(args.idiff) + cls.optional_formats = args.optional_formats + + os.makedirs(cls.diff_dir, exist_ok=True) + + cls.errors = 0 + cls.fail_threshold = 0.001 + cls.fail_percent = 1 + cls.verbose = os.environ.get("BLENDER_VERBOSE") is not None + cls.update = os.getenv('BLENDER_TEST_UPDATE') is not None + if os.environ.get("BLENDER_TEST_COLOR") is not None: + use_message_colors() + + def setUp(self): + self.errors = 0 + print_message("") + + def tearDown(self): + if self.errors > 0: + self.fail("{} errors encountered" . format(self.errors)) + + def skip_if_format_missing(self, format): + if self.optional_formats.find(format) < 0: + self.skipTest("format not available") + + def call_idiff(self, ref_path, out_path): + ref_filepath = str(ref_path) + out_filepath = str(out_path) + out_name = out_path.name + if os.path.exists(ref_filepath): + # Diff images test with threshold. + command = ( + str(self.idiff), + "-fail", str(self.fail_threshold), + "-failpercent", str(self.fail_percent), + ref_filepath, + out_filepath, + ) + try: + subprocess.check_output(command) + failed = False + except subprocess.CalledProcessError as e: + if self.verbose: + print_message(e.output.decode("utf-8", 'ignore')) + failed = e.returncode != 1 + else: + if not self.update: + return False + + failed = True + + if failed and self.update: + # Update reference image if requested. + shutil.copy(out_filepath, ref_filepath) + failed = False + + # Generate diff image. + diff_img = str(self.diff_dir.joinpath(out_name + ".diff.png")) + command = ( + str(self.idiff), + "-o", diff_img, + "-abs", "-scale", "16", + ref_filepath, + out_filepath + ) + + try: + subprocess.check_output(command) + except subprocess.CalledProcessError as e: + if self.verbose: + print_message(e.output.decode("utf-8", 'ignore')) + + return not failed diff --git a/tests/python/modules/render_report.py b/tests/python/modules/render_report.py index 5dcb73b65cc..d206f1338c5 100755 --- a/tests/python/modules/render_report.py +++ b/tests/python/modules/render_report.py @@ -14,44 +14,7 @@ import sys import time from . import global_report - - -class COLORS_ANSI: - RED = '\033[00;31m' - GREEN = '\033[00;32m' - ENDC = '\033[0m' - - -class COLORS_DUMMY: - RED = '' - GREEN = '' - ENDC = '' - - -COLORS = COLORS_DUMMY - - -def print_message(message, type=None, status=''): - if type == 'SUCCESS': - print(COLORS.GREEN, end="") - elif type == 'FAILURE': - print(COLORS.RED, end="") - status_text = ... - if status == 'RUN': - status_text = " RUN " - elif status == 'OK': - status_text = " OK " - elif status == 'PASSED': - status_text = " PASSED " - elif status == 'FAILED': - status_text = " FAILED " - else: - status_text = status - if status_text: - print("[{}]" . format(status_text), end="") - print(COLORS.ENDC, end="") - print(" {}" . format(message)) - sys.stdout.flush() +from .colored_print import (print_message, use_message_colors) def blend_list(dirpath, device, blacklist): @@ -151,8 +114,7 @@ class Report: self.update = os.getenv('BLENDER_TEST_UPDATE') is not None if os.environ.get("BLENDER_TEST_COLOR") is not None: - global COLORS, COLORS_ANSI - COLORS = COLORS_ANSI + use_message_colors() self.failed_tests = "" self.passed_tests = ""