Skip to content

Commit

Permalink
[Feat] Add support for locked layers (#116)
Browse files Browse the repository at this point in the history
  • Loading branch information
EmilDohne authored Oct 18, 2024
1 parent 8219e28 commit 28687a2
Show file tree
Hide file tree
Showing 9 changed files with 205 additions and 19 deletions.
45 changes: 45 additions & 0 deletions PhotoshopAPI/src/Core/Struct/TaggedBlock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -284,4 +284,49 @@ void UnicodeLayerNameTaggedBlock::write(File& document, [[maybe_unused]] const F
m_Name.write(document);
}


// ---------------------------------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------------------------------
void ProtectedSettingTaggedBlock::read(File& document, const uint64_t offset, const Signature signature)
{
m_Key = Enum::TaggedBlockKey::lrProtectedSetting;
m_Offset = offset;
m_Signature = signature;
uint32_t length = ReadBinaryData<uint32_t>(document);
m_Length = length;
TaggedBlock::totalSize(static_cast<size_t>(length) + 4u + 4u + 4u);

if (length != 4u)
{
PSAPI_LOG_WARNING("ProtectedSettingTaggedBlock", "Block size did not match expected size of 4, instead got %d, skipping reading this block", length);
document.setOffset(offset + 4u + length);
return;
}

auto flags = ReadBinaryData<uint8_t>(document);
m_IsLocked = flags & 128u; // Check if the flags at bit 7 are true
// Skip the last 3 bytes
document.skip(3u);
}


// ---------------------------------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------------------------------
void ProtectedSettingTaggedBlock::write(File& document, [[maybe_unused]] const FileHeader& header, [[maybe_unused]] ProgressCallback& callback, [[maybe_unused]] const uint16_t padding /*= 1u*/)
{
WriteBinaryData<uint32_t>(document, Signature("8BIM").m_Value);
WriteBinaryData<uint32_t>(document, Signature("lspf").m_Value);
WriteBinaryData<uint32_t>(document, TaggedBlock::totalSize<uint32_t>() - 12u);

if (m_IsLocked)
{
WriteBinaryData<uint8_t>(document, 128u);
WritePadddingBytes(document, 3u);
}
else
{
WritePadddingBytes(document, 4u);
}
}

PSAPI_NAMESPACE_END
18 changes: 18 additions & 0 deletions PhotoshopAPI/src/Core/Struct/TaggedBlock.h
Original file line number Diff line number Diff line change
Expand Up @@ -175,4 +175,22 @@ struct UnicodeLayerNameTaggedBlock : TaggedBlock
void write(File& document, const FileHeader& header, ProgressCallback& callback, const uint16_t padding = 1u) override;
};


/// The layers' pixel protection settings. This is internally a uint32_t of which only the first byte seems to hold relevant
/// information with the rest being for padding and/or aligment.
struct ProtectedSettingTaggedBlock : TaggedBlock
{
bool m_IsLocked = false; // 01000000 of the first byte

ProtectedSettingTaggedBlock() = default;
ProtectedSettingTaggedBlock(bool isLocked) : m_IsLocked(isLocked)
{
// This is the size of a uin32_t + 4 bytes for the signature, 4 bytes for the key and 4 bytes for the length
TaggedBlock::totalSize(4u + 4u + 4u + 4u);
};

void read(File& document, const uint64_t offset, const Signature signature);
void write(File& document, const FileHeader& header, ProgressCallback& callback, const uint16_t padding = 1u) override;
};

PSAPI_NAMESPACE_END
9 changes: 8 additions & 1 deletion PhotoshopAPI/src/Core/Struct/TaggedBlockStorage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ template std::shared_ptr<Lr32TaggedBlock> TaggedBlockStorage::getTaggedBlockView
template std::shared_ptr<LrSectionTaggedBlock> TaggedBlockStorage::getTaggedBlockView(const Enum::TaggedBlockKey key) const;
template std::shared_ptr<ReferencePointTaggedBlock> TaggedBlockStorage::getTaggedBlockView(const Enum::TaggedBlockKey key) const;
template std::shared_ptr<UnicodeLayerNameTaggedBlock> TaggedBlockStorage::getTaggedBlockView(const Enum::TaggedBlockKey key) const;

template std::shared_ptr<ProtectedSettingTaggedBlock> TaggedBlockStorage::getTaggedBlockView(const Enum::TaggedBlockKey key) const;

