Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ink/brush/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
51 changes: 22 additions & 29 deletions ink/brush/brush_family.cc
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@

namespace ink {

using ::ink::brush_internal::CalculatePaintAnimationLoopDuration;

BrushFamily::InputModel BrushFamily::DefaultInputModel() {
return SlidingWindowModel{};
}
Expand Down Expand Up @@ -92,35 +94,26 @@ absl::StatusOr<BrushFamily> 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<BrushPaint::StampingTexture>(&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");
}
}
}
Expand Down
23 changes: 23 additions & 0 deletions ink/brush/brush_paint.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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<BrushPaint::StampingTexture>(&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) {
Expand Down
13 changes: 13 additions & 0 deletions ink/brush/brush_paint.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 5 additions & 0 deletions ink/brush/internal/jni/brush_paint_jni.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
8 changes: 8 additions & 0 deletions ink/brush/internal/jni/brush_paint_native.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -147,6 +148,13 @@ int BrushPaintNative_getSelfOverlapInt(int64_t native_ptr) {
return static_cast<int>(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<MeshFormat::AttributeId> required_attribute_ids;
Expand Down
3 changes: 3 additions & 0 deletions ink/brush/internal/jni/brush_paint_native.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Loading