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 00000000..601a057b Binary files /dev/null and b/PhotoshopTest/documents/BlendFill/blend_fill.psd differ 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); });