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 @@ -339,21 +339,22 @@ void native_document::append_scene(
|| opacity <= 0.001F) {
return;
}
const auto resolve_brightness_filter = [&]() -> std::optional<float> {
const auto resolve_single_filter = [&](std::string_view name, float maximum)
-> 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) {
if (value.size() < name.size() + 2U
|| value[name.size()] != '(' || value.back() != ')') return std::nullopt;
for (size_t index = 0; index < name.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;
if (lower != name[index]) return std::nullopt;
}
value.remove_prefix(prefix.size());
value.remove_prefix(name.size() + 1U);
value.remove_suffix(1U);
while (!value.empty()
&& std::isspace(static_cast<unsigned char>(value.front()))) {
Expand All @@ -369,10 +370,11 @@ void native_document::append_scene(
const auto amount = suffix.empty()
? parsed->value
: suffix == "%" ? parsed->value / 100.0F : -1.0F;
return amount >= 0 && amount <= 10.0F
return amount >= 0 && amount <= maximum
? std::optional<float>{amount} : std::nullopt;
};
const auto brightness_filter = resolve_brightness_filter();
const auto brightness_filter = resolve_single_filter("brightness", 10.0F);
const auto grayscale_filter = resolve_single_filter("grayscale", 1.0F);
const auto has_opacity_group = opacity < 0.999F;
if (has_opacity_group) {
commands.push_back(webscene_scene_command{
Expand Down Expand Up @@ -404,6 +406,22 @@ void native_document::append_scene(
0,
*brightness_filter});
}
if (grayscale_filter.has_value()) {
commands.push_back(webscene_scene_command{
30U,
1U << 30U,
node.layout.x,
node.layout.y,
node.layout.width,
node.layout.height,
255U,
node.id,
0,
0,
0,
0,
*grayscale_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 @@ -1964,6 +1982,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 (grayscale_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 (brightness_filter.has_value()) {
commands.push_back(webscene_scene_command{
31U, 0U, node.layout.x, node.layout.y,
Expand Down Expand Up @@ -2321,6 +2344,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 (grayscale_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 (brightness_filter.has_value()) {
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,9 +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.
// Group kind 30 uses flags bit 31 for brightness and bit 30 for grayscale;
// stroke_width carries the bounded non-negative amount. 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
26 changes: 18 additions & 8 deletions src/WebScene.Backend.Avalonia/NativeCanvasSceneRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ internal sealed unsafe partial class NativeCanvasSceneRenderer
private const uint DomPolygonClipResource = 1u << 31;
private const uint DomPolygonClipIndexMask = ~DomPolygonClipResource;
private const uint DomBrightnessFilter = 1u << 31;
private const uint DomGrayscaleFilter = 1u << 30;
private const uint DomColorFilterMask = DomBrightnessFilter | DomGrayscaleFilter;

private readonly Dictionary<uint, RetainedLayer> s_layers = new();
private readonly List<RetainedLayer> s_orderedLayers = [];
Expand Down Expand Up @@ -840,21 +842,29 @@ private static void SaveDomGroup(
255,
255,
255,
(command.Flags & DomBrightnessFilter) != 0
(command.Flags & DomColorFilterMask) != 0
? (byte)255 : (byte)(command.Rgba & 0xff));
if ((command.Flags & DomBrightnessFilter) == 0)
if ((command.Flags & DomColorFilterMask) == 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
]);
var matrix = (command.Flags & DomBrightnessFilter) != 0
? new float[] {
amount, 0, 0, 0, 0,
0, amount, 0, 0, 0,
0, 0, amount, 0, 0,
0, 0, 0, 1, 0
}
: new float[] {
1 - 0.7874f * amount, 0.7152f * amount, 0.0722f * amount, 0, 0,
0.2126f * amount, 1 - 0.2848f * amount, 0.0722f * amount, 0, 0,
0.2126f * amount, 0.7152f * amount, 1 - 0.9278f * amount, 0, 0,
0, 0, 0, 1, 0
};
using var filter = SKColorFilter.CreateColorMatrix(matrix);
paint.ColorFilter = filter;
canvas.SaveLayer(paint);
paint.ColorFilter = null;
Expand Down
24 changes: 17 additions & 7 deletions src/WebScene.Backend.Flutter/lib/src/scene_projector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ const int _canvasEvenOdd = 1 << 16;
const int _domPolygonClipResource = 1 << 31;
const int _domPolygonClipIndexMask = _domPolygonClipResource - 1;
const int _domBrightnessFilter = 1 << 31;
const int _domGrayscaleFilter = 1 << 30;
const int _domColorFilterMask = _domBrightnessFilter | _domGrayscaleFilter;

final class SceneApplyResult {
const SceneApplyResult({
Expand Down Expand Up @@ -959,18 +961,26 @@ final class WebSceneSceneProjector extends ChangeNotifier {
}

static ui.Paint _domGroupPaint(WebSceneSceneCommand command) {
if (command.flags & _domBrightnessFilter == 0) {
if (command.flags & _domColorFilterMask == 0) {
return ui.Paint()
..color = ui.Color.fromARGB(command.rgba & 0xff, 255, 255, 255);
}
final amount = command.strokeWidth.clamp(0.0, double.infinity).toDouble();
final matrix = command.flags & _domBrightnessFilter != 0
? <double>[
amount, 0, 0, 0, 0,
0, amount, 0, 0, 0,
0, 0, amount, 0, 0,
0, 0, 0, 1, 0,
]
: <double>[
1 - 0.7874 * amount, 0.7152 * amount, 0.0722 * amount, 0, 0,
0.2126 * amount, 1 - 0.2848 * amount, 0.0722 * amount, 0, 0,
0.2126 * amount, 0.7152 * amount, 1 - 0.9278 * amount, 0, 0,
0, 0, 0, 1, 0,
];
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,
]);
..colorFilter = ui.ColorFilter.matrix(matrix);
}

static String _domString(WebSceneSceneView scene, int index) =>
Expand Down
Loading