Skip to content

Commit

Permalink
Extract prediction logic from LaserPointerView
Browse files Browse the repository at this point in the history
Renamed LaserPointerPoints to FastInkPoints and
move it under ash/fast_ink.

Moved prediction logic into FastInkPoints.

Added a unit test for prediction.

This should allow other tools such as highlighter
to use the common prediction logic.

Bug: 743083
Change-Id: I2dc1c9dfb09d816f3eb9bf7cc83e8c777a38da06
Reviewed-on: https://chromium-review.googlesource.com/580612
Reviewed-by: Mitsuru Oshima <oshima@chromium.org>
Reviewed-by: David Reveman <reveman@chromium.org>
Commit-Queue: Vladislav Kaznacheev <kaznacheev@chromium.org>
Cr-Commit-Position: refs/heads/master@{#489227}
  • Loading branch information
kaznacheev authored and Commit Bot committed Jul 25, 2017
1 parent 50ccafd commit cfd3740
Show file tree
Hide file tree
Showing 15 changed files with 529 additions and 496 deletions.
8 changes: 3 additions & 5 deletions ash/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ component("ash") {
"drag_drop/drag_drop_tracker.h",
"drag_drop/drag_image_view.cc",
"drag_drop/drag_image_view.h",
"fast_ink/fast_ink_points.cc",
"fast_ink/fast_ink_points.h",
"fast_ink/fast_ink_view.cc",
"fast_ink/fast_ink_view.h",
"first_run/desktop_cleaner.cc",
Expand Down Expand Up @@ -190,8 +192,6 @@ component("ash") {
"keyboard/keyboard_ui_observer.h",
"laser/laser_pointer_controller.cc",
"laser/laser_pointer_controller.h",
"laser/laser_pointer_points.cc",
"laser/laser_pointer_points.h",
"laser/laser_pointer_view.cc",
"laser/laser_pointer_view.h",
"laser/laser_segment_utils.cc",
Expand Down Expand Up @@ -1169,6 +1169,7 @@ source_set("common_unittests") {
"autoclick/autoclick_unittest.cc",
"display/display_configuration_controller_unittest.cc",
"drag_drop/drag_image_view_unittest.cc",
"fast_ink/fast_ink_points_unittest.cc",
"first_run/first_run_helper_unittest.cc",
"focus_cycler_unittest.cc",
"frame/caption_buttons/frame_caption_button_container_view_unittest.cc",
Expand All @@ -1178,7 +1179,6 @@ source_set("common_unittests") {
"highlighter/highlighter_gesture_util_unittest.cc",
"ime/ime_controller_unittest.cc",
"laser/laser_pointer_controller_unittest.cc",
"laser/laser_pointer_points_unittest.cc",
"laser/laser_segment_utils_unittest.cc",
"login/lock_screen_controller_unittest.cc",
"login/mock_lock_screen_client.cc",
Expand Down Expand Up @@ -1650,8 +1650,6 @@ static_library("test_support_common") {
"keyboard/test_keyboard_ui.h",
"laser/laser_pointer_controller_test_api.cc",
"laser/laser_pointer_controller_test_api.h",
"laser/laser_pointer_points_test_api.cc",
"laser/laser_pointer_points_test_api.h",
"metrics/task_switch_time_tracker_test_api.cc",
"metrics/task_switch_time_tracker_test_api.h",
"metrics/user_metrics_recorder_test_api.cc",
Expand Down
183 changes: 183 additions & 0 deletions ash/fast_ink/fast_ink_points.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// 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 <limits>

#include "base/containers/adapters.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rect_conversions.h"

namespace ash {

FastInkPoints::FastInkPoints(base::TimeDelta life_duration)
: life_duration_(life_duration) {}

FastInkPoints::~FastInkPoints() {}

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::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);
}
}

void FastInkPoints::Clear() {
points_.clear();
}

gfx::Rect FastInkPoints::GetBoundingBox() const {
if (IsEmpty())
return gfx::Rect();

gfx::PointF min_point = GetOldest().location;
gfx::PointF max_point = GetOldest().location;
for (const FastInkPoint& point : points_) {
min_point.SetToMin(point.location);
max_point.SetToMax(point.location);
}
return gfx::ToEnclosingRect(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 std::deque<FastInkPoints::FastInkPoint>& FastInkPoints::points() const {
return points_;
}

float FastInkPoints::GetFadeoutFactor(int index) const {
DCHECK(!life_duration_.is_zero());
DCHECK(0 <= index && index < GetNumberOfPoints());
base::TimeDelta age = collection_latest_time_ - points_[index].time;
return std::min(age.InMillisecondsF() / life_duration_.InMillisecondsF(),
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::TimeDelta::FromMilliseconds(kPredictionIntervalMs);
base::TimeDelta max_point_interval =
base::TimeDelta::FromMilliseconds(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;
}
// Pad with last point if needed.
std::fill(it, position.end(), last_point_location);

// 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 < arraysize(velocity); ++i)
velocity[i] = position[i] - position[i + 1];

gfx::Vector2dF acceleration[2];
for (size_t i = 0; i < arraysize(acceleration); ++i)
acceleration[i] = velocity[i] - velocity[i + 1];

gfx::Vector2dF jerk = acceleration[0] - acceleration[1];

// 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);

// Always stop at three predicted points as a four point history doesn't
// provide accurate prediction of more points.
if (GetNumberOfPoints() == 3)
break;
}
}

} // namespace ash
75 changes: 75 additions & 0 deletions ash/fast_ink/fast_ink_points.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// 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.

#ifndef ASH_FAST_INK_FAST_INK_POINTS_H_
#define ASH_FAST_INK_FAST_INK_POINTS_H_

#include <deque>
#include <memory>

#include "ash/ash_export.h"
#include "base/macros.h"
#include "base/time/time.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rect.h"

namespace ash {

// FastInkPoints is a helper class used for displaying low-latency palette tools
// It keeps track of the points needed to render the image.
class ASH_EXPORT FastInkPoints {
public:
// Struct to describe each point.
struct FastInkPoint {
gfx::PointF location;
base::TimeTicks time;
};

// Constructor with a parameter to choose the fade out time of the points in
// the collection. Zero means no fadeout.
explicit FastInkPoints(base::TimeDelta life_duration);
~FastInkPoints();

// Adds a point.
void AddPoint(const gfx::PointF& point, const base::TimeTicks& time);
// Updates the collection latest time. Automatically clears points that are
// too old.
void MoveForwardToTime(const base::TimeTicks& latest_time);
// Removes all points.
void Clear();
// Gets the bounding box of the points.
gfx::Rect GetBoundingBox() const;
// Returns the oldest point in the collection.
FastInkPoint GetOldest() const;
// Returns the newest point in the collection.
FastInkPoint GetNewest() const;
// Returns the number of points in the collection.
int GetNumberOfPoints() const;
// Whether there are any points or not.
bool IsEmpty() const;
// Expose the collection so callers can work with the points.
const std::deque<FastInkPoint>& points() const;
// Returns the fadeout factor for a point. This is a value between 0.0 and
// 1.0, where 0.0 corresponds to a recently added point, and 1.0 to a point
// that is about to expire. Do not call this method if |life_duration_| is 0.
float GetFadeoutFactor(int index) const;
// Fills the container with predicted points based on |real_points|.
void Predict(const FastInkPoints& real_points,
const base::TimeTicks& current_time,
base::TimeDelta prediction_duration,
const gfx::Size& screen_size);

private:
const base::TimeDelta life_duration_;
std::deque<FastInkPoint> points_;
// The latest time of the collection of points. This gets updated when new
// points are added or when MoveForwardToTime is called.
base::TimeTicks collection_latest_time_;

DISALLOW_COPY_AND_ASSIGN(FastInkPoints);
};

} // namespace ash

#endif // ASH_FAST_INK_FAST_INK_POINTS_H_
Loading

0 comments on commit cfd3740

Please sign in to comment.