Search code examples
c#.netbitmapdotnetzipgrayscale

Write grayscale BMP to ZIP file from Memorystream


I'm trying to test whether writing individual images or a bundle zipped is quicker. My approach is to create a random byte array of values between 0 and 255 (8-bit image) and form a Bitmap from it, writing repeatedly using Bitmap.Save. In this way I can set the PixelFormat to Format8bppIndexed, which gives a grayscale image:

// Random number Generator
Random rnd = new Random();

// Create a single image
int Width = 640;
int Height = 512;
var b = new Bitmap(Width, Height, PixelFormat.Format8bppIndexed);
ColorPalette ncp = b.Palette;
for (int i = 0; i < 256; i++)
    ncp.Entries[i] = Color.FromArgb(255, i, i, i);
b.Palette = ncp;

var BoundsRect = new Rectangle(0, 0, Width, Height);
BitmapData bmpData = b.LockBits(BoundsRect,
                ImageLockMode.WriteOnly,
                b.PixelFormat);
IntPtr ptr = bmpData.Scan0;
int bytes = bmpData.Stride * b.Height;
var rgbValues = new byte[bytes];

// fill in rgbValues, e.g. with a for loop over an input array
rnd.NextBytes(rgbValues);

Marshal.Copy(rgbValues, 0, ptr, bytes);
b.UnlockBits(bmpData);

// copy image to a list of ~1000
List<Bitmap> bmps = new List<Bitmap>();
for (int i = 0; i < 500; i++)
    bmps.Add(new Bitmap(b));

// Write to individual files
DateTime t0=DateTime.Now;
for (int i=0;i<bmps.Count;i++)
    b.Save(@"C:\Temp\DiskTransferTest\IndividualImages\" + i.ToString() + ".bmp");
DateTime t1=DateTime.Now;
Console.WriteLine("Time to write individually: " + (t1-t0).ToString());

After that, I try and zip them all into a single ZIP file and save, using DotNetZip. This works, but I get a colour image rather than a greyscale one, so the filesizes are much larger.

// Create memorystreams from bitmap to pass to DotNetZip
List<MemoryStream> mss = new List<MemoryStream>();
for (int i = 0; i < bmps.Count; i++)
{
    mss.Add(new MemoryStream());
    bmps[i].Save(mss[i], ImageFormat.Bmp);
    mss[i].Seek(0, SeekOrigin.Begin);
}

// Compress and write
t0 = DateTime.Now;
using (ZipFile zipfile = new ZipFile())
{
    zipfile.CompressionLevel = 0;
    int i=0;
    foreach (MemoryStream ms in mss)
        {
         string pictureName = i.ToString() + ".bmp";
         zipfile.AddEntry(pictureName,ms);
         i++;
        }           
    zipfile.Save(@"C:\Temp\DiskTransferTest\zipped.zip");
}
t1 = DateTime.Now;
Console.WriteLine("Time to write compressed: " + (t1 - t0).ToString());

Any suggestions on how to write a greyscale to the zip via the MemoryStream?


Solution

  • The problem is that your new bitmaps aren't 8bpp bitmaps. Consider your code:

    // copy image to a list of ~1000
    List<Bitmap> bmps = new List<Bitmap>();
    for (int i = 0; i < 500; i++)
        bmps.Add(new Bitmap(b));
    
    // Write to individual files
    DateTime t0=DateTime.Now;
    for (int i=0;i<bmps.Count;i++)
        b.Save(@"C:\Temp\DiskTransferTest\IndividualImages\" + i.ToString() + ".bmp");
    

    The bitmap b is an 8bpp bitmap. You're writing it to file. But if you examine bmps[0] I think you'll find that the PixelFormat is 32bpp. At least, that's what happens when I execute this code:

    var bmp = new Bitmap(640, 480, PixelFormat.Format8bppIndexed);
    Console.WriteLine(bmp.PixelFormat); // 8 bpp
    var bmp2 = new Bitmap(bmp);
    Console.WriteLine(bmp2.PixelFormat); // 32 bpp
    

    In your code that writes the bitmaps to the memory stream, you're accessing bmps[i], rather than the 8bpp image, b, as you are when writing to file.

    You need to create your list bitmaps, set their properties, and then copy b. You can't duplicate the bitmaps with their properties with the new Bitmap(b) constructor call.