From ba316640ad421f2d59b7c542c9bfceaa41620913 Mon Sep 17 00:00:00 2001 From: Dwayne Slater Date: Thu, 14 Nov 2019 16:32:50 -0800 Subject: [PATCH 1/8] Add ability to control dithering with Paint --- lib/ui/painting.dart | 25 ++++++++++++++++++++++++- lib/ui/painting/paint.cc | 7 ++++++- lib/web_ui/lib/src/ui/painting.dart | 15 +++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 0eacb1bca4e07..51602bdde7f64 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -1058,6 +1058,7 @@ class Paint { static const int _kMaskFilterBlurStyleIndex = 10; static const int _kMaskFilterSigmaIndex = 11; static const int _kInvertColorIndex = 12; + static const int _kDitherIndex = 13; static const int _kIsAntiAliasOffset = _kIsAntiAliasIndex << 2; static const int _kColorOffset = _kColorIndex << 2; @@ -1072,8 +1073,9 @@ class Paint { static const int _kMaskFilterBlurStyleOffset = _kMaskFilterBlurStyleIndex << 2; static const int _kMaskFilterSigmaOffset = _kMaskFilterSigmaIndex << 2; static const int _kInvertColorOffset = _kInvertColorIndex << 2; + static const int _kDitherOffset = _kDitherIndex << 2; // If you add more fields, remember to update _kDataByteCount. - static const int _kDataByteCount = 52; + static const int _kDataByteCount = 56; // Binary format must match the deserialization code in paint.cc. List _objects; @@ -1401,6 +1403,25 @@ class Paint { _data.setInt32(_kInvertColorOffset, value ? 1 : 0, _kFakeHostEndian); } + /// Whether to dither the output when drawing images. + /// + /// If false, the default value, dithering will be enabled when the input + /// color depth is higher than the output color depth. For example, + /// drawing an RGB8 image onto an RGB565 canvas. + /// + /// This value also controls dithering of [shader]s, which can make + /// gradients appear smoother. + /// + /// Whether or not dithering affects the output is implementation defined. + /// Some implementations may choose to ignore this completely, if they're + /// unable to control dithering. + bool get dither { + return _data.getInt32(_kDitherOffset, _kFakeHostEndian) == 1; + } + set dither(bool value) { + _data.setInt32(_kDitherOffset, value ? 1 : 0, _kFakeHostEndian); + } + @override String toString() { final StringBuffer result = StringBuffer(); @@ -1459,6 +1480,8 @@ class Paint { } if (invertColors) result.write('${semicolon}invert: $invertColors'); + if (dither) + result.write('${semicolon}dither: $dither'); result.write(')'); return result.toString(); } diff --git a/lib/ui/painting/paint.cc b/lib/ui/painting/paint.cc index 00006c3f989e9..2648ff2f66f41 100644 --- a/lib/ui/painting/paint.cc +++ b/lib/ui/painting/paint.cc @@ -32,7 +32,8 @@ constexpr int kMaskFilterIndex = 9; constexpr int kMaskFilterBlurStyleIndex = 10; constexpr int kMaskFilterSigmaIndex = 11; constexpr int kInvertColorIndex = 12; -constexpr size_t kDataByteCount = 52; // 4 * (last index + 1) +constexpr int kDitherIndex = 13; +constexpr size_t kDataByteCount = 56; // 4 * (last index + 1) // Indices for objects. constexpr int kShaderIndex = 0; @@ -154,6 +155,10 @@ Paint::Paint(Dart_Handle paint_objects, Dart_Handle paint_data) { paint_.setColorFilter(invert_filter); } + if (uint_data[kDitherIndex]) { + paint_.setDither(true); + } + switch (uint_data[kMaskFilterIndex]) { case Null: break; diff --git a/lib/web_ui/lib/src/ui/painting.dart b/lib/web_ui/lib/src/ui/painting.dart index ff280ad0bda16..0131f614d35b5 100644 --- a/lib/web_ui/lib/src/ui/painting.dart +++ b/lib/web_ui/lib/src/ui/painting.dart @@ -1174,6 +1174,21 @@ class Paint { // TODO(flutter/flutter#35156): Implement ImageFilter. } + /// Whether to dither the output when drawing images. + /// + /// If false, the default value, dithering will be enabled when the input + /// color depth is higher than the output color depth. For example, + /// drawing an RGB8 image onto an RGB565 canvas. + /// + /// This value also controls dithering of [shader]s, which can make + /// gradients appear smoother. + /// + /// Whether or not dithering affects the output is implementation defined. + /// Some implementations may choose to ignore this completely, if they're + /// unable to control dithering. + bool get dither => false; // Not implemented on web + set dither(bool value) {} + // True if Paint instance has used in RecordingCanvas. bool _frozen = false; From dbe803e7c3cfe4b8258c678545c0ad5a93f36d4c Mon Sep 17 00:00:00 2001 From: Dwayne Slater Date: Thu, 14 Nov 2019 16:38:54 -0800 Subject: [PATCH 2/8] Fix trailing spaces --- lib/ui/painting.dart | 10 +++++----- lib/web_ui/lib/src/ui/painting.dart | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 51602bdde7f64..7453cab999627 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -1404,14 +1404,14 @@ class Paint { } /// Whether to dither the output when drawing images. - /// + /// /// If false, the default value, dithering will be enabled when the input - /// color depth is higher than the output color depth. For example, + /// color depth is higher than the output color depth. For example, /// drawing an RGB8 image onto an RGB565 canvas. - /// - /// This value also controls dithering of [shader]s, which can make + /// + /// This value also controls dithering of [shader]s, which can make /// gradients appear smoother. - /// + /// /// Whether or not dithering affects the output is implementation defined. /// Some implementations may choose to ignore this completely, if they're /// unable to control dithering. diff --git a/lib/web_ui/lib/src/ui/painting.dart b/lib/web_ui/lib/src/ui/painting.dart index 0131f614d35b5..a382e913200d8 100644 --- a/lib/web_ui/lib/src/ui/painting.dart +++ b/lib/web_ui/lib/src/ui/painting.dart @@ -1175,14 +1175,14 @@ class Paint { } /// Whether to dither the output when drawing images. - /// + /// /// If false, the default value, dithering will be enabled when the input - /// color depth is higher than the output color depth. For example, + /// color depth is higher than the output color depth. For example, /// drawing an RGB8 image onto an RGB565 canvas. - /// - /// This value also controls dithering of [shader]s, which can make + /// + /// This value also controls dithering of [shader]s, which can make /// gradients appear smoother. - /// + /// /// Whether or not dithering affects the output is implementation defined. /// Some implementations may choose to ignore this completely, if they're /// unable to control dithering. From 0361a0b412e11eff7263e5c9c71290c144b3cdbf Mon Sep 17 00:00:00 2001 From: Dwayne Slater Date: Thu, 21 Nov 2019 15:22:21 -0800 Subject: [PATCH 3/8] Move to a temporary static field on Paint so dithering can be turned on uniformly --- lib/ui/painting.dart | 29 +++++++++++++++++++++-------- lib/web_ui/lib/src/ui/painting.dart | 6 ++++-- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 7453cab999627..83852bba490bc 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -1084,6 +1084,14 @@ class Paint { static const int _kImageFilterIndex = 2; static const int _kObjectCount = 3; // Must be one larger than the largest index. + /// Constructs an empty [Paint] object with all fields initialized to + /// their defaults. + Paint() { + if (enableDithering) { + _dither = true; + } + } + /// Whether to apply anti-aliasing to lines and images drawn on the /// canvas. /// @@ -1403,6 +1411,13 @@ class Paint { _data.setInt32(_kInvertColorOffset, value ? 1 : 0, _kFakeHostEndian); } + bool get _dither { + return _data.getInt32(_kDitherOffset, _kFakeHostEndian) == 1; + } + set _dither(bool value) { + _data.setInt32(_kDitherOffset, value ? 1 : 0, _kFakeHostEndian); + } + /// Whether to dither the output when drawing images. /// /// If false, the default value, dithering will be enabled when the input @@ -1415,12 +1430,10 @@ class Paint { /// Whether or not dithering affects the output is implementation defined. /// Some implementations may choose to ignore this completely, if they're /// unable to control dithering. - bool get dither { - return _data.getInt32(_kDitherOffset, _kFakeHostEndian) == 1; - } - set dither(bool value) { - _data.setInt32(_kDitherOffset, value ? 1 : 0, _kFakeHostEndian); - } + /// + /// To ensure that dithering is consistently enabled for your entire + /// application, set this to true before invoking any drawing related code. + static bool enableDithering = false; @override String toString() { @@ -1480,8 +1493,8 @@ class Paint { } if (invertColors) result.write('${semicolon}invert: $invertColors'); - if (dither) - result.write('${semicolon}dither: $dither'); + if (_dither) + result.write('${semicolon}dither: $_dither'); result.write(')'); return result.toString(); } diff --git a/lib/web_ui/lib/src/ui/painting.dart b/lib/web_ui/lib/src/ui/painting.dart index a382e913200d8..28c65165b731a 100644 --- a/lib/web_ui/lib/src/ui/painting.dart +++ b/lib/web_ui/lib/src/ui/painting.dart @@ -1186,8 +1186,10 @@ class Paint { /// Whether or not dithering affects the output is implementation defined. /// Some implementations may choose to ignore this completely, if they're /// unable to control dithering. - bool get dither => false; // Not implemented on web - set dither(bool value) {} + /// + /// To ensure that dithering is consistently enabled for your entire + /// application, set this to true before invoking any drawing related code. + static bool enableDithering = false; // True if Paint instance has used in RecordingCanvas. bool _frozen = false; From 464c6e082c1a7c9a872d8cf9ba7dedbbd87f16ed Mon Sep 17 00:00:00 2001 From: Dwayne Slater Date: Thu, 21 Nov 2019 15:30:52 -0800 Subject: [PATCH 4/8] Fix trailing whitespace again --- lib/ui/painting.dart | 4 ++-- lib/web_ui/lib/src/ui/painting.dart | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 83852bba490bc..bd2d7ec41c462 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -1430,8 +1430,8 @@ class Paint { /// Whether or not dithering affects the output is implementation defined. /// Some implementations may choose to ignore this completely, if they're /// unable to control dithering. - /// - /// To ensure that dithering is consistently enabled for your entire + /// + /// To ensure that dithering is consistently enabled for your entire /// application, set this to true before invoking any drawing related code. static bool enableDithering = false; diff --git a/lib/web_ui/lib/src/ui/painting.dart b/lib/web_ui/lib/src/ui/painting.dart index 28c65165b731a..95446c3a4326e 100644 --- a/lib/web_ui/lib/src/ui/painting.dart +++ b/lib/web_ui/lib/src/ui/painting.dart @@ -1186,8 +1186,8 @@ class Paint { /// Whether or not dithering affects the output is implementation defined. /// Some implementations may choose to ignore this completely, if they're /// unable to control dithering. - /// - /// To ensure that dithering is consistently enabled for your entire + /// + /// To ensure that dithering is consistently enabled for your entire /// application, set this to true before invoking any drawing related code. static bool enableDithering = false; From 32712a267fd3ed22e377f90584eec080e52023c0 Mon Sep 17 00:00:00 2001 From: Dwayne Slater Date: Mon, 9 Dec 2019 13:19:22 -0800 Subject: [PATCH 5/8] Add image compare test for dithered and non-dithered gradients --- testing/dart/canvas_test.dart | 60 ++++++++++++++++-- .../canvas_test_dithered_gradient.png | Bin 0 -> 1701 bytes testing/resources/canvas_test_gradient.png | Bin 0 -> 1029 bytes 3 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 testing/resources/canvas_test_dithered_gradient.png create mode 100644 testing/resources/canvas_test_gradient.png diff --git a/testing/dart/canvas_test.dart b/testing/dart/canvas_test.dart index 32249ae4f0b56..109eaa51ccb85 100644 --- a/testing/dart/canvas_test.dart +++ b/testing/dart/canvas_test.dart @@ -109,14 +109,20 @@ Future fuzzyGoldenImageCompare( Image image, String goldenImageName) async { final String imagesPath = path.join('flutter', 'testing', 'resources'); final File file = File(path.join(imagesPath, goldenImageName)); - final Uint8List goldenData = await file.readAsBytes(); - final Codec codec = await instantiateImageCodec(goldenData); - final FrameInfo frame = await codec.getNextFrame(); - expect(frame.image.height, equals(image.width)); - expect(frame.image.width, equals(image.height)); + bool areEqual = false; + + if (file.existsSync()) { + final Uint8List goldenData = await file.readAsBytes(); + + final Codec codec = await instantiateImageCodec(goldenData); + final FrameInfo frame = await codec.getNextFrame(); + expect(frame.image.height, equals(image.width)); + expect(frame.image.width, equals(image.height)); + + areEqual = await fuzzyCompareImages(frame.image, image); + } - final bool areEqual = await fuzzyCompareImages(frame.image, image); if (!areEqual) { final ByteData pngData = await image.toByteData(); final ByteBuffer buffer = pngData.buffer; @@ -151,4 +157,46 @@ void main() { await fuzzyGoldenImageCompare(image, 'canvas_test_toImage.png'); expect(areEqual, true); }); + + test('Simple gradient', () async { + Paint.enableDithering = false; + final PictureRecorder recorder = PictureRecorder(); + final Canvas canvas = Canvas(recorder); + final Paint paint = Paint() + ..shader = Gradient.linear(Offset.zero, + const Offset(100, 100), const [ + Color(0xFF4C4D52), + Color(0xFF202124), + ]); + canvas.drawPaint(paint); + final Picture picture = recorder.endRecording(); + final Image image = await picture.toImage(100, 100); + expect(image.width, equals(100)); + expect(image.height, equals(100)); + + final bool areEqual = + await fuzzyGoldenImageCompare(image, 'canvas_test_gradient.png'); + expect(areEqual, true); + }); + + test('Simple dithered gradient', () async { + Paint.enableDithering = true; + final PictureRecorder recorder = PictureRecorder(); + final Canvas canvas = Canvas(recorder); + final Paint paint = Paint() + ..shader = Gradient.linear(Offset.zero, + const Offset(100, 100), const [ + Color(0xFF4C4D52), + Color(0xFF202124), + ]); + canvas.drawPaint(paint); + final Picture picture = recorder.endRecording(); + final Image image = await picture.toImage(100, 100); + expect(image.width, equals(100)); + expect(image.height, equals(100)); + + final bool areEqual = + await fuzzyGoldenImageCompare(image, 'canvas_test_dithered_gradient.png'); + expect(areEqual, true); + }); } diff --git a/testing/resources/canvas_test_dithered_gradient.png b/testing/resources/canvas_test_dithered_gradient.png new file mode 100644 index 0000000000000000000000000000000000000000..d8062f2dc1f35424af652e5580097b4c8b1a1d50 GIT binary patch literal 1701 zcmV;W23q-vP)XMhftwzgzDorH#n7nhnuwsrAhbD63C{rP+S`uZx@>$Tj* z%jHs}eZ5{vDbl`{+XL-?e|cVq_P@)%|1Rw!{5HdwqKfdtl4CdNC2{eKHVZ}+B<+V4&uB8_JcZgi zPTH=#65}L|b_&~^T!iOG-&Z=e*BRdYvEASA$o%p?+^@DV9<@z^onc2^L5uy9&ftFS zC>K528MFJ)L6+-}I)QgaZ~kK9w%Aoy=9bLRL42k{JU_<>R+)?u7qQNHMjXuyjK5(B zW5!G}T4l+KX?Bkpm^5qKvmS%ND>h=p95e3E4lv_sU40wlU!#nT*uRGvyfb?1o8Q0R z86%8f@sR$i%otYeYnfpyHfDy;K-N)SC)m!wvwnmbMcI%0I5TkGsw-$ao$*YC2r6U7 z=jx0CHfILvzM49t&kVFZX0XmcyLUWJGvxO$<2`jo8~LkQ#gNxBW41H=F`oamI-_03 znOC173_1aAcD!gmZF8wJVrF2cROTc~#xfRr_MlsIA7Imm^x#e83pWh%)t99PX=bRl(;i6Id+p{%)ohcFrpy6i_Wk`q^~m! z`8~||Or0^nkklD{X5jqmbw+Rg>UV|?;_~P*!#~?oXNr3~@T&Jku{(yV=Mox!e? z@rQSNU&)Mf=!{+*tL==`88RvDzHM}dwY8tBZ%Uovb;@4MpbY7E1}4XDa*P=`j}Ar@ zq)*WqQ8Fk)547ANR7kiz6&$H`OXGEP5>psH}S#e{`Ks$8?>p(p+KfsVy>8Uf&-jf*^|H^{F zD>ik;9?Zb_3#FbqV=XgcKQS}#dhvNk>I`o`J!UY5*!^&33@{{h#*xfGou?QQ1u1n# z5&OsFtY=2RkklEY%!oThT$wI)inf9+@9*<9@<-dsAmu2-OumP89m)c8In377SKbigqzN_yQ|NIKN00000NkvXXu0mjfs6tU# literal 0 HcmV?d00001 diff --git a/testing/resources/canvas_test_gradient.png b/testing/resources/canvas_test_gradient.png new file mode 100644 index 0000000000000000000000000000000000000000..89f3f63dc518a1dfd84c9052125fdf6ac079253f GIT binary patch literal 1029 zcmeAS@N?(olHy`uVBq!ia0vp^DImZPyNbG536qR(6c%0y1_uDX$r)6RaP^_KtCs^Z7F}5e1$#N1C)Oos>^Ygrk zjO5{b>?IMgN~5i%(3Iyy1P|w8cAgUfKuVk^W4VNoo4jqO45fGF1`6k zk!QC0?}CX2&l(szj~d8C&C-=Sw4t(vUz{^prY9)Ukf(iGT6eoaj@-{%ZVUY+l;wjz z&QkBG1o}kAN@kA1u>%L*a2^h=TGz~&o6@nxO~O5K$}PT1MdzSqv+dv4uph2UcL8eK ze4rq+?F3t9-h0KK^D%)uoQI!T@;#obxJz-~?P?C?`Mf|MYYZ)CwQEv1@~Ff`ezeoC#x(qD%g_(Tqy zCiZX?WUkWz#*iw{ibWFZ*pu7NbfiP#sz(K6!{MDvcTQE`-?#f{fvv>SXN`@fOC{EE zCNE={ehm_B6H`14*03i(J9EM|5@^#z0|R%UGZrU0)P(}o15?K*C9m0c_>ajl#e7Qn z|2y_pi$T@Ff_EEbUzN8M9zVm8y*`qs^pM44OFm{Tr@02_D$X6ONap|r&@Iij;=>xH z`4Z<=9xbSo_;t6)UgCnA;T-8>S-`;Y1*WUS&s*9*@_l{5(USNjhWW7N)7y(Uo#(%{ zF_W0^V`;+XgBn}acvk!f+Hf Date: Mon, 9 Dec 2019 13:19:38 -0800 Subject: [PATCH 6/8] Add empty constructor to Web UI Paint --- lib/web_ui/lib/src/ui/painting.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/web_ui/lib/src/ui/painting.dart b/lib/web_ui/lib/src/ui/painting.dart index ef7e736ae35e0..ced100c1f9e2d 100644 --- a/lib/web_ui/lib/src/ui/painting.dart +++ b/lib/web_ui/lib/src/ui/painting.dart @@ -961,6 +961,8 @@ class PaintData { class Paint { PaintData _paintData = PaintData(); + Paint(); + /// A blend mode to apply when a shape is drawn or a layer is composited. /// /// The source colors are from the shape being drawn (e.g. from From ec129ad0a457e5adebe304167868c470e3545328 Mon Sep 17 00:00:00 2001 From: Dwayne Slater Date: Mon, 9 Dec 2019 13:24:29 -0800 Subject: [PATCH 7/8] Fix up formatting and unify gradient that is being tested --- testing/dart/canvas_test.dart | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/testing/dart/canvas_test.dart b/testing/dart/canvas_test.dart index 109eaa51ccb85..2bfe63d5eca1d 100644 --- a/testing/dart/canvas_test.dart +++ b/testing/dart/canvas_test.dart @@ -158,16 +158,19 @@ void main() { expect(areEqual, true); }); + Gradient makeGradient() { + return Gradient.linear( + Offset.zero, + const Offset(100, 100), + const [Color(0xFF4C4D52), Color(0xFF202124)], + ); + } + test('Simple gradient', () async { Paint.enableDithering = false; final PictureRecorder recorder = PictureRecorder(); final Canvas canvas = Canvas(recorder); - final Paint paint = Paint() - ..shader = Gradient.linear(Offset.zero, - const Offset(100, 100), const [ - Color(0xFF4C4D52), - Color(0xFF202124), - ]); + final Paint paint = Paint()..shader = makeGradient(); canvas.drawPaint(paint); final Picture picture = recorder.endRecording(); final Image image = await picture.toImage(100, 100); @@ -183,12 +186,7 @@ void main() { Paint.enableDithering = true; final PictureRecorder recorder = PictureRecorder(); final Canvas canvas = Canvas(recorder); - final Paint paint = Paint() - ..shader = Gradient.linear(Offset.zero, - const Offset(100, 100), const [ - Color(0xFF4C4D52), - Color(0xFF202124), - ]); + final Paint paint = Paint()..shader = makeGradient(); canvas.drawPaint(paint); final Picture picture = recorder.endRecording(); final Image image = await picture.toImage(100, 100); From f732e2c6c005fea735216d05ef13803a34ec895f Mon Sep 17 00:00:00 2001 From: Dwayne Slater Date: Mon, 9 Dec 2019 13:25:53 -0800 Subject: [PATCH 8/8] Copy documentation comment from dart:ui --- lib/web_ui/lib/src/ui/painting.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/web_ui/lib/src/ui/painting.dart b/lib/web_ui/lib/src/ui/painting.dart index ced100c1f9e2d..1460e1b08f2cd 100644 --- a/lib/web_ui/lib/src/ui/painting.dart +++ b/lib/web_ui/lib/src/ui/painting.dart @@ -961,6 +961,8 @@ class PaintData { class Paint { PaintData _paintData = PaintData(); + /// Constructs an empty [Paint] object with all fields initialized to + /// their defaults. Paint(); /// A blend mode to apply when a shape is drawn or a layer is composited.