diff --git a/ink/brush/BUILD.bazel b/ink/brush/BUILD.bazel index c953cc94..d5ecb89d 100644 --- a/ink/brush/BUILD.bazel +++ b/ink/brush/BUILD.bazel @@ -314,6 +314,7 @@ cc_library( "//ink/geometry:vec", "@abseil-cpp//absl/container:flat_hash_set", "@abseil-cpp//absl/functional:overload", + "@abseil-cpp//absl/log:absl_check", "@abseil-cpp//absl/status", "@abseil-cpp//absl/status:status_macros", "@abseil-cpp//absl/strings", diff --git a/ink/brush/brush_family.cc b/ink/brush/brush_family.cc index a94d0598..3e6d241b 100644 --- a/ink/brush/brush_family.cc +++ b/ink/brush/brush_family.cc @@ -37,6 +37,8 @@ namespace ink { +using ::ink::brush_internal::CalculatePaintAnimationLoopDuration; + BrushFamily::InputModel BrushFamily::DefaultInputModel() { return SlidingWindowModel{}; } @@ -92,35 +94,26 @@ absl::StatusOr BrushFamily::Create( // probably either have the same animation duration, or be a non-animated // fallback. for (const BrushPaint& paint : coat.paint_preferences) { - for (const BrushPaint::TextureLayer& texture_layer : - paint.texture_layers) { - if (const auto* stamping_texture = - std::get_if(&texture_layer)) { - if (stamping_texture->animation_duration == absl::ZeroDuration() || - stamping_texture->animation_frames == 1) { - continue; // This texture is not animated. - } - // Because we've already validated each `BrushCoat`, we know that each - // texture's `animation_duration` is a whole number of milliseconds, - // so `ToInt64Milliseconds` isn't losing any precision here. - int64_t texture_duration_ms = - absl::ToInt64Milliseconds(stamping_texture->animation_duration); - if (full_duration_ms == 0) { - full_duration_ms = texture_duration_ms; - continue; - } - // Because we've already validated each `BrushCoat`, we know that - // `texture_duration_ms` is at most (1 << 24), and similarly we know - // that `full_duration_ms` is at most (1 << 24) so far, so their - // product is at most (1 << 48), and therefore this 64-bit `std::lcm` - // call can't overflow. - full_duration_ms = std::lcm(full_duration_ms, texture_duration_ms); - if (full_duration_ms > (1 << 24)) { - return absl::InvalidArgumentError( - "The LCM of all texture animation durations in a `BrushFamily` " - "must be no more than 2^24 milliseconds"); - } - } + absl::Duration paint_duration = + CalculatePaintAnimationLoopDuration(paint); + // Because we've already validated each `BrushCoat`, we know that each + // paint's animation duration is a whole number of milliseconds, so + // `ToInt64Milliseconds` isn't losing any precision here. + int64_t paint_duration_ms = absl::ToInt64Milliseconds(paint_duration); + if (full_duration_ms == 0) { + full_duration_ms = paint_duration_ms; + continue; + } + // Because we've already validated each `BrushCoat`, we know that + // `paint_duration_ms` is at most (1 << 24), and we also know that + // `full_duration_ms` is at most (1 << 24) so far, so their product is + // at most (1 << 48), and therefore this 64-bit `std::lcm` call can't + // overflow. + full_duration_ms = std::lcm(full_duration_ms, paint_duration_ms); + if (full_duration_ms > (1 << 24)) { + return absl::InvalidArgumentError( + "The LCM of all texture animation durations in a `BrushFamily` " + "must be no more than 2^24 milliseconds"); } } } diff --git a/ink/brush/brush_paint.cc b/ink/brush/brush_paint.cc index 21ef780d..09f2b677 100644 --- a/ink/brush/brush_paint.cc +++ b/ink/brush/brush_paint.cc @@ -22,6 +22,7 @@ #include "absl/container/flat_hash_set.h" #include "absl/functional/overload.h" +#include "absl/log/absl_check.h" #include "absl/status/status.h" #include "absl/status/status_macros.h" #include "absl/strings/str_cat.h" @@ -328,6 +329,28 @@ absl::Status ValidateBrushPaint(const BrushPaint& paint) { return absl::OkStatus(); } +absl::Duration CalculatePaintAnimationLoopDuration(const BrushPaint& paint) { + ABSL_DCHECK_OK(ValidateBrushPaint(paint)); + // For now, all texture layers in a valid `BrushPaint` are required to be of + // the same type, and are required to all have the same animation duration. + // Therefore, we can just return the animation duration of the first texture + // layer, if any. + return paint.texture_layers.empty() + ? absl::ZeroDuration() + : CalculatePaintAnimationLoopDuration(paint.texture_layers[0]); +} + +absl::Duration CalculatePaintAnimationLoopDuration( + const BrushPaint::TextureLayer& texture_layer) { + ABSL_DCHECK_OK(ValidateBrushPaintTextureLayer(texture_layer)); + if (const auto* stamping_texture = + std::get_if(&texture_layer); + stamping_texture != nullptr && stamping_texture->animation_frames > 1) { + return stamping_texture->animation_duration; + } + return absl::ZeroDuration(); +} + Version CalculateMinimumRequiredVersion( BrushPaint::TextureOrigin texture_origin) { switch (texture_origin) { diff --git a/ink/brush/brush_paint.h b/ink/brush/brush_paint.h index ab735e16..a25c1946 100644 --- a/ink/brush/brush_paint.h +++ b/ink/brush/brush_paint.h @@ -345,6 +345,19 @@ absl::Status ValidateBrushPaintTopLevel(const BrushPaint& paint); absl::Status ValidateBrushPaintTextureLayer( const BrushPaint::TextureLayer& layer); +// Returns the duration of a complete paint animation loop for this brush paint +// (such that the paint goes through one complete loop and returns to its +// starting point), or zero if this brush paint is not animated. The +// `BrushPaint` must be valid. +absl::Duration CalculatePaintAnimationLoopDuration(const BrushPaint& paint); + +// Returns the duration of a complete paint animation loop for this texture +// layer (such that it goes through one complete loop and returns to its +// starting point), or zero if this texture layer is not animated. The +// `TextureLayer` must be valid. +absl::Duration CalculatePaintAnimationLoopDuration( + const BrushPaint::TextureLayer& texture_layer); + // Calculates the minimum version of the Ink library that is required to use // this brush paint. Version CalculateMinimumRequiredVersion(const BrushPaint& paint); diff --git a/ink/brush/internal/jni/brush_paint_jni.cc b/ink/brush/internal/jni/brush_paint_jni.cc index 9040f172..20b46fed 100644 --- a/ink/brush/internal/jni/brush_paint_jni.cc +++ b/ink/brush/internal/jni/brush_paint_jni.cc @@ -103,6 +103,11 @@ JNI_METHOD(brush, BrushPaintNative, jint, getSelfOverlapInt) return BrushPaintNative_getSelfOverlapInt(native_pointer); } +JNI_METHOD(brush, BrushPaintNative, jlong, getPaintAnimationLoopDurationMillis) +(JNIEnv* env, jobject object, jlong native_pointer) { + return BrushPaintNative_getPaintAnimationLoopDurationMillis(native_pointer); +} + JNI_METHOD(brush, BrushPaintNative, jboolean, isCompatibleWithMeshFormat) (JNIEnv* env, jobject obj, jlong native_pointer, jlong mesh_format_native_pointer) { diff --git a/ink/brush/internal/jni/brush_paint_native.cc b/ink/brush/internal/jni/brush_paint_native.cc index 6a85368c..439418ca 100644 --- a/ink/brush/internal/jni/brush_paint_native.cc +++ b/ink/brush/internal/jni/brush_paint_native.cc @@ -45,6 +45,7 @@ using ::ink::MeshFormat; using ::ink::Vec; using ::ink::brush_internal::AddAttributeIdsRequiredByPaint; using ::ink::brush_internal::CalculateMinimumRequiredVersion; +using ::ink::brush_internal::CalculatePaintAnimationLoopDuration; using ::ink::brush_internal::ValidateBrushPaint; using ::ink::brush_internal::ValidateBrushPaintTextureLayer; using ::ink::native::CastToBrushPaint; @@ -147,6 +148,13 @@ int BrushPaintNative_getSelfOverlapInt(int64_t native_ptr) { return static_cast(CastToBrushPaint(native_ptr).self_overlap); } +int64_t BrushPaintNative_getPaintAnimationLoopDurationMillis( + int64_t native_pointer) { + const BrushPaint& brush_paint = CastToBrushPaint(native_pointer); + return absl::ToInt64Milliseconds( + CalculatePaintAnimationLoopDuration(brush_paint)); +} + bool BrushPaintNative_isCompatibleWithMeshFormat( int64_t native_ptr, int64_t mesh_format_native_ptr) { absl::flat_hash_set required_attribute_ids; diff --git a/ink/brush/internal/jni/brush_paint_native.h b/ink/brush/internal/jni/brush_paint_native.h index 86a81c3d..5a6d7373 100644 --- a/ink/brush/internal/jni/brush_paint_native.h +++ b/ink/brush/internal/jni/brush_paint_native.h @@ -46,6 +46,9 @@ int64_t BrushPaintNative_newCopyOfColorFunction(int64_t native_ptr, int index); int BrushPaintNative_getSelfOverlapInt(int64_t native_ptr); +int64_t BrushPaintNative_getPaintAnimationLoopDurationMillis( + int64_t native_pointer); + bool BrushPaintNative_isCompatibleWithMeshFormat( int64_t native_ptr, int64_t mesh_format_native_ptr);