Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Impeller] Attempt to minimize jittering of glyphs when scaling text. #56414

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -42943,6 +42943,8 @@ ORIGIN: ../../../flutter/impeller/display_list/paint.cc + ../../../flutter/LICEN
ORIGIN: ../../../flutter/impeller/display_list/paint.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/display_list/skia_conversions.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/display_list/skia_conversions.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/display_list/timing_curve.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/display_list/timing_curve.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/contents/anonymous_contents.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/contents/anonymous_contents.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/entity/contents/atlas_contents.cc + ../../../flutter/LICENSE
Expand Down Expand Up @@ -45805,6 +45807,8 @@ FILE: ../../../flutter/impeller/display_list/paint.cc
FILE: ../../../flutter/impeller/display_list/paint.h
FILE: ../../../flutter/impeller/display_list/skia_conversions.cc
FILE: ../../../flutter/impeller/display_list/skia_conversions.h
FILE: ../../../flutter/impeller/display_list/timing_curve.cc
FILE: ../../../flutter/impeller/display_list/timing_curve.h
FILE: ../../../flutter/impeller/entity/contents/anonymous_contents.cc
FILE: ../../../flutter/impeller/entity/contents/anonymous_contents.h
FILE: ../../../flutter/impeller/entity/contents/atlas_contents.cc
Expand Down
2 changes: 2 additions & 0 deletions impeller/display_list/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ template("display_list_unittests_component") {
"dl_playground.cc",
"dl_playground.h",
"dl_unittests.cc",
"timing_curve.cc",
"timing_curve.h",
]
additional_sources = []
if (defined(invoker.sources)) {
Expand Down
54 changes: 53 additions & 1 deletion impeller/display_list/aiks_dl_text_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <cmath>

#include "display_list/display_list.h"
#include "display_list/dl_blend_mode.h"
#include "display_list/dl_tile_mode.h"
Expand All @@ -13,11 +15,12 @@
#include "flutter/fml/build_config.h"
#include "flutter/impeller/display_list/aiks_unittests.h"
#include "flutter/testing/testing.h"
#include "impeller/base/timing.h"
#include "impeller/display_list/timing_curve.h"
#include "impeller/geometry/matrix.h"
#include "impeller/typographer/backends/skia/text_frame_skia.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkRect.h"

#include "txt/platform.h"

using namespace flutter;
Expand Down Expand Up @@ -167,6 +170,55 @@ TEST_P(AiksTest, CanRenderTextFrameWithFractionScaling) {
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}

TEST_P(AiksTest, ScalingTextFramesDoesntCauseJitterBetweenTextFrames) {
const auto curve =
TimingCurve::SystemTimingCurve(TimingCurve::Type::kEaseInEaseOut);
auto start = Clock::now();
bool flip = false;
auto callback = [&]() -> sk_sp<DisplayList> {
static float font_size = 20;
static float interval = 8.0;
static float zoom = 5.0;

if (AiksTest::ImGuiBegin("Controls", nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::SliderFloat("Interval", &interval, 1.0, 20.0);
ImGui::SliderFloat("Zoom", &zoom, 1.0, 10.0);
ImGui::End();
}

const auto now = Clock::now();
const auto duration = now - start;
auto duration_seconds =
std::chrono::duration_cast<SecondsF>(duration).count();

auto unit_interval = duration_seconds / interval;
if (flip) {
unit_interval = 1.0 - unit_interval;
}
const auto scale = curve.x(unit_interval) * zoom;

if (duration_seconds > interval) {
start = now;
flip = !flip;
}

SkPoint position = SkPoint::Make(10, 100);
DisplayListBuilder builder;
builder.Scale(GetContentScale().x + scale, GetContentScale().y + scale);
if (!RenderTextInCanvasSkia(
GetContext(), builder,
"the quick brown fox jumped over "
"the lazy dog!.?",
"Roboto-Regular.ttf",
{.font_size = font_size, .position = position})) {
return nullptr;
}
return builder.Build();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}

TEST_P(AiksTest, TextFrameSubpixelAlignment) {
// "Random" numbers between 0 and 1. Hardcoded to avoid flakiness in goldens.
std::array<Scalar, 20> phase_offsets = {
Expand Down
135 changes: 135 additions & 0 deletions impeller/display_list/timing_curve.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Copyright 2013 The Flutter 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 "impeller/display_list/timing_curve.h"

namespace impeller::testing {

static inline Scalar TimingCurve_SampleCurve(Scalar a,
Scalar b,
Scalar c,
Scalar t) {
/*
* `a t^3 + b t^2 + c t' expanded using Horner's rule.
*/
return ((a * t + b) * t + c) * t;
}

static inline Scalar TimingCurve_SampleCurveDerivative(Scalar a,
Scalar b,
Scalar c,
Scalar t) {
return (3.0 * a * t + 2.0 * b) * t + c;
}

static inline Scalar TimingCurve_SolveCurveX(Scalar ax,
Scalar bx,
Scalar cx,
Scalar x,
Scalar epsilon) {
Scalar t0 = 0.0;
Scalar t1 = 0.0;
Scalar t2 = 0.0;
Scalar x2 = 0.0;
Scalar d2 = 0.0;
int i = 0;

/*
* Try Newton's method
*/
for (t2 = x, i = 0; i < 8; i++) {
x2 = TimingCurve_SampleCurve(ax, bx, cx, t2) - x;

if (fabs(x2) < epsilon) {
return t2;
}

d2 = TimingCurve_SampleCurveDerivative(ax, bx, cx, t2);

if (fabs(d2) < epsilon) {
break;
}

t2 = t2 - x2 / d2;
}

/*
* Fall back to bisection
*/
t0 = 0.0;
t1 = 1.0;
t2 = x;

if (t2 < t0) {
return t0;
}

if (t2 > t1) {
return t1;
}

while (t0 < t1) {
x2 = TimingCurve_SampleCurve(ax, bx, cx, t2);

if (fabs(x2 - x) < epsilon) {
return t2;
}

if (x > x2) {
t0 = t2;
} else {
t1 = t2;
}

t2 = (t1 - t0) * 0.5 + t0;
}

/*
* Failure
*/
return t2;
}

static inline Scalar TimingCurve_SolveX(Scalar ax,
Scalar bx,
Scalar cx,
Scalar ay,
Scalar by,
Scalar cy,
Scalar x,
Scalar epsilon) {
const Scalar xSolution = TimingCurve_SolveCurveX(ax, bx, cx, x, epsilon);
return TimingCurve_SampleCurve(ay, by, cy, xSolution);
}

TimingCurve TimingCurve::SystemTimingCurve(Type type) {
switch (type) {
case Type::kLinear:
return TimingCurve({0.0, 0.0}, {1.0, 1.0});
case Type::kEaseIn:
return TimingCurve({0.42, 0.0}, {1.0, 1.0});
case Type::kEaseOut:
return TimingCurve({0.0, 0.0}, {0.58, 1.0});
case Type::kEaseInEaseOut:
return TimingCurve({0.42, 0.0}, {0.58, 1.0});
}

return TimingCurve({0.0, 0.0}, {1.0, 1.0});
}

TimingCurve::TimingCurve(const Point& c1, const Point& c2) {
cx_ = 3.0 * c1.x;
bx_ = 3.0 * (c2.x - c1.x) - cx_;
ax_ = 1.0 - cx_ - bx_;

cy_ = 3.0 * c1.y;
by_ = 3.0 * (c2.y - c1.y) - cy_;
ay_ = 1.0 - cy_ - by_;
}

Scalar TimingCurve::x(Scalar t) const {
return TimingCurve_SolveX(ax_, bx_, cx_, ay_, by_, cy_, t, 1e-3);
}

} // namespace impeller::testing
38 changes: 38 additions & 0 deletions impeller/display_list/timing_curve.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2013 The Flutter 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 FLUTTER_IMPELLER_DISPLAY_LIST_TIMING_CURVE_H_
#define FLUTTER_IMPELLER_DISPLAY_LIST_TIMING_CURVE_H_

#include "impeller/geometry/point.h"

namespace impeller::testing {

class TimingCurve {
public:
enum class Type {
kLinear,
kEaseIn,
kEaseOut,
kEaseInEaseOut,
};

static TimingCurve SystemTimingCurve(Type type);

Scalar x(Scalar t) const;

private:
Scalar ax_ = 0.0;
Scalar bx_ = 0.0;
Scalar cx_ = 0.0;
Scalar ay_ = 0.0;
Scalar by_ = 0.0;
Scalar cy_ = 0.0;

TimingCurve(const Point& c1, const Point& c2);
};

} // namespace impeller::testing

#endif // FLUTTER_IMPELLER_DISPLAY_LIST_TIMING_CURVE_H_
Loading