Skip to content

Commit

Permalink
[web] Use fuzzy matching in Gold (flutter#29847)
Browse files Browse the repository at this point in the history
  • Loading branch information
mdebbar authored Nov 29, 2021
1 parent d877faa commit 2614113
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 17 deletions.
16 changes: 10 additions & 6 deletions lib/web_ui/dev/test_platform.dart
Original file line number Diff line number Diff line change
Expand Up @@ -339,17 +339,20 @@ class BrowserPlatform extends PlatformPlugin {
requestData['region'] as Map<String, dynamic>;
final PixelComparison pixelComparison = PixelComparison.values.firstWhere(
(PixelComparison value) => value.toString() == requestData['pixelComparison']);
final bool isCanvaskitTest = requestData['isCanvaskitTest'] as bool;
final String result = await _diffScreenshot(
filename, write, maxDiffRate, region, pixelComparison);
filename, write, maxDiffRate, region, pixelComparison, isCanvaskitTest);
return shelf.Response.ok(json.encode(result));
}

Future<String> _diffScreenshot(
String filename,
bool write,
double maxDiffRateFailure,
Map<String, dynamic> region,
PixelComparison pixelComparison) async {
String filename,
bool write,
double maxDiffRateFailure,
Map<String, dynamic> region,
PixelComparison pixelComparison,
bool isCanvaskitTest,
) async {
if (doUpdateScreenshotGoldens) {
write = true;
}
Expand Down Expand Up @@ -387,6 +390,7 @@ class BrowserPlatform extends PlatformPlugin {
pixelComparison,
maxDiffRateFailure,
skiaClient,
isCanvaskitTest: isCanvaskitTest,
goldensDirectory: goldensDirectory,
filenameSuffix: _screenshotManager!.filenameSuffix,
write: write,
Expand Down
6 changes: 3 additions & 3 deletions web_sdk/web_engine_tester/lib/golden_tester.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ import 'dart:async';
import 'dart:convert';
import 'dart:html' as html;

import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart';

import 'package:test/test.dart';
import 'package:ui/src/engine.dart' show operatingSystem, OperatingSystem, useCanvasKit;
import 'package:ui/ui.dart';

Future<dynamic> _callScreenshotServer(dynamic requestData) async {
final html.HttpRequest request = await html.HttpRequest.request(
Expand Down Expand Up @@ -63,6 +62,7 @@ Future<void> matchGoldenFile(String filename,
'height': region.height
},
'pixelComparison': pixelComparison.toString(),
'isCanvaskitTest': useCanvasKit,
};

// Chrome on macOS renders slightly differently from Linux, so allow it an
Expand Down
18 changes: 13 additions & 5 deletions web_sdk/web_test_utils/lib/image_compare.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Future<String> compareImage(
PixelComparison pixelComparison,
double maxDiffRateFailure,
SkiaGoldClient? skiaClient, {
required bool isCanvaskitTest,
// TODO(mdebbar): Remove these args with goldens repo.
String goldensDirectory = '',
String filenameSuffix = '',
Expand All @@ -43,7 +44,7 @@ Future<String> compareImage(
// comparison.

// TODO(mdebbar): Use Skia Gold for comparison, not only for uploading.
await _uploadToSkiaGold(skiaClient, screenshot, filename);
await _uploadToSkiaGold(skiaClient, screenshot, filename, isCanvaskitTest);
}

filename = filename.replaceAll('.png', '$filenameSuffix.png');
Expand Down Expand Up @@ -163,6 +164,7 @@ Future<void> _uploadToSkiaGold(
SkiaGoldClient skiaClient,
Image screenshot,
String filename,
bool isCanvaskitTest,
) async {
// Can't upload to Gold Skia unless running in LUCI.
assert(_isLuci);
Expand All @@ -172,28 +174,34 @@ Future<void> _uploadToSkiaGold(
final File goldenFile = File(p.join(environment.webUiSkiaGoldDirectory.path, filename));
await goldenFile.writeAsBytes(encodePng(screenshot), flush: true);

final int screenshotSize = screenshot.width * screenshot.height;

if (_isPreSubmit) {
return _uploadInPreSubmit(skiaClient, filename, goldenFile);
return _uploadInPreSubmit(skiaClient, filename, goldenFile, screenshotSize, isCanvaskitTest);
}
if (_isPostSubmit) {
return _uploadInPostSubmit(skiaClient, filename, goldenFile);
return _uploadInPostSubmit(skiaClient, filename, goldenFile, screenshotSize, isCanvaskitTest);
}
}

Future<void> _uploadInPreSubmit(
SkiaGoldClient skiaClient,
String filename,
File goldenFile,
int screenshotSize,
bool isCanvaskitTest,
) {
assert(_isPreSubmit);
return skiaClient.tryjobAdd(filename, goldenFile);
return skiaClient.tryjobAdd(filename, goldenFile, screenshotSize, isCanvaskitTest);
}

Future<void> _uploadInPostSubmit(
SkiaGoldClient skiaClient,
String filename,
File goldenFile,
int screenshotSize,
bool isCanvaskitTest,
) {
assert(_isPostSubmit);
return skiaClient.imgtestAdd(filename, goldenFile);
return skiaClient.imgtestAdd(filename, goldenFile, screenshotSize, isCanvaskitTest);
}
63 changes: 60 additions & 3 deletions web_sdk/web_test_utils/lib/skia_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ const String _kGoldctlKey = 'GOLDCTL';
const String _skiaGoldHost = 'https://flutter-engine-gold.skia.org';
const String _instance = 'flutter-engine';

/// The percentage of accepted pixels to be wrong.
///
/// This should be a double between 0.0 and 1.0. A value of 0.0 means we don't
/// accept any pixel to be different. A value of 1.0 means we accept 100% of
/// pixels to be different.
const double kMaxDifferentPixelsRate = 0.1;

/// A client for uploading image tests and making baseline requests to the
/// Flutter Gold Dashboard.
class SkiaGoldClient {
Expand Down Expand Up @@ -162,7 +169,12 @@ class SkiaGoldClient {
///
/// The [testName] and [goldenFile] parameters reference the current
/// comparison being evaluated.
Future<bool> imgtestAdd(String testName, File goldenFile) async {
Future<bool> imgtestAdd(
String testName,
File goldenFile,
int screenshotSize,
bool isCanvaskitTest,
) async {
await _imgtestInit();

final List<String> imgtestCommand = <String>[
Expand All @@ -171,6 +183,7 @@ class SkiaGoldClient {
'--work-dir', _tempPath,
'--test-name', cleanTestName(testName),
'--png-file', goldenFile.path,
..._getMatchingArguments(testName, screenshotSize, isCanvaskitTest),
];

final ProcessResult result = await process.run(imgtestCommand);
Expand Down Expand Up @@ -250,7 +263,12 @@ class SkiaGoldClient {
///
/// The [testName] and [goldenFile] parameters reference the current
/// comparison being evaluated.
Future<void> tryjobAdd(String testName, File goldenFile) async {
Future<void> tryjobAdd(
String testName,
File goldenFile,
int screenshotSize,
bool isCanvaskitTest,
) async {
await _tryjobInit();

final List<String> imgtestCommand = <String>[
Expand All @@ -259,6 +277,7 @@ class SkiaGoldClient {
'--work-dir', _tempPath,
'--test-name', cleanTestName(testName),
'--png-file', goldenFile.path,
..._getMatchingArguments(testName, screenshotSize, isCanvaskitTest),
];

final ProcessResult result = await process.run(imgtestCommand);
Expand All @@ -279,6 +298,44 @@ class SkiaGoldClient {
}
}

List<String> _getMatchingArguments(
String testName,
int screenshotSize,
bool isCanvaskitTest,
) {
// The algorithm to be used when matching images. The available options are:
// - "fuzzy": Allows for customizing the thresholds of pixel differences.
// - "sobel": Same as "fuzzy" but performs edge detection before performing
// a fuzzy match.
const String algorithm = 'fuzzy';

// The number of pixels in this image that are allowed to differ from the
// baseline. It's okay for this to be a slightly high number like 10% of the
// image size because those wrong pixels are constrained by
// `pixelDeltaThreshold` below.
final int maxDifferentPixels = (screenshotSize * kMaxDifferentPixelsRate).toInt();

// The maximum acceptable difference in RGB channels of each pixel.
//
// ```
// abs(r(image) - r(golden)) + abs(g(image) - g(golden)) + abs(b(image) - b(golden)) <= pixelDeltaThreshold
// ```
final String pixelDeltaThreshold;
if (isCanvaskitTest) {
pixelDeltaThreshold = '21';
} else if (browserName == 'ios-safari') {
pixelDeltaThreshold = '15';
} else {
pixelDeltaThreshold = '3';
}

return <String>[
'--add-test-optional-key', 'image_matching_algorithm:$algorithm',
'--add-test-optional-key', 'fuzzy_max_different_pixels:$maxDifferentPixels',
'--add-test-optional-key', 'fuzzy_pixel_delta_threshold:$pixelDeltaThreshold',
];
}

/// Returns the latest positive digest for the given test known to Skia Gold
/// at head.
Future<String?> getExpectationForTest(String testName) async {
Expand Down Expand Up @@ -356,9 +413,9 @@ class SkiaGoldClient {
/// browser the image was rendered on.
Map<String, dynamic> _getKeys() {
return <String, dynamic>{
'Browser': browserName,
'CI': 'luci',
'Platform': Platform.operatingSystem,
'Browser': browserName,
};
}

Expand Down

0 comments on commit 2614113

Please sign in to comment.