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

前端 未结 7 1681
清歌不尽
清歌不尽 2021-01-14 09:18

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 TIF

相关标签:
7条回答
  • 2021-01-14 09:37

    @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);
        }
    
    0 讨论(0)
  • 2021-01-14 09:41

    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.

    0 讨论(0)
  • 2021-01-14 09:42

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

    0 讨论(0)
  • 2021-01-14 09:44

    pixel by pixel manipulation is extremly slow. 40 times slower than System.DrawImage. System.Draw image is half solution, corrupts the picture (300dpi-->96dpi) and produces at 300dpi source 200-400kb large result files.

            public static Image GetBlackAndWhiteImage(Image SourceImage)
        {
    
            Bitmap bmp = new Bitmap(SourceImage.Width, SourceImage.Height);
    
            using (Graphics gr = Graphics.FromImage(bmp)) // SourceImage is a Bitmap object
            {
                var gray_matrix = new float[][] {
                new float[] { 0.299f, 0.299f, 0.299f, 0, 0 },
                new float[] { 0.587f, 0.587f, 0.587f, 0, 0 },
                new float[] { 0.114f, 0.114f, 0.114f, 0, 0 },
                new float[] { 0,      0,      0,      1, 0 },
                new float[] { 0,      0,      0,      0, 1 }
            };
    
                var ia = new System.Drawing.Imaging.ImageAttributes();
                ia.SetColorMatrix(new System.Drawing.Imaging.ColorMatrix(gray_matrix));
                ia.SetThreshold(float.Parse(Settings.Default["Threshold"].ToString())); // Change this threshold as needed
                var rc = new Rectangle(0, 0, SourceImage.Width, SourceImage.Height);
                gr.DrawImage(SourceImage, rc, 0, 0, SourceImage.Width, SourceImage.Height, GraphicsUnit.Pixel, ia);
            }
            return bmp;
        }
    

    The perfect way is just simply convert to CCITT decoded tif, that contains only BW. Much more efficent method with 30-50kb result file, 300dpi also remains correct as well:

            public void toCCITT(string tifURL)
        {
            byte[] imgBits = File.ReadAllBytes(tifURL);
    
            using (MemoryStream ms = new MemoryStream(imgBits))
            {
                using (Image i = Image.FromStream(ms))
                {
                    EncoderParameters parms = new EncoderParameters(1);
                    ImageCodecInfo codec = ImageCodecInfo.GetImageDecoders()
                                                         .FirstOrDefault(decoder => decoder.FormatID == ImageFormat.Tiff.Guid);
    
                    parms.Param[0] = new EncoderParameter(Encoder.Compression, (long)EncoderValue.CompressionCCITT4);
    
                    i.Save(@"c:\test\result.tif", codec, parms);
                }
            }
        }
    

    Good Luck Bro,

    0 讨论(0)
  • 2021-01-14 09:52

    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);
            }
        }
    }
    
    0 讨论(0)
  • 2021-01-14 10:03

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

    0 讨论(0)
提交回复
热议问题