Search code examples
c#imagetransparencygifimagesharp

Saving a transparent image to Gif, using ImageSharp, makes it opaque (white background)


enter image description here

I am using ImageSharp to save a half transparent image into GIF, but gif is saved with white background, no transparency.

var gifEnc = new SixLabors.ImageSharp.Formats.Gif.GifEncoder();
edited.Save(cache, gifEnc);

Gif Output, white background, instead of transparent:

enter image description here


Solution

  • Encouraged by your confirmation in the comments now I'm adding a non-ImageSharp solution. Instead, it uses my GIF Encoder from this NuGet package.

    1. Obtain an IReadableBitmapData instance from your image

    Assuming you work with ImageSharp images:

    using SixLabors.ImageSharp;
    using KGySoft.Drawing.Imaging;
    
    // [...]
    
    // Loading your image as an ImageSharp image with a specific pixel format.
    // If you have a non-generic Image instance use the CloneAs<Bgra32>() method.
    using var img = Image.Load<Bgra32>(fileName);
    
    // Copying the raw pixel data into an array
    // Now it's Bgra32[] but you can use a byte array, too
    var asArray = new Bgra32[img.Width * img.Height]; // * 4 if byte[]
    img.CopyPixelDataTo(asArray);
    
    // Obtaining a bitmap data from the array
    using IReadableBitmapData bitmapData = BitmapDataFactory.CreateBitmapData(
        asArray, new System.Drawing.Size(img.Width, img.Height),
        stride: img.Width * 4,
        pixelFormat: KnownPixelFormat.Format32bppArgb);
    
    // TODO: work with bitmapData
    

    If performance is crucial you can try to spare copying pixels and just access the image buffer directly by DangerousTryGetSinglePixelMemory if possible. You have a better chance if you load the image with new Configuration { PreferContiguousImageBuffers = true }.

    // If it returns true no copying is necessary
    if (img.DangerousTryGetSinglePixelMemory(out Memory<Bgra32> memory))
    {
        // please note that the memory gets unpinned after leaving the scope
        // so bitmapData must be used in the same scope to prevent corruption
        using MemoryHandle handle = memory.Pin();
        using IReadableBitmapData bitmapData = BitmapDataFactory.CreateBitmapData(
            (IntPtr)handle.Pointer, new System.Drawing.Size(img.Width, img.Height),
                stride: img.Width * 4,
                pixelFormat: KnownPixelFormat.Format32bppArgb);
    
        // TODO: work with bitmapData that uses the same memory as the image
    }
    

    Alternatively, if you are not tied to ImageSharp you can use one of the specialized packages of the directly supported frameworks. Then obtaining a bitmap data is as simple as follows:

    using IReadableBitmapData bitmapData = myBitmap.GetReadableBitmapData();
    // TODO: work with bitmapData
    

    2. Encode GIF

    Once you have the bitmap data, this is really simple. Ideally, the solution is a single line:

    // using KGySoft.Imaging.GifEncoder with default configuration:
    GifEncoder.EncodeImage(bitmapData, myStream);
    

    For the 32bpp pixel format selected above the EncodeImage method will use the Wu quantizer with alpha threshold = 128. This must work as requested but if you want to adjust transparency you can pass a non-null quantizer to the method. And, if your sources are high color images you also may want to specify the ditherer parameter to preserve the details better. See more details about quantizing and dithering here.