In flutter I created a simple drawing app which support android, iOS and web. It will load image from url as the background, and users will be able to draw lines and add texts on it. The problem is on the web, specifically in chrome-based browser, the background is missing, although it works like a charm in Safari & Firefox.
first, I have image url from network, I convert it into dart:ui
Image
Future<ui.Image?> loadUiNetworkImage(String? url) async {
if (url == null) return null;
final http.Response response = await http.get(Uri.parse(url));
final list = response.bodyBytes;
final completer = Completer<ui.Image>();
ui.decodeImageFromList(list, completer.complete);
return completer.future;
}
final background = await loadUiNetworkImage(backgroundUrl);
then in my CustomPainter widget I put the image as the background using paintImage
function:
@override
void paint(Canvas canvas, Size size) {
_addBackground(ui.Image background, {double scale = 1.0}) {
final width = background.width * scale;
final height = background.height * scale;
final left = (size.width - width) / 2.0;
final top = (size.height - height) / 2.0;
paintImage(
canvas: canvas,
rect: Rect.fromLTWH(left, top, width, height),
image: background,
fit: BoxFit.scaleDown,
repeat: ImageRepeat.noRepeat,
scale: 1.0,
alignment: Alignment.center,
flipHorizontally: false,
filterQuality: FilterQuality.high
);
}
if (background != null) {
final scale = min(size.width / background!.width, size.height / background!.height);
_addBackground(background!, scale: scale);
canvas.saveLayer(null, Paint());
}
// also some drawings i.e. `drawLine` & `TextPainter`
}
this is my CustomPainter resides in widget tree:
RepaintBoundary(
key: _globalKey,
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: Colors.white,
child: StreamBuilder<List<dynamic>>(
stream: linesStreamController.stream,
builder: (context, snapshot) {
return CustomPaint(
painter: Sketcher(
background: _background,
prevDrawing: _prevDrawing,
lines: _lines,
),
);
},
),
),
);
then I save the drawing or whole canvas as an image file using this function:
_save() async {
try {
final boundary = _globalKey.currentContext!.findRenderObject() as RenderRepaintBoundary;
final image = await boundary.toImage();
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
if (byteData == null) return h.showToast("Failed to process image, please try again!");
final pngBytes = byteData.buffer.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes);
final fileName = "${DateTime.now().millisecondsSinceEpoch}";
if (kIsWeb) {
Navigator.pop(context, [pngBytes]); // this is the bytes to be uploaded to server
} else {
final fileDir = (await getTemporaryDirectory()).path;
final File imgFile;
imgFile = File('$fileDir/$fileName.png');
imgFile.writeAsBytes(pngBytes);
Navigator.pop(context, [imgFile.path]);
}
} catch (e) {
print(e);
}
}
I ran it using canvaskit renderer:
flutter run -d chrome --web-renderer canvaskit
I suspect this is a bug in the blink (chrome) canvas or rendering engine. How to get around this problem?
Screenshots:
CodePudding user response:
The current workaround is building with BROWSER_IMAGE_DECODING_ENABLED=false:
flutter build web --release --web-renderer canvaskit --dart-define=BROWSER_IMAGE_DECODING_ENABLED=false
Seems like an issue with the image decoder on canvaskit. Going by this, it looks like this flag is going to go away. Hopefully we'll have a fix by then.
Oh also, supposedly this doesn't work on iOS Safari. See this.
EDIT: Seems to be only working in release mode. (tested on Chrome)