Firstly and most importantly: SkiaSharp does not support 24BPP RGB. You need to convert your 24bpp image to 32bpp format first, and that will almost inevitably require allocation and copying.
If you are resigned to allocation and copying anyway, things become a little simpler. You should construct an SkBitmap instance, because those can be writeable whereas SkImage is not. You can then trivially call SkImage.FromBitmap to create your image.
As copying or converting rows and pixels isn't what you asked about, and as it doesn't require understanding the not entirely well documented memory management of the two libraries, I won't talk about it further here.
If on the other hand you're prepared to work with eg. Image<Rgba32>, you can wrap the buffer underlying an Image<T> with an SkImage without any copying. The stuff you want is probably documented here: https://docs.sixlabors.com/articles/imagesharp/memorymanagement.html
To highlight the important bits of that doc:
You need to ensure that the image is loaded into contiguous memory, which imagesharp will not always do for you by default.
Configuration customConfig = Configuration.Default.Clone();
customConfig.PreferContiguousImageBuffers = true;
using (Image<Rgba32> image = new(customConfig, 640, 480))
{
// ...
}
You can only get a reference to the entire underlying buffer (which is what you need) if that buffer is continuous, so don't skip that step.
// this can fail, check the return value
// `image` must remain valid for the lifetime of this Memory<T>
image.DangerousTryGetSinglePixelMemory(out Memory<Rgba32> memory)
// this memory handle must remain valid for the lifetime of the
// SkImage that will be wrapping your memory.
var pin = memory.Pin();
SkImage skImage;
// you should probably catch exceptions and unpin the memory
unsafe
{
void* rawPtr = pin.Pointer;
var ptr = new IntPtr(rawPtr);
var info = new SkImageInfo(image.Width, image.Height, SkColorType.Rgba8888);
// imagesharp makes it frustratingly difficult to access stride
// information, even though it must know it internally, but
// for 32bpp formats the stride is generally the same as the
// row length in bytes.
var stride = image.Width * 4;
var pixmap = new SKPixmap(info, ptr, stride);
// when this image is disposed, it will automatically unpin
// the underlying memory
skImage = SKImage.FromPixels(
pixmap,
(ptr, ctx) => pin.Dispose());
}
There are multiple ways of doing this stuff (eg. via SkiBitmap.SetPixels or SkBitmap.InstallPixels, or using SkImage.FromPixels but without a cleanup delegate, perhaps because pin is a using variable, etc etc), but this is one I use (albeit not with ImageSharp).