blob: 388af1f4343a390492eeaf69e39acd96f66169d6 [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.
// @dart = 2.12
part of engine;
/// 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 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.
Future<ui.Codec> skiaInstantiateWebImageCodec(
String src, WebOnlyImageCodecChunkCallback? chunkCallback) {
Completer<ui.Codec> completer = Completer<ui.Codec>();
//TODO: Switch to using MakeImageFromCanvasImageSource when animated images are supported.
html.HttpRequest.request(src, responseType: "arraybuffer",
onProgress: (html.ProgressEvent event) {
if (event.lengthComputable) {
chunkCallback?.call(event.loaded!, event.total!);
}
}).then((html.HttpRequest response) {
if (response.status != 200) {
completer.completeError(Exception(
'Network image request failed with status: ${response.status}'));
}
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);
completer.complete(codec);
}, onError: (dynamic error) {
completer.completeError(error);
});
return completer.future;
}
/// A wrapper for `SkAnimatedImage`.
class CkAnimatedImage implements ui.Image {
// Use a box because `SkImage` may be deleted either due to this object
// being garbage-collected, or by an explicit call to [delete].
late final SkiaObjectBox<SkAnimatedImage> box;
SkAnimatedImage get _skAnimatedImage => box.skObject;
CkAnimatedImage(SkAnimatedImage skAnimatedImage) {
box = SkiaObjectBox<SkAnimatedImage>(this, skAnimatedImage);
}
CkAnimatedImage.cloneOf(SkiaObjectBox<SkAnimatedImage> boxToClone) {
box = boxToClone.clone(this);
}
bool _disposed = false;
@override
void dispose() {
box.delete();
_disposed = true;
}
@override
bool get debugDisposed {
if (assertionsEnabled) {
return _disposed;
}
throw StateError(
'Image.debugDisposed is only available when asserts are enabled.');
}
ui.Image clone() => CkAnimatedImage.cloneOf(box);
@override
bool isCloneOf(ui.Image other) {
return other is CkAnimatedImage &&
other._skAnimatedImage.isAliasOf(_skAnimatedImage);
}
@override
List<StackTrace>? debugGetOpenHandleStackTraces() =>
box.debugGetStackTraces();
int get frameCount => _skAnimatedImage.getFrameCount();
/// Decodes the next frame and returns the frame duration.
Duration decodeNextFrame() {
final int durationMillis = _skAnimatedImage.decodeNextFrame();
return Duration(milliseconds: durationMillis);
}
int get repetitionCount => _skAnimatedImage.getRepetitionCount();
CkImage get currentFrameAsImage {
return CkImage(_skAnimatedImage.getCurrentFrame());
}
@override
int get width => _skAnimatedImage.width();
@override
int get height => _skAnimatedImage.height();
@override
Future<ByteData> toByteData(
{ui.ImageByteFormat format = ui.ImageByteFormat.rawRgba}) {
Uint8List bytes;
if (format == ui.ImageByteFormat.rawRgba) {
final SkImageInfo imageInfo = SkImageInfo(
alphaType: canvasKit.AlphaType.Premul,
colorType: canvasKit.ColorType.RGBA_8888,
colorSpace: SkColorSpaceSRGB,
width: width,
height: height,
);
bytes = _skAnimatedImage.readPixels(imageInfo, 0, 0);
} else {
// Defaults to PNG 100%.
final SkData skData = _skAnimatedImage.encodeToData();
// Make a copy that we can return.
bytes = Uint8List.fromList(canvasKit.getDataBytes(skData));
}
final ByteData data = bytes.buffer.asByteData(0, bytes.length);
return Future<ByteData>.value(data);
}
@override
String toString() => '[$width\u00D7$height]';
}
/// A [ui.Image] backed by an `SkImage` from Skia.
class CkImage implements ui.Image {
// Use a box because `SkImage` may be deleted either due to this object
// being garbage-collected, or by an explicit call to [delete].
late final SkiaObjectBox<SkImage> box;
SkImage get skImage => box.skObject;
CkImage(SkImage skImage) {
box = SkiaObjectBox<SkImage>(this, skImage);
}
CkImage.cloneOf(SkiaObjectBox<SkImage> boxToClone) {
box = boxToClone.clone(this);
}
bool _disposed = false;
@override
void dispose() {
box.delete();
assert(() {
_disposed = true;
return true;
}());
}
@override
bool get debugDisposed {
if (assertionsEnabled) {
return _disposed;
}
throw StateError(
'Image.debugDisposed is only available when asserts are enabled.');
}
@override
ui.Image clone() => CkImage.cloneOf(box);
@override
bool isCloneOf(ui.Image other) {
return other is CkImage && other.skImage.isAliasOf(skImage);
}
@override
List<StackTrace>? debugGetOpenHandleStackTraces() =>
box.debugGetStackTraces();
@override
int get width => skImage.width();
@override
int get height => skImage.height();
@override
Future<ByteData> toByteData(
{ui.ImageByteFormat format = ui.ImageByteFormat.rawRgba}) {
Uint8List bytes;
if (format == ui.ImageByteFormat.rawRgba) {
final SkImageInfo imageInfo = SkImageInfo(
alphaType: canvasKit.AlphaType.Premul,
colorType: canvasKit.ColorType.RGBA_8888,
colorSpace: SkColorSpaceSRGB,
width: width,
height: height,
);
bytes = skImage.readPixels(imageInfo, 0, 0);
} else {
final SkData skData = skImage.encodeToData(); //defaults to PNG 100%
// make a copy that we can return
bytes = Uint8List.fromList(canvasKit.getDataBytes(skData));
}
final ByteData data = bytes.buffer.asByteData(0, bytes.length);
return Future<ByteData>.value(data);
}
@override
String toString() => '[$width\u00D7$height]';
}
/// A [Codec] that wraps an `SkAnimatedImage`.
class CkAnimatedImageCodec implements ui.Codec {
CkAnimatedImage animatedImage;
CkAnimatedImageCodec(this.animatedImage);
@override
void dispose() {
animatedImage.dispose();
}
@override
int get frameCount => animatedImage.frameCount;
@override
int get repetitionCount => animatedImage.repetitionCount;
@override
Future<ui.FrameInfo> getNextFrame() {
final Duration duration = animatedImage.decodeNextFrame();
final CkImage image = animatedImage.currentFrameAsImage;
return Future<ui.FrameInfo>.value(AnimatedImageFrameInfo(duration, image));
}
}
/// Data for a single frame of an animated image.
class AnimatedImageFrameInfo implements ui.FrameInfo {
final Duration _duration;
final CkImage _image;
AnimatedImageFrameInfo(this._duration, this._image);
@override
Duration get duration => _duration;
@override
ui.Image get image => _image;
}