// ---------------------------------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -111,6 +111,13 @@ const std::shared_ptr<TaggedBlock> TaggedBlockStorage::readTaggedBlock(File& doc
this->m_TaggedBlocks.push_back(lrUnicodeNameBlock);
return lrUnicodeNameBlock;
}
else if (taggedBlock.value() == Enum::TaggedBlockKey::lrProtectedSetting)
{
auto lrProtectionTaggedBlock = std::make_shared<ProtectedSettingTaggedBlock>();
lrProtectionTaggedBlock->read(document, offset, signature);
this->m_TaggedBlocks.push_back(lrProtectionTaggedBlock);
return lrProtectionTaggedBlock;
}
else
{
auto baseTaggedBlock = std::make_shared<TaggedBlock>();
Expand Down
3 changes: 2 additions & 1 deletion PhotoshopAPI/src/LayeredFile/LayerTypes/GroupLayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ struct GroupLayer : public Layer<T>
Layer<T>::m_BlendMode = parameters.blendMode;
Layer<T>::m_Opacity = parameters.opacity;
Layer<T>::m_IsVisible = parameters.isVisible;
Layer<T>::m_IsLocked = parameters.isLocked;
Layer<T>::m_CenterX = parameters.posX;
Layer<T>::m_CenterY = parameters.posY;
Layer<T>::m_Width = parameters.width;
Expand Down Expand Up @@ -139,7 +140,7 @@ struct GroupLayer : public Layer<T>
PascalString lrName = Layer<T>::generatePascalString();
ChannelExtents extents = generateChannelExtents(ChannelCoordinates(Layer<T>::m_Width, Layer<T>::m_Height, Layer<T>::m_CenterX, Layer<T>::m_CenterY), header);
uint8_t clipping = 0u; // No clipping mask for now
LayerRecords::BitFlags bitFlags = LayerRecords::BitFlags(false, !Layer<T>::m_IsVisible, false);
LayerRecords::BitFlags bitFlags = LayerRecords::BitFlags(Layer<T>::m_IsLocked, !Layer<T>::m_IsVisible, false);
std::optional<LayerRecords::LayerMaskData> lrMaskData = Layer<T>::generateMaskData(header);
LayerRecords::LayerBlendingRanges blendingRanges = Layer<T>::generateBlendingRanges();

Expand Down
3 changes: 2 additions & 1 deletion PhotoshopAPI/src/LayeredFile/LayerTypes/ImageLayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ struct ImageLayer : public Layer<T>
uint16_t channelCount = m_ImageData.size() + static_cast<uint16_t>(Layer<T>::m_LayerMask.has_value());

uint8_t clipping = 0u; // No clipping mask for now
LayerRecords::BitFlags bitFlags(false, !Layer<T>::m_IsVisible, false);
LayerRecords::BitFlags bitFlags(Layer<T>::m_IsLocked, !Layer<T>::m_IsVisible, false);
std::optional<LayerRecords::LayerMaskData> lrMaskData = Layer<T>::generateMaskData(header);
LayerRecords::LayerBlendingRanges blendingRanges = Layer<T>::generateBlendingRanges();

Expand Down Expand Up @@ -574,6 +574,7 @@ struct ImageLayer : public Layer<T>
}
Layer<T>::m_Opacity = parameters.opacity;
Layer<T>::m_IsVisible = parameters.isVisible;
Layer<T>::m_IsLocked = parameters.isLocked;
Layer<T>::m_CenterX = parameters.posX;
Layer<T>::m_CenterY = parameters.posY;
Layer<T>::m_Width = parameters.width;
Expand Down
28 changes: 26 additions & 2 deletions PhotoshopAPI/src/LayeredFile/LayerTypes/Layer.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,10 @@ struct Layer
Enum::Compression compression = Enum::Compression::ZipPrediction;
// The Layers color mode, currently only RGB is supported
Enum::ColorMode colorMode = Enum::ColorMode::RGB;
// Whether or not the layer is visible
// Whether the layer is visible
bool isVisible = true;
// Whether the layer is locked
bool isLocked = false;
};

std::string m_LayerName;
Expand All @@ -74,6 +76,9 @@ struct Layer
/// Marks whether or not the layer is visible or not
bool m_IsVisible{};

/// Whether the layer is locked inside of photoshop
bool m_IsLocked = false;

/// 0 - 255 despite the appearance being 0-100 in photoshop
uint8_t m_Opacity{};

Expand Down Expand Up @@ -213,6 +218,10 @@ struct Layer
auto unicodeNamePtr = std::make_shared<UnicodeLayerNameTaggedBlock>(m_LayerName, static_cast<uint8_t>(4u));
blockVec.push_back(unicodeNamePtr);

// Generate our LockedSettings Tagged block
auto protectionSettingsPtr = std::make_shared<ProtectedSettingTaggedBlock>(m_IsLocked);
blockVec.push_back(protectionSettingsPtr);

