Skip to content

Commit

Permalink
Wrap PathQuerySuite and PathDataSuite (#71)
Browse files Browse the repository at this point in the history
* Wrap PathQuerySuite and PathDataSuite
* Add higher level wrappers around path suites
  • Loading branch information
fadaaszhi authored Nov 15, 2024
1 parent 349c712 commit 9cf784e
Show file tree
Hide file tree
Showing 4 changed files with 313 additions and 2 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,8 @@ AfterEffectsSDK
| 🔳 Marker | ✅ iterate16 | | |
| ✅ Mask Outline | ✅ iterateFloat | | |
| ✅ Mask | ✅ Param Utils | | |
| 🔳 Math | 🔳 Path Data | | |
| ✅ Memory | 🔳 Path Query | | |
| 🔳 Math | Path Data | | |
| ✅ Memory | Path Query | | |
| 🔳 Output Module | ✅ Pixel Data | | |
| 🔳 Persistent Data | ✅ Pixel Format | | |
| ✅ PF Interface | ✅ PointParam | | |
Expand Down
14 changes: 14 additions & 0 deletions after-effects/src/pf/effect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ define_suite_item_wrapper!(
effect_sequence_data: pf::suites::EffectSequenceData,
param_utils: pf::suites::ParamUtils,
effect_ui: pf::suites::EffectUI,
path_query: pf::suites::PathQuery,
/// TODO: write docs for Effect
Effect {
dispose: ;
Expand Down Expand Up @@ -219,6 +220,19 @@ define_suite_item_wrapper!(
///
/// NOTE: This must be called during [`Command::ParamsSetup`].
set_options_button_name(name: &str) -> () => effect_ui.set_options_button_name,

// ―――――――――――――――――――――――――――― Path Query suite functions ――――――――――――――――――――――――――――

/// Retrieves the number of paths associated with the effect’s source layer.
num_paths() -> i32 => path_query.num_paths,

/// Retrieves the `PF_PathID` for the specified path.
path_info(index: i32) -> ae_sys::PF_PathID => path_query.path_info,

/// Acquires the [`PathOutline`] for the path at the specified time.
/// `PathOutline` automatically calls `checkin_path` on drop.
/// Note the result may be `None` even if `unique_id != PF_PathID_NONE` (the path may have been deleted).
checkout_path(unique_id: ae_sys::PF_PathID, what_time: i32, time_step: i32, time_scale: u32) -> Option<PathOutline> => path_query.checkout_path,
}
);

Expand Down
7 changes: 7 additions & 0 deletions after-effects/src/pf/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ pub mod suites {
PointParamSuite as PointParam };
pub(crate) mod gpu_device; pub use gpu_device ::GPUDeviceSuite as GPUDevice;
pub(crate) mod fill_matte; pub use fill_matte ::FillMatteSuite as FillMatte;
pub(crate) mod path; pub use path ::{ PathQuerySuite as PathQuery,
PathDataSuite as PathData };
}

pub use suites::adv_item::Step;
Expand Down Expand Up @@ -84,6 +86,11 @@ pub use suites::param_utils::{
TimeDir,
};
pub use suites::pixel_format::PixelFormat;
pub use suites::path::{
MaskMode,
PathOutline,
PathSegPrep,
};

define_enum! {
ae_sys::PF_XferMode,
Expand Down
290 changes: 290 additions & 0 deletions after-effects/src/pf/suites/path.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
use crate::*;
use ae_sys::*;

define_suite!(
/// This suite is used to identify and access the paths associated with the effect’s source layer.
PathQuerySuite,
PF_PathQuerySuite1,
kPFPathQuerySuite,
kPFPathQuerySuiteVersion1
);

impl PathQuerySuite {
/// Acquire this suite from the host. Returns error if the suite is not available.
/// Suite is released on drop.
pub fn new() -> Result<Self, Error> {
crate::Suite::new()
}

/// Retrieves the number of paths associated with the effect’s source layer.
pub fn num_paths(&self, effect_ref: impl AsPtr<PF_ProgPtr>) -> Result<i32, Error> {
call_suite_fn_single!(self, PF_NumPaths -> A_long, effect_ref.as_ptr())
}

/// Retrieves the `PF_PathID` for the specified path.
pub fn path_info(&self, effect_ref: impl AsPtr<PF_ProgPtr>, index: i32) -> Result<PF_PathID, Error> {
call_suite_fn_single!(self, PF_PathInfo -> PF_PathID, effect_ref.as_ptr(), index)
}

/// Acquires the [`PathOutline`] for the path at the specified time.
/// `PathOutline` automatically calls `checkin_path` on drop.
/// Note the result may be `None` even if `unique_id != PF_PathID_NONE` (the path may have been deleted).
pub fn checkout_path(&self, effect_ref: impl AsPtr<PF_ProgPtr>, unique_id: PF_PathID, what_time: i32, time_step: i32, time_scale: u32) -> Result<Option<PathOutline>, Error> {
let effect_ref = effect_ref.as_ptr();
let path = call_suite_fn_single!(self, PF_CheckoutPath -> PF_PathOutlinePtr, effect_ref, unique_id, what_time, time_step, time_scale)?;
PathOutline::from_raw(effect_ref, unique_id, path)
}

/// Releases the path back to After Effects. Always do this, regardless of any error conditions
/// encountered. Every checkout must be balanced by a checkin, or pain will ensue. This function
/// is automatically called in `PathOutline`'s `Drop` implementation.
pub fn checkin_path(&self, effect_ref: impl AsPtr<PF_ProgPtr>, unique_id: PF_PathID, changed: bool, path: PF_PathOutlinePtr) -> Result<(), Error> {
call_suite_fn!(self, PF_CheckinPath, effect_ref.as_ptr(), unique_id, changed as _, path)
}
}

define_suite!(
/// This suite provides information about paths (sequences of vertices).
PathDataSuite,
PF_PathDataSuite1,
kPFPathDataSuite,
kPFPathDataSuiteVersion1
);

impl PathDataSuite {
/// Acquire this suite from the host. Returns error if the suite is not available.
/// Suite is released on drop.
pub fn new() -> Result<Self, Error> {
crate::Suite::new()
}

/// Returns `true` if the path is not closed (if the beginning and end vertex are not identical).
pub fn path_is_open(&self, effect_ref: impl AsPtr<PF_ProgPtr>, path: PF_PathOutlinePtr) -> Result<bool, Error> {
Ok(call_suite_fn_single!(self, PF_PathIsOpen -> PF_Boolean, effect_ref.as_ptr(), path)? != 0)
}

/// Retrieves the number of segments in the path. N segments means there are segments `[0.N-1]`;
/// segment J is defined by vertex `J` and `J+1`.
pub fn path_num_segments(&self, effect_ref: impl AsPtr<PF_ProgPtr>, path: PF_PathOutlinePtr) -> Result<i32, Error> {
call_suite_fn_single!(self, PF_PathNumSegments -> A_long, effect_ref.as_ptr(), path)
}

/// Retrieves the `PF_PathVertex` for the specified path. The range of points is `[0.num_segments]`;
/// for closed paths, `vertex[0] == vertex[num_segments]`.
pub fn path_vertex_info(&self, effect_ref: impl AsPtr<PF_ProgPtr>, path: PF_PathOutlinePtr, which_point: i32) -> Result<PF_PathVertex, Error> {
call_suite_fn_single!(self, PF_PathVertexInfo -> PF_PathVertex, effect_ref.as_ptr(), path, which_point)
}

/// This fairly counter-intuitive function informs After Effects that you’re going to ask for the
/// length of a segment (using `path_get_seg_length` below), and it’d better get ready. `frequency`
/// indicates how many times you’d like us to sample the length; our internal effects use 100.
pub fn path_prepare_seg_length(&self, effect_ref: impl AsPtr<PF_ProgPtr>, path: PF_PathOutlinePtr, which_seg: i32, frequency: i32) -> Result<PF_PathSegPrepPtr, Error> {
call_suite_fn_single!(self, PF_PathPrepareSegLength -> PF_PathSegPrepPtr, effect_ref.as_ptr(), path, which_seg, frequency)
}

/// Retrieves the length of the given segment.
pub fn path_get_seg_length(&self, effect_ref: impl AsPtr<PF_ProgPtr>, path: PF_PathOutlinePtr, which_seg: i32, length_prep: &mut PF_PathSegPrepPtr) -> Result<f64, Error> {
call_suite_fn_single!(self, PF_PathGetSegLength -> PF_FpLong, effect_ref.as_ptr(), path, which_seg, length_prep)
}

/// Retrieves the location of a point `length` along the given path segment.
///
/// Returns a tuple containing `(x, y)`
pub fn path_eval_seg_length(&self, effect_ref: impl AsPtr<PF_ProgPtr>, path: PF_PathOutlinePtr, length_prep: &mut PF_PathSegPrepPtr, which_seg: i32, length: f64) -> Result<(f64, f64), Error> {
call_suite_fn_double!(self, PF_PathEvalSegLength -> PF_FpLong, PF_FpLong, effect_ref.as_ptr(), path, length_prep, which_seg, length)
}

/// Retrieves the location, and the first derivative, of a point `length` along the given path segment.
/// If you’re not sure why you’d ever need this, don’t use it. Math is hard.
///
/// Returns a tuple containing `(x, y, deriv1x, deriv1y)`
pub fn path_eval_seg_length_deriv1(&self, effect_ref: impl AsPtr<PF_ProgPtr>, path: PF_PathOutlinePtr, length_prep: &mut PF_PathSegPrepPtr, which_seg: i32, length: f64) -> Result<(f64, f64, f64, f64), Error> {
let mut x = 0.0;
let mut y = 0.0;
let mut deriv1x = 0.0;
let mut deriv1y = 0.0;
call_suite_fn!(self, PF_PathEvalSegLengthDeriv1, effect_ref.as_ptr(), path, length_prep, which_seg, length, &mut x, &mut y, &mut deriv1x, &mut deriv1y)?;
Ok((x, y, deriv1x, deriv1y))
}

/// Call this when you’re finished evaluating that segment length, so After Effects
/// can properly clean up the `PF_PathSegPrepPtr`.
pub fn path_cleanup_seg_length(&self, effect_ref: impl AsPtr<PF_ProgPtr>, path: PF_PathOutlinePtr, which_seg: i32, length_prep: &mut PF_PathSegPrepPtr) -> Result<(), Error> {
call_suite_fn!(self, PF_PathCleanupSegLength, effect_ref.as_ptr(), path, which_seg, length_prep)
}

/// Returns `true` if the path is inverted.
pub fn path_is_inverted(&self, effect_ref: impl AsPtr<PF_ProgPtr>, unique_id: PF_PathID) -> Result<bool, Error> {
Ok(call_suite_fn_single!(self, PF_PathIsInverted -> PF_Boolean, effect_ref.as_ptr(), unique_id)? != 0)
}

/// Retrieves the mode for the given path.
pub fn path_get_mask_mode(&self, effect_ref: impl AsPtr<PF_ProgPtr>, unique_id: PF_PathID) -> Result<MaskMode, Error> {
Ok(call_suite_fn_single!(self, PF_PathGetMaskMode -> PF_MaskMode, effect_ref.as_ptr(), unique_id)?.into())
}

/// Retrieves the name of the path.
pub fn path_get_name(&self, effect_ref: impl AsPtr<PF_ProgPtr>, unique_id: PF_PathID) -> Result<String, Error> {
let mut name = [0; PF_MAX_PATH_NAME_LEN as usize + 1];
call_suite_fn!(self, PF_PathGetName, effect_ref.as_ptr(), unique_id, name.as_mut_ptr())?;
Ok(unsafe { std::ffi::CStr::from_ptr(name.as_ptr()) }.to_string_lossy().into_owned())
}
}

define_enum! {
PF_MaskMode,
MaskMode {
None = ae_sys::PF_MaskMode_NONE,
Add = ae_sys::PF_MaskMode_ADD,
Subtract = ae_sys::PF_MaskMode_SUBTRACT,
Intersect = ae_sys::PF_MaskMode_INTERSECT,
Lighten = ae_sys::PF_MaskMode_LIGHTEN,
Darken = ae_sys::PF_MaskMode_DARKEN,
Difference = ae_sys::PF_MaskMode_DIFFERENCE,
Accum = ae_sys::PF_MaskMode_ACCUM,
}
}

/// The path from a text layer, shape layer or mask.
pub struct PathOutline {
suite: PathDataSuite,
effect_ref: PF_ProgPtr,
unique_id: PF_PathID,
path: PF_PathOutlinePtr,
}

impl std::fmt::Debug for PathOutline {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn e(r: &Result<impl std::fmt::Debug, Error>) -> &dyn std::fmt::Debug {
match r {
Ok(x) => x,
Err(e) => e,
}
}

f.debug_struct("PathOutline")
.field("name", e(&self.name()))
.field("id", &self.id())
.field("num_segments", e(&self.num_segments()))
.field("is_open", e(&self.is_open()))
.field("is_inverted", e(&self.is_inverted()))
.field("mask_mode", e(&self.mask_mode()))
.finish()
}
}

impl AsPtr<PF_PathOutlinePtr> for PathOutline {
fn as_ptr(&self) -> PF_PathOutlinePtr {
self.path
}
}

impl PathOutline {
pub fn from_raw(effect_ref: PF_ProgPtr, unique_id: PF_PathID, path: PF_PathOutlinePtr) -> Result<Option<Self>, Error> {
if path.is_null() {
Ok(None)
} else {
Ok(Some(Self {
suite: PathDataSuite::new()?,
effect_ref,
unique_id,
path,
}))
}
}

/// Returns the ID of the path.
pub fn id(&self) -> PF_PathID {
self.unique_id
}

/// Returns `true` if the path is not closed (if the beginning and end vertex are not identical).
pub fn is_open(&self) -> Result<bool, Error> {
self.suite.path_is_open(self.effect_ref, self.path)
}

/// Retrieves the number of segments in the path. N segments means there are segments `[0.N-1]`;
/// segment J is defined by vertex `J` and `J+1`.
pub fn num_segments(&self) -> Result<i32, Error> {
self.suite.path_num_segments(self.effect_ref, self.path)
}

/// Retrieves the `PF_PathVertex` for the specified path. The range of points is `[0.num_segments]`;
/// for closed paths, `vertex[0] == vertex[num_segments]`.
pub fn vertex(&self, which_point: i32) -> Result<PF_PathVertex, Error> {
self.suite.path_vertex_info(self.effect_ref, self.path, which_point)
}

/// This fairly counter-intuitive function informs After Effects that you’re going to ask for the
/// length of a segment (using [`PathSegPrep::length`]), and it’d better get ready. `frequency`
/// indicates how many times you’d like us to sample the length; our internal effects use 100.
pub fn prepare_seg_length(&self, which_seg: i32, frequency: i32) -> Result<PathSegPrep, Error> {
Ok(PathSegPrep {
path: self,
which_seg,
length_prep: self.suite.path_prepare_seg_length(self.effect_ref, self.path, which_seg, frequency)?,
})
}

/// Returns `true` if the path is inverted.
pub fn is_inverted(&self) -> Result<bool, Error> {
self.suite.path_is_inverted(self.effect_ref, self.unique_id)
}

/// Retrieves the mode for the path.
pub fn mask_mode(&self) -> Result<MaskMode, Error> {
self.suite.path_get_mask_mode(self.effect_ref, self.unique_id)
}

/// Retrieves the name of the path.
pub fn name(&self) -> Result<String, Error> {
self.suite.path_get_name(self.effect_ref, self.unique_id)
}
}

impl Drop for PathOutline {
fn drop(&mut self) {
PathQuerySuite::new()
.expect("Failed to acquire PathQuerySuite")
.checkin_path(self.effect_ref, self.unique_id, false, self.path)
.expect("Failed to check in PF_PathOutlinePtr");
}
}

/// Information pertaining to the length of a segment in a [`PathOutline`].
pub struct PathSegPrep<'a> {
path: &'a PathOutline,
which_seg: i32,
length_prep: PF_PathSegPrepPtr,
}

