123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- // Copyright 2016 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "ash/fast_ink/fast_ink_points.h"
- #include <algorithm>
- #include <array>
- #include <limits>
- #include "base/containers/adapters.h"
- #include "base/containers/circular_deque.h"
- #include "ui/gfx/geometry/point_f.h"
- #include "ui/gfx/geometry/rect_conversions.h"
- namespace {
- constexpr SkColor kDefaultPointColor = SkColorSetRGB(0x42, 0x85, 0xF4);
- constexpr int kDefaultOpacity = 0xCC;
- } // namespace
- namespace fast_ink {
- const SkColor FastInkPoints::kDefaultColor =
- SkColorSetA(kDefaultPointColor, kDefaultOpacity);
- FastInkPoints::FastInkPoints(base::TimeDelta life_duration)
- : life_duration_(life_duration) {}
- FastInkPoints::~FastInkPoints() = default;
- void FastInkPoints::AddPoint(const gfx::PointF& point,
- const base::TimeTicks& time) {
- FastInkPoint new_point;
- new_point.location = point;
- new_point.time = time;
- points_.push_back(new_point);
- }
- void FastInkPoints::AddPoint(const gfx::PointF& point,
- const base::TimeTicks& time,
- SkColor color) {
- FastInkPoint new_point;
- new_point.location = point;
- new_point.time = time;
- new_point.color = color;
- points_.push_back(new_point);
- }
- void FastInkPoints::AddGap() {
- // Not doing anything special regarding prediction, as in real usage there
- // will be a gap in timestamps, and the prediction algorithm will reject the
- // points that are too old.
- points_.back().gap_after = true;
- }
- void FastInkPoints::MoveForwardToTime(const base::TimeTicks& latest_time) {
- DCHECK_GE(latest_time, collection_latest_time_);
- collection_latest_time_ = latest_time;
- if (!points_.empty() && !life_duration_.is_zero()) {
- // Remove obsolete points.
- const base::TimeTicks expiration = latest_time - life_duration_;
- auto first_alive_point = std::find_if(
- points_.begin(), points_.end(),
- [expiration](const FastInkPoint& p) { return p.time > expiration; });
- points_.erase(points_.begin(), first_alive_point);
- }
- }
- gfx::Rect FastInkPoints::UndoLastStroke() {
- if (points_.empty())
- return gfx::Rect();
- gfx::PointF min_point = GetNewest().location;
- gfx::PointF max_point = min_point;
- // Skip the last gap to delete until the penultimate gap.
- if (points_.back().gap_after)
- points_.pop_back();
- while (!points_.empty() && !points_.back().gap_after) {
- const gfx::PointF& location = points_.back().location;
- min_point.SetToMin(location);
- max_point.SetToMax(location);
- points_.pop_back();
- }
- return gfx::ToEnclosingRect(gfx::BoundingRect(min_point, max_point));
- }
- void FastInkPoints::Clear() {
- points_.clear();
- }
- gfx::Rect FastInkPoints::GetBoundingBox() const {
- return gfx::ToEnclosingRect(GetBoundingBoxF());
- }
- gfx::RectF FastInkPoints::GetBoundingBoxF() const {
- if (IsEmpty())
- return gfx::RectF();
- gfx::PointF min_point = GetOldest().location;
- gfx::PointF max_point = min_point;
- for (const FastInkPoint& point : points_) {
- min_point.SetToMin(point.location);
- max_point.SetToMax(point.location);
- }
- return gfx::BoundingRect(min_point, max_point);
- }
- FastInkPoints::FastInkPoint FastInkPoints::GetOldest() const {
- DCHECK(!IsEmpty());
- return points_.front();
- }
- FastInkPoints::FastInkPoint FastInkPoints::GetNewest() const {
- DCHECK(!IsEmpty());
- return points_.back();
- }
- bool FastInkPoints::IsEmpty() const {
- return points_.empty();
- }
- int FastInkPoints::GetNumberOfPoints() const {
- return points_.size();
- }
- const base::circular_deque<FastInkPoints::FastInkPoint>& FastInkPoints::points()
- const {
- return points_;
- }
- float FastInkPoints::GetFadeoutFactor(int index) const {
- DCHECK(!life_duration_.is_zero());
- DCHECK_GE(index, 0);
- DCHECK_LT(index, GetNumberOfPoints());
- const base::TimeDelta age = collection_latest_time_ - points_[index].time;
- return std::min(age / life_duration_, 1.0);
- }
- void FastInkPoints::Predict(const FastInkPoints& real_points,
- const base::TimeTicks& current_time,
- base::TimeDelta prediction_duration,
- const gfx::Size& screen_size) {
- Clear();
- if (real_points.IsEmpty() || prediction_duration.is_zero())
- return;
- gfx::Vector2dF scale(1.0f / screen_size.width(), 1.0f / screen_size.height());
- // Create a new set of predicted points based on the last four points added.
- // We add enough predicted points to fill the time between the new point and
- // the expected presentation time. Note that estimated presentation time is
- // based on current time and inefficient rendering of points can result in an
- // actual presentation time that is later.
- // TODO(reveman): Determine interval based on history when event time stamps
- // are accurate. b/36137953
- const float kPredictionIntervalMs = 5.0f;
- const float kMaxPointIntervalMs = 10.0f;
- base::TimeDelta prediction_interval =
- base::Milliseconds(kPredictionIntervalMs);
- base::TimeDelta max_point_interval = base::Milliseconds(kMaxPointIntervalMs);
- const FastInkPoint newest_real_point = real_points.GetNewest();
- base::TimeTicks last_point_time = newest_real_point.time;
- gfx::PointF last_point_location =
- gfx::ScalePoint(newest_real_point.location, scale.x(), scale.y());
- // Use the last four points for prediction.
- using PositionArray = std::array<gfx::PointF, 4>;
- PositionArray position;
- PositionArray::iterator it = position.begin();
- for (const auto& point : base::Reversed(real_points.points())) {
- // Stop adding positions if interval between points is too large to provide
- // an accurate history for prediction.
- if ((last_point_time - point.time) > max_point_interval)
- break;
- last_point_time = point.time;
- last_point_location = gfx::ScalePoint(point.location, scale.x(), scale.y());
- *it++ = last_point_location;
- // Stop when no more positions are needed.
- if (it == position.end())
- break;
- }
- const size_t valid_positions = it - position.begin();
- if (valid_positions < 2) // Not enough reliable data, bail out.
- return;
- // Note: Currently there's no need to divide by the time delta between
- // points as we assume a constant delta between points that matches the
- // prediction point interval.
- gfx::Vector2dF velocity[3];
- for (size_t i = 0; i < valid_positions - 1; ++i)
- velocity[i] = position[i] - position[i + 1];
- // velocity[0] is always valid, since |valid_positions| >=2
- gfx::Vector2dF acceleration[2];
- for (size_t i = 0; i < valid_positions - 2; ++i)
- acceleration[i] = velocity[i] - velocity[i + 1];
- // acceleration[0] is always valid (zero if |valid_positions| < 3).
- gfx::Vector2dF jerk;
- if (valid_positions > 3)
- jerk = acceleration[0] - acceleration[1];
- // |jerk| is aways valid (zero if |valid_positions| < 4).
- // Adjust max prediction time based on speed as prediction data is not great
- // at lower speeds.
- const float kMaxPredictionScaleSpeed = 1e-5;
- double speed = velocity[0].LengthSquared();
- base::TimeTicks max_prediction_time =
- current_time +
- std::min(prediction_duration * (speed / kMaxPredictionScaleSpeed),
- prediction_duration);
- // Add predicted points until we reach the max prediction time.
- gfx::PointF location = position[0];
- for (base::TimeTicks time = newest_real_point.time + prediction_interval;
- time < max_prediction_time; time += prediction_interval) {
- // Note: Currently there's no need to multiply by the prediction interval
- // as the velocity is calculated based on a time delta between points that
- // is the same as the prediction interval.
- velocity[0] += acceleration[0];
- acceleration[0] += jerk;
- location += velocity[0];
- AddPoint(gfx::ScalePoint(location, 1 / scale.x(), 1 / scale.y()), time,
- newest_real_point.color);
- // Always stop at three predicted points as a four point history doesn't
- // provide accurate prediction of more points.
- if (GetNumberOfPoints() == 3)
- break;
- }
- }
- } // namespace fast_ink
|