AvaloniaUI/Avalonia · error · ArgumentOutOfRangeException

bufferSize

Error message

bufferSize

What it means

CopyPixelsCore requires the destination buffer to be at least stride * sourceRect.Height bytes. The check is performed in 64-bit to avoid an integer overflow that would otherwise let an oversized blit run past the buffer end. A smaller buffer produces a buffer overrun, hence the guard.

Source

Thrown at src/Avalonia.Base/Media/Imaging/Bitmap.cs:241

        /// <remarks>
        /// <paramref name="sourceRowBytes"/> is signed and may be negative: a negative value means the
        /// source rows are laid out bottom-up, with <paramref name="sourceAddress"/> pointing at the
        /// first (top) row. The destination <paramref name="stride"/> must be positive and at least the
        /// tightly-packed row size. The caller is responsible for validating <paramref name="sourceRect"/>
        /// against the source bounds (e.g. via <see cref="ValidateSourceRect"/>).
        /// </remarks>
        internal static unsafe void CopyPixelsCore(PixelRect sourceRect, IntPtr sourceAddress, int sourceRowBytes,
            PixelFormat sourceFormat, IntPtr buffer, int bufferSize, int stride)
        {
            int minStride = checked(((sourceRect.Width * sourceFormat.BitsPerPixel) + 7) / 8);
            if (stride < minStride)
                throw new ArgumentOutOfRangeException(nameof(stride));

            // 64-bit to avoid overflowing the guard for very large strides/heights, which would
            // otherwise let an oversized contiguous blit/loop run past the buffers.
            var minBufferSize = (long)stride * sourceRect.Height;
            if (minBufferSize > bufferSize)
                throw new ArgumentOutOfRangeException(nameof(bufferSize));

            var offsetX = checked(((sourceRect.X * sourceFormat.BitsPerPixel) + 7) / 8);

            // Fast-path: when the source and destination layouts are identical, tightly-packed and
            // forward (no row padding, no X offset, positive stride), the whole region is contiguous in
            // both buffers and can be copied with a single blit. This is meaningfully faster than the
            // per-row loop (up to ~5x for small images, ~30% for large ones). Requiring stride == minStride
            // also guarantees we don't read past the source's last row.
            if (offsetX == 0 && sourceRowBytes == stride && stride == minStride)
            {
                Unsafe.CopyBlock(buffer.ToPointer(),
                    (sourceAddress + sourceRowBytes * sourceRect.Y).ToPointer(), (uint)minBufferSize);
                return;
            }

            for (var y = 0; y < sourceRect.Height; y++)
            {
                var srcAddress = sourceAddress + sourceRowBytes * (sourceRect.Y + y) + offsetX;

View on GitHub (pinned to 11c5427268)

Solutions

  1. Size the buffer from the exact parameters you will pass: int bufferSize = checked((int)((long)stride * sourceRect.Height));
  2. When pooling, round the rented size up to stride*Height and slice to that length.
  3. Recompute bufferSize whenever stride or sourceRect changes.
  4. Use checked arithmetic to surface overflow as an exception rather than a too-small buffer.

Example fix

// before
byte[] buf = new byte[stride * (int)bmp.PixelSize.Height]; // wrong if sourceRect is taller
bmp.CopyPixels(rect, buf, buf.Length, stride);

// after
long minBufferSize = (long)stride * rect.Height;
byte[] buf = new byte[checked((int)minBufferSize)];
bmp.CopyPixels(rect, buf, buf.Length, stride);
Defensive patterns

Strategy: validation

Validate before calling

long minBufferSize = (long)stride * sourceRect.Height;
int bufferSize = checked((int)minBufferSize);
byte[] buffer = new byte[bufferSize];
bmp.CopyPixels(sourceRect, buffer, bufferSize, stride);

Prevention

When it happens

Trigger: Allocating bufferSize from width*height without accounting for stride padding; passing a buffer sized for a different (smaller) sourceRect; using stride*Height with the wrong height; reusing a buffer pool entry sized for a previous, smaller image.

Common situations: Pooling image buffers without rounding up to the largest expected stride*Height; switching destination to a padded stride without enlarging the buffer; copying a tall crop into a buffer sized for the original (shorter) image.

Related errors


AI-assisted analysis of AvaloniaUI/Avalonia@11c5427268 (2026-08-13). Data as JSON: /api/errors/a57cb4c0b7e877f4. Report an issue: GitHub.