From f7eb74c11ff0ed0e49d02ca76169b3ce812c0ab6 Mon Sep 17 00:00:00 2001 From: Dwayne Slater Date: Wed, 11 Dec 2019 14:53:50 -0800 Subject: [PATCH] Add ability to control dithering on Paint (flutter/engine#13868) --- engine/src/flutter/lib/ui/painting.dart | 38 +++++++++++- engine/src/flutter/lib/ui/painting/paint.cc | 7 ++- .../lib/web_ui/lib/src/ui/painting.dart | 21 +++++++ .../src/flutter/testing/dart/canvas_test.dart | 58 ++++++++++++++++-- .../canvas_test_dithered_gradient.png | Bin 0 -> 1701 bytes .../resources/canvas_test_gradient.png | Bin 0 -> 1029 bytes 6 files changed, 116 insertions(+), 8 deletions(-) create mode 100644 engine/src/flutter/testing/resources/canvas_test_dithered_gradient.png create mode 100644 engine/src/flutter/testing/resources/canvas_test_gradient.png diff --git a/engine/src/flutter/lib/ui/painting.dart b/engine/src/flutter/lib/ui/painting.dart index b30f3835902..a6c96ac9298 100644 --- a/engine/src/flutter/lib/ui/painting.dart +++ b/engine/src/flutter/lib/ui/painting.dart @@ -1066,6 +1066,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; @@ -1080,8 +1081,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; @@ -1090,6 +1092,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. /// @@ -1417,6 +1427,30 @@ 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 + /// 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. + /// + /// 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() { final StringBuffer result = StringBuffer(); @@ -1475,6 +1509,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/engine/src/flutter/lib/ui/painting/paint.cc b/engine/src/flutter/lib/ui/painting/paint.cc index 00006c3f989..2648ff2f66f 100644 --- a/engine/src/flutter/lib/ui/painting/paint.cc +++ b/engine/src/flutter/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/engine/src/flutter/lib/web_ui/lib/src/ui/painting.dart b/engine/src/flutter/lib/web_ui/lib/src/ui/painting.dart index 9a26fc95271..1460e1b08f2 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/ui/painting.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/ui/painting.dart @@ -961,6 +961,10 @@ 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. /// /// The source colors are from the shape being drawn (e.g. from @@ -1182,6 +1186,23 @@ 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. + /// + /// 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; diff --git a/engine/src/flutter/testing/dart/canvas_test.dart b/engine/src/flutter/testing/dart/canvas_test.dart index 32249ae4f0b..2bfe63d5eca 100644 --- a/engine/src/flutter/testing/dart/canvas_test.dart +++ b/engine/src/flutter/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,44 @@ void main() { await fuzzyGoldenImageCompare(image, 'canvas_test_toImage.png'); 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 = makeGradient(); + 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 = makeGradient(); + 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/engine/src/flutter/testing/resources/canvas_test_dithered_gradient.png b/engine/src/flutter/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/engine/src/flutter/testing/resources/canvas_test_gradient.png b/engine/src/flutter/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