Skip to content

Commit 80d80ff

Browse files
authored
Add ability to control dithering on Paint (#13868)
1 parent 2713225 commit 80d80ff

File tree

6 files changed

+116
-8
lines changed

6 files changed

+116
-8
lines changed

lib/ui/painting.dart

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1066,6 +1066,7 @@ class Paint {
10661066
static const int _kMaskFilterBlurStyleIndex = 10;
10671067
static const int _kMaskFilterSigmaIndex = 11;
10681068
static const int _kInvertColorIndex = 12;
1069+
static const int _kDitherIndex = 13;
10691070

10701071
static const int _kIsAntiAliasOffset = _kIsAntiAliasIndex << 2;
10711072
static const int _kColorOffset = _kColorIndex << 2;
@@ -1080,8 +1081,9 @@ class Paint {
10801081
static const int _kMaskFilterBlurStyleOffset = _kMaskFilterBlurStyleIndex << 2;
10811082
static const int _kMaskFilterSigmaOffset = _kMaskFilterSigmaIndex << 2;
10821083
static const int _kInvertColorOffset = _kInvertColorIndex << 2;
1084+
static const int _kDitherOffset = _kDitherIndex << 2;
10831085
// If you add more fields, remember to update _kDataByteCount.
1084-
static const int _kDataByteCount = 52;
1086+
static const int _kDataByteCount = 56;
10851087

10861088
// Binary format must match the deserialization code in paint.cc.
10871089
List<dynamic> _objects;
@@ -1090,6 +1092,14 @@ class Paint {
10901092
static const int _kImageFilterIndex = 2;
10911093
static const int _kObjectCount = 3; // Must be one larger than the largest index.
10921094

1095+
/// Constructs an empty [Paint] object with all fields initialized to
1096+
/// their defaults.
1097+
Paint() {
1098+
if (enableDithering) {
1099+
_dither = true;
1100+
}
1101+
}
1102+
10931103
/// Whether to apply anti-aliasing to lines and images drawn on the
10941104
/// canvas.
10951105
///
@@ -1417,6 +1427,30 @@ class Paint {
14171427
_data.setInt32(_kInvertColorOffset, value ? 1 : 0, _kFakeHostEndian);
14181428
}
14191429

1430+
bool get _dither {
1431+
return _data.getInt32(_kDitherOffset, _kFakeHostEndian) == 1;
1432+
}
1433+
set _dither(bool value) {
1434+
_data.setInt32(_kDitherOffset, value ? 1 : 0, _kFakeHostEndian);
1435+
}
1436+
1437+
/// Whether to dither the output when drawing images.
1438+
///
1439+
/// If false, the default value, dithering will be enabled when the input
1440+
/// color depth is higher than the output color depth. For example,
1441+
/// drawing an RGB8 image onto an RGB565 canvas.
1442+
///
1443+
/// This value also controls dithering of [shader]s, which can make
1444+
/// gradients appear smoother.
1445+
///
1446+
/// Whether or not dithering affects the output is implementation defined.
1447+
/// Some implementations may choose to ignore this completely, if they're
1448+
/// unable to control dithering.
1449+
///
1450+
/// To ensure that dithering is consistently enabled for your entire
1451+
/// application, set this to true before invoking any drawing related code.
1452+
static bool enableDithering = false;
1453+
14201454
@override
14211455
String toString() {
14221456
final StringBuffer result = StringBuffer();
@@ -1475,6 +1509,8 @@ class Paint {
14751509
}
14761510
if (invertColors)
14771511
result.write('${semicolon}invert: $invertColors');
1512+
if (_dither)
1513+
result.write('${semicolon}dither: $_dither');
14781514
result.write(')');
14791515
return result.toString();
14801516
}

lib/ui/painting/paint.cc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ constexpr int kMaskFilterIndex = 9;
3232
constexpr int kMaskFilterBlurStyleIndex = 10;
3333
constexpr int kMaskFilterSigmaIndex = 11;
3434
constexpr int kInvertColorIndex = 12;
35-
constexpr size_t kDataByteCount = 52; // 4 * (last index + 1)
35+
constexpr int kDitherIndex = 13;
36+
constexpr size_t kDataByteCount = 56; // 4 * (last index + 1)
3637

3738
// Indices for objects.
3839
constexpr int kShaderIndex = 0;
@@ -154,6 +155,10 @@ Paint::Paint(Dart_Handle paint_objects, Dart_Handle paint_data) {
154155
paint_.setColorFilter(invert_filter);
155156
}
156157

158+
if (uint_data[kDitherIndex]) {
159+
paint_.setDither(true);
160+
}
161+
157162
switch (uint_data[kMaskFilterIndex]) {
158163
case Null:
159164
break;

lib/web_ui/lib/src/ui/painting.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -961,6 +961,10 @@ class PaintData {
961961
class Paint {
962962
PaintData _paintData = PaintData();
963963

964+
/// Constructs an empty [Paint] object with all fields initialized to
965+
/// their defaults.
966+
Paint();
967+
964968
/// A blend mode to apply when a shape is drawn or a layer is composited.
965969
///
966970
/// The source colors are from the shape being drawn (e.g. from
@@ -1182,6 +1186,23 @@ class Paint {
11821186
// TODO(flutter/flutter#35156): Implement ImageFilter.
11831187
}
11841188

1189+
/// Whether to dither the output when drawing images.
1190+
///
1191+
/// If false, the default value, dithering will be enabled when the input
1192+
/// color depth is higher than the output color depth. For example,
1193+
/// drawing an RGB8 image onto an RGB565 canvas.
1194+
///
1195+
/// This value also controls dithering of [shader]s, which can make
1196+
/// gradients appear smoother.
1197+
///
1198+
/// Whether or not dithering affects the output is implementation defined.
1199+
/// Some implementations may choose to ignore this completely, if they're
1200+
/// unable to control dithering.
1201+
///
1202+
/// To ensure that dithering is consistently enabled for your entire
1203+
/// application, set this to true before invoking any drawing related code.
1204+
static bool enableDithering = false;
1205+
11851206
// True if Paint instance has used in RecordingCanvas.
11861207
bool _frozen = false;
11871208

testing/dart/canvas_test.dart

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,20 @@ Future<bool> fuzzyGoldenImageCompare(
109109
Image image, String goldenImageName) async {
110110
final String imagesPath = path.join('flutter', 'testing', 'resources');
111111
final File file = File(path.join(imagesPath, goldenImageName));
112-
final Uint8List goldenData = await file.readAsBytes();
113112

114-
final Codec codec = await instantiateImageCodec(goldenData);
115-
final FrameInfo frame = await codec.getNextFrame();
116-
expect(frame.image.height, equals(image.width));
117-
expect(frame.image.width, equals(image.height));
113+
bool areEqual = false;
114+
115+
if (file.existsSync()) {
116+
final Uint8List goldenData = await file.readAsBytes();
117+
118+
final Codec codec = await instantiateImageCodec(goldenData);
119+
final FrameInfo frame = await codec.getNextFrame();
120+
expect(frame.image.height, equals(image.width));
121+
expect(frame.image.width, equals(image.height));
122+
123+
areEqual = await fuzzyCompareImages(frame.image, image);
124+
}
118125

119-
final bool areEqual = await fuzzyCompareImages(frame.image, image);
120126
if (!areEqual) {
121127
final ByteData pngData = await image.toByteData();
122128
final ByteBuffer buffer = pngData.buffer;
@@ -151,4 +157,44 @@ void main() {
151157
await fuzzyGoldenImageCompare(image, 'canvas_test_toImage.png');
152158
expect(areEqual, true);
153159
});
160+
161+
Gradient makeGradient() {
162+
return Gradient.linear(
163+
Offset.zero,
164+
const Offset(100, 100),
165+
const <Color>[Color(0xFF4C4D52), Color(0xFF202124)],
166+
);
167+
}
168+
169+
test('Simple gradient', () async {
170+
Paint.enableDithering = false;
171+
final PictureRecorder recorder = PictureRecorder();
172+
final Canvas canvas = Canvas(recorder);
173+
final Paint paint = Paint()..shader = makeGradient();
174+
canvas.drawPaint(paint);
175+
final Picture picture = recorder.endRecording();
176+
final Image image = await picture.toImage(100, 100);
177+
expect(image.width, equals(100));
178+
expect(image.height, equals(100));
179+
180+
final bool areEqual =
181+
await fuzzyGoldenImageCompare(image, 'canvas_test_gradient.png');
182+
expect(areEqual, true);
183+
});
184+
185+
test('Simple dithered gradient', () async {
186+
Paint.enableDithering = true;
187+
final PictureRecorder recorder = PictureRecorder();
188+
final Canvas canvas = Canvas(recorder);
189+
final Paint paint = Paint()..shader = makeGradient();
190+
canvas.drawPaint(paint);
191+
final Picture picture = recorder.endRecording();
192+
final Image image = await picture.toImage(100, 100);
193+
expect(image.width, equals(100));
194+
expect(image.height, equals(100));
195+
196+
final bool areEqual =
197+
await fuzzyGoldenImageCompare(image, 'canvas_test_dithered_gradient.png');
198+
expect(areEqual, true);
199+
});
154200
}
1.66 KB
Loading
1 KB
Loading

0 commit comments

Comments
 (0)