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

发布于 2024-07-28 22:06:44 字数 993 浏览 8 评论 0原文

我需要一个 C# 函数,它将获取 8 位灰度 TIFF 的 Byte[],并返回 1 位(黑白)TIFF 的 Byte[]。

我对使用 TIFF 相当陌生,但总体思路是我们需要将它们从灰度或彩色转换为黑白/单色/二进制图像格式。

我们通过 WCF 以 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---------

我认为这里可能有不止 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();
    }

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

生寂 2024-08-04 22:06:45

我公司的产品 dotImage 可以做到这一点。

给定图像,您可以使用多种方法从多位转换为单位,包括简单阈值、全局阈值、局部阈值、自适应阈值、抖动(有序和 Floyd Steinberg)和动态阈值。 正确的选择取决于输入图像的类型(文档、图像、图表)。

典型的代码如下所示:

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

SomeFactoryToConstructAThresholdCommand() 是一个方法,它将返回一个处理图像的新命令。 它可以像

return new DynamicThresholdCommand();

return new GlobalThresholdCommand();

一样简单。一般来说,如果您希望将整个多页 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);
        }
    }
}

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);
        }
    }
}
秋千易 2024-08-04 22:06:45

首先,您需要知道 X,Y 像素位置如何映射到数组中的索引值。
这将取决于您的 Byte[] 的构造方式。
您需要了解图像格式的详细信息 - 例如,步幅是什么?

我在 PixelFormat 中没有看到 8 位灰度 TIFF枚举。 如果它在那里,它会告诉您需要了解的内容。

然后,迭代每个像素并查看其颜色值。
您需要确定一个阈值 - 如果像素的颜色高于阈值,则将新颜色设置为白色; 否则,将其设为黑色。

如果您想使用 1BPP 模拟灰度着色,您可以考虑更高级的技术,例如抖动。

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.

苍景流年 2024-08-04 22:06:45

像这样的东西可能会起作用,我还没有测试过。 (用 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")

这可能是更好的方法:

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

我相信您正在寻找的术语是“重新采样”。

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".

半窗疏影 2024-08-04 22:06:45

逐个像素的操作非常慢。 比 System.DrawImage 慢 40 倍。
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;
    }

完美的方法就是简单地转换为 CCITT 解码的 tif,其中仅包含 BW。 更有效的方法,结果文件为 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);
            }
        }
    }

祝你好运兄弟,

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,

梦里的微风 2024-08-04 22:06:45

我已经测试了这段代码并且对我来说工作得很好:

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

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;
}
椒妓 2024-08-04 22:06:44

CodeProject 上有一篇文章此处描述了您需要的内容。

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

雨落星ぅ辰 2024-08-04 22:06:44

@neodymium 有一个很好的答案,但 GetPixel/SetPixel 会降低性能。 鲍勃·鲍威尔有一个很棒的方法

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

@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);
    }
紅太極 2024-08-04 22:06:44

可能想查看“Craigs Utility Library”,我相信他已经具备该功能。
克雷格的实用程序库

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文