Skip to content

Commit ded3938

Browse files
Ink Open Sourcecopybara-github
authored andcommitted
Add per-stroke random seed values for use with BrushBehavior::NoiseNode
This CL allows supplying `InProgressStroke` with a `stroke_seed` value that is used to help seed the random number generators for `NoiseNode`s in the brush, if any. These means that two strokes with the same randomized brush will (in general) use different random values. To prevent existing strokes from changing when serialized or regenerated, we must also persist that each dry stroke's seed somehow. The most expedient places to keep it are within the `Brush` or in the `StrokeInputBatch` (since our API is built to assume that these two things combined are sufficient to regenerate a stroke). This CL opts to store this value in the `StrokeInputBatch`, since that's the component that already varies from stroke to stroke. PiperOrigin-RevId: 718365728
1 parent d0de0c3 commit ded3938

17 files changed

+95
-24
lines changed

ink/storage/proto/coded.proto

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ message CodedStrokeInputBatch {
110110
// A missing or zero value indicates that the relationship between stroke
111111
// space and physical space is unknown or ill-defined for this input batch.
112112
optional float stroke_unit_length_in_centimeters = 8;
113+
114+
// The seed value that should be used for seeding any noise generators for
115+
// brush behaviors when a full stroke is regenerated with this input batch.
116+
optional fixed32 noise_seed = 9;
113117
}
114118

115119
// A 2D mesh; compressed lossily using delta coding.

ink/storage/stroke_input_batch.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ void EncodeStrokeInputBatch(const StrokeInputBatch& input_batch,
6767
CodedStrokeInputBatch& input_proto) {
6868
if (input_batch.Size() == 0) {
6969
input_proto.Clear();
70+
input_proto.set_noise_seed(input_batch.GetNoiseSeed());
7071
return;
7172
}
7273
// Determine the envelope for the input positions and the maximum input time
@@ -236,6 +237,7 @@ void EncodeStrokeInputBatch(const StrokeInputBatch& input_batch,
236237
input_proto.set_stroke_unit_length_in_centimeters(
237238
stroke_unit_length->ToCentimeters());
238239
}
240+
input_proto.set_noise_seed(input_batch.GetNoiseSeed());
239241
}
240242

241243
namespace {
@@ -296,6 +298,7 @@ absl::StatusOr<StrokeInputBatch> DecodeStrokeInputBatch(
296298
return status;
297299
}
298300
}
301+
batch.SetNoiseSeed(input_proto.noise_seed());
299302
return batch;
300303
}
301304

ink/storage/stroke_input_batch_test.cc

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,15 @@ TEST_F(StrokeInputBatchTest, EncodeEmptyInputs) {
219219
EXPECT_FALSE(input_proto.has_x_stroke_space());
220220
}
221221

