mono / SkiaSharp

SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library. It provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.
MIT License
4.17k stars 522 forks source link

[BUG] SKPixmap.GetPixelSpan() returns incorrectly sized ReadOnlySpan<byte> #2751

Open assumenothing opened 3 months ago

assumenothing commented 3 months ago

Description

When using SKPixmap to get the raw pixel data via GetPixelSpan(), the length that is used to create the span is incorrectly calculated when a pixmap is derived from ExtractSubset on another pixmap/bitmap (or manually created with a larger-than-normal row size).

Code

// A sample PNG image that is larger than the subset required below (25x25).
// This sample image is sized: 111x33
byte[] imageData = [
    0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D,
    0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x00, 0x00, 0x21,
    0x08, 0x02, 0x00, 0x00, 0x00, 0x58, 0xF5, 0x62, 0x2B, 0x00, 0x00, 0x00,
    0x03, 0x73, 0x42, 0x49, 0x54, 0x08, 0x08, 0x08, 0xDB, 0xE1, 0x4F, 0xE0,
    0x00, 0x00, 0x00, 0x7B, 0x49, 0x44, 0x41, 0x54, 0x68, 0x81, 0xED, 0xDA,
    0xB1, 0x0D, 0x02, 0x51, 0x0C, 0x05, 0xC1, 0x6F, 0x44, 0xFF, 0x2D, 0x9B,
    0x80, 0x13, 0x0D, 0xB0, 0xD2, 0x09, 0x34, 0x13, 0x39, 0xB4, 0x36, 0x7E,
    0xB3, 0xBB, 0x87, 0x8A, 0x9A, 0x95, 0xDD, 0x7D, 0x7E, 0xAE, 0x7B, 0x5F,
    0xF9, 0x75, 0x33, 0x73, 0xCE, 0x79, 0xDC, 0xFD, 0xC6, 0x5F, 0x51, 0xB3,
    0xA4, 0x66, 0x49, 0xCD, 0x92, 0x9A, 0x25, 0x35, 0x4B, 0x6A, 0x96, 0xD4,
    0x2C, 0xA9, 0x59, 0x52, 0xB3, 0xA4, 0x66, 0x49, 0xCD, 0x92, 0x9A, 0x25,
    0x35, 0x4B, 0x6A, 0x96, 0xD4, 0x2C, 0xA9, 0x59, 0x52, 0xB3, 0xA4, 0x66,
    0x49, 0xCD, 0x92, 0x9A, 0x25, 0x35, 0x4B, 0x6A, 0x96, 0xD4, 0x2C, 0xA9,
    0x59, 0x52, 0xB3, 0xA4, 0x66, 0x49, 0xCD, 0xD2, 0xB5, 0xEA, 0x7A, 0x8F,
    0x92, 0xF8, 0xD2, 0x58, 0xC7, 0x85, 0x5E, 0x3B, 0x1C, 0x0F, 0x3A, 0x22,
    0x47, 0x3D, 0x10, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE,
    0x42, 0x60, 0x82
];

using var bitmap = SKBitmap.Decode(imageData);

// Get a smaller portion of the bitmap (bottom-right corner--to exemplify the last row requiring less than the
// pixmap's full row bytes).
using var bitmapSubset = new SKBitmap();
if (!bitmap.ExtractSubset(bitmapSubset, SKRectI.Create(bitmap.Width - 25, bitmap.Height - 25, 25, 25)))
    throw new InvalidOperationException("Unable to create subset!");

// Get the subset bitmap's pixmap.
var pixmap = bitmapSubset.PeekPixels();
var pixmapInfo = pixmap.Info;
var pixmapBytes = pixmap.GetPixelSpan();

// Calculate the actual byte length of the pixmap data.
var pixmapActualByteLength = pixmapInfo.Height > 0
    ? ((pixmap.RowBytes * (pixmapInfo.Height - 1)) + pixmapInfo.RowBytes)
    : 0;

// Validate pixmap span's length.
if (pixmapBytes.Length < pixmapActualByteLength)
    throw new InvalidOperationException("Pixel data not fully specified!");

Expected Behavior

No exception should be thrown and SKPixmap.GetPixelSpan() should return a span with a length of 11200 bytes (in this particular example).

The span's length should match the native Skia API: SkPixmap::computeByteSize() which in turn calls SkImageInfo::computeByteSize(pixmap->rowBytes()). Neither of these APIs appear to be available in the exposed/referenced C library. Though it is easy to calculate as seen in the example code above.

Actual Behavior

InvalidOperationException is thrown with the message: Pixel data not fully specified!

Version of SkiaSharp

Other (Please indicate in the description)

Last Known Good Version of SkiaSharp

Other (Please indicate in the description)

IDE / Editor

Visual Studio (Windows)

Platform / Operating System

Windows

Platform / Operating System Version

Tested on Windows 10.0.19045.0 x64, .NET 8.0.1 x64, SkiaSharp 2.88.7.

Devices

In theory, bug should occur on all devices.

Relevant Screenshots

No response

Relevant Log Output

No response

Code of Conduct