diff --git a/python/psapi-test/documents/MismatchedMaskChannel.psb b/python/psapi-test/documents/MismatchedMaskChannel.psb new file mode 100644 index 0000000..b215741 Binary files /dev/null and b/python/psapi-test/documents/MismatchedMaskChannel.psb differ diff --git a/python/psapi-test/test_imagelayer.py b/python/psapi-test/test_imagelayer.py index 26034f6..80a8688 100644 --- a/python/psapi-test/test_imagelayer.py +++ b/python/psapi-test/test_imagelayer.py @@ -11,6 +11,7 @@ class TestImageLayer(unittest.TestCase): path = os.path.join(os.path.dirname(__file__), "documents", "BaseFile.psb") + mask_path = os.path.join(os.path.dirname(__file__), "documents", "MismatchedMaskChannel.psb") bin_data_path = os.path.join(os.path.dirname(__file__), "bin_data", "monza_npy.bin") def test_get_image_data(self): @@ -26,6 +27,24 @@ def test_get_image_data(self): self.assertTrue(len(layer.channels) == 4) self.assertTrue(layer.num_channels == 4) + def test_get_image_data_mismatched_mask(self): + """ + Test that the mask channel gets read properly even if it has a different size than + """ + file = psapi.LayeredFile.read(self.mask_path) + layer: psapi.ImageLayer_16bit = file["MonzaSP1_DawnShot_V1_v002_ED.BaseAOV"] + + image_data = layer.image_data + image_data_2 = layer.get_image_data() + + self.assertTrue(-2 in image_data and -2 in image_data_2) + self.assertTrue(-1 in image_data and -1 in image_data_2) + self.assertTrue(0 in image_data and 0 in image_data_2) + self.assertTrue(1 in image_data and 1 in image_data_2) + self.assertTrue(2 in image_data and 2 in image_data_2) + self.assertTrue(len(layer.channels) == 5) + self.assertTrue(layer.num_channels == 5) + def test_channel_setting_roundtrip(self): file = psapi.LayeredFile.read(self.path) layer: psapi.ImageLayer_16bit = file["Render"]["AOV"]["Beauties"]["MonzaSP1_DawnShot_V1_v002_ED.BaseAOV"] diff --git a/python/src/DeclareImageLayer.h b/python/src/DeclareImageLayer.h index 00e3d08..7a319b0 100644 --- a/python/src/DeclareImageLayer.h +++ b/python/src/DeclareImageLayer.h @@ -488,14 +488,33 @@ void declareImageLayer(py::module& m, const std::string& extension) { { auto data = self.getImageData(do_copy); std::unordered_map> outData; + + constexpr auto mask_id = Enum::ChannelIDInfo{ Enum::ChannelID::UserSuppliedLayerMask, -2 }; for (auto& [key, value] : data) { - outData[key.index] = to_py_array(std::move(value), self.m_Width, self.m_Height); + // Mask channels may have a different resolution compared to the actual layer so we must account for this while parsing. + if (key == mask_id) + { + if (!self.m_LayerMask) + { + throw py::value_error("Internal error: Encountered mask channel but layer does not have mask"); + } + auto& mask = self.m_LayerMask.value(); + auto width = mask.maskData->getWidth(); + auto height = mask.maskData->getHeight(); + + outData[key.index] = to_py_array(std::move(value), width, height); + } + else + { + outData[key.index] = to_py_array(std::move(value), self.m_Width, self.m_Height); + } } return outData; }, py::arg("do_copy") = true, R"pbdoc( - Extract all the channels of the ImageLayer into an unordered_map. + Extract all the channels of the ImageLayer into an unordered_map. The channels may have differing sizes + as photoshop optimizes mask channels differently than the pixel data :param do_copy: Defaults to true, Whether to copy the data :type do_copy: bool @@ -616,9 +635,27 @@ void declareImageLayer(py::module& m, const std::string& extension) { { auto data = self.getImageData(true); std::unordered_map> outData; + + constexpr auto mask_id = Enum::ChannelIDInfo{ Enum::ChannelID::UserSuppliedLayerMask, -2 }; for (auto& [key, value] : data) { - outData[key.index] = to_py_array(std::move(value), self.m_Width, self.m_Height); + // Mask channels may have a different resolution compared to the actual layer so we must account for this while parsing. + if (key == mask_id) + { + if (!self.m_LayerMask) + { + throw py::value_error("Internal error: Encountered mask channel but layer does not have mask"); + } + auto& mask = self.m_LayerMask.value(); + auto width = mask.maskData->getWidth(); + auto height = mask.maskData->getHeight(); + + outData[key.index] = to_py_array(std::move(value), width, height); + } + else + { + outData[key.index] = to_py_array(std::move(value), self.m_Width, self.m_Height); + } } return outData; }, R"pbdoc(