Smart Object Warp Structure#

struct Warp#

Base warp structure, these encompass the warps found under Edit -> Transform which are:

Edit/Transform/Skew

Edit/Transform/Distort

Edit/Transform/Perspective

Edit/Transform/Warp

These are stored as part of the descriptor found on the PlacedLayer or PlacedLayerData Tagged Blocks. The data stored on the file does not differentiate between these types and they are all stored identically (the internal structure might change though)

Public Types

enum class WarpType#

Values:

enumerator normal#
enumerator quilt#

Normal warps in this case define a 4x4 warp grid.

Public Functions

Warp() = default#
Warp(std::vector<Geometry::Point2D<double>> warp, size_t u_dims, size_t v_dims)#

Initialize the warp struct from a set of geometric points describing a bezier surface with one or more quadratic bezier patches. These points are in scanline order (i.e. going first along the horizontal axis, then across the vertical axis).

Being a set of quadratic bezier patches the dimensions across the u and v (x and y) must be 4 or 4 + n * 3 where n is the number of subdivisions and is greater than one. In simple terms, this means a valid number of points per axis is 4, 7, 10, 13 etc.

Parameters:
  • warp – The warp points in scanline order.

  • u_dims – The dimensions across the u (x)

  • v_dims – The dimensions across the v (y)

inline bool valid()#

Deprecated: Check if the warp struct is valid. Returns true always.

Geometry::QuadMesh<double> mesh() const#

Generate a Mesh from our warp structure, this is primarily used for directly visualizing the points. To get a subdivided Bezier representation use surface().mesh()

Geometry::BezierSurface surface() const#

Generate a bezier surface from this warp structure. This can be used e.g. for rendering.

bool no_op() const#

Check if the warp resolves to a no-op. This means that all points for a given row/column lie on a single line and the non-affine transform is also a no op. If this is the case applying a warp can be skipped

template<typename T, size_t supersample_resolution = 4>
inline void apply(Render::ChannelBuffer<T> buffer, Render::ConstChannelBuffer<T> image, const Geometry::QuadMesh<double> &warp_mesh) const#

Apply the warp by warping the image into the buffer using the locally stored warp description. The buffer passed should match the general resolution of the warp points. So if e.g. the warp points are from { 0 - 4000, 0 - 2000 } the buffer parameter should cover these. If this isn’t the case the function won’t fail but the image will not contain the full warped picture.

Template Parameters:

supersample_resolution – The number of times to supersample mesh collisions within a given pixel along one axis, the default value of 4 implies that we sample 4x4 = 16 times per pixel.

Parameters:
  • buffer – The buffer to render into

  • image – The image to warp using the local warp struct

  • warp_mesh – A mesh to apply the warp with, mainly to be used as an optimization step if you wish to apply the warp to multiple channels at the same time to only calculate the mesh construction once. Can be gotten using SmartObjectWarp::mesh()

template<typename T, size_t supersample_resolution = 4>
inline void apply(Render::ChannelBuffer<T> buffer, Render::ConstChannelBuffer<T> image, size_t resolution = 25) const#

Apply the warp by warping the image into the buffer using the locally stored warp description. The buffer passed should match the general resolution of the warp points. So if e.g. the warp points are from { 0 - 4000, 0 - 2000 } the buffer parameter should cover these. If this isn’t the case the function won’t fail but the image will not contain the full warped picture.

Template Parameters:

supersample_resolution – The number of times to supersample mesh collisions within a given pixel along one axis, the default value of 4 implies that we sample 4x4 = 16 times per pixel.

Parameters:
  • buffer – The buffer to render into

  • image – The image do warp using the local warp struct

  • resolution – The resolution of warp geometry. Since we first convert the BezierSurface into a Mesh this parameter dictates how many pixels apart each subdivision line should be. Do note that increasing this massively does not necessarily give a better result. Defaults to 25.

Geometry::Point2D<double> point(size_t u_idx, size_t v_idx) const#

Get the warp point at the given u and v index. Checks for out of bounds index.

Geometry::Point2D<double> &point(size_t u_idx, size_t v_idx)#

Get a reference to the warp point at the given u and v index. Checks for out of bounds index.

inline std::vector<Geometry::Point2D<double>> points() const#

Get the points this warp struct holds.

void points(const std::vector<Geometry::Point2D<double>> &pts)#

Set the points this warp struct holds. Updates the bounding box. This assumes that the point ordering and resolution did not change.

inline size_t u_dimensions() const noexcept#

Get the dimensions/subdivisions across the u (x) axis.

inline size_t v_dimensions() const noexcept#

Get the dimensions/subdivisions across the v (y) axis.

Geometry::BoundingBox<double> bounds(bool consider_bezier = true) const#

