// Copyright 2024-2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Proto definitions for Brush and related messages for use in Ink Strokes API.

syntax = "proto2";

package ink.proto;

import "ink/storage/proto/color.proto";
import "ink/storage/proto/options.proto";

// Specifies a list of `BrushCoat`s that determine the stroke shape and dynamic
// input response and the shading of the stroke geometry. The `BrushFamily` can
// optionally be identified by setting a non-empty `client_brush_family_id`.
message BrushFamily {
  option (ink.proto.message_min_version) = 0;

  // A naive input model that passes raw inputs through unchanged, performing
  // only base-minimal modeling to derive velocity and acceleration values for
  // the modeled inputs. This can be useful as a point of comparison for other
  // input models, or for callers who wish to do their own input modeling prior
  // to passing inputs into Ink.
  message PassthroughModel {
    option (ink.proto.message_min_version) = 0;
  }

  // Averages nearby inputs together within a sliding time window.
  message SlidingWindowModel {
    option (ink.proto.message_min_version) = 0;

    // The duration over which to average together nearby raw inputs. Typically
    // this should be somewhere in the 1 ms to 100 ms range. The default value
    // is 20 ms.
    optional float window_size_seconds = 1
        [default = 0.02, (ink.proto.field_min_version) = 0];
    // The maximum duration between modeled inputs; if raw inputs are spaced
    // more than this far apart in time, then additional modeled inputs will be
    // inserted between them. Set this to infinity to disable upsampling.  The
    // default value is 1/180 seconds.
    //
    // This is an experimental field which may be removed later.
    optional float experimental_upsampling_period_seconds = 2
        [default = 0.00555555555, (ink.proto.field_min_version) = 0];
  }

  message InputModel {
    option (ink.proto.message_min_version) = 0;

    oneof input_model {
      PassthroughModel passthrough_model = 4
          [(ink.proto.field_min_version) = 0];
      SlidingWindowModel sliding_window_model = 5
          [(ink.proto.field_min_version) = 0];
    }
    // Removed InputModel types go here (reserved needs to be outside oneof).
    reserved 1;
    reserved 2;
    reserved 3;
  }

  // The coats of paint applied by any brush of this brush family. When a stroke
  // drawn by a multi-coat brush is rendered, each coat of paint will be drawn
  // entirely atop the previous coat, even if the stroke crosses over itself, as
  // though each coat were painted in its entirety one at a time.
  repeated BrushCoat coats = 4 [(ink.proto.field_min_version) = 0];

  // Was `tip` and `paint`, use `coats[0].tip` and `coats[0].paint`
  // instead.
  reserved 1 to 2;

  // The ID for this brush family specified by the client that created it. The
  // meaning of this ID is determined by that client.
  optional string client_brush_family_id = 3
      [(ink.proto.field_min_version) = 0];

  // Specifies a model for turning a sequence of raw hardware inputs (e.g. from
  // a stylus, touchscreen, or mouse) into a sequence of smoothed, modeled
  // inputs. Raw hardware inputs tend to be noisy, and must be smoothed before
  // being passed into a brush's behaviors and extruded into a mesh in order to
  // get a good-looking stroke.
  optional InputModel input_model = 5 [(ink.proto.field_min_version) = 0];

  // A mapping of texture IDs (as used in `BrushPaint.TextureLayer`) to bitmaps
  // in PNG format.
  map<string, bytes> texture_id_to_bitmap = 6
      [(ink.proto.field_min_version) = 0];

  // A multi-line, human-readable string with a description of the brush and how
  // it works, with the intended audience being designers/developers who are
  // editing the brush definition. This string is not generally intended to be
  // displayed to end users.
  optional string developer_comment = 7 [(ink.proto.field_min_version) = 0];

  // The minimum version of the Ink API that this brush family is compatible
  // with. This field should not be set by clients, only by the Ink serializer,
  // as it is computed based on the features used in the brush family.
  optional int32 min_version = 8 [(ink.proto.field_min_version) = 0];

  // Specifies a list of brush families that are similar in behavior to this
  // brush family, but with a higher `min_version`. This is intended to provide
  // a fallback for sharing brushes between devices with different Ink API
  // versions. The serializer will only populate this field on the lowest
  // version, "top-level" brush family passed to `EncodeMultipleBrushFamilies`.
  // The deserializer will only decode this field from the top-level brush
  // family as well. Though a deeper recursive structure is possible, it is not
  // supported, to avoid hitting parser recursion depth limits (100 for C++ by
  // default) which are more likely to be hit than repeated field limits (only
  // restricted by the overall message size limit of 2GiB).
  repeated BrushFamily newer_brush_families = 9
      [(ink.proto.field_min_version) = 0];
}

// A `BrushCoat` represents one coat of ink applied by a brush. It includes a
// `BrushTip` that describes the structure of that coat, and a non-empty list of
// possible `BrushPaint` objects - each one describes how to render the coat
// structure, and the one `BrushPaint` that is actually used is the first one in
// the list that is compatible with the device and renderer. Multiple
// `BrushCoat`s can be combined within a single brush; when a stroke drawn by a
// multi-coat brush is rendered, each coat of ink will be drawn entirely atop
// the previous coat, even if the stroke crosses over itself, as though each
// coat were painted in its entirety one at a time.
message BrushCoat {
  option (ink.proto.message_min_version) = 0;

  optional BrushTip tip = 1 [(ink.proto.field_min_version) = 0];

  // Was `paint`; use `paint_preferences` instead.
  reserved 2;

  // If empty, the default `BrushPaint` will be used.
  repeated BrushPaint paint_preferences = 3 [(ink.proto.field_min_version) = 0];
}

// Parameters that control how stroke inputs are used to model the tip shape and
// color, and to create vertices for the stroke mesh. The specification can be
// considered in two parts:
//   1. Parameters for the baseline shape of the tip as a function of `Brush`
//      size, which can be modified dynamically by `BrushBehavior`s.
//   2. An array of `BrushBehavior`s that allow dynamic properties of each input
//      to modify the tip shape and color.
//
// Depending on the combination of values, the tip can be shaped as a rounded
// parallelogram, circle, or stadium. Through `BrushBehavior`s, the tip can
// produce a per-vertex HSLA color shift that can be used to modify the `Brush`
// color when drawing. The default values produce a static circular tip shape
// with diameter equal to the `Brush` size and no color shift.
message BrushTip {
  option (ink.proto.message_min_version) = 0;

  // Scale used to calculate the baseline width of the tip shape relative to the
  // brush size prior to applying `slant` and `rotation`.  The baseline width of
  // the tip will be equal to the brush size multiplied by `scale_x`. Valid
  // values must be finite and non-negative, with at least one of `scale_x` and
  // `scale_y` greater than zero.
  optional float scale_x = 1 [default = 1.0, (ink.proto.field_min_version) = 0];
  // Same as `scale_x`, but for baseline height.
  optional float scale_y = 2 [default = 1.0, (ink.proto.field_min_version) = 0];

  // A normalized value in the range [0, 1] that is used to calculate the
  // baseline radius of curvature for the tip's corners. A value of 0 results in
  // sharp corners and a value of 1 results in the maximum radius of curvature
  // given the current tip dimensions.
  optional float corner_rounding = 3
      [default = 1.0, (ink.proto.field_min_version) = 0];

  // Angle used to calculate the baseline slant of the tip shape prior to
  // applying `rotation` and `pinch`.
  //
  // This property is similar to the single-arg CSS skew() transformation.
  // Unlike skew, slant tries to preserve the perimeter of the tip shape as
  // opposed to its area. This is akin to "pressing" a rectangle into a
  // parallelogram with non-right angles while preserving the side lengths.
  //
  // The value should be in the range [-π/2, π/2] radians, and represents the
  // angle by which "vertical" lines of the tip shape will appear rotated about
  // their intersection with the x-axis.
  optional float slant_radians = 4 [(ink.proto.field_min_version) = 0];

  // A unitless parameter in the range [0, 1] that controls the baseline
  // separation between two of the shape's corners prior to applying `rotation`.
  //
  // The two corners affected lie toward the negative y-axis relative to the
  // center of the tip shape. I.e. the "upper edge" of the shape if positive y
  // is chosen to point "down" in stroke coordinates.
  //
  // If `scale_x` is not 0, different values of `pinch` produce the following
  // shapes:
  //  - A value of 0 will leave the corners unaffected as a rectangle or
  //    parallelogram.
  //  - Values between 0 and 1 will bring the corners closer together to result
  //    in a (possibly slanted) trapezoidal shape.
  //  - A value of 1 will make the two corners coincide and result in a
  //    triangular shape.
  optional float pinch = 5 [(ink.proto.field_min_version) = 0];

  // Angle specifying the baseline rotation of the tip shape after applying
  // `scale`, `pinch`, and `slant`.
  optional float rotation_radians = 6 [(ink.proto.field_min_version) = 0];

  // Was `opacity_multiplier`; use brush paint color functions instead.
  reserved 8;

  // Controls emission of particles as a function of distance traveled by the
  // stroke inputs. The value must be finite and non-negative.
  //
  // When this and `particle_gap_duration` are both zero, the stroke will be
  // continuous, unless gaps are introduced dynamically by `BrushBehavior`s.
  // Otherwise, the stroke will be made up of particles. A new particle will be
  // emitted after at least `particle_gap_distance_scale * brush_size` distance
  // has been traveled by the stoke inputs.
  optional float particle_gap_distance_scale = 9
      [(ink.proto.field_min_version) = 0];

  // Parameter controlling emission of particles as a function of time elapsed
  // along the stroke. The value must be finite and non-negative.
  //
  // When this and `particle_gap_distance_scale` are both zero, the stroke will
  // be continuous, unless gaps are introduced dynamically by `BrushBehavior`s.
  // Otherwise, the stroke will be made up of particles. Particles will be
  // emitted at most once every `particle_gap_duration`.
  optional float particle_gap_duration_seconds = 10
      [(ink.proto.field_min_version) = 0];

  // Behaviors affecting this tip.
  repeated BrushBehavior behaviors = 7 [(ink.proto.field_min_version) = 0];
}