222+
TEST_F(StrokeInputBatchTest, EncodeEmptyInputsWithNoiseSeed) {
223+
CodedStrokeInputBatch input_proto;
224+
StrokeInputBatch empty_input_batch;
225+
empty_input_batch.SetNoiseSeed(12345);
226+
EncodeStrokeInputBatch(empty_input_batch, input_proto);
227+
EXPECT_FALSE(input_proto.has_x_stroke_space());
228+
EXPECT_EQ(input_proto.noise_seed(), 12345);
229+
}
230+
222231
TEST_F(StrokeInputBatchTest, EncodeSingleInputPosition) {
223232
// Create a stroke with a single input point.
224233
absl::StatusOr<StrokeInputBatch> single_input_batch =
@@ -382,7 +391,10 @@ void StrokeInputBatchRoundTrip(const StrokeInputBatch& inputs) {
382391
absl::StatusOr<StrokeInputBatch> decoded = DecodeStrokeInputBatch(proto);
383392
ASSERT_EQ(decoded.status(), absl::OkStatus());
384393
// Because of quantization, the decoded batch is not guaranteed to exactly
385-
// match the original.
394+
// match the original. However, some data should match exactly:
395+
EXPECT_EQ(decoded->GetToolType(), inputs.GetToolType());
396+
EXPECT_EQ(decoded->GetStrokeUnitLength(), inputs.GetStrokeUnitLength());
397+
EXPECT_EQ(decoded->GetNoiseSeed(), inputs.GetNoiseSeed());
386398
}
387399
FUZZ_TEST(StrokeInputBatchFuzzTest, StrokeInputBatchRoundTrip)
388400
// TODO: b/349965543 - Currently, extreme input position values sometimes

ink/strokes/in_progress_stroke.cc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,10 @@ void InProgressStroke::Clear() {
5656
inputs_are_finished_ = true;
5757
}
5858

59-
void InProgressStroke::Start(const Brush& brush) {
59+
void InProgressStroke::Start(const Brush& brush, uint32_t noise_seed) {
6060
Clear();
6161
brush_ = brush;
62+
processed_inputs_.SetNoiseSeed(noise_seed);
6263
inputs_are_finished_ = false;
6364

6465
absl::Span<const BrushCoat> coats = brush_->GetCoats();
@@ -72,7 +73,7 @@ void InProgressStroke::Start(const Brush& brush) {
7273
for (uint32_t i = 0; i < num_coats; ++i) {
7374
shape_builders_[i].StartStroke(brush_->GetFamily().GetInputModel(),
7475
coats[i], brush_->GetSize(),
75-
brush_->GetEpsilon());
76+
brush_->GetEpsilon(), noise_seed);
7677
}
7778
}
7879

ink/strokes/in_progress_stroke.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ class InProgressStroke {
9696
// This includes clearing or resetting any existing inputs, mesh data, and
9797
// updated region. This method must be called at least once after construction
9898
// before starting to call `EnqueueInputs()` or `UpdateShape()`.
99-
void Start(const Brush& brush);
99+
void Start(const Brush& brush, uint32_t noise_seed = 0);
100100

101101
// Enqueues the incremental `real_inputs` and sets the prediction to
102102
// `predicted_inputs`, overwriting any previous prediction. Queued inputs will

ink/strokes/input/fuzz_domains.cc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include <algorithm>
1818
#include <cstddef>
19+
#include <cstdint>
1920
#include <optional>
2021
#include <utility>
2122
#include <vector>
@@ -82,7 +83,7 @@ fuzztest::Domain<StrokeInputBatch> StrokeInputBatchWithPositionsAndMinSize(
8283
[](const std::vector<std::pair<Point, Duration32>>& xyts) {
8384
return fuzztest::Map(
8485
[xyts](StrokeInput::ToolType tool_type,
85-
PhysicalDistance stroke_unit_length,
86+
PhysicalDistance stroke_unit_length, uint32_t noise_seed,
8687
const std::optional<std::vector<float>>& pressures,
8788
const std::optional<std::vector<Angle>>& tilts,
8889
const std::optional<std::vector<Angle>>& orientations) {
@@ -104,11 +105,12 @@ fuzztest::Domain<StrokeInputBatch> StrokeInputBatchWithPositionsAndMinSize(
104105
: StrokeInput::kNoOrientation,
105106
});
106107
}
107-
return StrokeInputBatch::Create(inputs).value();
108+
return StrokeInputBatch::Create(inputs, noise_seed).value();
108109
},
109110
ArbitraryToolType(),
110111
fuzztest::OneOf(fuzztest::Just(StrokeInput::kNoStrokeUnitLength),
111112
ValidStrokeUnitLength()),
113+
fuzztest::Arbitrary<uint32_t>(),
112114
fuzztest::OptionalOf(
113115
fuzztest::VectorOf(ValidPressure()).WithSize(xyts.size())),
114116
fuzztest::OptionalOf(

ink/strokes/input/stroke_input_batch.cc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717
#include <algorithm>
1818
#include <cmath>
1919
#include <cstddef>
20+
#include <cstdint>
2021
#include <string>
2122
#include <vector>
2223

2324
#include "absl/log/absl_check.h"
2425
#include "absl/status/status.h"
26+
#include "absl/status/statusor.h"
2527
#include "absl/strings/str_cat.h"
2628
#include "absl/strings/str_join.h"
2729
#include "absl/strings/substitute.h"
@@ -71,6 +73,7 @@ void StrokeInputBatch::Clear() {
7173
size_ = 0;
7274
tool_type_ = StrokeInput::ToolType::kUnknown;
7375
stroke_unit_length_ = StrokeInput::kNoStrokeUnitLength;
76+
noise_seed_ = 0;
7477
has_pressure_ = false;
7578
has_tilt_ = false;
7679
has_orientation_ = false;
@@ -312,7 +315,7 @@ absl::Status StrokeInputBatch::Append(absl::Span<const StrokeInput> inputs) {
312315
}
313316

314317
absl::StatusOr<StrokeInputBatch> StrokeInputBatch::Create(
315-
absl::Span<const StrokeInput> inputs) {
318+
absl::Span<const StrokeInput> inputs, uint32_t noise_seed) {
316319
StrokeInputBatch batch;
317320

318321
if (!inputs.empty()) {
@@ -323,6 +326,7 @@ absl::StatusOr<StrokeInputBatch> StrokeInputBatch::Create(
323326
}
324327
}
325328

329+
batch.SetNoiseSeed(noise_seed);
326330
return batch;
327331
}
328332

ink/strokes/input/stroke_input_batch.h

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#define INK_STROKES_INPUT_STROKE_INPUT_BATCH_H_
1717

1818
#include <cstddef>
19+
#include <cstdint>
1920
#include <iterator>
2021
#include <limits>
2122
#include <optional>
@@ -73,7 +74,7 @@ class StrokeInputBatch {
7374

7475
// Performs validation on `inputs` and returns the resulting batch or error.
7576
static absl::StatusOr<StrokeInputBatch> Create(
76-
absl::Span<const StrokeInput> inputs);
77+
absl::Span<const StrokeInput> inputs, uint32_t noise_seed = 0);
7778

7879
StrokeInputBatch() = default;
7980
StrokeInputBatch(const StrokeInputBatch&) = default;
@@ -111,7 +112,9 @@ class StrokeInputBatch {
111112
// Returns an error and does not modify the batch if validation fails.
112113
absl::Status Append(const StrokeInput& input);
113114

114-
// Validates and appends a sequence of `inputs`.
115+
// Validates and appends a sequence of `inputs`. This batch's per-stroke seed
116+
// value is left unchanged, even when appending another batch with a different
117+
// seed value.
115118
//
116119
// Returns an error and does not modify the batch if validation fails.
117120
absl::Status Append(absl::Span<const StrokeInput> inputs);
@@ -147,6 +150,16 @@ class StrokeInputBatch {
147150
bool HasTilt() const;
148151
bool HasOrientation() const;
149152

153+
// Returns the seed value that should be used for seeding any noise generators
154+
// for brush behaviors when a full stroke is regenerated with this input
155+
// batch. If no seed value has yet been set for this input batch, returns the
156+
// default seed of zero.
157+
uint32_t GetNoiseSeed() const;
158+
159+
// Sets the per-stroke seed value that should be used when regenerating a
160+
// stroke from this input batch.
161+
void SetNoiseSeed(uint32_t seed);
162+
150163
// Which properties of the stroke should be preserved over transforms.
151164
enum class TransformInvariant {
152165
kPreserveDuration = 0,
@@ -222,6 +235,7 @@ class StrokeInputBatch {
222235
size_t size_ = 0;
223236
StrokeInput::ToolType tool_type_ = StrokeInput::ToolType::kUnknown;
224237
PhysicalDistance stroke_unit_length_ = StrokeInput::kNoStrokeUnitLength;
238+
uint32_t noise_seed_ = 0;
225239
bool has_pressure_ = false;
226240
bool has_tilt_ = false;
227241
bool has_orientation_ = false;
@@ -308,6 +322,12 @@ inline std::optional<PhysicalDistance> StrokeInputBatch::GetStrokeUnitLength()
308322
return stroke_unit_length_;
309323
}
310324

325+
inline uint32_t StrokeInputBatch::GetNoiseSeed() const { return noise_seed_; }
326+
327+
inline void StrokeInputBatch::SetNoiseSeed(uint32_t seed) {
328+
noise_seed_ = seed;
329+
}
330+
311331
inline bool StrokeInputBatch::HasStrokeUnitLength() const {
312332
return stroke_unit_length_ != StrokeInput::kNoStrokeUnitLength;
313333
}

ink/strokes/input/stroke_input_batch_test.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ TEST(StrokeInputBatchTest, SetReplacingLastValueOfMany) {
323323
TEST(StrokeInputBatchTest, Clear) {
324324
std::vector<StrokeInput> input_vector = MakeValidTestInputSequence();
325325
absl::StatusOr<StrokeInputBatch> batch =
326-
StrokeInputBatch::Create(input_vector);
326+
StrokeInputBatch::Create(input_vector, /*noise_seed=*/12345);
327327
ASSERT_EQ(batch.status(), absl::OkStatus());
328328

329329
ASSERT_FALSE(batch->IsEmpty());
@@ -332,6 +332,7 @@ TEST(StrokeInputBatchTest, Clear) {
332332
ASSERT_TRUE(batch->HasTilt());
333333
ASSERT_TRUE(batch->HasOrientation());
334334
ASSERT_EQ(batch->GetToolType(), StrokeInput::ToolType::kStylus);
335+
EXPECT_EQ(batch->GetNoiseSeed(), 12345u);
335336

336337
batch->Clear();
337338
// Batch should now be empty and the tool type should be unknown.
@@ -341,6 +342,7 @@ TEST(StrokeInputBatchTest, Clear) {
341342
ASSERT_FALSE(batch->HasTilt());
342343
ASSERT_FALSE(batch->HasOrientation());
343344
EXPECT_EQ(batch->GetToolType(), StrokeInput::ToolType::kUnknown);
345+
EXPECT_EQ(batch->GetNoiseSeed(), 0u);
344346
}
345347

346348
TEST(StrokeInputBatchTest, AppendAfterClear) {

ink/strokes/input/type_matchers.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ MATCHER_P(StrokeInputBatchEqMatcher, expected, "") {
8080
}
8181
}
8282

83-
return true;
83+
return ExplainMatchResult(Eq(expected.GetNoiseSeed()), arg.GetNoiseSeed(),
84+
result_listener);
8485
}
8586

8687
} // namespace

0 commit comments

Comments
 (0)