在 C# 中将双色 TIFF 转换为双色 PNG

发布于 2024-09-13 04:15:23 字数 695 浏览 10 评论 0原文

我需要将黑白(黑白)TIFF 文件转换为另一种格式以便通过网络浏览器显示,目前我们使用的是 JPG,但格式并不重要。从阅读周围的内容来看,.NET 似乎并不容易支持编写双色调图像,因此我们最终得到的文件大约为 1MB,而不是大约 100K。我正在考虑使用 ImageMagick 来执行此操作,但理想情况下我想要一个如果可能的话不需要此操作的解决方案。

当前代码片段(它还对图像进行了一些调整大小):

using (Image img = Image.FromFile(imageName))
{
    using (Bitmap resized = new Bitmap(resizedWidth, resizedHeight)
    {
        using (Graphics g = Graphics.FromImage(resized))
        {
            g.DrawImage(img, new Rectangle(0, 0, resized.Width, resized.Height), 0, 0, img.Width, img.Height, GraphicsUnit.Pixel);
        }

        resized.Save(outputFilename, System.Drawing.Imaging.ImageFormat.Jpeg);

    }
}

有什么方法可以实现这一点吗?

谢谢。

I need to convert bitonal (black and white) TIFF files into another format for display by a web browser, currently we're using JPGs, but the format isn't crucial. From reading around .NET doesn't seem to easily support writing bitonal images, so we're ending up with ~1MB files instead of ~100K ones. I'm considering using ImageMagick to do this, but ideally i'd like a solution which doesn't require this if possible.

Current code snippet (which also does some resizing on the image):

using (Image img = Image.FromFile(imageName))
{
    using (Bitmap resized = new Bitmap(resizedWidth, resizedHeight)
    {
        using (Graphics g = Graphics.FromImage(resized))
        {
            g.DrawImage(img, new Rectangle(0, 0, resized.Width, resized.Height), 0, 0, img.Width, img.Height, GraphicsUnit.Pixel);
        }

        resized.Save(outputFilename, System.Drawing.Imaging.ImageFormat.Jpeg);

    }
}

Is there any way to achieve this?

Thanks.

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

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

发布评论

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

评论(5

迎风吟唱 2024-09-20 04:15:23

我相信通过检查 resized 位图是否为 PixelFormat.Format1bppIndexed 可以解决该问题。如果不是,您应该将其转换为 1bpp 位图,然后您可以将其保存为黑白 png,没有问题。

换句话说,您应该使用以下代码而不是 resized.Save(outputFilename, System.Drawing.Imaging.ImageFormat.Jpeg);

if (resized.PixelFormat != PixelFormat.Format1bppIndexed)
{
    using (Bitmap bmp = convertToBitonal(resized))
        bmp.Save(outputFilename, System.Drawing.Imaging.ImageFormat.Png);
}
else
{
    resized.Save(outputFilename, System.Drawing.Imaging.ImageFormat.Png);
}

我对 convertToBitonal 使用以下代码:

private static Bitmap convertToBitonal(Bitmap original)
{
    int sourceStride;
    byte[] sourceBuffer = extractBytes(original, out sourceStride);

    // Create destination bitmap
    Bitmap destination = new Bitmap(original.Width, original.Height,
        PixelFormat.Format1bppIndexed);

    destination.SetResolution(original.HorizontalResolution, original.VerticalResolution);

    // Lock destination bitmap in memory
    BitmapData destinationData = destination.LockBits(
        new Rectangle(0, 0, destination.Width, destination.Height),
        ImageLockMode.WriteOnly, PixelFormat.Format1bppIndexed);

    // Create buffer for destination bitmap bits
    int imageSize = destinationData.Stride * destinationData.Height;
    byte[] destinationBuffer = new byte[imageSize];

    int sourceIndex = 0;
    int destinationIndex = 0;
    int pixelTotal = 0;
    byte destinationValue = 0;
    int pixelValue = 128;
    int height = destination.Height;
    int width = destination.Width;
    int threshold = 500;

    for (int y = 0; y < height; y++)
    {
        sourceIndex = y * sourceStride;
        destinationIndex = y * destinationData.Stride;
        destinationValue = 0;
        pixelValue = 128;

        for (int x = 0; x < width; x++)
        {
            // Compute pixel brightness (i.e. total of Red, Green, and Blue values)
            pixelTotal = sourceBuffer[sourceIndex + 1] + sourceBuffer[sourceIndex + 2] +
                sourceBuffer[sourceIndex + 3];

            if (pixelTotal > threshold)
                destinationValue += (byte)pixelValue;

            if (pixelValue == 1)
            {
                destinationBuffer[destinationIndex] = destinationValue;
                destinationIndex++;
                destinationValue = 0;
                pixelValue = 128;
            }
            else
            {
                pixelValue >>= 1;
            }

            sourceIndex += 4;
        }

        if (pixelValue != 128)
            destinationBuffer[destinationIndex] = destinationValue;
    }

    Marshal.Copy(destinationBuffer, 0, destinationData.Scan0, imageSize);
    destination.UnlockBits(destinationData);
    return destination;
}

private static byte[] extractBytes(Bitmap original, out int stride)
{
    Bitmap source = null;

    try
    {
        // If original bitmap is not already in 32 BPP, ARGB format, then convert
        if (original.PixelFormat != PixelFormat.Format32bppArgb)
        {
            source = new Bitmap(original.Width, original.Height, PixelFormat.Format32bppArgb);
            source.SetResolution(original.HorizontalResolution, original.VerticalResolution);
            using (Graphics g = Graphics.FromImage(source))
            {
                g.DrawImageUnscaled(original, 0, 0);
            }
        }
        else
        {
            source = original;
        }

        // Lock source bitmap in memory
        BitmapData sourceData = source.LockBits(
            new Rectangle(0, 0, source.Width, source.Height),
            ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

        // Copy image data to binary array
        int imageSize = sourceData.Stride * sourceData.Height;
        byte[] sourceBuffer = new byte[imageSize];
        Marshal.Copy(sourceData.Scan0, sourceBuffer, 0, imageSize);

        // Unlock source bitmap
        source.UnlockBits(sourceData);

        stride = sourceData.Stride;
        return sourceBuffer;
    }
    finally
    {
        if (source != original)
            source.Dispose();
    }        
}

I believe the problem can be solved by checking that resized bitmap is of PixelFormat.Format1bppIndexed. If it's not, you should convert it to 1bpp bitmap and after that you can save it as black and white png without problems.

In other words, you should use following code instead of resized.Save(outputFilename, System.Drawing.Imaging.ImageFormat.Jpeg);

if (resized.PixelFormat != PixelFormat.Format1bppIndexed)
{
    using (Bitmap bmp = convertToBitonal(resized))
        bmp.Save(outputFilename, System.Drawing.Imaging.ImageFormat.Png);
}
else
{
    resized.Save(outputFilename, System.Drawing.Imaging.ImageFormat.Png);
}

I use following code for convertToBitonal :

private static Bitmap convertToBitonal(Bitmap original)
{
    int sourceStride;
    byte[] sourceBuffer = extractBytes(original, out sourceStride);

    // Create destination bitmap
    Bitmap destination = new Bitmap(original.Width, original.Height,
        PixelFormat.Format1bppIndexed);

    destination.SetResolution(original.HorizontalResolution, original.VerticalResolution);

    // Lock destination bitmap in memory
    BitmapData destinationData = destination.LockBits(
        new Rectangle(0, 0, destination.Width, destination.Height),
        ImageLockMode.WriteOnly, PixelFormat.Format1bppIndexed);

    // Create buffer for destination bitmap bits
    int imageSize = destinationData.Stride * destinationData.Height;
    byte[] destinationBuffer = new byte[imageSize];

    int sourceIndex = 0;
    int destinationIndex = 0;
    int pixelTotal = 0;
    byte destinationValue = 0;
    int pixelValue = 128;
    int height = destination.Height;
    int width = destination.Width;
    int threshold = 500;

    for (int y = 0; y < height; y++)
    {
        sourceIndex = y * sourceStride;
        destinationIndex = y * destinationData.Stride;
        destinationValue = 0;
        pixelValue = 128;

        for (int x = 0; x < width; x++)
        {
            // Compute pixel brightness (i.e. total of Red, Green, and Blue values)
            pixelTotal = sourceBuffer[sourceIndex + 1] + sourceBuffer[sourceIndex + 2] +
                sourceBuffer[sourceIndex + 3];

            if (pixelTotal > threshold)
                destinationValue += (byte)pixelValue;

            if (pixelValue == 1)
            {
                destinationBuffer[destinationIndex] = destinationValue;
                destinationIndex++;
                destinationValue = 0;
                pixelValue = 128;
            }
            else
            {
                pixelValue >>= 1;
            }

            sourceIndex += 4;
        }

        if (pixelValue != 128)
            destinationBuffer[destinationIndex] = destinationValue;
    }

    Marshal.Copy(destinationBuffer, 0, destinationData.Scan0, imageSize);
    destination.UnlockBits(destinationData);
    return destination;
}

private static byte[] extractBytes(Bitmap original, out int stride)
{
    Bitmap source = null;

    try
    {
        // If original bitmap is not already in 32 BPP, ARGB format, then convert
        if (original.PixelFormat != PixelFormat.Format32bppArgb)
        {
            source = new Bitmap(original.Width, original.Height, PixelFormat.Format32bppArgb);
            source.SetResolution(original.HorizontalResolution, original.VerticalResolution);
            using (Graphics g = Graphics.FromImage(source))
            {
                g.DrawImageUnscaled(original, 0, 0);
            }
        }
        else
        {
            source = original;
        }

        // Lock source bitmap in memory
        BitmapData sourceData = source.LockBits(
            new Rectangle(0, 0, source.Width, source.Height),
            ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

        // Copy image data to binary array
        int imageSize = sourceData.Stride * sourceData.Height;
        byte[] sourceBuffer = new byte[imageSize];
        Marshal.Copy(sourceData.Scan0, sourceBuffer, 0, imageSize);

        // Unlock source bitmap
        source.UnlockBits(sourceData);

        stride = sourceData.Stride;
        return sourceBuffer;
    }
    finally
    {
        if (source != original)
            source.Dispose();
    }        
}

您是否尝试过使用 Image.Save 重载进行保存 编码器参数?
就像 Encoder.ColorDepth 参数

Have you tried saving using the Image.Save overload with Encoder parameters?
Like the Encoder.ColorDepth Parameter?

南风几经秋 2024-09-20 04:15:23

尝试 jaroslav 对颜色深度的建议不起作用:

static void Main(string[] args)
{
        var list = ImageCodecInfo.GetImageDecoders();
        var jpegEncoder = list[1]; // i know this is the jpeg encoder by inspection
        Bitmap bitmap = new Bitmap(500, 500);
        Graphics g = Graphics.FromImage(bitmap);
        g.DrawRectangle(new Pen(Color.Red), 10, 10, 300, 300);
        var encoderParams = new EncoderParameters();
        encoderParams.Param[0] = new EncoderParameter(Encoder.ColorDepth, 2);
        bitmap.Save(@"c:\newbitmap.jpeg", jpegEncoder, encoderParams);

}

jpeg 仍然是全色 jpeg。

我认为 gdi plus 不支持灰度 jpeg。您是否尝试过查看 Windows 映像组件?

http://www.microsoft.com/downloads/details.aspx microsoft.com/downloads/details.aspx?FamilyID=8e011506-6307-445b-b950-215def45ddd8&displaylang=en

代码示例:http://www.codeproject.com/KB/GDI-plus/windows_imaging.aspx

维基百科:http://en.wikipedia.org/wiki/Windows_Imaging_Component

Trying jaroslav's suggestion for color depth doesn't work:

static void Main(string[] args)
{
        var list = ImageCodecInfo.GetImageDecoders();
        var jpegEncoder = list[1]; // i know this is the jpeg encoder by inspection
        Bitmap bitmap = new Bitmap(500, 500);
        Graphics g = Graphics.FromImage(bitmap);
        g.DrawRectangle(new Pen(Color.Red), 10, 10, 300, 300);
        var encoderParams = new EncoderParameters();
        encoderParams.Param[0] = new EncoderParameter(Encoder.ColorDepth, 2);
        bitmap.Save(@"c:\newbitmap.jpeg", jpegEncoder, encoderParams);

}

The jpeg is still a full color jpeg.

I don't think there is any support for grayscale jpeg in gdi plus. Have you tried looking in windows imaging component?

http://www.microsoft.com/downloads/details.aspx?FamilyID=8e011506-6307-445b-b950-215def45ddd8&displaylang=en

code example: http://www.codeproject.com/KB/GDI-plus/windows_imaging.aspx

wikipedia: http://en.wikipedia.org/wiki/Windows_Imaging_Component

卖梦商人 2024-09-20 04:15:23

这是一个旧线程。不过,我还是要加 2 美分。

我使用 AForge.Net 库(开源)

使用这些 dll。 Aforge.dllAForge.Imaging.dll

using AForge.Imaging.Filters;

private void ConvertBitmap()
{
    markedBitmap = Grayscale.CommonAlgorithms.RMY.Apply(markedBitmap);
    ApplyFilter(new FloydSteinbergDithering());
}
private void ApplyFilter(IFilter filter)
{
    // apply filter
    convertedBitmap = filter.Apply(markedBitmap);
}

This is an old thread. However, I'll add my 2 cents.

I use AForge.Net libraries (open source)

use these dlls. Aforge.dll, AForge.Imaging.dll

using AForge.Imaging.Filters;

private void ConvertBitmap()
{
    markedBitmap = Grayscale.CommonAlgorithms.RMY.Apply(markedBitmap);
    ApplyFilter(new FloydSteinbergDithering());
}
private void ApplyFilter(IFilter filter)
{
    // apply filter
    convertedBitmap = filter.Apply(markedBitmap);
}
锦欢 2024-09-20 04:15:23

您尝试过 1 位颜色深度的 PNG 吗?

要获得类似于 CCITT4 TIFF 的大小,我相信您的图像需要使用 1 位索引调色板。

但是,您不能使用 .NET 中的 Graphics 对象在索引图像上绘图。

您可能必须使用 LockBits 来操作每个像素。

请参阅 鲍勃·鲍威尔的精彩文章

Have you tried PNG with 1 bit color depth?

To achieve a size similar to a CCITT4 TIFF, I believe your image needs to use a 1-bit indexed pallette.

However, you can't use the Graphics object in .NET to draw on an indexed image.

You will probably have to use LockBits to manipulate each pixel.

See Bob Powell's excellent article.

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