// Parameters that describe how a stroke mesh should be rendered.
message BrushPaint {
  option (ink.proto.message_min_version) = 0;

  message TextureLayer {
    option (ink.proto.message_min_version) = 0;

    // LINT.IfChange(texture_wrap)

    // Texture wrapping modes for specifying `TextureLayer::wrap_x` and
    // `wrap_y`.
    enum Wrap {
      WRAP_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = 0];

      // Repeats texture image horizontally/vertically.
      WRAP_REPEAT = 1 [(ink.proto.enum_value_min_version) = 0];

      // Repeats texture image horizontally/vertically, alternating mirror
      // images so that adjacent edges always match.
      WRAP_MIRROR = 2 [(ink.proto.enum_value_min_version) = 0];

      // Points outside of the texture have the color of the nearest texture
      // edge point. This mode is typically most useful when the edge pixels of
      // the texture image are all the same, e.g. either transparent or a single
      // solid color.
      WRAP_CLAMP = 3 [(ink.proto.enum_value_min_version) = 0];
    }
    // LINT.ThenChange(../../brush/brush_paint.h:texture_wrap)

    // LINT.IfChange(texture_size_unit)

    // Units for specifying `TextureLayer::size`.
    enum SizeUnit {
      SIZE_UNIT_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = 0];

      // In the same units as the provided `StrokeInput` position.
      SIZE_UNIT_STROKE_COORDINATES = 1 [(ink.proto.enum_value_min_version) = 0];

      // As multiples of brush size.
      SIZE_UNIT_BRUSH_SIZE = 2 [(ink.proto.enum_value_min_version) = 0];

      reserved 3;
    }
    // LINT.ThenChange(../../brush/brush_paint.h:texture_size_unit)

    // LINT.IfChange(texture_origin)

    // Specification of the origin point to use for the texture.
    enum Origin {
      ORIGIN_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = 0];

      // The texture origin is the origin of stroke space, however that happens
      // to be defined for a given stroke.
      ORIGIN_STROKE_SPACE_ORIGIN = 1 [(ink.proto.enum_value_min_version) = 0];

      // The texture origin is the first input position for the stroke.
      ORIGIN_FIRST_STROKE_INPUT = 2 [(ink.proto.enum_value_min_version) = 0];

      // The texture origin is the last input position (including predicted
      // inputs) for the stroke. Note that this means that the texture origin
      // for an in-progress stroke will move as more inputs are added.
      ORIGIN_LAST_STROKE_INPUT = 3 [(ink.proto.enum_value_min_version) = 0];
    }
    // LINT.ThenChange(../../brush/brush_paint.h:texture_origin)

    // LINT.IfChange(texture_mapping)

    // Specification of how the texture should be applied to the stroke.
    enum Mapping {
      MAPPING_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = 0];

      // The texture will repeat according to a 2D affine transformation of
      // vertex positions. Each copy of the texture will have the same size and
      // shape modulo reflections.
      MAPPING_TILING = 1 [(ink.proto.enum_value_min_version) = 0];

      // This mode is intended for use with particle brush coats (i.e. with a
      // brush tip with a nonzero particle gap). A copy of the texture (or one
      // animation frame thereof) will be "stamped" onto each particle of the
      // stroke, scaled or rotated appropriately to cover the whole particle.
      MAPPING_STAMPING = 2 [(ink.proto.enum_value_min_version) = 0];
    }
    // LINT.ThenChange(../../brush/brush_paint.h:texture_mapping)

    // LINT.IfChange(blend_mode)

    // Setting for how an incoming ("source" / "src") color should be combined
    // with the already present ("destination" / "dst") color at a given pixel.
    enum BlendMode {
      BLEND_MODE_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = 0];

      // Source and destination are component-wise multiplied, including
      // opacity.
      //
      // Alpha = Alpha_src * Alpha_dst
      //
      // Color = Color_src * Color_dst
      BLEND_MODE_MODULATE = 1 [(ink.proto.enum_value_min_version) = 0];

      // Keeps destination pixels that cover source pixels. Discards remaining
      // source and destination pixels.
      //
      // Alpha = Alpha_src * Alpha_dst
      //
      // Color = Alpha_src * Color_dst
      BLEND_MODE_DST_IN = 2 [(ink.proto.enum_value_min_version) = 0];

      // Keeps the destination pixels not covered by source pixels. Discards
      // destination pixels that are covered by source pixels and all source
      // pixels.
      //
      // Alpha = (1 - Alpha_src) * Alpha_dst
      //
      // Color = (1 - Alpha_src) * Color_dst
      BLEND_MODE_DST_OUT = 3 [(ink.proto.enum_value_min_version) = 0];

      // Discards source pixels that do not cover destination pixels.
      // Draws remaining pixels over destination pixels.
      //
      // Alpha = Alpha_dst
      //
      // Color = Alpha_dst * Color_src + (1 - Alpha_src) * Color_dst
      BLEND_MODE_SRC_ATOP = 4 [(ink.proto.enum_value_min_version) = 0];

      // Keeps the source pixels that cover destination pixels. Discards
      // remaining source and destination pixels.
      //
      // Alpha = Alpha_src * Alpha_dst
      //
      // Color = Color_src * Alpha_dst
      BLEND_MODE_SRC_IN = 5 [(ink.proto.enum_value_min_version) = 0];

      // The source pixels are drawn over the destination pixels.
      //
      // Alpha = Alpha_src + (1 - Alpha_src) * Alpha_dst
      //
      // Color = Color_src + (1 - Alpha_src) * Color_dst
      BLEND_MODE_SRC_OVER = 6 [(ink.proto.enum_value_min_version) = 0];

      // The source pixels are drawn behind the destination pixels.
      //
      // Alpha = Alpha_dst + (1 - Alpha_dst) * Alpha_src
      //
      // Color = Color_dst + (1 - Alpha_dst) * Color_src
      BLEND_MODE_DST_OVER = 7 [(ink.proto.enum_value_min_version) = 0];

      // Keeps the source pixels and discards the destination pixels.
      // When used on the last `TextureLayer`, this effectively causes the
      // texture(s) to ignore the brush's base color, which may sometimes be
      // useful for special effects in brushes with multiple coats of paint.
      //
      // Alpha = Alpha_src
      //
      // Color = Color_src
      BLEND_MODE_SRC = 8 [(ink.proto.enum_value_min_version) = 0];

      // Keeps the destination pixels and discards the source pixels.
      // This mode is unlikely to be useful, since it effectively causes the
      // renderer to just ignore this `TextureLayer` and all layers before it,
      // but it is included for completeness.
      //
      // Alpha = Alpha_dst
      //
      // Color = Color_dst
      BLEND_MODE_DST = 9 [(ink.proto.enum_value_min_version) = 0];

      // Keeps the source pixels that do not cover destination pixels. Discards
      // destination pixels and all source pixels that cover destination pixels.
      //
      // Alpha = (1 - Alpha_dst) * Alpha_src
      //
      // Color = (1 - Alpha_dst) * Color_src
      BLEND_MODE_SRC_OUT = 10 [(ink.proto.enum_value_min_version) = 0];

      // Discards destination pixels that aren't covered by source
      // pixels. Remaining destination pixels are drawn over source pixels.
      //
      // Alpha = Alpha_src
      //
      // Color = Alpha_src * Color_dst + (1 - Alpha_dst) * Color_src
      BLEND_MODE_DST_ATOP = 11 [(ink.proto.enum_value_min_version) = 0];

      // Discards source and destination pixels that intersect; keeps source and
      // destination pixels that do not intersect.
      //
      // Alpha = (1 - Alpha_dst) * Alpha_src + (1 - Alpha_src) * Alpha_dst
      //
      // Color = (1 - Alpha_dst) * Color_src + (1 - Alpha_src) * Color_dst
      BLEND_MODE_XOR = 12 [(ink.proto.enum_value_min_version) = 0];
    }
    // LINT.ThenChange(../../brush/brush_paint.h:blend_mode)

    // String id that will be used by renderers to retrieve the color texture.
    optional string client_texture_id = 1 [(ink.proto.field_min_version) = 0];

    // The x-dimension of size of (one animation frame of) the texture,
    // specified in  `size_unit`s
    optional float size_x = 2 [default = 1, (ink.proto.field_min_version) = 0];

    // The y-dimension of size of (one animation frame of) the texture,
    // specified in  `size_unit`s
    optional float size_y = 3 [default = 1, (ink.proto.field_min_version) = 0];

    // The unit for size_x and size_y.
    optional SizeUnit size_unit = 4 [
      default = SIZE_UNIT_STROKE_COORDINATES,
      (ink.proto.field_min_version) = 0
    ];

    // How the texture should be applied to the stroke.
    optional Mapping mapping = 5
        [default = MAPPING_TILING, (ink.proto.field_min_version) = 0];

    // An x-offset into the texture, specified as fractions of the texture size.
    optional float offset_x = 6 [(ink.proto.field_min_version) = 0];

    // A y-offset into the texture, specified as fractions of the texture size.
    optional float offset_y = 7 [(ink.proto.field_min_version) = 0];

    // Angle in radians specifying the rotation of the texture. The rotation is
    // carried out about the center of the texture's first repetition along both
    // axes.
    optional float rotation_in_radians = 8 [(ink.proto.field_min_version) = 0];

    reserved 9 to 15;

    // The rule by which the texture layers up to and including this one are
    // combined with the subsequent layer.
    // I.e. `BrushPaint::texture_layers[index].blend_mode` will be used to
    // combine "src", which is the result of blending layers [0..index], with
    // "dst", which is the layer at index + 1. If index refers to the last
    // texture layer, then the layer at "index + 1" is the brush color layer.
    optional BlendMode blend_mode = 16
        [default = BLEND_MODE_MODULATE, (ink.proto.field_min_version) = 0];

    // The origin point to use for the texture
    optional Origin origin = 17 [
      default = ORIGIN_STROKE_SPACE_ORIGIN,
      (ink.proto.field_min_version) = 0
    ];

    // How points outside the texture in the x direction are treated for this
    // texture layer
    optional Wrap wrap_x = 18
        [default = WRAP_REPEAT, (ink.proto.field_min_version) = 0];

    // How points outside the texture in the y direction are treated for this
    // texture layer
    optional Wrap wrap_y = 19
        [default = WRAP_REPEAT, (ink.proto.field_min_version) = 0];

    reserved 20;
    reserved 21;
    reserved 22;
    reserved 23;
  }

  // Specifies how parts of the stroke that intersect itself should be treated
  // during the rendering process. The simplest example of this is with
  // translucent, solid-color strokes - such as a highlighter - where a later
  // part of a stroke that overlaps an earlier part of itself may appear with
  // either double the opacity (self overlap is accumulated) or the same opacity
  // (self overlap is discarded). More complex examples may involve color or
  // opacity variations (e.g. with
  // `BrushBehavior::Target::HUE_OFFSET_IN_RADIANS`), or complex textures (e.g.
  // with `TextureMapping::kStamping`).
  // LINT.IfChange(self_overlap)
  enum SelfOverlap {
    SELF_OVERLAP_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = 0];
    SELF_OVERLAP_ANY = 1 [(ink.proto.enum_value_min_version) = 0];
    SELF_OVERLAP_ACCUMULATE = 2 [(ink.proto.enum_value_min_version) = 0];
    SELF_OVERLAP_DISCARD = 3 [(ink.proto.enum_value_min_version) = 0];
  }
  // LINT.ThenChange(../../brush/brush_paint.h:self_overlap)

  // Zero or more textures to blend together to affect this coat's appearance.
  // Each layer is blended into the next one, and finally into the color of the
  // paint, according to each layer's `blend_mode`.
  repeated TextureLayer texture_layers = 1 [(ink.proto.field_min_version) = 0];

  // Transforms the brush color to be used as an alternative base color for any
  // effects or textures in a `BrushCoat`. Each function is applied in the order
  // they are specified.
  //
  // If this list is empty, the brush color will be used unchanged.
  repeated ColorFunction color_functions = 2
      [(ink.proto.field_min_version) = 0];

  // How parts of the stroke that intersect itself should be treated during the
  // rendering process. See `SelfOverlap` for more details.
  optional SelfOverlap self_overlap = 3
      [default = SELF_OVERLAP_ANY, (ink.proto.field_min_version) = 0];
}

