C# 如何在ASP.NET中将位图保存为16色灰度GIF或PNG?

C# 如何在ASP.NET中将位图保存为16色灰度GIF或PNG?,c#,asp.net,png,gif,C#,Asp.net,Png,Gif,在ASP.NET C#中,我试图将位图图像保存为16色非透明灰度图像,保存为PNG或GIF。我假设我必须创建一个调色板,然后以某种方式将调色板附加到图像上,但不确定如何执行此操作 源图像是一个24位彩色位图。它被称为量化,而且很复杂。我对这个问题进行了广泛的研究,我的最佳结果是使用八叉树量化和自定义扩散算法 从A到B的最快点是,使用极其简单的API将颜色计数设置为16,并另存为GIF或PNG。应该是大约2行代码,如果你想通过代码隐藏。。。或者,如果querystring位于文件系统上,则可以使用

在ASP.NET C#中,我试图将位图图像保存为16色非透明灰度图像,保存为PNG或GIF。我假设我必须创建一个调色板,然后以某种方式将调色板附加到图像上,但不确定如何执行此操作


源图像是一个24位彩色位图。

它被称为量化,而且很复杂。我对这个问题进行了广泛的研究,我的最佳结果是使用八叉树量化和自定义扩散算法

从A到B的最快点是,使用极其简单的API将颜色计数设置为16,并另存为GIF或PNG。应该是大约2行代码,如果你想通过代码隐藏。。。或者,如果querystring位于文件系统上,则可以使用它:

image.bmp?format=gif&colors=16
如果图像尚未灰度化,则可以使用模块的ImageAttributes类来执行此操作。生成的GIF将自动具有灰度调色板。工作少,效果好

请记住,您不必将其用作HttpModule—它主要是一个用于调整大小、修改和编码图像的库

如果你想玩你自己的,以下是我开始的内容:

阅读注释并根据我的注释修补指针算术错误


但是,没有抖动,并且在不完全信任的环境中运行原始版本时可能会遇到问题。多年来我做了很多补丁,但我记不清所有补丁。

如果你不介意浏览一堆开源代码,另一种可能是下载Paint.Net。我相信它可以转换成灰度,但我可能是错的,因为我已经有一段时间没有使用它了。

这其实一点也不难,一旦你有了工具集,我就建立了很多工具集。您需要的是:

  • 16色灰度调色板
  • 将图像数据与最近颜色匹配的函数(用于获取调色板数据)
  • 将这些匹配项转换为4位数据(每个值半字节)的函数
  • 将数据写入新的4位图像对象的方法
调色板很简单。灰度值是指红色、绿色和蓝色具有相同值的颜色,对于16色上颜色之间的等亮度阶跃,该值仅为0x00、0x11、0x22等到0xFF的范围。应该不难做到

下一步是将图像颜色与调色板颜色匹配,并生成这些值的字节数组。有几种方法可以在stackoverflow上获得最接近的匹配。这个问题有很多:

接下来是棘手的部分:将实际图像数据转换为4位

需要记住的一点是,图像是按行保存的,这样的行(称为“扫描线”)不一定与图像的宽度相同。例如,在每像素4位中,每个字节可以容纳2个像素,因此从逻辑上讲,步幅的宽度除以2。但是,如果宽度是一个不均匀的数字,则每行的末尾将有一个仅填充了一半的字节。系统不会将下一行的第一个像素放在其中;相反,它只是空白的。对于8位甚至16位的图像,我知道步长通常会将扫描线对齐到4字节的倍数。因此,永远不要假设宽度与扫描线长度相同

对于我在回答中进一步提到的功能,我使用了所需的最小扫描线长度。由于这只是宽度乘以位长度除以8,如果该除法中有余数,则加上1,因此很容易计算为
((bpp*width)+7)/8

现在,如果生成灰度调色板,然后创建一个字节数组,其中包含图像上每个像素的最近调色板值,则所有值都要输入到实际的8位到4位转换函数

我编写了一个函数,将8位数据转换为任意给定的位长度。因此,对于4位图像,这将需要
bitsLength=4

BigEndian参数将确定是否切换一个字节内的值。我不确定这里的.Net图像,但我知道很多1BPP格式使用大端位,而我遇到的4BPP格式是以最小半字节开始的

    /// <summary>
    /// Converts given raw image data for a paletted 8-bit image to lower amount of bits per pixel.
    /// </summary>
    /// <param name="data8bit">The eight bit per pixel image data</param>
    /// <param name="width">The width of the image</param>
    /// <param name="height">The height of the image</param>
    /// <param name="newBpp">The new amount of bits per pixel</param>
    /// <param name="stride">Stride used in the original image data. Will be adjusted to the new stride value.</param>
    /// <param name="bigEndian">Values inside a single byte are read from the largest to the smallest bit.</param>
    /// <returns>The image data converted to the requested amount of bits per pixel.</returns>
