简体   繁体   English

需要 C# 函数将灰度 TIFF 转换为黑白(单色/1BPP)TIFF

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

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.我需要一个 C# 函数,它将采用 8 位灰度 TIFF 的 Byte[],并返回 1 位(黑白)TIFF 的 Byte[]。

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.我对使用 TIFF 还很陌生,但总体思路是我们需要将它们从灰度或彩色转换为黑白/单色/二进制图像格式。

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.我们通过 WCF 作为 Byte[] 接收图像,然后我们需要将此转换为黑白,以便将它们发送到进行进一步处理的组件。 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[]:作为参考,在我们的测试客户端中,这是我们创建 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:我认为这里可能有不止 1 个好的答案,但我们最终使用了 CodeProject 站点的代码,并添加了以下方法来重载转换函数以接受 Byte[] 以及位图:

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

There is an article on CodeProject here that describes what you need. 这里有一篇关于 CodeProject 的文章,描述了您的需要。

@neodymium has a good answer, but GetPixel/SetPixel will kill performance. @neodymium 有一个很好的答案,但 GetPixel/SetPixel 会降低性能。 Bob Powell has a great method . 鲍勃鲍威尔有一个很好的方法

C#: 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);
    }

might want to check out 'Craigs Utility Library' I believe he has that functionality in place.可能想查看“Craigs Utility Library”,我相信他具有该功能。 Craig's Utility Library克雷格的实用程序库

My company's product, dotImage , will do this.我公司的产品dotImage将执行此操作。

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.给定一张图像,您可以使用多种方法将多位转换为单位,包括简单阈值、全局阈值、局部阈值、自适应阈值、抖动(有序和 Floyd Steinberg)和动态阈值。 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. SomeFactoryToConstructAThresholdCommand() 是一种方法,它将返回处理图像的新命令。 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:一般来说,如果你想将整个多页 tiff 转换为黑白,你会做这样的事情:

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

First, you would need to know how an X,Y pixel location maps to an index value in you array.首先,您需要知道 X、Y 像素位置如何映射到数组中的索引值。 This will depend upon how your Byte[] was constructed. 这将取决于您的 Byte[] 是如何构建的。 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.我在PixelFormat 枚举中没有看到 8 位灰度 TIFF。 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.如果你想用 1BPP 模拟灰度着色,你可以看看更高级的技术,比如抖动。

Something like this might work, I haven't tested it.像这样的东西可能有用,我还没有测试过。 (Should be easy to C# it.) (应该很容易用 C# 吧。)

    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".我相信您正在寻找的术语是“重采样”。

pixel by pixel manipulation is extremly slow.逐像素操作非常慢。 40 times slower than System.DrawImage.比 System.DrawImage 慢 40 倍。 System.Draw image is half solution, corrupts the picture (300dpi-->96dpi) and produces at 300dpi source 200-400kb large result files. System.Draw 图像是半解决方案,损坏图片 (300dpi-->96dpi) 并在 300dpi 源生成 200-400kb 大结果文件。

        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.完美的方法就是简单地转换为仅包含 BW 的 CCITT 解码的 tif。 Much more efficent method with 30-50kb result file, 300dpi also remains correct as well:使用 30-50kb 结果文件的更有效方法,300dpi 也保持正确:

        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,祝你好运,兄弟,

I've tested this code and worked fine for me:我已经测试了这段代码并且对我来说工作得很好:

//You should use System.Linq for this to work
public static ImageCodecInfo TiffCodecInfo => ImageCodecInfo.GetImageDecoders().
    FirstOrDefault(decoder => decoder.FormatID == ImageFormat.Tiff.Guid);

//Encapsulate this in a try catch block for possible exceptions
public static Bitmap ConvertToBitonal(Bitmap original)
{
    EncoderParameters encoderParameters;
    MemoryStream ms = new MemoryStream();
    Bitmap result;

    encoderParameters = new EncoderParameters(1);
    encoderParameters.Param[0] = new EncoderParameter(Encoder.ColorDepth, 1L);
    original.Save(ms, TiffCodecInfo, encoderParameters);
    result = new Bitmap(Image.FromStream(ms));
    ms.Dispose();
    return result;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM