diff options
author | MacDue <macdue@dueutil.tech> | 2022-08-11 18:37:29 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-08-12 12:24:15 +0200 |
commit | b205cf967d7c5b4a3ddd79205f9d24dc5554c449 (patch) | |
tree | e3bbbcee387d27ffc9f94068b9274e4cfc7a9a1c /Userland | |
parent | 4f83b70c7fbc317dc450a956e101b0e20532c97f (diff) | |
download | serenity-b205cf967d7c5b4a3ddd79205f9d24dc5554c449.zip |
LibWeb: Implement `linear-gradient()` transition hints
These allow you to specify the point were the gradient transitions
from one color to the next (without a transition hint the transition
occurs at the point 50% of the way between the two colors).
There is a little bit of guesswork in this implementation as the
specification left out how hints work with the color stop fixup,
though it appears that they are treated the same as color stops.
Diffstat (limited to 'Userland')
-rw-r--r-- | Userland/Libraries/LibWeb/Painting/GradientPainting.cpp | 74 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/Painting/GradientPainting.h | 1 |
2 files changed, 55 insertions, 20 deletions
diff --git a/Userland/Libraries/LibWeb/Painting/GradientPainting.cpp b/Userland/Libraries/LibWeb/Painting/GradientPainting.cpp index 12b3aaafe7..db39eea7c5 100644 --- a/Userland/Libraries/LibWeb/Painting/GradientPainting.cpp +++ b/Userland/Libraries/LibWeb/Painting/GradientPainting.cpp @@ -58,28 +58,37 @@ LinearGradientData resolve_linear_gradient_data(Layout::Node const& node, Gfx::F ? last_stop.length->resolved(node, gradient_length).to_px(node) : gradient_length_px; - // FIXME: Handle transition hints // 2. If a color stop or transition hint has a position that is less than the // specified position of any color stop or transition hint before it in the list, // set its position to be equal to the largest specified position of any color stop // or transition hint before it. - auto max_previous_color_stop = resolved_color_stops[0].position; + auto max_previous_color_stop_or_hint = resolved_color_stops[0].position; for (size_t i = 1; i < color_stop_list.size(); i++) { auto& stop = color_stop_list[i]; + if (stop.transition_hint.has_value()) { + float value = stop.transition_hint->value.resolved(node, gradient_length).to_px(node); + value = max(value, max_previous_color_stop_or_hint); + resolved_color_stops[i].transition_hint = value; + max_previous_color_stop_or_hint = value; + } if (stop.color_stop.length.has_value()) { float value = stop.color_stop.length->resolved(node, gradient_length).to_px(node); - value = max(value, max_previous_color_stop); + value = max(value, max_previous_color_stop_or_hint); resolved_color_stops[i].position = value; - max_previous_color_stop = value; + max_previous_color_stop_or_hint = value; } } // 3. If any color stop still does not have a position, then, for each run of adjacent color stops // without positions, set their positions so that they are evenly spaced between the preceding // and following color stops with positions. + // Note: Though not mentioned anywhere in the specification transition hints are counted as "color stops with positions". size_t i = 1; auto find_run_end = [&] { - while (i < color_stop_list.size() - 1 && !color_stop_list[i].color_stop.length.has_value()) { + auto color_stop_has_position = [](auto& color_stop) { + return color_stop.transition_hint.has_value() || color_stop.color_stop.length.has_value(); + }; + while (i < color_stop_list.size() - 1 && !color_stop_has_position(color_stop_list[i])) { i++; } return i; @@ -88,9 +97,9 @@ LinearGradientData resolve_linear_gradient_data(Layout::Node const& node, Gfx::F auto& stop = color_stop_list[i]; if (!stop.color_stop.length.has_value()) { auto run_start = i - 1; + auto start_position = resolved_color_stops[i++].transition_hint.value_or(resolved_color_stops[run_start].position); auto run_end = find_run_end(); - auto start_position = resolved_color_stops[run_start].position; - auto end_position = resolved_color_stops[run_end].position; + auto end_position = resolved_color_stops[run_end].transition_hint.value_or(resolved_color_stops[run_end].position); auto spacing = (end_position - start_position) / (run_end - run_start); for (auto j = run_start + 1; j < run_end; j++) { resolved_color_stops[j].position = start_position + (j - run_start) * spacing; @@ -99,6 +108,18 @@ LinearGradientData resolve_linear_gradient_data(Layout::Node const& node, Gfx::F i++; } + // Determine the location of the transition hint as a percentage of the distance between the two color stops, + // denoted as a number between 0 and 1, where 0 indicates the hint is placed right on the first color stop, + // and 1 indicates the hint is placed right on the second color stop. + for (size_t i = 1; i < resolved_color_stops.size(); i++) { + auto& color_stop = resolved_color_stops[i]; + auto& previous_color_stop = resolved_color_stops[i - 1]; + if (color_stop.transition_hint.has_value()) { + auto stop_length = color_stop.position - previous_color_stop.position; + color_stop.transition_hint = stop_length > 0 ? (*color_stop.transition_hint - previous_color_stop.position) / stop_length : 0; + } + } + return { gradient_angle, resolved_color_stops }; } @@ -148,13 +169,26 @@ void paint_linear_gradient(PaintContext& context, Gfx::IntRect const& gradient_r // Rotate gradient line to be horizontal auto rotated_start_point_x = start_point.x() * cos_angle - start_point.y() * -sin_angle; - // FIXME: Handle transition hint interpolation - auto linear_step = [](float min, float max, float value) -> float { - if (value < min) - return 0.; - if (value > max) - return 1.; - return (value - min) / (max - min); + auto color_stop_step = [&](auto& previous_stop, auto& next_stop, float position) -> float { + if (position < previous_stop.position) + return 0; + if (position > next_stop.position) + return 1; + // For any given point between the two color stops, + // determine the pointโs location as a percentage of the distance between the two color stops. + // Let this percentage be P. + auto p = (position - previous_stop.position) / (next_stop.position - previous_stop.position); + if (!next_stop.transition_hint.has_value()) + return p; + if (*next_stop.transition_hint >= 1) + return 0; + if (*next_stop.transition_hint <= 0) + return 1; + // Let C, the color weighting at that point, be equal to P^(logH(.5)). + auto c = AK::pow(p, AK::log<float>(0.5) / AK::log(*next_stop.transition_hint)); + // The color at that point is then a linear blend between the colors of the two color stops, + // blending (1 - C) of the first stop and C of the second stop. + return c; }; Vector<Gfx::Color, 1024> gradient_line_colors; @@ -165,17 +199,17 @@ void paint_linear_gradient(PaintContext& context, Gfx::IntRect const& gradient_r Gfx::Color gradient_color = color_mix( color_stops[0].color, color_stops[1].color, - linear_step( - color_stops[0].position, - color_stops[1].position, + color_stop_step( + color_stops[0], + color_stops[1], loc)); for (size_t i = 1; i < color_stops.size() - 1; i++) { gradient_color = color_mix( gradient_color, color_stops[i + 1].color, - linear_step( - color_stops[i].position, - color_stops[i + 1].position, + color_stop_step( + color_stops[i], + color_stops[i + 1], loc)); } gradient_line_colors[loc] = gradient_color; diff --git a/Userland/Libraries/LibWeb/Painting/GradientPainting.h b/Userland/Libraries/LibWeb/Painting/GradientPainting.h index 9dc13b09f5..3a5da9d4e4 100644 --- a/Userland/Libraries/LibWeb/Painting/GradientPainting.h +++ b/Userland/Libraries/LibWeb/Painting/GradientPainting.h @@ -17,6 +17,7 @@ namespace Web::Painting { struct ColorStop { Gfx::Color color; float position = 0; + Optional<float> transition_hint = {}; }; using ColorStopList = Vector<ColorStop, 4>; |