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
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
4or4 + n * 3wherenis 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()#
Check if the warp struct is valid, for now returns whether the warp points hold any data.
-
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
imageinto thebufferusing the locally stored warp description. Thebufferpassed should match the general resolution of the warp points. So if e.g. the warp points are from { 0 - 4000, 0 - 2000 } thebufferparameter 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
imageinto thebufferusing the locally stored warp description. Thebufferpassed should match the general resolution of the warp points. So if e.g. the warp points are from { 0 - 4000, 0 - 2000 } thebufferparameter 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_bezieris 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.
-
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}
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.
-
struct normal_warp#
Tags for dispatch.
-
struct quilt_warp#
Tags for dispatch.
-
Warp() = default#
-
struct PuppetWarp#
Not yet supported.
-
struct PerspectiveWarp#
Smart filter perspective warp, not yet supported.