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

时间:2010-08-05 11:08:22

标签: c# jpeg tiff

我需要将双色(黑白)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);

    }
}

有没有办法实现这个目标?

感谢。

5 个答案:

答案 0 :(得分:7)

我相信问题可以通过检查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();
    }        
}

答案 1 :(得分:0)

您是否尝试使用带有Image.Save参数的Encoder重载进行保存? 就像Encoder.ColorDepth Parameter

一样

答案 2 :(得分:0)

尝试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?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

答案 3 :(得分:0)

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

要达到类似CCITT4 TIFF的尺寸,我相信您的图像需要使用1位索引的调色板。

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

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

请参阅Bob Powell's excellent article

答案 4 :(得分:0)

这是一个老线程。但是,我会增加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);
}