Docs needed: async RequestReadPixels API
The new async rescale-and-read-pixels API was added in mono/SkiaSharp#4545 (managed) + mono/skia#295 (native). Inline XML doc comments were intentionally not added to the source (the build ignores them), so all documentation needs to live here as mdoc XML.
This issue lists every new public API and gives writers ready-to-use content (summaries, params, returns, remarks).
Shared concepts (reuse across the members' <remarks>)
- What it does. Asynchronously reads a (optionally rescaled) rectangle of pixels back from an
SKImage or SKSurface. This is the primary way to get pixels off a GPU surface — notably for GPU backends where synchronous ReadPixels is unavailable.
- Synchronous vs asynchronous. On a raster (CPU) image/surface the callback is invoked synchronously, before the method returns. On a GPU (Ganesh) image/surface the read is deferred: the callback only fires after the work is submitted (
GRContext.Submit) and GRContext.CheckAsyncWorkCompletion is pumped.
- Callback lifetime. The
SKImageReadPixelsResult handed to the callback — and the pixel memory it points at — are valid only for the duration of the callback. The result is disposed automatically when the callback returns; every member then throws ObjectDisposedException. To keep the pixels, copy them out with ToImage, ToBitmap, ToArray, or CopyPlaneTo (do not stash the ReadOnlySpan<byte> from GetPlaneData).
- Failure. On failure (for example a
srcRect not contained by the source, or an unsupported configuration) the callback is invoked with a null result.
- No state argument. There is no
context/state parameter — capture any state you need in the callback closure.
- Rescaling. The source
srcRect is rescaled to the dimensions of info and converted to its color type/alpha/color space. rescaleGamma/rescaleMode control the rescale and are no-ops when srcRect is the same size as info (no scaling occurs).
- Planes. A standard (interleaved RGBA) read has
PlaneCount == 1. The multi-plane accessors are forward-looking for future YUV reads (which would report 3 or 4 planes).
enum SKImageRescaleGamma
Controls the gamma space in which rescaling happens (only relevant when actually scaling).
Src — rescale in the source's stored (usually non-linear / sRGB) gamma; no gamma conversion. Cheapest.
Linear — convert to linear light before rescaling, then convert back. More physically-correct downscaling (avoids dark halos) at the cost of two extra gamma conversions.
enum SKImageRescaleMode
The sampling technique used when rescaling (irrelevant at 1:1).
Nearest — nearest-neighbour; no filtering. Cheapest; at 1:1 an exact copy.
Linear — a single bilinear step.
RepeatedLinear — repeated bilinear halving passes; good quality for large downscales.
RepeatedCubic — repeated bicubic passes; highest quality, most expensive.
SKImage.RequestReadPixels (2 overloads) and SKSurface.RequestReadPixels (2 overloads)
public void RequestReadPixels (SKImageInfo info, SKRectI srcRect, Action<SKImageReadPixelsResult> callback);
public void RequestReadPixels (SKImageInfo info, SKRectI srcRect, SKImageRescaleGamma rescaleGamma, SKImageRescaleMode rescaleMode, Action<SKImageReadPixelsResult> callback);
- summary — "Asynchronously reads (and optionally rescales) a rectangle of pixels from this image/surface, delivering the result to
callback."
- param
info — "Describes the destination pixels: the dimensions the source is rescaled to, and the color type, alpha type and color space it is converted to."
- param
srcRect — "The sub-rectangle of the source to read. Reading fails (callback receives null) if it is not contained within the source bounds."
- param
rescaleGamma — "The gamma space to rescale in. See SKImageRescaleGamma." (4-arg overload only)
- param
rescaleMode — "The rescaling technique. See SKImageRescaleMode." (4-arg overload only)
- param
callback — "Receives the pixel data, or null on failure. Only valid for the duration of the call — see remarks. Throws ArgumentNullException if null."
- remarks — Pull in the Shared concepts above (sync-vs-async, callback lifetime, failure→null, no-state, rescaling). For the 3-arg overload, note the defaults:
SKImageRescaleGamma.Src + SKImageRescaleMode.Nearest (the least-transform choice; this matches what Skia itself uses when no rescale is required).
GRContext.CheckAsyncWorkCompletion
public void CheckAsyncWorkCompletion ();
- summary — "Checks whether outstanding asynchronous work on this context has completed and invokes any ready callbacks."
- remarks — "Used to drive deferred asynchronous reads (see
SKImage.RequestReadPixels / SKSurface.RequestReadPixels) to completion on GPU contexts. A Submit must also occur to guarantee the work is sent to the GPU; typically callers Submit then call this in a loop until the read's callback has fired. Has no effect on backends without outstanding async work."
SKImageReadPixelsResult (new sealed class, IDisposable)
Type summary — "The pixel data delivered to the callback of SKImage.RequestReadPixels / SKSurface.RequestReadPixels."
Type remarks — "A non-owning view that is valid only for the duration of the callback it is delivered to; it is disposed automatically when the callback returns, after which every member throws ObjectDisposedException. Use ToImage, ToBitmap, ToArray or CopyPlaneTo to obtain a copy that outlives the callback."
| Member |
Summary |
Notes for writers |
int PlaneCount { get; } |
The number of planes in the result. |
1 for a standard RGBA read. |
int GetPlaneRowBytes (int planeIndex) |
The stride (bytes per row) of the given plane. |
May exceed width×bytesPerPixel due to row padding. Throws ArgumentOutOfRangeException for an invalid index. |
ReadOnlySpan<byte> GetPlaneData (int planeIndex) |
A raw, zero-copy view over the plane's bytes. |
Valid only during the callback; may include row padding — use GetPlaneRowBytes as the stride. For a stable/packed copy use CopyPlaneTo/ToArray. Throws ArgumentOutOfRangeException for an invalid index. |
void CopyPlaneTo (int planeIndex, Span<byte> destination) |
Copies the plane into destination as tightly-packed pixels (row padding stripped). |
Throws ArgumentException if destination is smaller than the packed size; ArgumentOutOfRangeException for an invalid index. |
byte[] ToArray (int planeIndex = 0) |
Returns a new, tightly-packed copy of the plane that outlives the callback. |
|
SKImage ToImage () |
Materializes the result into a new SKImage that outlives the callback. |
Only valid for a single-plane (interleaved) result; throws InvalidOperationException otherwise. |
SKBitmap ToBitmap () |
Materializes the result into a new SKBitmap that outlives the callback. |
Same single-plane constraint as ToImage. |
void Dispose () |
Invalidates this view. |
Called automatically when the callback returns; safe to call again (idempotent). |
Suggested example (for the SKSurface.RequestReadPixels / SKImageReadPixelsResult page)
// GPU surface -> owned SKImage on the CPU
SKImage snapshot = null;
surface.RequestReadPixels(info, new SKRectI(0, 0, info.Width, info.Height), result =>
{
if (result != null)
snapshot = result.ToImage(); // owned copy that survives past the callback
});
// Drive the deferred GPU read to completion.
grContext.Submit(synchronous: true);
grContext.CheckAsyncWorkCompletion();
// `snapshot` is now populated (on a raster surface the callback would already have run synchronously).
Ping me (or see PR #4545) if any signature or behavior needs clarifying.
The Graphite backend exposes the same async read-pixels API on its GPU context, reusing the neutral SKImageReadPixelsResult result type and the SKImageRescaleGamma / SKImageRescaleMode enums documented above (there are no SKGraphite*-prefixed result/enum types — the earlier SKGraphiteAsyncReadResult / SKGraphiteRescaleGamma / SKGraphiteRescaleMode were removed in favour of the neutral ones). Writers should document these members consistently with the SKImage / SKSurface versions and cross-link them.
Why it matters for Graphite specifically: Graphite-backed surfaces do not support the synchronous SKSurface.ReadPixels in shipping builds (Skia gates it on GPU_TEST_UTILS), so this async path is the only way to read pixels back from a Graphite surface.
SKGraphiteContext.RequestReadPixels (2 overloads)
public void RequestReadPixels (SKSurface surface, SKImageInfo dstInfo, SKRectI srcRect, Action<SKImageReadPixelsResult> callback);
public void RequestReadPixels (SKSurface surface, SKImageInfo dstInfo, SKRectI srcRect, SKImageRescaleGamma rescaleGamma, SKImageRescaleMode rescaleMode, Action<SKImageReadPixelsResult> callback);
- summary — "Asynchronously reads (and optionally rescales) a rectangle of pixels back from a Graphite-backed
surface, delivering the result to callback."
- param
surface — "The Graphite-backed surface to read from. Throws ArgumentNullException if null."
- param
dstInfo — "Describes the destination pixels: the dimensions the source is rescaled to, and the color type, alpha type and color space it is converted to."
- param
srcRect — "The sub-rectangle of the surface to read. Reading fails (callback receives null) if it is not contained within the surface bounds."
- param
rescaleGamma — "The gamma space to rescale in. See SKImageRescaleGamma." (5-arg overload only)
- param
rescaleMode — "The rescaling technique. See SKImageRescaleMode." (5-arg overload only)
- param
callback — "Receives the pixel data, or null on failure. Only valid for the duration of the call — see the shared remarks above. Throws ArgumentNullException if null."
- remarks — Reuse the Shared concepts (callback lifetime, failure→null, no-state, rescaling). The read is always deferred: drive it to completion with
SKGraphiteContext.Submit (with Sync = true) followed by pumping SKGraphiteContext.CheckAsyncWorkCompletion until the callback has fired. For the 4-arg overload note the defaults: SKImageRescaleGamma.Src + SKImageRescaleMode.Nearest (matching SKImage/SKSurface).
SKGraphiteContext.CheckAsyncWorkCompletion
public void CheckAsyncWorkCompletion ();
- summary — "Checks whether outstanding asynchronous work on this Graphite context has completed and invokes any ready callbacks."
- remarks — "The Graphite analogue of
GRContext.CheckAsyncWorkCompletion. Used to drive deferred SKGraphiteContext.RequestReadPixels reads to completion; a Submit must also occur to guarantee the work is sent to the GPU. Typically callers Submit then call this in a loop until the read's callback has fired."
Suggested example (Graphite readback)
// Graphite GPU surface -> owned SKImage on the CPU (sync ReadPixels is unavailable on Graphite).
SKImage snapshot = null;
graphiteContext.RequestReadPixels(surface, info, new SKRectI(0, 0, info.Width, info.Height), result =>
{
if (result != null)
snapshot = result.ToImage(); // owned copy that survives past the callback
});
// Drive the deferred GPU read to completion.
graphiteContext.Submit(new SKGraphiteSubmitInfo { Sync = true });
while (snapshot is null)
graphiteContext.CheckAsyncWorkCompletion();
// `snapshot` is now populated.
Docs needed: async
RequestReadPixelsAPIThe new async rescale-and-read-pixels API was added in mono/SkiaSharp#4545 (managed) + mono/skia#295 (native). Inline XML doc comments were intentionally not added to the source (the build ignores them), so all documentation needs to live here as mdoc XML.
This issue lists every new public API and gives writers ready-to-use content (summaries, params, returns, remarks).
Shared concepts (reuse across the members'
<remarks>)SKImageorSKSurface. This is the primary way to get pixels off a GPU surface — notably for GPU backends where synchronousReadPixelsis unavailable.GRContext.Submit) andGRContext.CheckAsyncWorkCompletionis pumped.SKImageReadPixelsResulthanded to the callback — and the pixel memory it points at — are valid only for the duration of the callback. The result is disposed automatically when the callback returns; every member then throwsObjectDisposedException. To keep the pixels, copy them out withToImage,ToBitmap,ToArray, orCopyPlaneTo(do not stash theReadOnlySpan<byte>fromGetPlaneData).srcRectnot contained by the source, or an unsupported configuration) the callback is invoked with anullresult.context/state parameter — capture any state you need in the callback closure.srcRectis rescaled to the dimensions ofinfoand converted to its color type/alpha/color space.rescaleGamma/rescaleModecontrol the rescale and are no-ops whensrcRectis the same size asinfo(no scaling occurs).PlaneCount == 1. The multi-plane accessors are forward-looking for future YUV reads (which would report 3 or 4 planes).enum SKImageRescaleGammaControls the gamma space in which rescaling happens (only relevant when actually scaling).
Src— rescale in the source's stored (usually non-linear / sRGB) gamma; no gamma conversion. Cheapest.Linear— convert to linear light before rescaling, then convert back. More physically-correct downscaling (avoids dark halos) at the cost of two extra gamma conversions.enum SKImageRescaleModeThe sampling technique used when rescaling (irrelevant at 1:1).
Nearest— nearest-neighbour; no filtering. Cheapest; at 1:1 an exact copy.Linear— a single bilinear step.RepeatedLinear— repeated bilinear halving passes; good quality for large downscales.RepeatedCubic— repeated bicubic passes; highest quality, most expensive.SKImage.RequestReadPixels(2 overloads) andSKSurface.RequestReadPixels(2 overloads)callback."info— "Describes the destination pixels: the dimensions the source is rescaled to, and the color type, alpha type and color space it is converted to."srcRect— "The sub-rectangle of the source to read. Reading fails (callback receivesnull) if it is not contained within the source bounds."rescaleGamma— "The gamma space to rescale in. SeeSKImageRescaleGamma." (4-arg overload only)rescaleMode— "The rescaling technique. SeeSKImageRescaleMode." (4-arg overload only)callback— "Receives the pixel data, ornullon failure. Only valid for the duration of the call — see remarks. ThrowsArgumentNullExceptionifnull."SKImageRescaleGamma.Src+SKImageRescaleMode.Nearest(the least-transform choice; this matches what Skia itself uses when no rescale is required).GRContext.CheckAsyncWorkCompletionSKImage.RequestReadPixels/SKSurface.RequestReadPixels) to completion on GPU contexts. ASubmitmust also occur to guarantee the work is sent to the GPU; typically callersSubmitthen call this in a loop until the read's callback has fired. Has no effect on backends without outstanding async work."SKImageReadPixelsResult(new sealed class,IDisposable)Type summary — "The pixel data delivered to the callback of
SKImage.RequestReadPixels/SKSurface.RequestReadPixels."Type remarks — "A non-owning view that is valid only for the duration of the callback it is delivered to; it is disposed automatically when the callback returns, after which every member throws
ObjectDisposedException. UseToImage,ToBitmap,ToArrayorCopyPlaneToto obtain a copy that outlives the callback."int PlaneCount { get; }1for a standard RGBA read.int GetPlaneRowBytes (int planeIndex)ArgumentOutOfRangeExceptionfor an invalid index.ReadOnlySpan<byte> GetPlaneData (int planeIndex)GetPlaneRowBytesas the stride. For a stable/packed copy useCopyPlaneTo/ToArray. ThrowsArgumentOutOfRangeExceptionfor an invalid index.void CopyPlaneTo (int planeIndex, Span<byte> destination)destinationas tightly-packed pixels (row padding stripped).ArgumentExceptionifdestinationis smaller than the packed size;ArgumentOutOfRangeExceptionfor an invalid index.byte[] ToArray (int planeIndex = 0)SKImage ToImage ()SKImagethat outlives the callback.InvalidOperationExceptionotherwise.SKBitmap ToBitmap ()SKBitmapthat outlives the callback.ToImage.void Dispose ()Suggested example (for the
SKSurface.RequestReadPixels/SKImageReadPixelsResultpage)Ping me (or see PR #4545) if any signature or behavior needs clarifying.
Graphite backend consumers (added in mono/SkiaSharp#3968 + mono/skia#236)
The Graphite backend exposes the same async read-pixels API on its GPU context, reusing the neutral
SKImageReadPixelsResultresult type and theSKImageRescaleGamma/SKImageRescaleModeenums documented above (there are noSKGraphite*-prefixed result/enum types — the earlierSKGraphiteAsyncReadResult/SKGraphiteRescaleGamma/SKGraphiteRescaleModewere removed in favour of the neutral ones). Writers should document these members consistently with theSKImage/SKSurfaceversions and cross-link them.SKGraphiteContext.RequestReadPixels(2 overloads)surface, delivering the result tocallback."surface— "The Graphite-backed surface to read from. ThrowsArgumentNullExceptionifnull."dstInfo— "Describes the destination pixels: the dimensions the source is rescaled to, and the color type, alpha type and color space it is converted to."srcRect— "The sub-rectangle of the surface to read. Reading fails (callback receivesnull) if it is not contained within the surface bounds."rescaleGamma— "The gamma space to rescale in. SeeSKImageRescaleGamma." (5-arg overload only)rescaleMode— "The rescaling technique. SeeSKImageRescaleMode." (5-arg overload only)callback— "Receives the pixel data, ornullon failure. Only valid for the duration of the call — see the shared remarks above. ThrowsArgumentNullExceptionifnull."SKGraphiteContext.Submit(withSync = true) followed by pumpingSKGraphiteContext.CheckAsyncWorkCompletionuntil the callback has fired. For the 4-arg overload note the defaults:SKImageRescaleGamma.Src+SKImageRescaleMode.Nearest(matchingSKImage/SKSurface).SKGraphiteContext.CheckAsyncWorkCompletionGRContext.CheckAsyncWorkCompletion. Used to drive deferredSKGraphiteContext.RequestReadPixelsreads to completion; aSubmitmust also occur to guarantee the work is sent to the GPU. Typically callersSubmitthen call this in a loop until the read's callback has fired."Suggested example (Graphite readback)