Need C# function to convert grayscale TIFF to black & white (monochrome/1BPP) TIFF
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);
}
}
}
alchemical
Thus have I heard: Premature optimization is the root of all evil.
Updated on June 04, 2022Comments
-
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 almost 15 yearsThis is a cool library, however, it seems to focus on bitmaps rather than tifs.
-
plinth almost 15 yearsThis will turn any non-white pixel black, so all shades of gray will become black - probably not what he wants.
-
user79755 almost 15 years@plinth - Yup. But you have to admit that it would be pretty darn easy to apply a threshold. :)
-
plinth almost 15 yearsThis method is using simple threshold to do the conversion.
-
alchemical almost 15 yearsOK, that looks interesting. How would I get my Byte[] into the Bitmap object to send to this function?
-
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 almost 15 yearsThis solution appears to have worked! I added a function to enable it to accept Byte[] as well as bitmap.
-
Rex Bloom about 4 yearsThis does not seem to work on dotnet core on macOS. Does the macOS implement this codec and compression as described here?