Generating Dynamic Images in C# with SkiaSharp

February 2026

TL;DR: System.Drawing.Common is essentially dead for cross-platform .NET. If you need to generate images, resize photos, or add watermarks on a Linux Docker container, use SkiaSharp. Remember to wrap your bitmaps and canvases in using blocks to prevent catastrophic memory leaks.

For years, C# developers relied on System.Drawing to manipulate images. However, Microsoft recently restricted System.Drawing.Common to Windows-only due to underlying GDI+ dependencies.

If you want to generate images on a Linux server or inside a Docker container in .NET 9, you need a modern, cross-platform graphics library. Enter SkiaSharp.

What is SkiaSharp?

SkiaSharp is a cross-platform 2D graphics API for .NET based on Google's Skia Graphics Engine (the same engine that powers Google Chrome and Android). It is ridiculously fast and works flawlessly on Linux, macOS, iOS, Android, and Windows.

Example: Generating a Dynamic Watermarked Image

Here is a production-ready example of how to load an image, draw text over it, and save it back out.

✅ The Good Way (Memory Safe)

using SkiaSharp;
using System.IO;

public class ImageService
{
    public void AddWatermark(string inputPath, string outputPath, string watermarkText)
    {
        // 1. ALWAYS use 'using' blocks for SkiaSharp objects!
        // They wrap native C++ memory that the GC struggles to clean up automatically.
        using var inputStream = File.OpenRead(inputPath);
        using var originalBitmap = SKBitmap.Decode(inputStream);
        
        // 2. Create the drawing surface
        using var surface = SKSurface.Create(new SKImageInfo(originalBitmap.Width, originalBitmap.Height));
        using var canvas = surface.Canvas;
        
        // 3. Draw the original image onto the canvas
        canvas.DrawBitmap(originalBitmap, 0, 0);
        
        // 4. Configure the font/paint for the watermark
        using var paint = new SKPaint
        {
            Color = SKColors.White.WithAlpha(128), // 50% transparency
            IsAntialias = true,
            TextSize = 48,
            Typeface = SKTypeface.FromFamilyName("Arial", SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright)
        };
        
        // 5. Draw the text in the bottom right corner
        canvas.DrawText(watermarkText, originalBitmap.Width - 300, originalBitmap.Height - 50, paint);
        
        // 6. Save the final image
        using var image = surface.Snapshot();
        using var data = image.Encode(SKEncodedImageFormat.Jpeg, 90); // 90% Quality
        using var outputStream = File.OpenWrite(outputPath);
        data.SaveTo(outputStream);
    }
}

Critical SkiaSharp Rules

  1. The using Keyword is Mandatory: SKBitmap, SKSurface, SKCanvas, and SKPaint all implement IDisposable. If you don't dispose of them, your server will run out of RAM in minutes under heavy load.
  2. Quality vs Size: When encoding JPEGs, setting the quality to 100 exponentially increases file size for virtually no visible benefit. A quality setting between 80 and 90 is the sweet spot for web delivery.

Summary

SkiaSharp is the undisputed king of cross-platform 2D graphics in modern .NET. It's powerful enough to build entire rendering engines and simple enough to add a quick watermark to an uploaded avatar.