blob: 6b6ec48bd312c1ec7441ae332b4291507db861b2 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:html';
import 'dart:typed_data';
import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' hide TextStyle;
void main() {
internalBootstrapBrowserTest(() => testMain);
}
typedef _ListPredicate<T> = bool Function(List<T>);
_ListPredicate<T> deepEqualList<T>(List<T> a) {
return (List<T> b) {
if (a.length != b.length)
return false;
for (int i = 0; i < a.length; i += 1) {
if (a[i] != b[i])
return false;
}
return true;
};
}
Matcher listEqual(List<int> source, {int tolerance = 0}) {
return predicate(
(List<int> target) {
if (source.length != target.length)
return false;
for (int i = 0; i < source.length; i += 1) {
if ((source[i] - target[i]).abs() > tolerance)
return false;
}
return true;
},
source.toString(),
);
}
// Converts `rawPixels` into a list of bytes that represent raw pixels in rgba8888.
//
// Each element of `rawPixels` represents a bytes in order 0xRRGGBBAA, with
// pixel order Left to right, then top to bottom.
Uint8List _pixelsToBytes(List<int> rawPixels) {
return Uint8List.fromList((() sync* {
for (final int pixel in rawPixels) {
yield (pixel >> 24) & 0xff; // r
yield (pixel >> 16) & 0xff; // g
yield (pixel >> 8) & 0xff; // b
yield (pixel >> 0) & 0xff; // a
}
})().toList());
}
Future<Image> _encodeToHtmlThenDecode(
Uint8List rawBytes,
int width,
int height, {
PixelFormat pixelFormat = PixelFormat.rgba8888,
}) async {
final ImageDescriptor descriptor = ImageDescriptor.raw(
await ImmutableBuffer.fromUint8List(rawBytes),
width: width,
height: height,
pixelFormat: pixelFormat,
);
return (await (await descriptor.instantiateCodec()).getNextFrame()).image;
}
Future<void> testMain() async {
test('Correctly encodes an opaque image', () async {
// A 2x2 testing image without transparency.
final Image sourceImage = await _encodeToHtmlThenDecode(
_pixelsToBytes(
<int>[0xFF0102FF, 0x04FE05FF, 0x0708FDFF, 0x0A0B0C00],
), 2, 2,
);
final Uint8List actualPixels = Uint8List.sublistView(
(await sourceImage.toByteData(format: ImageByteFormat.rawStraightRgba))!);
// The `benchmarkPixels` is identical to `sourceImage` except for the fully
// transparent last pixel, whose channels are turned 0.
final Uint8List benchmarkPixels = _pixelsToBytes(
<int>[0xFF0102FF, 0x04FE05FF, 0x0708FDFF, 0x00000000],
);
expect(actualPixels, listEqual(benchmarkPixels));
});
test('Correctly encodes an opaque image in bgra8888', () async {
// A 2x2 testing image without transparency.
final Image sourceImage = await _encodeToHtmlThenDecode(
_pixelsToBytes(
<int>[0xFF0102FF, 0x04FE05FF, 0x0708FDFF, 0x0A0B0C00],
), 2, 2, pixelFormat: PixelFormat.bgra8888,
);
final Uint8List actualPixels = Uint8List.sublistView(
(await sourceImage.toByteData(format: ImageByteFormat.rawStraightRgba))!);
// The `benchmarkPixels` is the same as `sourceImage` except that the R and
// G channels are swapped and the fully transparent last pixel is turned 0.
final Uint8List benchmarkPixels = _pixelsToBytes(
<int>[0x0201FFFF, 0x05FE04FF, 0xFD0807FF, 0x00000000],
);
expect(actualPixels, listEqual(benchmarkPixels));
});
test('Correctly encodes a transparent image', () async {
// A 2x2 testing image with transparency.
final Image sourceImage = await _encodeToHtmlThenDecode(
_pixelsToBytes(
<int>[0xFF800006, 0xFF800080, 0xFF8000C0, 0xFF8000FF],
), 2, 2,
);
final Image blueBackground = await _encodeToHtmlThenDecode(
_pixelsToBytes(
<int>[0x0000FFFF, 0x0000FFFF, 0x0000FFFF, 0x0000FFFF],
), 2, 2,
);
// The standard way of testing the raw bytes of `sourceImage` is to draw
// the image onto a canvas and fetch its data (see HtmlImage.toByteData).
// But here, we draw an opaque background first before drawing the image,
// and test if the blended result is expected.
//
// This is because, if we only draw the `sourceImage`, the resulting pixels
// will be slightly off from the raw pixels. The reason is unknown, but
// very likely because the canvas.getImageData introduces rounding errors
// if any pixels are left semi-transparent, which might be caused by
// converting to and from pre-multiplied values. See
// https://github.com/flutter/flutter/issues/92958 .
final CanvasElement canvas = CanvasElement()
..width = 2
..height = 2;
final CanvasRenderingContext2D ctx = canvas.context2D;
ctx.drawImage((blueBackground as HtmlImage).imgElement, 0, 0);
ctx.drawImage((sourceImage as HtmlImage).imgElement, 0, 0);
final ImageData imageData = ctx.getImageData(0, 0, 2, 2);
final List<int> actualPixels = imageData.data;
final Uint8List benchmarkPixels = _pixelsToBytes(
<int>[0x0603F9FF, 0x80407FFF, 0xC0603FFF, 0xFF8000FF],
);
expect(actualPixels, listEqual(benchmarkPixels, tolerance: 1));
});
}