Search code examples
bitmap.net-8.0imagesharp

1bpp Bitmap: converting code from BitmapData to ImageSharp's


I'm trying to migrate an existing library from .NET 4.8 to .NET 8.0. The library loads an existing 1 bpp bitmap and converts it to an array which is then printed on a Zebra library. Here's the current working code that relies on BitmapData and LockBits:

public ImageData GetImageBits() {        
    BitmapData? bits = null;
     ImageData data = new();
     try {
         bits = BitmapToPrint.LockBits(
               new Rectangle(0, 0, BitmapToPrint.Width, BitmapToPrint.Height),
               ImageLockMode.ReadOnly, 
               BitmapToPrint.PixelFormat);

         if( bits is null ) {
             throw new InvalidOperationException("Error creating bitmap data");
         }

         byte[] imageBytes = new byte[bits.Height *bits.Stride];
         Marshal.Copy(bits.Scan0, imageBytes, 0, bits.Stride *bits.Height);

         int imgWidth = bits.Stride;
         int imgHeight = bits.Height;
         int realWidth = bits.Width /8; //only works for fixed size bits

         if (realWidth != imgWidth) {
             int bytesToClear = imgWidth - realWidth;
             for (int i = 0; i < imgHeight; i++) {
                 int pos = realWidth + imgWidth *i;
                 for (int counter = 0; counter < bytesToClear; counter++, pos++) {
                     imageBytes[pos] = 255;
                 }
             }
         }

         data.ImageBytes = imageBytes;
         data.ImageHeight = imgHeight;
         data.ImageWidth = imgWidth;
     }
     finally {
         if (bits != null) {
             BitmapToPrint.UnlockBits(bits);
         }
     }
     return data;
}

The code starts by locking the bitmap and then uses the Stride property (which, if I'm not mistaken, returns the correct size of each row with padding) to calculate the required size of the array that will hold the bitmap's bytes.

After copying the bytes, the method checks to see if the bitmap's width is a larger than the stride and, when that happens, it will "cleanup" the bytes (sets it to FF).

I'm trying to migrate this code to ImageSharp, but my limited knowledge of bitmaps and of ImageSharp is making this hard. Since we're talking abouta 1 bpp bitmap, then I'm under the impression that I should rely on Image<L8> to load my bitmap:

// assume we're loading the bitmap from an embedded resource
Assembly assembly = typeof(Program).Assembly;
using Stream stream = assembly.GetManifestResourceStream("DemoImages.logoCABio.bmp");
Image<L8> bitmap = Image.Load<L8>(stream);

From the Image<L8> I can get the bitmaps width and height, but how can I recover the stride? It seems like I should be able to access the bitmaps bytes with code similar to this one:

byte[] bytes = new byte[bitmap.Width*bitmap.Height*bitmap.PixelType.BitsPerPixel/8];
bitmap.CopyPixelDataTo(bytes);

However, when I compare the byte array from the LockBits approach with this one, it's completely off and it simply doesn't get printed by the Zebra (it will simply generate several lines).

When I look at the initial code, it's easy to see that stride is an important property for calculating the size of the array (and it will also get passed to the zebra code so that it can find the "bitmap rows" from the array). Anyone knows how to calculate this with ImageSharp?

Thanks.


Solution

  • After several hours, I've finally managed to get it to work. Regarding my question about calculating the stride, I've finally got it:

    ushort bpp = 1; // in my case, I'm working with 1bpp pixels
    int stride = (int)(4 * ((((uint)BitmapToPrint.Width * bpp) + 31) / 32));
    int padding = bytesPerLine - (int)(BitmapToPrint.Width * (bpp / 8F));
    

    It seems like strides (ie, bytes per line of the bitmap) should always be aligned to 32-bit address boundary. After having the stride, you can also calculate padding easily in order to add the "missing bytes" so that the stride is 32-bit address aligned.

    Now, there was still the question of getting only the bitmap array encapsulated by the Image<L8> instance into a 1bpp array which can be passed to the Zebra printer. Unfortunately, ImageSharp doesn't support sub-byte packed pixels into memory (that's why I had to load the image using the L8 format). However, the library is capable of exporting an image into a 1bpp bitmap through the BmpEncoder and it's fairly easy to adapt its code in order to the the 1bpp bitmap array only (in my case, I had only to adapt the Write1BitPalette method in order to ensure that it fills all the bits when the bitmap's width is not a multiple of 8).

    Huge thumbs for Six Labors guys for building such a great framework!