From 115bd2e30036e9a71b65bad4de591740a3eae2d9 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Fri, 1 May 2020 18:01:52 -0700 Subject: [PATCH 01/11] Implement Color filter for images --- lib/web_ui/lib/src/engine/bitmap_canvas.dart | 110 +++++++++++++- .../lib/src/engine/html_image_codec.dart | 2 +- .../engine/recording_canvas_golden_test.dart | 135 +++++++++++++++++- 3 files changed, 239 insertions(+), 8 deletions(-) diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index ff63feae7e53d..1288adf1f7da3 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -358,12 +358,52 @@ class BitmapCanvas extends EngineCanvas { _canvasPool.closeCurrentCanvas(); } - html.ImageElement _drawImage( + html.HtmlElement _drawImage( ui.Image image, ui.Offset p, SurfacePaintData paint) { final HtmlImage htmlImage = image; - final html.Element imgElement = htmlImage.cloneImageElement(); final ui.BlendMode blendMode = paint.blendMode; + final ui.ColorFilter colorFilter = paint.colorFilter; + final ui.BlendMode colorFilterBlendMode = (colorFilter != null && + colorFilter is EngineColorFilter && colorFilter._blendMode != null) + ? colorFilter._blendMode : null; + html.HtmlElement imgElement; + if (colorFilterBlendMode != ui.BlendMode.srcIn && + colorFilterBlendMode != ui.BlendMode.saturation + && colorFilter is EngineColorFilter) { + // When blending with color we can't use an image element. + // Instead use a div element with background image, color and + // background blend mode. + imgElement = html.DivElement(); + imgElement.style + ..position = 'absolute' + ..backgroundImage = "url('${htmlImage.imgElement.src}')" + ..backgroundBlendMode = + _stringForBlendMode(colorFilterBlendMode) + ..backgroundColor = colorToCssString(colorFilter._color); + } else { + imgElement = htmlImage.cloneImageElement(); + } imgElement.style.mixBlendMode = _stringForBlendMode(blendMode); + if (colorFilter != null && colorFilter is EngineColorFilter && + (colorFilter._blendMode == ui.BlendMode.srcIn || + colorFilter._blendMode == ui.BlendMode.saturation)) { + // For srcIn blendMode, we use an svg filter to apply to image element. + String svgFilter; + bool isSaturationFilter = colorFilter._blendMode == ui.BlendMode.saturation; + if (isSaturationFilter) { + svgFilter = _saturationFilterToSvg(colorFilter._color); + } else { + svgFilter = _srcInColorFilterToSvg(colorFilter._color); + } + final html.Element filterElement = + html.Element.html(svgFilter, treeSanitizer: _NullTreeSanitizer()); + rootElement.append(filterElement); + _children.add(filterElement); + imgElement.style.filter = 'url(#_fcf${_filterIdCounter})'; + if (isSaturationFilter) { + imgElement.style.backgroundColor = colorToCssString(colorFilter._color); + } + } if (_canvasPool.isClipped) { // Reset width/height since they may have been previously set. imgElement.style..removeProperty('width')..removeProperty('height'); @@ -398,7 +438,14 @@ class BitmapCanvas extends EngineCanvas { if (dst.width == image.width && dst.height == image.height && !requiresClipping) { - drawImage(image, dst.topLeft, paint); + html.Element imgElement = _drawImage(image, dst.topLeft, paint); + _childOverdraw = true; + _canvasPool.closeCurrentCanvas(); + if (imgElement is! html.ImageElement) { + imgElement.style.backgroundSize = + '${dst.width.toStringAsFixed(2)}px ' + '${dst.height.toStringAsFixed(2)}px'; + } } else { if (requiresClipping) { save(); @@ -417,7 +464,7 @@ class BitmapCanvas extends EngineCanvas { } } - final html.ImageElement imgElement = + final html.Element imgElement = _drawImage(image, ui.Offset(targetLeft, targetTop), paint); // To scale set width / height on destination image. // For clipping we need to scale according to @@ -430,9 +477,14 @@ class BitmapCanvas extends EngineCanvas { targetHeight *= image.height / src.height; } final html.CssStyleDeclaration imageStyle = imgElement.style; + final String widthPx = '${targetWidth.toStringAsFixed(2)}px'; + final String heightPx = '${targetHeight.toStringAsFixed(2)}px'; imageStyle - ..width = '${targetWidth.toStringAsFixed(2)}px' - ..height = '${targetHeight.toStringAsFixed(2)}px'; + ..width = widthPx + ..height = heightPx; + if (imgElement is! html.ImageElement) { + imgElement.style.backgroundSize = '$widthPx $heightPx'; + } if (requiresClipping) { restore(); } @@ -796,3 +848,49 @@ String _maskFilterToCss(ui.MaskFilter maskFilter) { } return 'blur(${maskFilter.webOnlySigma}px)'; } + +int _filterIdCounter = 0; + +// The color matrix 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; + final double r = color.red / 255.0; + final double b = color.blue / 255.0; + final double g = color.green / 255.0; + final double a = color.alpha / 255.0; + return '' + '' + '' // alpha is identical. + ''; +} + +String _saturationFilterToSvg(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; + final double cMax = math.max(r, math.max(b, g)); + final double cMin = math.min(r, math.min(b, g)); + final double delta = cMax - cMin; + final double lightness = (cMax + cMin) / 2.0; + final double saturation = delta / (1.0 - (2 * lightness - 1.0).abs()); + return '' + '' + '' + ''; +} diff --git a/lib/web_ui/lib/src/engine/html_image_codec.dart b/lib/web_ui/lib/src/engine/html_image_codec.dart index c380258d90411..bed954df4a6df 100644 --- a/lib/web_ui/lib/src/engine/html_image_codec.dart +++ b/lib/web_ui/lib/src/engine/html_image_codec.dart @@ -149,7 +149,7 @@ class HtmlImage implements ui.Image { return imgElement.clone(true); } else { _requiresClone = true; - imgElement.style..position = 'absolute'; + imgElement.style.position = 'absolute'; return imgElement; } } diff --git a/lib/web_ui/test/golden_tests/engine/recording_canvas_golden_test.dart b/lib/web_ui/test/golden_tests/engine/recording_canvas_golden_test.dart index 5ad259445ff54..3b8f3f6f35908 100644 --- a/lib/web_ui/test/golden_tests/engine/recording_canvas_golden_test.dart +++ b/lib/web_ui/test/golden_tests/engine/recording_canvas_golden_test.dart @@ -652,10 +652,134 @@ void main() async { typedef PaintSpreadPainter = void Function(RecordingCanvas canvas, SurfacePaint paint); -const String _base64Encoded20x20TestImage = 'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAACXBIWXMAAC4jAAAuIwF4pT92AAAA' +const String _base64Encoded20x20TestImage = 'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUC' + 'AIAAAAC64paAAAACXBIWXMAAC4jAAAuIwF4pT92AAAA' 'B3RJTUUH5AMFFBksg4i3gQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAj' 'SURBVDjLY2TAC/7jlWVioACMah4ZmhnxpyHG0QAb1UyZZgBjWAIm/clP0AAAAABJRU5ErkJggg=='; +const String _flutterLogoBase64 = 'iVBORw0KGgoAAAANSUhEUgAAASAAAAEQCAYAAAAQ+akt' + 'AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZ' + 'G9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek' + '5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdG' + 's9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1Nj' + 'oyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOT' + 'k5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9Ii' + 'IgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOn' + 'N0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIi' + 'B4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW' + '50SUQ9InhtcC5kaWQ6MzFFNzQxN0E4NDBBMTFFQTk4RThBQjg5NEIyOENFQTciIHhtcE1NO' + 'kluc3RhbmNlSUQ9InhtcC5paWQ6MzFFNzQxNzk4NDBBMTFFQTk4RThBQjg5NEIyOENFQTci' + 'IHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSI+IDx4' + 'bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuZGlkOjAxODAxMTc0MDcyM' + 'DY4MTE4MjJBQUI1NDhBQTAzMDNBIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjAxODAxMT' + 'c0MDcyMDY4MTE4MjJBQUI1NDhBQTAzMDNBIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjp' + 'SREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+M8R8IAAAFj1JREFUeNrs3W1w' + 'HdV9x/Gzq6t7r/xAxplJiuQHkIEyPCZASJsQHhwbuzHUSUkgE2JZDeNMpp12+vimb6BJXvR9J' + '9MpE48TI9skmKdAIImhbUqIgfgpMgkpGJtOCLWxcTSd1JKsh93urnyvrq6upLu755w9Z/f7M8' + 'a2xtq7Xp396P8/e/au4/u+IISQLOJyCAghAEQIASBCCNGVUvMHHMfhqJAoj66586OfrV6w05s' + 'YvizWJyaZV/S9BJ+j4SDE/be0+Ptuuftvbzx26Bv73zw4zqiiAiIq8SEt8Tlw7BD4ABDRgg9V' + 'NPgAEKHyAR8AIuADPgSAiNH4OIYOxZjtJPgAEKHyyeZkqoAPABHwyQqfowfBB4AI+IAPABHwA' + 'R8CQKQ5j9125x/kAp+Mlxu51Z6/AZ90KXEIiofPXdWlIT6XcjTS4dNz/JVvnDg2OMHRoAIic' + 'fCZHAEfGfi8Dj4ARNrHp+sC8AEfACLgAz4EgMCHgA8AEfABHwAi4EOaT5BKN/gAEAEf8AEgA' + 'j7gQwCIgA/4ABABH/AhKcKtGDnIE2s3/eFnyksGwCclPuWev+o5/vK/gA8AkVj4LA4rn0s4G' + 'inxeQt8aMFIAnxGwQd8AIiAD/gQACoCPpUl4JPyyajgA0AkMT7M+YAPABGb8PF9DiL4ABCh8' + 'gEfAkDgAz4EgEgCfPLWTfke+AAQofKh8iH6wkpoQ/Pk2k9/7NOVxQPgkxKfSs9f9hx/+V/BB4' + 'BIPHzCyme1ne2UGf0h+NCCkaLhQ+VDAAh8TK1KwIcAkO34sJ4QfACI0HZlGB98AIiAD5UPAS' + 'DwsbuaSbcIEXwAiOjCJ8n8j84JaM2T3eADQCQJPtWlVD7gw9eQQwA+4EMACHxIu4O2DD55Cbd' + 'iaMoP1935sQ2VBnySTLyy/ieofJb/Rc+xlx4EHwAisfC5YFeAT6/2Fzd9AjoGxOBDC0YS43Ou' + 'l6NB5UMAKHt8FL/5FvgQAAIfuZWP6et/wIcAkAX4UP2ADwEgayofWyJ5Ahp8AIiYgk/BLr+DD' + 'wARFfjobL8snf8BHwAiJrVdBap+wAeAiCp88jz5LGH+B3wAiJhU+RSo/QKf4oZbMRLmR2v/+O' + 'Pro3u72sAnafVTgPbLLff8eYDPN8EHgEg8fMLK5+JctEMZvV6Ez/GXwYcWjMTGx2sTH6qflsc' + 'EfAgAqcaHtB501RXgQwBIOT66qx8L2i+3EuDz5j7wIQBE5aN5sEX4vAg+BICU45P36gd8CADl' + 'DB+bEgM88CEAZEPblcMrX+BDAEgnPllUP4a2X+BDAMgWfGyqftoAD3xIO2EldNZtVw6rH/ApR' + 'IPuAJDEPLfmzpvWhfd2JcWnKNXPQvhUV/xZz9EXt4EPAaA4+HQt3R3gs0o7PrZVP/O8JviQ2N' + '+wwKeGz9iqTHYgJ9UP+BAAygKfrFovg6of8CEAZBs+trVeVD4EgHKETw5aL/AhAFQ0fLKqfpp' + 'eF3wIABWp7aLyIQBUcHykVBJ2Vz/gQwAoK3yK2HpR+RAAKjg+mVZsPvgQZcn1Suj/vHXDJ27p' + 'Cu/tyhgfiS1QJt+lKiu+EuCzHXwIAMXBZ/H7w8pnZeb42Np6Ba8d4fMm+BBasOLhk/XgAB8CQ' + 'BngE8JjAj4ZVj9uF/gQAMoGHynti+X4vP4T8CEAVEh8shwQ4EMAqOD4ZFT9gA8BIPABH1KYWH' + '0ZPjU+pt3XBT6ECgh8Mqt+wIdQAYFPJvBkUP2ADwEgnfiYWvWADwEg8/PiJzfefFP1fbvAJyU' + '+1eVfDvD5NvgQAIqHT1j5rMgUnjzg88aL4EMASCk+qq5wgQ8h8sYk+IAPIVRAMvBRua4HfAgp' + 'DkCx8LEBHvAhxA6A2sJHxypm8CGkWAAtiI+u2yfAh5BiATQnPjrv2ZJtBfgQYj5ALfHRfbMo+' + 'BBSPIDq+EyOrshkB1Q4AT6ELDxmjcCnshR8wIdQAenNT9dsvOXjlei5XfrxUWVENvhsDfDZA' + 'T4EgOLgU42eWLocfMCHAFD+8ckRPOBDAMgWfFT6AD6E2AGQdnxU2wA+hNgBkFZ8dLgAPoTYA' + 'ZAWfHR5kOUTS8GHAJBh+Oj0AHwIsQcgZfjodsDP9pk74EMAKGt8sjIAfAixCyBp+GR57vvZP' + '2kQfAgAxcXnto23BvjsSoxP1ue9b8YjTt1K930BPgPgQwAoDj5dUeXTYw04puJzdB/4EACSh' + 'o/Jz073zdk58CEAlKjtWrLbm4xR+QAP+BAAkoaPN24PPr555Rj4EADKOz6+mX0g+BAAyjM+v' + 'rkTUKbj8zn/9q3DntfrCGfcE551AzyLffYbXtMXflePW9n3bxuGng/+eBZyJABkBT6+b/zBN' + 'x+f9Q8Oe5P3heMkPKmcLL+cCa5ihJ/jpHjFZODN/LwVbvXnP9z4vztP7D0IPjIAMh4fC+CxD' + 'Z/Mv6T6772RspXlbiXEZ8uJH/zsVaiRAJCx+FiCjr34ZHd8k+LjZ7DPjdUP+EgGyDh8LEMHf' + 'GypmNL/e8FHMkDG4GMpOuBjS+uVvvoBH8kAHbxtw23XV5fsygwfy9Gp41Pt+VLPGz/dCT4mt' + '17pJp7BRzJAU/iEj0se7wYc8KH1ou3SBpAWfHKIDfgUr/UCH8kAScenANCAT/FaLyofBQD97' + 'JZPXX99ZemANznWzeEpEj72tlBZ4dPtlg8H+PSDT4rzpPkDH33hB4eCX14KfnJfUm7x2fDN2' + 'fj4fOFiVT7Vw0+tP0PlIxugMM6PHrnHdctPgFBe8Zn4U9Pwsan6CfF5cv3pLWeeO/QLRrwCg' + 'EAIfPKPT/LKB3w0AARC4GMyPjJeGXwMB2gaoc7HQQh8TIru1gt8MgJoCqE9nwch8Clq6wU+G' + 'QMEQnnBxwefmJ8PPoYABEJNB67S3W8fPnlon8CnsACBUAM+R/ftBh+9+OiEC3wMBajoCIGPr' + 'fi0//ngYzhAhUTI98HH2pYNfHIHUGEQCm+kDfGp9oBPpoiADwAVCaHz8EQHyXB8Puuv35ZXf' + 'HTBFeLzvQ1n+sDHMoByh1ADPLbgM+JN9oNP8uqnhs97ew/8EhIsBCgXCDXBYx8+5qzxAR+iHS' + 'ArEaqh0+IN0+zDx7BDCz5EN0B1hJzOR41GaA50wCcP+LQX8MkpQBFCe/d8wTiE5ql27MTHzx' + '0+svYAfAoOkDEItYmOnfgYWFimxEdH6wU+BQEoM4RiolM/EOULt4AP+BC9cfymE9VxHPmDc/3' + 'dD3v++OeEqjdAT/nUjQifYy8/bCo+l/mrt5W8Sv9y8YFSWXSKvC0ylNO2+cbj46cbp6b121Kg' + 'cLXsqexKqLHKKQA+E57bPyLGSr8Rp8Q5MSbraw8+JN8tmDSEJIJjIz5h5egE6IyKcfGOOG0MQ' + 'jbg0+NWDoFPwVuwWO2YpocYWoDP9gCfvubjFJ601aANC9oxURHlzCpza/BZ+17fmX8/9JoR1S' + 'ItWPYA1RHyxtTNCeUUH1MQAh8AEjbNAbVsx9xyJpfobcdn6iufXTsGPkTq+ZgZnxkglAd85kLI' + '1YCQGfgI8AEgqQg9ogOhPOHTCqERxQiZg48PPgAkFaEvqkYoj/joRAh8SG4BUo1QnvHRgRD4' + 'kNwDpAoht9q9Oe/4NCbE5x1xShpC4EMKA5BshCJ8jr703SLgUzvRw0poJKqE0iHkn/8BPqRQA' + 'MlCqCj4tIIiLUIy4AAfYi1AiRDyvfpPt/P3CoPPnMcvIULgQ7Sf61mshG57CK6/e5fnjd0z62' + 'QNsWmlaTmofI6/kmt84pzg4d/tilZMfzD4tSy8eT7XInwOBvhssREfVkJbBlAdocnRexY6aS3A' + '51sBPpt14dMuQrLQ0IbPmqG+Mz/e/ysbv9sDkCUt2Kx2rGP+dizv+KSZFJ6vHQMfknVcG3bS2' + 'fvYnAgVAZ/036pmIwQ+BIBSIpRnfGRcCp8boXNGrBMCH+LatLMRQm5lT7TjlZ4tecRHNjzNGQ' + '4qoPCdFUdTLlYEH1I4gMK8MDKx1e1a9eGLjr3weB7xUZXmxYpJEZILJPgUPcZfBbMtSfDRAU+' + 'rj4dXx1aID4rqApfo1exn8fDhKlgOKqA84aO63WpnsWK7lRD4EFqwnOCjGp52wWgXIfAhAJQD' + 'fHTBE+c15kNI/v6CDwEg7fjogCdNpdIKIfn7Cz4EgLTioxOetK9TQ+htieuE2sTnAPgUN1wFS' + '5jVfu8O3+u4txU+vqYLFipexwt+hFfFVooPtH11LA0+T695b/PpHx96vQhjhqtgVEBK8dFV8a' + 'jAp7bvU2/vOhZVQukWK/rgQwBINT5+ww9d8KjAp7kdGz3fjiVDaMHndoEPAaA0+OhERyU8c20' + 'zOULgQwBICT6TnnNvcMKWfMsehZx0m/ERAh8CQMrwCU5Irc+yV1VlxV0nNIXQ6QUQAh8CQLnA' + 'RyU8SbY7PTHdCiEffAgA5QEflfNKMtYJzUZo4W2CDwEgw/FRDY+sbTci1M5iRfAhAGQwPrbA04' + 'zQSB2hueeEwIcAkKH4ZPk2HDL2OySnNjHdCiHwIQBkID464NE1hzQXQt1ueT/4EACKmUv83od' + 'U4WPK+//I3nYzQsvd6v6nPwE+pP2UOARCXPiTK/9eiJFNsvEx+S04ZG07Qsg9FyB0Rrxy49t' + '/PXzg7TcYUaTdcDd8QwU04TlfSIuQzXfCJ9m+4/pi/JQjzv7HkuAzqvt/93f/t1m8818g1O' + 'qYcjc8AM2XNHNARYNnBj7PXCwmTweH7Mi9wl20bP/vnnmgT/zPEdowAFowzAE15LjzVn+H6+' + '8OTsKJdk9Wm9+CQzY+YbzhoRuXbvzqTtFz7eWMKAJAChDKw53wabY/Fz61eCNDHwEhAkASE' + 'dJd7eiCLu72F8KnlskAocUgRBYaT8wBzZ1e/+Idnue2fNtVm1utpK/RDj7NW3W6lh04++wDm' + '5kTYg6ICihm3nL+u991vd3Bb7U8Atrkp2ckwSf6GJUQASCzETL96RlJ8QEhAkCGIqRzTinN6' + 'yyEz8LvBgRCBICMQSiLiezETX6Az8QpN8CnV0zMgU+sfQEh0jzGmISOl6QT07a9j/QUPiVx9' + 'vurgsqnLPwjn69PO6b9lxR1YppJaCog7ZWQjZfup/EJ2q5T5aDyuSd6WPP5/9LvI5UQASB1C' + 'GWxZkhWlVXH5+kQn1KEz/RmfWnfh0GIAFBKhDpdb2cNoazQkVllTeNzUYBPpxCDd9dr/1r34' + 'EhEyBv+7UcWhQh1X30FI6qYYQ4oZXr9Vd+a9Do2iwze2kQmdhE+73YE+FwivNMBPkfumr7C1' + 'Tgm6vNATuJZgFljbtGyg8PP3N8nTvziV3keK8wBUQEpqIR+/aUOd3Kn0LRYUWbFMwufp1YLL2' + 'y7Bu8SM86Vxj+krIRanYT+8NANi+742gCVEC0YMRghFS1eDZ/hpy4N8KlM4VN7Db/h25wEhOa' + 'rAEAIgIihCCl7wkUNn+8FbVd4tWvwM1Ov4jcoIwGhEJ522g8QAiBiEEIqJ7UbK59wwtkf3DT1' + 'cb8RmPQIxZ33AKFihUloBUk7Ma36StoUPqWg8rlMTL4bXmrf1CDM1G98p/GPTv33/vRAadhgb' + 'b9nTkynmXTN48R0ykloKiCithLScRm/js+Tlwrv3fBS+6YZ1UztN2kqoXZbLiohQgWUcSWkc9' + '1QdGNp0HaNPPH7AT5lIV69o8bGtCxpKqE6oo6ki7T5qoSogKiAjKmEdC9ajPA5GeDz+OVT+By' + '5o17dOI0VUMJKqLHqkbpY8ezQDV0bqYQAiEhBKJPV0u5khM/oE5cLP8Rn8I6m9ikdQr7nzdm' + 'OJa8Wpru8sB0DIQAiKRFq92kb0kr+UIAAn4mTnWL08SuEd7ISVD6fmo1DQoR8r1GJ2XNCyVdK' + 't/gYCAEQSYdQp+sPCA0rpmtVluN6U/g8dsXUhPORP5q7QomJkO97s9ux8I9OsGXXiX6Vhc8sh' + 'C687kpGVD7CJLTmrPYv2j7uOX1Cwb1jM1q7oPIJK57Rx64Uk+92BPhsaO+eLqfOU8uJ6RkNpB' + 'MNmDo6qfY9RrvmLF52aOT7X+8TJw+/ZtPXnkloAMolQrPmlGr4PHrl1ArnI+tird9piVD9dZz' + 'oytgUOq6UGjrJeWkjQgAEQLlCqOVkdg2fPSE+Ydt1ex2VpAjV4ek43165jsSTMsXgtQwhAAKg' + 'XCA011U0J8Bn8mR1Gp/BtelWMp+vdLzS+WpH6skoaQBbhBAAAZDVCM13+b6Gz7k9V0XrfPwja' + '2ZVM+0iFFY6XgCO1yFvQaEKfGxDCIAAyEqEFlo3VMfnkasDfMJ7u9bU52raRShCp+SKyQ5X8U' + 'moYJvh/wKEzhmOEAABkFUItbNgMcLnRFj5nMdncM3Mq1ZzIRQkbK28UkdQ6XREk8kqTxBl8D' + 'TGcIQACICMR2jME23PCdUrn+9cNfVmYkdundVSzUAoXL8TVDleZ1DplEJ0HC0nhhZ8LEAIgA' + 'DI+Fzsr9w+4bkLIjSFT1eAzzXCj9quW+a4khXEDcDp7Ih+hq2WrhNC1eYX3KyhCAEQAOUCoX' + 'rb9d1rzt/bdfPMK1lhAmgidMohOq72E0Fr1WMJQgAEQNYjVMNn7DvXCu9kpxCv3jw9wMN5nK' + 'C1mqh2RhPKWZwAmVU9FiAEQABkVXr9ldvGPbe/hlAdn4c/FFQ+4SLDm6JFgV5Y6VRKUcWT5c' + 'A3Cp9GhPZ+dYv49eAvAQiASEKEAnxKNXyidT6v3Swmujqn0HGyHfBGwtOYRe8/fO65f+zLGiE' + 'AAiArc1mA0NkTlf7RR68oTQz5YvL1P5n3bnPb4ZGKj0EIARAAWZtF//zJbWLc6ReHvlgyYYDb' + 'hE/9nRoXLTt87tmv9YmT2SAEQABkN0L9A9vExHh9Tgh44p/0WSIEQLPDG5JZlOEdfVtFqXOHq' + 'L29q4SnT8SBx3Z8oo8ND11X2Xj/gLjwQ1cxogCIJEQoOLn0PYteMTy68AEhACKSEHI6y/VKiK' + 'pnJjxtPgYahACImIiQDnh0Vz0gBEDEcIRUw6Oq6kmCDwgBEDEIIR3wZNlygRAAEYUI+aXyt5' + 'MgVMSqZ16EVl57NSMKgEjMjD7U9+U4COmCxwZ8asfDOzt0XXndAw+BEAARRQjpgEd11aMCn8' + 'ZKCIQAiEhGSCc8tlQ9zfiAEAARyQjlAR6d+IAQABFN7Zjp7ZbKlqudTYIQAJGUCIlOdQjlre' + 'oBIQAiFiCkAx4T8AEhACIGIaQaHlVVTxp8QAiAiAEI6YBHVdUja7MhQp0gBEBEH0JFrnpmH4' + 'vgx/Bvryutux+EAIioREgXPKZXPY341BNUQiAEQCQNQqXy9lYI2QyPiqpnFj7NCK244RpGF' + 'ACRuAgN9H2lESEd8Khst7Ti04jQ7f8AQgBE0iDk61isqLjqUTLf0w7Jw0MfBiEAIpIqIaoe' + '0R48IARARE7OKULItqonET4gBEDELIRUPy5I3aOgU24YhGKHBxOSGan0DTwoJsbuE00PP8' + 'y61TIanuZ0Lfv5xPP/tEX85uCrOo8PFRApbCUEPg2tYVAJueuohACIKEdIR7tlEz71TgKEA' + 'IioQ0jHY6FVwqMSHxBqP8wBkXlT3TywVUyM9QbfqsZrH/M8z9p/j6d9o36Xv3jZvslnv/68f' + '3LwLCNqAYAIIYQWjBACQIQQAkCEkNzl/wUYAMs+E8KgHv2mAAAAAElFTkSuQmCC'; + HtmlImage _createRealTestImage() { return HtmlImage( html.ImageElement() @@ -665,6 +789,15 @@ HtmlImage _createRealTestImage() { ); } +HtmlImage _createTestImageWithAlphaChannel() { + return HtmlImage( + html.ImageElement() + ..src = 'data:text/plain;base64,$_flutterLogoBase64', + 20, + 20, + ); +} + class TestImage implements Image { @override int get width => 20; From 9729b552565d657d97d26839dad951d37cbb9257 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Fri, 1 May 2020 23:54:00 -0700 Subject: [PATCH 02/11] Add blend modes --- lib/web_ui/lib/src/engine/bitmap_canvas.dart | 194 +++++++++++++++---- 1 file changed, 154 insertions(+), 40 deletions(-) diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index 1288adf1f7da3..cbc718437af70 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -358,52 +358,112 @@ class BitmapCanvas extends EngineCanvas { _canvasPool.closeCurrentCanvas(); } + html.HtmlElement _createBackgroundImageWithBlend(HtmlImage image, + ui.Color filterColor, ui.BlendMode colorFilterBlendMode, + SurfacePaintData paint) { + // When blending with color we can't use an image element. + // Instead use a div element with background image, color and + // background blend mode. + final html.HtmlElement imgElement = html.DivElement(); + final html.CssStyleDeclaration style = imgElement.style; + switch (colorFilterBlendMode) { + case ui.BlendMode.clear: + case ui.BlendMode.dstOut: + style.position = 'absolute'; + break; + case ui.BlendMode.src: + case ui.BlendMode.srcOver: + style + ..position = 'absolute' + ..backgroundColor = colorToCssString(filterColor); + break; + case ui.BlendMode.dst: + case ui.BlendMode.dstIn: + style + ..position = 'absolute' + ..backgroundImage = "url('${image.imgElement.src}')"; + break; + default: + style + ..position = 'absolute' + ..backgroundImage = "url('${image.imgElement.src}')" + ..backgroundBlendMode = _stringForBlendMode(colorFilterBlendMode) + ..backgroundColor = colorToCssString(filterColor); + break; + } + return imgElement; + } + + html.HtmlElement _createImageElementWithSvgFilter(HtmlImage image, + ui.Color filterColor, ui.BlendMode colorFilterBlendMode, + SurfacePaintData paint) { + // For srcIn blendMode, we use an svg filter to apply to image element. + String svgFilter; + bool isSaturationFilter = false; + switch (colorFilterBlendMode) { + case ui.BlendMode.saturation: + svgFilter = _saturationFilterToSvg(filterColor); + break; + 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; + } + final html.Element filterElement = + html.Element.html(svgFilter, treeSanitizer: _NullTreeSanitizer()); + rootElement.append(filterElement); + _children.add(filterElement); + final html.HtmlElement imgElement = image.cloneImageElement(); + imgElement.style.filter = 'url(#_fcf${_filterIdCounter})'; + if (colorFilterBlendMode == ui.BlendMode.saturation) { + imgElement.style.backgroundColor = colorToCssString(filterColor); + } + return imgElement; + } + html.HtmlElement _drawImage( ui.Image image, ui.Offset p, SurfacePaintData paint) { final HtmlImage htmlImage = image; final ui.BlendMode blendMode = paint.blendMode; - final ui.ColorFilter colorFilter = paint.colorFilter; - final ui.BlendMode colorFilterBlendMode = (colorFilter != null && - colorFilter is EngineColorFilter && colorFilter._blendMode != null) - ? colorFilter._blendMode : null; + final EngineColorFilter colorFilter = paint.colorFilter as EngineColorFilter; + final ui.BlendMode colorFilterBlendMode = colorFilter?._blendMode; html.HtmlElement imgElement; - if (colorFilterBlendMode != ui.BlendMode.srcIn && - colorFilterBlendMode != ui.BlendMode.saturation - && colorFilter is EngineColorFilter) { - // When blending with color we can't use an image element. - // Instead use a div element with background image, color and - // background blend mode. - imgElement = html.DivElement(); - imgElement.style - ..position = 'absolute' - ..backgroundImage = "url('${htmlImage.imgElement.src}')" - ..backgroundBlendMode = - _stringForBlendMode(colorFilterBlendMode) - ..backgroundColor = colorToCssString(colorFilter._color); - } else { + if (colorFilterBlendMode == null) { + // No Blending, create an image by cloning original loaded image. imgElement = htmlImage.cloneImageElement(); - } - imgElement.style.mixBlendMode = _stringForBlendMode(blendMode); - if (colorFilter != null && colorFilter is EngineColorFilter && - (colorFilter._blendMode == ui.BlendMode.srcIn || - colorFilter._blendMode == ui.BlendMode.saturation)) { - // For srcIn blendMode, we use an svg filter to apply to image element. - String svgFilter; - bool isSaturationFilter = colorFilter._blendMode == ui.BlendMode.saturation; - if (isSaturationFilter) { - svgFilter = _saturationFilterToSvg(colorFilter._color); - } else { - svgFilter = _srcInColorFilterToSvg(colorFilter._color); - } - final html.Element filterElement = - html.Element.html(svgFilter, treeSanitizer: _NullTreeSanitizer()); - rootElement.append(filterElement); - _children.add(filterElement); - imgElement.style.filter = 'url(#_fcf${_filterIdCounter})'; - if (isSaturationFilter) { - imgElement.style.backgroundColor = colorToCssString(colorFilter._color); + } else { + switch (colorFilterBlendMode) { + case ui.BlendMode.modulate: + case ui.BlendMode.plus: + case ui.BlendMode.srcIn: + case ui.BlendMode.srcATop: + case ui.BlendMode.srcOut: + case ui.BlendMode.saturation: + case ui.BlendMode.xor: + imgElement = _createImageElementWithSvgFilter(image, + colorFilter._color, colorFilterBlendMode, paint); + break; + default: + imgElement = _createBackgroundImageWithBlend(image, + colorFilter._color, colorFilterBlendMode, paint); + break; } } + imgElement.style.mixBlendMode = _stringForBlendMode(blendMode); if (_canvasPool.isClipped) { // Reset width/height since they may have been previously set. imgElement.style..removeProperty('width')..removeProperty('height'); @@ -871,7 +931,7 @@ String _srcInColorFilterToSvg(ui.Color color) { final double g = color.green / 255.0; final double a = color.alpha / 255.0; return '' - '' + '' '' - '' + '' '' ''; } + +String _srcOutColorFilterToSvg(ui.Color color) { + _filterIdCounter += 1; + return '' + '' + '' + '' + '' + '' + ''; +} + +String _xorColorFilterToSvg(ui.Color color) { + _filterIdCounter += 1; + return '' + '' + '' + '' + '' + '' + ''; +} + +// 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 '' + '' + '' + '' + '' + '' + ''; +} + +// 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 '' + '' + '' + '' + '' + ''; +} From f2f733b93af12446273927b5746464d6f6c42527 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Sat, 2 May 2020 12:08:26 -0700 Subject: [PATCH 03/11] complete set of blend modes --- lib/web_ui/lib/src/engine/bitmap_canvas.dart | 111 ++++++++++++------- 1 file changed, 74 insertions(+), 37 deletions(-) diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index cbc718437af70..051d28f9933a1 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -401,9 +401,6 @@ class BitmapCanvas extends EngineCanvas { String svgFilter; bool isSaturationFilter = false; switch (colorFilterBlendMode) { - case ui.BlendMode.saturation: - svgFilter = _saturationFilterToSvg(filterColor); - break; case ui.BlendMode.srcIn: case ui.BlendMode.srcATop: svgFilter = _srcInColorFilterToSvg(filterColor); @@ -422,6 +419,31 @@ class BitmapCanvas extends EngineCanvas { // 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; } final html.Element filterElement = html.Element.html(svgFilter, treeSanitizer: _NullTreeSanitizer()); @@ -447,12 +469,18 @@ class BitmapCanvas extends EngineCanvas { imgElement = htmlImage.cloneImageElement(); } else { switch (colorFilterBlendMode) { + case ui.BlendMode.colorBurn: + case ui.BlendMode.colorDodge: + case ui.BlendMode.hue: case ui.BlendMode.modulate: + case ui.BlendMode.overlay: case ui.BlendMode.plus: case ui.BlendMode.srcIn: case ui.BlendMode.srcATop: case ui.BlendMode.srcOut: case ui.BlendMode.saturation: + case ui.BlendMode.color: + case ui.BlendMode.luminosity: case ui.BlendMode.xor: imgElement = _createImageElementWithSvgFilter(image, colorFilter._color, colorFilterBlendMode, paint); @@ -466,7 +494,9 @@ class BitmapCanvas extends EngineCanvas { imgElement.style.mixBlendMode = _stringForBlendMode(blendMode); if (_canvasPool.isClipped) { // Reset width/height since they may have been previously set. - imgElement.style..removeProperty('width')..removeProperty('height'); + imgElement.style + ..removeProperty('width') + ..removeProperty('height'); final List clipElements = _clipContent( _canvasPool._clipStack, imgElement, p, _canvasPool.currentTransform); for (html.Element clipElement in clipElements) { @@ -911,7 +941,8 @@ String _maskFilterToCss(ui.MaskFilter maskFilter) { int _filterIdCounter = 0; -// The color matrix changes colors based on the following: +// 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 | @@ -923,42 +954,28 @@ int _filterIdCounter = 0; // 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; - final double r = color.red / 255.0; - final double b = color.blue / 255.0; - final double g = color.green / 255.0; - final double a = color.alpha / 255.0; return '' - '' - '' // alpha is identical. - ''; -} - -String _saturationFilterToSvg(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; - final double cMax = math.max(r, math.max(b, g)); - final double cMin = math.min(r, math.min(b, g)); - final double delta = cMax - cMin; - final double lightness = (cMax + cMin) / 2.0; - final double saturation = delta / (1.0 - (2 * lightness - 1.0).abs()); - return '' - '' - '' + '' + '' // Just take alpha channel of of destination + '' + '' + '' + '' ''; } String _srcOutColorFilterToSvg(ui.Color color) { _filterIdCounter += 1; return '' - '' + '' '' '' '' @@ -969,7 +986,8 @@ String _srcOutColorFilterToSvg(ui.Color color) { String _xorColorFilterToSvg(ui.Color color) { _filterIdCounter += 1; return '' - '' + '' '' '' '' @@ -982,10 +1000,12 @@ String _xorColorFilterToSvg(ui.Color color) { String _compositeColorFilterToSvg(ui.Color color, double k1, double k2, double k3 , double k4) { _filterIdCounter += 1; return '' - '' + '' '' '' - '' + '' '' ''; } @@ -999,12 +1019,29 @@ String _modulateColorFilterToSvg(ui.Color color) { final double b = color.blue / 255.0; final double g = color.green / 255.0; return '' - '' + '' '' - '' + '' '' ''; } + +// Uses feBlend element to blend source image with a color. +String _blendColorFilterToSvg(ui.Color color, String feBlend, + {bool swapLayers = false}) { + _filterIdCounter += 1; + return '' + '' + '' + '' + + (swapLayers + ? '' + : '') + + ''; +} From 264c87dc365925c23c3f515dfb688f7ad38772da Mon Sep 17 00:00:00 2001 From: ferhatb Date: Sun, 3 May 2020 11:04:53 -0700 Subject: [PATCH 04/11] Add golden test for blend modes, fix size setting when using drawImage instead of drawImageRect --- lib/web_ui/lib/src/engine/bitmap_canvas.dart | 48 ++-- .../engine/canvas_image_blend_mode_test.dart | 224 ++++++++++++++++++ 2 files changed, 256 insertions(+), 16 deletions(-) create mode 100644 lib/web_ui/test/golden_tests/engine/canvas_image_blend_mode_test.dart diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index 051d28f9933a1..c41803cff6dc5 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -353,7 +353,11 @@ class BitmapCanvas extends EngineCanvas { @override void drawImage(ui.Image image, ui.Offset p, SurfacePaintData paint) { - _drawImage(image, p, paint); + final html.HtmlElement imageElement = _drawImage(image, p, paint); + if (paint.colorFilter != null) { + _applyTargetSize(imageElement, image.width.toDouble(), + image.height.toDouble()); + } _childOverdraw = true; _canvasPool.closeCurrentCanvas(); } @@ -525,17 +529,19 @@ class BitmapCanvas extends EngineCanvas { src.top != 0 || src.width != image.width || src.height != image.height; + // If source and destination sizes are identical, we can skip the longer + // code path that sets the size of the element and clips. + // + // If there is a color filter set however, we maybe using background-image + // to render therefore we have to explicitely set width/height of the + // element for blending to work with background-color. if (dst.width == image.width && dst.height == image.height && - !requiresClipping) { + !requiresClipping && + paint.colorFilter == null) { html.Element imgElement = _drawImage(image, dst.topLeft, paint); _childOverdraw = true; _canvasPool.closeCurrentCanvas(); - if (imgElement is! html.ImageElement) { - imgElement.style.backgroundSize = - '${dst.width.toStringAsFixed(2)}px ' - '${dst.height.toStringAsFixed(2)}px'; - } } else { if (requiresClipping) { save(); @@ -566,15 +572,7 @@ class BitmapCanvas extends EngineCanvas { targetWidth *= image.width / src.width; targetHeight *= image.height / src.height; } - final html.CssStyleDeclaration imageStyle = imgElement.style; - final String widthPx = '${targetWidth.toStringAsFixed(2)}px'; - final String heightPx = '${targetHeight.toStringAsFixed(2)}px'; - imageStyle - ..width = widthPx - ..height = heightPx; - if (imgElement is! html.ImageElement) { - imgElement.style.backgroundSize = '$widthPx $heightPx'; - } + _applyTargetSize(imgElement, targetWidth, targetHeight); if (requiresClipping) { restore(); } @@ -582,6 +580,24 @@ class BitmapCanvas extends EngineCanvas { _closeCurrentCanvas(); } + void _applyTargetSize(html.HtmlElement imageElement, double targetWidth, + double targetHeight) { + final html.CssStyleDeclaration imageStyle = imageElement.style; + final String widthPx = '${targetWidth.toStringAsFixed(2)}px'; + final String heightPx = '${targetHeight.toStringAsFixed(2)}px'; + imageStyle + // left,top are set to 0 (although position is absolute) because + // Chrome will glitch if you leave them out, reproducable with + // canvas_image_blend_test on row 6, MacOS / Chrome 81.04. + ..left = "0px" + ..top = "0px" + ..width = widthPx + ..height = heightPx; + if (imageElement is! html.ImageElement) { + imageElement.style.backgroundSize = '$widthPx $heightPx'; + } + } + // Should be called when we add new html elements into rootElement so that // paint order is preserved. // diff --git a/lib/web_ui/test/golden_tests/engine/canvas_image_blend_mode_test.dart b/lib/web_ui/test/golden_tests/engine/canvas_image_blend_mode_test.dart new file mode 100644 index 0000000000000..bb914992fa98d --- /dev/null +++ b/lib/web_ui/test/golden_tests/engine/canvas_image_blend_mode_test.dart @@ -0,0 +1,224 @@ +// 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 'dart:js_util' as js_util; + +import 'package:ui/ui.dart' hide TextStyle; +import 'package:ui/src/engine.dart'; +import 'package:test/test.dart'; + +import 'package:web_engine_tester/golden_tester.dart'; + +void main() async { + const double screenWidth = 600.0; + const double screenHeight = 800.0; + const Rect screenRect = Rect.fromLTWH(0, 0, screenWidth, screenHeight); + + // Commit a recording canvas to a bitmap, and compare with the expected + Future _checkScreenshot(RecordingCanvas rc, String fileName, + {Rect region = const Rect.fromLTWH(0, 0, 500, 500), + double maxDiffRatePercent = 0.0}) async { + final EngineCanvas engineCanvas = BitmapCanvas(screenRect); + + rc.endRecording(); + rc.apply(engineCanvas, screenRect); + + // Wrap in so that our CSS selectors kick in. + final html.Element sceneElement = html.Element.tag('flt-scene'); + try { + sceneElement.append(engineCanvas.rootElement); + html.document.body.append(sceneElement); + await matchGoldenFile('$fileName.png', region: region, maxDiffRatePercent: maxDiffRatePercent, write: true); + } finally { + // The page is reused across tests, so remove the element after taking the + // Scuba screenshot. + sceneElement.remove(); + } + } + + setUp(() async { + debugEmulateFlutterTesterEnvironment = true; + await webOnlyInitializePlatform(); + webOnlyFontCollection.debugRegisterTestFonts(); + await webOnlyFontCollection.ensureFontsLoaded(); + }); + + const Color red = Color(0xFFFF0000); + const Color green = Color(0xFF00FF00); + const Color blue = Color(0xFF2196F3); + const Color white = Color(0xFFFFFFFF); + const Color grey = Color(0xFF808080); + const Color black = Color(0xFF000000); + + List> modes = [[BlendMode.clear, BlendMode.src, BlendMode.dst, + BlendMode.srcOver, BlendMode.dstOver, BlendMode.srcIn, BlendMode.dstIn, BlendMode.srcOut], + [BlendMode.dstOut, BlendMode.srcATop, BlendMode.dstATop, BlendMode.xor, + BlendMode.plus, BlendMode.modulate, BlendMode.screen, BlendMode.overlay], + [BlendMode.darken, BlendMode.lighten, BlendMode.colorDodge, BlendMode.hardLight, + BlendMode.softLight, BlendMode.difference, BlendMode.exclusion, BlendMode.multiply], + [BlendMode.hue, BlendMode.saturation, BlendMode.color, + BlendMode.luminosity]]; + + for (int blendGroup = 0; blendGroup < 8; ++blendGroup) { + test('Draw image with Group1 blend modes', () async { + final RecordingCanvas rc = RecordingCanvas( + const Rect.fromLTRB(0, 0, 400, 400)); + rc.save(); + List blendModes = modes[blendGroup]; + for (int row = 0; row < blendModes.length; row++) { + // draw white background for first 4, black for next 4 blends. + double top = row * 50.0; + rc.drawRect(Rect.fromLTWH(0, top, 200, 50), Paint() + ..color = white); + rc.drawRect(Rect.fromLTWH(200, top, 200, 50), Paint() + ..color = grey); + BlendMode blendMode = blendModes[row]; + rc.drawImage(createTestImage(), Offset(0, top), + Paint() + ..colorFilter = EngineColorFilter.mode(red, blendMode)); + rc.drawImage(createTestImage(), Offset(50, top), + Paint() + ..colorFilter = EngineColorFilter.mode(green, blendMode)); + rc.drawImage(createTestImage(), Offset(100, top), + Paint() + ..colorFilter = EngineColorFilter.mode(blue, blendMode)); + rc.drawImage(createTestImage(), Offset(150, top), + Paint() + ..colorFilter = EngineColorFilter.mode(black, blendMode)); + rc.drawImage(createTestImage(), Offset(200, top), + Paint() + ..colorFilter = EngineColorFilter.mode(red, blendMode)); + rc.drawImage(createTestImage(), Offset(250, top), + Paint() + ..colorFilter = EngineColorFilter.mode(green, blendMode)); + rc.drawImage(createTestImage(), Offset(300, top), + Paint() + ..colorFilter = EngineColorFilter.mode(blue, blendMode)); + rc.drawImage(createTestImage(), Offset(350, top), + Paint() + ..colorFilter = EngineColorFilter.mode(black, blendMode)); + } + rc.restore(); + await _checkScreenshot(rc, 'canvas_image_blend_group$blendGroup'); + }); + } +} + +const String _flutterLogoBase64 = + 'iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAAXNSR0IArs4c6QAAAKRlWElm' + 'TU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgExAAIAAAAg' + 'AAAAWodpAAQAAAABAAAAegAAAAAAAABIAAAAAQAAAEgAAAABQWRvYmUgUGhvdG9zaG9wIENT' + 'NiAoTWFjaW50b3NoKQAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAMqADAAQAAAABAAAAMgAA' + 'AABWBXsWAAAACXBIWXMAAAsTAAALEwEAmpwYAAAEemlUWHRYTUw6Y29tLmFkb2JlLnhtcAAA' + 'AAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENv' + 'cmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5' + 'OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjph' + 'Ym91dD0iIgogICAgICAgICAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94' + 'YXAvMS4wL21tLyIKICAgICAgICAgICAgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5j' + 'b20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiCiAgICAgICAgICAgIHhtbG5zOnhtcD0i' + 'aHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6dGlmZj0i' + 'aHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iPgogICAgICAgICA8eG1wTU06SW5zdGFu' + 'Y2VJRD54bXAuaWlkOjMyOERERjc5ODRCRjExRUE5QUE4OEM5NTZDREM5QkUyPC94bXBNTTp' + 'JbnN0YW5jZUlEPgogICAgICAgICA8eG1wTU06RG9jdW1lbnRJRD54bXAuZGlkOjMyOERERj' + 'dBODRCRjExRUE5QUE4OEM5NTZDREM5QkUyPC94bXBNTTpEb2N1bWVudElEPgogICAgICAgI' + 'CA8eG1wTU06T3JpZ2luYWxEb2N1bWVudElEPnhtcC5kaWQ6MDE4MDExNzQwNzIwNjgxMTgy' + 'MkFBQjU0OEFBMDMwM0E8L3htcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD4KICAgICAgICAgPHht' + 'cE1NOkRlcml2ZWRGcm9tIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICA' + 'gPHN0UmVmOmluc3RhbmNlSUQ+eG1wLmlpZDowNDgwMTE3NDA3MjA2ODExODIyQUFCNTQ4QU' + 'EwMzAzQTwvc3RSZWY6aW5zdGFuY2VJRD4KICAgICAgICAgICAgPHN0UmVmOmRvY3VtZW50SU' + 'Q+eG1wLmRpZDowMTgwMTE3NDA3MjA2ODExODIyQUFCNTQ4QUEwMzAzQTwvc3RSZWY6ZG9jd' + 'W1lbnRJRD4KICAgICAgICAgPC94bXBNTTpEZXJpdmVkRnJvbT4KICAgICAgICAgPHhtcDpD' + 'cmVhdG9yVG9vbD5BZG9iZSBQaG90b3Nob3AgQ1M2IChNYWNpbnRvc2gpPC94bXA6Q3JlYXRv' + 'clRvb2w+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb2' + '4+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhP' + 'gr/+ApQAAAQNUlEQVRoBbVaDYxdRRWemTv3vt2WbrctpRYxtCVU4io1LomElrjYWnbbgtr2' + 'CbISwGpR0UBUjD8YHyZCNCqIP5GmtLWSAn3SVYqw7PbnFUQFXGOMRUFlW9G2gKalu/veu38' + 'zfufce3ff275dWtBJ5s7cuTPnnO+cM2d+3pN2urDiVSHEVCFuOpgT35vVIuaLV8Tgtw8Icf' + 'PZkZgntDggrhNCbhHCuihD9J402Y4OLUulqNy1+iwvFIscaaqRMWqyQRofIxFH2ohpwqjDc' + 't/OZyyYSTAVHQUtSoVIfPiL7xWu3i2i0KA7PnFmstpaEVMLBnHm1rFHhEbiQZ9PKtl83pPF' + 'YnB0xVVne2HlKcdz5wgTC62cZLwlGdJUSxXtWnngFItIOhdzj3xeiWIxzrpPVmpAZg4EJjc' + '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'; + +HtmlImage createTestImage() { + return HtmlImage( + html.ImageElement() + ..src = 'data:text/plain;base64,$_flutterLogoBase64', + 50, + 50, + ); +} From 4c403f178dd7299463ada8e2ca86b362884a85a8 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Sun, 3 May 2020 11:15:12 -0700 Subject: [PATCH 05/11] Add comments --- lib/web_ui/lib/src/engine/bitmap_canvas.dart | 213 ++++++++++--------- 1 file changed, 114 insertions(+), 99 deletions(-) diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index c41803cff6dc5..d54073e6e4dd2 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -362,105 +362,6 @@ class BitmapCanvas extends EngineCanvas { _canvasPool.closeCurrentCanvas(); } - html.HtmlElement _createBackgroundImageWithBlend(HtmlImage image, - ui.Color filterColor, ui.BlendMode colorFilterBlendMode, - SurfacePaintData paint) { - // When blending with color we can't use an image element. - // Instead use a div element with background image, color and - // background blend mode. - final html.HtmlElement imgElement = html.DivElement(); - final html.CssStyleDeclaration style = imgElement.style; - switch (colorFilterBlendMode) { - case ui.BlendMode.clear: - case ui.BlendMode.dstOut: - style.position = 'absolute'; - break; - case ui.BlendMode.src: - case ui.BlendMode.srcOver: - style - ..position = 'absolute' - ..backgroundColor = colorToCssString(filterColor); - break; - case ui.BlendMode.dst: - case ui.BlendMode.dstIn: - style - ..position = 'absolute' - ..backgroundImage = "url('${image.imgElement.src}')"; - break; - default: - style - ..position = 'absolute' - ..backgroundImage = "url('${image.imgElement.src}')" - ..backgroundBlendMode = _stringForBlendMode(colorFilterBlendMode) - ..backgroundColor = colorToCssString(filterColor); - break; - } - return imgElement; - } - - html.HtmlElement _createImageElementWithSvgFilter(HtmlImage image, - ui.Color filterColor, ui.BlendMode colorFilterBlendMode, - SurfacePaintData paint) { - // For srcIn blendMode, we use an svg filter to apply to image element. - String svgFilter; - bool isSaturationFilter = false; - 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; - } - final html.Element filterElement = - html.Element.html(svgFilter, treeSanitizer: _NullTreeSanitizer()); - rootElement.append(filterElement); - _children.add(filterElement); - final html.HtmlElement imgElement = image.cloneImageElement(); - imgElement.style.filter = 'url(#_fcf${_filterIdCounter})'; - if (colorFilterBlendMode == ui.BlendMode.saturation) { - imgElement.style.backgroundColor = colorToCssString(filterColor); - } - return imgElement; - } - html.HtmlElement _drawImage( ui.Image image, ui.Offset p, SurfacePaintData paint) { final HtmlImage htmlImage = image; @@ -598,6 +499,120 @@ class BitmapCanvas extends EngineCanvas { } } + // Creates a Div element to render an image using background-image css + // attribute to be able to use background blend mode(s) when possible. + // + // Example:
+ // + // Special cases: + // For clear,dstOut it generates a blank element. + // For src,srcOver it only sets background-color attribute. + // For dst,dstIn , it only sets source not background color. + html.HtmlElement _createBackgroundImageWithBlend(HtmlImage image, + ui.Color filterColor, ui.BlendMode colorFilterBlendMode, + SurfacePaintData paint) { + // When blending with color we can't use an image element. + // Instead use a div element with background image, color and + // background blend mode. + final html.HtmlElement imgElement = html.DivElement(); + final html.CssStyleDeclaration style = imgElement.style; + switch (colorFilterBlendMode) { + case ui.BlendMode.clear: + case ui.BlendMode.dstOut: + style.position = 'absolute'; + break; + case ui.BlendMode.src: + case ui.BlendMode.srcOver: + style + ..position = 'absolute' + ..backgroundColor = colorToCssString(filterColor); + break; + case ui.BlendMode.dst: + case ui.BlendMode.dstIn: + style + ..position = 'absolute' + ..backgroundImage = "url('${image.imgElement.src}')"; + break; + default: + style + ..position = 'absolute' + ..backgroundImage = "url('${image.imgElement.src}')" + ..backgroundBlendMode = _stringForBlendMode(colorFilterBlendMode) + ..backgroundColor = colorToCssString(filterColor); + break; + } + return imgElement; + } + + // Creates an image element and an svg filter to apply on the element. + html.HtmlElement _createImageElementWithSvgFilter(HtmlImage image, + ui.Color filterColor, ui.BlendMode colorFilterBlendMode, + SurfacePaintData paint) { + // For srcIn blendMode, we use an svg filter to apply to image element. + String svgFilter; + bool isSaturationFilter = false; + 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; + } + final html.Element filterElement = + html.Element.html(svgFilter, treeSanitizer: _NullTreeSanitizer()); + rootElement.append(filterElement); + _children.add(filterElement); + final html.HtmlElement imgElement = image.cloneImageElement(); + imgElement.style.filter = 'url(#_fcf${_filterIdCounter})'; + if (colorFilterBlendMode == ui.BlendMode.saturation) { + imgElement.style.backgroundColor = colorToCssString(filterColor); + } + return imgElement; + } + // Should be called when we add new html elements into rootElement so that // paint order is preserved. // From 707a459043e154157194b408d63bad157b707557 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Mon, 4 May 2020 09:43:37 -0700 Subject: [PATCH 06/11] Update golden locks --- lib/web_ui/dev/goldens_lock.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/web_ui/dev/goldens_lock.yaml b/lib/web_ui/dev/goldens_lock.yaml index 44bd90cbc64e6..e761b7bed1667 100644 --- a/lib/web_ui/dev/goldens_lock.yaml +++ b/lib/web_ui/dev/goldens_lock.yaml @@ -1,2 +1,2 @@ repository: https://github.com/flutter/goldens.git -revision: f64d8957ae281d1558647f0591ff9742e6135385 +revision: 790616cbfb269fe17d44840ce52ec187fff5f9a7 From 338eb368a218fae487570a10cea3e58d695aadf2 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Mon, 4 May 2020 11:01:04 -0700 Subject: [PATCH 07/11] Fix analyzer errors --- lib/web_ui/lib/src/engine/bitmap_canvas.dart | 5 +- .../engine/canvas_image_blend_mode_test.dart | 1 - .../engine/recording_canvas_golden_test.dart | 132 ------------------ 3 files changed, 3 insertions(+), 135 deletions(-) diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index 478d84cb5dd3b..71171ccc7a797 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -441,7 +441,7 @@ class BitmapCanvas extends EngineCanvas { dst.height == image.height && !requiresClipping && paint.colorFilter == null) { - html.Element imgElement = _drawImage(image, dst.topLeft, paint); + _drawImage(image, dst.topLeft, paint); _childOverdraw = true; _canvasPool.closeCurrentCanvas(); } else { @@ -555,7 +555,6 @@ class BitmapCanvas extends EngineCanvas { SurfacePaintData paint) { // For srcIn blendMode, we use an svg filter to apply to image element. String svgFilter; - bool isSaturationFilter = false; switch (colorFilterBlendMode) { case ui.BlendMode.srcIn: case ui.BlendMode.srcATop: @@ -601,6 +600,8 @@ class BitmapCanvas extends EngineCanvas { svgFilter = _blendColorFilterToSvg(filterColor, _stringForBlendMode(colorFilterBlendMode)); break; + default: + break; } final html.Element filterElement = html.Element.html(svgFilter, treeSanitizer: _NullTreeSanitizer()); diff --git a/lib/web_ui/test/golden_tests/engine/canvas_image_blend_mode_test.dart b/lib/web_ui/test/golden_tests/engine/canvas_image_blend_mode_test.dart index bb914992fa98d..a1f2bcf724be6 100644 --- a/lib/web_ui/test/golden_tests/engine/canvas_image_blend_mode_test.dart +++ b/lib/web_ui/test/golden_tests/engine/canvas_image_blend_mode_test.dart @@ -4,7 +4,6 @@ // @dart = 2.6 import 'dart:html' as html; -import 'dart:js_util' as js_util; import 'package:ui/ui.dart' hide TextStyle; import 'package:ui/src/engine.dart'; diff --git a/lib/web_ui/test/golden_tests/engine/recording_canvas_golden_test.dart b/lib/web_ui/test/golden_tests/engine/recording_canvas_golden_test.dart index 3b8f3f6f35908..c974485c12635 100644 --- a/lib/web_ui/test/golden_tests/engine/recording_canvas_golden_test.dart +++ b/lib/web_ui/test/golden_tests/engine/recording_canvas_golden_test.dart @@ -657,129 +657,6 @@ const String _base64Encoded20x20TestImage = 'iVBORw0KGgoAAAANSUhEUgAAABQAAAAUC' 'B3RJTUUH5AMFFBksg4i3gQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAj' 'SURBVDjLY2TAC/7jlWVioACMah4ZmhnxpyHG0QAb1UyZZgBjWAIm/clP0AAAAABJRU5ErkJggg=='; -const String _flutterLogoBase64 = 'iVBORw0KGgoAAAANSUhEUgAAASAAAAEQCAYAAAAQ+akt' - 'AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZ' - 'G9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek' - '5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdG' - 's9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1Nj' - 'oyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOT' - 'k5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9Ii' - 'IgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOn' - 'N0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIi' - 'B4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW' - '50SUQ9InhtcC5kaWQ6MzFFNzQxN0E4NDBBMTFFQTk4RThBQjg5NEIyOENFQTciIHhtcE1NO' - 'kluc3RhbmNlSUQ9InhtcC5paWQ6MzFFNzQxNzk4NDBBMTFFQTk4RThBQjg5NEIyOENFQTci' - 'IHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSI+IDx4' - 'bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuZGlkOjAxODAxMTc0MDcyM' - 'DY4MTE4MjJBQUI1NDhBQTAzMDNBIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjAxODAxMT' - 'c0MDcyMDY4MTE4MjJBQUI1NDhBQTAzMDNBIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjp' - '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'; - HtmlImage _createRealTestImage() { return HtmlImage( html.ImageElement() @@ -789,15 +666,6 @@ HtmlImage _createRealTestImage() { ); } -HtmlImage _createTestImageWithAlphaChannel() { - return HtmlImage( - html.ImageElement() - ..src = 'data:text/plain;base64,$_flutterLogoBase64', - 20, - 20, - ); -} - class TestImage implements Image { @override int get width => 20; From 6f3e3cfbab2fed02fd2e3919af555a5dcb1ec039 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Mon, 4 May 2020 11:24:04 -0700 Subject: [PATCH 08/11] disable write:true on golden --- .../test/golden_tests/engine/canvas_image_blend_mode_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/web_ui/test/golden_tests/engine/canvas_image_blend_mode_test.dart b/lib/web_ui/test/golden_tests/engine/canvas_image_blend_mode_test.dart index a1f2bcf724be6..62d4b6db15610 100644 --- a/lib/web_ui/test/golden_tests/engine/canvas_image_blend_mode_test.dart +++ b/lib/web_ui/test/golden_tests/engine/canvas_image_blend_mode_test.dart @@ -30,7 +30,7 @@ void main() async { try { sceneElement.append(engineCanvas.rootElement); html.document.body.append(sceneElement); - await matchGoldenFile('$fileName.png', region: region, maxDiffRatePercent: maxDiffRatePercent, write: true); + await matchGoldenFile('$fileName.png', region: region, maxDiffRatePercent: maxDiffRatePercent); } finally { // The page is reused across tests, so remove the element after taking the // Scuba screenshot. From 9ce84ab798abf3bf50173a114cd5818aee3230cb Mon Sep 17 00:00:00 2001 From: Ferhat Date: Mon, 4 May 2020 17:31:34 -0700 Subject: [PATCH 09/11] Update lib/web_ui/lib/src/engine/bitmap_canvas.dart Co-authored-by: Harry Terkelsen --- lib/web_ui/lib/src/engine/bitmap_canvas.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index 71171ccc7a797..cc87fdcb2cadb 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -995,7 +995,7 @@ String _srcInColorFilterToSvg(ui.Color color) { '' // Just take alpha channel of of destination + '0 0 0 1 0" result="destalpha"/>' // Just take alpha channel of destination '' '' ' Date: Mon, 4 May 2020 17:36:18 -0700 Subject: [PATCH 10/11] Add logo image size to comment --- .../test/golden_tests/engine/canvas_image_blend_mode_test.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/web_ui/test/golden_tests/engine/canvas_image_blend_mode_test.dart b/lib/web_ui/test/golden_tests/engine/canvas_image_blend_mode_test.dart index 62d4b6db15610..814f2101bf0e6 100644 --- a/lib/web_ui/test/golden_tests/engine/canvas_image_blend_mode_test.dart +++ b/lib/web_ui/test/golden_tests/engine/canvas_image_blend_mode_test.dart @@ -106,6 +106,7 @@ void main() async { } } +// 50x50 pixel flutter logo image. const String _flutterLogoBase64 = 'iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAAXNSR0IArs4c6QAAAKRlWElm' 'TU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgExAAIAAAAg' From b1790c7c064b664b112087c1746f6fbf57c4db69 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Tue, 5 May 2020 08:09:24 -0700 Subject: [PATCH 11/11] fix blend group count in test --- .../golden_tests/engine/canvas_image_blend_mode_test.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/web_ui/test/golden_tests/engine/canvas_image_blend_mode_test.dart b/lib/web_ui/test/golden_tests/engine/canvas_image_blend_mode_test.dart index 814f2101bf0e6..2753863abe37e 100644 --- a/lib/web_ui/test/golden_tests/engine/canvas_image_blend_mode_test.dart +++ b/lib/web_ui/test/golden_tests/engine/canvas_image_blend_mode_test.dart @@ -61,8 +61,8 @@ void main() async { [BlendMode.hue, BlendMode.saturation, BlendMode.color, BlendMode.luminosity]]; - for (int blendGroup = 0; blendGroup < 8; ++blendGroup) { - test('Draw image with Group1 blend modes', () async { + for (int blendGroup = 0; blendGroup < 4; ++blendGroup) { + test('Draw image with Group$blendGroup blend modes', () async { final RecordingCanvas rc = RecordingCanvas( const Rect.fromLTRB(0, 0, 400, 400)); rc.save(); @@ -101,7 +101,8 @@ void main() async { ..colorFilter = EngineColorFilter.mode(black, blendMode)); } rc.restore(); - await _checkScreenshot(rc, 'canvas_image_blend_group$blendGroup'); + await _checkScreenshot(rc, 'canvas_image_blend_group$blendGroup', + maxDiffRatePercent: 8.0); }); } }