return blockVec;
}

Expand Down Expand Up @@ -332,9 +341,24 @@ struct Layer
{
m_BlendMode = layerRecord.m_BlendMode;
}

// Parse the layer protection settings
auto protectionSettings = additionalLayerInfo.getTaggedBlock<ProtectedSettingTaggedBlock>(Enum::TaggedBlockKey::lrProtectedSetting);
if (protectionSettings)
{
m_IsLocked = protectionSettings.value()->m_IsLocked;
}
else
{
m_IsLocked = false;
}
}
// For now we only parse visibility from the bitflags but this could be expanded to parse other information as well.
m_IsVisible = !layerRecord.m_BitFlags.m_isHidden;
if (m_IsLocked && !layerRecord.m_BitFlags.m_isTransparencyProtected)
{
PSAPI_LOG_WARNING("Layer", "Mismatch in parsing of protected layer settings detected. Expected both the layer to be locked and the transparency to be locked");
}
m_Opacity = layerRecord.m_Opacity;

// Generate our coordinates from the extents
Expand Down Expand Up @@ -438,7 +462,7 @@ struct Layer
m_BlendMode,
m_Opacity,
0u, // Clipping
LayerRecords::BitFlags(false, !m_IsVisible, false),
LayerRecords::BitFlags(m_IsLocked, !m_IsVisible, false),
std::nullopt, // LayerMaskData
Layer<T>::generateBlendingRanges(), // Generate some defaults
std::move(taggedBlocks) // Additional layer information
Expand Down
49 changes: 40 additions & 9 deletions PhotoshopAPI/src/LayeredFile/LayeredFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -321,18 +321,21 @@ namespace LayeredFileImpl

