mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[web] Implement SceneBuilder.pushColorFilter for html (flutter/engine#20802)
* Implement Color filter layer * Add test with BlendMode color * update licenses file * Move blend functions into color_filter.dart * dartfmt
This commit is contained in:
parent
cc5fef5f32
commit
b84b4e6df7
@ -465,6 +465,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/history.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/backdrop_filter.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/canvas.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/clip.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/color_filter.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/debug_canvas_reuse_overlay.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/image_filter.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/offset.dart
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
repository: https://github.com/flutter/goldens.git
|
||||
revision: f26d68c3596eece3d40112e9dff01dc55d9bae97
|
||||
revision: 8a831253654d151635ee9cfb71389c257413de5d
|
||||
|
||||
@ -67,6 +67,7 @@ part 'engine/history.dart';
|
||||
part 'engine/html/backdrop_filter.dart';
|
||||
part 'engine/html/canvas.dart';
|
||||
part 'engine/html/clip.dart';
|
||||
part 'engine/html/color_filter.dart';
|
||||
part 'engine/html/debug_canvas_reuse_overlay.dart';
|
||||
part 'engine/html/image_filter.dart';
|
||||
part 'engine/html/offset.dart';
|
||||
|
||||
@ -573,57 +573,10 @@ class BitmapCanvas extends EngineCanvas {
|
||||
ui.Color? filterColor, ui.BlendMode colorFilterBlendMode,
|
||||
SurfacePaintData paint) {
|
||||
// For srcIn blendMode, we use an svg filter to apply to image element.
|
||||
String? svgFilter;
|
||||
switch (colorFilterBlendMode) {
|
||||
case ui.BlendMode.srcIn:
|
||||
case ui.BlendMode.srcATop:
|
||||
svgFilter = _srcInColorFilterToSvg(filterColor);
|
||||
break;
|
||||
case ui.BlendMode.srcOut:
|
||||
svgFilter = _srcOutColorFilterToSvg(filterColor);
|
||||
break;
|
||||
case ui.BlendMode.xor:
|
||||
svgFilter = _xorColorFilterToSvg(filterColor);
|
||||
break;
|
||||
case ui.BlendMode.plus:
|
||||
// Porter duff source + destination.
|
||||
svgFilter = _compositeColorFilterToSvg(filterColor, 0, 1, 1, 0);
|
||||
break;
|
||||
case ui.BlendMode.modulate:
|
||||
// Porter duff source * destination but preserves alpha.
|
||||
svgFilter = _modulateColorFilterToSvg(filterColor!);
|
||||
break;
|
||||
case ui.BlendMode.overlay:
|
||||
// Since overlay is the same as hard-light by swapping layers,
|
||||
// pass hard-light blend function.
|
||||
svgFilter = _blendColorFilterToSvg(filterColor, 'hard-light',
|
||||
swapLayers: true);
|
||||
break;
|
||||
// Several of the filters below (although supported) do not render the
|
||||
// same (close but not exact) as native flutter when used as blend mode
|
||||
// for a background-image with a background color. They only look
|
||||
// identical when feBlend is used within an svg filter definition.
|
||||
//
|
||||
// Saturation filter uses destination when source is transparent.
|
||||
// cMax = math.max(r, math.max(b, g));
|
||||
// cMin = math.min(r, math.min(b, g));
|
||||
// delta = cMax - cMin;
|
||||
// lightness = (cMax + cMin) / 2.0;
|
||||
// saturation = delta / (1.0 - (2 * lightness - 1.0).abs());
|
||||
case ui.BlendMode.saturation:
|
||||
case ui.BlendMode.colorDodge:
|
||||
case ui.BlendMode.colorBurn:
|
||||
case ui.BlendMode.hue:
|
||||
case ui.BlendMode.color:
|
||||
case ui.BlendMode.luminosity:
|
||||
svgFilter = _blendColorFilterToSvg(filterColor,
|
||||
_stringForBlendMode(colorFilterBlendMode));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
String? svgFilter = svgFilterFromBlendMode(filterColor,
|
||||
colorFilterBlendMode);
|
||||
final html.Element filterElement =
|
||||
html.Element.html(svgFilter, treeSanitizer: _NullTreeSanitizer());
|
||||
html.Element.html(svgFilter, treeSanitizer: _NullTreeSanitizer());
|
||||
rootElement.append(filterElement);
|
||||
_children.add(filterElement);
|
||||
final html.HtmlElement imgElement = _reuseOrCreateImage(image);
|
||||
@ -1013,109 +966,3 @@ String _maskFilterToCanvasFilter(ui.MaskFilter? maskFilter) {
|
||||
}
|
||||
}
|
||||
|
||||
int _filterIdCounter = 0;
|
||||
|
||||
// The color matrix for feColorMatrix element changes colors based on
|
||||
// the following:
|
||||
//
|
||||
// | R' | | r1 r2 r3 r4 r5 | | R |
|
||||
// | G' | | g1 g2 g3 g4 g5 | | G |
|
||||
// | B' | = | b1 b2 b3 b4 b5 | * | B |
|
||||
// | A' | | a1 a2 a3 a4 a5 | | A |
|
||||
// | 1 | | 0 0 0 0 1 | | 1 |
|
||||
//
|
||||
// R' = r1*R + r2*G + r3*B + r4*A + r5
|
||||
// G' = g1*R + g2*G + g3*B + g4*A + g5
|
||||
// B' = b1*R + b2*G + b3*B + b4*A + b5
|
||||
// A' = a1*R + a2*G + a3*B + a4*A + a5
|
||||
String _srcInColorFilterToSvg(ui.Color? color) {
|
||||
_filterIdCounter += 1;
|
||||
return '<svg width="0" height="0">'
|
||||
'<filter id="_fcf$_filterIdCounter" '
|
||||
'filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">'
|
||||
'<feColorMatrix values="0 0 0 0 1 ' // Ignore input, set it to absolute.
|
||||
'0 0 0 0 1 '
|
||||
'0 0 0 0 1 '
|
||||
'0 0 0 1 0" result="destalpha"/>' // Just take alpha channel of destination
|
||||
'<feFlood flood-color="${colorToCssString(color)}" flood-opacity="1" result="flood">'
|
||||
'</feFlood>'
|
||||
'<feComposite in="flood" in2="destalpha" '
|
||||
'operator="arithmetic" k1="1" k2="0" k3="0" k4="0" result="comp">'
|
||||
'</feComposite>'
|
||||
'</filter></svg>';
|
||||
}
|
||||
|
||||
String _srcOutColorFilterToSvg(ui.Color? color) {
|
||||
_filterIdCounter += 1;
|
||||
return '<svg width="0" height="0">'
|
||||
'<filter id="_fcf$_filterIdCounter" '
|
||||
'filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">'
|
||||
'<feFlood flood-color="${colorToCssString(color)}" flood-opacity="1" result="flood">'
|
||||
'</feFlood>'
|
||||
'<feComposite in="flood" in2="SourceGraphic" operator="out" result="comp">'
|
||||
'</feComposite>'
|
||||
'</filter></svg>';
|
||||
}
|
||||
|
||||
String _xorColorFilterToSvg(ui.Color? color) {
|
||||
_filterIdCounter += 1;
|
||||
return '<svg width="0" height="0">'
|
||||
'<filter id="_fcf$_filterIdCounter" '
|
||||
'filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">'
|
||||
'<feFlood flood-color="${colorToCssString(color)}" flood-opacity="1" result="flood">'
|
||||
'</feFlood>'
|
||||
'<feComposite in="flood" in2="SourceGraphic" operator="xor" result="comp">'
|
||||
'</feComposite>'
|
||||
'</filter></svg>';
|
||||
}
|
||||
|
||||
// The source image and color are composited using :
|
||||
// result = k1 *in*in2 + k2*in + k3*in2 + k4.
|
||||
String _compositeColorFilterToSvg(ui.Color? color, double k1, double k2, double k3 , double k4) {
|
||||
_filterIdCounter += 1;
|
||||
return '<svg width="0" height="0">'
|
||||
'<filter id="_fcf$_filterIdCounter" '
|
||||
'filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">'
|
||||
'<feFlood flood-color="${colorToCssString(color)}" flood-opacity="1" result="flood">'
|
||||
'</feFlood>'
|
||||
'<feComposite in="flood" in2="SourceGraphic" '
|
||||
'operator="arithmetic" k1="$k1" k2="$k2" k3="$k3" k4="$k4" result="comp">'
|
||||
'</feComposite>'
|
||||
'</filter></svg>';
|
||||
}
|
||||
|
||||
// Porter duff source * destination , keep source alpha.
|
||||
// First apply color filter to source to change it to [color], then
|
||||
// composite using multiplication.
|
||||
String _modulateColorFilterToSvg(ui.Color color) {
|
||||
_filterIdCounter += 1;
|
||||
final double r = color.red / 255.0;
|
||||
final double b = color.blue / 255.0;
|
||||
final double g = color.green / 255.0;
|
||||
return '<svg width="0" height="0">'
|
||||
'<filter id="_fcf$_filterIdCounter" '
|
||||
'filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">'
|
||||
'<feColorMatrix values="0 0 0 0 $r ' // Ignore input, set it to absolute.
|
||||
'0 0 0 0 $g '
|
||||
'0 0 0 0 $b '
|
||||
'0 0 0 1 0" result="recolor"/>'
|
||||
'<feComposite in="recolor" in2="SourceGraphic" '
|
||||
'operator="arithmetic" k1="1" k2="0" k3="0" k4="0" result="comp">'
|
||||
'</feComposite>'
|
||||
'</filter></svg>';
|
||||
}
|
||||
|
||||
// Uses feBlend element to blend source image with a color.
|
||||
String _blendColorFilterToSvg(ui.Color? color, String? feBlend,
|
||||
{bool swapLayers = false}) {
|
||||
_filterIdCounter += 1;
|
||||
return '<svg width="0" height="0">'
|
||||
'<filter id="_fcf$_filterIdCounter" filterUnits="objectBoundingBox" '
|
||||
'x="0%" y="0%" width="100%" height="100%">'
|
||||
'<feFlood flood-color="${colorToCssString(color)}" flood-opacity="1" result="flood">'
|
||||
'</feFlood>' +
|
||||
(swapLayers
|
||||
? '<feBlend in="SourceGraphic" in2="flood" mode="$feBlend"/>'
|
||||
: '<feBlend in="flood" in2="SourceGraphic" mode="$feBlend"/>') +
|
||||
'</filter></svg>';
|
||||
}
|
||||
|
||||
@ -0,0 +1,292 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// @dart = 2.10
|
||||
part of engine;
|
||||
|
||||
/// A surface that applies an [ColorFilter] to its children.
|
||||
class PersistedColorFilter extends PersistedContainerSurface
|
||||
implements ui.ColorFilterEngineLayer {
|
||||
PersistedColorFilter(PersistedColorFilter? oldLayer, this.filter)
|
||||
: super(oldLayer);
|
||||
|
||||
@override
|
||||
html.Element? get childContainer => _childContainer;
|
||||
|
||||
/// The dedicated child container element that's separate from the
|
||||
/// [rootElement] is used to compensate for the coordinate system shift
|
||||
/// introduced by the [rootElement] translation.
|
||||
html.Element? _childContainer;
|
||||
|
||||
final ui.ColorFilter filter;
|
||||
html.Element? _filterElement;
|
||||
bool containerVisible = true;
|
||||
|
||||
@override
|
||||
void adoptElements(PersistedColorFilter oldSurface) {
|
||||
super.adoptElements(oldSurface);
|
||||
_childContainer = oldSurface._childContainer;
|
||||
oldSurface._childContainer = null;
|
||||
}
|
||||
|
||||
@override
|
||||
void discard() {
|
||||
super.discard();
|
||||
// Do not detach the child container from the root. It is permanently
|
||||
// attached. The elements are reused together and are detached from the DOM
|
||||
// together.
|
||||
_childContainer = null;
|
||||
}
|
||||
|
||||
@override
|
||||
html.Element createElement() {
|
||||
html.Element element = defaultCreateElement('flt-color-filter');
|
||||
html.Element container = html.Element.tag('flt-filter-interior');
|
||||
container.style.position = 'absolute';
|
||||
_childContainer = container;
|
||||
element.append(_childContainer!);
|
||||
return element;
|
||||
}
|
||||
|
||||
@override
|
||||
void apply() {
|
||||
if (_filterElement != null) {
|
||||
_filterElement?.remove();
|
||||
}
|
||||
final EngineColorFilter? engineValue = filter as EngineColorFilter?;
|
||||
if (engineValue == null) {
|
||||
rootElement!.style.backgroundColor = '';
|
||||
childContainer?.style.visibility = 'visible';
|
||||
return;
|
||||
}
|
||||
if (engineValue._blendMode == null) {
|
||||
rootElement!.style.backgroundColor =
|
||||
colorToCssString(engineValue._color!);
|
||||
childContainer?.style.visibility = 'visible';
|
||||
return;
|
||||
}
|
||||
|
||||
ui.Color filterColor = engineValue._color!;
|
||||
ui.BlendMode? colorFilterBlendMode = engineValue._blendMode;
|
||||
html.CssStyleDeclaration style = rootElement!.style;
|
||||
if (colorFilterBlendMode != null) {
|
||||
switch (colorFilterBlendMode) {
|
||||
case ui.BlendMode.clear:
|
||||
case ui.BlendMode.dstOut:
|
||||
case ui.BlendMode.srcOut:
|
||||
childContainer?.style.visibility = 'hidden';
|
||||
return;
|
||||
case ui.BlendMode.dst:
|
||||
case ui.BlendMode.dstIn:
|
||||
// Noop.
|
||||
return;
|
||||
case ui.BlendMode.src:
|
||||
case ui.BlendMode.srcOver:
|
||||
// Uses source filter color.
|
||||
// Since we don't have a size, we can't use background color.
|
||||
// Use svg filter srcIn instead.
|
||||
colorFilterBlendMode = ui.BlendMode.srcIn;
|
||||
break;
|
||||
}
|
||||
|
||||
// Use SVG filter for blend mode.
|
||||
String? svgFilter =
|
||||
svgFilterFromBlendMode(filterColor, colorFilterBlendMode);
|
||||
if (svgFilter != null) {
|
||||
_filterElement =
|
||||
html.Element.html(svgFilter, treeSanitizer: _NullTreeSanitizer());
|
||||
rootElement!.append(_filterElement!);
|
||||
rootElement!.style.filter = 'url(#_fcf${_filterIdCounter})';
|
||||
if (colorFilterBlendMode == ui.BlendMode.saturation ||
|
||||
colorFilterBlendMode == ui.BlendMode.multiply ||
|
||||
colorFilterBlendMode == ui.BlendMode.modulate) {
|
||||
style.backgroundColor = colorToCssString(filterColor);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void update(PersistedColorFilter oldSurface) {
|
||||
super.update(oldSurface);
|
||||
|
||||
if (oldSurface.filter != filter) {
|
||||
apply();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String? svgFilterFromBlendMode(
|
||||
ui.Color? filterColor, ui.BlendMode colorFilterBlendMode) {
|
||||
String? svgFilter;
|
||||
switch (colorFilterBlendMode) {
|
||||
case ui.BlendMode.srcIn:
|
||||
case ui.BlendMode.srcATop:
|
||||
svgFilter = _srcInColorFilterToSvg(filterColor);
|
||||
break;
|
||||
case ui.BlendMode.srcOut:
|
||||
svgFilter = _srcOutColorFilterToSvg(filterColor);
|
||||
break;
|
||||
case ui.BlendMode.xor:
|
||||
svgFilter = _xorColorFilterToSvg(filterColor);
|
||||
break;
|
||||
case ui.BlendMode.plus:
|
||||
// Porter duff source + destination.
|
||||
svgFilter = _compositeColorFilterToSvg(filterColor, 0, 1, 1, 0);
|
||||
break;
|
||||
case ui.BlendMode.modulate:
|
||||
// Porter duff source * destination but preserves alpha.
|
||||
svgFilter = _modulateColorFilterToSvg(filterColor!);
|
||||
break;
|
||||
case ui.BlendMode.overlay:
|
||||
// Since overlay is the same as hard-light by swapping layers,
|
||||
// pass hard-light blend function.
|
||||
svgFilter =
|
||||
_blendColorFilterToSvg(filterColor, 'hard-light', swapLayers: true);
|
||||
break;
|
||||
// Several of the filters below (although supported) do not render the
|
||||
// same (close but not exact) as native flutter when used as blend mode
|
||||
// for a background-image with a background color. They only look
|
||||
// identical when feBlend is used within an svg filter definition.
|
||||
//
|
||||
// Saturation filter uses destination when source is transparent.
|
||||
// cMax = math.max(r, math.max(b, g));
|
||||
// cMin = math.min(r, math.min(b, g));
|
||||
// delta = cMax - cMin;
|
||||
// lightness = (cMax + cMin) / 2.0;
|
||||
// saturation = delta / (1.0 - (2 * lightness - 1.0).abs());
|
||||
case ui.BlendMode.saturation:
|
||||
case ui.BlendMode.colorDodge:
|
||||
case ui.BlendMode.colorBurn:
|
||||
case ui.BlendMode.hue:
|
||||
case ui.BlendMode.color:
|
||||
case ui.BlendMode.luminosity:
|
||||
case ui.BlendMode.multiply:
|
||||
case ui.BlendMode.screen:
|
||||
case ui.BlendMode.overlay:
|
||||
case ui.BlendMode.darken:
|
||||
case ui.BlendMode.lighten:
|
||||
case ui.BlendMode.colorDodge:
|
||||
case ui.BlendMode.colorBurn:
|
||||
case ui.BlendMode.hardLight:
|
||||
case ui.BlendMode.softLight:
|
||||
case ui.BlendMode.difference:
|
||||
case ui.BlendMode.exclusion:
|
||||
svgFilter = _blendColorFilterToSvg(
|
||||
filterColor, _stringForBlendMode(colorFilterBlendMode));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return svgFilter;
|
||||
}
|
||||
|
||||
int _filterIdCounter = 0;
|
||||
|
||||
// The color matrix for feColorMatrix element changes colors based on
|
||||
// the following:
|
||||
//
|
||||
// | R' | | r1 r2 r3 r4 r5 | | R |
|
||||
// | G' | | g1 g2 g3 g4 g5 | | G |
|
||||
// | B' | = | b1 b2 b3 b4 b5 | * | B |
|
||||
// | A' | | a1 a2 a3 a4 a5 | | A |
|
||||
// | 1 | | 0 0 0 0 1 | | 1 |
|
||||
//
|
||||
// R' = r1*R + r2*G + r3*B + r4*A + r5
|
||||
// G' = g1*R + g2*G + g3*B + g4*A + g5
|
||||
// B' = b1*R + b2*G + b3*B + b4*A + b5
|
||||
// A' = a1*R + a2*G + a3*B + a4*A + a5
|
||||
String _srcInColorFilterToSvg(ui.Color? color) {
|
||||
_filterIdCounter += 1;
|
||||
return '<svg width="0" height="0">'
|
||||
'<filter id="_fcf$_filterIdCounter" '
|
||||
'filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">'
|
||||
'<feColorMatrix values="0 0 0 0 1 ' // Ignore input, set it to absolute.
|
||||
'0 0 0 0 1 '
|
||||
'0 0 0 0 1 '
|
||||
'0 0 0 1 0" result="destalpha"/>' // Just take alpha channel of destination
|
||||
'<feFlood flood-color="${colorToCssString(color)}" flood-opacity="1" result="flood">'
|
||||
'</feFlood>'
|
||||
'<feComposite in="flood" in2="destalpha" '
|
||||
'operator="arithmetic" k1="1" k2="0" k3="0" k4="0" result="comp">'
|
||||
'</feComposite>'
|
||||
'</filter></svg>';
|
||||
}
|
||||
|
||||
String _srcOutColorFilterToSvg(ui.Color? color) {
|
||||
_filterIdCounter += 1;
|
||||
return '<svg width="0" height="0">'
|
||||
'<filter id="_fcf$_filterIdCounter" '
|
||||
'filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">'
|
||||
'<feFlood flood-color="${colorToCssString(color)}" flood-opacity="1" result="flood">'
|
||||
'</feFlood>'
|
||||
'<feComposite in="flood" in2="SourceGraphic" operator="out" result="comp">'
|
||||
'</feComposite>'
|
||||
'</filter></svg>';
|
||||
}
|
||||
|
||||
String _xorColorFilterToSvg(ui.Color? color) {
|
||||
_filterIdCounter += 1;
|
||||
return '<svg width="0" height="0">'
|
||||
'<filter id="_fcf$_filterIdCounter" '
|
||||
'filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">'
|
||||
'<feFlood flood-color="${colorToCssString(color)}" flood-opacity="1" result="flood">'
|
||||
'</feFlood>'
|
||||
'<feComposite in="flood" in2="SourceGraphic" operator="xor" result="comp">'
|
||||
'</feComposite>'
|
||||
'</filter></svg>';
|
||||
}
|
||||
|
||||
// The source image and color are composited using :
|
||||
// result = k1 *in*in2 + k2*in + k3*in2 + k4.
|
||||
String _compositeColorFilterToSvg(
|
||||
ui.Color? color, double k1, double k2, double k3, double k4) {
|
||||
_filterIdCounter += 1;
|
||||
return '<svg width="0" height="0">'
|
||||
'<filter id="_fcf$_filterIdCounter" '
|
||||
'filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">'
|
||||
'<feFlood flood-color="${colorToCssString(color)}" flood-opacity="1" result="flood">'
|
||||
'</feFlood>'
|
||||
'<feComposite in="flood" in2="SourceGraphic" '
|
||||
'operator="arithmetic" k1="$k1" k2="$k2" k3="$k3" k4="$k4" result="comp">'
|
||||
'</feComposite>'
|
||||
'</filter></svg>';
|
||||
}
|
||||
|
||||
// Porter duff source * destination , keep source alpha.
|
||||
// First apply color filter to source to change it to [color], then
|
||||
// composite using multiplication.
|
||||
String _modulateColorFilterToSvg(ui.Color color) {
|
||||
_filterIdCounter += 1;
|
||||
final double r = color.red / 255.0;
|
||||
final double b = color.blue / 255.0;
|
||||
final double g = color.green / 255.0;
|
||||
return '<svg width="0" height="0">'
|
||||
'<filter id="_fcf$_filterIdCounter" '
|
||||
'filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">'
|
||||
'<feColorMatrix values="0 0 0 0 $r ' // Ignore input, set it to absolute.
|
||||
'0 0 0 0 $g '
|
||||
'0 0 0 0 $b '
|
||||
'0 0 0 1 0" result="recolor"/>'
|
||||
'<feComposite in="recolor" in2="SourceGraphic" '
|
||||
'operator="arithmetic" k1="1" k2="0" k3="0" k4="0" result="comp">'
|
||||
'</feComposite>'
|
||||
'</filter></svg>';
|
||||
}
|
||||
|
||||
// Uses feBlend element to blend source image with a color.
|
||||
String _blendColorFilterToSvg(ui.Color? color, String? feBlend,
|
||||
{bool swapLayers = false}) {
|
||||
_filterIdCounter += 1;
|
||||
return '<svg width="0" height="0">'
|
||||
'<filter id="_fcf$_filterIdCounter" filterUnits="objectBoundingBox" '
|
||||
'x="0%" y="0%" width="100%" height="100%">'
|
||||
'<feFlood flood-color="${colorToCssString(color)}" flood-opacity="1" result="flood">'
|
||||
'</feFlood>' +
|
||||
(swapLayers
|
||||
? '<feBlend in="SourceGraphic" in2="flood" mode="$feBlend"/>'
|
||||
: '<feBlend in="flood" in2="SourceGraphic" mode="$feBlend"/>') +
|
||||
'</filter></svg>';
|
||||
}
|
||||
@ -179,7 +179,7 @@ class SurfaceSceneBuilder implements ui.SceneBuilder {
|
||||
ui.ColorFilterEngineLayer? oldLayer,
|
||||
}) {
|
||||
assert(filter != null); // ignore: unnecessary_null_comparison
|
||||
throw UnimplementedError();
|
||||
return _pushSurface(PersistedColorFilter(oldLayer as PersistedColorFilter?, filter)) as ui.ColorFilterEngineLayer;
|
||||
}
|
||||
|
||||
/// Pushes an image filter operation onto the operation stack.
|
||||
|
||||
@ -0,0 +1,89 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// @dart = 2.6
|
||||
import 'dart:html' as html;
|
||||
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/ui.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
final Rect region = Rect.fromLTWH(0, 0, 500, 500);
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
void testMain() async {
|
||||
setUp(() async {
|
||||
debugShowClipLayers = true;
|
||||
SurfaceSceneBuilder.debugForgetFrameScene();
|
||||
for (html.Node scene in html.document.querySelectorAll('flt-scene')) {
|
||||
scene.remove();
|
||||
}
|
||||
|
||||
await webOnlyInitializePlatform();
|
||||
webOnlyFontCollection.debugRegisterTestFonts();
|
||||
await webOnlyFontCollection.ensureFontsLoaded();
|
||||
});
|
||||
|
||||
test('Should apply color filter to image', () async {
|
||||
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
|
||||
final Picture backgroundPicture = _drawBackground();
|
||||
builder.addPicture(Offset.zero, backgroundPicture);
|
||||
builder.pushColorFilter(EngineColorFilter.mode(Color(0xF0000080),
|
||||
BlendMode.color));
|
||||
final Picture circles1 = _drawTestPictureWithCircles(30, 30);
|
||||
builder.addPicture(Offset.zero, circles1);
|
||||
builder.pop();
|
||||
html.document.body.append(builder
|
||||
.build()
|
||||
.webOnlyRootElement);
|
||||
|
||||
await matchGoldenFile('color_filter_blendMode_color.png', region: region);
|
||||
});
|
||||
}
|
||||
|
||||
Picture _drawTestPictureWithCircles(double offsetX, double offsetY) {
|
||||
final EnginePictureRecorder recorder = PictureRecorder();
|
||||
final RecordingCanvas canvas =
|
||||
recorder.beginRecording(const Rect.fromLTRB(0, 0, 400, 400));
|
||||
canvas.drawCircle(
|
||||
Offset(offsetX + 10, offsetY + 10), 10, Paint()..style = PaintingStyle.fill);
|
||||
canvas.drawCircle(
|
||||
Offset(offsetX + 60, offsetY + 10),
|
||||
10,
|
||||
Paint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color.fromRGBO(255, 0, 0, 1));
|
||||
canvas.drawCircle(
|
||||
Offset(offsetX + 10, offsetY + 60),
|
||||
10,
|
||||
Paint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color.fromRGBO(0, 255, 0, 1));
|
||||
canvas.drawCircle(
|
||||
Offset(offsetX + 60, offsetY + 60),
|
||||
10,
|
||||
Paint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color.fromRGBO(0, 0, 255, 1));
|
||||
return recorder.endRecording();
|
||||
}
|
||||
|
||||
Picture _drawBackground() {
|
||||
final EnginePictureRecorder recorder = PictureRecorder();
|
||||
final RecordingCanvas canvas =
|
||||
recorder.beginRecording(const Rect.fromLTRB(0, 0, 400, 400));
|
||||
canvas.drawRect(
|
||||
Rect.fromLTWH(8, 8, 400.0 - 16, 400.0 - 16),
|
||||
Paint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = Color(0xFFE0FFE0)
|
||||
);
|
||||
return recorder.endRecording();
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user