// A behavior describing how stroke input properties should affect the shape and
// color of the brush tip.
//
// The behavior is conceptually a graph made from the various node types defined
// below. Each edge of the graph represents passing a nullable floating point
// value between nodes, and each node in the graph fits into one of the
// following categories:
//   1. Value node: produces a single output value. There are three sub-types of
//   value nodes:
//     * Start node: generates an output value without graph inputs. For
//     example, it can create a value from properties of stroke
//     input (a SourceNode), or emit a constant value (a ConstantNode).
//     * Filter node: takes in an input value and can conditionally toggle
//     branches of the graph "on" (by outputting their input value) or "off" (by
//     outputting a null value).
//     * Operator node: takes in one or more input values and generates an
//     output. For example, by mapping input to output with an easing function.
//   2. Terminal node: applies one or more input values to chosen properties of
//   the brush tip. For example, a TargetNode is a terminal node that modifies
//   a scalar brush tip property like a brush width multiplier.
//
// The behavior is specified by a single list of nodes that encode one or more
// directed trees, where edges point from the leaves (Start nodes) up to the
// root (a Terminal node). The order of the nodes in the list is given by a
// post-order traversal of each tree, ignoring edge orientations.
//
// The simplest form of behavior consists of two nodes:
//
//                     +--------+      +--------+
//                     | Source | ---> | Target |
//                     +--------+      +--------+
//
// This behavior would be represented by the list {Source, Target}.
//
// A more complex behavior could use two source values for a single target:
//
//             +----------+      +-----+
//             | Source 1 | ---> |     |
//             +----------+      |     |      +--------+
//                               | Max | ---> | Target |
//             +----------+      |     |      +--------+
//             | Source 2 | ---> |     |
//             +----------+      +-----+
//
// This could be represented by the list {Source 1, Source 2, Max, Target}.
//
// For each input in a stroke, `BrushTip::behaviors` are applied as follows:
//   1. A target modifier for each tip property is accumulated from every
//      `BrushBehavior` present on the current `BrushTip`:
//       * Multiple behaviors can affect the same `Target`.
//       * Depending on the `Target`, modifiers from multiple behaviors will
//         stack either additively or multiplicatively, according to the
//         descriptions on that `BrushBehavior::Target`.
//       * Regardless, the order of specified behaviors does not affect the
//         result.
//   2. The modifiers are applied to the shape and color shift values of the
//      tip's state according to the descriptions on each `Target`. The
//      resulting tip property values are then clamped or normalized to within
//      their valid range of values. E.g. the final value of
//      `BrushTip::corner_rounding` will be clamped within [0, 1]. Generally:
//       * The affected shape values are those found in `BrushTip` members.
//       * The color shift values remain in the range -100% to +100%. Note that
//         when stored on a vertex, the color shift is encoded such that each
//         channel is in the range [0, 1], where 0.5 represents a 0% shift.
//
// Note that the accumulated tip shape property modifiers may be adjusted by the
// implementation before being applied: the rates of change of shape properties
// may be constrained to keep them from changing too rapidly with respect to
// distance traveled from one input to the next.
message BrushBehavior {
  option (ink.proto.message_min_version) = 0;

  // LINT.IfChange(source)

  // A stroke input property, along with its units, that can act as a source for
  // a `BrushBehavior.SourceNode`.
  //
  // Behaviors that consider properties of the stroke input do not consider
  // alterations to the visible position of that point in the stroke by brush
  // behaviors that modify that position (e.g.
  // Target::TARGET_POSITION_OFFSET_X_IN_MULTIPLES_OF_BRUSH_SIZE). That is, the
  // position, velocity, and acceleration of the stroke input may not match the
  // visible position, velocity, and acceleration of that point in the drawn
  // stroke. The stroke inputs considered by these behaviors are specifically
  // the "modeled" inputs used to construct the stroke geometry, which may be
  // upsampled, denoised, or otherwise transformed from the raw stroke input
  // (see `BrushFamily::InputModel`).
  enum Source {
    SOURCE_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = 0];

    // Stylus or touch pressure with values reported in the range [0, 1].
    SOURCE_NORMALIZED_PRESSURE = 1 [(ink.proto.enum_value_min_version) = 0];

    // Stylus tilt with values reported in the range [0, π/2] radians.
    SOURCE_TILT_IN_RADIANS = 2 [(ink.proto.enum_value_min_version) = 0];

    // Stylus tilt along the x axis in the range [-π/2, π/2], with a positive
    // value corresponding to tilt toward the positive x-axis. In order for this
    // value to be reported, both tilt and orientation have to be populated on
    // the StrokeInput.
    SOURCE_TILT_X_IN_RADIANS = 3 [(ink.proto.enum_value_min_version) = 0];

    // Stylus tilt along the y axis in the range [-π/2, π/2], with a positive
    // value corresponding to tilt toward the positive y-axis. In order for this
    // value to be reported, both tilt and orientation have to be populated on
    // the StrokeInput.
    SOURCE_TILT_Y_IN_RADIANS = 4 [(ink.proto.enum_value_min_version) = 0];

    // Stylus orientation with values reported in the range [0, 2π).
    SOURCE_ORIENTATION_IN_RADIANS = 5 [(ink.proto.enum_value_min_version) = 0];

    // Stylus orientation with values reported in the range (-π, π].
    SOURCE_ORIENTATION_ABOUT_ZERO_IN_RADIANS = 6
        [(ink.proto.enum_value_min_version) = 0];

    // Absolute speed of the modeled stroke input in multiples of the brush size
    // per second. Note that this value doesn't take into account brush
    // behaviors that offset the position of the visual tip of the stroke.
    SOURCE_SPEED_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND = 7
        [(ink.proto.enum_value_min_version) = 0];

    // Signed x component of the velocity of the modeled stroke input in
    // multiples of the brush size per second. Note that this value doesn't take
    // into account brush behaviors that offset the visible position of that
    // point in the stroke.
    SOURCE_VELOCITY_X_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND = 8
        [(ink.proto.enum_value_min_version) = 0];

    // Signed y component of the velocity of the modeled stroke input in
    // multiples of the brush size per second. Note that this value doesn't take
    // into account brush behaviors that offset the visible position of that
    // point in the stroke.
    SOURCE_VELOCITY_Y_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND = 9
        [(ink.proto.enum_value_min_version) = 0];

    // Signed x component of the modeled stroke input's current direction of
    // travel in stroke coordinate space, normalized to the range [-1, 1].
    SOURCE_NORMALIZED_DIRECTION_X = 10 [(ink.proto.enum_value_min_version) = 0];

    // Signed y component of the modeled stroke input's current direction of
    // travel in stroke coordinate space, normalized to the range [-1, 1].
    SOURCE_NORMALIZED_DIRECTION_Y = 11 [(ink.proto.enum_value_min_version) = 0];

    // Distance traveled by the inputs of the current stroke, starting at 0 at
    // the first input, where one distance unit is equal to the brush size.
    SOURCE_DISTANCE_TRAVELED_IN_MULTIPLES_OF_BRUSH_SIZE = 12
        [(ink.proto.enum_value_min_version) = 0];

    // The time elapsed (in seconds) from when the stroke started to when this
    // part of the stroke was drawn. The value remains fixed for any given part
    // of the stroke once drawn.
    SOURCE_TIME_OF_INPUT_IN_SECONDS = 13
        [(ink.proto.enum_value_min_version) = 0];

    reserved 14;

    // Distance traveled by the inputs of the current prediction, starting at 0
    // at the last non-predicted input, in multiples of the brush size. Zero for
    // inputs before the predicted portion of the stroke.
    SOURCE_PREDICTED_DISTANCE_TRAVELED_IN_MULTIPLES_OF_BRUSH_SIZE = 15
        [(ink.proto.enum_value_min_version) = 0];

    // Elapsed time (in seconds) of the prediction, starting at 0 at the last
    // non-predicted input. Zero for inputs before the predicted portion of the
    // stroke.
    SOURCE_PREDICTED_TIME_ELAPSED_IN_SECONDS = 16
        [(ink.proto.enum_value_min_version) = 0];

    reserved 17;

    // The distance left to be traveled from a given modeled input to the
    // current last modeled input of the stroke in multiples of the brush size.
    // This value changes for each input as the stroke is drawn.
    SOURCE_DISTANCE_REMAINING_IN_MULTIPLES_OF_BRUSH_SIZE = 18
        [(ink.proto.enum_value_min_version) = 0];

    // Time elapsed (in seconds) since the modeled stroke input. This continues
    // to increase even after all stroke inputs have completed, and can be used
    // to drive wet-layer stroke animations. This source is only compatible with
    // a `source_out_of_range_behavior` of `OUT_OF_RANGE_CLAMP`, to ensure that
    // the animation will eventually end.
    SOURCE_TIME_SINCE_INPUT_IN_SECONDS = 19
        [(ink.proto.enum_value_min_version) = 0];

    reserved 20;

    // Angle of the modeled stroke input's current direction of travel in stroke
    // coordinate space, normalized to the range [0, 2π). A value of 0 indicates
    // the direction of the positive x-axis; a value of π/2 indicates the
    // direction of the positive y-axis.
    SOURCE_DIRECTION_IN_RADIANS = 21 [(ink.proto.enum_value_min_version) = 0];

    // Angle of the modeled stroke input's current direction of travel in stroke
    // coordinate space, normalized to the range (-π, π]. A value of 0 indicates
    // the direction of the positive x-axis; a value of π/2 indicates the
    // direction of the positive y-axis.
    SOURCE_DIRECTION_ABOUT_ZERO_IN_RADIANS = 22
        [(ink.proto.enum_value_min_version) = 0];

    reserved 23;

    // Absolute acceleration of the modeled stroke input in multiples of the
    // brush size per second squared. Note that this value doesn't take into
    // account brush behaviors that offset the position of that visible point in
    // the stroke.
    SOURCE_ACCELERATION_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED = 24
        [(ink.proto.enum_value_min_version) = 0];

    // Signed x component of the acceleration of the modeled stroke input
    // in multiples of the brush size per second squared. Note that this value
    // doesn't take into account brush behaviors that offset the position of
    // that visible point in the stroke.
    SOURCE_ACCELERATION_X_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED = 25
        [(ink.proto.enum_value_min_version) = 0];

    // Signed y component of the acceleration of the modeled stroke input
    // in multiples of the brush size per second squared. Note that this value
    // doesn't take into account brush behaviors that offset the position of
    // that visible point in the stroke.
    SOURCE_ACCELERATION_Y_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED = 26
        [(ink.proto.enum_value_min_version) = 0];

    // Signed component of acceleration of the modeled stroke input in the
    // direction of its velocity in multiples of the brush size per second
    // squared. Note that this value doesn't take into account brush behaviors
    // that offset the position of that visible point in the stroke.
    SOURCE_ACCELERATION_FORWARD_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED =
        27 [(ink.proto.enum_value_min_version) = 0];

    // Signed component of acceleration of the modeled stroke input
    // perpendicular to its velocity, rotated 90 degrees in the direction from
    // the positive x-axis towards the positive y-axis, in multiples of the
    // brush size per second squared. Note that this value doesn't take into
    // account brush behaviors that offset the position of that visible point
    // in the stroke.
    SOURCE_ACCELERATION_LATERAL_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED =
        28 [(ink.proto.enum_value_min_version) = 0];

    // Absolute speed of the modeled stroke input pointer in centimeters per
    // second.
    SOURCE_SPEED_IN_CENTIMETERS_PER_SECOND = 29
        [(ink.proto.enum_value_min_version) = 0];

    // Signed x component of the modeled stroke input pointer velocity
    // in centimeters per second.
    SOURCE_VELOCITY_X_IN_CENTIMETERS_PER_SECOND = 30
        [(ink.proto.enum_value_min_version) = 0];

    // Signed y component of the modeled stroke input pointer velocity
    // in centimeters per second.
    SOURCE_VELOCITY_Y_IN_CENTIMETERS_PER_SECOND = 31
        [(ink.proto.enum_value_min_version) = 0];

    // Distance in centimeters traveled by the modeled stroke input pointer
    // along the input path from the start of the stroke.
    SOURCE_DISTANCE_TRAVELED_IN_CENTIMETERS = 32
        [(ink.proto.enum_value_min_version) = 0];

    // Distance in centimeters along the input path from the real portion of
    // the modeled stroke to this input. Zero for inputs before the predicted
    // portion of the stroke.
    SOURCE_PREDICTED_DISTANCE_TRAVELED_IN_CENTIMETERS = 33
        [(ink.proto.enum_value_min_version) = 0];

    // Absolute acceleration of the modeled stroke input pointer in centimeters
    // per second squared.
    SOURCE_ACCELERATION_IN_CENTIMETERS_PER_SECOND_SQUARED = 34
        [(ink.proto.enum_value_min_version) = 0];

    // Signed x component of the acceleration of the modeled stroke input
    // pointer in centimeters per second squared.
    SOURCE_ACCELERATION_X_IN_CENTIMETERS_PER_SECOND_SQUARED = 35
        [(ink.proto.enum_value_min_version) = 0];

    // Signed y component of the acceleration of the modeled stroke input
    // pointer in centimeters per second squared.
    SOURCE_ACCELERATION_Y_IN_CENTIMETERS_PER_SECOND_SQUARED = 36
        [(ink.proto.enum_value_min_version) = 0];

    // Signed component of acceleration of the modeled stroke input pointer in
    // the direction of its velocity in centimeters per second squared.
    SOURCE_ACCELERATION_FORWARD_IN_CENTIMETERS_PER_SECOND_SQUARED = 37
        [(ink.proto.enum_value_min_version) = 0];

    // Signed component of acceleration of the modeled stroke input pointer
    // perpendicular to its velocity, rotated 90 degrees in the direction from
    // the positive x-axis towards the positive y-axis, in centimeters per
    // second squared.
    SOURCE_ACCELERATION_LATERAL_IN_CENTIMETERS_PER_SECOND_SQUARED = 38
        [(ink.proto.enum_value_min_version) = 0];

    // Distance from the current modeled input to the end of the stroke along
    // the input path, as a fraction of the current total length of the stroke.
    // This value changes for each input as inputs are added.
    SOURCE_DISTANCE_REMAINING_AS_FRACTION_OF_STROKE_LENGTH = 39
        [(ink.proto.enum_value_min_version) = 0];

    // Time elapsed (in seconds) since the final input of the stroke, or zero if
    // the final input hasn't arrived yet. This can be used to drive wet-layer
    // stroke animations that should occur after the final input. This source is
    // only compatible with a `source_out_of_range_behavior` of
    // `OUT_OF_RANGE_CLAMP`, to ensure that the animation will eventually end.
    SOURCE_TIME_SINCE_STROKE_END_IN_SECONDS = 40
        [(ink.proto.enum_value_min_version) = 1];

    // Time elapsed (in seconds) from the current modeled stroke input until the
    // last input in the stroke.
    SOURCE_TIME_FROM_INPUT_TO_STROKE_END_IN_SECONDS = 41
        [(ink.proto.enum_value_min_version) = 2147483647];
  }
  // LINT.ThenChange(../../brush/brush_behavior.h:source)

  // LINT.IfChange(target)

  // `BrushTip` properties that can be modified by a `BrushBehavior`.
  enum Target {
    TARGET_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = 0];

    // Scales the brush-tip width, starting from the value calculated using
    // `BrushTip::scale_x`. If multiple behaviors have this target, they stack
    // multiplicatively.
    TARGET_WIDTH_MULTIPLIER = 1 [(ink.proto.enum_value_min_version) = 0];

    // Scales the brush-tip width, starting from the value calculated using
    // `BrushTip::scale_y`. If multiple behaviors have this target, they stack
    // multiplicatively.
    TARGET_HEIGHT_MULTIPLIER = 2 [(ink.proto.enum_value_min_version) = 0];

    // Convenience enumerator to target both `TARGET_WIDTH_MULTIPLIER` and
    // `TARGET_HEIGHT_MULTIPLIER`.
    TARGET_SIZE_MULTIPLIER = 3 [(ink.proto.enum_value_min_version) = 0];

    // Adds the target modifier to `BrushTip::slant`. The final brush slant
    // value is clamped to [-π/2, π/2]. If multiple behaviors have this target,
    // they stack additively.
    TARGET_SLANT_OFFSET_IN_RADIANS = 4 [(ink.proto.enum_value_min_version) = 0];

    // Adds the target modifier to `BrushTip::pinch`. The final brush pinch
    // value is clamped to [0, 1]. If multiple behaviors have this target, they
    // stack additively.
    TARGET_PINCH_OFFSET = 5 [(ink.proto.enum_value_min_version) = 0];

    // Adds the target modifier to `BrushTip::rotation`. The final brush
    // rotation angle is effectively normalized (mod 2π). If multiple behaviors
    // have this target, they stack additively.
    TARGET_ROTATION_OFFSET_IN_RADIANS = 6
        [(ink.proto.enum_value_min_version) = 0];

    // Adds the target modifier to `BrushTip::corner_rounding`. The final brush
    // corner rounding value is clamped to [0, 1]. If multiple behaviors have
    // this target, they stack additively.
    TARGET_CORNER_ROUNDING_OFFSET = 7 [(ink.proto.enum_value_min_version) = 0];

    // Shifts the hue of the base brush color.  A positive offset shifts around
    // the hue wheel from red towards orange, while a negative offset shifts the
    // other way, from red towards violet. The final hue offset is not clamped,
    // but is effectively normalized (mod 2π). If multiple behaviors have this
    // target, they stack additively.
    //
    // This target is for tip color adjustments. Renderers can apply it to the
    // brush color when a stroke is drawn to contribute to the local color of
    // each part of the stroke.
    TARGET_HUE_OFFSET_IN_RADIANS = 8 [(ink.proto.enum_value_min_version) = 0];

    // Scales the saturation of the base brush color.  If multiple behaviors
    // have one of these targets, they stack multiplicatively.  The final
    // saturation multiplier is clamped to [0, 2].
    //
    // This target is for tip color adjustments. Renderers can apply it to the
    // brush color when a stroke is drawn to contribute to the local color of
    // each part of the stroke.
    TARGET_SATURATION_MULTIPLIER = 9 [(ink.proto.enum_value_min_version) = 0];

    // Shifts the luminosity of the base brush color. An offset of ±1.0
    // corresponds to changing the luminosity by up to ±100%. If multiple
    // behaviors have this target, they stack additively.  The final luminosity
    // offset is clamped to [-1, 1].
    //
    // This target is for tip color adjustments. Renderers can apply it to the
    // brush color when a stroke is drawn to contribute to the local color of
    // each part of the stroke.
    TARGET_LUMINOSITY_OFFSET = 10 [(ink.proto.enum_value_min_version) = 0];

    // Scales the opacity of the base brush color.  If multiple behaviors have
    // one of these targets, they stack multiplicatively.  The final opacity
    // multiplier is clamped to [0, 2].
    //
    // This target is for tip color adjustments. Renderers can apply it to the
    // brush color when a stroke is drawn to contribute to the local color of
    // each part of the stroke.
    TARGET_OPACITY_MULTIPLIER = 11 [(ink.proto.enum_value_min_version) = 0];

    // Adds the target modifier to the brush tip x position in multiples of
    // the brush size.
    TARGET_POSITION_OFFSET_X_IN_MULTIPLES_OF_BRUSH_SIZE = 12
        [(ink.proto.enum_value_min_version) = 0];

    // Adds the target modifier to the brush tip y position in multiples of
    // the brush size.
    TARGET_POSITION_OFFSET_Y_IN_MULTIPLES_OF_BRUSH_SIZE = 13
        [(ink.proto.enum_value_min_version) = 0];

    // Moves the brush tip by the target modifier times the brush size in the
    // direction of the modeled stroke input's velocity (the opposite direction
    // if the value is negative).
    TARGET_POSITION_OFFSET_FORWARD_IN_MULTIPLES_OF_BRUSH_SIZE = 14
        [(ink.proto.enum_value_min_version) = 0];

    // Moves the brush tip by the target modifier times the brush size
    // perpendicular to the modeled stroke input's velocity, rotated 90 degrees
    // in the direction from the positive x-axis to the positive y-axis.
    TARGET_POSITION_OFFSET_LATERAL_IN_MULTIPLES_OF_BRUSH_SIZE = 15
        [(ink.proto.enum_value_min_version) = 0];

    reserved 16;
    reserved 17;
  }
  // LINT.ThenChange(../../brush/brush_behavior.h:target)

  // LINT.IfChange(polar_target)

  // Like `Target`, but for vector values.
  enum PolarTarget {
    POLAR_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = 0];

    // Adds the vector to the brush tip's absolute x/y position in stroke space,
    // where the angle input is measured in radians and the magnitude input is
    // measured in units equal to the brush size. An angle of zero indicates an
    // offset in the direction of the positive X-axis in stroke space; an angle
    // of π/2 indicates the direction of the positive Y-axis in stroke space.
    POLAR_POSITION_OFFSET_ABSOLUTE_IN_RADIANS_AND_MULTIPLES_OF_BRUSH_SIZE = 1
        [(ink.proto.enum_value_min_version) = 0];

    // Adds the vector to the brush tip's forward/lateral position relative to
    // the current direction of input travel, where the angle input is measured
    // in radians and the magnitude input is measured in units equal to the
    // brush size. An angle of zero indicates a forward offset in the current
    // direction of input travel, while an angle of π indicates a backwards
    // offset. Meanwhile, if the X- and Y-axes of stroke space were rotated so
    // that the positive X-axis points in the direction of stroke travel, then
    // an angle of π/2 would indicate a lateral offset towards the positive
    // Y-axis, and an angle of -π/2 would indicate a lateral offset towards the
    // negative Y-axis.
    POLAR_POSITION_OFFSET_RELATIVE_IN_RADIANS_AND_MULTIPLES_OF_BRUSH_SIZE = 2
        [(ink.proto.enum_value_min_version) = 0];
  }
  // LINT.ThenChange(../../brush/brush_behavior.h:polar_target)

  // LINT.IfChange(out_of_range)

  // The desired behavior when an input value is outside the bounds of
  // `source_value_range`.
  enum OutOfRange {
    OUT_OF_RANGE_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = 0];

    // Values outside the range will be clamped to not exceed the bounds.
    OUT_OF_RANGE_CLAMP = 1 [(ink.proto.enum_value_min_version) = 0];

    // Values will be shifted by an integer multiple of the range size so that
    // they fall within the bounds.
    //
    // In this case, the range will be treated as a half-open interval, with a
    // value exactly at `source_value_range[1]` being treated as though it was
    // `source_value_range[0]`.
    OUT_OF_RANGE_REPEAT = 2 [(ink.proto.enum_value_min_version) = 0];

    // Similar to `OUT_OF_RANGE_REPEAT`, but every other repetition of the
    // bounds will be mirrored, as though the two elements of
    // `source_value_range` were swapped. This means the range does not need to
    // be treated as a half-open interval like in the case of
    // `OUT_OF_RANGE_REPEAT`.
    OUT_OF_RANGE_MIRROR = 3 [(ink.proto.enum_value_min_version) = 0];
  }
  // LINT.ThenChange(../../brush/brush_behavior.h:out_of_range)

  // LINT.IfChange(binary_op)

  // A binary operation for combining two values in a `BinaryOpNode`.
  enum BinaryOp {
    BINARY_OP_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = 0];

    // A * B, or null if either is null
    BINARY_OP_PRODUCT = 1 [(ink.proto.enum_value_min_version) = 0];

    // A + B, or null if either is null
    BINARY_OP_SUM = 2 [(ink.proto.enum_value_min_version) = 0];

    // min(A, B), or null if either is null
    BINARY_OP_MIN = 3 [(ink.proto.enum_value_min_version) = 1];

    // max(A, B), or null if either is null
    BINARY_OP_MAX = 4 [(ink.proto.enum_value_min_version) = 1];

    // null if A is null, or B otherwise
    BINARY_OP_AND_THEN = 5 [(ink.proto.enum_value_min_version) = 1];

    // A if A isn't null, or B otherwise
    BINARY_OP_OR_ELSE = 6 [(ink.proto.enum_value_min_version) = 1];

    // A if B is null, or B if A is null, or null if neither is null
    BINARY_OP_XOR_ELSE = 7 [(ink.proto.enum_value_min_version) = 1];
  }
  // LINT.ThenChange(../../brush/brush_behavior.h:binary_op)

  // LINT.IfChange(progress_domain)

  // Dimensions and units for measuring distance/time along the length/duration
  // of a stroke.
  enum ProgressDomain {
    PROGRESS_DOMAIN_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = 0];

    // Progress in input time since the start of the stroke, measured in
    // seconds.
    PROGRESS_DOMAIN_TIME_IN_SECONDS = 1
        [(ink.proto.enum_value_min_version) = 0];

    // Progress in input distance traveled since the start of the stroke,
    // measured in centimeters. If the stroke input data does not indicate the
    // relationship between stroke units and physical units (e.g. as may be the
    // case for programmatically-generated inputs), then any node relying on
    // this domain will emit null.
    PROGRESS_DOMAIN_DISTANCE_IN_CENTIMETERS = 2
        [(ink.proto.enum_value_min_version) = 0];

    // Progress in input distance traveled since the start of the stroke,
    // measured in multiples of the brush size.
    PROGRESS_DOMAIN_DISTANCE_IN_MULTIPLES_OF_BRUSH_SIZE = 3
        [(ink.proto.enum_value_min_version) = 0];
  }
  // LINT.ThenChange(../../brush/brush_behavior.h:progress_domain)

  // LINT.IfChange(interpolation)

  // An interpolation function for combining three values in an
  // `InterpolationNode`.
  enum Interpolation {
    INTERPOLATION_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = 0];

    // Linear interpolation. Uses parameter A to interpolate between B (when
    // A=0) and C (when A=1).
    INTERPOLATION_LERP = 1 [(ink.proto.enum_value_min_version) = 0];

    // Inverse linear interpolation. Outputs 0 when A=B and 1 when A=C,
    // interpolating linearly in between. Outputs null if B=C.
    INTERPOLATION_INVERSE_LERP = 2 [(ink.proto.enum_value_min_version) = 0];
  }
  // LINT.ThenChange(../../brush/brush_behavior.h:interpolation)

  // A single node in a behavior's graph. Each node type is either a "value
  // node" which consumes zero or more input values and produces a single output
  // value, or a "terminal node" which consumes one or more input values and
  // applies some effect to the brush tip (but does not produce any output
  // value).
  message Node {
    option (ink.proto.message_min_version) = 0;

    oneof node {
      SourceNode source_node = 1 [(ink.proto.field_min_version) = 0];
      ConstantNode constant_node = 2 [(ink.proto.field_min_version) = 0];
      ToolTypeFilterNode tool_type_filter_node = 4
          [(ink.proto.field_min_version) = 0];
      DampingNode damping_node = 5 [(ink.proto.field_min_version) = 0];
      ResponseNode response_node = 6 [(ink.proto.field_min_version) = 0];
      BinaryOpNode binary_op_node = 7 [(ink.proto.field_min_version) = 0];
      TargetNode target_node = 8 [(ink.proto.field_min_version) = 0];
      InterpolationNode interpolation_node = 9
          [(ink.proto.field_min_version) = 0];
      NoiseNode noise_node = 10 [(ink.proto.field_min_version) = 0];
      PolarTargetNode polar_target_node = 11
          [(ink.proto.field_min_version) = 0];
      IntegralNode integral_node = 12 [(ink.proto.field_min_version) = 1];
    }
    // Removed Node types go here (reserved needs to be outside oneof).
    reserved 3;
  }

  // Value node for getting data from the stroke input batch.
  //
  // Inputs: 0
  //
  // Output: The value of the source after inverse-lerping from the specified
  // value range (i.e., linearly mapping the range's start to an output of 0.0
  // and the range's end to an output of 1.0) and applying the specified
  // out-of-range behavior, or null if the source value is indeterminate (e.g.
  // because the stroke input batch is missing that property).
  //
  // To be valid:
  //   * `source` must be a valid `Source` enumerator.
  //   * `source_out_of_range_behavior` must be a valid `OutOfRange` enumerator.
  //   * The endpoints of `source_value_range` must be finite and distinct.
  message SourceNode {
    option (ink.proto.message_min_version) = 0;

    // What property of the stroke input to use for source values for this node.
    optional Source source = 1 [(ink.proto.field_min_version) = 0];

    // What to do with source values outside the source value range.
    optional OutOfRange source_out_of_range_behavior = 2
        [(ink.proto.field_min_version) = 0];

    // The source value that maps to 0.0 in the output. Below this value,
    // `out_of_range_behavior` determines the output value.
    optional float source_value_range_start = 3
        [(ink.proto.field_min_version) = 0];

    // The source value that maps to 1.0 in the output. Above this value,
    // `out_of_range_behavior` determines the output value.
    optional float source_value_range_end = 4
        [(ink.proto.field_min_version) = 0];
  }

  // Value node for producing a constant value.
  //
  // Inputs: 0
  //
  // Output: The specified constant value.
  //
  // To be valid: `value` must be finite.
  message ConstantNode {
    option (ink.proto.message_min_version) = 0;

    optional float value = 1 [(ink.proto.field_min_version) = 0];
  }

  // Value node for producing a continuous random noise function with values
  // between 0 to 1.
  //
  // Inputs: 0
  //
  // Output: The current random value.
  //
  // To be valid:
  //   * `vary_over` must be a valid `ProgressDomain` enumerator.
  //   * `base_period` must be finite and strictly positive.
  message NoiseNode {
    option (ink.proto.message_min_version) = 0;

    optional fixed32 seed = 1 [(ink.proto.field_min_version) = 0];

    // The domain units over which random noise is generated.
    optional ProgressDomain vary_over = 2 [(ink.proto.field_min_version) = 0];

    // The period (in `vary_over` units) over which the output value smoothly
    // varies from one random value to another uncorrelated random value. (In
    // other words, if two points in the input are separated by at least this
    // period, their output values are uncorrelated.)
    optional float base_period = 3 [(ink.proto.field_min_version) = 0];
  }

  // Value node for filtering out a branch of a behavior graph unless this
  // stroke's tool type is in the specified set.
  //
  // Inputs: 1
  //
  // Output: Null if this stroke's tool type is not in the specified set,
  // otherwise the input value.
  //
  // To be valid: At least one tool type must be enabled.
  message ToolTypeFilterNode {
    option (ink.proto.message_min_version) = 0;

    // A bitset of tool types, using ink.proto.CodedStrokeInputBatch.ToolType
    // enum values as bit numbers for each tool type.  For example, if only
    // touch and stylus are enabled, then the value of this field should be:
    //   ((1 << ToolType.TOUCH) | (1 << ToolType.STYLUS))
    optional uint32 enabled_tool_types = 1 [(ink.proto.field_min_version) = 0];
  }

  // Value node for damping changes in an input value, causing the output value
  // to slowly follow changes in the input value over a specified time or
  // distance.
  //
  // Inputs: 1
  //
  // Output: The damped input value. If the input value becomes null, this node
  // continues to emit its previous output value.  If the input value starts out
  // null, the output value is null until the first non-null input.
  //
  // To be valid:
  //   * `damping_source` must be a valid `ProgressDomain` enumerator.
  //   * `damping_gap` must be finite and non-negative.
  message DampingNode {
    option (ink.proto.message_min_version) = 0;

    // The domain units over which damping is applied.
    optional ProgressDomain damping_source = 1
        [(ink.proto.field_min_version) = 0];

    // A scaling factor, in `damping_source` units, for the damping. Smaller
    // gaps result in less damping, so the output follows the input more
    // closely.
    optional float damping_gap = 2 [(ink.proto.field_min_version) = 0];
  }

  // Value node for mapping a value through a response curve.
  //
  // Inputs: 1
  //
  // Output: The result of the easing function when applied to the input value,
  // or null if the input value is null.
  //
  // To be valid: `response_curve` must be a valid `EasingFunction`.
  message ResponseNode {
    option (ink.proto.message_min_version) = 0;

    oneof response_curve {
      PredefinedEasingFunction predefined_response_curve = 1
          [(ink.proto.field_min_version) = 0];
      CubicBezierEasingFunction cubic_bezier_response_curve = 2
          [(ink.proto.field_min_version) = 0];
      LinearEasingFunction linear_response_curve = 3
          [(ink.proto.field_min_version) = 0];
      StepsEasingFunction steps_response_curve = 4
          [(ink.proto.field_min_version) = 0];
    }
  }

  // Value node for combining two other values with a binary operation.
  //
  // Inputs: 2
  //
  // Output: The result of the specified operation on the two input values. See
  // comments on `BinaryOp` for details on how each operator handles null input
  // values.
  //
  // To be valid: `operation` must be a valid `BinaryOp` enumerator.
  message BinaryOpNode {
    option (ink.proto.message_min_version) = 0;

    optional BinaryOp operation = 1 [(ink.proto.field_min_version) = 0];
  }

  // Value node for interpolating to/from a range of two values.
  //
  // Inputs: 3
  //
  // Output: The result of using the first input value as an interpolation
  // parameter between the second and third input values, using the specified
  // interpolation function, or null if any input value is null.
  //
  // To be valid: `interpolation` must be a valid `Interpolation` enumerator.
  message InterpolationNode {
    option (ink.proto.message_min_version) = 0;

    optional Interpolation interpolation = 1
        [(ink.proto.field_min_version) = 0];
  }

  // Value node for integrating an input value over time or distance.
  //
  // Inputs: 1
  //
  // Output: The integral of the input value since the start of the stroke,
  // after inverse-lerping from the specified value range and applying the
  // specified out-of-range behavior. If the input value ever becomes null, this
  // node acts as though the input value were still equal to its most recent
  // non-null value. If the input value starts out null, it is treated as zero
  // until the first non-null input.
  //
  // To be valid:
  //   * `integrate_over` must be a valid `ProgressDomain` enumerator.
  //   * The endpoints of `integral_value_range` must be finite and distinct.
  //   * `integral_out_of_range_behavior` must be a valid `OutOfRange`
  //     enumerator.
  message IntegralNode {
    option (ink.proto.message_min_version) = 1;

    // The variable and units (e.g. time or distance) over which the input value
    // is integrated.
    optional ProgressDomain integrate_over = 1
        [(ink.proto.field_min_version) = 1];

    // The integral value that maps to 0.0 in the output. Below this value,
    // `out_of_range_behavior` determines the output value.
    optional float integral_value_range_start = 2
        [(ink.proto.field_min_version) = 1];

    // The integral value that maps to 1.0 in the output. Above this value,
    // `out_of_range_behavior` determines the output value.
    optional float integral_value_range_end = 3
        [(ink.proto.field_min_version) = 1];

    // What to do with integral values outside the integral value range.
    optional OutOfRange integral_out_of_range_behavior = 4
        [(ink.proto.field_min_version) = 1];
  }

  // Terminal node that consumes a single input value to modify a scalar brush
  // tip property.
  //
  // Inputs: 1
  //
  // Effect: Applies a modifier to the specified target equal to the input value
  // lerped to the specified range. If the input becomes null, the target
  // continues to apply its previous effect from the most recent non-null input
  // (if any).
  //
  // To be valid:
  //   * `target` must be a valid `Target` enumerator.
  //   * The endpoints of `target_modifier_range` must be finite and distinct.
  message TargetNode {
    option (ink.proto.message_min_version) = 0;

    // What aspect of the brush to affect, and how.
    optional Target target = 1 [(ink.proto.field_min_version) = 0];

    // The output value produced by an input value of 0.0.
    optional float target_modifier_range_start = 2
        [(ink.proto.field_min_version) = 0];

    // The output value produced by an input value of 1.0.
    optional float target_modifier_range_end = 3
        [(ink.proto.field_min_version) = 0];
  }

  // Terminal node that consumes two input values (angle and magnitude), forming
  // a polar vector to modify a vector brush tip property.
  //
  // Inputs: 2
  //
  // Effect: Applies a vector modifier to the specified target equal to the
  // polar vector formed by lerping the first input value to the specified angle
  // range, and the second input to the specified magnitude range. If either
  // input becomes null, the target continues to apply its previous effect from
  // the most recent non-null inputs (if any).
  //
  // To be valid:
  //   * `target` must be a valid `PolarTarget` enumerator.
  //   * The endpoints of `angle_range` and of `magnitude_range` must be finite
  //     and distinct.
  message PolarTargetNode {
    option (ink.proto.message_min_version) = 0;

    // What aspect of the brush to affect, and how.
    optional PolarTarget target = 1 [(ink.proto.field_min_version) = 0];

    // The output angle produced by a value of 0.0 for the first input.
    optional float angle_range_start = 2 [(ink.proto.field_min_version) = 0];

    // The output angle produced by a value of 1.0 for the first input.
    optional float angle_range_end = 3 [(ink.proto.field_min_version) = 0];

    // The output magnitude produced by a value of 0.0 for the second input.
    optional float magnitude_range_start = 4
        [(ink.proto.field_min_version) = 0];

    // The output magnitude produced by a value of 1.0 for the second input.
    optional float magnitude_range_end = 5 [(ink.proto.field_min_version) = 0];
  }

  // A post-order traversal of the graph of Nodes.
  repeated Node nodes = 15 [(ink.proto.field_min_version) = 0];

  // Were fields controlling brush behavior, use `nodes` instead.
  reserved 1 to 14;

  // A multi-line, human-readable string with a description of this brush
  // behavior and its purpose within the brush, with the intended audience being
  // designers/developers who are editing the brush definition. This string is
  // not generally intended to be displayed to end users.
  optional string developer_comment = 16 [(ink.proto.field_min_version) = 0];
}

