Repository navigation
Feature Request for a Synchronous way to Rasterize Images / Draw Pixels to Canvas #37180
Description
Use case
This feature request is about the Canvas class in engine/painting.dart.
As extensively discussed in #31598, using regular canvas operations to draw pixel data is extremely slow.
This makes the only option we have decoding an image from our raw pixel data, e.g. using decodeImageFromPixels. This has another problem, pointed out by @andrewackerman in the mentioned issue, which is that creating an Image in Dart is an async operations.
This means that we will have to postpone drawing our raw pixel data to the next frame if we wish to use real-time operations.
The async Image problem
Images in Flutter currently have the inherent problem that rasterizing / decoding them is always an asynchronous operation. This is why using them for drawing pixel data does not work synchronously, but it also creates a whole nother class of problems:
Any other canvas operation in Flutter that involves Images is also asynchronous. So not only can you not draw pixel data synchronously, but you cannot rasterize any Picture synchronously, which is necessary for achieving various effects (e.g. this). Also Canvas.drawAtlas suffers from this problem.
drawPixels proposal
Here is an example proposal (written by @andrewackerman in #31598) for directly drawing pixel data.
Note that this might not cover all use cases because often we do need to have a decoded image (dart:ui.Image) available to us, e.g. when using Canvas.drawAtlas. It should, however, give an idea as to what we are looking for.
[...] it should have a similar signature to
decodImageFromPixels[sic], so something like:
class Canvas {
...
void drawPixels(
Offset c,
Uint8List bytes,
int width,
int height,
PixelFormat pixelFormat, {
int rowBytes,
int targetWidth,
int targetHeight,
}) {
assert(c != null && bytes != null && width != null && height != null && pixelFormat != null);
assert(bytes.length == width * height * 4);
if (rowBytes == null) rowBytes = width * 4;
if (targetWidth == null && targetHeight == null) {
targetWidth = width;
targetHeight = height;
} else if (targetWidth == null) {
targetWidth = (targetHeight * (width / height)).toInt();
} else if (targetHeight == null) {
targetHeight = (targetWidth * (height / width)).toInt();
}
_drawPixels(c, pixelData, width, height, pixelFormat, rowBytes, targetWidth, targetHeight);
}
}Click to expand details
cis the position that the image will be drawn at.
bytesis the pixel data that will be drawn to the canvas, represented as byte groups of 4 bytes per pixel. The color channels will be extrapolated based on the value ofpixelFormat.
widthandheightare the dimensions of the image represented by the pixel data. Ifwidth * height * 4is not equal topixelData.length, an error will be thrown.
rowBytesis the number of bytes consumed by each row of pixels in the data buffer. If unspecified, it defaults to width multiplied by the number of bytes per pixel in the providedformat.The
targetWidth andtargetHeight` arguments specify the size of the output image, in image pixels. If they are not equal to the intrinsic dimensions of the image, then the image will be draw. If exactly one of these two arguments is specified, then the aspect ratio will be maintained while forcing the image to match the other given dimension. If neither is specified, then the image maintains its real size. (Debatable as to whether these parameters should be supported as they don't really fall into the whole "just draw these pixels" mentality of this method.)**
@andrewackerman assumed that the byte data will always have 32 bits per pixel, which makes sense because the only PixelFormat values that currently exist are bgra8888 and rgba8888, which use 32 bits per pixel.
The decodeImageFromPixels function is open to any PixelFormat that might be supported in the future:
rowBytesis the number of bytes consumed by each row of pixels in the data buffer. If unspecified, it defaults to width multiplied by the number of bytes per pixel in the providedformat.
I think that this approach would make sense to also be used for the Canvas method (if it will use PixelFormat). Consequently, the assertion for width * height * 4 should probably be width * height * bytesPerPixel and similarly, bytes should be pixel data, represented as byte groups of bytesPerPixel bytes per pixel.
Activity
Solido commentedon Aug 17, 2019
int width, int height & int targetWidth, int targetHeight should be replaced with Size class
andrewackerman commentedon Aug 18, 2019
Ideally, though this signature matches the style of the next closest existing method for this sort of thing (
decodeImageFromPixels). I could see arguments both for leaving it "low-levelish" using only primitives and for standardizing it withSize.Solido commentedon Aug 18, 2019
I get the idea but this method would not only be used in conjunction with
decodeImageFromPixelsand it can feel strange to have different pattern that other methods in Canvas that use Data Object (here it's Rect but it can be any other base class)void drawImageRect ( Image image, Rect src, Rect dst, Paint paint )I may be interested to draw pixel directly in a buffer without having Image ever exposed.
Just thinking loudly but I may be interested in having access to a Buffer object with low level methods and then high level methods under Canvas to draw pixels.
Also I think there's I miss about the title of this ticket. I want access to Buffer which is different from synchronous access. I don't know enough the framework but isn't this feature against the principles of the rendering pipeline and impossible (very bad idea) to implement ?
We should never block the main thread so I guess behind the future is the conversion to raster model in another Isolate.
creativecreatorormaybenot commentedon Aug 18, 2019
@Solido Well, the other canvas methods are synchronous. It would just have to be a little faster..
Solido commentedon Aug 18, 2019
Others methods are native. Not sure that what we're asking for is available is the same way else
drawPointswould be enoughcreativecreatorormaybenot commentedon Aug 18, 2019
@Solido Well, adding this definitely requires an engine adjustment.
andrewackerman commentedon Aug 18, 2019
My personal hope is that the implementation of this would be something along the lines of
Create internal texture from pixel data >> Paint texture to canvas/painting context. There are currently several methods to draw texture data to the screen (though exact methods escape me at the moment), but precious little documentation on what a "texture" is as those methods take a texture id as a parameter. If a texture is a Skia texture, then I can't imagine it would take that much effort to do it this way (relatively speaking, of course).It's also possible that low-level painting methods would require waiting for FFI to be finished so that the Skia rendering functions can be invoked directly, bypassing the Flutter engine altogether.
creativecreatorormaybenot commentedon Aug 18, 2019
@andrewackerman If you mean
Texture, that is unrelated to Skia. It is native textures, so not what you are looking for.Solido commentedon Aug 18, 2019
@andrewackerman There seems to be a lot of consideration done before integrating any new feature and make it visible from skia to flutter.
See Dotted lines #4858
Implemented in Skia since ever and still now in the API.
I let you imagine about exposing Texture ...
andrewackerman commentedon Aug 19, 2019
@creativecreatorormaybenot Actually that's exactly what I was looking for. The texture used by that widget is a native texture that can be, among other things, a texture registered to the internal OpenGL context. This is very nearly what would need to happen in order for raw pixel data rendering to get implemented, so it's comforting that it already exists. The only problem with it is that the actual supplying of the texture is done exclusively through native code, so there would need to be some kind of Dart-facing interface to expose the functionality, and doing that through platform channels would run into the same asynchronous and/or performance issues.
creativecreatorormaybenot commentedon Dec 18, 2019
If anyone knows of a faster way than manipulating the byte arrays using C++ and
dart:ffi, then getting images viadecodeImageFromPixelsasynchronously, and then rendering usingCanvas.drawImage, please let me know - I feel like this is severely limiting Flutter.I recently also read a very nicely written article touching upon exactly this problem - just this section hurt me:
considering all my efforts online regarding this topic.
58 remaining items
andy98725 commentedon May 8, 2023
Any updates on this? Trying to dynamically generate a perlin noise background, and there's no clear way from within Flutter.
fzyzcjy commentedon May 8, 2023
@andy98725 Not sure whether shaders can be used for that noise. e.g. https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83#perlin-noise
Abion47 commentedon May 8, 2023
@andy98725 @fzyzcjy I actually did something very similar when I was trying out the new shader features.
https://github.com/Abion47/flutter-book-of-shaders/blob/main/shaders/src/3-generative-designs/noise-1.glsl
jonahwilliams commentedon May 8, 2023
There are no updates because the feature itself is vague and if interpreted literally, technically impossible
razenpok commentedon Oct 5, 2023
How about adding a render thread callback? Unity does precisely that via GL.IssuePluginEvent, and it allows thread-safe custom rendering.
A demonstration of the API can be found in Plug-in callbacks on the rendering thread section here:
https://docs.unity3d.com/Manual/NativePluginInterface.html
C++ side:
C# side:
A full example of a rendering plugin can be found here
https://github.com/Unity-Technologies/NativeRenderingPlugin/blob/master/PluginSource/source/RenderingPlugin.cpp
Abion47 commentedon Oct 5, 2023
@razenpok Implementing the feature as a multithreaded callback strikes me as being idiomatically bizarre for Flutter. Dart uses Isolates for its multithreading which by definition don't share any memory with any other thread, so even if the work was done to somehow wrap the rendering thread in an Isolate to be able to invoke Dart code with it, compared to either a custom shader or the asynchronous options we already have, it sounds like far more work for the same or worse result. I'd imagine this feature would at the very least have to be implemented either as a native-code option or as an FFI plugin in order to even have a chance.
esDotDev commentedon Jul 13, 2024
It might be useful to look at some prior art here. When Flash added the
BitmapDataclass to AS3, it was a complete game-changer in how you could work with bitmaps in Flash. It has fast-copy methods for one Bitmap to another, and fast syncronous draw methods where we could blit any DisplayObject (widget) to a BitmapData.https://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/BitmapData.html
We abused this to do a ton of cool effects, it was useful for all kinds of usecases: advanced image filtering like blur/sharpen /other pixel level effects, caching/exporting complex vectors as a raster images etc. You can also combine multiple bitmapdata using blend-modes to create depth of field and tilt-shift style effects.
I think this is all "possible" now in Flutter, but it's slow and requires a lot of work. BitmapData was fast and low-level, which made an excellent foundation to build on. Instead of jumping through a million hoops at the Widget level, just give us a blazing fast path to manipulate textures directly.
If we look at the use-cases asked for above they would all be trivial with bitmapData API. You could just set a single bitmap to be displayed, and directly update it's pixels from dart,
It has lots of utility APIs that make things very easy for developers to work with efficiently:
The core idea here is
iterationharold commentedon Oct 16, 2025
gaaclarke commentedon Feb 24, 2026
There is a decodeImageFromPixelsSync now: https://api.flutter.dev/flutter/dart-ui/decodeImageFromPixelsSync.html
This should be resolved.
github-actions commentedon Mar 10, 2026
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of
flutter doctor -vand a minimal reproduction of the issue.