From a890ea03c2e921791490408979ecd8a3398e0acb Mon Sep 17 00:00:00 2001 From: emild Date: Tue, 28 Apr 2026 21:31:29 +0200 Subject: [PATCH] bug: fix #214 layer fill not being respected --- .../Core/TaggedBlocks/BlendFillTaggedBlock.h | 54 +++++++++++++++++ .../Core/TaggedBlocks/TaggedBlockStorage.cpp | 13 +++- .../src/LayeredFile/LayerTypes/Layer.h | 56 ++++++++++++------ PhotoshopAPI/src/Util/Enum.h | 4 ++ .../documents/BlendFill/blend_fill.psd | Bin 0 -> 22914 bytes .../src/TestBlendFill/TestBlendFill.cpp | 28 +++++++++ python/py_module/photoshopapi/_layer.pyi | 24 ++++++++ python/src/Layers/DeclareLayer.h | 3 + 8 files changed, 163 insertions(+), 19 deletions(-) create mode 100644 PhotoshopAPI/src/Core/TaggedBlocks/BlendFillTaggedBlock.h create mode 100644 PhotoshopTest/documents/BlendFill/blend_fill.psd create mode 100644 PhotoshopTest/src/TestBlendFill/TestBlendFill.cpp diff --git a/PhotoshopAPI/src/Core/TaggedBlocks/BlendFillTaggedBlock.h b/PhotoshopAPI/src/Core/TaggedBlocks/BlendFillTaggedBlock.h new file mode 100644 index 00000000..4370f6c1 --- /dev/null +++ b/PhotoshopAPI/src/Core/TaggedBlocks/BlendFillTaggedBlock.h @@ -0,0 +1,54 @@ +#pragma once + +#include "Macros.h" +#include "TaggedBlock.h" + +#include "Core/Struct/File.h" +#include "Core/Struct/Signature.h" +#include "Util/ProgressCallback.h" +#include "Util/Enum.h" +#include "Core/FileIO/LengthMarkers.h" + +#include + +PSAPI_NAMESPACE_BEGIN + +struct BlendFillTaggedBlock : TaggedBlock +{ + uint8_t m_Fill = 255u; + + BlendFillTaggedBlock() = default; + + explicit BlendFillTaggedBlock(const uint8_t value) {m_Fill = value;}; + + void read(File& document, const uint64_t offset, const Signature signature, [[maybe_unused]] const uint16_t padding = 1u) + { + m_Key = Enum::TaggedBlockKey::lrSheetColorSetting; + m_Offset = offset; + m_Signature = signature; + + if (const auto length = ReadBinaryData(document); length != 4u) + { + throw std::runtime_error( + std::format( + "Error while reading BlendFillTaggedBlock tagged block, expected exactly 4 bytes of size but instead" + " got {}", length + ) + ); + } + + m_Fill = ReadBinaryData(document); + document.skip(3u); + } + void write(File& document, [[maybe_unused]] const FileHeader& header, [[maybe_unused]] ProgressCallback& callback, const uint16_t padding = 1u) override + { + WriteBinaryData(document, Signature("8BIM").m_Value); + WriteBinaryData(document, Signature("iOpa").m_Value); + Impl::ScopedLengthBlock len_block(document, padding); + + WriteBinaryData(document, m_Fill); + WritePadddingBytes(document, 3u); + } +}; + +PSAPI_NAMESPACE_END \ No newline at end of file diff --git a/PhotoshopAPI/src/Core/TaggedBlocks/TaggedBlockStorage.cpp b/PhotoshopAPI/src/Core/TaggedBlocks/TaggedBlockStorage.cpp index 70993c9d..6987b974 100644 --- a/PhotoshopAPI/src/Core/TaggedBlocks/TaggedBlockStorage.cpp +++ b/PhotoshopAPI/src/Core/TaggedBlocks/TaggedBlockStorage.cpp @@ -2,7 +2,6 @@ #include "Macros.h" #include "Core/FileIO/Read.h" -#include "Core/FileIO/Write.h" #include "Logger.h" #include "StringUtil.h" @@ -18,6 +17,8 @@ #include "TypeToolTaggedBlock.h" #include "UnicodeLayerNameTaggedBlock.h" #include "SheetColorTaggedBlock.h" +#include "BlendFillTaggedBlock.h" + PSAPI_NAMESPACE_BEGIN @@ -64,6 +65,7 @@ template std::shared_ptr TaggedBlockStorage::getTagg template std::shared_ptr TaggedBlockStorage::getTaggedBlockView(const Enum::TaggedBlockKey key) const; template std::shared_ptr TaggedBlockStorage::getTaggedBlockView(const Enum::TaggedBlockKey key) const; template std::shared_ptr TaggedBlockStorage::getTaggedBlockView(const Enum::TaggedBlockKey key) const; +template std::shared_ptr TaggedBlockStorage::getTaggedBlockView(const Enum::TaggedBlockKey key) const; template @@ -93,6 +95,7 @@ template std::shared_ptr TaggedBlockStorage::getTagg template std::shared_ptr TaggedBlockStorage::getTaggedBlockView() const; template std::shared_ptr TaggedBlockStorage::getTaggedBlockView() const; template std::shared_ptr TaggedBlockStorage::getTaggedBlockView() const; +template std::shared_ptr TaggedBlockStorage::getTaggedBlockView() const; template @@ -124,6 +127,7 @@ template std::vector> TaggedBlockSto template std::vector> TaggedBlockStorage::get_tagged_blocks() const; template std::vector> TaggedBlockStorage::get_tagged_blocks() const; template std::vector> TaggedBlockStorage::get_tagged_blocks() const; +template std::vector> TaggedBlockStorage::get_tagged_blocks() const; @@ -225,6 +229,13 @@ const std::shared_ptr TaggedBlockStorage::readTaggedBlock(File& doc this->m_TaggedBlocks.push_back(lrSheetColorSetting); return lrSheetColorSetting; } + else if (taggedBlock.value() == Enum::TaggedBlockKey::lrBlendFill) + { + auto block = std::make_shared(); + block->read(document, offset, signature, padding); + this->m_TaggedBlocks.push_back(block); + return block; + } else { auto baseTaggedBlock = std::make_shared(); diff --git a/PhotoshopAPI/src/LayeredFile/LayerTypes/Layer.h b/PhotoshopAPI/src/LayeredFile/LayerTypes/Layer.h index 32e00ae0..82da7a16 100644 --- a/PhotoshopAPI/src/LayeredFile/LayerTypes/Layer.h +++ b/PhotoshopAPI/src/LayeredFile/LayerTypes/Layer.h @@ -11,6 +11,7 @@ #include "Core/TaggedBlocks/PlacedLayerTaggedBlock.h" #include "Core/TaggedBlocks/ProtectedSettingTaggedBlock.h" #include "Core/TaggedBlocks/ReferencePointTaggedBlock.h" +#include "Core/TaggedBlocks/BlendFillTaggedBlock.h" #include "Core/TaggedBlocks/UnicodeLayerNameTaggedBlock.h" #include "MaskDataMixin.h" @@ -127,6 +128,20 @@ struct Layer : public MaskMixin m_Opacity = static_cast(value * 255.0f); } + /// The layers' fill value + float fill() const noexcept { return static_cast(m_Fill) / 255; } + /// The layers' fill value. + void fill(float value) noexcept + { + if (value < 0.0f || value > 1.0f) + { + PSAPI_LOG_WARNING("Layer", "Encountered fill value not between 0-1. Clamping this to fit into that range"); + } + value = std::clamp(value, 0.0f, 1.0f); + + m_Fill = static_cast(value * 255.0f); + } + /// The layers' width from 0 - 300,000 virtual uint32_t width() const noexcept { return m_Width; } /// The layers' width from 0 - 300,000 @@ -222,8 +237,7 @@ struct Layer : public MaskMixin // Pass along the unparsed blocks m_UnparsedBlocks = additional_layer_info.get_base_tagged_blocks(); - auto section_divider_block = additional_layer_info.getTaggedBlock(Enum::TaggedBlockKey::lrSectionDivider); - if (section_divider_block.has_value() && section_divider_block.value()->m_BlendMode.has_value()) + if (auto section_divider_block = additional_layer_info.getTaggedBlock(Enum::TaggedBlockKey::lrSectionDivider); section_divider_block.has_value() && section_divider_block.value()->m_BlendMode.has_value()) { m_BlendMode = section_divider_block.value()->m_BlendMode.value(); } @@ -233,10 +247,9 @@ struct Layer : public MaskMixin } // Parse the layer protection settings - auto protectionSettings = additional_layer_info.getTaggedBlock(Enum::TaggedBlockKey::lrProtectedSetting); - if (protectionSettings) + if (auto protection_settings = additional_layer_info.getTaggedBlock(Enum::TaggedBlockKey::lrProtectedSetting)) { - m_IsLocked = protectionSettings.value()->m_IsLocked; + m_IsLocked = protection_settings.value()->m_IsLocked; } else { @@ -244,8 +257,7 @@ struct Layer : public MaskMixin } // Parse the layer color - auto layer_sheet_color_block = additional_layer_info.getTaggedBlock(Enum::TaggedBlockKey::lrSheetColorSetting); - if (layer_sheet_color_block.has_value()) + if (auto layer_sheet_color_block = additional_layer_info.getTaggedBlock(Enum::TaggedBlockKey::lrSheetColorSetting); layer_sheet_color_block.has_value()) { m_LayerColor = layer_sheet_color_block.value()->m_Color; } @@ -323,20 +335,23 @@ struct Layer : public MaskMixin if (layerRecord.m_AdditionalLayerInfo.has_value()) { // Get the reference point (if it is there) - auto& additionalLayerInfo = layerRecord.m_AdditionalLayerInfo.value(); - auto reference_point = additionalLayerInfo.get_tagged_block(); - if (reference_point) + auto& additional_layer_info = layerRecord.m_AdditionalLayerInfo.value(); + if (auto reference_point = additional_layer_info.get_tagged_block()) { m_ReferencePointX.emplace(reference_point->m_ReferenceX); m_ReferencePointY.emplace(reference_point->m_ReferenceY); } // Get the unicode layer name (if it is there) and override the pascal string name - auto unicode_name = additionalLayerInfo.get_tagged_block(); - if (unicode_name) + if (auto unicode_name = additional_layer_info.get_tagged_block()) { m_LayerName = unicode_name->m_Name.string(); } + + if (auto blend_fill = additional_layer_info.get_tagged_block()) + { + m_Fill = blend_fill->m_Fill; + } } } @@ -405,6 +420,8 @@ struct Layer : public MaskMixin /// 0 - 255 despite the appearance being 0-100 in photoshop uint8_t m_Opacity{}; + /// 0 - 255 despite the appearance being 0-100 in photoshop + uint8_t m_Fill{}; uint32_t m_Width{}; @@ -477,15 +494,18 @@ struct Layer : public MaskMixin // Generate our unicode layer name block, we always include this as its size is trivial and this avoids // any issues with names being truncated - auto unicodeNamePtr = std::make_shared(m_LayerName, static_cast(4u)); - block_vec.push_back(unicodeNamePtr); + const auto unicode_layer_name_tagged_block = std::make_shared(m_LayerName, static_cast(4u)); + block_vec.push_back(unicode_layer_name_tagged_block); // Generate our LockedSettings Tagged block - auto protectionSettingsPtr = std::make_shared(m_IsLocked); - block_vec.push_back(protectionSettingsPtr); + const auto protected_setting_tagged_block = std::make_shared(m_IsLocked); + block_vec.push_back(protected_setting_tagged_block); + + const auto sheet_color_tagged_block = std::make_shared(m_LayerColor); + block_vec.push_back(sheet_color_tagged_block); - auto lr_color_ptr = std::make_shared(m_LayerColor); - block_vec.push_back(lr_color_ptr); + const auto blend_fill_tagged_block = std::make_shared(m_Fill); + block_vec.push_back(blend_fill_tagged_block); return block_vec; } diff --git a/PhotoshopAPI/src/Util/Enum.h b/PhotoshopAPI/src/Util/Enum.h index 47fbc85d..08d71f09 100644 --- a/PhotoshopAPI/src/Util/Enum.h +++ b/PhotoshopAPI/src/Util/Enum.h @@ -799,6 +799,8 @@ namespace Enum lrSavingMergedTransparency, // Holds no data, just indicates channel Image data section includes transparency (needs to be tested) lrPixelSourceData, // Data for 3d or video layers lrUserMask, + lrObjectBasedFXInfo, + lrBlendFill, // 16- and 32-bit files store their layer records under these tagged blocks at the end of the layer // and mask information section Lr16, @@ -880,6 +882,8 @@ namespace Enum {"Mt32", TaggedBlockKey::lrSavingMergedTransparency}, {"PxSD", TaggedBlockKey::lrPixelSourceData}, {"LMsk", TaggedBlockKey::lrUserMask}, + {"lfx2", TaggedBlockKey::lrObjectBasedFXInfo }, + {"iOpa", TaggedBlockKey::lrBlendFill}, {"Lr16", TaggedBlockKey::Lr16}, {"Lr32", TaggedBlockKey::Lr32}, {"Layr", TaggedBlockKey::Layr}, diff --git a/PhotoshopTest/documents/BlendFill/blend_fill.psd b/PhotoshopTest/documents/BlendFill/blend_fill.psd new file mode 100644 index 0000000000000000000000000000000000000000..601a057b2793c9c4204f5585ce5aa9b3ca73ecf0 GIT binary patch literal 22914 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 literal 0 HcmV?d00001 diff --git a/PhotoshopTest/src/TestBlendFill/TestBlendFill.cpp b/PhotoshopTest/src/TestBlendFill/TestBlendFill.cpp new file mode 100644 index 00000000..1d494e84 --- /dev/null +++ b/PhotoshopTest/src/TestBlendFill/TestBlendFill.cpp @@ -0,0 +1,28 @@ +#include "doctest.h" + +#include "Macros.h" +#include "LayeredFile/LayeredFile.h" + +#include + + +// --------------------------------------------------------------------------------------------------------------------- +// --------------------------------------------------------------------------------------------------------------------- +TEST_CASE("Blend fill round tripping") +{ + using namespace NAMESPACE_PSAPI; + + { + auto file = LayeredFile::read("documents/BlendFill/blend_fill.psd"); + const auto layer_ptr = file.layers().at(0); + // We choose fairly aggressive epsilon as this is internally represented by a + CHECK(layer_ptr->fill() == doctest::Approx(0.51f).epsilon(1e-2)); + LayeredFile::write(std::move(file), "documents/out/blend_fill_out.psd"); + } + + { + auto file = LayeredFile::read("documents/out/blend_fill_out.psd"); + const auto layer_ptr = file.layers().at(0); + CHECK(layer_ptr->fill() == doctest::Approx(0.51f).epsilon(1e-2)); + } +} diff --git a/python/py_module/photoshopapi/_layer.pyi b/python/py_module/photoshopapi/_layer.pyi index 12718ca4..35c59f3e 100644 --- a/python/py_module/photoshopapi/_layer.pyi +++ b/python/py_module/photoshopapi/_layer.pyi @@ -42,6 +42,14 @@ class Layer_8bit: def opacity(self: Layer_8bit, value: float) -> None: ... + @property + def fill(self: Layer_8bit) -> float: + ... + + @fill.setter + def fill(self: Layer_8bit, value: float) -> None: + ... + @property def width(self: Layer_8bit) -> int: ... @@ -201,6 +209,14 @@ class Layer_16bit: def opacity(self: Layer_16bit, value: float) -> None: ... + @property + def fill(self: Layer_16bit) -> float: + ... + + @fill.setter + def fill(self: Layer_16bit, value: float) -> None: + ... + @property def width(self: Layer_16bit) -> int: ... @@ -361,6 +377,14 @@ class Layer_32bit: def opacity(self: Layer_32bit, value: float) -> None: ... + @property + def fill(self: Layer_32bit) -> float: + ... + + @fill.setter + def fill(self: Layer_32bit, value: float) -> None: + ... + @property def width(self: Layer_32bit) -> int: ... diff --git a/python/src/Layers/DeclareLayer.h b/python/src/Layers/DeclareLayer.h index 548b4421..173daea9 100644 --- a/python/src/Layers/DeclareLayer.h +++ b/python/src/Layers/DeclareLayer.h @@ -47,6 +47,8 @@ void declare_layer(py::module& m, const std::string& extension) { The blend mode of the layer, 'Passthrough' is reserved for group layers opacity : float The layers opacity from 0.0 - 1.0 + fill : float + The layers fill from 0.0 - 1.0 width : int The width of the layer ranging up to 30,000 for PSD and 300,000 for PSB, this does not have to match the files width @@ -93,6 +95,7 @@ void declare_layer(py::module& m, const std::string& extension) { layer.def_property("name", &Class::name, &Class::name); layer.def_property("blend_mode", [](const Class& self) { return self.blendmode(); }, [](Class& self, Enum::BlendMode blendmode) { self.blendmode(blendmode); }); layer.def_property("opacity", [](const Class& self) { return self.opacity(); }, [](Class& self, float opacity) { self.opacity(opacity); }); + layer.def_property("fill", [](const Class& self) { return self.fill(); }, [](Class& self, float fill) { self.fill(fill); }); layer.def_property("width", [](const Class& self) { return self.width(); }, [](Class& self, uint32_t width) { self.width(width); }); layer.def_property("height", [](const Class& self) { return self.height(); }, [](Class& self, uint32_t height) { self.height(height); }); layer.def_property("center_x", [](const Class& self) { return self.center_x(); }, [](Class& self, float center_x) { self.center_x(center_x); });