Skip to content

Instantly share code, notes, and snippets.

@DrkWzrd
Created March 5, 2026 15:21
Show Gist options
  • Select an option

  • Save DrkWzrd/32049daaad944cd5dbcc6b14a2b9eedd to your computer and use it in GitHub Desktop.

Select an option

Save DrkWzrd/32049daaad944cd5dbcc6b14a2b9eedd to your computer and use it in GitHub Desktop.
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
#pragma warning disable CS0067
public sealed class ImageSharpBitmapSource : BitmapSource
{
private static readonly Type ByteType = typeof(byte);
[EditorBrowsable(EditorBrowsableState.Never)]
internal sealed class ImageSharpBitmapSourceTypeConverter : TypeConverter
{
private static readonly ConditionalWeakTable<Image, ImageSource> Cache = [];
private static readonly Type ImageType = typeof(Image);
private static readonly Type ImageSourceType = typeof(ImageSource);
[ModuleInitializer]
public static void LoadTypeConverter()
{
_ = TypeDescriptor.AddAttributes(ImageType, new TypeConverterAttribute(typeof(ImageSharpBitmapSourceTypeConverter)));
}
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
{
return sourceType.IsAssignableTo(ImageType);
}
public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType)
{
return destinationType?.IsAssignableTo(ImageSourceType) ?? false;
}
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
return value is Image image ? (object)Cache.GetValue(image, img => new ImageSharpBitmapSource(img)) : throw new InvalidCastException();
}
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
{
return value is Image image && destinationType.IsAssignableTo(ImageSourceType)
? (object)Cache.GetValue(image, img => new ImageSharpBitmapSource(img))
: throw new InvalidCastException();
}
}
/// <summary>
/// The number of pixels (Width * Height) above which CopyPixels will use
/// a parallel loop.
///
/// 512 * 512 = 262144.
/// maxValue for disable.
/// </summary>
private const long PARALLEL_COPY_THRESHOLD = 262144;
private readonly bool _isClone;
public Image<Bgra32> Source { get; }
public override bool IsDownloading => false;
public override PixelFormat Format => PixelFormats.Bgra32;
public override int PixelHeight => Source.Height;
public override int PixelWidth => Source.Width;
public override double DpiX => Source.Metadata.HorizontalDpi ?? 96;
public override double DpiY => Source.Metadata.VerticalDpi ?? 96;
public override BitmapPalette? Palette => null;
public override event EventHandler<ExceptionEventArgs>? DecodeFailed;
public override event EventHandler<DownloadProgressEventArgs>? DownloadProgress;
public override event EventHandler<ExceptionEventArgs>? DownloadFailed;
public override event EventHandler? DownloadCompleted;
public ImageSharpBitmapSource(Image image)
: this(image is Image<Bgra32> bgra ? bgra : image.CloneAs<Bgra32>(), image is not Image<Bgra32>) { }
private ImageSharpBitmapSource(Image<Bgra32> image, bool disposeAfter)
{
Source = image;
_isClone = disposeAfter;
}
//WPF doesn't dispose IDisposable BitmapSource,
//and we can not do it ourselves because is generated behind,
//we have to trust in the GC, if we cloned the image.
~ImageSharpBitmapSource()
{
if (_isClone)
Source.Dispose();
}
protected override Freezable CreateInstanceCore()
{
return new ImageSharpBitmapSource(Source.Clone(), true);
}
public override void CopyPixels(Int32Rect sourceRect, Array pixels, int stride, int offset)
{
if (Source is null)
return;
if (pixels.Length <= 0)
throw new ArgumentException("The array must have at least one element", nameof(pixels));
var value = pixels.GetValue(0) ?? throw new InvalidOperationException("Array contains null values");
var valueType = value.GetType();
if (valueType != ByteType)
throw new ArgumentException("The content of the array must be bytes", nameof(pixels));
var elementSize = Marshal.SizeOf(valueType);
int bufferSize = Buffer.ByteLength(pixels);
if (sourceRect.IsEmpty)
sourceRect = new Int32Rect(0, 0, PixelWidth, PixelHeight);
int offsetInBytes = checked(offset * elementSize);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(offsetInBytes, bufferSize, nameof(stride));
int actualBufferSize = bufferSize - offsetInBytes;
int bytesPerPixel = Format.GetBytesPerPixel();
long totalPixels = (long)sourceRect.Width * sourceRect.Height;
if (totalPixels < PARALLEL_COPY_THRESHOLD)
{
Source.ProcessPixelRows(accessor =>
{
Span<byte> rawSpan = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetArrayDataReference(pixels), bufferSize);
Span<byte> actualBuffer = rawSpan.Slice(offsetInBytes, actualBufferSize);
for (int y = 0; y < sourceRect.Height; y++)
{
Span<byte> bufferRowSection = actualBuffer.Slice((y * stride) + (sourceRect.X * bytesPerPixel), sourceRect.Width * bytesPerPixel);
Span<Bgra32> sourceRowMemory = accessor.GetRowSpan(y + sourceRect.Y);
Span<Bgra32> sourceRowSectionSpan = sourceRowMemory.Slice(sourceRect.X, sourceRect.Width);
Span<byte> rawSourceRowSectionSpan = MemoryMarshal.AsBytes(sourceRowSectionSpan);
rawSourceRowSectionSpan.CopyTo(bufferRowSection);
}
});
}
else
{
_ = Parallel.For(0, sourceRect.Height, y =>
{
Span<byte> rawSpan = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetArrayDataReference(pixels), bufferSize);
Span<byte> actualBuffer = rawSpan.Slice(offsetInBytes, actualBufferSize);
Span<byte> bufferRowSection = actualBuffer.Slice((y * stride) + (sourceRect.X * bytesPerPixel), sourceRect.Width * bytesPerPixel);
Memory<Bgra32> sourceRowMemory = Source.DangerousGetPixelRowMemory(y + sourceRect.Y);
Span<Bgra32> sourceRowSectionSpan = sourceRowMemory.Span.Slice(sourceRect.X, sourceRect.Width);
Span<byte> rawSourceRowSectionSpan = MemoryMarshal.AsBytes(sourceRowSectionSpan);
rawSourceRowSectionSpan.CopyTo(bufferRowSection);
});
}
}
public override void CopyPixels(Int32Rect sourceRect, nint buffer, int bufferSize, int stride)
{
if (Source is null)
return;
if (sourceRect.IsEmpty)
sourceRect = new Int32Rect(0, 0, PixelWidth, PixelHeight);
ArgumentOutOfRangeException.ThrowIfNegative(sourceRect.X);
ArgumentOutOfRangeException.ThrowIfNegative(sourceRect.Y);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(sourceRect.Width);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(sourceRect.Height);
ArgumentOutOfRangeException.ThrowIfGreaterThan(sourceRect.X + sourceRect.Width, PixelWidth);
ArgumentOutOfRangeException.ThrowIfGreaterThan(sourceRect.Y + sourceRect.Height, PixelHeight);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(buffer);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(stride);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(bufferSize);
int bytesPerPixel = Format.GetBytesPerPixel();
uint minStride = (uint)(sourceRect.Width * bytesPerPixel);
ArgumentOutOfRangeException.ThrowIfLessThan((uint)stride, minStride, nameof(stride));
uint requiredSize = (uint)((sourceRect.Height - 1) * stride) + minStride;
ArgumentOutOfRangeException.ThrowIfLessThan((uint)bufferSize, requiredSize, nameof(bufferSize));
long totalPixels = (long)sourceRect.Width * sourceRect.Height;
nint offset = buffer + (sourceRect.X * bytesPerPixel);
int rowByteLength = sourceRect.Width * bytesPerPixel;
if (totalPixels < PARALLEL_COPY_THRESHOLD)
{
Source.ProcessPixelRows(accessor =>
{
for (int y = 0; y < sourceRect.Height; y++)
{
nint pDestRow = offset + (y * stride);
unsafe
{
Span<byte> bufferRowSection = new(pDestRow.ToPointer(), rowByteLength);
Span<Bgra32> sourceRowMemory = accessor.GetRowSpan(y + sourceRect.Y);
Span<Bgra32> sourceRowSectionSpan = sourceRowMemory.Slice(sourceRect.X, sourceRect.Width);
Span<byte> rawSourceRowSectionSpan = MemoryMarshal.AsBytes(sourceRowSectionSpan);
rawSourceRowSectionSpan.CopyTo(bufferRowSection);
}
}
});
}
else
{
_ = Parallel.For(0, sourceRect.Height, y =>
{
nint pDestRow = offset + (y * stride);
unsafe
{
Span<byte> bufferRowSection = new(pDestRow.ToPointer(), rowByteLength);
Memory<Bgra32> sourceRowMemory = Source.DangerousGetPixelRowMemory(y + sourceRect.Y);
Span<Bgra32> sourceRowSectionSpan = sourceRowMemory.Span.Slice(sourceRect.X, sourceRect.Width);
Span<byte> rawSourceRowSectionSpan = MemoryMarshal.AsBytes(sourceRowSectionSpan);
rawSourceRowSectionSpan.CopyTo(bufferRowSection);
}
});
}
}
}
#pragma warning restore CS0067
public static class ImageSharpMetadataExtensions
{
extension(ImageMetadata metadata)
{
public double? HorizontalDpi => metadata.ResolutionUnits switch
{
PixelResolutionUnit.PixelsPerInch => metadata.HorizontalResolution,
PixelResolutionUnit.PixelsPerCentimeter => metadata.HorizontalResolution * 2.54,
PixelResolutionUnit.PixelsPerMeter => metadata.HorizontalResolution * 0.0254,
_ => null
};
public double? VerticalDpi => metadata.ResolutionUnits switch
{
PixelResolutionUnit.PixelsPerInch => metadata.VerticalResolution,
PixelResolutionUnit.PixelsPerCentimeter => metadata.VerticalResolution * 2.54,
PixelResolutionUnit.PixelsPerMeter => metadata.VerticalResolution * 0.0254,
_ => null
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment