From 87d89ade105693c93982f131e897f3d9eb250806 Mon Sep 17 00:00:00 2001 From: Emil Dohne Date: Tue, 9 Jul 2024 22:02:01 +0200 Subject: [PATCH] move template function body into header file foregoing explicit template initialization due to compile errors on clang --- .../src/LayeredFile/LayerTypes/GroupLayer.cpp | 256 ------ .../src/LayeredFile/LayerTypes/GroupLayer.h | 209 ++++- .../src/LayeredFile/LayerTypes/ImageLayer.cpp | 472 ---------- .../src/LayeredFile/LayerTypes/ImageLayer.h | 428 ++++++++- .../src/LayeredFile/LayerTypes/Layer.cpp | 310 ------- .../src/LayeredFile/LayerTypes/Layer.h | 303 ++++++- .../LayerTypes/SectionDividerLayer.cpp | 72 -- .../LayerTypes/SectionDividerLayer.h | 55 +- PhotoshopAPI/src/LayeredFile/LayeredFile.cpp | 818 ------------------ PhotoshopAPI/src/LayeredFile/LayeredFile.h | 781 +++++++++++++++-- 10 files changed, 1641 insertions(+), 2063 deletions(-) delete mode 100644 PhotoshopAPI/src/LayeredFile/LayerTypes/GroupLayer.cpp delete mode 100644 PhotoshopAPI/src/LayeredFile/LayerTypes/ImageLayer.cpp delete mode 100644 PhotoshopAPI/src/LayeredFile/LayerTypes/Layer.cpp delete mode 100644 PhotoshopAPI/src/LayeredFile/LayerTypes/SectionDividerLayer.cpp diff --git a/PhotoshopAPI/src/LayeredFile/LayerTypes/GroupLayer.cpp b/PhotoshopAPI/src/LayeredFile/LayerTypes/GroupLayer.cpp deleted file mode 100644 index 7f88f276..00000000 --- a/PhotoshopAPI/src/LayeredFile/LayerTypes/GroupLayer.cpp +++ /dev/null @@ -1,256 +0,0 @@ -#include "GroupLayer.h" - -#include "Macros.h" -#include "Core/Struct/TaggedBlock.h" -#include "Core/Struct/TaggedBlockStorage.h" -#include "LayeredFile/LayeredFile.h" - - -PSAPI_NAMESPACE_BEGIN - - -// Instantiate the template types for GroupLayer -template struct GroupLayer; -template struct GroupLayer; -template struct GroupLayer; - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -void GroupLayer::addLayer(const LayeredFile& layeredFile, std::shared_ptr> layer) -{ - if (layeredFile.isLayerInDocument(layer)) - { - PSAPI_LOG_WARNING("GroupLayer", "Cannot insert a layer into the document twice, please use a unique layer. Skipping layer '%s'", layer->m_LayerName.c_str()); - return; - } - m_Layers.push_back(layer); -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -void GroupLayer::removeLayer(const int index) -{ - if (index >= m_Layers.size()) - { - PSAPI_LOG_WARNING("GroupLayer", "Cannot remove index %i from the group as it would exceed the amount of layers in the group", index); - return; - } - m_Layers.erase(m_Layers.begin() + index); -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -void GroupLayer::removeLayer(std::shared_ptr>& layer) -{ - int index = 0; - for (auto& sceneLayer : m_Layers) - { - if (layer == sceneLayer) - { - m_Layers.erase(m_Layers.begin() + index); - return; - } - ++index; - } - PSAPI_LOG_WARNING("GroupLayer", "Cannot remove layer %s from the group as it doesnt appear to be a child of the group", layer->m_LayerName.c_str()); -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -void GroupLayer::removeLayer(const std::string layerName) -{ - int index = 0; - for (auto& sceneLayer : m_Layers) - { - if (layerName == sceneLayer->m_LayerName) - { - m_Layers.erase(m_Layers.begin() + index); - return; - } - ++index; - } - PSAPI_LOG_WARNING("GroupLayer", "Cannot remove layer %s from the group as it doesnt appear to be a child of the group", layerName.c_str()); -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -std::tuple GroupLayer::toPhotoshop(const Enum::ColorMode colorMode, const FileHeader& header) -{ - PascalString lrName = Layer::generatePascalString(); - ChannelExtents extents = generateChannelExtents(ChannelCoordinates(Layer::m_Width, Layer::m_Height, Layer::m_CenterX, Layer::m_CenterY), header); - uint16_t channelCount = static_cast(Layer::m_LayerMask.has_value()); - uint8_t clipping = 0u; // No clipping mask for now - LayerRecords::BitFlags bitFlags = LayerRecords::BitFlags(false, !Layer::m_IsVisible, false); - std::optional lrMaskData = Layer::generateMaskData(header); - LayerRecords::LayerBlendingRanges blendingRanges = Layer::generateBlendingRanges(colorMode); - - - // Initialize the channelInfo. Note that if the data is to be compressed the channel size gets update - // again later - std::vector channelInfoVec; - std::vector> channelDataVec; - - // First extract our mask data, the order of our channels does not matter as long as the - // order of channelInfo and channelData is the same - auto maskData = Layer::extractLayerMask(); - if (maskData.has_value()) - { - channelInfoVec.push_back(std::get<0>(maskData.value())); - channelDataVec.push_back(std::move(std::get<1>(maskData.value()))); - } - - auto blockVec = this->generateTaggedBlocks(); - std::optional taggedBlocks = std::nullopt; - if (blockVec.size() > 0) - { - TaggedBlockStorage blockStorage = { blockVec }; - taggedBlocks.emplace(blockStorage); - } - - if (Layer::m_BlendMode != Enum::BlendMode::Passthrough) - { - LayerRecord lrRecord = LayerRecord( - lrName, - extents.top, - extents.left, - extents.bottom, - extents.right, - channelCount, - channelInfoVec, - Layer::m_BlendMode, - Layer::m_Opacity, - clipping, - bitFlags, - lrMaskData, - blendingRanges, - std::move(taggedBlocks) - ); - return std::make_tuple(std::move(lrRecord), ChannelImageData(std::move(channelDataVec))); - } - else - { - // If the group has a blendMode of Passthrough we actually need to pass that in the LrSectionDivider tagged block while the layers blendmode is set to normal - LayerRecord lrRecord = LayerRecord( - lrName, - extents.top, - extents.left, - extents.bottom, - extents.right, - channelCount, - channelInfoVec, - Enum::BlendMode::Normal, - Layer::m_Opacity, - clipping, - bitFlags, - lrMaskData, - blendingRanges, - std::move(taggedBlocks) - ); - return std::make_tuple(std::move(lrRecord), ChannelImageData(std::move(channelDataVec))); - } -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -GroupLayer::GroupLayer(const LayerRecord& layerRecord, ChannelImageData& channelImageData, const FileHeader& header) : Layer(layerRecord, channelImageData, header) -{ - // Because Photoshop stores the Passthrough blend mode on the layer section divider tagged block we must check if it present here - if (!layerRecord.m_AdditionalLayerInfo.has_value()) return; - const auto& taggedBlocks = layerRecord.m_AdditionalLayerInfo.value().m_TaggedBlocks; - const auto lrSectionBlockPtr = taggedBlocks.getTaggedBlockView(Enum::TaggedBlockKey::lrSectionDivider); - if (!lrSectionBlockPtr) return; - - if (lrSectionBlockPtr->m_BlendMode.has_value()) - { - Layer::m_BlendMode = lrSectionBlockPtr->m_BlendMode.value(); - } - if (lrSectionBlockPtr->m_Type == Enum::SectionDivider::ClosedFolder) - { - m_isCollapsed = true; - } -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -GroupLayer::GroupLayer(Layer::Params& layerParameters, bool isCollapsed /*= false*/) -{ - PROFILE_FUNCTION(); - Layer::m_LayerName = layerParameters.layerName; - Layer::m_BlendMode = layerParameters.blendMode; - Layer::m_Opacity = layerParameters.opacity; - Layer::m_IsVisible = true; - Layer::m_CenterX = layerParameters.posX; - Layer::m_CenterY = layerParameters.posY; - Layer::m_Width = layerParameters.width; - Layer::m_Height = layerParameters.height; - - - // Set the layer mask if present - if (layerParameters.layerMask.has_value()) - { - LayerMask mask{}; - Enum::ChannelIDInfo info{ .id = Enum::ChannelID::UserSuppliedLayerMask, .index = -2 }; - std::unique_ptr maskChannel = std::make_unique( - layerParameters.compression, - layerParameters.layerMask.value(), - info, - layerParameters.width, - layerParameters.height, - static_cast(layerParameters.posX), - static_cast(layerParameters.posY)); - mask.maskData = std::move(maskChannel); - Layer::m_LayerMask = std::move(mask); - } -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -std::vector> GroupLayer::generateTaggedBlocks() -{ - auto blockVec = Layer::generateTaggedBlocks(); - - LrSectionTaggedBlock sectionBlock; - if (m_isCollapsed) - { - if (Layer::m_BlendMode == Enum::BlendMode::Passthrough) - { - sectionBlock = LrSectionTaggedBlock(Enum::SectionDivider::ClosedFolder, std::make_optional(Enum::BlendMode::Passthrough)); - } - else - { - sectionBlock = LrSectionTaggedBlock(Enum::SectionDivider::ClosedFolder, std::nullopt); - } - } - else - { - if (Layer::m_BlendMode == Enum::BlendMode::Passthrough) - { - sectionBlock = LrSectionTaggedBlock(Enum::SectionDivider::OpenFolder, std::make_optional(Enum::BlendMode::Passthrough)); - } - else - { - sectionBlock = LrSectionTaggedBlock(Enum::SectionDivider::OpenFolder, std::nullopt); - } - } - blockVec.push_back(std::make_shared(sectionBlock)); - - return blockVec; -} - -PSAPI_NAMESPACE_END \ No newline at end of file diff --git a/PhotoshopAPI/src/LayeredFile/LayerTypes/GroupLayer.h b/PhotoshopAPI/src/LayeredFile/LayerTypes/GroupLayer.h index 47327392..262f58f2 100644 --- a/PhotoshopAPI/src/LayeredFile/LayerTypes/GroupLayer.h +++ b/PhotoshopAPI/src/LayeredFile/LayerTypes/GroupLayer.h @@ -10,6 +10,9 @@ #include "TextLayer.h" #include "AdjustmentLayer.h" #include "PhotoshopFile/LayerAndMaskInformation.h" +#include "Core/Struct/TaggedBlock.h" +#include "Core/Struct/TaggedBlockStorage.h" +#include "LayeredFile/LayeredFile.h" #include #include @@ -38,41 +41,231 @@ struct GroupLayer : public Layer /// \brief Adds a layer to the group, checking for duplicates in the process. /// \param layeredFile The layered file containing the group. /// \param layer The layer to be added. - void addLayer(const LayeredFile& layeredFile, std::shared_ptr> layer); + void addLayer(const LayeredFile& layeredFile, std::shared_ptr> layer) + { + if (layeredFile.isLayerInDocument(layer)) + { + PSAPI_LOG_WARNING("GroupLayer", "Cannot insert a layer into the document twice, please use a unique layer. Skipping layer '%s'", layer->m_LayerName.c_str()); + return; + } + m_Layers.push_back(layer); + } /// \brief Removes a layer at the given index from the group. /// \param index The index of the layer to be removed. - void removeLayer(const int index); + void removeLayer(const int index) + { + if (index >= m_Layers.size()) + { + PSAPI_LOG_WARNING("GroupLayer", "Cannot remove index %i from the group as it would exceed the amount of layers in the group", index); + return; + } + m_Layers.erase(m_Layers.begin() + index); + } /// \brief Removes the specified layer from the group. /// \param layer The layer to be removed. - void removeLayer(std::shared_ptr>& layer); + void removeLayer(std::shared_ptr>& layer) + { + int index = 0; + for (auto& sceneLayer : m_Layers) + { + if (layer == sceneLayer) + { + m_Layers.erase(m_Layers.begin() + index); + return; + } + ++index; + } + PSAPI_LOG_WARNING("GroupLayer", "Cannot remove layer %s from the group as it doesnt appear to be a child of the group", layer->m_LayerName.c_str()); + } /// \brief Removes the specified layer from the group. /// \param layerName The name of the layer to be removed - void removeLayer(const std::string layerName); + void removeLayer(const std::string layerName) + { + int index = 0; + for (auto& sceneLayer : m_Layers) + { + if (layerName == sceneLayer->m_LayerName) + { + m_Layers.erase(m_Layers.begin() + index); + return; + } + ++index; + } + PSAPI_LOG_WARNING("GroupLayer", "Cannot remove layer %s from the group as it doesnt appear to be a child of the group", layerName.c_str()); + } /// \brief Converts the group layer to Photoshop layerRecords and imageData. /// \param colorMode The color mode for the conversion. /// \param header The file header for the conversion. /// \return A tuple containing layerRecords and imageData. - std::tuple toPhotoshop(const Enum::ColorMode colorMode, const FileHeader& header) override; + std::tuple toPhotoshop(const Enum::ColorMode colorMode, const FileHeader& header) override + { + PascalString lrName = Layer::generatePascalString(); + ChannelExtents extents = generateChannelExtents(ChannelCoordinates(Layer::m_Width, Layer::m_Height, Layer::m_CenterX, Layer::m_CenterY), header); + uint16_t channelCount = static_cast(Layer::m_LayerMask.has_value()); + uint8_t clipping = 0u; // No clipping mask for now + LayerRecords::BitFlags bitFlags = LayerRecords::BitFlags(false, !Layer::m_IsVisible, false); + std::optional lrMaskData = Layer::generateMaskData(header); + LayerRecords::LayerBlendingRanges blendingRanges = Layer::generateBlendingRanges(colorMode); + + + // Initialize the channelInfo. Note that if the data is to be compressed the channel size gets update + // again later + std::vector channelInfoVec; + std::vector> channelDataVec; + + // First extract our mask data, the order of our channels does not matter as long as the + // order of channelInfo and channelData is the same + auto maskData = Layer::extractLayerMask(); + if (maskData.has_value()) + { + channelInfoVec.push_back(std::get<0>(maskData.value())); + channelDataVec.push_back(std::move(std::get<1>(maskData.value()))); + } + + auto blockVec = this->generateTaggedBlocks(); + std::optional taggedBlocks = std::nullopt; + if (blockVec.size() > 0) + { + TaggedBlockStorage blockStorage = { blockVec }; + taggedBlocks.emplace(blockStorage); + } + + if (Layer::m_BlendMode != Enum::BlendMode::Passthrough) + { + LayerRecord lrRecord = LayerRecord( + lrName, + extents.top, + extents.left, + extents.bottom, + extents.right, + channelCount, + channelInfoVec, + Layer::m_BlendMode, + Layer::m_Opacity, + clipping, + bitFlags, + lrMaskData, + blendingRanges, + std::move(taggedBlocks) + ); + return std::make_tuple(std::move(lrRecord), ChannelImageData(std::move(channelDataVec))); + } + else + { + // If the group has a blendMode of Passthrough we actually need to pass that in the LrSectionDivider tagged block while the layers blendmode is set to normal + LayerRecord lrRecord = LayerRecord( + lrName, + extents.top, + extents.left, + extents.bottom, + extents.right, + channelCount, + channelInfoVec, + Enum::BlendMode::Normal, + Layer::m_Opacity, + clipping, + bitFlags, + lrMaskData, + blendingRanges, + std::move(taggedBlocks) + ); + return std::make_tuple(std::move(lrRecord), ChannelImageData(std::move(channelDataVec))); + } + } /// \brief Constructs a GroupLayer using layerRecord, channelImageData, and file header. /// \param layerRecord The layer record for the group layer. /// \param channelImageData The channel image data for the group layer. /// \param header The file header for the group layer. - GroupLayer(const LayerRecord& layerRecord, ChannelImageData& channelImageData, const FileHeader& header); + GroupLayer(const LayerRecord& layerRecord, ChannelImageData& channelImageData, const FileHeader& header) + { + // Because Photoshop stores the Passthrough blend mode on the layer section divider tagged block we must check if it present here + if (!layerRecord.m_AdditionalLayerInfo.has_value()) return; + const auto& taggedBlocks = layerRecord.m_AdditionalLayerInfo.value().m_TaggedBlocks; + const auto lrSectionBlockPtr = taggedBlocks.getTaggedBlockView(Enum::TaggedBlockKey::lrSectionDivider); + if (!lrSectionBlockPtr) return; + + if (lrSectionBlockPtr->m_BlendMode.has_value()) + { + Layer::m_BlendMode = lrSectionBlockPtr->m_BlendMode.value(); + } + if (lrSectionBlockPtr->m_Type == Enum::SectionDivider::ClosedFolder) + { + m_isCollapsed = true; + } + } /// \brief Constructs a GroupLayer with the given layer parameters and collapse state. /// \param layerParameters The parameters for the group layer. /// \param isCollapsed Specifies whether the group layer is initially collapsed. - GroupLayer(Layer::Params& layerParameters, bool isCollapsed = false); + GroupLayer(Layer::Params& layerParameters, bool isCollapsed = false) + { + PROFILE_FUNCTION(); + Layer::m_LayerName = layerParameters.layerName; + Layer::m_BlendMode = layerParameters.blendMode; + Layer::m_Opacity = layerParameters.opacity; + Layer::m_IsVisible = true; + Layer::m_CenterX = layerParameters.posX; + Layer::m_CenterY = layerParameters.posY; + Layer::m_Width = layerParameters.width; + Layer::m_Height = layerParameters.height; + + + // Set the layer mask if present + if (layerParameters.layerMask.has_value()) + { + LayerMask mask{}; + Enum::ChannelIDInfo info{ .id = Enum::ChannelID::UserSuppliedLayerMask, .index = -2 }; + std::unique_ptr maskChannel = std::make_unique( + layerParameters.compression, + layerParameters.layerMask.value(), + info, + layerParameters.width, + layerParameters.height, + static_cast(layerParameters.posX), + static_cast(layerParameters.posY)); + mask.maskData = std::move(maskChannel); + Layer::m_LayerMask = std::move(mask); + } + } protected: /// \brief Generate the tagged blocks necessary for writing the layer - std::vector> generateTaggedBlocks() override; + std::vector> generateTaggedBlocks() override + { + auto blockVec = Layer::generateTaggedBlocks(); + LrSectionTaggedBlock sectionBlock; + if (m_isCollapsed) + { + if (Layer::m_BlendMode == Enum::BlendMode::Passthrough) + { + sectionBlock = LrSectionTaggedBlock(Enum::SectionDivider::ClosedFolder, std::make_optional(Enum::BlendMode::Passthrough)); + } + else + { + sectionBlock = LrSectionTaggedBlock(Enum::SectionDivider::ClosedFolder, std::nullopt); + } + } + else + { + if (Layer::m_BlendMode == Enum::BlendMode::Passthrough) + { + sectionBlock = LrSectionTaggedBlock(Enum::SectionDivider::OpenFolder, std::make_optional(Enum::BlendMode::Passthrough)); + } + else + { + sectionBlock = LrSectionTaggedBlock(Enum::SectionDivider::OpenFolder, std::nullopt); + } + } + blockVec.push_back(std::make_shared(sectionBlock)); + + return blockVec; + } }; diff --git a/PhotoshopAPI/src/LayeredFile/LayerTypes/ImageLayer.cpp b/PhotoshopAPI/src/LayeredFile/LayerTypes/ImageLayer.cpp deleted file mode 100644 index 8310f2f4..00000000 --- a/PhotoshopAPI/src/LayeredFile/LayerTypes/ImageLayer.cpp +++ /dev/null @@ -1,472 +0,0 @@ -#include "ImageLayer.h" - -#include "Layer.h" -#include "PhotoshopFile/LayerAndMaskInformation.h" - -#include -#include -#include -#include - -#define __STDC_FORMAT_MACROS 1 -#include - - -PSAPI_NAMESPACE_BEGIN - - -// Instantiate the template types for ImageLayer -template struct ImageLayer; -template struct ImageLayer; -template struct ImageLayer; - - -namespace -{ - // Check that the map of channels has the required amount of keys. For example for an RGB image R, G and B must be present. - // Returns false if a specific key is not found - // --------------------------------------------------------------------------------------------------------------------- - // --------------------------------------------------------------------------------------------------------------------- - bool checkChannelKeys(const std::unordered_map, Enum::ChannelIDInfoHasher>& data, const std::vector& requiredKeys) - { - for (const auto& requiredKey : requiredKeys) - { - const auto& it = data.find(requiredKey); - if (it == data.end()) - { - return false; - } - } - return true; - } - -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -std::tuple ImageLayer::toPhotoshop(const Enum::ColorMode colorMode, const FileHeader& header) -{ - PascalString lrName = Layer::generatePascalString(); - ChannelExtents extents = generateChannelExtents(ChannelCoordinates(Layer::m_Width, Layer::m_Height, Layer::m_CenterX, Layer::m_CenterY), header); - uint16_t channelCount = m_ImageData.size() + static_cast(Layer::m_LayerMask.has_value()); - - uint8_t clipping = 0u; // No clipping mask for now - LayerRecords::BitFlags bitFlags(false, !Layer::m_IsVisible, false); - std::optional lrMaskData = Layer::generateMaskData(header); - LayerRecords::LayerBlendingRanges blendingRanges = Layer::generateBlendingRanges(colorMode); - - // Generate our AdditionalLayerInfoSection. We dont need any special Tagged Blocks besides what is stored by the generic layer - auto blockVec = this->generateTaggedBlocks(); - std::optional taggedBlocks = std::nullopt; - if (blockVec.size() > 0) - { - TaggedBlockStorage blockStorage = { blockVec }; - taggedBlocks.emplace(blockStorage); - } - - // Initialize the channel information as well as the channel image data, the size held in the channelInfo might change depending on - // the compression mode chosen on export and must therefore be updated later. This step is done last as generateChannelImageData() invalidates - // all image data which we might need for operations above - auto channelData = this->generateChannelImageData(); - auto& channelInfoVec = std::get<0>(channelData); - ChannelImageData channelImgData = std::move(std::get<1>(channelData)); - - LayerRecord lrRecord = LayerRecord( - lrName, - extents.top, - extents.left, - extents.bottom, - extents.right, - channelCount, - channelInfoVec, - Layer::m_BlendMode, - Layer::m_Opacity, - clipping, - bitFlags, - lrMaskData, - blendingRanges, - std::move(taggedBlocks) - ); - - return std::make_tuple(std::move(lrRecord), std::move(channelImgData)); -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -ImageLayer::ImageLayer(const LayerRecord& layerRecord, ChannelImageData& channelImageData, const FileHeader& header) : - Layer(layerRecord, channelImageData, header) -{ - // Move the layers into our own layer representation - for (int i = 0; i < layerRecord.m_ChannelCount; ++i) - { - auto& channelInfo = layerRecord.m_ChannelInformation[i]; - - // We already extract masks ahead of time and skip them here to avoid raising warnings - if (channelInfo.m_ChannelID.id == Enum::ChannelID::UserSuppliedLayerMask) continue; - - auto channelPtr = channelImageData.extractImagePtr(channelInfo.m_ChannelID); - // Pointers might have already been released previously - if (!channelPtr) continue; - - // Insert any valid pointers to channels we have. We move to avoid having - // to uncompress / recompress - m_ImageData[channelInfo.m_ChannelID] = std::move(channelPtr); - } -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -std::tuple, ChannelImageData> ImageLayer::generateChannelImageData() -{ - std::vector channelInfoVec; - std::vector> channelDataVec; - - // First extract our mask data, the order of our channels does not matter as long as the - // order of channelInfo and channelData is the same - auto maskData = Layer::extractLayerMask(); - if (maskData.has_value()) - { - channelInfoVec.push_back(std::get<0>(maskData.value())); - channelDataVec.push_back(std::move(std::get<1>(maskData.value()))); - } - - // Extract all the channels next and push them into our data representation - for (auto& it : m_ImageData) - { - channelInfoVec.push_back(LayerRecords::ChannelInformation{ it.first, it.second->m_OrigByteSize }); - channelDataVec.push_back(std::move(it.second)); - } - - // Construct the channel image data from our vector of ptrs, moving gets handled by the constructor - ChannelImageData channelData(std::move(channelDataVec)); - - return std::make_tuple(channelInfoVec, std::move(channelData)); -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -ImageLayer::ImageLayer(std::unordered_map>&& imageData, Layer::Params& layerParameters) -{ - PROFILE_FUNCTION(); - Layer::m_LayerName = layerParameters.layerName; - if (layerParameters.blendMode == Enum::BlendMode::Passthrough) - { - PSAPI_LOG_WARNING("ImageLayer", "The Passthrough blend mode is reserved for groups, defaulting to 'Normal'"); - Layer::m_BlendMode = Enum::BlendMode::Normal; - } - else - { - Layer::m_BlendMode = layerParameters.blendMode; - } - Layer::m_Opacity = layerParameters.opacity; - Layer::m_IsVisible = true; - Layer::m_CenterX = layerParameters.posX; - Layer::m_CenterY = layerParameters.posY; - Layer::m_Width = layerParameters.width; - Layer::m_Height = layerParameters.height; - - - // Construct a ChannelIDInfo for each of the channels and then an ImageLayer instance to hold the data - for (auto& [key, value] : imageData) - { - Enum::ChannelIDInfo info = {}; - if (layerParameters.colorMode == Enum::ColorMode::RGB) - info = Enum::rgbChannelIDToChannelIDInfo(key); - else if (layerParameters.colorMode == Enum::ColorMode::CMYK) - info = Enum::cmykChannelIDToChannelIDInfo(key); - else if (layerParameters.colorMode == Enum::ColorMode::Grayscale) - info = Enum::grayscaleChannelIDToChannelIDInfo(key); - else - PSAPI_LOG_ERROR("ImageLayer", "Currently PhotoshopAPI only supports RGB, CMYK and Grayscale ColorMode"); - - // Channel sizes must match the size of the layer - if (static_cast(layerParameters.width) * layerParameters.height > value.size()) [[unlikely]] - { - PSAPI_LOG_ERROR("ImageLayer", "Size of ImageChannel does not match the size of width * height, got %" PRIu64 " but expected %" PRIu64 ".", - value.size(), - static_cast(layerParameters.width) * layerParameters.height); - } - m_ImageData[info] = std::make_unique( - layerParameters.compression, - value, - info, - layerParameters.width, - layerParameters.height, - static_cast(layerParameters.posX), - static_cast(layerParameters.posY) - ); - } - - // Check that the required keys are actually present. e.g. for an RGB colorMode the channels R, G and B must be present - if (layerParameters.colorMode == Enum::ColorMode::RGB) - { - Enum::ChannelIDInfo channelR = { .id = Enum::ChannelID::Red, .index = 0 }; - Enum::ChannelIDInfo channelG = { .id = Enum::ChannelID::Green, .index = 1 }; - Enum::ChannelIDInfo channelB = { .id = Enum::ChannelID::Blue, .index = 2 }; - std::vector channelVec = { channelR, channelG, channelB }; - bool hasRequiredChannels = checkChannelKeys(m_ImageData, channelVec); - if (!hasRequiredChannels) - { - PSAPI_LOG_ERROR("ImageLayer", "For RGB ColorMode R, G and B channels need to be specified"); - } - } - else if (layerParameters.colorMode == Enum::ColorMode::CMYK) - { - Enum::ChannelIDInfo channelC = { .id = Enum::ChannelID::Cyan, .index = 0 }; - Enum::ChannelIDInfo channelM = { .id = Enum::ChannelID::Magenta, .index = 1 }; - Enum::ChannelIDInfo channelY = { .id = Enum::ChannelID::Yellow, .index = 2 }; - Enum::ChannelIDInfo channelK = { .id = Enum::ChannelID::Black, .index = 3 }; - std::vector channelVec = { channelC, channelM, channelY, channelK }; - bool hasRequiredChannels = checkChannelKeys(m_ImageData, channelVec); - if (!hasRequiredChannels) - { - PSAPI_LOG_ERROR("ImageLayer", "For CMYK ColorMode C, M, Y and K channels need to be specified"); - } - } - else if (layerParameters.colorMode == Enum::ColorMode::Grayscale) - { - Enum::ChannelIDInfo channelG = { .id = Enum::ChannelID::Gray, .index = 0 }; - std::vector channelVec = { channelG }; - bool hasRequiredChannels = checkChannelKeys(m_ImageData, { channelG }); - if (!hasRequiredChannels) - { - PSAPI_LOG_ERROR("ImageLayer", "For Grayscale ColorMode Gray channel needs to be specified"); - } - } - - - // Set the layer mask - if (layerParameters.layerMask.has_value()) - { - LayerMask mask{}; - Enum::ChannelIDInfo info{ .id = Enum::ChannelID::UserSuppliedLayerMask, .index = -2 }; - mask.maskData = std::make_unique( - layerParameters.compression, - layerParameters.layerMask.value(), - info, - layerParameters.width, - layerParameters.height, - static_cast(layerParameters.posX), - static_cast(layerParameters.posY) - ); - Layer::m_LayerMask = std::move(mask); - } -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -ImageLayer::ImageLayer(std::unordered_map>&& imageData, Layer::Params& layerParameters) -{ - PROFILE_FUNCTION(); - Layer::m_LayerName = layerParameters.layerName; - if (layerParameters.blendMode == Enum::BlendMode::Passthrough) - { - PSAPI_LOG_WARNING("ImageLayer", "The Passthrough blend mode is reserved for groups, defaulting to 'Normal'"); - Layer::m_BlendMode = Enum::BlendMode::Normal; - } - else - { - Layer::m_BlendMode = layerParameters.blendMode; - } - Layer::m_Opacity = layerParameters.opacity; - Layer::m_IsVisible = true; - Layer::m_CenterX = layerParameters.posX; - Layer::m_CenterY = layerParameters.posY; - Layer::m_Width = layerParameters.width; - Layer::m_Height = layerParameters.height; - - // Construct a ChannelIDInfo for each of the channels and then an ImageLayer instance to hold the data - for (auto& [key, value] : imageData) - { - Enum::ChannelIDInfo info = {}; - if (layerParameters.colorMode == Enum::ColorMode::RGB) - info = Enum::rgbIntToChannelID(key); - else if (layerParameters.colorMode == Enum::ColorMode::CMYK) - info = Enum::cmykIntToChannelID(key); - else if (layerParameters.colorMode == Enum::ColorMode::Grayscale) - info = Enum::grayscaleIntToChannelID(key); - else - PSAPI_LOG_ERROR("ImageLayer", "Currently PhotoshopAPI only supports RGB, CMYK and Grayscale ColorMode"); - - // Channel sizes must match the size of the layer - if (static_cast(layerParameters.width) * layerParameters.height > value.size()) [[unlikely]] - { - PSAPI_LOG_ERROR("ImageLayer", "Size of ImageChannel does not match the size of width * height, got %" PRIu64 " but expected %" PRIu64 ".", - value.size(), - static_cast(layerParameters.width) * layerParameters.height); - } - m_ImageData[info] = std::make_unique( - layerParameters.compression, - value, - info, - layerParameters.width, - layerParameters.height, - static_cast(layerParameters.posX), - static_cast(layerParameters.posY) - ); - } - - // Check that the required keys are actually present. e.g. for an RGB colorMode the channels R, G and B must be present - if (layerParameters.colorMode == Enum::ColorMode::RGB) - { - Enum::ChannelIDInfo channelR = { .id = Enum::ChannelID::Red, .index = 0 }; - Enum::ChannelIDInfo channelG = { .id = Enum::ChannelID::Green, .index = 1 }; - Enum::ChannelIDInfo channelB = { .id = Enum::ChannelID::Blue, .index = 2 }; - std::vector channelVec = { channelR, channelG, channelB}; - bool hasRequiredChannels = checkChannelKeys(m_ImageData, channelVec); - if (!hasRequiredChannels) - { - PSAPI_LOG_ERROR("ImageLayer", "For RGB ColorMode R, G and B channels need to be specified"); - } - } - else if (layerParameters.colorMode == Enum::ColorMode::CMYK) - { - Enum::ChannelIDInfo channelC = { .id = Enum::ChannelID::Cyan, .index = 0 }; - Enum::ChannelIDInfo channelM = { .id = Enum::ChannelID::Magenta, .index = 1 }; - Enum::ChannelIDInfo channelY = { .id = Enum::ChannelID::Yellow, .index = 2 }; - Enum::ChannelIDInfo channelK = { .id = Enum::ChannelID::Black, .index = 3 }; - std::vector channelVec = { channelC, channelM, channelY, channelK }; - bool hasRequiredChannels = checkChannelKeys(m_ImageData, channelVec); - if (!hasRequiredChannels) - { - PSAPI_LOG_ERROR("ImageLayer", "For CMYK ColorMode C, M, Y and K channels need to be specified"); - } - } - else if (layerParameters.colorMode == Enum::ColorMode::Grayscale) - { - Enum::ChannelIDInfo channelG = { .id = Enum::ChannelID::Gray, .index = 0 }; - std::vector channelVec = { channelG }; - bool hasRequiredChannels = checkChannelKeys(m_ImageData, { channelG }); - if (!hasRequiredChannels) - { - PSAPI_LOG_ERROR("ImageLayer", "For Grayscale ColorMode Gray channel needs to be specified"); - } - } - - - // Set the layer mask - if (layerParameters.layerMask.has_value()) - { - LayerMask mask{}; - Enum::ChannelIDInfo info{ .id = Enum::ChannelID::UserSuppliedLayerMask, .index = -2 }; - mask.maskData = std::make_unique( - layerParameters.compression, - layerParameters.layerMask.value(), - info, - layerParameters.width, - layerParameters.height, - static_cast(layerParameters.posX), - static_cast(layerParameters.posY) - ); - Layer::m_LayerMask = std::move(mask); - } -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -std::vector ImageLayer::getChannel(const Enum::ChannelID channelID, bool doCopy /*= true*/) -{ - if (channelID == Enum::ChannelID::UserSuppliedLayerMask) - { - return this->getMaskData(doCopy); - } - for (auto& [key, value] : m_ImageData) - { - if (key.id == channelID) - { - if (doCopy) - return value->template getData(); - else - return value->template extractData(); - } - } - PSAPI_LOG_WARNING("ImageLayer", "Unable to find channel in ImageData, returning an empty vector"); - return std::vector(); -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -std::vector ImageLayer::getChannel(const int16_t channelIndex, bool doCopy /*= true*/) -{ - if (channelIndex == -2) - { - return this->getMaskData(doCopy); - } - for (auto& [key, value] : m_ImageData) - { - if (key.index == channelIndex) - { - if (doCopy) - return value->template getData(); - else - return value->template extractData(); - } - } - PSAPI_LOG_WARNING("ImageLayer", "Unable to find channel in ImageData, returning an empty vector"); - return std::vector(); -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -std::unordered_map, Enum::ChannelIDInfoHasher> ImageLayer::getImageData(bool doCopy /*= true*/) -{ - std::unordered_map, Enum::ChannelIDInfoHasher> imgData; - - if (Layer::m_LayerMask.has_value()) - { - Enum::ChannelIDInfo maskInfo; - maskInfo.id = Enum::ChannelID::UserSuppliedLayerMask; - maskInfo.index = -2; - imgData[maskInfo] = Layer::getMaskData(doCopy); - } - - if (doCopy) - { - for (auto& [key, value] : m_ImageData) - { - imgData[key] = std::move(value->template getData()); - } - } - else - { - for (auto& [key, value] : m_ImageData) - { - imgData[key] = std::move(value->template extractData()); - } - } - return imgData; -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -void ImageLayer::setCompression(const Enum::Compression compCode) -{ - // Change the mask channels' compression codec - Layer::setCompression(compCode); - // Change the image channel compression codecs - for (const auto& [key, val] : m_ImageData) - { - m_ImageData[key]->m_Compression = compCode; - } -} - - -PSAPI_NAMESPACE_END \ No newline at end of file diff --git a/PhotoshopAPI/src/LayeredFile/LayerTypes/ImageLayer.h b/PhotoshopAPI/src/LayeredFile/LayerTypes/ImageLayer.h index 2745c049..1056c232 100644 --- a/PhotoshopAPI/src/LayeredFile/LayerTypes/ImageLayer.h +++ b/PhotoshopAPI/src/LayeredFile/LayerTypes/ImageLayer.h @@ -6,11 +6,40 @@ #include "Core/Struct/ImageChannel.h" #include "PhotoshopFile/LayerAndMaskInformation.h" +#include #include +#include +#include + +#define __STDC_FORMAT_MACROS 1 +#include + PSAPI_NAMESPACE_BEGIN +namespace +{ + // Check that the map of channels has the required amount of keys. For example for an RGB image R, G and B must be present. + // Returns false if a specific key is not found + // --------------------------------------------------------------------------------------------------------------------- + // --------------------------------------------------------------------------------------------------------------------- + bool checkChannelKeys(const std::unordered_map, Enum::ChannelIDInfoHasher>& data, const std::vector& requiredKeys) + { + for (const auto& requiredKey : requiredKeys) + { + const auto& it = data.find(requiredKey); + if (it == data.end()) + { + return false; + } + } + return true; + } + +} + + // A pixel based image layer template struct ImageLayer : public Layer @@ -18,43 +47,418 @@ struct ImageLayer : public Layer /// Store the image data as a per-channel map to be used later using a custom hash function std::unordered_map, Enum::ChannelIDInfoHasher> m_ImageData; +private: + // Extracts the m_ImageData as well as the layer mask into two vectors holding channel information as well as the image data + // itself. This also takes care of generating our layer mask channel if it is present. Invalidates any data held by the ImageLayer + std::tuple, ChannelImageData> generateChannelImageData() + { + std::vector channelInfoVec; + std::vector> channelDataVec; + + // First extract our mask data, the order of our channels does not matter as long as the + // order of channelInfo and channelData is the same + auto maskData = Layer::extractLayerMask(); + if (maskData.has_value()) + { + channelInfoVec.push_back(std::get<0>(maskData.value())); + channelDataVec.push_back(std::move(std::get<1>(maskData.value()))); + } + + // Extract all the channels next and push them into our data representation + for (auto& it : m_ImageData) + { + channelInfoVec.push_back(LayerRecords::ChannelInformation{ it.first, it.second->m_OrigByteSize }); + channelDataVec.push_back(std::move(it.second)); + } + + // Construct the channel image data from our vector of ptrs, moving gets handled by the constructor + ChannelImageData channelData(std::move(channelDataVec)); + + return std::make_tuple(channelInfoVec, std::move(channelData)); + } + +public: + /// Generate a photoshop layerRecord and imageData based on the current layer - std::tuple toPhotoshop(const Enum::ColorMode colorMode, const FileHeader& header) override; + std::tuple toPhotoshop(const Enum::ColorMode colorMode, const FileHeader& header) override + { + PascalString lrName = Layer::generatePascalString(); + ChannelExtents extents = generateChannelExtents(ChannelCoordinates(Layer::m_Width, Layer::m_Height, Layer::m_CenterX, Layer::m_CenterY), header); + uint16_t channelCount = m_ImageData.size() + static_cast(Layer::m_LayerMask.has_value()); + + uint8_t clipping = 0u; // No clipping mask for now + LayerRecords::BitFlags bitFlags(false, !Layer::m_IsVisible, false); + std::optional lrMaskData = Layer::generateMaskData(header); + LayerRecords::LayerBlendingRanges blendingRanges = Layer::generateBlendingRanges(colorMode); + + // Generate our AdditionalLayerInfoSection. We dont need any special Tagged Blocks besides what is stored by the generic layer + auto blockVec = this->generateTaggedBlocks(); + std::optional taggedBlocks = std::nullopt; + if (blockVec.size() > 0) + { + TaggedBlockStorage blockStorage = { blockVec }; + taggedBlocks.emplace(blockStorage); + } + + // Initialize the channel information as well as the channel image data, the size held in the channelInfo might change depending on + // the compression mode chosen on export and must therefore be updated later. This step is done last as generateChannelImageData() invalidates + // all image data which we might need for operations above + auto channelData = this->generateChannelImageData(); + auto& channelInfoVec = std::get<0>(channelData); + ChannelImageData channelImgData = std::move(std::get<1>(channelData)); + + LayerRecord lrRecord = LayerRecord( + lrName, + extents.top, + extents.left, + extents.bottom, + extents.right, + channelCount, + channelInfoVec, + Layer::m_BlendMode, + Layer::m_Opacity, + clipping, + bitFlags, + lrMaskData, + blendingRanges, + std::move(taggedBlocks) + ); + + return std::make_tuple(std::move(lrRecord), std::move(channelImgData)); + } /// Initialize our imageLayer by first parsing the base Layer instance and then moving /// the additional channels into our representation - ImageLayer(const LayerRecord& layerRecord, ChannelImageData& channelImageData, const FileHeader& header); + ImageLayer(const LayerRecord& layerRecord, ChannelImageData& channelImageData, const FileHeader& header) + { + // Move the layers into our own layer representation + for (int i = 0; i < layerRecord.m_ChannelCount; ++i) + { + auto& channelInfo = layerRecord.m_ChannelInformation[i]; + + // We already extract masks ahead of time and skip them here to avoid raising warnings + if (channelInfo.m_ChannelID.id == Enum::ChannelID::UserSuppliedLayerMask) continue; + + auto channelPtr = channelImageData.extractImagePtr(channelInfo.m_ChannelID); + // Pointers might have already been released previously + if (!channelPtr) continue; + + // Insert any valid pointers to channels we have. We move to avoid having + // to uncompress / recompress + m_ImageData[channelInfo.m_ChannelID] = std::move(channelPtr); + } + } /// Generate an ImageLayer instance ready to be used in a LayeredFile document. - ImageLayer(std::unordered_map>&& imageData, Layer::Params& layerParameters); + ImageLayer(std::unordered_map>&& imageData, Layer::Params& layerParameters) + { + PROFILE_FUNCTION(); + Layer::m_LayerName = layerParameters.layerName; + if (layerParameters.blendMode == Enum::BlendMode::Passthrough) + { + PSAPI_LOG_WARNING("ImageLayer", "The Passthrough blend mode is reserved for groups, defaulting to 'Normal'"); + Layer::m_BlendMode = Enum::BlendMode::Normal; + } + else + { + Layer::m_BlendMode = layerParameters.blendMode; + } + Layer::m_Opacity = layerParameters.opacity; + Layer::m_IsVisible = true; + Layer::m_CenterX = layerParameters.posX; + Layer::m_CenterY = layerParameters.posY; + Layer::m_Width = layerParameters.width; + Layer::m_Height = layerParameters.height; + + + // Construct a ChannelIDInfo for each of the channels and then an ImageLayer instance to hold the data + for (auto& [key, value] : imageData) + { + Enum::ChannelIDInfo info = {}; + if (layerParameters.colorMode == Enum::ColorMode::RGB) + info = Enum::rgbChannelIDToChannelIDInfo(key); + else if (layerParameters.colorMode == Enum::ColorMode::CMYK) + info = Enum::cmykChannelIDToChannelIDInfo(key); + else if (layerParameters.colorMode == Enum::ColorMode::Grayscale) + info = Enum::grayscaleChannelIDToChannelIDInfo(key); + else + PSAPI_LOG_ERROR("ImageLayer", "Currently PhotoshopAPI only supports RGB, CMYK and Grayscale ColorMode"); + + // Channel sizes must match the size of the layer + if (static_cast(layerParameters.width) * layerParameters.height > value.size()) [[unlikely]] + { + PSAPI_LOG_ERROR("ImageLayer", "Size of ImageChannel does not match the size of width * height, got %" PRIu64 " but expected %" PRIu64 ".", + value.size(), + static_cast(layerParameters.width) * layerParameters.height); + } + m_ImageData[info] = std::make_unique( + layerParameters.compression, + value, + info, + layerParameters.width, + layerParameters.height, + static_cast(layerParameters.posX), + static_cast(layerParameters.posY) + ); + } + + // Check that the required keys are actually present. e.g. for an RGB colorMode the channels R, G and B must be present + if (layerParameters.colorMode == Enum::ColorMode::RGB) + { + Enum::ChannelIDInfo channelR = { .id = Enum::ChannelID::Red, .index = 0 }; + Enum::ChannelIDInfo channelG = { .id = Enum::ChannelID::Green, .index = 1 }; + Enum::ChannelIDInfo channelB = { .id = Enum::ChannelID::Blue, .index = 2 }; + std::vector channelVec = { channelR, channelG, channelB }; + bool hasRequiredChannels = checkChannelKeys(m_ImageData, channelVec); + if (!hasRequiredChannels) + { + PSAPI_LOG_ERROR("ImageLayer", "For RGB ColorMode R, G and B channels need to be specified"); + } + } + else if (layerParameters.colorMode == Enum::ColorMode::CMYK) + { + Enum::ChannelIDInfo channelC = { .id = Enum::ChannelID::Cyan, .index = 0 }; + Enum::ChannelIDInfo channelM = { .id = Enum::ChannelID::Magenta, .index = 1 }; + Enum::ChannelIDInfo channelY = { .id = Enum::ChannelID::Yellow, .index = 2 }; + Enum::ChannelIDInfo channelK = { .id = Enum::ChannelID::Black, .index = 3 }; + std::vector channelVec = { channelC, channelM, channelY, channelK }; + bool hasRequiredChannels = checkChannelKeys(m_ImageData, channelVec); + if (!hasRequiredChannels) + { + PSAPI_LOG_ERROR("ImageLayer", "For CMYK ColorMode C, M, Y and K channels need to be specified"); + } + } + else if (layerParameters.colorMode == Enum::ColorMode::Grayscale) + { + Enum::ChannelIDInfo channelG = { .id = Enum::ChannelID::Gray, .index = 0 }; + std::vector channelVec = { channelG }; + bool hasRequiredChannels = checkChannelKeys(m_ImageData, { channelG }); + if (!hasRequiredChannels) + { + PSAPI_LOG_ERROR("ImageLayer", "For Grayscale ColorMode Gray channel needs to be specified"); + } + } + + + // Set the layer mask + if (layerParameters.layerMask.has_value()) + { + LayerMask mask{}; + Enum::ChannelIDInfo info{ .id = Enum::ChannelID::UserSuppliedLayerMask, .index = -2 }; + mask.maskData = std::make_unique( + layerParameters.compression, + layerParameters.layerMask.value(), + info, + layerParameters.width, + layerParameters.height, + static_cast(layerParameters.posX), + static_cast(layerParameters.posY) + ); + Layer::m_LayerMask = std::move(mask); + } + } + /// Generate an ImageLayer instance ready to be used in a LayeredFile document. - ImageLayer(std::unordered_map>&& imageData, Layer::Params& layerParameters); + ImageLayer(std::unordered_map>&& imageData, Layer::Params& layerParameters) + { + PROFILE_FUNCTION(); + Layer::m_LayerName = layerParameters.layerName; + if (layerParameters.blendMode == Enum::BlendMode::Passthrough) + { + PSAPI_LOG_WARNING("ImageLayer", "The Passthrough blend mode is reserved for groups, defaulting to 'Normal'"); + Layer::m_BlendMode = Enum::BlendMode::Normal; + } + else + { + Layer::m_BlendMode = layerParameters.blendMode; + } + Layer::m_Opacity = layerParameters.opacity; + Layer::m_IsVisible = true; + Layer::m_CenterX = layerParameters.posX; + Layer::m_CenterY = layerParameters.posY; + Layer::m_Width = layerParameters.width; + Layer::m_Height = layerParameters.height; + + // Construct a ChannelIDInfo for each of the channels and then an ImageLayer instance to hold the data + for (auto& [key, value] : imageData) + { + Enum::ChannelIDInfo info = {}; + if (layerParameters.colorMode == Enum::ColorMode::RGB) + info = Enum::rgbIntToChannelID(key); + else if (layerParameters.colorMode == Enum::ColorMode::CMYK) + info = Enum::cmykIntToChannelID(key); + else if (layerParameters.colorMode == Enum::ColorMode::Grayscale) + info = Enum::grayscaleIntToChannelID(key); + else + PSAPI_LOG_ERROR("ImageLayer", "Currently PhotoshopAPI only supports RGB, CMYK and Grayscale ColorMode"); + + // Channel sizes must match the size of the layer + if (static_cast(layerParameters.width) * layerParameters.height > value.size()) [[unlikely]] + { + PSAPI_LOG_ERROR("ImageLayer", "Size of ImageChannel does not match the size of width * height, got %" PRIu64 " but expected %" PRIu64 ".", + value.size(), + static_cast(layerParameters.width) * layerParameters.height); + } + m_ImageData[info] = std::make_unique( + layerParameters.compression, + value, + info, + layerParameters.width, + layerParameters.height, + static_cast(layerParameters.posX), + static_cast(layerParameters.posY) + ); + } + + // Check that the required keys are actually present. e.g. for an RGB colorMode the channels R, G and B must be present + if (layerParameters.colorMode == Enum::ColorMode::RGB) + { + Enum::ChannelIDInfo channelR = { .id = Enum::ChannelID::Red, .index = 0 }; + Enum::ChannelIDInfo channelG = { .id = Enum::ChannelID::Green, .index = 1 }; + Enum::ChannelIDInfo channelB = { .id = Enum::ChannelID::Blue, .index = 2 }; + std::vector channelVec = { channelR, channelG, channelB}; + bool hasRequiredChannels = checkChannelKeys(m_ImageData, channelVec); + if (!hasRequiredChannels) + { + PSAPI_LOG_ERROR("ImageLayer", "For RGB ColorMode R, G and B channels need to be specified"); + } + } + else if (layerParameters.colorMode == Enum::ColorMode::CMYK) + { + Enum::ChannelIDInfo channelC = { .id = Enum::ChannelID::Cyan, .index = 0 }; + Enum::ChannelIDInfo channelM = { .id = Enum::ChannelID::Magenta, .index = 1 }; + Enum::ChannelIDInfo channelY = { .id = Enum::ChannelID::Yellow, .index = 2 }; + Enum::ChannelIDInfo channelK = { .id = Enum::ChannelID::Black, .index = 3 }; + std::vector channelVec = { channelC, channelM, channelY, channelK }; + bool hasRequiredChannels = checkChannelKeys(m_ImageData, channelVec); + if (!hasRequiredChannels) + { + PSAPI_LOG_ERROR("ImageLayer", "For CMYK ColorMode C, M, Y and K channels need to be specified"); + } + } + else if (layerParameters.colorMode == Enum::ColorMode::Grayscale) + { + Enum::ChannelIDInfo channelG = { .id = Enum::ChannelID::Gray, .index = 0 }; + std::vector channelVec = { channelG }; + bool hasRequiredChannels = checkChannelKeys(m_ImageData, { channelG }); + if (!hasRequiredChannels) + { + PSAPI_LOG_ERROR("ImageLayer", "For Grayscale ColorMode Gray channel needs to be specified"); + } + } + + + // Set the layer mask + if (layerParameters.layerMask.has_value()) + { + LayerMask mask{}; + Enum::ChannelIDInfo info{ .id = Enum::ChannelID::UserSuppliedLayerMask, .index = -2 }; + mask.maskData = std::make_unique( + layerParameters.compression, + layerParameters.layerMask.value(), + info, + layerParameters.width, + layerParameters.height, + static_cast(layerParameters.posX), + static_cast(layerParameters.posY) + ); + Layer::m_LayerMask = std::move(mask); + } + } /// Extract a specified channel from the layer given its channel ID. This also works for masks /// /// \param channelID the channel ID to extract /// \param doCopy whether to extract the channel by copying the data. If this is false the channel will no longer hold any image data! - std::vector getChannel(const Enum::ChannelID channelID, bool doCopy = true); + std::vector getChannel(const Enum::ChannelID channelID, bool doCopy = true) + { + if (channelID == Enum::ChannelID::UserSuppliedLayerMask) + { + return this->getMaskData(doCopy); + } + for (auto& [key, value] : m_ImageData) + { + if (key.id == channelID) + { + if (doCopy) + return value->template getData(); + else + return value->template extractData(); + } + } + PSAPI_LOG_WARNING("ImageLayer", "Unable to find channel in ImageData, returning an empty vector"); + return std::vector(); + } /// Extract a specified channel from the layer given its channel ID. This also works for masks /// /// \param channelIndex the channel index to extract /// \param doCopy whether to extract the channel by copying the data. If this is false the channel will no longer hold any image data! - std::vector getChannel(const int16_t channelIndex, bool doCopy = true); + std::vector getChannel(const int16_t channelIndex, bool doCopy = true) + { + if (channelIndex == -2) + { + return this->getMaskData(doCopy); + } + for (auto& [key, value] : m_ImageData) + { + if (key.index == channelIndex) + { + if (doCopy) + return value->template getData(); + else + return value->template extractData(); + } + } + PSAPI_LOG_WARNING("ImageLayer", "Unable to find channel in ImageData, returning an empty vector"); + return std::vector(); + } /// Extract all the channels of the ImageLayer into an unordered_map. Includes the mask channel /// /// \param doCopy whether to extract the image data by copying the data. If this is false the channel will no longer hold any image data! - std::unordered_map, Enum::ChannelIDInfoHasher> getImageData(bool doCopy = true); + std::unordered_map, Enum::ChannelIDInfoHasher> getImageData(bool doCopy = true) + { + std::unordered_map, Enum::ChannelIDInfoHasher> imgData; + if (Layer::m_LayerMask.has_value()) + { + Enum::ChannelIDInfo maskInfo; + maskInfo.id = Enum::ChannelID::UserSuppliedLayerMask; + maskInfo.index = -2; + imgData[maskInfo] = Layer::getMaskData(doCopy); + } + + if (doCopy) + { + for (auto& [key, value] : m_ImageData) + { + imgData[key] = std::move(value->template getData()); + } + } + else + { + for (auto& [key, value] : m_ImageData) + { + imgData[key] = std::move(value->template extractData()); + } + } + return imgData; + } /// Change the compression codec of all the image channels - void setCompression(const Enum::Compression compCode) override; + void setCompression(const Enum::Compression compCode) override + { + // Change the mask channels' compression codec + Layer::setCompression(compCode); + // Change the image channel compression codecs + for (const auto& [key, val] : m_ImageData) + { + m_ImageData[key]->m_Compression = compCode; + } + } + -private: - // Extracts the m_ImageData as well as the layer mask into two vectors holding channel information as well as the image data - // itself. This also takes care of generating our layer mask channel if it is present. Invalidates any data held by the ImageLayer - std::tuple, ChannelImageData> generateChannelImageData(); }; PSAPI_NAMESPACE_END \ No newline at end of file diff --git a/PhotoshopAPI/src/LayeredFile/LayerTypes/Layer.cpp b/PhotoshopAPI/src/LayeredFile/LayerTypes/Layer.cpp deleted file mode 100644 index 518c23e7..00000000 --- a/PhotoshopAPI/src/LayeredFile/LayerTypes/Layer.cpp +++ /dev/null @@ -1,310 +0,0 @@ -#include "Layer.h" - -#include "Macros.h" -#include "Enum.h" -#include "PhotoshopFile/LayerAndMaskInformation.h" -#include "PhotoshopFile/AdditionalLayerInfo.h" -#include "Core/Struct/TaggedBlock.h" - -#include -#include -#include -#include - -PSAPI_NAMESPACE_BEGIN - - -// Instantiate the template types for Layer -template struct Layer; -template struct Layer; -template struct Layer; - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -Layer::Layer(const LayerRecord& layerRecord, ChannelImageData& channelImageData, const FileHeader& header) -{ - m_LayerName = layerRecord.m_LayerName.getString(); - // To parse the blend mode we must actually check for the presence of the sectionDivider blendMode as this overrides the layerRecord - // blendmode if it is present - if (!layerRecord.m_AdditionalLayerInfo.has_value()) - { - // Short circuit if no additional layer info is present - m_BlendMode = layerRecord.m_BlendMode; - } - else - { - auto& additionalLayerInfo = layerRecord.m_AdditionalLayerInfo.value(); - auto sectionDivider = additionalLayerInfo.getTaggedBlock(Enum::TaggedBlockKey::lrSectionDivider); - if (sectionDivider.has_value() && sectionDivider.value()->m_BlendMode.has_value()) - { - m_BlendMode = sectionDivider.value()->m_BlendMode.value(); - } - else - { - m_BlendMode = layerRecord.m_BlendMode; - } - } - // 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; - m_Opacity = layerRecord.m_Opacity; - - // Generate our coordinates from the extents - ChannelExtents extents{ layerRecord.m_Top, layerRecord.m_Left, layerRecord.m_Bottom, layerRecord.m_Right }; - ChannelCoordinates coordinates = generateChannelCoordinates(extents, header); - m_Width = coordinates.width; - m_Height = coordinates.height; - m_CenterX = coordinates.centerX; - m_CenterY = coordinates.centerY; - - // Move the layer mask into our layerMask struct, for now this only does pixel masks - for (int i = 0; i < layerRecord.m_ChannelCount; ++i) - { - auto& channelInfo = layerRecord.m_ChannelInformation[i]; - if (channelInfo.m_ChannelID.id == Enum::ChannelID::UserSuppliedLayerMask) - { - // Move the compressed image data into our LayerMask struct - LayerMask lrMask{}; - auto channelPtr = channelImageData.extractImagePtr(channelInfo.m_ChannelID); - if (channelPtr) - lrMask.maskData = std::move(channelPtr); - else - PSAPI_LOG_ERROR("Layer", "Unable to extract mask channel for layer '%s'", m_LayerName.c_str()); - channelPtr = nullptr; - - // If no mask parameters are present we just use sensible defaults and skip - if (!layerRecord.m_LayerMaskData.has_value()) continue; - auto& maskParams = layerRecord.m_LayerMaskData.value(); - - // Read the mask parameters - if (!maskParams.m_LayerMask.has_value()) continue; - auto& layerMaskParams = maskParams.m_LayerMask.value(); - - lrMask.isDisabled = layerMaskParams.m_Disabled; - lrMask.isMaskRelativeToLayer = layerMaskParams.m_PositionRelativeToLayer; - lrMask.defaultColor = layerMaskParams.m_DefaultColor; - lrMask.maskDensity = layerMaskParams.m_UserMaskDensity; - lrMask.maskFeather = layerMaskParams.m_UserMaskFeather; - - // Set the layer mask by moving our temporary struct - m_LayerMask = std::optional(std::move(lrMask)); - } - } - - // Get the reference point (if it is there) - if (layerRecord.m_AdditionalLayerInfo.has_value()) - { - auto& additionalLayerInfo = layerRecord.m_AdditionalLayerInfo.value(); - auto referencePoint = additionalLayerInfo.getTaggedBlock(Enum::TaggedBlockKey::lrReferencePoint); - if (referencePoint.has_value()) - { - m_ReferencePointX.emplace(referencePoint.value()->m_ReferenceX); - m_ReferencePointY.emplace(referencePoint.value()->m_ReferenceY); - } - } -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -std::optional Layer::generateMaskData(const FileHeader& header) -{ - auto lrMaskData = LayerRecords::LayerMaskData(); - - // We dont have support for vector masks so far so we do not consider them - lrMaskData.m_VectorMask = std::nullopt; - if (!m_LayerMask.has_value()) - { - lrMaskData.m_LayerMask = std::nullopt; - } - else - { - LayerRecords::LayerMask lrMask = LayerRecords::LayerMask{}; - - float centerX = m_LayerMask.value().maskData->getCenterX(); - float centerY = m_LayerMask.value().maskData->getCenterY(); - int32_t width = m_LayerMask.value().maskData->getWidth(); - int32_t height = m_LayerMask.value().maskData->getHeight(); - ChannelExtents extents = generateChannelExtents(ChannelCoordinates(width, height, centerX, centerY), header); - lrMaskData.m_Size += 16u; - - // Default color - lrMaskData.m_Size += 1u; - - // This is the size for the mask bitflags - lrMaskData.m_Size += 1u; - // This is the size for the mask parameters - lrMaskData.m_Size += 1u; - bool hasMaskDensity = m_LayerMask.value().maskDensity.has_value(); - uint8_t maskDensity = 0u; - if (hasMaskDensity) - { - maskDensity = m_LayerMask.value().maskDensity.value(); - lrMaskData.m_Size += 1u; - } - - bool hasMaskFeather = m_LayerMask.value().maskFeather.has_value(); - float64_t maskFeather = 0.0f; - if (hasMaskFeather) - { - maskFeather = m_LayerMask.value().maskFeather.value(); - lrMaskData.m_Size += 8u; - - } - - lrMask.m_Top = extents.top; - lrMask.m_Left = extents.left; - lrMask.m_Bottom = extents.bottom; - lrMask.m_Right = extents.right; - lrMask.m_DefaultColor = m_LayerMask.value().defaultColor; - lrMask.m_Disabled = m_LayerMask.value().isDisabled; - lrMask.m_PositionRelativeToLayer = m_LayerMask.value().isMaskRelativeToLayer; - lrMask.m_HasMaskParams = hasMaskDensity || hasMaskFeather; - lrMask.m_HasUserMaskDensity = hasMaskDensity; - lrMask.m_HasUserMaskFeather = hasMaskFeather; - if (hasMaskDensity) - { - lrMask.m_UserMaskDensity.emplace(maskDensity); - } - if (hasMaskFeather) - { - lrMask.m_UserMaskFeather.emplace(maskFeather); - } - - lrMaskData.m_LayerMask.emplace(lrMask); - } - - - std::optional lrMaskDataOpt; - lrMaskDataOpt.emplace(lrMaskData); - if (!lrMaskData.m_LayerMask.has_value() && !lrMaskData.m_VectorMask.has_value()) - { - return std::nullopt; - } - return lrMaskDataOpt; -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -PascalString Layer::generatePascalString() -{ - return PascalString(m_LayerName, 4u); -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -std::vector> Layer::generateTaggedBlocks() -{ - std::vector> blockVec; - // Generate our reference point tagged block - if (m_ReferencePointX.has_value() && m_ReferencePointY.has_value()) - { - auto referencePointPtr = std::make_shared(m_ReferencePointX.value(), m_ReferencePointY.value()); - blockVec.push_back(referencePointPtr); - } - - return blockVec; -} - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -LayerRecords::LayerBlendingRanges Layer::generateBlendingRanges(const Enum::ColorMode colorMode) -{ - using Data = std::vector>; - LayerRecords::LayerBlendingRanges blendingRanges{}; - return blendingRanges; -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -std::optional>> Layer::extractLayerMask() -{ - if (!m_LayerMask.has_value()) - { - return std::nullopt; - } - auto maskImgChannel = std::move(m_LayerMask.value().maskData); - Enum::ChannelIDInfo maskIdInfo{ Enum::ChannelID::UserSuppliedLayerMask, -2 }; - LayerRecords::ChannelInformation channelInfo{ maskIdInfo, maskImgChannel->m_OrigByteSize }; - std::tuple> data = std::make_tuple(channelInfo, std::move(maskImgChannel)); - return std::optional(std::move(data)); -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -std::tuple Layer::toPhotoshop(const Enum::ColorMode colorMode, const FileHeader& header) -{ - std::vector channelInfo{}; // Just have this be empty - ChannelImageData channelData{}; - - ChannelExtents extents = generateChannelExtents(ChannelCoordinates(m_Width, m_Height, m_CenterX, m_CenterY), header); - - auto blockVec = this->generateTaggedBlocks(); - std::optional taggedBlocks = std::nullopt; - if (blockVec.size() > 0) - { - TaggedBlockStorage blockStorage = { blockVec }; - taggedBlocks.emplace(blockStorage); - } - - LayerRecord lrRecord( - PascalString(m_LayerName, 4u), // Photoshop does sometimes explicitly write out the name such as '' to indicate what it belongs to - extents.top, - extents.left, - extents.bottom, - extents.right, - 0u, // Number of channels, photoshop does appear to actually write out all the channels with 0 length, we will see later if that is a requirement - channelInfo, - m_BlendMode, - m_Opacity, - 0u, // Clipping - LayerRecords::BitFlags(false, !m_IsVisible, false), - std::nullopt, // LayerMaskData - Layer::generateBlendingRanges(colorMode), // Generate some defaults - std::move(taggedBlocks) // Additional layer information - ); - - return std::make_tuple(std::move(lrRecord), std::move(channelData)); -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -std::vector Layer::getMaskData(const bool doCopy /*= true*/) -{ - if (m_LayerMask.has_value()) - { - if (doCopy) - return m_LayerMask.value().maskData->getData(); - else - return m_LayerMask.value().maskData->extractData(); - } - PSAPI_LOG_WARNING("Layer", "Layer doesnt have a mask channel, returning an empty vector"); - return std::vector(); -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -void Layer::setCompression(const Enum::Compression compCode) -{ - if (m_LayerMask.has_value()) - m_LayerMask.value().maskData->m_Compression = compCode; -} - - - -PSAPI_NAMESPACE_END \ No newline at end of file diff --git a/PhotoshopAPI/src/LayeredFile/LayerTypes/Layer.h b/PhotoshopAPI/src/LayeredFile/LayerTypes/Layer.h index 9d8a389f..f233684d 100644 --- a/PhotoshopAPI/src/LayeredFile/LayerTypes/Layer.h +++ b/PhotoshopAPI/src/LayeredFile/LayerTypes/Layer.h @@ -4,9 +4,13 @@ #include "Enum.h" #include "Core/Struct/ImageChannel.h" #include "PhotoshopFile/LayerAndMaskInformation.h" +#include "PhotoshopFile/AdditionalLayerInfo.h" +#include "Core/Struct/TaggedBlock.h" #include +#include #include +#include PSAPI_NAMESPACE_BEGIN @@ -76,6 +80,142 @@ struct Layer float m_CenterY; +protected: + + /// \brief Generates the LayerMaskData struct from the layer mask (if provided). + /// + /// \return An optional containing LayerMaskData if a layer mask is present; otherwise, std::nullopt. + std::optional generateMaskData(const FileHeader& header) + { + auto lrMaskData = LayerRecords::LayerMaskData(); + + // We dont have support for vector masks so far so we do not consider them + lrMaskData.m_VectorMask = std::nullopt; + if (!m_LayerMask.has_value()) + { + lrMaskData.m_LayerMask = std::nullopt; + } + else + { + LayerRecords::LayerMask lrMask = LayerRecords::LayerMask{}; + + float centerX = m_LayerMask.value().maskData->getCenterX(); + float centerY = m_LayerMask.value().maskData->getCenterY(); + int32_t width = m_LayerMask.value().maskData->getWidth(); + int32_t height = m_LayerMask.value().maskData->getHeight(); + ChannelExtents extents = generateChannelExtents(ChannelCoordinates(width, height, centerX, centerY), header); + lrMaskData.m_Size += 16u; + + // Default color + lrMaskData.m_Size += 1u; + + // This is the size for the mask bitflags + lrMaskData.m_Size += 1u; + // This is the size for the mask parameters + lrMaskData.m_Size += 1u; + bool hasMaskDensity = m_LayerMask.value().maskDensity.has_value(); + uint8_t maskDensity = 0u; + if (hasMaskDensity) + { + maskDensity = m_LayerMask.value().maskDensity.value(); + lrMaskData.m_Size += 1u; + } + + bool hasMaskFeather = m_LayerMask.value().maskFeather.has_value(); + float64_t maskFeather = 0.0f; + if (hasMaskFeather) + { + maskFeather = m_LayerMask.value().maskFeather.value(); + lrMaskData.m_Size += 8u; + + } + + lrMask.m_Top = extents.top; + lrMask.m_Left = extents.left; + lrMask.m_Bottom = extents.bottom; + lrMask.m_Right = extents.right; + lrMask.m_DefaultColor = m_LayerMask.value().defaultColor; + lrMask.m_Disabled = m_LayerMask.value().isDisabled; + lrMask.m_PositionRelativeToLayer = m_LayerMask.value().isMaskRelativeToLayer; + lrMask.m_HasMaskParams = hasMaskDensity || hasMaskFeather; + lrMask.m_HasUserMaskDensity = hasMaskDensity; + lrMask.m_HasUserMaskFeather = hasMaskFeather; + if (hasMaskDensity) + { + lrMask.m_UserMaskDensity.emplace(maskDensity); + } + if (hasMaskFeather) + { + lrMask.m_UserMaskFeather.emplace(maskFeather); + } + + lrMaskData.m_LayerMask.emplace(lrMask); + } + + + std::optional lrMaskDataOpt; + lrMaskDataOpt.emplace(lrMaskData); + if (!lrMaskData.m_LayerMask.has_value() && !lrMaskData.m_VectorMask.has_value()) + { + return std::nullopt; + } + return lrMaskDataOpt; + } + + /// \brief Generate the layer name as a Pascal string. + /// + /// \return A PascalString representing the layer name. + PascalString generatePascalString() + { + return PascalString(m_LayerName, 4u); + } + + /// \brief Generate the tagged blocks necessary for writing the layer + virtual std::vector> generateTaggedBlocks() + { + std::vector> blockVec; + // Generate our reference point tagged block + if (m_ReferencePointX.has_value() && m_ReferencePointY.has_value()) + { + auto referencePointPtr = std::make_shared(m_ReferencePointX.value(), m_ReferencePointY.value()); + blockVec.push_back(referencePointPtr); + } + + return blockVec; + } + + /// \brief Generate the layer blending ranges (which for now are just the defaults). + /// + /// The blending ranges depend on the specified ColorMode. This function returns the default + /// blending ranges for the given color mode. + /// + /// \param colorMode The ColorMode to determine the blending ranges. + /// \return A LayerBlendingRanges object representing the layer blending ranges. + LayerRecords::LayerBlendingRanges generateBlendingRanges(const Enum::ColorMode colorMode) + { + using Data = std::vector>; + LayerRecords::LayerBlendingRanges blendingRanges{}; + return blendingRanges; + } + + /// \brief Extract the layer mask into a tuple of channel information and image data + /// + /// \return An optional containing a tuple of ChannelInformation and a unique_ptr to BaseImageChannel. + std::optional>> extractLayerMask() + { + if (!m_LayerMask.has_value()) + { + return std::nullopt; + } + auto maskImgChannel = std::move(m_LayerMask.value().maskData); + Enum::ChannelIDInfo maskIdInfo{ Enum::ChannelID::UserSuppliedLayerMask, -2 }; + LayerRecords::ChannelInformation channelInfo{ maskIdInfo, maskImgChannel->m_OrigByteSize }; + std::tuple> data = std::make_tuple(channelInfo, std::move(maskImgChannel)); + return std::optional(std::move(data)); + } + +public: + Layer() : m_LayerName(""), m_LayerMask({}), m_BlendMode(Enum::BlendMode::Normal), m_IsVisible(true), m_Opacity(255), m_Width(0u), m_Height(0u), m_CenterX(0u), m_CenterY(0u) {}; /// \brief Initialize a Layer instance from the internal Photoshop File Format structures. @@ -87,7 +227,87 @@ struct Layer /// \param layerRecord The LayerRecord containing information about the layer. /// \param channelImageData The ChannelImageData holding the image data. /// \param header The FileHeader providing overall file information. - Layer(const LayerRecord& layerRecord, ChannelImageData& channelImageData, const FileHeader& header); + Layer(const LayerRecord& layerRecord, ChannelImageData& channelImageData, const FileHeader& header) + { + m_LayerName = layerRecord.m_LayerName.getString(); + // To parse the blend mode we must actually check for the presence of the sectionDivider blendMode as this overrides the layerRecord + // blendmode if it is present + if (!layerRecord.m_AdditionalLayerInfo.has_value()) + { + // Short circuit if no additional layer info is present + m_BlendMode = layerRecord.m_BlendMode; + } + else + { + auto& additionalLayerInfo = layerRecord.m_AdditionalLayerInfo.value(); + auto sectionDivider = additionalLayerInfo.getTaggedBlock(Enum::TaggedBlockKey::lrSectionDivider); + if (sectionDivider.has_value() && sectionDivider.value()->m_BlendMode.has_value()) + { + m_BlendMode = sectionDivider.value()->m_BlendMode.value(); + } + else + { + m_BlendMode = layerRecord.m_BlendMode; + } + } + // 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; + m_Opacity = layerRecord.m_Opacity; + + // Generate our coordinates from the extents + ChannelExtents extents{ layerRecord.m_Top, layerRecord.m_Left, layerRecord.m_Bottom, layerRecord.m_Right }; + ChannelCoordinates coordinates = generateChannelCoordinates(extents, header); + m_Width = coordinates.width; + m_Height = coordinates.height; + m_CenterX = coordinates.centerX; + m_CenterY = coordinates.centerY; + + // Move the layer mask into our layerMask struct, for now this only does pixel masks + for (int i = 0; i < layerRecord.m_ChannelCount; ++i) + { + auto& channelInfo = layerRecord.m_ChannelInformation[i]; + if (channelInfo.m_ChannelID.id == Enum::ChannelID::UserSuppliedLayerMask) + { + // Move the compressed image data into our LayerMask struct + LayerMask lrMask{}; + auto channelPtr = channelImageData.extractImagePtr(channelInfo.m_ChannelID); + if (channelPtr) + lrMask.maskData = std::move(channelPtr); + else + PSAPI_LOG_ERROR("Layer", "Unable to extract mask channel for layer '%s'", m_LayerName.c_str()); + channelPtr = nullptr; + + // If no mask parameters are present we just use sensible defaults and skip + if (!layerRecord.m_LayerMaskData.has_value()) continue; + auto& maskParams = layerRecord.m_LayerMaskData.value(); + + // Read the mask parameters + if (!maskParams.m_LayerMask.has_value()) continue; + auto& layerMaskParams = maskParams.m_LayerMask.value(); + + lrMask.isDisabled = layerMaskParams.m_Disabled; + lrMask.isMaskRelativeToLayer = layerMaskParams.m_PositionRelativeToLayer; + lrMask.defaultColor = layerMaskParams.m_DefaultColor; + lrMask.maskDensity = layerMaskParams.m_UserMaskDensity; + lrMask.maskFeather = layerMaskParams.m_UserMaskFeather; + + // Set the layer mask by moving our temporary struct + m_LayerMask = std::optional(std::move(lrMask)); + } + } + + // Get the reference point (if it is there) + if (layerRecord.m_AdditionalLayerInfo.has_value()) + { + auto& additionalLayerInfo = layerRecord.m_AdditionalLayerInfo.value(); + auto referencePoint = additionalLayerInfo.getTaggedBlock(Enum::TaggedBlockKey::lrReferencePoint); + if (referencePoint.has_value()) + { + m_ReferencePointX.emplace(referencePoint.value()->m_ReferenceX); + m_ReferencePointY.emplace(referencePoint.value()->m_ReferenceY); + } + } + } /// \brief Function for creating a PhotoshopFile from the layer. /// @@ -99,13 +319,61 @@ struct Layer /// \param colorMode The desired ColorMode for the PhotoshopFile. /// \param header The FileHeader providing overall file information. /// \return A tuple containing LayerRecord and ChannelImageData representing the layer in the PhotoshopFile. - virtual std::tuple toPhotoshop(Enum::ColorMode colorMode, const FileHeader& header); + virtual std::tuple toPhotoshop(Enum::ColorMode colorMode, const FileHeader& header) + { + std::vector channelInfo{}; // Just have this be empty + ChannelImageData channelData{}; + + ChannelExtents extents = generateChannelExtents(ChannelCoordinates(m_Width, m_Height, m_CenterX, m_CenterY), header); + + auto blockVec = this->generateTaggedBlocks(); + std::optional taggedBlocks = std::nullopt; + if (blockVec.size() > 0) + { + TaggedBlockStorage blockStorage = { blockVec }; + taggedBlocks.emplace(blockStorage); + } + + LayerRecord lrRecord( + PascalString(m_LayerName, 4u), // Photoshop does sometimes explicitly write out the name such as '' to indicate what it belongs to + extents.top, + extents.left, + extents.bottom, + extents.right, + 0u, // Number of channels, photoshop does appear to actually write out all the channels with 0 length, we will see later if that is a requirement + channelInfo, + m_BlendMode, + m_Opacity, + 0u, // Clipping + LayerRecords::BitFlags(false, !m_IsVisible, false), + std::nullopt, // LayerMaskData + Layer::generateBlendingRanges(colorMode), // Generate some defaults + std::move(taggedBlocks) // Additional layer information + ); + + return std::make_tuple(std::move(lrRecord), std::move(channelData)); + } /// Extract the mask data as a vector, if doCopy is false the image data is freed and no longer usable - std::vector getMaskData(const bool doCopy = true); + std::vector getMaskData(const bool doCopy = true) + { + if (m_LayerMask.has_value()) + { + if (doCopy) + return m_LayerMask.value().maskData->getData(); + else + return m_LayerMask.value().maskData->extractData(); + } + PSAPI_LOG_WARNING("Layer", "Layer doesnt have a mask channel, returning an empty vector"); + return std::vector(); + } /// Changes the compression mode of all channels in this layer to the given compression mode - virtual void setCompression(const Enum::Compression compCode); + virtual void setCompression(const Enum::Compression compCode) + { + if (m_LayerMask.has_value()) + m_LayerMask.value().maskData->m_Compression = compCode; + } virtual ~Layer() = default; @@ -119,33 +387,6 @@ struct Layer /// currently this is only used for roundtripping, therefore optional. This value must be within the layers bounding box (or no /// more than .5 away since it is a double) std::optional m_ReferencePointY = std::nullopt; - - /// \brief Generates the LayerMaskData struct from the layer mask (if provided). - /// - /// \return An optional containing LayerMaskData if a layer mask is present; otherwise, std::nullopt. - std::optional generateMaskData(const FileHeader& header); - - /// \brief Generate the layer name as a Pascal string. - /// - /// \return A PascalString representing the layer name. - PascalString generatePascalString(); - - /// \brief Generate the tagged blocks necessary for writing the layer - virtual std::vector> generateTaggedBlocks(); - - /// \brief Generate the layer blending ranges (which for now are just the defaults). - /// - /// The blending ranges depend on the specified ColorMode. This function returns the default - /// blending ranges for the given color mode. - /// - /// \param colorMode The ColorMode to determine the blending ranges. - /// \return A LayerBlendingRanges object representing the layer blending ranges. - LayerRecords::LayerBlendingRanges generateBlendingRanges(const Enum::ColorMode colorMode); - - /// \brief Extract the layer mask into a tuple of channel information and image data - /// - /// \return An optional containing a tuple of ChannelInformation and a unique_ptr to BaseImageChannel. - std::optional>> extractLayerMask(); }; diff --git a/PhotoshopAPI/src/LayeredFile/LayerTypes/SectionDividerLayer.cpp b/PhotoshopAPI/src/LayeredFile/LayerTypes/SectionDividerLayer.cpp deleted file mode 100644 index cd90926e..00000000 --- a/PhotoshopAPI/src/LayeredFile/LayerTypes/SectionDividerLayer.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include "SectionDividerLayer.h" - -#include "Macros.h" -#include "Logger.h" -#include "Enum.h" -#include "Core/Struct/TaggedBlock.h" - -#include -#include -#include - - -PSAPI_NAMESPACE_BEGIN - - -// Instantiate the template types for SectionDividerLayer -template struct SectionDividerLayer; -template struct SectionDividerLayer; -template struct SectionDividerLayer; - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -std::tuple SectionDividerLayer::toPhotoshop(const Enum::ColorMode colorMode, const FileHeader& header) -{ - std::vector channelInfo{}; // Just have this be empty - ChannelImageData channelData{}; - - auto blockVec = this->generateTaggedBlocks(); - std::optional taggedBlocks = std::nullopt; - if (blockVec.size() > 0) - { - TaggedBlockStorage blockStorage = { blockVec }; - taggedBlocks.emplace(blockStorage); - } - - LayerRecord lrRecord( - PascalString("", 4u), // Photoshop does sometimes explicitly write out the name such as '' to indicate what it belongs to - 0, // top - 0, // left - 0, // bottom - 0, // right - 0u, // Number of channels, photoshop does appear to actually write out all the channels with 0 length, we will see later if that is a requirement - channelInfo, - Enum::BlendMode::Normal, - 255u, // Opacity - 0u, // Clipping - LayerRecords::BitFlags{}, - std::nullopt, - Layer::generateBlendingRanges(colorMode), // Generate some defaults - std::move(taggedBlocks) - ); - - return std::make_tuple(std::move(lrRecord), std::move(channelData)); -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -std::vector> SectionDividerLayer::generateTaggedBlocks() -{ - auto blockVec = Layer::generateTaggedBlocks(); - LrSectionTaggedBlock sectionBlock{ Enum::SectionDivider::BoundingSection, std::nullopt }; - blockVec.push_back(std::make_shared(sectionBlock)); - - return blockVec; -} - - -PSAPI_NAMESPACE_END \ No newline at end of file diff --git a/PhotoshopAPI/src/LayeredFile/LayerTypes/SectionDividerLayer.h b/PhotoshopAPI/src/LayeredFile/LayerTypes/SectionDividerLayer.h index 3008cfae..5550a5ec 100644 --- a/PhotoshopAPI/src/LayeredFile/LayerTypes/SectionDividerLayer.h +++ b/PhotoshopAPI/src/LayeredFile/LayerTypes/SectionDividerLayer.h @@ -2,6 +2,13 @@ #include "Macros.h" #include "Layer.h" +#include "Enum.h" +#include "Logger.h" +#include "Core/Struct/TaggedBlock.h" + +#include +#include +#include PSAPI_NAMESPACE_BEGIN @@ -11,13 +18,53 @@ PSAPI_NAMESPACE_BEGIN template struct SectionDividerLayer : Layer { +protected: + // Generates an additional LrSectionDivider Tagged Block + std::vector> generateTaggedBlocks() override + { + auto blockVec = Layer::generateTaggedBlocks(); + LrSectionTaggedBlock sectionBlock{ Enum::SectionDivider::BoundingSection, std::nullopt }; + blockVec.push_back(std::make_shared(sectionBlock)); + + return blockVec; + } + +public: + SectionDividerLayer() = default; - std::tuple toPhotoshop(const Enum::ColorMode colorMode, const FileHeader& header) override; + std::tuple toPhotoshop(const Enum::ColorMode colorMode, const FileHeader& header) override + { + std::vector channelInfo{}; // Just have this be empty + ChannelImageData channelData{}; -protected: - // Generates an additional LrSectionDivider Tagged Block - std::vector> generateTaggedBlocks() override; + auto blockVec = this->generateTaggedBlocks(); + std::optional taggedBlocks = std::nullopt; + if (blockVec.size() > 0) + { + TaggedBlockStorage blockStorage = { blockVec }; + taggedBlocks.emplace(blockStorage); + } + + LayerRecord lrRecord( + PascalString("", 4u), // Photoshop does sometimes explicitly write out the name such as '' to indicate what it belongs to + 0, // top + 0, // left + 0, // bottom + 0, // right + 0u, // Number of channels, photoshop does appear to actually write out all the channels with 0 length, we will see later if that is a requirement + channelInfo, + Enum::BlendMode::Normal, + 255u, // Opacity + 0u, // Clipping + LayerRecords::BitFlags{}, + std::nullopt, + Layer::generateBlendingRanges(colorMode), // Generate some defaults + std::move(taggedBlocks) + ); + + return std::make_tuple(std::move(lrRecord), std::move(channelData)); + } }; diff --git a/PhotoshopAPI/src/LayeredFile/LayeredFile.cpp b/PhotoshopAPI/src/LayeredFile/LayeredFile.cpp index 5044f1ff..38a04b8a 100644 --- a/PhotoshopAPI/src/LayeredFile/LayeredFile.cpp +++ b/PhotoshopAPI/src/LayeredFile/LayeredFile.cpp @@ -21,11 +21,6 @@ #include -#if (__cplusplus < 202002L) -#include "tcb_span.hpp" -#else -#include -#endif #include #include @@ -35,19 +30,6 @@ PSAPI_NAMESPACE_BEGIN -// Instantiate the template types for LayeredFile -template struct LayeredFile; -template struct LayeredFile; -template struct LayeredFile; - - -// Instantiate the template types for LayeredToPhotoshopFile -template std::unique_ptr LayeredToPhotoshopFile(LayeredFile&& layeredFile); -template std::unique_ptr LayeredToPhotoshopFile(LayeredFile&& layeredFile); -template std::unique_ptr LayeredToPhotoshopFile(LayeredFile&& layeredFile); - - - // --------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------------------- ICCProfile::ICCProfile(const std::filesystem::path& pathToICCFile) @@ -61,806 +43,6 @@ ICCProfile::ICCProfile(const std::filesystem::path& pathToICCFile) m_Data = ReadBinaryArray(iccFile, iccFile.getSize()); } - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -std::unique_ptr LayeredToPhotoshopFile(LayeredFile&& layeredFile) -{ - PROFILE_FUNCTION(); - FileHeader header = generateHeader(layeredFile); - ColorModeData colorModeData = generateColorModeData(layeredFile); - ImageResources imageResources = generateImageResources(layeredFile); - LayerAndMaskInformation lrMaskInfo = generateLayerMaskInfo(layeredFile, header); - ImageData imageData = ImageData(layeredFile.getNumChannels(true, true)); // Ignore any mask or alpha channels - - return std::make_unique(header, colorModeData, std::move(imageResources), std::move(lrMaskInfo), imageData); -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -LayeredFile::LayeredFile(std::unique_ptr file) -{ - // Take ownership of document - std::unique_ptr document = std::move(file); - - m_BitDepth = document->m_Header.m_Depth; - m_ColorMode = document->m_Header.m_ColorMode; - m_Width = document->m_Header.m_Width; - m_Height = document->m_Header.m_Height; - - // Extract the ICC Profile if it exists on the document, otherwise it will simply be empty - m_ICCProfile = LayeredFileImpl::readICCProfile(document.get()); - // Extract the DPI from the document, default to 72 - m_DotsPerInch = LayeredFileImpl::readDPI(document.get()); - - m_Layers = LayeredFileImpl::buildLayerHierarchy(std::move(document)); - if (m_Layers.size() == 0) - { - PSAPI_LOG_ERROR("LayeredFile", "Read an invalid PhotoshopFile as it does not contain any layers. Is the only layer in the scene locked? This is not supported by the PhotoshopAPI"); - } -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -LayeredFile::LayeredFile(Enum::ColorMode colorMode, uint64_t width, uint64_t height) requires std::same_as -{ - m_BitDepth = Enum::BitDepth::BD_8; - m_ColorMode = colorMode; - m_Width = width; - m_Height = height; -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -LayeredFile::LayeredFile(Enum::ColorMode colorMode, uint64_t width, uint64_t height) requires std::same_as -{ - m_BitDepth = Enum::BitDepth::BD_16; - m_ColorMode = colorMode; - m_Width = width; - m_Height = height; -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -LayeredFile::LayeredFile(Enum::ColorMode colorMode, uint64_t width, uint64_t height) requires std::same_as -{ - m_BitDepth = Enum::BitDepth::BD_32; - m_ColorMode = colorMode; - m_Width = width; - m_Height = height; -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -std::shared_ptr> LayeredFile::findLayer(std::string path) const -{ - PROFILE_FUNCTION(); - std::vector segments = splitString(path, '/'); - for (const auto& layer : m_Layers) - { - // Get the layer name and recursively check the path - if (layer->m_LayerName == segments[0]) - { - // This is a simple path with no nested layers - if (segments.size() == 1) - { - return layer; - } - // Pass an index of one as we already found the first layer - return LayeredFileImpl::findLayerRecurse(layer, segments, 1); - } - } - PSAPI_LOG_WARNING("LayeredFile", "Unable to find layer path %s", path.c_str()); - return nullptr; -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -void LayeredFile::addLayer(std::shared_ptr> layer) -{ - if (isLayerInDocument(layer)) - { - PSAPI_LOG_WARNING("LayeredFile", "Cannot insert a layer into the document twice, please use a unique layer. Skipping layer '%s'", layer->m_LayerName.c_str()); - return; - } - m_Layers.push_back(layer); -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -void LayeredFile::moveLayer(std::shared_ptr> layer, std::shared_ptr> parentLayer /*= nullptr */) -{ - PROFILE_FUNCTION(); - // We must first check that we are not trying to move a layer higher in the hierarchy to lower in the hierarchy - // as that would be undefined behaviour. E.g. if we want to move /Group/ to /Group/NestedGroup that wouldnt work - // since the down stream nodes are dependant on the upstream nodes - if (parentLayer && isMovingToInvalidHierarchy(layer, parentLayer)) - { - PSAPI_LOG_WARNING("LayeredFile", "Cannot move layer '%s' under '%s' as that would represent an illegal move operation", - layer->m_LayerName.c_str(), parentLayer->m_LayerName.c_str()); - return; - } - - - // First we must remove the layer from the hierarchy and then reappend it in a different place - removeLayer(layer); - - // Insert the layer back, either under the provided parent layer or under the scene root - if (parentLayer) - { - if (auto groupLayerPtr = std::dynamic_pointer_cast>(parentLayer)) - { - groupLayerPtr->addLayer(*this, layer); - } - else - { - PSAPI_LOG_WARNING("LayeredFile", "Parent layer '%s' provided is not a group layer, can only move layers under groups", - parentLayer->m_LayerName.c_str()); - return; - } - } - else - { - addLayer(layer); - } -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -void LayeredFile::moveLayer(const std::string layer, const std::string parentLayer /* = "" */) -{ - PROFILE_FUNCTION(); - if (parentLayer == "") - { - auto layerPtr = findLayer(layer); - if (!layerPtr) [[unlikely]] - { - PSAPI_LOG_ERROR("LayeredFile", "Could not find the layer %s for moveLayer()", layer.c_str()); - } - moveLayer(layerPtr); - } - else - { - auto layerPtr = findLayer(layer); - auto parentLayerPtr = findLayer(parentLayer); - if (!layerPtr) [[unlikely]] - { - PSAPI_LOG_ERROR("LayeredFile", "Could not find the layer %s for moveLayer()", layer.c_str()); - } - if (!parentLayerPtr) [[unlikely]] - { - PSAPI_LOG_ERROR("LayeredFile", "Could not find the parentlayer %s for moveLayer()", parentLayer.c_str()); - } - moveLayer(layerPtr, parentLayerPtr); - } -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -void LayeredFile::removeLayer(std::shared_ptr> layer) -{ - PROFILE_FUNCTION(); - int index = 0; - for (auto& sceneLayer : m_Layers) - { - // Check if the layers directly in the scene root is the layer we are looking for and remove the layer if that is the case - if (sceneLayer == layer) - { - m_Layers.erase(m_Layers.begin() + index); - return; - } - - // Recurse down and short circuit if we find a match - if (LayeredFileImpl::removeLayerRecurse(sceneLayer, layer)) - { - return; - } - ++index; - } -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -void LayeredFile::removeLayer(const std::string layer) -{ - PROFILE_FUNCTION(); - auto layerPtr = findLayer(layer); - if (!layerPtr) [[unlikely]] - { - PSAPI_LOG_ERROR("LayeredFile", "Could not find the layer %s for removeLayer()", layer.c_str()); - } - removeLayer(layerPtr); -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -void LayeredFile::setCompression(const Enum::Compression compCode) -{ - for (const auto& documentLayer : m_Layers) - { - documentLayer->setCompression(compCode); - LayeredFileImpl::setCompressionRecurse(documentLayer, compCode); - } -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -std::vector>> LayeredFile::generateFlatLayers(std::optional>> layer, const LayerOrder order) const -{ - if (order == LayerOrder::forward) - { - if (layer.has_value()) - { - std::vector>> layerVec; - layerVec.push_back(layer.value()); - return LayeredFileImpl::generateFlatLayers(layerVec); - } - return LayeredFileImpl::generateFlatLayers(m_Layers); - } - else if (order == LayerOrder::reverse) - { - if (layer.has_value()) - { - std::vector>> layerVec; - layerVec.push_back(layer.value()); - std::vector>> flatLayers = LayeredFileImpl::generateFlatLayers(layerVec); - std::reverse(flatLayers.begin(), flatLayers.end()); - return flatLayers; - } - std::vector>> flatLayers = LayeredFileImpl::generateFlatLayers(m_Layers); - std::reverse(flatLayers.begin(), flatLayers.end()); - return flatLayers; - } - PSAPI_LOG_ERROR("LayeredFile", "Invalid layer order specified, only accepts forward or reverse"); - return std::vector>>(); -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -uint16_t LayeredFile::getNumChannels(bool ignoreMaskChannels /*= true*/, bool ignoreAlphaChannel /* = false */) -{ - std::set channelIndices = {}; - for (const auto& layer : m_Layers) - { - LayeredFileImpl::getNumChannelsRecurse(layer, channelIndices); - } - - uint16_t numChannels = channelIndices.size(); - if (ignoreMaskChannels) - { - // Photoshop doesnt consider mask channels for the total amount of channels - if (channelIndices.contains(-2)) - numChannels -= 1u; - if (channelIndices.contains(-3)) - numChannels -= 1u; - } - if (ignoreAlphaChannel) - { - // Photoshop doesnt store the alpha channels in the merged image data section so we must not count it - if (channelIndices.contains(-1)) - numChannels -= 1u; - } - return numChannels; -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -bool LayeredFile::isLayerInDocument(const std::shared_ptr> layer) const -{ - PROFILE_FUNCTION(); - for (const auto& documentLayer : m_Layers) - { - if (documentLayer == layer) - { - return true; - } - if (LayeredFileImpl::isLayerInDocumentRecurse(documentLayer, layer)) - { - return true; - } - } - return false; -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -LayeredFile LayeredFile::read(const std::filesystem::path& filePath, ProgressCallback& callback) -{ - auto inputFile = File(filePath); - auto psDocumentPtr = std::make_unique(); - psDocumentPtr->read(inputFile, callback); - LayeredFile layeredFile = { std::move(psDocumentPtr) }; - return layeredFile; -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -LayeredFile LayeredFile::read(const std::filesystem::path& filePath) -{ - ProgressCallback callback{}; - return LayeredFile::read(filePath, callback); -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -void LayeredFile::write(LayeredFile&& layeredFile, const std::filesystem::path& filePath, ProgressCallback& callback, const bool forceOvewrite /*= true*/) -{ - File::FileParams params = {}; - params.doRead = false; - params.forceOverwrite = forceOvewrite; - auto outputFile = File(filePath, params); - auto psdOutDocumentPtr = LayeredToPhotoshopFile(std::move(layeredFile)); - psdOutDocumentPtr->write(outputFile, callback); -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -void LayeredFile::write(LayeredFile&& layeredFile, const std::filesystem::path& filePath,const bool forceOvewrite /*= true*/) -{ - ProgressCallback callback{}; - LayeredFile::write(std::move(layeredFile), filePath, callback, forceOvewrite); -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -bool LayeredFile::isMovingToInvalidHierarchy(const std::shared_ptr> layer, const std::shared_ptr> parentLayer) -{ - // Check if the layer would be moving to one of its descendants which is illegal. Therefore the argument order is reversed - bool isDescendantOf = LayeredFileImpl::isLayerInDocumentRecurse(parentLayer, layer); - // We additionally check if the layer is the same as the parent layer as that would also not be allowed - return isDescendantOf || layer == parentLayer; -} - - -// LayeredFileImpl -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -std::vector>> LayeredFileImpl::buildLayerHierarchy(std::unique_ptr file) -{ - auto* layerRecords = &file->m_LayerMaskInfo.m_LayerInfo.m_LayerRecords; - auto* channelImageData = &file->m_LayerMaskInfo.m_LayerInfo.m_ChannelImageData; - - if (layerRecords->size() != channelImageData->size()) - { - PSAPI_LOG_ERROR("LayeredFile", "LayerRecords Size does not match channelImageDataSize. File appears to be corrupted"); - } - - // 16 and 32 bit files store their layer records in the additional layer information section. We must therefore overwrite our previous results - if (sizeof(T) >= 2u && file->m_LayerMaskInfo.m_AdditionalLayerInfo.has_value()) - { - const AdditionalLayerInfo& additionalLayerInfo = file->m_LayerMaskInfo.m_AdditionalLayerInfo.value(); - auto lr16TaggedBlock = additionalLayerInfo.getTaggedBlock(Enum::TaggedBlockKey::Lr16); - auto lr32TaggedBlock = additionalLayerInfo.getTaggedBlock(Enum::TaggedBlockKey::Lr32); - if (lr16TaggedBlock.has_value()) - { - auto& block = lr16TaggedBlock.value(); - layerRecords = &block->m_Data.m_LayerRecords; - channelImageData = &block->m_Data.m_ChannelImageData; - } - else if (lr32TaggedBlock.has_value()) - { - auto& block = lr32TaggedBlock.value(); - layerRecords = &block->m_Data.m_LayerRecords; - channelImageData = &block->m_Data.m_ChannelImageData; - } - else - { - PSAPI_LOG_ERROR("LayeredFile", "PhotoshopFile does not seem to contain a Lr16 or Lr32 Tagged block which would hold layer information"); - } - } - - // Extract and iterate the layer records. We do this in reverse as Photoshop stores the layers in reverse - // For example, imagine this layer structure: - // - // Group - // ImageLayer - // - // Photoshop will actually store the layers like this - // - // Layer Divider - // ImageLayer - // Group - // - // Layer divider in this case being an empty layer with a 'lsct' tagged block with Type set to 3 - auto layerRecordsIterator = layerRecords->rbegin(); - auto channelImageDataIterator = channelImageData->rbegin(); - std::vector>> root = buildLayerHierarchyRecurse(*layerRecords, *channelImageData, layerRecordsIterator, channelImageDataIterator, file->m_Header); - - return root; -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -std::vector>> LayeredFileImpl::buildLayerHierarchyRecurse( - std::vector& layerRecords, - std::vector& channelImageData, - std::vector::reverse_iterator& layerRecordsIterator, - std::vector::reverse_iterator& channelImageDataIterator, - const FileHeader& header -) -{ - std::vector>> root; - - // Iterate the layer records and channelImageData. These are always the same size - while (layerRecordsIterator != layerRecords.rend() && channelImageDataIterator != channelImageData.rend()) - { - auto& layerRecord = *layerRecordsIterator; - auto& channelImage = *channelImageDataIterator; - - std::shared_ptr> layer = identifyLayerType(layerRecord, channelImage, header); - - if (auto groupLayerPtr = std::dynamic_pointer_cast>(layer)) - { - // Recurse a level down - groupLayerPtr->m_Layers = buildLayerHierarchyRecurse(layerRecords, channelImageData, ++layerRecordsIterator, ++channelImageDataIterator, header); - root.push_back(groupLayerPtr); - } - else if (auto sectionLayerPtr = std::dynamic_pointer_cast>(layer)) - { - // We have reached the end of the current nested section therefore we return the current root object we hold; - return root; - } - else - { - root.push_back(layer); - } - try - { - ++layerRecordsIterator; - ++channelImageDataIterator; - } - catch (const std::exception& ex) - { - PSAPI_UNUSED(ex); - PSAPI_LOG_ERROR("LayeredFile", "Unhandled exception when trying to decrement the layer iterator"); - } - } - return root; -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -std::vector>> LayeredFileImpl::generateFlatLayers(const std::vector>>& nestedLayers) -{ - std::vector>> flatLayers; - LayeredFileImpl::generateFlatLayersRecurse(nestedLayers, flatLayers); - - return flatLayers; -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -void LayeredFileImpl::generateFlatLayersRecurse(const std::vector>>& nestedLayers, std::vector>>& flatLayers) -{ - for (const auto& layer : nestedLayers) - { - // Recurse down if its a group layer - if (auto groupLayerPtr = std::dynamic_pointer_cast>(layer)) - { - flatLayers.push_back(layer); - LayeredFileImpl::generateFlatLayersRecurse(groupLayerPtr->m_Layers, flatLayers); - // 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>()); - } - else - { - flatLayers.push_back(layer); - } - } -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -std::shared_ptr> LayeredFileImpl::identifyLayerType(LayerRecord& layerRecord, ChannelImageData& channelImageData, const FileHeader& header) -{ - // Short ciruit here as we have an image layer for sure - if (!layerRecord.m_AdditionalLayerInfo.has_value()) - { - return std::make_shared>(layerRecord, channelImageData, header); - } - const AdditionalLayerInfo& additionalLayerInfo = layerRecord.m_AdditionalLayerInfo.value(); - - // Check for GroupLayer, ArtboardLayer or SectionDividerLayer - auto sectionDividerTaggedBlock = additionalLayerInfo.getTaggedBlock(Enum::TaggedBlockKey::lrSectionDivider); - if (sectionDividerTaggedBlock.has_value()) - { - if (sectionDividerTaggedBlock.value()->m_Type == Enum::SectionDivider::ClosedFolder - || sectionDividerTaggedBlock.value()->m_Type == Enum::SectionDivider::OpenFolder) - { - // This may actually house not only a group layer, but potentially also an artboard layer which we check for first - // These are, as of yet, unsupported. Therefore we simply return an empty container - auto artboardTaggedBlock = additionalLayerInfo.getTaggedBlock(Enum::TaggedBlockKey::lrArtboard); - if (artboardTaggedBlock.has_value()) - { - return std::make_shared>(); - } - return std::make_shared>(layerRecord, channelImageData, header); - } - else if (sectionDividerTaggedBlock.value()->m_Type == Enum::SectionDivider::BoundingSection) - { - return std::make_shared>(); - } - // If it is Enum::SectionDivider::Any this is just any other type of layer - // we do not need to worry about checking for correctness here as the tagged block takes care of that - } - - // Check for Text Layers - auto typeToolTaggedBlock = additionalLayerInfo.getTaggedBlock(Enum::TaggedBlockKey::lrTypeTool); - if (typeToolTaggedBlock.has_value()) - { - return std::make_shared>(); - } - - // Check for Smart Object Layers - auto smartObjectTaggedBlock = additionalLayerInfo.getTaggedBlock(Enum::TaggedBlockKey::lrSmartObject); - if (typeToolTaggedBlock.has_value()) - { - return std::make_shared>(); - } - - // Check if it is one of many adjustment layers - // We do not currently implement these but it would be worth investigating - { -#define getGenericTaggedBlock additionalLayerInfo.getTaggedBlock - - auto blackAndWhiteTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjBlackandWhite); - auto gradientTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjGradient); - auto invertTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjInvert); - auto patternFillTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjPattern); - auto posterizeTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjPosterize); - auto solidColorTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjSolidColor); - auto thresholdTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjThreshold); - auto vibranceTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjVibrance); - auto brightnessContrastTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjBrightnessContrast); - auto colorBalanceTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjColorBalance); - auto colorLookupTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjColorLookup); - auto channelMixerTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjChannelMixer); - auto curvesTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjCurves); - auto gradientMapTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjGradientMap); - auto exposureTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjExposure); - auto newHueSatTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjNewHueSat); - auto oldHueSatTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjOldHueSat); - auto levelsTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjLevels); - auto photoFilterTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjPhotoFilter); - auto selectiveColorTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjSelectiveColor); - - if (blackAndWhiteTaggedBlock.has_value() - || gradientTaggedBlock.has_value() - || invertTaggedBlock.has_value() - || patternFillTaggedBlock.has_value() - || posterizeTaggedBlock.has_value() - || solidColorTaggedBlock.has_value() - || thresholdTaggedBlock.has_value() - || vibranceTaggedBlock.has_value() - || brightnessContrastTaggedBlock.has_value() - || colorBalanceTaggedBlock.has_value() - || colorLookupTaggedBlock.has_value() - || channelMixerTaggedBlock.has_value() - || curvesTaggedBlock.has_value() - || gradientMapTaggedBlock.has_value() - || exposureTaggedBlock.has_value() - || newHueSatTaggedBlock.has_value() - || oldHueSatTaggedBlock.has_value() - || levelsTaggedBlock.has_value() - || photoFilterTaggedBlock.has_value() - || selectiveColorTaggedBlock.has_value() - ) - { - return std::make_shared>(); - } -#undef getGenericTaggedBlock - } - - // Now the layer could only be one of two more. A shape or pixel layer (Note files written before CS6 could fail this shape layer check here - { -#define getGenericTaggedBlock additionalLayerInfo.getTaggedBlock - - { - auto vecOriginDataTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::vecOriginData); - auto vecMaskSettingTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::vecMaskSettings); - auto vecStrokeDataTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::vecStrokeData); - auto vecStrokeContentDataTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::vecStrokeContentData); - - if (vecOriginDataTaggedBlock.has_value() - || vecMaskSettingTaggedBlock.has_value() - || vecStrokeDataTaggedBlock.has_value() - || vecStrokeContentDataTaggedBlock.has_value() - ) - { - return std::make_shared>(); - } -#undef getGenericTaggedBlock - } - } - - return std::make_shared>(layerRecord, channelImageData, header); -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -std::shared_ptr> LayeredFileImpl::findLayerRecurse(std::shared_ptr> parentLayer, std::vector path, int index) -{ - // We must first check that the parent layer passed in is actually a group layer - if (auto groupLayerPtr = std::dynamic_pointer_cast>(parentLayer)) - { - for (const auto layerPtr : groupLayerPtr->m_Layers) - { - // Get the layer name and recursively check the path - if (layerPtr->m_LayerName == path[index]) - { - if (index == path.size() - 1) - { - // This is the last element and we return the item and propagate it up - return layerPtr; - } - return LayeredFileImpl::findLayerRecurse(layerPtr, path, index+1); - } - } - PSAPI_LOG_WARNING("LayeredFile", "Failed to find layer '%s' based on the path", path[index].c_str()); - return nullptr; - } - PSAPI_LOG_WARNING("LayeredFile", "Provided parent layer is not a grouplayer and can therefore not have children"); - return nullptr; -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -void LayeredFileImpl::getNumChannelsRecurse(std::shared_ptr> parentLayer, std::set& channelIndices) -{ - // We must first check if we could recurse down another level. We dont check for masks on the - // group here yet as we do that further down - if (auto groupLayerPtr = std::dynamic_pointer_cast>(parentLayer)) - { - for (const auto layerPtr : groupLayerPtr->m_Layers) - { - LayeredFileImpl::getNumChannelsRecurse(layerPtr, channelIndices); - } - } - - // Check for a pixel mask - if (parentLayer->m_LayerMask.has_value()) - { - channelIndices.insert(-2); - } - - // Deal with Image channels - if (auto imageLayerPtr = std::dynamic_pointer_cast>(parentLayer)) - { - for (const auto& pair : imageLayerPtr->m_ImageData) - { - channelIndices.insert(pair.first.index); - } - } -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -void LayeredFileImpl::setCompressionRecurse(std::shared_ptr> parentLayer, const Enum::Compression compCode) -{ - // We must first check if we could recurse down another level. We dont check for masks on the - // group here yet as we do that further down - if (const auto groupLayerPtr = std::dynamic_pointer_cast>(parentLayer)) - { - for (const auto& layerPtr : groupLayerPtr->m_Layers) - { - layerPtr->setCompression(compCode); - setCompressionRecurse(layerPtr, compCode); - } - } -} - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -bool LayeredFileImpl::isLayerInDocumentRecurse(const std::shared_ptr> parentLayer, const std::shared_ptr> layer) -{ - // We must first check that the parent layer passed in is actually a group layer - if (const auto groupLayerPtr = std::dynamic_pointer_cast>(parentLayer)) - { - for (const auto& layerPtr : groupLayerPtr->m_Layers) - { - if (layerPtr == layer) - { - return true; - } - if (isLayerInDocumentRecurse(layerPtr, layer)) - { - return true; - } - } - } - return false; -} - - -// --------------------------------------------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------------------------------------------- -template -bool LayeredFileImpl::removeLayerRecurse(std::shared_ptr> parentLayer, std::shared_ptr> layer) -{ - // We must first check that the parent layer passed in is actually a group layer - if (auto groupLayerPtr = std::dynamic_pointer_cast>(parentLayer)) - { - int index = 0; - for (const auto& layerPtr : groupLayerPtr->m_Layers) - { - if (layerPtr == layer) - { - groupLayerPtr->removeLayer(index); - return true; - } - if (removeLayerRecurse(layerPtr, layer)) - { - return true; - } - ++index; - } - } - return false; -} - - - // --------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------------------- ICCProfile LayeredFileImpl::readICCProfile(const PhotoshopFile* file) diff --git a/PhotoshopAPI/src/LayeredFile/LayeredFile.h b/PhotoshopAPI/src/LayeredFile/LayeredFile.h index f197029e..069ad120 100644 --- a/PhotoshopAPI/src/LayeredFile/LayeredFile.h +++ b/PhotoshopAPI/src/LayeredFile/LayeredFile.h @@ -2,6 +2,8 @@ #include "Macros.h" #include "Enum.h" +#include "StringUtil.h" +#include "Core/Struct/TaggedBlock.h" #include "PhotoshopFile/PhotoshopFile.h" #include "PhotoshopFile/LayerAndMaskInformation.h" @@ -15,6 +17,11 @@ #include "LayerTypes/SmartObjectLayer.h" #include "LayerTypes/TextLayer.h" +#include "LayeredFile/Util/GenerateHeader.h" +#include "LayeredFile/Util/GenerateColorModeData.h" +#include "LayeredFile/Util/GenerateImageResources.h" +#include "LayeredFile/Util/GenerateLayerMaskInfo.h" + #include #include #include @@ -48,6 +55,7 @@ enum class LayerOrder }; + /// Helper Structure for loading an ICC profile from memory of disk. Photoshop will then store /// the raw bytes of the ICC profile in their ICCProfile ResourceBlock (ID 1039) struct ICCProfile @@ -70,6 +78,412 @@ struct ICCProfile }; +namespace LayeredFileImpl +{ + /// Identify the type of layer the current layer record represents and return a layerVariant object (std::variant) + /// initialized with the given layer record and corresponding channel image data. + /// This function was heavily inspired by the psd-tools library as they have the most coherent parsing of this information + template + std::shared_ptr> identifyLayerType(LayerRecord& layerRecord, ChannelImageData& channelImageData, const FileHeader& header) + { + // Short ciruit here as we have an image layer for sure + if (!layerRecord.m_AdditionalLayerInfo.has_value()) + { + return std::make_shared>(layerRecord, channelImageData, header); + } + const AdditionalLayerInfo& additionalLayerInfo = layerRecord.m_AdditionalLayerInfo.value(); + + // Check for GroupLayer, ArtboardLayer or SectionDividerLayer + auto sectionDividerTaggedBlock = additionalLayerInfo.getTaggedBlock(Enum::TaggedBlockKey::lrSectionDivider); + if (sectionDividerTaggedBlock.has_value()) + { + if (sectionDividerTaggedBlock.value()->m_Type == Enum::SectionDivider::ClosedFolder + || sectionDividerTaggedBlock.value()->m_Type == Enum::SectionDivider::OpenFolder) + { + // This may actually house not only a group layer, but potentially also an artboard layer which we check for first + // These are, as of yet, unsupported. Therefore we simply return an empty container + auto artboardTaggedBlock = additionalLayerInfo.getTaggedBlock(Enum::TaggedBlockKey::lrArtboard); + if (artboardTaggedBlock.has_value()) + { + return std::make_shared>(); + } + return std::make_shared>(layerRecord, channelImageData, header); + } + else if (sectionDividerTaggedBlock.value()->m_Type == Enum::SectionDivider::BoundingSection) + { + return std::make_shared>(); + } + // If it is Enum::SectionDivider::Any this is just any other type of layer + // we do not need to worry about checking for correctness here as the tagged block takes care of that + } + + // Check for Text Layers + auto typeToolTaggedBlock = additionalLayerInfo.getTaggedBlock(Enum::TaggedBlockKey::lrTypeTool); + if (typeToolTaggedBlock.has_value()) + { + return std::make_shared>(); + } + + // Check for Smart Object Layers + auto smartObjectTaggedBlock = additionalLayerInfo.getTaggedBlock(Enum::TaggedBlockKey::lrSmartObject); + if (typeToolTaggedBlock.has_value()) + { + return std::make_shared>(); + } + + // Check if it is one of many adjustment layers + // We do not currently implement these but it would be worth investigating + { + #define getGenericTaggedBlock additionalLayerInfo.getTaggedBlock + + auto blackAndWhiteTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjBlackandWhite); + auto gradientTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjGradient); + auto invertTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjInvert); + auto patternFillTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjPattern); + auto posterizeTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjPosterize); + auto solidColorTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjSolidColor); + auto thresholdTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjThreshold); + auto vibranceTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjVibrance); + auto brightnessContrastTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjBrightnessContrast); + auto colorBalanceTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjColorBalance); + auto colorLookupTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjColorLookup); + auto channelMixerTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjChannelMixer); + auto curvesTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjCurves); + auto gradientMapTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjGradientMap); + auto exposureTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjExposure); + auto newHueSatTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjNewHueSat); + auto oldHueSatTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjOldHueSat); + auto levelsTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjLevels); + auto photoFilterTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjPhotoFilter); + auto selectiveColorTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::adjSelectiveColor); + + if (blackAndWhiteTaggedBlock.has_value() + || gradientTaggedBlock.has_value() + || invertTaggedBlock.has_value() + || patternFillTaggedBlock.has_value() + || posterizeTaggedBlock.has_value() + || solidColorTaggedBlock.has_value() + || thresholdTaggedBlock.has_value() + || vibranceTaggedBlock.has_value() + || brightnessContrastTaggedBlock.has_value() + || colorBalanceTaggedBlock.has_value() + || colorLookupTaggedBlock.has_value() + || channelMixerTaggedBlock.has_value() + || curvesTaggedBlock.has_value() + || gradientMapTaggedBlock.has_value() + || exposureTaggedBlock.has_value() + || newHueSatTaggedBlock.has_value() + || oldHueSatTaggedBlock.has_value() + || levelsTaggedBlock.has_value() + || photoFilterTaggedBlock.has_value() + || selectiveColorTaggedBlock.has_value() + ) + { + return std::make_shared>(); + } + #undef getGenericTaggedBlock + } + + // Now the layer could only be one of two more. A shape or pixel layer (Note files written before CS6 could fail this shape layer check here + { + #define getGenericTaggedBlock additionalLayerInfo.getTaggedBlock + + { + auto vecOriginDataTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::vecOriginData); + auto vecMaskSettingTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::vecMaskSettings); + auto vecStrokeDataTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::vecStrokeData); + auto vecStrokeContentDataTaggedBlock = getGenericTaggedBlock(Enum::TaggedBlockKey::vecStrokeContentData); + + if (vecOriginDataTaggedBlock.has_value() + || vecMaskSettingTaggedBlock.has_value() + || vecStrokeDataTaggedBlock.has_value() + || vecStrokeContentDataTaggedBlock.has_value() + ) + { + return std::make_shared>(); + } + #undef getGenericTaggedBlock + } + } + + return std::make_shared>(layerRecord, channelImageData, header); + } + + + + /// Recursively build a layer hierarchy using the LayerRecords, ChannelImageData and their respective reverse iterators + /// See comments in buildLayerHierarchy on why we iterate in reverse + template + std::vector>> buildLayerHierarchyRecurse( + std::vector& layerRecords, + std::vector& channelImageData, + std::vector::reverse_iterator& layerRecordsIterator, + std::vector::reverse_iterator& channelImageDataIterator, + const FileHeader& header + ) + { + std::vector>> root; + + // Iterate the layer records and channelImageData. These are always the same size + while (layerRecordsIterator != layerRecords.rend() && channelImageDataIterator != channelImageData.rend()) + { + auto& layerRecord = *layerRecordsIterator; + auto& channelImage = *channelImageDataIterator; + + std::shared_ptr> layer = identifyLayerType(layerRecord, channelImage, header); + + if (auto groupLayerPtr = std::dynamic_pointer_cast>(layer)) + { + // Recurse a level down + groupLayerPtr->m_Layers = buildLayerHierarchyRecurse(layerRecords, channelImageData, ++layerRecordsIterator, ++channelImageDataIterator, header); + root.push_back(groupLayerPtr); + } + else if (auto sectionLayerPtr = std::dynamic_pointer_cast>(layer)) + { + // We have reached the end of the current nested section therefore we return the current root object we hold; + return root; + } + else + { + root.push_back(layer); + } + try + { + ++layerRecordsIterator; + ++channelImageDataIterator; + } + catch (const std::exception& ex) + { + PSAPI_UNUSED(ex); + PSAPI_LOG_ERROR("LayeredFile", "Unhandled exception when trying to decrement the layer iterator"); + } + } + return root; + } + + + /// Build the layer hierarchy from a PhotoshopFile object using the Layer and Mask section with its LayerRecords and ChannelImageData subsections; + /// Returns a vector of nested layer variants which can go to any depth + template + std::vector>> buildLayerHierarchy(std::unique_ptr file) + { + auto* layerRecords = &file->m_LayerMaskInfo.m_LayerInfo.m_LayerRecords; + auto* channelImageData = &file->m_LayerMaskInfo.m_LayerInfo.m_ChannelImageData; + + if (layerRecords->size() != channelImageData->size()) + { + PSAPI_LOG_ERROR("LayeredFile", "LayerRecords Size does not match channelImageDataSize. File appears to be corrupted"); + } + + // 16 and 32 bit files store their layer records in the additional layer information section. We must therefore overwrite our previous results + if (sizeof(T) >= 2u && file->m_LayerMaskInfo.m_AdditionalLayerInfo.has_value()) + { + const AdditionalLayerInfo& additionalLayerInfo = file->m_LayerMaskInfo.m_AdditionalLayerInfo.value(); + auto lr16TaggedBlock = additionalLayerInfo.getTaggedBlock(Enum::TaggedBlockKey::Lr16); + auto lr32TaggedBlock = additionalLayerInfo.getTaggedBlock(Enum::TaggedBlockKey::Lr32); + if (lr16TaggedBlock.has_value()) + { + auto& block = lr16TaggedBlock.value(); + layerRecords = &block->m_Data.m_LayerRecords; + channelImageData = &block->m_Data.m_ChannelImageData; + } + else if (lr32TaggedBlock.has_value()) + { + auto& block = lr32TaggedBlock.value(); + layerRecords = &block->m_Data.m_LayerRecords; + channelImageData = &block->m_Data.m_ChannelImageData; + } + else + { + PSAPI_LOG_ERROR("LayeredFile", "PhotoshopFile does not seem to contain a Lr16 or Lr32 Tagged block which would hold layer information"); + } + } + + // Extract and iterate the layer records. We do this in reverse as Photoshop stores the layers in reverse + // For example, imagine this layer structure: + // + // Group + // ImageLayer + // + // Photoshop will actually store the layers like this + // + // Layer Divider + // ImageLayer + // Group + // + // Layer divider in this case being an empty layer with a 'lsct' tagged block with Type set to 3 + auto layerRecordsIterator = layerRecords->rbegin(); + auto channelImageDataIterator = channelImageData->rbegin(); + std::vector>> root = buildLayerHierarchyRecurse(*layerRecords, *channelImageData, layerRecordsIterator, channelImageDataIterator, file->m_Header); + + return root; + } + + /// Recursively build a flat layer hierarchy + template + void generateFlatLayersRecurse(const std::vector>>& nestedLayers, std::vector>>& flatLayers) + { + for (const auto& layer : nestedLayers) + { + // Recurse down if its a group layer + if (auto groupLayerPtr = std::dynamic_pointer_cast>(layer)) + { + flatLayers.push_back(layer); + LayeredFileImpl::generateFlatLayersRecurse(groupLayerPtr->m_Layers, flatLayers); + // 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>()); + } + else + { + flatLayers.push_back(layer); + } + } + } + + + /// Build a flat layer hierarchy from a nested layer structure and return this vector. Layer order + /// is not guaranteed + template + std::vector>> generateFlatLayers(const std::vector>>& nestedLayers) + { + std::vector>> flatLayers; + LayeredFileImpl::generateFlatLayersRecurse(nestedLayers, flatLayers); + + return flatLayers; + } + + + /// Find a layer based on a separated path and a parent layer. To be called by LayeredFile::findLayer + template + std::shared_ptr> findLayerRecurse(std::shared_ptr> parentLayer, std::vector path, int index) + { + // We must first check that the parent layer passed in is actually a group layer + if (auto groupLayerPtr = std::dynamic_pointer_cast>(parentLayer)) + { + for (const auto layerPtr : groupLayerPtr->m_Layers) + { + // Get the layer name and recursively check the path + if (layerPtr->m_LayerName == path[index]) + { + if (index == path.size() - 1) + { + // This is the last element and we return the item and propagate it up + return layerPtr; + } + return LayeredFileImpl::findLayerRecurse(layerPtr, path, index+1); + } + } + PSAPI_LOG_WARNING("LayeredFile", "Failed to find layer '%s' based on the path", path[index].c_str()); + return nullptr; + } + PSAPI_LOG_WARNING("LayeredFile", "Provided parent layer is not a grouplayer and can therefore not have children"); + return nullptr; + } + + template + void getNumChannelsRecurse(std::shared_ptr> parentLayer, std::set& channelIndices) + { + // We must first check if we could recurse down another level. We dont check for masks on the + // group here yet as we do that further down + if (auto groupLayerPtr = std::dynamic_pointer_cast>(parentLayer)) + { + for (const auto layerPtr : groupLayerPtr->m_Layers) + { + LayeredFileImpl::getNumChannelsRecurse(layerPtr, channelIndices); + } + } + + // Check for a pixel mask + if (parentLayer->m_LayerMask.has_value()) + { + channelIndices.insert(-2); + } + + // Deal with Image channels + if (auto imageLayerPtr = std::dynamic_pointer_cast>(parentLayer)) + { + for (const auto& pair : imageLayerPtr->m_ImageData) + { + channelIndices.insert(pair.first.index); + } + } + } + + template + void setCompressionRecurse(std::shared_ptr> parentLayer, const Enum::Compression compCode) + { + // We must first check if we could recurse down another level. We dont check for masks on the + // group here yet as we do that further down + if (const auto groupLayerPtr = std::dynamic_pointer_cast>(parentLayer)) + { + for (const auto& layerPtr : groupLayerPtr->m_Layers) + { + layerPtr->setCompression(compCode); + setCompressionRecurse(layerPtr, compCode); + } + } + } + + template + bool isLayerInDocumentRecurse(const std::shared_ptr> parentLayer, const std::shared_ptr> layer) + { + // We must first check that the parent layer passed in is actually a group layer + if (const auto groupLayerPtr = std::dynamic_pointer_cast>(parentLayer)) + { + for (const auto& layerPtr : groupLayerPtr->m_Layers) + { + if (layerPtr == layer) + { + return true; + } + if (isLayerInDocumentRecurse(layerPtr, layer)) + { + return true; + } + } + } + return false; + } + + /// Remove a layer from the hierarchy recursively, if a match is found we short circuit and return early + template + bool removeLayerRecurse(std::shared_ptr> parentLayer, std::shared_ptr> layer) + { + // We must first check that the parent layer passed in is actually a group layer + if (auto groupLayerPtr = std::dynamic_pointer_cast>(parentLayer)) + { + int index = 0; + for (const auto& layerPtr : groupLayerPtr->m_Layers) + { + if (layerPtr == layer) + { + groupLayerPtr->removeLayer(index); + return true; + } + if (removeLayerRecurse(layerPtr, layer)) + { + return true; + } + ++index; + } + } + return false; + } + + // Util functions + // -------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------- + + /// Read the ICC profile from the PhotoshopFile, if it doesnt exist we simply initialize an + /// empty ICC profile + ICCProfile readICCProfile(const PhotoshopFile* file); + + /// Read the document DPI, default to 72 if we cannot read it. + float readDPI(const PhotoshopFile* file); +} + + /// \brief Represents a layered file structure. /// /// This struct defines a layered file structure, where each file contains a hierarchy @@ -111,7 +525,27 @@ struct LayeredFile /// to a layered file using the lrSectionDivider taggedBlock to identify layer breaks. /// /// \param file The PhotoshopFile to transfer - LayeredFile(std::unique_ptr file); + LayeredFile(std::unique_ptr file) + { + // Take ownership of document + std::unique_ptr document = std::move(file); + + m_BitDepth = document->m_Header.m_Depth; + m_ColorMode = document->m_Header.m_ColorMode; + m_Width = document->m_Header.m_Width; + m_Height = document->m_Header.m_Height; + + // Extract the ICC Profile if it exists on the document, otherwise it will simply be empty + m_ICCProfile = LayeredFileImpl::readICCProfile(document.get()); + // Extract the DPI from the document, default to 72 + m_DotsPerInch = LayeredFileImpl::readDPI(document.get()); + + m_Layers = LayeredFileImpl::buildLayerHierarchy(std::move(document)); + if (m_Layers.size() == 0) + { + PSAPI_LOG_ERROR("LayeredFile", "Read an invalid PhotoshopFile as it does not contain any layers. Is the only layer in the scene locked? This is not supported by the PhotoshopAPI"); + } + } /// \ingroup Constructors /// \brief Constructs an empty LayeredFile object. @@ -121,9 +555,27 @@ struct LayeredFile /// \param colorMode The color mode of the file. /// \param width The width of the file in pixels. /// \param height The height of the file in pixels. - LayeredFile(Enum::ColorMode colorMode, uint64_t width, uint64_t height) requires std::same_as; - LayeredFile(Enum::ColorMode colorMode, uint64_t width, uint64_t height) requires std::same_as; - LayeredFile(Enum::ColorMode colorMode, uint64_t width, uint64_t height) requires std::same_as; + LayeredFile(Enum::ColorMode colorMode, uint64_t width, uint64_t height) requires std::same_as + { + m_BitDepth = Enum::BitDepth::BD_8; + m_ColorMode = colorMode; + m_Width = width; + m_Height = height; + } + LayeredFile(Enum::ColorMode colorMode, uint64_t width, uint64_t height) requires std::same_as + { + m_BitDepth = Enum::BitDepth::BD_16; + m_ColorMode = colorMode; + m_Width = width; + m_Height = height; + } + LayeredFile(Enum::ColorMode colorMode, uint64_t width, uint64_t height) requires std::same_as + { + m_BitDepth = Enum::BitDepth::BD_32; + m_ColorMode = colorMode; + m_Width = width; + m_Height = height; + } /// \brief Finds a layer based on the given path. /// @@ -132,7 +584,27 @@ struct LayeredFile /// /// \param path The path to the layer. /// \return A shared pointer to the found layer or nullptr. - std::shared_ptr> findLayer(std::string path) const; + std::shared_ptr> findLayer(std::string path) const + { + PROFILE_FUNCTION(); + std::vector segments = splitString(path, '/'); + for (const auto& layer : m_Layers) + { + // Get the layer name and recursively check the path + if (layer->m_LayerName == segments[0]) + { + // This is a simple path with no nested layers + if (segments.size() == 1) + { + return layer; + } + // Pass an index of one as we already found the first layer + return LayeredFileImpl::findLayerRecurse(layer, segments, 1); + } + } + PSAPI_LOG_WARNING("LayeredFile", "Unable to find layer path %s", path.c_str()); + return nullptr; + } /// /// \brief Inserts a layer into the root of the layered file. @@ -140,7 +612,15 @@ struct LayeredFile /// If you wish to add a layer to a group, use GroupLayer::addLayer() on a group node retrieved by \ref findLayer(). /// /// \param layer The layer to be added. - void addLayer(std::shared_ptr> layer); + void addLayer(std::shared_ptr> layer) + { + if (isLayerInDocument(layer)) + { + PSAPI_LOG_WARNING("LayeredFile", "Cannot insert a layer into the document twice, please use a unique layer. Skipping layer '%s'", layer->m_LayerName.c_str()); + return; + } + m_Layers.push_back(layer); + } /// \brief Moves a layer from its current parent to a new parent node. /// @@ -150,7 +630,42 @@ struct LayeredFile /// /// \param layer The layer to be moved. /// \param parentLayer The new parent layer (if not provided, moves to the root). - void moveLayer(std::shared_ptr> layer, std::shared_ptr> parentLayer = nullptr); + void moveLayer(std::shared_ptr> layer, std::shared_ptr> parentLayer = nullptr) + { + PROFILE_FUNCTION(); + // We must first check that we are not trying to move a layer higher in the hierarchy to lower in the hierarchy + // as that would be undefined behaviour. E.g. if we want to move /Group/ to /Group/NestedGroup that wouldnt work + // since the down stream nodes are dependant on the upstream nodes + if (parentLayer && isMovingToInvalidHierarchy(layer, parentLayer)) + { + PSAPI_LOG_WARNING("LayeredFile", "Cannot move layer '%s' under '%s' as that would represent an illegal move operation", + layer->m_LayerName.c_str(), parentLayer->m_LayerName.c_str()); + return; + } + + + // First we must remove the layer from the hierarchy and then reappend it in a different place + removeLayer(layer); + + // Insert the layer back, either under the provided parent layer or under the scene root + if (parentLayer) + { + if (auto groupLayerPtr = std::dynamic_pointer_cast>(parentLayer)) + { + groupLayerPtr->addLayer(*this, layer); + } + else + { + PSAPI_LOG_WARNING("LayeredFile", "Parent layer '%s' provided is not a group layer, can only move layers under groups", + parentLayer->m_LayerName.c_str()); + return; + } + } + else + { + addLayer(layer); + } + } /// \brief Moves a layer from its current parent to a new parent node. /// @@ -160,21 +675,76 @@ struct LayeredFile /// /// \param layer The layer to be moved. /// \param parentLayer The new parent layer (if not provided, moves to the root). - void moveLayer(const std::string layer, const std::string parentLayer = ""); + void moveLayer(const std::string layer, const std::string parentLayer = "") + { + PROFILE_FUNCTION(); + if (parentLayer == "") + { + auto layerPtr = findLayer(layer); + if (!layerPtr) [[unlikely]] + { + PSAPI_LOG_ERROR("LayeredFile", "Could not find the layer %s for moveLayer()", layer.c_str()); + } + moveLayer(layerPtr); + } + else + { + auto layerPtr = findLayer(layer); + auto parentLayerPtr = findLayer(parentLayer); + if (!layerPtr) [[unlikely]] + { + PSAPI_LOG_ERROR("LayeredFile", "Could not find the layer %s for moveLayer()", layer.c_str()); + } + if (!parentLayerPtr) [[unlikely]] + { + PSAPI_LOG_ERROR("LayeredFile", "Could not find the parentlayer %s for moveLayer()", parentLayer.c_str()); + } + moveLayer(layerPtr, parentLayerPtr); + } + } /// \brief Recursively removes a layer from the layer structure. /// /// Iterates the layer structure until the given node is found and then removes it from the tree. /// /// \param layer The layer to be removed. - void removeLayer(std::shared_ptr> layer); + void removeLayer(std::shared_ptr> layer) + { + PROFILE_FUNCTION(); + int index = 0; + for (auto& sceneLayer : m_Layers) + { + // Check if the layers directly in the scene root is the layer we are looking for and remove the layer if that is the case + if (sceneLayer == layer) + { + m_Layers.erase(m_Layers.begin() + index); + return; + } + + // Recurse down and short circuit if we find a match + if (LayeredFileImpl::removeLayerRecurse(sceneLayer, layer)) + { + return; + } + ++index; + } + } /// \brief Recursively removes a layer from the layer structure. /// /// Iterates the layer structure until the given node is found and then removes it from the tree. /// /// \param layer The layer to be removed. - void removeLayer(const std::string layer); + void removeLayer(const std::string layer) + { + PROFILE_FUNCTION(); + auto layerPtr = findLayer(layer); + if (!layerPtr) [[unlikely]] + { + PSAPI_LOG_ERROR("LayeredFile", "Could not find the layer %s for removeLayer()", layer.c_str()); + } + removeLayer(layerPtr); + } /// \brief change the compression codec across all layers and channels /// @@ -183,7 +753,14 @@ struct LayeredFile /// but ZipCompression gives us better ratios /// /// \param compCode the compression codec to apply - void setCompression(const Enum::Compression compCode); + void setCompression(const Enum::Compression compCode) + { + for (const auto& documentLayer : m_Layers) + { + documentLayer->setCompression(compCode); + LayeredFileImpl::setCompressionRecurse(documentLayer, compCode); + } + } /// Generate a flat layer stack from either the current root or (if supplied) from the given layer. /// Use this function if you wish to get the most up to date flat layer stack that is in the given @@ -194,7 +771,35 @@ struct LayeredFile /// \param layer Optional layer to start the generation from (default is root). /// \param order The order in which layers should be stacked. /// \return The flat layer tree with automatic \ref SectionDividerLayer inserted to mark section ends - std::vector>> generateFlatLayers(std::optional>> layer, const LayerOrder order) const; + std::vector>> generateFlatLayers(std::optional>> layer, const LayerOrder order) const + { + if (order == LayerOrder::forward) + { + if (layer.has_value()) + { + std::vector>> layerVec; + layerVec.push_back(layer.value()); + return LayeredFileImpl::generateFlatLayers(layerVec); + } + return LayeredFileImpl::generateFlatLayers(m_Layers); + } + else if (order == LayerOrder::reverse) + { + if (layer.has_value()) + { + std::vector>> layerVec; + layerVec.push_back(layer.value()); + std::vector>> flatLayers = LayeredFileImpl::generateFlatLayers(layerVec); + std::reverse(flatLayers.begin(), flatLayers.end()); + return flatLayers; + } + std::vector>> flatLayers = LayeredFileImpl::generateFlatLayers(m_Layers); + std::reverse(flatLayers.begin(), flatLayers.end()); + return flatLayers; + } + PSAPI_LOG_ERROR("LayeredFile", "Invalid layer order specified, only accepts forward or reverse"); + return std::vector>>(); + } /// \brief Gets the total number of channels in the document. /// @@ -204,13 +809,52 @@ struct LayeredFile /// \param ignoreMaskChannels Flag to exclude mask channels from the count. /// \param ignoreMaskChannel Flag to exclude the transparency alpha channel from the count. /// \return The total number of channels in the document. - uint16_t getNumChannels(bool ignoreMaskChannels = true, bool ignoreAlphaChannel = true); + uint16_t getNumChannels(bool ignoreMaskChannels = true, bool ignoreAlphaChannel = true) + { + std::set channelIndices = {}; + for (const auto& layer : m_Layers) + { + LayeredFileImpl::getNumChannelsRecurse(layer, channelIndices); + } + + uint16_t numChannels = channelIndices.size(); + if (ignoreMaskChannels) + { + // Photoshop doesnt consider mask channels for the total amount of channels + if (channelIndices.contains(-2)) + numChannels -= 1u; + if (channelIndices.contains(-3)) + numChannels -= 1u; + } + if (ignoreAlphaChannel) + { + // Photoshop doesnt store the alpha channels in the merged image data section so we must not count it + if (channelIndices.contains(-1)) + numChannels -= 1u; + } + return numChannels; + } /// \brief Checks if a layer already exists in the nested structure. /// /// \param layer The layer to check for existence. /// \return True if the layer exists, false otherwise. - bool isLayerInDocument(const std::shared_ptr> layer) const; + bool isLayerInDocument(const std::shared_ptr> layer) const + { + PROFILE_FUNCTION(); + for (const auto& documentLayer : m_Layers) + { + if (documentLayer == layer) + { + return true; + } + if (LayeredFileImpl::isLayerInDocumentRecurse(documentLayer, layer)) + { + return true; + } + } + return false; + } /// \brief read and create a LayeredFile from disk /// @@ -220,7 +864,14 @@ struct LayeredFile /// /// \param filePath the path on disk of the file to be read /// \param callback the callback which reports back the current progress and task to the user - static LayeredFile read(const std::filesystem::path& filePath, ProgressCallback& callback); + static LayeredFile read(const std::filesystem::path& filePath, ProgressCallback& callback) + { + auto inputFile = File(filePath); + auto psDocumentPtr = std::make_unique(); + psDocumentPtr->read(inputFile, callback); + LayeredFile layeredFile = { std::move(psDocumentPtr) }; + return layeredFile; + } /// \brief read and create a LayeredFile from disk /// @@ -229,7 +880,11 @@ struct LayeredFile /// PhotoshopFile instance to the user /// /// \param filePath the path on disk of the file to be read - static LayeredFile read(const std::filesystem::path& filePath); + static LayeredFile read(const std::filesystem::path& filePath) + { + ProgressCallback callback{}; + return LayeredFile::read(filePath, callback); + } /// \brief write the LayeredFile instance to disk, consumes and invalidates the instance /// @@ -241,7 +896,15 @@ struct LayeredFile /// \param filePath The path on disk of the file to be written /// \param callback the callback which reports back the current progress and task to the user /// \param forceOvewrite Whether to forcefully overwrite the file or fail if the file already exists - static void write(LayeredFile&& layeredFile, const std::filesystem::path& filePath, ProgressCallback& callback, const bool forceOvewrite = true); + static void write(LayeredFile&& layeredFile, const std::filesystem::path& filePath, ProgressCallback& callback, const bool forceOvewrite = true) + { + File::FileParams params = {}; + params.doRead = false; + params.forceOverwrite = forceOvewrite; + auto outputFile = File(filePath, params); + auto psdOutDocumentPtr = LayeredToPhotoshopFile(std::move(layeredFile)); + psdOutDocumentPtr->write(outputFile, callback); + } /// \brief write the LayeredFile instance to disk, consumes and invalidates the instance /// @@ -252,7 +915,11 @@ struct LayeredFile /// \param layeredFile The LayeredFile to consume, invalidates it /// \param filePath The path on disk of the file to be written /// \param forceOvewrite Whether to forcefully overwrite the file or fail if the file already exists - static void write(LayeredFile&& layeredFile, const std::filesystem::path& filePath, const bool forceOvewrite = true); + static void write(LayeredFile&& layeredFile, const std::filesystem::path& filePath, const bool forceOvewrite = true) + { + ProgressCallback callback{}; + LayeredFile::write(std::move(layeredFile), filePath, callback, forceOvewrite); + } private: @@ -263,7 +930,13 @@ struct LayeredFile /// \param layer The child layer to be moved. /// \param parentLayer The new parent layer. /// \return True if the move is valid, false otherwise. - bool isMovingToInvalidHierarchy(const std::shared_ptr> layer, const std::shared_ptr> parentLayer); + bool isMovingToInvalidHierarchy(const std::shared_ptr> layer, const std::shared_ptr> parentLayer) + { + // Check if the layer would be moving to one of its descendants which is illegal. Therefore the argument order is reversed + bool isDescendantOf = LayeredFileImpl::isLayerInDocumentRecurse(parentLayer, layer); + // We additionally check if the layer is the same as the parent layer as that would also not be allowed + return isDescendantOf || layer == parentLayer; + } }; @@ -305,68 +978,16 @@ std::shared_ptr> findLayerAs(const std::string path, const LayeredF /// \note This will not fill any specific TaggedBlocks or ResourceBlocks beyond what is required /// to create the layer structure. template -std::unique_ptr LayeredToPhotoshopFile(LayeredFile&& layeredFile); - - -namespace LayeredFileImpl +std::unique_ptr LayeredToPhotoshopFile(LayeredFile&& layeredFile) { - /// Build the layer hierarchy from a PhotoshopFile object using the Layer and Mask section with its LayerRecords and ChannelImageData subsections; - /// Returns a vector of nested layer variants which can go to any depth - template - std::vector>> buildLayerHierarchy(std::unique_ptr file); - /// Recursively build a layer hierarchy using the LayerRecords, ChannelImageData and their respective reverse iterators - /// See comments in buildLayerHierarchy on why we iterate in reverse - template - std::vector>> buildLayerHierarchyRecurse( - std::vector& layerRecords, - std::vector& channelImageData, - std::vector::reverse_iterator& layerRecordsIterator, - std::vector::reverse_iterator& channelImageDataIterator, - const FileHeader& header - ); - - /// Identify the type of layer the current layer record represents and return a layerVariant object (std::variant) - /// initialized with the given layer record and corresponding channel image data. - /// This function was heavily inspired by the psd-tools library as they have the most coherent parsing of this information - template - std::shared_ptr> identifyLayerType(LayerRecord& layerRecord, ChannelImageData& channelImageData, const FileHeader& header); - - /// Build a flat layer hierarchy from a nested layer structure and return this vector. Layer order - /// is not guaranteed - template - std::vector>> generateFlatLayers(const std::vector>>& nestedLayers); - - /// Recursively build a flat layer hierarchy - template - void generateFlatLayersRecurse(const std::vector>>& nestedLayers, std::vector>>& flatLayers); - - /// Find a layer based on a separated path and a parent layer. To be called by LayeredFile::findLayer - template - std::shared_ptr> findLayerRecurse(std::shared_ptr> parentLayer, std::vector path, int index); - - template - void getNumChannelsRecurse(std::shared_ptr> parentLayer, std::set& channelIndices); - - template - void setCompressionRecurse(std::shared_ptr> parentLayer, const Enum::Compression compCode); - - template - bool isLayerInDocumentRecurse(const std::shared_ptr> parentLayer, const std::shared_ptr> layer); - - /// Remove a layer from the hierarchy recursively, if a match is found we short circuit and return early - template - bool removeLayerRecurse(std::shared_ptr> parentLayer, std::shared_ptr> layer); - - // Util functions - // -------------------------------------------------------------------------------- - // -------------------------------------------------------------------------------- - - /// Read the ICC profile from the PhotoshopFile, if it doesnt exist we simply initialize an - /// empty ICC profile - ICCProfile readICCProfile(const PhotoshopFile* file); - - /// Read the document DPI, default to 72 if we cannot read it. - float readDPI(const PhotoshopFile* file); + PROFILE_FUNCTION(); + FileHeader header = generateHeader(layeredFile); + ColorModeData colorModeData = generateColorModeData(layeredFile); + ImageResources imageResources = generateImageResources(layeredFile); + LayerAndMaskInformation lrMaskInfo = generateLayerMaskInfo(layeredFile, header); + ImageData imageData = ImageData(layeredFile.getNumChannels(true, true)); // Ignore any mask or alpha channels + + return std::make_unique(header, colorModeData, std::move(imageResources), std::move(lrMaskInfo), imageData); }