Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -196,13 +196,16 @@ struct paint_z_index_update final {

bool style_establishes_atomic_stacking_context(const dom_node& node) noexcept
{
const auto& effects = node.style.textual().effect_values;
const auto filter = effects.find("filter");
return node.style.position == position_mode::fixed
|| node.style.position == position_mode::sticky
|| (node.style.position != position_mode::normal
&& !node.style.z_index_auto)
|| node.style.contain_stacking_context
|| node.style.opacity < 0.999F
|| node.style.transform_stacking_context;
|| node.style.transform_stacking_context
|| (filter != effects.end() && filter->second != "none");
}

paint_z_index_update update_paint_z_index(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,40 @@ void native_document::append_scene(
|| opacity <= 0.001F) {
return;
}
const auto resolve_brightness_filter = [&]() -> std::optional<float> {
const auto& effects = node.style.textual().effect_values;
const auto known = effects.find("filter");
if (known == effects.end()) return std::nullopt;
auto value = std::string_view{known->second};
constexpr std::string_view prefix = "brightness(";
if (value.size() < prefix.size() + 1U || value.back() != ')') return std::nullopt;
for (size_t index = 0; index < prefix.size(); ++index) {
const auto character = static_cast<unsigned char>(value[index]);
const auto lower = character >= 'A' && character <= 'Z'
? static_cast<char>(character - 'A' + 'a')
: static_cast<char>(character);
if (lower != prefix[index]) return std::nullopt;
}
value.remove_prefix(prefix.size());
value.remove_suffix(1U);
while (!value.empty()
&& std::isspace(static_cast<unsigned char>(value.front()))) {
value.remove_prefix(1U);
}
while (!value.empty()
&& std::isspace(static_cast<unsigned char>(value.back()))) {
value.remove_suffix(1U);
}
const auto parsed = css::parse_ascii_number_prefix(value);
if (!parsed.has_value() || parsed->value < 0) return std::nullopt;
const auto suffix = value.substr(parsed->consumed);
const auto amount = suffix.empty()
? parsed->value
: suffix == "%" ? parsed->value / 100.0F : -1.0F;
return amount >= 0 && amount <= 10.0F
? std::optional<float>{amount} : std::nullopt;
};
const auto brightness_filter = resolve_brightness_filter();
const auto has_opacity_group = opacity < 0.999F;
if (has_opacity_group) {
commands.push_back(webscene_scene_command{
Expand All @@ -354,6 +388,22 @@ void native_document::append_scene(
255L)),
node.id});
}
if (brightness_filter.has_value()) {
commands.push_back(webscene_scene_command{
30U,
1U << 31U,
node.layout.x,
node.layout.y,
node.layout.width,
node.layout.height,
255U,
node.id,
0,
0,
0,
0,
*brightness_filter});
}
const auto resolve_transform_origin = [](css_length value, float available, float fallback) {
if (value.unit == length_unit::pixels) return value.value;
if (value.unit == length_unit::percent) {
Expand Down Expand Up @@ -1914,6 +1964,11 @@ void native_document::append_scene(
if (has_scale_transform) {
commands.push_back(webscene_scene_command{16U, 0U, 0, 0, 0, 0, 0U, node.id});
}
if (brightness_filter.has_value()) {
commands.push_back(webscene_scene_command{
31U, 0U, node.layout.x, node.layout.y,
node.layout.width, node.layout.height, 0U, node.id});
}
if (has_opacity_group) {
commands.push_back(webscene_scene_command{
31U, 0U, node.layout.x, node.layout.y,
Expand Down Expand Up @@ -2266,6 +2321,11 @@ void native_document::append_scene(
if (has_scale_transform) {
commands.push_back(webscene_scene_command{16U, 0U, 0, 0, 0, 0, 0U, node.id});
}
if (brightness_filter.has_value()) {
commands.push_back(webscene_scene_command{
31U, 0U, node.layout.x, node.layout.y,
node.layout.width, node.layout.height, 0U, node.id});
}
if (has_opacity_group) {
commands.push_back(webscene_scene_command{
31U, 0U, node.layout.x, node.layout.y,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ typedef struct webscene_scene_header {
// Clip kind 12 uses flags bit 31 to select an SVG path stored in the indexed
// scene string; the remaining bits are its string index. A zero flag retains
// the rounded-rectangle fields used by existing producers and presenters.
// Group kind 30 uses flags bit 31 to select a foreground brightness filter;
// stroke_width carries its non-negative multiplier. A zero flag retains the
// opacity-group alpha stored in the low byte of rgba.
typedef struct webscene_scene_command {
uint32_t kind;
uint32_t flags;
Expand Down
42 changes: 33 additions & 9 deletions src/WebScene.Backend.Avalonia/NativeCanvasSceneRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ internal sealed unsafe partial class NativeCanvasSceneRenderer
private const uint OffscreenCanvasLayer = 1u << 31;
private const uint DomPolygonClipResource = 1u << 31;
private const uint DomPolygonClipIndexMask = ~DomPolygonClipResource;
private const uint DomBrightnessFilter = 1u << 31;

private readonly Dictionary<uint, RetainedLayer> s_layers = new();
private readonly List<RetainedLayer> s_orderedLayers = [];
Expand Down Expand Up @@ -646,13 +647,8 @@ private static bool ValidateLayer(NativeSceneView* view, in NativeCanvasLayer la
switch (command.Kind)
{
case 30:
opacity.Color = new SKColor(
255,
255,
255,
(byte)(command.Rgba & 0xff));
backdrop.SaveLayer(opacity);
overlay.SaveLayer(opacity);
SaveDomGroup(backdrop, command, opacity);
SaveDomGroup(overlay, command, opacity);
break;
case 31:
backdrop.Restore();
Expand Down Expand Up @@ -835,6 +831,35 @@ private static void ApplyScale(SKCanvas canvas, in SceneCommand command)
canvas.Translate(-command.X, -command.Y);
}

private static void SaveDomGroup(
SKCanvas canvas,
in SceneCommand command,
SKPaint paint)
{
paint.Color = new SKColor(
255,
255,
255,
(command.Flags & DomBrightnessFilter) != 0
? (byte)255 : (byte)(command.Rgba & 0xff));
if ((command.Flags & DomBrightnessFilter) == 0)
{
paint.ColorFilter = null;
canvas.SaveLayer(paint);
return;
}
var amount = Math.Max(0, command.StrokeWidth);
using var filter = SKColorFilter.CreateColorMatrix([
amount, 0, 0, 0, 0,
0, amount, 0, 0, 0,
0, 0, amount, 0, 0,
0, 0, 0, 1, 0
]);
paint.ColorFilter = filter;
canvas.SaveLayer(paint);
paint.ColorFilter = null;
}

private static void ApplyRotation(SKCanvas canvas, in SceneCommand command)
{
canvas.Save();
Expand Down Expand Up @@ -3786,8 +3811,7 @@ private void RenderOrderedGpuDom(SKCanvas canvas, Action<uint, SKRect>? drawGpuI
case 15: ApplyScale(canvas, command); break;
case 19: ApplyRotation(canvas, command); break;
case 30:
opacity.Color = new SKColor(255, 255, 255, (byte)(command.Rgba & 255));
canvas.SaveLayer(opacity); break;
SaveDomGroup(canvas, command, opacity); break;
case 13: case 16: case 20: case 31:
// Never allow an invalid stream to pop the host's state.
if (canvas.SaveCount <= save + 1) throw new InvalidOperationException("Unbalanced GPU scene state.");
Expand Down
22 changes: 17 additions & 5 deletions src/WebScene.Backend.Flutter/lib/src/scene_projector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const int _layerRemove = 2;
const int _canvasEvenOdd = 1 << 16;
const int _domPolygonClipResource = 1 << 31;
const int _domPolygonClipIndexMask = _domPolygonClipResource - 1;
const int _domBrightnessFilter = 1 << 31;

final class SceneApplyResult {
const SceneApplyResult({
Expand Down Expand Up @@ -234,11 +235,7 @@ final class WebSceneSceneProjector extends ChangeNotifier {
final command = scene.commands[index];
switch (command.kind) {
case 30:
canvas.saveLayer(
null,
ui.Paint()
..color = ui.Color.fromARGB(command.rgba & 0xff, 255, 255, 255),
);
canvas.saveLayer(null, _domGroupPaint(command));
case 31:
canvas.restore();
case 15:
Expand Down Expand Up @@ -961,6 +958,21 @@ final class WebSceneSceneProjector extends ChangeNotifier {
}
}

static ui.Paint _domGroupPaint(WebSceneSceneCommand command) {
if (command.flags & _domBrightnessFilter == 0) {
return ui.Paint()
..color = ui.Color.fromARGB(command.rgba & 0xff, 255, 255, 255);
}
final amount = command.strokeWidth.clamp(0.0, double.infinity).toDouble();
return ui.Paint()
..colorFilter = ui.ColorFilter.matrix([
amount, 0, 0, 0, 0,
0, amount, 0, 0, 0,
0, 0, amount, 0, 0,
0, 0, 0, 1, 0,
]);
}

static String _domString(WebSceneSceneView scene, int index) =>
_stringAt(scene, index);

Expand Down
Loading