views:

3947

answers:

6

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();
    }
+1  A: 

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

SomeMiscGuy
This is a cool library, however, it seems to focus on bitmaps rather than tifs.
alchemical
A: 

First, you would need to know how an X,Y pixel location maps to an index value in you array. This will depend upon how your Byte[] was constructed. You need to know the details of your image format - for example, what is the stride?

I don't see 8 bit grayscale TIFF in the PixelFormat enumeration. If it was there, it would tell you what you need to know.

Then, iterate through each pixel and look at its color value. You need to decide on a threshold value - if the color of the pixel is above the threshold, make the new color white; otherwise, make it black.

If you want to simulate grayscale shading with 1BPP, you could look at more advanced techniques, such as dithering.

mbeckish
+1  A: 

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);
        }
    }
}
plinth
I can't upvote this, but I'm giving you a virtual +1 for being a code pimp.
Erich Mirabal
he be big pimpin
Justin
A: 

Something like this might work, I haven't tested it. (Should be easy to C# it.)

    Dim bmpGrayscale As Bitmap = Bitmap.FromFile("Grayscale.tif")
    Dim bmpMonochrome As New Bitmap(bmpGrayscale.Width, bmpgrayscale.Height, Imaging.PixelFormat.Format1bppIndexed)
    Using gfxMonochrome As Graphics = Graphics.FromImage(bmpMonochrome)
        gfxMonochrome.Clear(Color.White)
    End Using
    For y As Integer = 0 To bmpGrayscale.Height - 1
        For x As Integer = 0 To bmpGrayscale.Width - 1
            If bmpGrayscale.GetPixel(x, y) <> Color.White Then
                bmpMonochrome.SetPixel(x, y, Color.Black)
            End If
        Next
    Next
    bmpMonochrome.Save("Monochrome.tif")

This might be a better way still:

Using bmpGrayscale As Bitmap = Bitmap.FromFile("Grayscale.tif")
    Using bmpMonochrome As New Bitmap(bmpGrayscale.Width, bmpgrayscale.Height, Imaging.PixelFormat.Format1bppIndexed)
        Using gfxMonochrome As Graphics = Graphics.FromImage(bmpMonochrome)
            gfxMonochrome.CompositingQuality = Drawing2D.CompositingQuality.HighQuality
            gfxMonochrome.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
            gfxMonochrome.DrawImage(bmpGrayscale, new Rectangle(0, 0, bmpMonochrome.Width, bmpMonochrome.Height)
        End Using
        bmpMonochrome.Save("Monochrome.tif")
    End Using
End Using

I believe the term you are looking for is "resampling".

This will turn any non-white pixel black, so all shades of gray will become black - probably not what he wants.
plinth
@plinth - Yup. But you have to admit that it would be pretty darn easy to apply a threshold. :)
+4  A: 

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

R Ubben
This solution appears to have worked! I added a function to enable it to accept Byte[] as well as bitmap.
alchemical
+2  A: 

@neodymium has a good answer, but GetPixel/SetPixel will kill performance. Bob Powell has a great method here: http://www.bobpowell.net/onebit.htm

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);
}

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 = 0x80 >> (x & 0x7);

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

    Marshal.WriteByte(bmd.Scan0, index, p);
}
codekaizen
This is helpful, but the code is in VB rather than c#.
alchemical
+1 beat me by 1 minute :)
Justin
This method is using simple threshold to do the conversion.
plinth
That's funny, I didn't even notice it was in VB!Here it is in C#...
codekaizen
OK, that looks interesting. How would I get my Byte[] into the Bitmap object to send to this function?
alchemical
@NagaMensch - I'm not sure you will be able to. According to http://msdn.microsoft.com/en-us/library/system.drawing.imaging.pixelformat.aspx, 8 bit grayscale is not a supported PixelFormat.
mbeckish
@NagaMensch if you already have a Byte[], you don't even need to read the source bitmap via a BitmapData instance. Just use the Byte array - just make sure you know what the stride is.
codekaizen