Need C# function to convert grayscale TIFF to black & white (monochrome/1BPP) TIFF

15,652

Solution 1

There is an article on CodeProject here that describes what you need.

Solution 2

@neodymium has a good answer, but GetPixel/SetPixel will kill performance. Bob Powell has a great method.

C#:

    private Bitmap convertTo1bpp(Bitmap img)
    {
        BitmapData bmdo = img.LockBits(new Rectangle(0, 0, img.Width, img.Height),
                                       ImageLockMode.ReadOnly, 
                                       img.PixelFormat);

        // and the new 1bpp bitmap
        Bitmap bm = new Bitmap(img.Width, img.Height, PixelFormat.Format1bppIndexed);
        BitmapData bmdn = bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height),
                                      ImageLockMode.ReadWrite, 
                                      PixelFormat.Format1bppIndexed);

        // scan through the pixels Y by X
        for(int y = 0; y < img.Height; y++)
        {
            for(int x = 0; x < img.Width; x++)
            {
                // generate the address of the colour pixel
                int index = y * bmdo.Stride + x * 4;

                // check its brightness
                if(Color.FromArgb(Marshal.ReadByte(bmdo.Scan0, index + 2), 
                                  Marshal.ReadByte(bmdo.Scan0, index + 1), 
                                  Marshal.ReadByte(bmdo.Scan0, index)).GetBrightness() > 0.5F)
                {
                    setIndexedPixel(x, y, bmdn, true); // set it if its bright.
                }
             }
        }

        // tidy up
        bm.UnlockBits(bmdn);
        img.UnlockBits(bmdo);
        return bm;
    }

    private void setIndexedPixel(int x, int y, BitmapData bmd, bool pixel)
    {
        int index = y * bmd.Stride + (x >> 3);
        byte p = Marshal.ReadByte(bmd.Scan0, index);
        byte mask = (byte)(0x80 >> (x & 0x7));

        if (pixel)
        {
            p |= mask;
        }
        else
        {
            p &= (byte)(mask ^ 0xFF);
        }

        Marshal.WriteByte(bmd.Scan0, index, p);
    }

Solution 3

might want to check out 'Craigs Utility Library' I believe he has that functionality in place. Craig's Utility Library

Solution 4

My company's product, dotImage, will do this.

Given an image, you can convert from multi-bit to single bit using several methods including simple threshold, global threshold, local threshold, adaptive threshold, dithering (ordered and Floyd Steinberg), and dynamic threshold. The right choice depends on the type of the input image (document, image, graph).

The typical code looks like this:

AtalaImage image = new AtalaImage("path-to-tiff", null);
ImageCommand threshold = SomeFactoryToConstructAThresholdCommand();
AtalaImage finalImage = threshold.Apply(image).Image;

SomeFactoryToConstructAThresholdCommand() is a method that will return a new command that will process the image. It could be as simple as

return new DynamicThresholdCommand();

or

return new GlobalThresholdCommand();

And generally speaking, if you're looking to convert an entire multi-page tiff to black and white, you would do something like this:

// open a sequence of images
FileSystemImageSource source = new FileSystemImageSource("path-to-tiff", true);

using (FileStream outstm = new FileStream("outputpath", FileMode.Create)) {
    // make an encoder and a threshold command
    TiffEncoder encoder = new TiffEncoder(TiffCompression.Auto, true);
    // dynamic is good for documents -- needs the DocumentImaging SDK
    ImageCommand threshold = new DynamicThreshold();

    while (source.HasMoreImages()) {
        // get next image
        AtalaImage image = source.AcquireNext();
        AtalaImage final = threshold.Apply(image).Image;
        try {
            encoder.Save(outstm, final, null);
        }
        finally {
            // free memory from current image
            final.Dispose();
            // release the source image back to the image source
            source.Release(image);
        }
    }
}
Share:
15,652
alchemical
Author by

alchemical

Thus have I heard: Premature optimization is the root of all evil.

Updated on June 04, 2022

Comments

  • alchemical
    alchemical almost 2 years

    I need a C# function that will take a Byte[] of an 8 bit grayscale TIFF, and return a Byte[] of a 1 bit (black & white) TIFF.

    I'm fairly new to working with TIFFs, but the general idea is that we need to convert them from grayscale or color to black and white/monochrome/binary image format.

    We receive the images via a WCF as a Byte[], then we need to make this conversion to black & white in order to send them to a component which does further processing. We do not plan at this point, to ever save them as files.

    For reference, in our test client, this is how we create the Byte[]:

            FileStream fs = new FileStream("test1.tif", FileMode.Open, FileAccess.Read);
            this.image = new byte[fs.Length];
            fs.Read(this.image, 0, System.Convert.ToInt32(fs.Length));
            fs.Close();
    

    --------update---------

    I think there may be more than 1 good answer here, but we ended up using the code from the CodeProject site with the following method added to overload the convert function to accept Byte[] as well as bitmap:

    public static Byte[] ConvertToBitonal(Byte[] original)
        {
            Bitmap bm = new Bitmap(new System.IO.MemoryStream(original, false));
            bm = ConvertToBitonal(bm);
            System.IO.MemoryStream s = new System.IO.MemoryStream();
            bm.Save(s, System.Drawing.Imaging.ImageFormat.Tiff);
            return s.ToArray();
        }
    
  • alchemical
    alchemical almost 15 years
    This is a cool library, however, it seems to focus on bitmaps rather than tifs.
  • plinth
    plinth almost 15 years
    This will turn any non-white pixel black, so all shades of gray will become black - probably not what he wants.
  • user79755
    user79755 almost 15 years
    @plinth - Yup. But you have to admit that it would be pretty darn easy to apply a threshold. :)
  • plinth
    plinth almost 15 years
    This method is using simple threshold to do the conversion.
  • alchemical
    alchemical almost 15 years
    OK, that looks interesting. How would I get my Byte[] into the Bitmap object to send to this function?
  • mbeckish
    mbeckish almost 15 years
    @NagaMensch - I'm not sure you will be able to. According to msdn.microsoft.com/en-us/library/…, 8 bit grayscale is not a supported PixelFormat.
  • alchemical
    alchemical almost 15 years
    This solution appears to have worked! I added a function to enable it to accept Byte[] as well as bitmap.
  • Rex Bloom
    Rex Bloom about 4 years
    This does not seem to work on dotnet core on macOS. Does the macOS implement this codec and compression as described here?