-
Notifications
You must be signed in to change notification settings - Fork 2.9k
[Proposal] ImageBitmap.getImageData #4785
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I don't really understand the proposal (in particular it seems you're suggesting to use a detached |
@annevk, I'm not suggesting using a detached ArrayBuffer, I'm suggesting detaching one. let imageBitmap; // Assume this is an ImageBitmap we want ImageData from
const oldImageData = new ImageData(1920, 1080);
const oldArrayBuffer = oldImageData.data.buffer;
const newImageData = await imageBitmap.getImageData(0, 0, 1920, 1080, { buffer: oldArrayBuffer });
const newArrayBuffer = newImageData.data.buffer;
// oldArrayBuffer is now detached - the memory it had now belongs to newArrayBuffer That's an overly wordy example with the variable names, how it would actually be used with let imageBitmap;
let imageData = new ImageData(1920, 1080);
while (true) {
imageBitmap = await imageCapturer.grabFrame();
imageData = await imageBitmap.getImageData(0, 0, 1920, 1080, { buffer: imageData.data.buffer });
// Process ImageData
} |
This would be useful also for cases where code wants to get raw decoded image data. Currently constructed However, there is currently no way to read raw pixels back from the canvas once the image bitmap has been transferred to it, because Canvas allows getting context only of a single type and |
Referring to the closure of #4748 |
@Kaiido, thanks for taking a look at this.
Sure, there's plenty of benefit to it existing in both places. As I mentioned in the original comment ("in providing a new API it can have changes which would otherwise be breaking for So, yes, a synchronous version of this proposal could also be implemented with
I believe you're right, if you're solely concerned with |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
cc @whatwg/canvas |
I'm a bit torn by this asynchronicity thing. On the one hand, I can see very well how having more async APIs is a good thing, and how we should aim at making most new APIs that could work in parallel Promise based. I believe that the slowest operation that will be done here will be the read-back of bitmaps living in the GPU*, and this can't be done asynchronously because indeed the ImageBitmap could be closed right after the call, synchronously discarding the bitmap before it's been read. I fear this ends just like *(I could be wrong here, maybe color-space conversion or unmultiplying are also slow operations?) |
A new pending async readback would hold a strong ref to the resource, preventing actual closing of the resource. (this is the details behind the scenes, but this way close does precipitate release mostly-deterministically, unreliant on GC) (The alternative would be rejecting outstanding read promises on close(), but I don't think we want that)
cpu-side colorspace conversion is slow (loooots of ops per pixel). Readback isn't really cpu-heavy per se, but rather potentially high-latency, as we have to wait for that resource to be "done". Otherwise it's just a copy or two, not slow per se. |
As a user that reads images, reads their data (for elevation gis tiles), modifies their data (steganography), and needs to write it back out as a modified image (whew!) I REALLY need a lossless workflow. ImageBitmap is at least hopeful with options to avoid alpha premultiply and color-space conversion. For example, I currently have to use a webgl stunt to convert an image into reliabally lossless data. It gets weirder bundling the new modified data back to the file system. Have pity on us image-as-data folks!! |
I hope this gets the interest of implementers because it'd be nice to have this more direct access to |
Ran into yet another project where I'd really want to decode image to raw RGBA data and remembered this issue. It would be really helpful to have getImageData on ImageBitmap itself, rather than going through the whole create canvas -> get 2D context -> draw image -> get image data back, both because it's slow & unergonomic, and because it doesn't actually preserve original RGBA values (due to premultiplication mentioned earlier). |
Should be noted that WebCodecs do now offer a mean to get that data without going through a canvas. You can either create a |
@Kaiido Thanks, I missed the ability to do this via new APIs (probably because, intuitively, VideoFrame seemed unrelated to just image decoding). But yeah, while performance is one aspect of it, if it returns different formats, then it doesn't quite solve the usecase of just decoding image into RGBA data like we can with canvas. This step is still important / useful for graphic manipulation via external libraries, especially those compiled to Wasm. Admittedly, users can compile format conversion to Wasm too, but, assuming I'm not alone in using graphic manipulation libraries in Wasm, it would be good to avoid that bloat and have the format handled by the decoding APIs (maybe as part of |
w3c/webcodecs#92 is about adding such an API, however the current status is that it's not really required because going through an So if you want raw data as fast as possible, you take directly the data from the However I don't see anything there enforcing that behavior, and indeed in my tests against current Chrome's implementation, it does correctly convert from |
Not sure what you mean here by "going through an ImageBitmap". I mean, this issue is exactly about getting RGBA data (as Uint8Array) from ImageBitmap, which today is not possible without additionally going via Canvas -> 2d context -> drawImage -> getImageData. |
Ah, I guess you're saying that creating If so, yeah, if that issue is resolved and formats are converted consistently, that could be a viable alternative. |
Yes that's what I mean. |
Thanks for the clarification. It's an interesting alternative. It does seem a little worrying in terms of performance implications (will it always copy the image data to GPU and read it back?) but I agree it should be still faster than canvas... except that maybe canvas + willReadFrequently would be still faster? |
I'm not sure if this is useful, but I've got a webgl function that turns an image into bytes with no distortions via alpha premultiply and color corrections. |
Have you managed to get this working using a |
Uh oh!
There was an error while loading. Please reload this page.
This is meant to address #3802, and supersedes #4748. More background info can be found on those two issues.
There is currently no high-performance code path for getting
ImageData
from aMediaStreamTrack
or anHTMLVideoElement
. The current idiom for doing so involves drawing to anHTMLCanvasElement
(orOffscreenCanvas
where available) and creating a newImageData
each time. This is particularly low-performance on lower spec devices.For the purposes of this proposal, high-performance means:
I have a repo with performance results testing the various methods of getting
ImageData
for webcam frames in the latest Chromium and Firefox, using a Raspberry Pi Model 3B+ as a low-spec, easily reproducible test bed. The low memory (1 GB) is of particular concern for this kind of test, and low disk performance means swapping at that kind of rate will lock up the device. Firefox fails all tests as it immediately runs the system out of memory when creatingImageData
for 1080p video, presumably because its garbage collector is overwhelmed. This isn't entirely surprising, as at 30 FPS that's 240 MB/sec of garbage being generated. This is why a memory-efficient way of generatingImageData
for webcam frames is crucial.As opposed to #3802 and #4748, this proposal seeks to remove the need to use a canvas as an intermediary for getting
ImageData
, which provides a performance boost, and in providing a new API it can have changes which would otherwise be breaking forCanvasRenderingContext2D.getImageData
.The main usefulness in this proposed change is the ability to steal the memory from an existing
ArrayBuffer
when gettingImageData
from anImageBitmap
. Since anArrayBuffer
can be detached/neutered, this allows a clean way to reuse memory without dangling references.Here's the proposed change in Web IDL. No strong opinion on any naming. The (perhaps poorly named)
neuter
option would callImageBitmap.close
before resolving theImageBitmap.getImageData
promise, with the idea being to avoid triggering GC as much as possible. Actual testing shows mixed (and possibly negligible) results, so it may be dropped as an option.Benefits of this proposed change:
ArrayBuffer
allows developers to know the memory footprint of their code rather than leaving it to the fluctuations of GC, which as seen by the Firefox test results, may be inadequate.ImageBitmap
is[Transferable]
, it can be efficiently offloaded to a web worker where theImageData
can be extracted and processing done off the main thread. Firefox currently has no way to perform this extraction ofImageData
on a web worker due to not supporting2d
as a context forOffscreenCanvas
.ImageBitmap
for webcam data or video, such ascreateImageBitmap(videoElement)
andImageCapture.grabFrame
.Open questions:
ArrayBuffer
? I believe as long as the originalArrayBuffer
is detached before promise is returned, then it should be no different than transferring anArrayBuffer
currently.ImageBitmap.getImageData
is in-progress and anImageBitmap.close
or transfer occurs? I would assume both issues also exist with usingcreateImageBitmap(otherImageBitmap)
, although I don't see anything mentioned in the spec for concurrency issues here.The repo I linked earlier in this post contains patch implementations for both Chromium and Firefox. They're incomplete and were done just for testing the 'go right' path: they don't have error handling, and they're not pretty (apologies to the devs for bastardizing their code). The Chromium patch shows 2-3x the performance of the current best method, achieving 30 FPS at 720p@30 and over 20 FPS at 1080p@30. In the latter case the bottleneck is elsewhere in the code. For Firefox there can be no performance comparison since it can't complete the testing without the patch to begin with.
Overall this seems like a low-effort to implement, with a large positive impact.
The text was updated successfully, but these errors were encountered: