Smart Object Layer Structure#
Warp
-
template<typename T>
struct SmartObjectLayer : public Layer<T>, public ImageDataMixin<T># Smart objects are Photoshops’ way of non-destructive image data edits while keeping a live link to the original file.
We expose not only ways to replace this linked image data but also have functionality to recreate and store the warps applied to these objects (with more features coming in the future). We currently support recreating all the warps found in the Edit->Transform tab. We do not yet support the
Edit->Puppet WarpandEdit->Perspective Warpwhich are stored as Smart Filters.Smart objects store their original image data on the
LayeredFile<T>while storing a decoded preview the size of the layer on the layer itself. We provide multiple methods to get both the scaled and warped image data as well as the full size image data.Image Data:
Due to how SmartObjects work, image data is read-only. In order to modify the underlying image data you should use the
replace()method which will actually replace the underlying file the smart object is linked to.Getting the image data can be done via the
get_image_data(),get_channel()andget_original_image_data()functions. These will retrieve the transformed and warped image data. If you modify these you can requery these functions and get up to date image data.Transformations:
Unlike normal layers, SmartObjects have slightly different transformation rules. As they link back to a file in memory or on disk the transformations are stored ‘live’ and can be modified without negatively impacting the quality of the image. We expose a variety of transformation options to allow you to express this freedom.
Since we have both the original image data, and the rescaled image data to worry about there is two different widths and heights available:
original_width()/original_height()These represent the resolution of the original file image data, irrespective of what transforms are applied to it. If you are e.g. loading a 4000x2000 jpeg these will return 4000 and 2000 respectively. These values may not be written towidth()/height()These represent the final dimensions of the SmartObject with the warp and any transformations applied to it.
For actually transforming the layer we expose the following methods:
These are all individually documented and abstract away the underlying implementation of these operations. You likely will not have to dive deeper than these.
Warp:
Smart objects can also store warps which we implement using the
SmartObject::Warpstructure. These warps are stored as bezier surfaces with transformations applied on top of them. The transformations should be disregarded by the user as we provide easier functions on the SmartObjectLayer directly (see above). The warp itself is stored as a bezier surface. You may transfer these warps from one layer to another, modify them (although this requires knowledge of how bezier surfaces work), or clear them entirely.For the latter we provide the
reset_transform()andreset_warp()functions.Masks
-
inline bool has_mask() const noexcept#
Checks whether the layer has a pixel mask.
- Returns:
trueif a mask is present, otherwisefalse.
-
inline std::vector<T> get_mask() const#
Retrieves the mask channel data, if present.
If the layer does not have a mask, this function returns an empty vector.
The returned vector contains
mask_width() * mask_height()elements.
-
inline void get_mask(std::span<T> buffer) const#
Fills a preallocated buffer with the mask channel data, if present.
If no mask is present, the buffer remains unchanged.
- Parameters:
buffer – A preallocated buffer expected to have exactly
mask_width() * mask_height()elements.
-
inline compressed::channel<T> extract_mask()#
Extract the compressed mask channel used internally.
- Throws:
std::runtime_error – if no mask is present (as checked by
has_mask).
-
inline void set_mask(std::span<const T> buffer, size_t width, size_t height)#
Sets the layer’s mask to the given buffer.
If no mask was previously held the inserted mask will be at the top-left of the canvas. Use
mask_positionto adjust this.- Parameters:
buffer – The image data for the mask, provided in scanline order.
width – The width of the new mask.
height – The height of the new mask.
- Throws:
std::invalid_argument – If the buffer size does not match the expected dimensions.
-
inline void set_mask(const LayeredFile<T> &document, std::span<const T> buffer, std::optional<size_t> width = std::nullopt, std::optional<size_t> height = std::nullopt)#
Sets the layer’s mask to the given buffer.
If the layer previously had a mask, the new mask will be centered at the same position.
If no mask was previously present, the new mask will be centered on the document.
- Parameters:
document – The file to which this mask layer belongs.
buffer – The image data for the mask, provided in scanline order. If
widthandheightare unspecified, the buffer must have exactlymask_width() * mask_height()elements. If no dimensions are specified and the layer previously had no mask, an exception is thrown.width – (Optional) The width of the new mask. Required if replacing with a differently sized mask.
height – (Optional) The height of the new mask. Required if replacing with a differently sized mask.
- Throws:
std::invalid_argument – If the buffer size does not match the expected dimensions.
std::runtime_error – If no mask existed and no explicit dimensions were provided.
-
inline void set_mask_compression(Enum::Compression _compcode) noexcept#
Set the masks write compression.
If
has_mask()evaluates to false this is a no-op.- Parameters:
_compcode – The compression codec to apply on-write.
-
inline Geometry::BoundingBox<double> mask_bbox() const#
Retrieves the bounding box of the mask, if present.
If no mask exists, this function returns a zero-sized bounding box.
- Returns:
The mask’s bounding box, or an empty bounding box if no mask is present.
-
inline size_t mask_width() const#
Retrieves the width of the mask, if present.
If no mask exists, this function returns zero
- Returns:
The mask’s width or zero if no mask exists
-
inline size_t mask_height() const#
Retrieves the height of the mask, if present.
If no mask exists, this function returns zero
- Returns:
The mask’s height or zero if no mask exists
-
inline Geometry::Point2D<double> mask_position() const#
Retrieves the position of the mask, defined as its center.
If no mask is present, this function returns
{-1.0, -1.0}.- Returns:
The center position of the mask, or
{-1.0, -1.0}if no mask exists.
-
inline void mask_position(Geometry::Point2D<double> position)#
Sets the center position of the mask.
If no mask is present, this function does nothing.
- Parameters:
position – The new center position of the mask.
-
inline bool mask_relative_to_layer()#
Checks whether the mask is relative to the layer.
- Returns:
trueif the mask is relative to the layer, otherwisefalse.
-
inline void mask_relative_to_layer(bool value)#
Sets whether the mask should be relative to the layer.
- Parameters:
value –
trueto make the mask relative to the layer, otherwisefalse.
-
inline bool mask_disabled()#
Checks whether the mask is disabled.
- Returns:
trueif the mask is disabled, otherwisefalse.
-
inline void mask_disabled(bool value)#
Enables or disables the mask.
- Parameters:
value –
trueto disable the mask, otherwisefalse.
-
inline uint8_t mask_default_color()#
Retrieves the mask’s default fill color.
- Returns:
The default mask color.
-
inline void mask_default_color(uint8_t value)#
Sets the mask’s default fill color.
- Parameters:
value – The new default color value.
-
inline std::optional<uint8_t> mask_density()#
Retrieves the mask density, if specified.
- Returns:
The mask density value, or an empty optional if unspecified.
-
inline void mask_density(uint8_t value)#
Sets the mask density.
- Parameters:
value – The new mask density.
-
inline void mask_density(std::optional<uint8_t> value)#
Sets the mask density, allowing for removal.
- Parameters:
value – An optional new mask density.
-
inline std::optional<float64_t> mask_feather()#
Retrieves the mask feathering amount, if specified.
- Returns:
The mask feather value, or an empty optional if unspecified.
-
inline void mask_feather(float64_t value)#
Sets the mask feathering amount.
- Parameters:
value – The new mask feather value.
-
inline void mask_feather(std::optional<float64_t> value)#
Public Types
-
using channel_type = std::unique_ptr<channel_wrapper>#
Type used for a single channel.
-
using image_type = std::unordered_map<Enum::ChannelIDInfo, channel_type, Enum::ChannelIDInfoHasher>#
Type used for a mapping of channels.
Public Functions
-
inline virtual std::vector<int> channel_indices(bool include_mask) const override#
Get the channel indices held by this layer.
-
inline virtual size_t num_channels(bool include_mask) const override#
Get the total number of channels held by this layer.
-
inline virtual void set_write_compression(Enum::Compression _compcode) override#
Set the write compression for all channels.
This has no effect on the in-memory compression of these channels but only on write. Setting this therefore has a near-zero runtime cost.
- Parameters:
_compcode – The new compression setting.
-
SmartObjectLayer() = default#
-
inline SmartObjectLayer(LayeredFile<T> &file, Layer<T>::Params ¶meters, std::filesystem::path filepath, LinkedLayerType linkage = LinkedLayerType::data)#
Initialize a SmartObject layer from a filepath.
This will internally load the given file (assuming it exists) into memory, decoding the full resolution image data as well as generating a resampled image data based on the resolution provided in the layers’ parameters (this may be zero in which case we will ignore the width and height and keep the original size). Requires the
LayeredFileto be passed so we can keep track of this global state of linked layer data.- Parameters:
file – The LayeredFile this SmartObject is to be associated with
parameters – The Layers’ parameters
filepath – The path of the file to load, this must be a file format Photoshop knows about and can decode. If
link_externallyis set to true it is highly recommended to keep this file local to the output directory. I.e. if the file gets written toC:/PhotoshopFiles/file.psbThe file should be inC:/PhotoshopFiles/(same applies to linux). To learn more about how photoshop resolves these linkes head to this page: https://helpx.adobe.com/photoshop/using/create-smart-objects.html#linking_logiclinkage – Whether to link the file externally (without saving it in the document). While this does reduce file size, due to linking limitations it is usually recommended to leave this at its default
false. If the given file already exists on theLayeredFile<T>e.g. when you link 2 layers with the same filepath the settings for the first layer are used instead of overriding the behaviour
-
inline SmartObjectLayer(LayeredFile<T> &file, Layer<T>::Params ¶meters, std::filesystem::path filepath, const SmartObject::Warp &warp, LinkedLayerType linkage = LinkedLayerType::data)#
Initialize a SmartObject layer from a filepath.
This will internally load the given file (assuming it exists) into memory, decoding the full resolution image data as well as generating a resampled image data based on the resolution provided in the layers’ parameters (this may be zero in which case we will ignore the width and height and keep the original size). Requires the
LayeredFileto be passed so we can keep track of this global state of linked layer data.- Parameters:
file – The LayeredFile this SmartObject is to be associated with
parameters – The Layers’ parameters
filepath – The path of the file to load, this must be a file format Photoshop knows about and can decode. If
link_externallyis set to true it is highly recommended to keep this file local to the output directory. I.e. if the file gets written toC:/PhotoshopFiles/file.psbThe file should be inC:/PhotoshopFiles/(same applies to linux). To learn more about how photoshop resolves these linkes head to this page: https://helpx.adobe.com/photoshop/using/create-smart-objects.html#linking_logicwarp – The warp to apply to the image data, this may be default generated warp which can be modified later on by retrieving it using
warp(). After then modifying it, the updated warp will be lazily evaluated on write or access. So you may modify it as many times as you want but only retrieving it will call the evaluation. If you wish to skip this you can passSmartObject::Warp::generate_default(width, height)or use the alternative ctor withoutwarpargumentlinkage – Whether to link the file externally (without saving it in the document). While this does reduce file size, due to linking limitations it is usually recommended to leave this at its default
false. If the given file already exists on theLayeredFile<T>e.g. when you link 2 layers with the same filepath the settings for the first layer are used instead of overriding the behaviour
-
inline SmartObjectLayer(LayeredFile<T> &file, const LayerRecord &layerRecord, ChannelImageData &channelImageData, const FileHeader &header, const AdditionalLayerInfo &globalAdditionalLayerInfo)#
Generate a SmartObjectLayer from a Photoshop File object. This is for internal uses and not intended to be used by users directly. Please use the other constructors instead.
-
inline SmartObject::Warp warp() const noexcept#
Retrieve the warp object that is stored on this layer.
-
inline void warp(SmartObject::Warp _warp)#
Set the warp object held by this layer, this function may be used to replace the warp with e.g. the warp from another layer
-
inline void replace(std::filesystem::path path, bool link_externally = false)#
Replace the smart object with the given path keeping transformations as well as warp in place.
- Parameters:
path – The path to replace the image data with
link_externally – Whether to link the file externally or store the raw file bytes on the photoshop document itself. Keeping this at its default
falseis recommended for sharing these files.
-
inline bool linked_externally() const#
Check whether the original image file stored by this smart object is linked externally.
-
inline void set_linkage(LinkedLayerType linkage)#
Set the type of linkage for the original image data.
This can be data or external where data is storing the raw file bytes on the file itself while external will reference a file from disk. As this property only has an effect on-write this can be modified as many times as wanted.
As this affects not the layer directly but the shared linked layer if any other layers refer to this same file we modify that too.
- Parameters:
linkage – The type of linkage we want to set
-
inline const std::string &hash() const noexcept#
Retrieve the hashed value associated with the layer, this is what is used to identify the linked layer associated with this smart object (where the original image data is stored)
-
inline const std::string &filename() const noexcept#
Retrieve the filename associated with this smart object.
-
inline std::filesystem::path filepath() const#
Retrieve the filepath associated with this smart object. Depending on how the Smart object is linked (
externalordata) this may not be written to disk.
-
inline data_type get_original_image_data()#
Extract all the channels of the original image data.
Unlike the accessors
get_image_data()andget_channel()this function gets the full resolution image data that is stored on the smart object, i.e. the original image data. This may be smaller or larger than the layerswidthorheight. To get the actual resolution you can query:original_width()andoriginal_height()
-
inline size_t original_width() const#
Retrieve the original image datas’ width.
This does not have the same limitation as Photoshop layers of being limited to 30,000 or 300,000 pixels depending on the file type
- Throws:
std::runtime_error – if the hash defined by
hash()is not valid for the document- Returns:
The width of the original image data
-
inline size_t original_height() const#
Retrieve the original image datas’ height.
This does not have the same limitation as Photoshop layers of being limited to 30,000 or 300,000 pixels depending on the file type
- Throws:
std::runtime_error – if the hash defined by
hash()is not valid for the document- Returns:
The height of the original image data
-
inline void move(Geometry::Point2D<double> offset)#
Move the SmartObjectLayer (including any warps) by the given offset
- Parameters:
offset – the offset to move the layer by
-
inline void rotate(double offset, Geometry::Point2D<double> center)#
Rotate the SmartObjectLayer (including any warps) by the given offset in degrees around the provided center point, this point does not have to lie on the pixels of the image
- Parameters:
offset – The rotation value in degrees
center – The center point to rotate around
-
inline void rotate(double offset)#
Rotate the SmartObjectLayer (including any warps) by the given offset in degrees around the center of the layer
- Parameters:
offset – The rotation value in degrees
-
inline void scale(Geometry::Point2D<double> factor, Geometry::Point2D<double> center)#
Scale the SmartObjectLayer (including any warps) by the given factor in both the x and y dimensions.
- Parameters:
factor – The scalar factor
center – The point to scale about
-
inline void scale(double factor, Geometry::Point2D<double> center)#
Scale the SmartObjectLayer (including any warps) by the given factor in both the x and y dimensions.
- Parameters:
factor – The scalar factor
center – The point to scale about
-
inline void scale(Geometry::Point2D<double> factor)#
Scale the SmartObjectLayer (including any warps) by the given factor in both the x and y dimensions around the layers center.
- Parameters:
factor – The scalar factor
-
inline void scale(double factor)#
Scale the SmartObjectLayer (including any warps) by the given factor in both the x and y dimensions around the layers center.
- Parameters:
factor – The scalar factor
-
inline void transform(const Eigen::Matrix3d &matrix)#
Apply a transformation (affine or non affine) to the smart object. This can be used in order to e.g. skew or perspective transform the image. Automatically splits the matrix into it’s affine and non-affine transformations and applies these separately.
- Parameters:
matrix – The transformation matrix, will internally be split into its affine and non-affine components.
-
inline virtual uint32_t width() const noexcept override#
The layers’ width from 0 - 300,000.
-
inline virtual void width(uint32_t layer_width) override#
Set the layers’ width, analogous to calling
scale()while only scaling around the x axis.
-
inline virtual uint32_t height() const noexcept override#
The layers’ height from 0 - 300,000.
-
inline virtual void height(uint32_t layer_height) override#
Set the layers’ height, analogous to calling
scale()while only scaling around the y axis.
-
inline virtual float center_x() const noexcept override#
The layers’ x center coordinate
I.e. if the layer has the bounds { 200, 200 } - { 1000, 1000 } The center would be at { 600, 600 }
-
inline virtual void center_x(float x_coord) noexcept override#
Set the x center coordinate, analogous to calling
move()while only moving on the x axis.
-
inline virtual float center_y() const noexcept override#
The layers’ y center coordinate
I.e. if the layer has the bounds { 200, 200 } - { 1000, 1000 } The center would be at { 600, 600 }
-
inline virtual void center_y(float y_coord) noexcept override#
Set the y center coordinate, analogous to calling
move()while only moving on the y axis.
-
inline void reset_transform()#
Reset all the transformations (not the warp) applied to the layer to map it back to the original square from [0 -
original_width()] and [0 -original_height()]. This does not reset the warp itself so if you had a warp applied it will stay.If you instead wish to clear the warp you can use
reset_warp().These two may be used in combination and sequence, so it is perfectly valid to call
reset_transformandreset_warpin any order
-
inline void reset_warp()#
Reset the warp (not the transformations) applied to the Smart Object.
If you instead wish to clear the transformations you can use the
reset_transform()function.These two may be used in combination and sequence, so it is perfectly valid to call
reset_transformandreset_warpin any order
-
inline virtual std::tuple<LayerRecord, ChannelImageData> to_photoshop() override#
Function for creating a PhotoshopFile compatible types from the layer.
This is part of the internal API and as a user you will likely never have to use this function.
In the future, the intention is to make this a pure virtual function. However, due to the presence of multiple miscellaneous layers not yet implemented for the initial release, this function is provided. It generates a tuple containing LayerRecord and ChannelImageData based on the specified ColorMode, and using the provided FileHeader.
- Returns:
A tuple containing LayerRecord and ChannelImageData representing the layer in the PhotoshopFile.
-
inline const std::string &name() const noexcept#
The layers’ name. Stored as a utf-8 string.
-
inline std::string &name() noexcept#
The layers’ name. Stored as a utf-8 string.
-
inline void name(const std::string &layer_name) noexcept#
The layers’ name. Stored as a utf-8 string.
-
inline Enum::BlendMode &blendmode() noexcept#
The blendmode of the layer, the
Passthroughblendmode is only valid for groups.
-
inline Enum::BlendMode blendmode() const noexcept#
The blendmode of the layer, the
Passthroughblendmode is only valid for groups.
-
inline void blendmode(Enum::BlendMode blend_mode) noexcept#
The blendmode of the layer, the
Passthroughblendmode is only valid for groups.
-
inline bool &locked() noexcept#
Whether the layers’ pixel values are locked. This is currently an all or nothing setting.
-
inline bool locked() const noexcept#
Whether the layers’ pixel values are locked. This is currently an all or nothing setting.
-
inline void locked(bool is_locked) noexcept#
Whether the layers’ pixel values are locked. This is currently an all or nothing setting.
-
inline bool &visible() noexcept#
Visibility toggle of the layer.
-
inline bool visible() const noexcept#
Visibility toggle of the layer.
-
inline void visible(bool is_visible) noexcept#
Visibility toggle of the layer.
-
inline bool &clipping_mask() noexcept#
Clipping mask toggle of the layer, clips it to the layer below.
-
inline bool clipping_mask() const noexcept#
Clipping mask toggle of the layer, clips it to the layer below.
-
inline void clipping_mask(bool is_clipped) noexcept#
Clipping mask toggle of the layer, clips it to the layer below.
-
inline float opacity() const noexcept#
The layers’ opacity.
In photoshop this is stored as a
uint8_tfrom 0-255 but access and write is in terms of a float for better consistency.
-
inline void opacity(float value) noexcept#
The layers’ opacity.
In photoshop this is stored as a
uint8_tfrom 0-255 but access and write is in terms of a float for better consistency.
-
inline float fill() const noexcept#
The layers’ fill value.
-
inline void fill(float value) noexcept#
The layers’ fill value.
-
inline float top_left_x() const noexcept#
Convenience function for accessing the top left x coordinate of a layer.
-
inline float top_left_y() const noexcept#
Convenience function for accessing the top left y coordinate of a layer.
-
inline Enum::ColorMode color_mode() const noexcept#
The color mode with which the file was created, only stored to allow better detection during channel access for e.g. image layers
-
inline Enum::LayerColor display_color() const noexcept#
The layers’ display color in the GUI.
-
inline void display_color(const Enum::LayerColor color) noexcept#
-
inline const image_type &get_storage() const#
Get the underlying storage of the image data held by this Layer. This may not always be up-to-date in the case of layers that require rendering such as SmartObject layers or Text layers.
Usually a user should not have to access image data this way and should instead use
get_image_data()orget_channel().
-
inline data_type get_image_data()#
Get the image data held by this layer, this includes all channels as well as any masks.
Therefore not all channels are guaranteed to be the same. If
has_mask()is true (or channel -2 if in the data) the mask channel may be any size and does not have to overlap with the layer.The other channels do however have to be the same size
- Returns:
The evaluated image data including mask.
-
inline std::vector<T> get_channel(int _id)#
Get the channel held at the given index
This channel will have the dimensions
width()*height()unless you are requesting the mask channel -2. This will instead hold the dimensions described bymask_bbox(). Callingget_channel()with index -2 is equivalent to callingget_mask()Generally this will method will be slightly slower than calling
get_image_datafor multiple channels asget_image_datais parallelized.- Returns:
The evaluated channel
-
inline std::vector<T> get_channel(Enum::ChannelID _id)#
Get the channel held at the given index
This channel will have the dimensions
width()*height()unless you are requesting the mask channel -2. This will instead hold the dimensions described bymask_bbox(). Callingget_channel()with index -2 is equivalent to callingget_mask()Generally this will method will be slightly slower than calling
get_image_datafor multiple channels asget_image_datais parallelized.- Returns:
The evaluated channel
-
inline std::vector<T> get_channel(Enum::ChannelIDInfo _id)#
Get the channel held at the given index
This channel will have the dimensions
width()*height()unless you are requesting the mask channel -2. This will instead hold the dimensions described bymask_bbox(). Callingget_channel()with index -2 is equivalent to callingget_mask()Generally this will method will be slightly slower than calling
get_image_datafor multiple channels asget_image_datais parallelized.- Returns:
The evaluated channel
Public Static Functions
-
static inline std::optional<channel_type> split_mask(image_type &data)#
Convenience function for splitting a mask channel from an image data mapping. This is usually necessary to ensure the mask is handled separately and stored on e.g. the
MaskMixin<T>.This will extract the mask channel from
dataand optionally return it (if it exists)- Returns:
an optional mask channel if the passed image data contains it.
Public Static Attributes
-
static constexpr auto s_mask_index = Enum::ChannelIDInfo{Enum::ChannelID::UserSuppliedLayerMask, -2}#
Colormode independent mask index as Enum::ChannelIDInfo that may be used.