From 91a1ec34bf87c4551eec732cbaa0ad691e6433a2 Mon Sep 17 00:00:00 2001 From: Florin Malita Date: Tue, 5 Nov 2019 14:09:57 -0500 Subject: [PATCH] [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 Reviewed-by: Mike Reed --- modules/skottie/src/SkottieAdapter.cpp | 157 ++--- .../skottie/skottie-gradient-opacity.json | 585 ++++++++++++++++++ 2 files changed, 653 insertions(+), 89 deletions(-) create mode 100644 resources/skottie/skottie-gradient-opacity.json diff --git a/modules/skottie/src/SkottieAdapter.cpp b/modules/skottie/src/SkottieAdapter.cpp index 4faf43b66f..e77deae011 100644 --- a/modules/skottie/src/SkottieAdapter.cpp +++ b/modules/skottie/src/SkottieAdapter.cpp @@ -269,6 +269,16 @@ GradientAdapter::GradientAdapter(sk_sp grad, size_t colorStopCou : fGradient(std::move(grad)) , fColorStopCount(colorStopCount) {} +template +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(fStops.data()); - const auto* end_c = current_c + c_count; - const auto* current_o = reinterpret_cast(end_c); - const auto* end_o = current_o + o_count; + const auto* c_rec = c_count > 0 ? reinterpret_cast(fStops.data()) + : nullptr; + const auto* o_rec = o_count > 0 ? reinterpret_cast(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 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(c_rec, c_end); + } + if (o_pos <= c_pos) { + o_rec = next_rec(o_rec, o_end); + } } stops.shrink_to_fit(); diff --git a/resources/skottie/skottie-gradient-opacity.json b/resources/skottie/skottie-gradient-opacity.json new file mode 100644 index 0000000000..db0057b255 --- /dev/null +++ b/resources/skottie/skottie-gradient-opacity.json @@ -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 +}