Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions lib/web_ui/lib/src/engine/compositor/image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,36 @@
// @dart = 2.10
part of engine;

/// Instantiates a [ui.Codec] backed by an `SkImage` from Skia.
/// Instantiates a [ui.Codec] backed by an `SkAnimatedImage` from Skia.
void skiaInstantiateImageCodec(Uint8List list, Callback<ui.Codec> callback,
[int? width, int? height, int? format, int? rowBytes]) {
final SkAnimatedImage skAnimatedImage = canvasKit.MakeAnimatedImageFromEncoded(list);
final SkAnimatedImage skAnimatedImage =
canvasKit.MakeAnimatedImageFromEncoded(list);
final CkAnimatedImage animatedImage = CkAnimatedImage(skAnimatedImage);
final CkAnimatedImageCodec codec = CkAnimatedImageCodec(animatedImage);
callback(codec);
}

/// Instantiates a [ui.Codec] backed by an `SkAnimatedImage` from Skia after requesting from URI.
void skiaInstantiateWebImageCodec(String src, Callback<ui.Codec> callback,
WebOnlyImageCodecChunkCallback? chunkCallback) {
chunkCallback?.call(0, 100);
//TODO: Switch to using MakeImageFromCanvasImageSource when animated images are supported.
html.HttpRequest.request(
src,
responseType: "arraybuffer",
).then((html.HttpRequest response) {
chunkCallback?.call(100, 100);
final Uint8List list =
new Uint8List.view((response.response as ByteBuffer));
final SkAnimatedImage skAnimatedImage =
canvasKit.MakeAnimatedImageFromEncoded(list);
final CkAnimatedImage animatedImage = CkAnimatedImage(skAnimatedImage);
final CkAnimatedImageCodec codec = CkAnimatedImageCodec(animatedImage);
callback(codec);
});
}

