[web] Use fuzzy matching in Gold (flutter/engine#29847)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -387,6 +390,7 @@ class BrowserPlatform extends PlatformPlugin {
|
||||
pixelComparison,
|
||||
maxDiffRateFailure,
|
||||
skiaClient,
|
||||
isCanvaskitTest: isCanvaskitTest,
|
||||
goldensDirectory: goldensDirectory,
|
||||
filenameSuffix: _screenshotManager!.filenameSuffix,
|
||||
write: write,
|
||||
|
||||
@@ -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(
|
||||
@@ -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
|
||||
|
||||
@@ -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 = '',
|
||||
@@ -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');
|
||||
@@ -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);
|
||||
@@ -172,11 +174,13 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,16 +188,20 @@ 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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -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>[
|
||||
@@ -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);
|
||||
@@ -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>[
|
||||
@@ -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);
|
||||
@@ -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 {
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user