Xalien
Xalien

Reputation: 55

Convert raw bytes to a gif file image in c#

I have raw byte array data, and would like to convert it to a .gif image on c#. I have attempted at it is a way which is shown down below. I would like to ask how to do this in c#.

what I tried: constructing MemoryStream from byte[] and using Image.FromStream: it doesn't work since what I have is only raw byte array, the pixels for the image to show, and what the constructor which takes as an argument the byte array wants is also the metadata of the image.

public static Image ConvertByteArraytoBitmap(byte[] bytes)
        {
            using (MemoryStream ms = new MemoryStream(bytes))
                return Image.FromStream(ms);
        }

edit: for John, a raw byte array (this image i smaller, and the byte array is only 100 bytes):

 woDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoDCgMKAwoA=

Upvotes: 2

Views: 1223

Answers (1)

Nyerguds
Nyerguds

Reputation: 5629

The way to manipulate 8-bit images in .Net is not very straightforward; you need to make a new Bitmap using the (width, height, pixelformat) constructor, then use LockBits to open its backing byte array, and copy your image data into it using Marshal.Copy.

Note that the length in bytes of one line in this backing data array, called the "stride", is always rounded up to a multiple of four bytes, so you have to copy your data line by line, and skip to the next line position using the stride, not just the (image width * bits per pixel) value.

Though, on the subject of the image width... your bytes array is only a part of the image. If it is just the pixel data, you are missing all your usual image header information, such as the image dimensions, and, assuming the pixel format is 8-bit indexed (as gif is), the colour palette. This data isn't optional; without it, there is no way to reconstruct an image.

If the image is grayscale, and each byte simply represents a brightness, you can generate the palette easily with a simple for loop, though:

Color[] palette = new Color[256];
for (Int32 i = 0; i < palette.Length; i++)
    palette[i] = Color.FromArgb(i, i, i);

Once you (somehow) got that missing information, this is the way to make an image out of the bytes array:

/// <summary>
/// Creates a bitmap based on data, width, height, stride and pixel format.
/// </summary>
/// <param name="sourceData">Byte array of raw source data</param>
/// <param name="width">Width of the image</param>
/// <param name="height">Height of the image</param>
/// <param name="stride">Scanline length inside the data</param>
/// <param name="pixelFormat">Pixel format</param>
/// <param name="palette">Color palette</param>
/// <param name="defaultColor">Default color to fill in on the palette if the given colors don't fully fill it.</param>
/// <returns>The new image</returns>
public static Bitmap BuildImage(Byte[] sourceData, Int32 width, Int32 height, Int32 stride, PixelFormat pixelFormat, Color[] palette, Color? defaultColor)
{
    Bitmap newImage = new Bitmap(width, height, pixelFormat);
    BitmapData targetData = newImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, pixelFormat);
    //If the input stride is larger than the width, this calculates the actual amount of bytes to copy for each line.
    Int32 newDataWidth = ((Image.GetPixelFormatSize(pixelFormat) * width) + 7) / 8;
    // Compensate for possible negative stride on BMP format data.
    Boolean isFlipped = stride < 0;
    stride = Math.Abs(stride);
    // Cache these to avoid unnecessary getter calls.
    Int32 targetStride = targetData.Stride;
    Int64 scan0 = targetData.Scan0.ToInt64();
    for (Int32 y = 0; y < height; y++)
        Marshal.Copy(sourceData, y * stride, new IntPtr(scan0 + y * targetStride), newDataWidth);
    newImage.UnlockBits(targetData);
    // Fix negative stride on BMP format.
    if (isFlipped)
        newImage.RotateFlip(RotateFlipType.Rotate180FlipX);
    // For indexed images, set the palette.
    if ((pixelFormat & PixelFormat.Indexed) != 0 && palette != null)
    {
        ColorPalette pal = newImage.Palette;
        for (Int32 i = 0; i < pal.Entries.Length; i++)
        {
            if (i < palette.Length)
                pal.Entries[i] = palette[i];
            else if (defaultColor.HasValue)
                pal.Entries[i] = defaultColor.Value;
            else
                break;
        }
        newImage.Palette = pal;
    }
    return newImage;
}

For a compact bytes array of an 8-bit image, the width and stride should be identical. Without a palette or the dimensions, there's no way to do it, though.

For the record, your little repeating { 0xC2, 0x80 } byte array above, loaded as 10×20 grayscale image (it's 200 bytes, not 100 as you said), gives this result (zoomed to x20):

Byte array loaded as 8-bit grayscale image It's just 2 repeated bytes, on an even width, so all you get are vertical lines...

Upvotes: 1

Related Questions