// Transforms the brush color to be used as an alternative base color for any
// effects or textures in a `BrushCoat`.
message ColorFunction {
  option (ink.proto.message_min_version) = 0;

  oneof function {
    float opacity_multiplier = 1 [(ink.proto.field_min_version) = 0];
    Color replace_color = 2 [(ink.proto.field_min_version) = 0];
  }
}

// LINT.IfChange(predefined)

// Specifies a predefined easing function.
enum PredefinedEasingFunction {
  PREDEFINED_EASING_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = 0];

  // The linear identity function: accepts and returns values outside [0, 1].
  PREDEFINED_EASING_LINEAR = 1 [(ink.proto.enum_value_min_version) = 0];

  // Predefined cubic Bezier function:
  // https://www.w3.org/TR/css-easing-1/#cubic-bezier-easing-functions (see note
  // on `CubicBezier` about input values outside [0, 1])
  PREDEFINED_EASING_EASE = 2 [(ink.proto.enum_value_min_version) = 0];

  // Predefined cubic Bezier function:
  // https://www.w3.org/TR/css-easing-1/#cubic-bezier-easing-functions (see note
  // on `CubicBezier` about input values outside [0, 1])
  PREDEFINED_EASING_EASE_IN = 3 [(ink.proto.enum_value_min_version) = 0];

  // Predefined cubic Bezier function:
  // https://www.w3.org/TR/css-easing-1/#cubic-bezier-easing-functions (see note
  // on `CubicBezier` about input values outside [0, 1])
  PREDEFINED_EASING_EASE_OUT = 4 [(ink.proto.enum_value_min_version) = 0];

  // Predefined cubic Bezier function:
  // https://www.w3.org/TR/css-easing-1/#cubic-bezier-easing-functions (see note
  // on `CubicBezier` about input values outside [0, 1])
  PREDEFINED_EASING_EASE_IN_OUT = 5 [(ink.proto.enum_value_min_version) = 0];

  // Predefined step functions:
  // https://www.w3.org/TR/css-easing-1/#step-easing-functions
  PREDEFINED_EASING_STEP_START = 6 [(ink.proto.enum_value_min_version) = 0];

  // Predefined step functions:
  // https://www.w3.org/TR/css-easing-1/#step-easing-functions
  PREDEFINED_EASING_STEP_END = 7 [(ink.proto.enum_value_min_version) = 0];
}
// LINT.ThenChange(../../brush/easing_function.h:predefined)