impl<'a> PathSegPrep<'a> {
/// Retrieves the length of the segment.
pub fn length(&mut self) -> Result<f64, Error> {
self.path.suite.path_get_seg_length(self.path.effect_ref, self.path.path, self.which_seg, &mut self.length_prep)
}

/// Retrieves the location of a point `length` along the segment.
///
/// Returns a tuple containing `(x, y)`
pub fn eval(&mut self, length: f64) -> Result<(f64, f64), Error> {
self.path.suite.path_eval_seg_length(self.path.effect_ref, self.path.path, &mut self.length_prep, self.which_seg, length)
}

/// Retrieves the location, and the first derivative, of a point `length` along the segment.
/// If you’re not sure why you’d ever need this, don’t use it. Math is hard.
///
/// Returns a tuple containing `(x, y, deriv1x, deriv1y)`
pub fn eval_deriv1(&mut self, length: f64) -> Result<(f64, f64, f64, f64), Error> {
self.path.suite.path_eval_seg_length_deriv1(self.path.effect_ref, self.path.path, &mut self.length_prep, self.which_seg, length)
}
}

impl<'a> Drop for PathSegPrep<'a> {
fn drop(&mut self) {
self.path
.suite
.path_cleanup_seg_length(self.path.effect_ref, self.path.path, self.which_seg, &mut self.length_prep)
.expect("Failed to clean up PF_PathSegPrepPtr");
}
}

0 comments on commit 9cf784e

Please sign in to comment.