Repository navigation
Description
Steps to reproduce
- Extract the attached project zip
- Run flutter pub get
- Run flutter run -d chrome
- Press the "PNG Export" button
- Save and inspect the exported PNG
ZIP Source File:
Expected results
The exported PNG should be a pixel-perfect copy of the canvas content with clean edges and no corruption.
Actual results
The exported PNG has fully transparent (alpha=0) strips along the right and top edges. The drawn content appears squeezed/shifted into a smaller area within the full image dimensions.
At 4960×7016:
Top edge: transparent horizontal strip, full width (4960px), height 166px
Right edge: transparent vertical strip, full height (7016px), width 117px
Observations
Where it fails:
Chrome 145.0.7632.76, on two separate machines with different GPUs
Where it works correctly:
Firefox 147.0.4
Windows Desktop build
Android
Size dependency:
At 4960×7016 -> bug present
At 2480×3508 (half) -> works correctly
Format independent:
Both ImageByteFormat.png and ImageByteFormat.rawRgba produce the same corruption. The issue is in toImage(), not in toByteData() encoding.
WebGL parameters (Chrome DevTools):
(() => {
const c = document.createElement('canvas');
const gl = c.getContext('webgl2') || c.getContext('webgl');
if (!gl) { console.log('No WebGL'); return; }
const gp = p => gl.getParameter(p);
console.log('MAX_TEXTURE_SIZE:', gp(gl.MAX_TEXTURE_SIZE));
console.log('MAX_RENDERBUFFER_SIZE:', gp(gl.MAX_RENDERBUFFER_SIZE));
console.log('MAX_VIEWPORT_DIMS:', gp(gl.MAX_VIEWPORT_DIMS));
console.log('devicePixelRatio:', window.devicePixelRatio);
})();
- MAX_TEXTURE_SIZE: 16384
- MAX_RENDERBUFFER_SIZE: 16384
- MAX_VIEWPORT_DIMS: Int32Array(2) [32767, 32767]
- devicePixelRatio: 1
All WebGL limits are well above the requested canvas size.
Ruled out causes:
- WASM build vs JS build: bug present in both
- Canvas size aligned to multiples of 32: no effect
- Chrome ANGLE backend (D3D11, OpenGL, Vulkan, WARP): same on all
- Disable GPU (--disable-gpu --disable-gpu-compositing): same corrupt output
- Disable PWA / service worker: no effect
- Different GPU (GTX 1060 3GB vs RTX 4070): bug present on both
Tested on
- Flutter: 3.41.1
- Dart: 3.11.0
- Chrome: 145.0.7632.76
- Firefox: 147.0.4
- GPUs: NVIDIA GTX 1060 3GB, NVIDIA RTX 4070
- OS: Windows 11
- iOS: Not tested (no device available)
Code sample
Code sample
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'helpers/save_file_web.dart';
void main() => runApp(const MaterialApp(home: ExportTest()));
const int kW = 4960;
const int kH = 7016;
class ExportTest extends StatefulWidget {
const ExportTest({super.key});
@override
State<ExportTest> createState() => _ExportTestState();
}
class _ExportTestState extends State<ExportTest> {
ui.Image? _img;
String _status = 'Loading...';
@override
void initState() {
super.initState();
_load();
}
Future<void> _load() async {
final data = await rootBundle.load('assets/test.png');
final codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
final frame = await codec.getNextFrame();
setState(() {
_img = frame.image;
_status = 'Ready';
});
}
Future<void> _export() async {
if (_img == null) return;
setState(() => _status = 'Export...');
final rec = ui.PictureRecorder();
final canvas = Canvas(
rec,
Rect.fromLTWH(0, 0, kW.toDouble(), kH.toDouble()),
);
final src = Rect.fromLTWH(
0,
0,
_img!.width.toDouble(),
_img!.height.toDouble(),
);
final dst = Rect.fromLTWH(0, 0, kW.toDouble(), kH.toDouble());
canvas.drawImageRect(_img!, src, dst, Paint());
final picture = rec.endRecording();
final image = await picture.toImage(kW, kH);
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
if (byteData == null) {
setState(() => _status = 'Error: null');
return;
}
final bytes = byteData.buffer.asUint8List();
await FileSaveHelper.saveFile(bytes, 'image.png');
setState(
() =>
_status = 'OK: ${(bytes.length / 1024 / 1024).toStringAsFixed(1)} MB',
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (_img != null)
Flexible(
child: AspectRatio(
aspectRatio: kW / kH,
child: CustomPaint(painter: _Painter(_img!)),
),
),
const SizedBox(height: 16),
Text(_status),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _img == null ? null : _export,
child: const Text('PNG Export'),
),
],
),
),
);
}
}
class _Painter extends CustomPainter {
final ui.Image img;
_Painter(this.img);
@override
void paint(Canvas canvas, Size size) {
final src = Rect.fromLTWH(
0,
0,
img.width.toDouble(),
img.height.toDouble(),
);
final dst = Rect.fromLTWH(0, 0, size.width, size.height);
canvas.drawImageRect(img, src, dst, Paint());
}
@override
bool shouldRepaint(covariant _Painter old) => old.img != img;
}
Screenshots or Video
No response
Logs
No response
Flutter Doctor output
Doctor output
[√] Flutter (Channel stable, 3.41.1, on Microsoft Windows [Version 10.0.26200.7840], locale tr-TR) [355ms]
• Flutter version 3.41.1 on channel stable at D:\fluttersdk\flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision 582a0e7c55 (4 days ago), 2026-02-12 17:12:32 -0800
• Engine revision 3452d735bd
• Dart version 3.11.0
• DevTools version 2.54.1
• Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android,
enable-ios, cli-animations, enable-native-assets, omit-legacy-version-file, enable-lldb-debugging,
enable-uiscene-migration
[√] Windows Version (11 Pro 64-bit, 25H2, 2009) [1.098ms]
[√] Android toolchain - develop for Android devices (Android SDK version 36.1.0) [1.945ms]
• Android SDK at C:\Users\yesil\AppData\Local\Android\sdk
• Emulator version 36.4.9.0 (build_id 14788078) (CL:N/A)
• Platform android-36, build-tools 36.1.0
• Java binary at: C:\Program Files\Android\Android Studio\jbr\bin\java
This is the JDK bundled with the latest Android Studio installation on this machine.
To manually set the JDK path, use: `flutter config --jdk-dir="path/to/jdk"`.
• Java version OpenJDK Runtime Environment (build 21.0.9+-14649483-b1163.86)
• All Android licenses accepted.
[√] Chrome - develop for the web [83ms]
• Chrome at C:\Program Files\Google\Chrome\Application\chrome.exe
[√] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.14.26) [82ms]
• Visual Studio at C:\Program Files\Microsoft Visual Studio\2022\Community
• Visual Studio Community 2022 version 17.14.36930.0
• Windows 10 SDK version 10.0.26100.0
[√] Connected device (3 available) [178ms]
• Windows (desktop) • windows • windows-x64 • Microsoft Windows [Version 10.0.26200.7840]
• Chrome (web) • chrome • web-javascript • Google Chrome 145.0.7632.76
• Edge (web) • edge • web-javascript • Microsoft Edge 144.0.3719.115
[√] Network resources [546ms]
• All expected network resources are available.
• No issues found!
Activity
saurabh-mirajkar commentedon Feb 17, 2026
@yesilcimenahmet Thanks for the report.
yesilcimenahmet commentedon Feb 17, 2026
@saurabh-mirajkar
This bug is blocking our production release. Our application relies on exporting high-resolution images at 4960×7016, and this issue makes it impossible to export correctly on Chrome, which is the primary browser for our users.
Is there any estimate on when this might be picked up? This is a critical blocker for us.
saurabh-mirajkar commentedon Feb 18, 2026
@yesilcimenahmet Thanks for the heads-up on the production impact. I’ve already assigned the ticket to
team-web. Please note that the team doesn't typically provide firm ETAs for fixes, however they will pick up the ticket soon. Thanks.stuartmorgan-g commentedon Feb 24, 2026
@saurabh-mirajkar You've marked this
c: regressionbut I don't see any discussion of a version where this worked. What version did you test that did not have this problem?saurabh-mirajkar commentedon Feb 24, 2026
@stuartmorgan-g My apologies, I previously tested this on Flutter 3.38.10 and Chrome 145 without issue. However, I can now confirm the regression in Flutter 3.41.1 using the same Chrome version.
jason-simmons commentedon Aug 12, 2026
Reproduced this in Chrome on macOS
This changed during the canvas/surface refactoring in cbd8912
Part of the output appears to be clipped when the canvas is resized to the large size used to produce the app's output image. Smaller sizes work as expected.
The output looks correct if I change
CkSurface.setSizeto create a new canvas instead of resizing the existing canvas.(by replacing the
_canvasProvider.resizeCanvascall withrecreateContextForCanvas(_canvasProvider.acquireCanvas(_currentSize, onContextLost: onContextLost)))@harryterkelsen
jason-simmons commentedon Aug 12, 2026
When run using Skwasm, this app also demonstrates a race related to the changes in cbd8912
On my macOS/Chrome setup with Skwasm, clicking the "PNG Export" button in the app produces a PNG with size 1290x1732 instead of the intended 4960x7016.
This happens because the onscreen display and the offscreen bitmap are both rendering using the same
Skwasm::Surfaceinstance. The size of theSkwasm::Surfaceis being overwritten before the offscreen bitmap is rendered.The offscreen bitmap is created by
SkwasmImage.toByteData, which is async and does anawait surface.setSizefollowed by anawait surface.rasterizeToImageBitmaps. Other tasks may run during those awaits.In particular, a
Renderer.renderScenemay be scheduled, which will trigger anothersetSizecall that sets the surface size to the onscreen window size. So by the timeSkwasmImage.toByteDatarasterizes the picture, the surface's size has been changed, and the output bitmap's size does not match the desired size.Upload glTF textures straight into the web backend's own context
harryterkelsen commentedon Oct 29, 2026
Quick status update on the two root causes Jason identified above:
The Skwasm shared-surface resize race between
renderSceneand offscreen rasterization was fixed onmasterin #194401, which made resize and render atomic in a single raster-worker message. I had an older draft PR isolatingpictureToImageSurface(#192770) that is superseded by that change, so I'm closing it.For the CanvasKit clipping at large dimensions (where Chromium silently clamps
gl.drawingBufferWidth/drawingBufferHeightduring in-placeOffscreenCanvasresize), I have a draft fix in #192769 that keeps fast in-place resizing normally and recreates the offscreen canvas when the drawing buffer is clamped. I'm rebasing that onto currentmasterto get it ready for review, and will also check whether Skwasm still hits the same Chromium WebGL drawing-buffer clamp at4960×7016.[web] Recreate canvas and context on CkOffscreenSurface resize to fix…
harryterkelsen commentedon Nov 10, 2026
Both underlying issues are now fixed on
master:CkOffscreenSurfaceWebGL buffer clamping: [web] Recreate canvas and context on CkOffscreenSurface resize to fix WebGL clipping #192769Closing this issue as fixed.