/// Recursively build a flat layer hierarchy
template <typename T>
void generateFlatLayersRecurse(const std::vector<std::shared_ptr<Layer<T>>>& nestedLayers, std::vector<std::shared_ptr<Layer<T>>>& flatLayers)
void generateFlatLayersRecurse(const std::vector<std::shared_ptr<Layer<T>>>& nestedLayers, std::vector<std::shared_ptr<Layer<T>>>& flatLayers, bool insertSectionDivider)
{
for (const auto& layer : nestedLayers)
{
// Recurse down if its a group layer
if (auto groupLayerPtr = std::dynamic_pointer_cast<GroupLayer<T>>(layer))
{
flatLayers.push_back(layer);
LayeredFileImpl::generateFlatLayersRecurse(groupLayerPtr->m_Layers, flatLayers);
LayeredFileImpl::generateFlatLayersRecurse(groupLayerPtr->m_Layers, flatLayers, insertSectionDivider);
// If the layer is a group we actually want to insert a section divider at the end of it. This makes reconstructing the layer
// hierarchy much easier later on. We dont actually need to give this a name
flatLayers.push_back(std::make_shared<SectionDividerLayer<T>>());
if (insertSectionDivider)
{
flatLayers.push_back(std::make_shared<SectionDividerLayer<T>>());
}
}
else
{
Expand All @@ -345,10 +348,10 @@ namespace LayeredFileImpl
/// Build a flat layer hierarchy from a nested layer structure and return this vector. Layer order
/// is not guaranteed
template <typename T>
std::vector<std::shared_ptr<Layer<T>>> generateFlatLayers(const std::vector<std::shared_ptr<Layer<T>>>& nestedLayers)
std::vector<std::shared_ptr<Layer<T>>> generateFlatLayers(const std::vector<std::shared_ptr<Layer<T>>>& nestedLayers, bool insertSectionDivider)
{
std::vector<std::shared_ptr<Layer<T>>> flatLayers;
LayeredFileImpl::generateFlatLayersRecurse(nestedLayers, flatLayers);
LayeredFileImpl::generateFlatLayersRecurse(nestedLayers, flatLayers, insertSectionDivider);

return flatLayers;
}
Expand Down Expand Up @@ -802,21 +805,49 @@ struct LayeredFile
{
std::vector<std::shared_ptr<Layer<T>>> layerVec;
layerVec.push_back(layer.value());
return LayeredFileImpl::generateFlatLayers(layerVec);
return LayeredFileImpl::generateFlatLayers(layerVec, true);
}
return LayeredFileImpl::generateFlatLayers(m_Layers);
return LayeredFileImpl::generateFlatLayers(m_Layers, true);
}
else if (order == LayerOrder::reverse)
{
if (layer.has_value())
{
std::vector<std::shared_ptr<Layer<T>>> layerVec;
layerVec.push_back(layer.value());
std::vector<std::shared_ptr<Layer<T>>> flatLayers = LayeredFileImpl::generateFlatLayers(layerVec);
std::vector<std::shared_ptr<Layer<T>>> flatLayers = LayeredFileImpl::generateFlatLayers(layerVec, true);
std::reverse(flatLayers.begin(), flatLayers.end());
return flatLayers;
}
std::vector<std::shared_ptr<Layer<T>>> flatLayers = LayeredFileImpl::generateFlatLayers(m_Layers);
std::vector<std::shared_ptr<Layer<T>>> flatLayers = LayeredFileImpl::generateFlatLayers(m_Layers, true);
std::reverse(flatLayers.begin(), flatLayers.end());
return flatLayers;
}
PSAPI_LOG_ERROR("LayeredFile", "Invalid layer order specified, only accepts forward or reverse");
return std::vector<std::shared_ptr<Layer<T>>>();
}


/// Get a view over the flattened layer stack, helpful for iterating and applying properties to all
/// layers such as visibility overrides etc.
///
/// After any layer modification actions this list may no longer be up-to-date so it would have to be re-generated.
/// It is highly discouraged to use this flattened layer vector for any layer hierarchy modifications
///
/// \param order The traversal order, forward would be equivalent to a top down traversal. Defaults to LayerOrder::forward
/// \param generateSectionDividers
/// Whether to generate a section divider layer at the end of each group.
/// Unless you intend to write this to Photoshop this should be false
/// \return The layer hierarchy as a flattened vector that can be iterated over.
std::vector<std::shared_ptr<Layer<T>>> flatLayers(const LayerOrder order = LayerOrder::forward, bool generateSectionDividers = false)
{
if (order == LayerOrder::forward)
{
return LayeredFileImpl::generateFlatLayers(m_Layers, false);
}
else if (order == LayerOrder::reverse)
{
std::vector<std::shared_ptr<Layer<T>>> flatLayers = LayeredFileImpl::generateFlatLayers(m_Layers, false);
std::reverse(flatLayers.begin(), flatLayers.end());
return flatLayers;
}
Expand Down
7 changes: 2 additions & 5 deletions PhotoshopAPI/src/PhotoshopFile/AdditionalLayerInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,9 @@ void AdditionalLayerInfo::read(File& document, const FileHeader& header, Progres
FileSection::size(FileSection::size() + toRead);
document.skip(toRead);
return;
}
else
{
PSAPI_LOG_WARNING("AdditionalLayerInfo", "Read too much data for the additional layer info, was allowed %" PRIu64 " but read %" PRIu64 " instead",
maxLength, maxLength - toRead);
}
PSAPI_LOG_WARNING("AdditionalLayerInfo", "Read too much data for the additional layer info, was allowed %" PRIu64 " but read %" PRIu64 " instead",
maxLength, maxLength - toRead);
}


Expand Down
62 changes: 62 additions & 0 deletions PhotoshopTest/src/TestLockedLayer/TestLockedLayer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#include "doctest.h"

#include "../DetectArmMac.h"

#include "Macros.h"
#include "LayeredFile/LayeredFile.h"
#include "LayeredFile/LayerTypes/ImageLayer.h"
#include "LayeredFile/LayerTypes/GroupLayer.h"

#include <string>
#include <vector>



// ---------------------------------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------------------------------
TEST_CASE("Create File with locked layers and then check if we can read them again")
{
using namespace NAMESPACE_PSAPI;
using type = bpp16_t;
constexpr int32_t width = 64;
constexpr int32_t height = 64;
constexpr int32_t size = width * height;

{
LayeredFile<type> file(Enum::ColorMode::RGB, width, height);
{
std::unordered_map<int16_t, std::vector<type>> data =
{
{0, std::vector<type>(size)},
{1, std::vector<type>(size)},
{2, std::vector<type>(size)},
};
auto params = typename Layer<type>::Params
{
.layerName = "Layer",
.width = width,
.height = height,
.isLocked = true,
};
auto layer = std::make_shared<ImageLayer<type>>(std::move(data), params);
file.addLayer(layer);
}
{
auto params = typename Layer<type>::Params
{
.layerName = "Group",
.isLocked = true
};
auto layer = std::make_shared<GroupLayer<type>>(params);
file.addLayer(layer);
}
LayeredFile<type>::write(std::move(file), "LockedLayerFile.psb");
}
{
auto file = LayeredFile<type>::read("LockedLayerFile.psb");
for (const auto& layer : file.flatLayers())
{
CHECK(layer->m_IsLocked);
}
}
}

0 comments on commit 28687a2

Please sign in to comment.