/// A wrapper for `SkAnimatedImage`.
class CkAnimatedImage implements ui.Image {
final SkAnimatedImage _skAnimatedImage;
Expand Down
8 changes: 2 additions & 6 deletions lib/web_ui/lib/src/engine/html_image_codec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,13 @@ class HtmlCodec implements ui.Codec {
// Currently there is no way to watch decode progress, so
// we add 0/100 , 100/100 progress callbacks to enable loading progress
// builders to create UI.
if (chunkCallback != null) {
chunkCallback!(0, 100);
}
chunkCallback?.call(0, 100);
if (_supportsDecode) {
final html.ImageElement imgElement = html.ImageElement();
imgElement.src = src;
js_util.setProperty(imgElement, 'decoding', 'async');
imgElement.decode().then((dynamic _) {
if (chunkCallback != null) {
chunkCallback!(100, 100);
}
chunkCallback?.call(100, 100);
final HtmlImage image = HtmlImage(
imgElement,
imgElement.naturalWidth,
Expand Down
121 changes: 67 additions & 54 deletions lib/web_ui/lib/src/ui/painting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ part of ui;

// ignore: unused_element, Used in Shader assert.
bool _offsetIsValid(Offset offset) {
assert(offset != null, 'Offset argument was null.'); // ignore: unnecessary_null_comparison
assert(offset != null,
'Offset argument was null.'); // ignore: unnecessary_null_comparison
assert(!offset.dx.isNaN && !offset.dy.isNaN,
'Offset argument contained a NaN value.');
return true;
}

// ignore: unused_element, Used in Shader assert.
bool _matrix4IsValid(Float32List matrix4) {
assert(matrix4 != null, 'Matrix4 argument was null.'); // ignore: unnecessary_null_comparison
assert(matrix4 != null,
'Matrix4 argument was null.'); // ignore: unnecessary_null_comparison
assert(matrix4.length == 16, 'Matrix4 must have 16 entries.');
return true;
}
Expand Down Expand Up @@ -246,8 +248,7 @@ class Color {
if (other.runtimeType != runtimeType) {
return false;
}
return other is Color
&& other.value == value;
return other is Color && other.value == value;
}

@override
Expand Down Expand Up @@ -1141,7 +1142,8 @@ abstract class Gradient extends Shader {
_validateColorStops(colors, colorStops);
// If focal is null or focal radius is null, this should be treated as a regular radial gradient
// If focal == center and the focal radius is 0.0, it's still a regular radial gradient
final Float32List? matrix32 = matrix4 != null ? engine.toMatrix32(matrix4) : null;
final Float32List? matrix32 =
matrix4 != null ? engine.toMatrix32(matrix4) : null;
if (focal == null || (focal == center && focalRadius == 0.0)) {
return engine.GradientRadial(
center, radius, colors, colorStops, tileMode, matrix32);
Expand Down Expand Up @@ -1184,12 +1186,12 @@ abstract class Gradient extends Shader {
List<Color> colors, [
List<double>? colorStops,
TileMode tileMode = TileMode.clamp,
double startAngle/*?*/ = 0.0,
double endAngle/*!*/ = math.pi * 2,
double startAngle /*?*/ = 0.0,
double endAngle /*!*/ = math.pi * 2,
Float64List? matrix4,
]) =>
engine.GradientSweep(
center, colors, colorStops, tileMode, startAngle, endAngle, matrix4 != null ? engine.toMatrix32(matrix4) : null);
engine.GradientSweep(center, colors, colorStops, tileMode, startAngle,
endAngle, matrix4 != null ? engine.toMatrix32(matrix4) : null);
}

/// Opaque handle to raw decoded image data (pixels).
Expand Down Expand Up @@ -1383,9 +1385,9 @@ class MaskFilter {

@override
bool operator ==(Object other) {
return other is MaskFilter
&& other._style == _style
&& other._sigma == _sigma;
return other is MaskFilter &&
other._style == _style &&
other._sigma == _sigma;
}

@override
Expand Down Expand Up @@ -1594,7 +1596,8 @@ String? _instantiateImageCodec(
} else {
assert(height != null);
assert(format != null);
engine.skiaInstantiateImageCodec(list, callback, width, height, format!.index, rowBytes);
engine.skiaInstantiateImageCodec(
list, callback, width, height, format!.index, rowBytes);
}
return null;
}
Expand All @@ -1613,8 +1616,14 @@ String? _instantiateImageCodecFromUrl(
Uri uri,
engine.WebOnlyImageCodecChunkCallback? chunkCallback,
engine.Callback<Codec> callback) {
callback(engine.HtmlCodec(uri.toString(), chunkCallback: chunkCallback));
return null;
if (engine.experimentalUseSkia) {
engine.skiaInstantiateWebImageCodec(
uri.toString(), callback, chunkCallback);
return null;
} else {
callback(engine.HtmlCodec(uri.toString(), chunkCallback: chunkCallback));
return null;
}
}

/// Loads a single image frame from a byte array into an [Image] object.
Expand Down Expand Up @@ -1650,16 +1659,16 @@ void decodeImageFromPixels(
int? targetHeight,
bool allowUpscaling = true,
}) {
final Future<Codec> codecFuture = _futurize(
(engine.Callback<Codec> callback) {
return _instantiateImageCodec(
pixels,
callback,
width: width,
height: height,
format: format,
rowBytes: rowBytes,
);
final Future<Codec> codecFuture =
_futurize((engine.Callback<Codec> callback) {
return _instantiateImageCodec(
pixels,
callback,
width: width,
height: height,
format: format,
rowBytes: rowBytes,
);
});
codecFuture
.then((Codec codec) => codec.getNextFrame())
Expand All @@ -1684,8 +1693,10 @@ class Shadow {
this.color = const Color(_kColorDefault),
this.offset = Offset.zero,
this.blurRadius = 0.0,
}) : assert(color != null, 'Text shadow color was null.'), // ignore: unnecessary_null_comparison
assert(offset != null, 'Text shadow offset was null.'), // ignore: unnecessary_null_comparison
}) : assert(color != null,
'Text shadow color was null.'), // ignore: unnecessary_null_comparison
assert(offset != null,
'Text shadow offset was null.'), // ignore: unnecessary_null_comparison
assert(blurRadius >= 0.0,
'Text shadow blur radius should be non-negative.');

Expand Down Expand Up @@ -1816,10 +1827,10 @@ class Shadow {
if (identical(this, other)) {
return true;
}
return other is Shadow
&& other.color == color
&& other.offset == offset
&& other.blurRadius == blurRadius;
return other is Shadow &&
other.color == color &&
other.offset == offset &&
other.blurRadius == blurRadius;
}

@override
Expand All @@ -1829,7 +1840,6 @@ class Shadow {
String toString() => 'TextShadow($color, $offset, $blurRadius)';
}


/// A shader (as used by [Paint.shader]) that tiles an image.
class ImageShader extends Shader {
/// Creates an image-tiling shader. The first argument specifies the image to
Expand All @@ -1838,19 +1848,14 @@ class ImageShader extends Shader {
/// matrix to apply to the effect. All the arguments are required and must not
/// be null.
factory ImageShader(
Image image,
TileMode tmx,
TileMode tmy,
Float64List matrix4) {
Image image, TileMode tmx, TileMode tmy, Float64List matrix4) {
if (engine.experimentalUseSkia) {
return engine.EngineImageShader(image, tmx, tmy, matrix4);
}
throw UnsupportedError(
'ImageShader not implemented for web platform.');
throw UnsupportedError('ImageShader not implemented for web platform.');
}
}


/// A handle to a read-only byte buffer that is managed by the engine.
class ImmutableBuffer {
ImmutableBuffer._(this.length);
Expand Down Expand Up @@ -1878,7 +1883,11 @@ class ImmutableBuffer {
/// Use this class to determine the height, width, and byte size of image data
/// before decoding it.
class ImageDescriptor {
ImageDescriptor._() : _width = null, _height = null, _rowBytes = null, _format = null;
ImageDescriptor._()
: _width = null,
_height = null,
_rowBytes = null,
_format = null;

/// Creates an image descriptor from encoded data in a supported format.
static Future<ImageDescriptor> encoded(ImmutableBuffer buffer) async {
Expand All @@ -1902,7 +1911,10 @@ class ImageDescriptor {
required int height,
int? rowBytes,
required PixelFormat pixelFormat,
}) : _width = width, _height = height, _rowBytes = rowBytes, _format = pixelFormat {
}) : _width = width,
_height = height,
_rowBytes = rowBytes,
_format = pixelFormat {
_data = buffer._list;
}

Expand All @@ -1913,7 +1925,8 @@ class ImageDescriptor {
final PixelFormat? _format;

Never _throw(String parameter) {
throw UnsupportedError('ImageDescriptor.$parameter is not supported on web.');
throw UnsupportedError(
'ImageDescriptor.$parameter is not supported on web.');
}

/// The width, in pixels, of the image.
Expand All @@ -1923,7 +1936,8 @@ class ImageDescriptor {
int get height => _height ?? _throw('height');

/// The number of bytes per pixel in the image.
int get bytesPerPixel => throw UnsupportedError('ImageDescriptor.bytesPerPixel is not supported on web.');
int get bytesPerPixel => throw UnsupportedError(
'ImageDescriptor.bytesPerPixel is not supported on web.');

/// Release the resources used by this object. The object is no longer usable
/// after this method is called.
Expand All @@ -1943,16 +1957,15 @@ class ImageDescriptor {
allowUpscaling: false,
);
}
return _futurize(
(engine.Callback<Codec> callback) {
return _instantiateImageCodec(
_data!,
callback,
width: _width,
height: _height,
format: _format,
rowBytes: _rowBytes,
);
});
return _futurize((engine.Callback<Codec> callback) {
return _instantiateImageCodec(
_data!,
callback,
width: _width,
height: _height,
format: _format,
rowBytes: _rowBytes,
);
});
}
}
22 changes: 21 additions & 1 deletion lib/web_ui/test/engine/image/html_image_codec_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,27 @@ Future<void> main() async {
test('provides image loading progress', () async {
StringBuffer buffer = new StringBuffer();
final HtmlCodec codec = HtmlCodec('sample_image1.png',
chunkCallback: (int loaded, int total) {
chunkCallback: (int loaded, int total) {
buffer.write('$loaded/$total,');
});
await codec.getNextFrame();
expect(buffer.toString(), '0/100,100/100,');
});
});

group('ImageCodecUrl', () {
test('loads sample image from web', () async {
final Uri uri = Uri.base.resolve('sample_image1.png');
final HtmlCodec codec = await ui.webOnlyInstantiateImageCodecFromUrl(uri);
final ui.FrameInfo frameInfo = await codec.getNextFrame();
expect(frameInfo.image, isNotNull);
expect(frameInfo.image.width, 100);
});
test('provides image loading progress from web', () async {
final Uri uri = Uri.base.resolve('sample_image1.png');
StringBuffer buffer = new StringBuffer();
final HtmlCodec codec = await ui.webOnlyInstantiateImageCodecFromUrl(uri,
chunkCallback: (int loaded, int total) {
buffer.write('$loaded/$total,');
});
await codec.getNextFrame();
Expand Down