blob: 1734c414ccd1c0e6341db6f2f25ba3058fcedc4a [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:async';
import 'package:ui/ui.dart' as ui;
import 'dom.dart';
import 'html/bitmap_canvas.dart';
import 'html/recording_canvas.dart';
import 'html_image_codec.dart';
import 'safe_browser_api.dart';
import 'util.dart';
/// An implementation of [ui.PictureRecorder] backed by a [RecordingCanvas].
class EnginePictureRecorder implements ui.PictureRecorder {
EnginePictureRecorder();
RecordingCanvas? _canvas;
late ui.Rect cullRect;
bool _isRecording = false;
RecordingCanvas beginRecording(ui.Rect bounds) {
assert(!_isRecording);
cullRect = bounds;
_isRecording = true;
return _canvas = RecordingCanvas(cullRect);
}
@override
bool get isRecording => _isRecording;
@override
EnginePicture endRecording() {
if (!_isRecording) {
// The mobile version returns an empty picture in this case. To match the
// behavior we produce a blank picture too.
beginRecording(ui.Rect.largest);
}
_isRecording = false;
_canvas!.endRecording();
return EnginePicture(_canvas, cullRect);
}
}
/// An implementation of [ui.Picture] which is backed by a [RecordingCanvas].
class EnginePicture implements ui.Picture {
/// This class is created by the engine, and should not be instantiated
/// or extended directly.
///
/// To create a [Picture], use a [PictureRecorder].
EnginePicture(this.recordingCanvas, this.cullRect);
@override
Future<ui.Image> toImage(int width, int height) async {
final ui.Rect imageRect = ui.Rect.fromLTRB(0, 0, width.toDouble(), height.toDouble());
final BitmapCanvas canvas = BitmapCanvas.imageData(imageRect);
recordingCanvas!.apply(canvas, imageRect);
final String imageDataUrl = canvas.toDataUrl();
final DomHTMLImageElement imageElement = createDomHTMLImageElement()
..src = imageDataUrl
..width = width
..height = height;
// The image loads asynchronously. We need to wait before returning,
// otherwise the returned HtmlImage will be temporarily unusable.
final Completer<ui.Image> onImageLoaded = Completer<ui.Image>.sync();
// Ignoring the returned futures from onError and onLoad because we're
// communicating through the `onImageLoaded` completer.
late final DomEventListener errorListener;
errorListener = allowInterop((DomEvent event) {
onImageLoaded.completeError(event);
imageElement.removeEventListener('error', errorListener);
});
imageElement.addEventListener('error', errorListener);
late final DomEventListener loadListener;
loadListener = allowInterop((DomEvent event) {
onImageLoaded.complete(HtmlImage(
imageElement,
width,
height,
));
imageElement.removeEventListener('load', loadListener);
});
imageElement.addEventListener('load', loadListener);
return onImageLoaded.future;
}
@override
ui.Image toGpuImage(int width, int height) {
throw UnsupportedError('toGpuImage is not supported on the HTML backend. Use drawPicture instead, or toImage.');
}
bool _disposed = false;
@override
void dispose() {
_disposed = true;
}
@override
bool get debugDisposed {
if (assertionsEnabled) {
return _disposed;
}
throw StateError('Picture.debugDisposed is only available when asserts are enabled.');
}
@override
int get approximateBytesUsed => 0;
final RecordingCanvas? recordingCanvas;
final ui.Rect? cullRect;
}