Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
38 changes: 37 additions & 1 deletion lib/ui/painting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<dynamic> _objects;
Expand All @@ -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.
///
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
}
Expand Down
7 changes: 6 additions & 1 deletion lib/ui/painting/paint.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
21 changes: 21 additions & 0 deletions lib/web_ui/lib/src/ui/painting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,10 @@ class PaintData {
class Paint {
PaintData _paintData = PaintData();

/// Constructs an empty [Paint] object with all fields initialized to
/// their defaults.
Paint();

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be unnecessary, FWIW. The constructor is normally implied.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When it was omitted one of the web UI lints complained

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the one in lib/ui/painting.dart too and the lint should go away.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That constructor can't be removed because its used to set _dither to true if enableDithering is true. I could use cascade syntax on _data's initializer, but readability suffers a little.

Copy link
Contributor

@liyuqian liyuqian Dec 11, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Ping @jonahwilliams and @yjbanov to see if the web tools (the lint) can be relaxed in this case. If not, I'm Ok with just having this dummy constructor. My experience is that there's already a lot of dummy/duplicate code in lib/web_ui. The ideal future would be that we don't need any duplicate code in lib/web_ui, and this issue would go away naturally.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep this one. We are splitting the implementation between HTML and CanvasKit (#14320), and this constructor is becoming a factory, which we cannot omit.

/// 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
Expand Down Expand Up @@ -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;

Expand Down
58 changes: 52 additions & 6 deletions testing/dart/canvas_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,20 @@ Future<bool> 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;
Expand Down Expand Up @@ -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>[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);
});
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added testing/resources/canvas_test_gradient.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.