// Parameters for a custom cubic Bezier easing function.
//
// A cubic Bezier is generally defined by four points, P0 - P3. In the case of
// the easing function, P0 is defined to be the point (0, 0), and P3 is
// defined to be the point (1, 1). The values of `x1` and `x2` are required to
// be in the range [0, 1]. This guarantees that the resulting curve is a
// function with respect to x and follows the CSS cubic Bezier specification:
// https://www.w3.org/TR/css-easing-1/#cubic-bezier-easing-functions
//
// Valid parameters must have all finite values, and `x1` and `x2` must be in
// the interval [0, 1].
//
// Input x values that are outside the interval [0, 1] will be clamped, but
// output values will not. This is somewhat different from the w3c defined
// cubic Bezier that allows extrapolated values outside x in [0, 1] by
// following end-point tangents.
message CubicBezierEasingFunction {
  option (ink.proto.message_min_version) = 0;

  optional float x1 = 1 [(ink.proto.field_min_version) = 0];
  optional float y1 = 2 [(ink.proto.field_min_version) = 0];
  optional float x2 = 3 [(ink.proto.field_min_version) = 0];
  optional float y2 = 4 [(ink.proto.field_min_version) = 0];
}

// Parameters for a custom piecewise-linear easing function.
//
// A piecewise-linear function is defined by a sequence of points; the value
// of the function at an x-position equal to one of those points is equal to
// the y-position of that point, and the value of the function at an
// x-position between two points is equal to the linear interpolation between
// those points' y-positions. This easing function implicitly includes the
// points (0, 0) and (1, 1), so the `points` field below need only include any
// points between those. If `points` is empty, then this function is
// equivalent to the predefined `kLinear` identity function.
//
// To be valid, all y-positions must be finite, and all x-positions must be in
// the range [0, 1] and must be monotonicly non-decreasing. It is valid for
// multiple points to have the same x-position, in order to create a
// discontinuity in the function; in that case, the value of the function at
// exactly that x-position is equal to the y-position of the last of these
// points.
//
// If the input x-value is outside the interval [0, 1], the output will be
// extrapolated from the first/last line segment.
message LinearEasingFunction {
  option (ink.proto.message_min_version) = 0;

  // These two lists must have the same length.
  repeated float x = 1 [packed = true, (ink.proto.field_min_version) = 0];
  repeated float y = 2 [packed = true, (ink.proto.field_min_version) = 0];
}