private static Byte[] ConvertFrom8Bit(Byte[] data8bit, Int32 width, Int32 height, Int32 bitsLength, Boolean bigEndian)
    {
        if (newBpp > 8)
            throw new ArgumentException("Cannot convert to bit format greater than 8!", "newBpp");
        if (stride < width)
            throw new ArgumentException("Stride is too small for the given width!", "stride");
        if (data8bit.Length < stride * height)
            throw new ArgumentException("Data given data is too small to contain an 8-bit image of the given dimensions", "data8bit");
    Int32 parts = 8 / bitsLength;
    // Amount of bytes to write per width
    Int32 stride = ((bpp * width) + 7) / 8;
    // Bit mask for reducing original data to actual bits maximum.
    // Should not be needed if data is correct, but eh.
    Int32 bitmask = (1 << bitsLength) - 1;
    Byte[] dataXbit = new Byte[stride * height];
    // Actual conversion porcess.
    for (Int32 y = 0; y < height; y++)
    {
        for (Int32 x = 0; x < width; x++)
        {
            // This will hit the same byte multiple times
            Int32 indexXbit = y * stride + x / parts;
            // This will always get a new index
            Int32 index8bit = y * width + x;
            // Amount of bits to shift the data to get to the current pixel data
            Int32 shift = (x % parts) * bitsLength;
            // Reversed for big-endian
            if (bigEndian)
                shift = 8 - shift - bitsLength;
            // Get data, reduce to bit rate, shift it and store it.
            dataXbit[indexXbit] |= (Byte)((data8bit[index8bit] & bitmask) << shift);
        }
    }
    return dataXbit;
}

查看codeplex.com和code.msdn.com以获取更多免费示例,包括八叉树和其他算法。下载哪种“开源”软件需要69美元@不退款-是的,MSDN上有一些,但它们甚至比codebetter.com更旧、更轻便。如果你读了这篇文章,那就是他的出发点@标记许多开源软件。就像许多linux发行版一样。它是开源的,作为另一个项目的一部分,它是可再发行的,而不是免费的。我讨厌许可证的麻烦,我想其他开发者也一样。减少到灰度不需要量化,而且一点也不复杂。。。您只需生成一个16色的灰色调色板,并将数据与之匹配。
    /// <summary>
    /// Creates a bitmap based on data, width, height, stride and pixel format.
    /// </summary>
    /// <param name="sourceData">Byte array of raw source data.</param>
    /// <param name="width">Width of the image.</param>
    /// <param name="height">Height of the image.</param>
    /// <param name="stride">Scanline length inside the data.</param>
    /// <param name="pixelFormat">Pixel format.</param>
    /// <param name="palette">Color palette.</param>
    /// <param name="defaultColor">Default color to fill in on the palette if the given colors don't fully fill it.</param>
    /// <returns>The new image.</returns>
    public static Bitmap BuildImage(Byte[] sourceData, Int32 width, Int32 height, Int32 stride, PixelFormat pixelFormat, Color[] palette, Color? defaultColor)
    {
        Bitmap newImage = new Bitmap(width, height, pixelFormat);
        BitmapData targetData = newImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, newImage.PixelFormat);
        Int32 newDataWidth = ((Image.GetPixelFormatSize(pixelFormat) * width) + 7) / 8;
        Int32 targetStride = targetData.Stride;
        Int64 scan0 = targetData.Scan0.ToInt64();
        for (Int32 y = 0; y < height; ++y)
            Marshal.Copy(sourceData, y * stride, new IntPtr(scan0 + y * targetStride), newDataWidth);
        newImage.UnlockBits(targetData);
        // For indexed images, set the palette.
        if ((pixelFormat & PixelFormat.Indexed) != 0 && (palette != null || defaultColor.HasValue))
        {
            if (palette == null)
                palette = new Color[0];
            ColorPalette pal = newImage.Palette;
            Int32 palLen = pal.Entries.Length;
            Int32 paletteLength = palette.Length;
            for (Int32 i = 0; i < palLen; ++i)
            {
                if (i < paletteLength)
                    pal.Entries[i] = palette[i];
                else if (defaultColor.HasValue)
                    pal.Entries[i] = defaultColor.Value;
                else
                    break;
            }
            // Palette property getter creates a copy, so the newly filled in palette
            // is not actually referenced in the image until you set it again explicitly.
            newImage.Palette = pal;
        }
        return newImage;
    }