Get the bounds of the smart object warp. These are recomputed on access so you are guaranteed to get the most up-to-date bounding box.

Parameters:

consider_bezier – If this is set to true we first create a bezier surface from the mesh giving us a precise bounding box while without this we may undershoot the bounding box. This carries some performance cost. Defaults to true

Returns:

either a tight fitting bbox or an approximate bbox depending on whether consider_bezier is set to true.

inline std::array<Geometry::Point2D<double>, 4> affine_transform() const#

Retrieve the affine transform which describes 4 points in the order {top-left, top-right, bot-left, bot-right} These represent the corner coordinates of the quad that describes the transformation (move, rotate, scale and shear).

All the opposing lines of this quad are guaranteed to be parallel unlike the non-affine transform.

This represents the following step in the transformation pipeline:

Full image -> Affine transform -> Non-affine transform -> Warp

inline void affine_transform(std::array<Geometry::Point2D<double>, 4> transform)#

must be parallel. It is recommended to use the transformation functions on the SmartObjectLayer such as move() rotate() scale() instead of modifying the quad directly for better and more intuitive control.

void affine_transform(Geometry::Point2D<double> top_left, Geometry::Point2D<double> top_right, Geometry::Point2D<double> bot_left, Geometry::Point2D<double> bot_right)#

Set the affine transform for the SmartObjectWarp. The opposing sides of the quad formed by the four points must be parallel. It is recommended to use the transformation functions on the SmartObjectLayer such as move() rotate() scale() instead of modifying the quad directly for better and more intuitive control.

inline const std::array<Geometry::Point2D<double>, 4> &non_affine_transform() const#

Retrieve the non-affine transform which describes 4 points in the order {top-left, top-right, bot-left, bot-right}. If no non-affine transform is present these are the same as affine_transform, pushing e.g. the top left and top right outwards would represent a perspective transform.

inline void non_affine_transform(std::array<Geometry::Point2D<double>, 4> transform)#

Set the non-affine transform which describes 4 points in the order {top-left, top-right, bot-left, bot-right}. These are described as positions in world-space that for a no-op must be the same as affine_transform. When wanting to e.g. perspective warp the top corners you must push the first two points outwards or inwards.

void non_affine_transform(Geometry::Point2D<double> top_left, Geometry::Point2D<double> top_right, Geometry::Point2D<double> bot_left, Geometry::Point2D<double> bot_right)#

Set the non-affine transform which describes 4 points in the order {top-left, top-right, bot-left, bot-right}. These are described as positions in world-space that for a no-op must be the same as affine_transform. When wanting to e.g. perspective warp the top corners you must push the first two points outwards or inwards.

bool operator==(const Warp &other) const#
std::unique_ptr<Descriptors::Descriptor> _serialize() const#

For internal API use: Create a “warp”/”quiltWarp” descriptor from this class ready to be stored on a PlacedLayer or PlacedLayerData tagged block

std::tuple<std::unique_ptr<Descriptors::List>, std::unique_ptr<Descriptors::List>> _generate_transform_descriptors() const#

For internal API use: Create the transform and non-affine transform descriptors

void _serialize_common(std::unique_ptr<Descriptors::Descriptor> &warp_descriptor) const#

For internal API use: Serialize the common parts between both quilt and normal warps

void _warp_style(std::string style)#

For internal API use: Set the warp style (“warpCustom” or “warpNone”)

void _warp_bounds(Geometry::BoundingBox<double> bounds)#

For internal API use: Set the bounds using the bounding box

Geometry::BoundingBox<double> _warp_bounds() const#

For internal API use: Get the bounds using the bounding box

void _warp_value(double value)#

For internal API use: Set the warp value, unsure where and how this is used, usually 0.0f

void _warp_perspective(double value)#

For internal API use: Set the warp perspective value, unsure where and how this is used, usually 0.0f

void _warp_perspective_other(double value)#

For internal API use: Set the warp perspective other? value, unsure where and how this is used, usually 0.0f

void _warp_rotate(std::string rotate)#

For internal API use: Set the warp rotate value, either “Hrzn” or “Vrtc”

void _quilt_slices_x(std::vector<double> slices)#

For internal API use: The positions of the quilt slices in terms of coordinates in the original image. if a slice is needed in the middle for example for an image with a width of 5000px the slice coordinate would be 2500 and the whole slices vect would be {0, 2500, 5000}

void _quilt_slices_y(std::vector<double> slices)#

For internal API use: The positions of the quilt slices in terms of coordinates in the original image. if a slice is needed in the middle for example for an image with a height of 5000px the slice coordinate would be 2500 and the whole slices vect would be {0, 2500, 5000}

void _warp_type(WarpType type)#

For internal API use: Set the type of warp. The two valid options are ‘quilt’ and ‘normal’ where normal is a 4x4 grid and quilt is anything beyond that (resolutions under 4x4 are not supported).

