[skottie] Streamlined gradient stop merger

Refactor as a single interpolating loop, based on careful selection
of lerp coefficients.

Change-Id: I58786cddb2f042b53dcbac80c2346736429be102
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/252858
Commit-Queue: Florin Malita <fmalita@chromium.org>
Reviewed-by: Mike Reed <reed@google.com>
This commit is contained in:
Florin Malita 2019-11-05 14:09:57 -05:00 committed by Skia Commit-Bot
parent 07b3994556
commit 91a1ec34bf
2 changed files with 653 additions and 89 deletions

View File

@ -269,6 +269,16 @@ GradientAdapter::GradientAdapter(sk_sp<sksg::Gradient> grad, size_t colorStopCou
: fGradient(std::move(grad))
, fColorStopCount(colorStopCount) {}
template <typename T>
static inline const T* next_rec(const T* rec, const T* end_rec) {
if (!rec) return nullptr;
SkASSERT(rec < end_rec);
rec++;
return rec < end_rec ? rec : nullptr;
};
void GradientAdapter::apply() {
this->onApply();
@ -298,100 +308,69 @@ void GradientAdapter::apply() {
return;
}
const auto* current_c = reinterpret_cast<const ColorRec*>(fStops.data());
const auto* end_c = current_c + c_count;
const auto* current_o = reinterpret_cast<const OpacityRec*>(end_c);
const auto* end_o = current_o + o_count;
const auto* c_rec = c_count > 0 ? reinterpret_cast<const ColorRec*>(fStops.data())
: nullptr;
const auto* o_rec = o_count > 0 ? reinterpret_cast<const OpacityRec*>(fStops.data() + c_size)
: nullptr;
const auto* c_end = c_rec + c_count;
const auto* o_end = o_rec + o_count;
sksg::Gradient::ColorStop prev_stop = { 0.0f, SkColors::kBlack };
if (current_c < end_c) {
prev_stop.fColor = SkColor4f{ current_c->r, current_c->g, current_c->b, 1.0 };
}
if (current_o < end_o) {
prev_stop.fColor.fA = current_o->a;
}
auto lerp = [](float a, float b, float t) { return a + t * (b - a); };
auto next_stop = [&]() -> sksg::Gradient::ColorStop {
const uint8_t has_color_stop = SkToU8(current_c < end_c),
has_opacity_stop = SkToU8(current_o < end_o);
switch (has_color_stop | (has_opacity_stop << 1)) {
case 0x01: {
// Color-only stop.
sksg::Gradient::ColorStop cs{
current_c->t,
SkColor4f{ current_c->r, current_c->g, current_c->b, prev_stop.fColor.fA }
};
current_c++;
return cs;
}
case 0x02: {
// Opacity-only stop.
sksg::Gradient::ColorStop cs{
current_o->t,
SkColor4f{ prev_stop.fColor.fR,
prev_stop.fColor.fG,
prev_stop.fColor.fB,
current_o->a }
};
current_o++;
return cs;
}
case 0x03: {
// Separate color and opacity stops.
// Merge-sort the two arrays, LERP-ing intermediate channel values as needed.
auto c = SkColor4f{ current_c->r, current_c->g, current_c->b, current_o->a };
auto t_rgb = current_c->t,
t_a = current_o->t;
if (SkScalarNearlyEqual(t_rgb, t_a)) {
// Coincident color and opacity stops: no LERP needed, consume both.
current_c++;
current_o++;
return { t_rgb, c };
}
if (t_rgb < t_a) {
// Color stop followed by opacity stop: LERP alpha, consume the color stop.
const auto rel_t = SkTPin(sk_ieee_float_divide(t_rgb - prev_stop.fPosition,
t_a - prev_stop.fPosition),
0.0f, 1.0f);
c.fA = lerp(prev_stop.fColor.fA, c.fA, rel_t);
current_c++;
return { t_rgb, c };
} else {
// Opacity stop followed by color stop: LERP r/g/b, consume the opacity stop.
const auto rel_t = SkTPin(sk_ieee_float_divide(t_a - prev_stop.fPosition,
t_rgb - prev_stop.fPosition),
0.0f, 1.0f);
c.fR = lerp(prev_stop.fColor.fR, c.fR, rel_t);
c.fG = lerp(prev_stop.fColor.fG, c.fG, rel_t);
c.fB = lerp(prev_stop.fColor.fB, c.fB, rel_t);
current_o++;
return { t_a, c };
}
}
default: SkUNREACHABLE;
}
};
sksg::Gradient::ColorStop current_stop = {
0.0f, {
c_rec ? c_rec->r : 0,
c_rec ? c_rec->g : 0,
c_rec ? c_rec->b : 0,
o_rec ? o_rec->a : 1,
}};
std::vector<sksg::Gradient::ColorStop> stops;
stops.reserve(c_count);
while (current_c < end_c || current_o < end_o) {
prev_stop = next_stop();
stops.push_back(prev_stop);
// Merge-sort the color and opacity stops, LERP-ing intermediate channel values as needed.
while (c_rec || o_rec) {
// After exhausting one of color recs / opacity recs, continue propagating the last
// computed values (as if they were specified at the current position).
const auto& cs = c_rec
? *c_rec
: ColorRec{ o_rec->t,
current_stop.fColor.fR,
current_stop.fColor.fG,
current_stop.fColor.fB };
const auto& os = o_rec
? *o_rec
: OpacityRec{ c_rec->t, current_stop.fColor.fA };
// Compute component lerp coefficients based on the relative position of the stops
// being considered. The idea is to select the smaller-pos stop, use its own properties
// as specified (lerp with t == 1), and lerp (with t < 1) the properties from the
// larger-pos stop against the previously computed gradient stop values.
const auto c_pos = std::max(cs.t, current_stop.fPosition),
o_pos = std::max(os.t, current_stop.fPosition),
c_pos_rel = c_pos - current_stop.fPosition,
o_pos_rel = o_pos - current_stop.fPosition,
t_c = SkTPin(sk_ieee_float_divide(o_pos_rel, c_pos_rel), 0.0f, 1.0f),
t_o = SkTPin(sk_ieee_float_divide(c_pos_rel, o_pos_rel), 0.0f, 1.0f);
auto lerp = [](float a, float b, float t) { return a + t * (b - a); };
current_stop = {
std::min(c_pos, o_pos),
{
lerp(current_stop.fColor.fR, cs.r, t_c ),
lerp(current_stop.fColor.fG, cs.g, t_c ),
lerp(current_stop.fColor.fB, cs.b, t_c ),
lerp(current_stop.fColor.fA, os.a, t_o)
}
};
stops.push_back(current_stop);
// Consume one of, or both (for coincident positions) color/opacity stops.
if (c_pos <= o_pos) {
c_rec = next_rec<ColorRec>(c_rec, c_end);
}
if (o_pos <= c_pos) {
o_rec = next_rec<OpacityRec>(o_rec, o_end);
}
}
stops.shrink_to_fit();

View File

@ -0,0 +1,585 @@
{
"assets": [],
"ddd": 0,
"fr": 60,
"h": 500,
"ip": 0,
"layers": [
{
"ao": 0,
"bm": 0,
"ddd": 0,
"ind": 1,
"ip": 0,
"ks": {
"a": {
"a": 0,
"ix": 1,
"k": [
0,
0,
0
]
},
"o": {
"a": 0,
"ix": 11,
"k": 100
},
"p": {
"a": 0,
"ix": 2,
"k": [
250,
75,
0
]
},
"r": {
"a": 0,
"ix": 10,
"k": 0
},
"s": {
"a": 0,
"ix": 6,
"k": [
100,
100,
100
]
}
},
"nm": "Shape Layer 1",
"op": 301,
"shapes": [
{
"d": 1,
"hd": false,
"mn": "ADBE Vector Shape - Rect",
"nm": "Rectangle Path 1",
"p": {
"a": 0,
"ix": 3,
"k": [
0,
0
]
},
"r": {
"a": 0,
"ix": 4,
"k": 0
},
"s": {
"a": 0,
"ix": 2,
"k": [
400,
100
]
},
"ty": "rc"
},
{
"bm": 0,
"e": {
"a": 0,
"ix": 6,
"k": [
200,
0
]
},
"g": {
"k": {
"a": 0,
"ix": 9,
"k": [
0.50, 0, 1, 0,
0.50, 1, 0, 0,
0.00, 1,
0.25, 0,
0.50, 1,
0.75, 0,
1.00, 1
]
},
"p": 2
},
"hd": false,
"mn": "ADBE Vector Graphic - G-Fill",
"nm": "Gradient Fill 1",
"o": {
"a": 0,
"ix": 10,
"k": 100
},
"r": 1,
"s": {
"a": 0,
"ix": 5,
"k": [
-200,
0
]
},
"t": 1,
"ty": "gf"
}
],
"sr": 1,
"st": 0,
"ty": 4
},
{
"ao": 0,
"bm": 0,
"ddd": 0,
"ind": 2,
"ip": 0,
"ks": {
"a": {
"a": 0,
"ix": 1,
"k": [
0,
0,
0
]
},
"o": {
"a": 0,
"ix": 11,
"k": 100
},
"p": {
"a": 0,
"ix": 2,
"k": [
250,
190,
0
]
},
"r": {
"a": 0,
"ix": 10,
"k": 0
},
"s": {
"a": 0,
"ix": 6,
"k": [
100,
100,
100
]
}
},
"nm": "Shape Layer 2",
"op": 301,
"shapes": [
{
"d": 1,
"hd": false,
"mn": "ADBE Vector Shape - Rect",
"nm": "Rectangle Path 1",
"p": {
"a": 0,
"ix": 3,
"k": [
0,
0
]
},
"r": {
"a": 0,
"ix": 4,
"k": 0
},
"s": {
"a": 0,
"ix": 2,
"k": [
400,
100
]
},
"ty": "rc"
},
{
"bm": 0,
"e": {
"a": 0,
"ix": 6,
"k": [
200,
0
]
},
"g": {
"k": {
"a": 0,
"ix": 9,
"k": [
0.00, 0, 1, 0,
0.25, 1, 0, 0,
0.50, 0, 0, 1,
0.75, 1, 0, 0,
1.00, 0, 1, 0,
0.50, 0.35,
0.50, 1.00
]
},
"p": 5
},
"hd": false,
"mn": "ADBE Vector Graphic - G-Fill",
"nm": "Gradient Fill 1",
"o": {
"a": 0,
"ix": 10,
"k": 100
},
"r": 1,
"s": {
"a": 0,
"ix": 5,
"k": [
-200,
0
]
},
"t": 1,
"ty": "gf"
}
],
"sr": 1,
"st": 0,
"ty": 4
},
{
"ao": 0,
"bm": 0,
"ddd": 0,
"ind": 3,
"ip": 0,
"ks": {
"a": {
"a": 0,
"ix": 1,
"k": [
0,
0,
0
]
},
"o": {
"a": 0,
"ix": 11,
"k": 100
},
"p": {
"a": 0,
"ix": 2,
"k": [
250,
305,
0
]
},
"r": {
"a": 0,
"ix": 10,
"k": 0
},
"s": {
"a": 0,
"ix": 6,
"k": [
100,
100,
100
]
}
},
"nm": "Shape Layer 3",
"op": 301,
"shapes": [
{
"d": 1,
"hd": false,
"mn": "ADBE Vector Shape - Rect",
"nm": "Rectangle Path 1",
"p": {
"a": 0,
"ix": 3,
"k": [
0,
0
]
},
"r": {
"a": 0,
"ix": 4,
"k": 0
},
"s": {
"a": 0,
"ix": 2,
"k": [
400,
100
]
},
"ty": "rc"
},
{
"bm": 0,
"e": {
"a": 0,
"ix": 6,
"k": [
200,
0
]
},
"g": {
"k": {
"a": 0,
"ix": 9,
"k": [
0.00, 0, 1, 0,
0.25, 1, 0, 0,
0.50, 0, 0, 1,
0.75, 1, 0, 0,
1.00, 0, 1, 0,
0.25, 1,
0.37, 0,
0.50, 1,
0.62, 0,
0.75, 1
]
},
"p": 5
},
"hd": false,
"mn": "ADBE Vector Graphic - G-Fill",
"nm": "Gradient Fill 1",
"o": {
"a": 0,
"ix": 10,
"k": 100
},
"r": 1,
"s": {
"a": 0,
"ix": 5,
"k": [
-200,
0
]
},
"t": 1,
"ty": "gf"
}
],
"sr": 1,
"st": 0,
"ty": 4
},
{
"ao": 0,
"bm": 0,
"ddd": 0,
"ind": 4,
"ip": 0,
"ks": {
"a": {
"a": 0,
"ix": 1,
"k": [
0,
0,
0
]
},
"o": {
"a": 0,
"ix": 11,
"k": 100
},
"p": {
"a": 0,
"ix": 2,
"k": [
250,
425,
0
]
},
"r": {
"a": 0,
"ix": 10,
"k": 0
},
"s": {
"a": 0,
"ix": 6,
"k": [
100,
100,
100
]
}
},
"nm": "Shape Layer 4",
"op": 301,
"shapes": [
{
"d": 1,
"hd": false,
"mn": "ADBE Vector Shape - Rect",
"nm": "Rectangle Path 1",
"p": {
"a": 0,
"ix": 3,
"k": [
0,
0
]
},
"r": {
"a": 0,
"ix": 4,
"k": 0
},
"s": {
"a": 0,
"ix": 2,
"k": [
400,
100
]
},
"ty": "rc"
},
{
"bm": 0,
"e": {
"a": 0,
"ix": 6,
"k": [
200,
0
]
},
"g": {
"k": {
"a": 0,
"ix": 9,
"k": [
0.25, 0, 1, 0,
0.37, 1, 0, 0,
0.50, 0, 0, 1,
0.62, 1, 0, 0,
0.75, 0, 1, 0,
0.00, 1,
0.25, 0,
0.50, 1,
0.75, 0,
1.00, 1
]
},
"p": 5
},
"hd": false,
"mn": "ADBE Vector Graphic - G-Fill",
"nm": "Gradient Fill 1",
"o": {
"a": 0,
"ix": 10,
"k": 100
},
"r": 1,
"s": {
"a": 0,
"ix": 5,
"k": [
-200,
0
]
},
"t": 1,
"ty": "gf"
}
],
"sr": 1,
"st": 0,
"ty": 4
},
{
"ao": 0,
"bm": 0,
"ddd": 0,
"ind": 5,
"ip": 0,
"ks": {
"a": {
"a": 0,
"ix": 1,
"k": [
250,
250,
0
]
},
"o": {
"a": 0,
"ix": 11,
"k": 100
},
"p": {
"a": 0,
"ix": 2,
"k": [
250,
250,
0
]
},
"r": {
"a": 0,
"ix": 10,
"k": 0
},
"s": {
"a": 0,
"ix": 6,
"k": [
100,
100,
100
]
}
},
"nm": "White Solid 1",
"op": 301,
"sc": "#ffffff",
"sh": 500,
"sr": 1,
"st": 0,
"sw": 500,
"ty": 1
}
],
"markers": [],
"nm": "gradient opacity",
"op": 301,
"v": "5.5.5",
"w": 500
}