// LINT.IfChange(step_position)

// Setting to determine the desired output value of the first and last
// step of [0, 1) for the Steps EasingFunction. See below for more context.
enum StepPosition {
  STEP_POSITION_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = 0];

  // The step function "jumps" at the start of [0, 1):
  //   * for x in [0, 1/step_count)      =>     y = 1/step_count
  //   * for x in [1 - 1/step_count, 1)  =>     y = 1
  STEP_POSITION_JUMP_START = 1 [(ink.proto.enum_value_min_version) = 0];

  // The step function "jumps" at the end of [0, 1):
  //   * for x in [0, 1/step_count)      =>     y = 0
  //   * for x in [1 - 1/step_count, 1)  =>     y = 1 - 1/step_count
  STEP_POSITION_JUMP_END = 2 [(ink.proto.enum_value_min_version) = 0];

  // The step function does not "jump" at either boundary:
  //   * for x in [0, 1/step_count)      =>     y = 0
  //   * for x in [1 - 1/step_count, 1)  =>     y = 1
  STEP_POSITION_JUMP_NONE = 3 [(ink.proto.enum_value_min_version) = 0];

  // The step function "jumps" at both the start and the end:
  //   * for x in [0, 1/step_count)      =>     y = 1/(step_count + 1)
  //   * for x in [1 - 1/step_count, 1)  =>     y = 1 - 1/(step_count + 1)
  STEP_POSITION_JUMP_BOTH = 4 [(ink.proto.enum_value_min_version) = 0];
}
// LINT.ThenChange(../../brush/easing_function.h:step_position)

// Parameters for a custom step easing function.
//
// A step function is defined by the number of equal-sized steps into which
// the [0, 1) interval of input-x is split and the behavior at the extremes.
// When x < 0, the output will always be 0. When x >= 1, the output will
// always be 1. The output of the first and last steps is governed by the
// `StepPosition`.
//
// The behavior and naming follows the CSS steps() specification at:
// https:www.w3.org/TR/css-easing-1/#step-easing-functions
message StepsEasingFunction {
  option (ink.proto.message_min_version) = 0;

  // The number of steps.
  //
  // Must always be greater than 0, and must be greater than 1 if
  // `step_position` is `kJumpNone`.
  optional int32 step_count = 1 [(ink.proto.field_min_version) = 0];

  // Position of the step(s)
  optional StepPosition step_position = 2 [(ink.proto.field_min_version) = 0];
}