WarpType _warp_type() const#

Public Static Functions

static Warp generate_default(size_t width, size_t height)#

Generates a default warp, this should be the main entry point if you wish to author a custom warp.

Internally this will author a 4x4 grid describing a cubic bezier patch whose points can be transformed by retrieving point() and applying the given transformation to it. These grid points are laid out as follows:

0 1 2 3

4 5 6 7

8 9 10 11

12 13 14 15

Here point 0 would be the top left corner point. And points 1 and 4 the handles to the bezier. You now might wonder what the purpose of the center points are as they are not exposed in Photoshop itself.

These appear to be added to form a quad that is a parallelogram. We don’t currently expose any functionality for making this parallelogram from 3 points as the output is ambiguous. So if we continue with our previous example, to form the parallelogram we would have to modify point 5

If you wish to see how this 4x4 grid can look check out this page: https://github.com/EmilDohne/PhotoshopAPI/issues/90#issuecomment-2441823792

Parameters:
  • width – The width of the warp, logically this should be the full image width. In the context of a smart object for example this would be the width of the whole image, not of the generated preview.

  • height – The height of the warp, logically this should be the full image height. In the context of a smart object for example this would be the height of the whole image, not of the generated preview.

static Warp generate_default(size_t width, size_t height, size_t u_dimensions, size_t v_dimensions)#

Generates a default warp, this should be the main entry point if you wish to author a custom warp.

Internally this will author a u*v grid describing a collection of cubic bezier patches whose points can be transformed by retrieving point() and applying the given transformation to it. These grid points are laid out as follows (for a 4x4 grid, for other dimensions this would change accordingly):

0 1 2 3

4 5 6 7

8 9 10 11

12 13 14 15

Here point 0 would be the top left corner point. And points 1 and 4 the handles to the bezier. You now might wonder what the purpose of the center points are as they are not exposed in Photoshop itself.

These appear to be added to form a quad that is a parallelogram. We don’t currently expose any functionality for making this parallelogram from 3 points as the output is ambiguous. So if we continue with our previous example, to form the parallelogram we would have to modify point 5

If you wish to see how a 4x4 grid can look check out this page: https://github.com/EmilDohne/PhotoshopAPI/issues/90#issuecomment-2441823792

Note

For most use cases you likely want to use the overload with just a width and height parameter as that warp is easier to understand conceptually. Authoring warps using arbitray u and v dimensions requires fairly good knowledge of how bezier surfaces composed of quadratic bezier patches look like.

Parameters:
  • width – The width of the warp, logically this should be the full image width. In the context of a smart object for example this would be the width of the whole image, not of the generated preview.

  • height – The height of the warp, logically this should be the full image height. In the context of a smart object for example this would be the height of the whole image, not of the generated preview.

  • u_dimensions – The divisions in the u (x) dimension. These must follow the formula 4 + n * 3 where n represents the number of horizontal bezier patches - 1. So if you wish to construct 3 bezier patches horizontally this would be 10.

  • v_dimensions – The divisions in the v (y) dimension. These must follow the formula 4 + n * 3 where n represents the number of vertical bezier patches - 1. So if you wish to construct 3 bezier patches vertically this would be 10.

static std::unique_ptr<Descriptors::Descriptor> _serialize_default(size_t width, size_t height)#

For internal API use:

Create an empty “warp” descriptor from this class. This is to be used if the actual warp type is a quilt warp. Photoshop, if the warp is a “quiltWarp” stores a default initialized (but with the given width and height) “warp” descriptor to store after

static Warp _deserialize(const Descriptors::Descriptor *warp_descriptor, const Descriptors::List *transform, const Descriptors::List *non_affine_transform, normal_warp)#

For internal API use: Deserialize the SmartObjectWarp from a warp descriptor. In the context of the smart object this would be at the “warp” key.

static Warp _deserialize(const Descriptors::Descriptor *quilt_warp_descriptor, const Descriptors::List *transform, const Descriptors::List *non_affine_transform, quilt_warp)#

For internal API use: Deserialize the SmartObjectWarp from a warp descriptor. In the context of the smart object this would be at the “warp” key.

static std::array<Geometry::Point2D<double>, 4> _generate_affine_transform(const Descriptors::List *transform)#

For internal API use:

Generates an affine transform mesh from the descriptor.

static std::array<Geometry::Point2D<double>, 4> _generate_non_affine_transform(const Descriptors::List *non_affine_transform)#

For internal API use:

Generates a non affine transform from the descriptor

struct normal_warp#

Tags for dispatch.

struct quilt_warp#

Tags for dispatch.

struct PuppetWarp#

Not yet supported.

struct PerspectiveWarp#

Smart filter perspective warp, not yet supported.