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,73 +339,105 @@ void native_document::append_scene(
|| opacity <= 0.001F) {
return;
}
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};
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 != name[index]) return std::nullopt;
}
value.remove_prefix(name.size() + 1U);
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 <= maximum
? std::optional<float>{amount} : std::nullopt;
struct retained_foreground_filter final {
uint32_t flags{};
float amount{};
};
const auto brightness_filter = resolve_single_filter("brightness", 10.0F);
const auto grayscale_filter = resolve_single_filter("grayscale", 1.0F);
const auto contrast_filter = resolve_single_filter("contrast", 10.0F);
const auto blur_filter = [&]() -> std::optional<float> {
const auto foreground_filters = [&]() {
std::vector<retained_foreground_filter> result;
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 auto name = std::string_view{"blur"};
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 != name[index]) return std::nullopt;
if (known == effects.end()) return result;
auto remaining = std::string_view{known->second};
const auto trim = [](std::string_view value) {
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);
return value;
};
const auto equals_ascii_case_insensitive = [](std::string_view left,
std::string_view right) {
if (left.size() != right.size()) return false;
for (size_t index = 0; index < left.size(); ++index) {
const auto character = static_cast<unsigned char>(left[index]);
const auto lower = character >= 'A' && character <= 'Z'
? static_cast<char>(character - 'A' + 'a')
: static_cast<char>(character);
if (lower != right[index]) return false;
}
return true;
};
remaining = trim(remaining);
if (equals_ascii_case_insensitive(remaining, "none")) return result;
constexpr size_t maximum_filter_functions = 16U;
while (!remaining.empty()) {
if (result.size() >= maximum_filter_functions) {
result.clear();
return result;
}
const auto opening = remaining.find('(');
const auto closing = opening == std::string_view::npos
? std::string_view::npos : remaining.find(')', opening + 1U);
if (opening == 0U || opening == std::string_view::npos
|| closing == std::string_view::npos) {
result.clear();
return result;
}
const auto name = trim(remaining.substr(0U, opening));
const auto argument = trim(
remaining.substr(opening + 1U, closing - opening - 1U));
const auto parsed = css::parse_ascii_number_prefix(argument);
if (!parsed.has_value() || parsed->value < 0.0F) {
result.clear();
return result;
}
const auto suffix = argument.substr(parsed->consumed);
uint32_t flags = 0U;
float amount = -1.0F;
float maximum = 0.0F;
if (equals_ascii_case_insensitive(name, "blur")) {
flags = 1U << 28U;
maximum = 256.0F;
amount = suffix == "px" || (suffix.empty() && parsed->value == 0.0F)
? parsed->value : -1.0F;
} else {
if (equals_ascii_case_insensitive(name, "brightness")) {
flags = 1U << 31U;
maximum = 10.0F;
} else if (equals_ascii_case_insensitive(name, "grayscale")) {
flags = 1U << 30U;
maximum = 1.0F;
} else if (equals_ascii_case_insensitive(name, "contrast")) {
flags = 1U << 29U;
maximum = 10.0F;
} else if (equals_ascii_case_insensitive(name, "saturate")) {
flags = 1U << 27U;
maximum = 10.0F;
} else {
result.clear();
return result;
}
amount = suffix.empty()
? parsed->value
: suffix == "%" ? parsed->value / 100.0F : -1.0F;
}
if (amount < 0.0F || amount > maximum) {
result.clear();
return result;
}
result.push_back(retained_foreground_filter{flags, amount});
remaining.remove_prefix(closing + 1U);
if (!remaining.empty()
&& !std::isspace(static_cast<unsigned char>(remaining.front()))) {
result.clear();
return result;
}
remaining = trim(remaining);
}
value.remove_prefix(name.size() + 1U);
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.0F) return std::nullopt;
const auto suffix = value.substr(parsed->consumed);
if (suffix != "px" && !(suffix.empty() && parsed->value == 0.0F))
return std::nullopt;
return parsed->value <= 256.0F
? std::optional<float>{parsed->value} : std::nullopt;
return result;
}();
const auto has_opacity_group = opacity < 0.999F;
if (has_opacity_group) {
Expand All @@ -422,26 +454,13 @@ 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});
}
if (grayscale_filter.has_value()) {
// SaveLayer effects apply when their groups restore. Open in reverse CSS
// order so restoration applies the authored function list left-to-right.
for (auto filter = foreground_filters.rbegin();
filter != foreground_filters.rend(); ++filter) {
commands.push_back(webscene_scene_command{
30U,
1U << 30U,
filter->flags,
node.layout.x,
node.layout.y,
node.layout.width,
Expand All @@ -452,39 +471,7 @@ void native_document::append_scene(
0,
0,
0,
*grayscale_filter});
}
if (contrast_filter.has_value()) {
commands.push_back(webscene_scene_command{
30U,
1U << 29U,
node.layout.x,
node.layout.y,
node.layout.width,
node.layout.height,
255U,
node.id,
0,
0,
0,
0,
*contrast_filter});
}
if (blur_filter.has_value()) {
commands.push_back(webscene_scene_command{
30U,
1U << 28U,
node.layout.x,
node.layout.y,
node.layout.width,
node.layout.height,
255U,
node.id,
0,
0,
0,
0,
*blur_filter});
filter->amount});
}
const auto resolve_transform_origin = [](css_length value, float available, float fallback) {
if (value.unit == length_unit::pixels) return value.value;
Expand Down Expand Up @@ -2046,22 +2033,8 @@ 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 (blur_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 (contrast_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 (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()) {
for (size_t filter_index = 0U;
filter_index < foreground_filters.size(); ++filter_index) {
commands.push_back(webscene_scene_command{
31U, 0U, node.layout.x, node.layout.y,
node.layout.width, node.layout.height, 0U, node.id});
Expand Down Expand Up @@ -2418,22 +2391,8 @@ 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 (blur_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 (contrast_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 (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()) {
for (size_t filter_index = 0U;
filter_index < foreground_filters.size(); ++filter_index) {
commands.push_back(webscene_scene_command{
31U, 0U, node.layout.x, node.layout.y,
node.layout.width, node.layout.height, 0U, node.id});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +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 for brightness, bit 30 for grayscale, and
// bit 29 for contrast, and bit 28 for foreground blur; stroke_width carries
// Group kind 30 uses flags bit 31 for brightness, bit 30 for grayscale,
// bit 29 for contrast, bit 28 for foreground blur, and bit 27 for saturation;
// stroke_width carries
// the bounded non-negative multiplier or CSS blur standard deviation.
// A zero flag retains the opacity-group alpha stored in the low byte of rgba.
typedef struct webscene_scene_command {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ struct clip_scene_counts final {
uint32_t blur_filter_begins{};
uint32_t command_count{};
bool transform_clip_nested{};
bool compound_filter_ordered{};
};

clip_scene_counts wait_for_inset_clip_scene(webscene_engine* engine, uint32_t expected_count)
Expand All @@ -78,6 +79,8 @@ clip_scene_counts wait_for_inset_clip_scene(webscene_engine* engine, uint32_t ex
latest.command_count = scene->header.command_count;
uint32_t transform_clip_node = 0U;
auto transform_clip_stage = 0U;
uint32_t compound_filter_node = 0U;
auto compound_filter_stage = 0U;
for (uint32_t index = 0; index < scene->header.command_count; ++index) {
const auto& command = scene->commands[index];
if (transform_clip_stage == 0U && command.kind == 15U) {
Expand All @@ -97,6 +100,30 @@ clip_scene_counts wait_for_inset_clip_scene(webscene_engine* engine, uint32_t ex
transform_clip_stage = 6U;
}
}
if (command.kind == 30U) {
if (compound_filter_stage == 0U
&& (command.flags & (1U << 30U)) != 0U
&& std::abs(command.stroke_width - 0.25F) < 0.01F) {
compound_filter_node = command.node_id;
compound_filter_stage = 1U;
} else if (command.node_id == compound_filter_node
&& compound_filter_stage == 1U
&& (command.flags & (1U << 29U)) != 0U
&& std::abs(command.stroke_width - 1.5F) < 0.01F) {
compound_filter_stage = 2U;
} else if (command.node_id == compound_filter_node
&& compound_filter_stage == 2U
&& (command.flags & (1U << 27U)) != 0U
&& std::abs(command.stroke_width - 1.08F) < 0.01F) {
compound_filter_stage = 3U;
} else if (command.node_id == compound_filter_node
&& compound_filter_stage == 3U
&& (command.flags & (1U << 28U)) != 0U
&& std::abs(command.stroke_width - 2.0F) < 0.01F) {
latest.compound_filter_ordered = true;
compound_filter_stage = 4U;
}
}
if (command.kind == 12U
&& std::abs(command.width - 6.0F) < 0.01F
&& std::abs(command.height - 2.0F) < 0.01F) {
Expand Down Expand Up @@ -182,6 +209,7 @@ int main()
clip-path: inset(0px 1px); filter: brightness(0.5); backdrop-filter: blur(1px); }
#effects.alternate > span { clip-path: circle(25%); filter: contrast(2); }
#effects > span:first-child { transform: scale(1.25) rotate(3deg); }
#compound-filter { filter: blur(2px) saturate(1.08) contrast(1.5) grayscale(0.25); }
#effects > span:last-child { filter: blur(2px); }
`;
document.head.appendChild(rules);
Expand All @@ -190,6 +218,7 @@ int main()
const fragment = document.createDocumentFragment();
for (let index = 0; index < 4096; index++) fragment.appendChild(document.createElement('span'));
host.appendChild(fragment);
host.children[4094].id = 'compound-filter';
document.body.appendChild(host);
const first = host.firstElementChild;
const style = getComputedStyle(first);
Expand All @@ -202,6 +231,10 @@ int main()
if (getComputedStyle(host.lastElementChild).getPropertyValue('filter') !== 'blur(2px)') {
throw new Error('initial blur filter value failed');
}
if (getComputedStyle(document.getElementById('compound-filter')).getPropertyValue('filter')
!== 'blur(2px) saturate(1.08) contrast(1.5) grayscale(0.25)') {
throw new Error('initial compound filter list failed');
}
})()
)JS", "native-effects-fixture.js");

Expand Down Expand Up @@ -268,8 +301,10 @@ int main()
"retained scene did not preserve all 4096 clipped fills");
require(initial_clip_scene.transform_clip_nested,
"transform commands did not wrap the inset clip scope");
require(initial_clip_scene.blur_filter_begins == 1U,
"retained scene did not emit the bounded foreground blur group");
require(initial_clip_scene.blur_filter_begins == 2U,
"retained scene did not emit both bounded foreground blur groups");
require(initial_clip_scene.compound_filter_ordered,
"retained scene did not preserve compound foreground filter order");
const auto initial_scene_command_bytes =
static_cast<uint64_t>(initial_clip_scene.command_count)
* sizeof(webscene_scene_command);
Expand Down Expand Up @@ -382,6 +417,7 @@ int main()
<< " clip-ends=" << initial_clip_scene.inset_clip_ends
<< " clipped-fills=" << initial_clip_scene.clipped_fills
<< " blur-filter-begins=" << initial_clip_scene.blur_filter_begins
<< " compound-filter-ordered=" << initial_clip_scene.compound_filter_ordered
<< " transform-clip-nested=" << initial_clip_scene.transform_clip_nested
<< " initial-scene-command-bytes=" << initial_scene_command_bytes
<< " peak-textual-style-count-delta=" << peak_textual_style_count_delta
Expand Down
Loading
Loading