如何在 ASP.NET 中将位图另存为 16 色灰度 GIF 或 PNG?
在 ASP.NET C# 中,我尝试将位图图像另存为 16 色不透明灰度图像(PNG 或 GIF)。我假设我必须创建一个调色板,然后以某种方式将调色板附加到图像上,但不知道如何去做。
源图像是 24 位颜色位图。
In ASP.NET C# I'm trying to save a bitmap image as an 16-color non-transparent grayscale image as either a PNG or GIF. I assume I have to create a palette and then somehow attach the palette to the image but not sure how to go about doing this.
The source image is a 24-bit color Bitmap.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
这就是所谓的量化,而且很复杂。我对这个问题进行了广泛的研究,我的最佳结果是使用八叉树量化和自定义扩散算法。
从 A 到 B 的最快点是 获取我的代码(开源,但 69 美元)下载),并使用极其简单的 API 将颜色数设置为 16 并保存为 GIF 或 PNG。如果您想通过代码隐藏来执行此操作,则应该大约 2 行代码...或者,如果它位于文件系统上,则可以使用查询字符串:
如果图像还不是灰度图像,您可以使用 ImageAttributes 类来执行此操作模块的。生成的 GIF 将自动具有灰度调色板。最少的工作,最大的成果。
请记住,您不必将其用作 HttpModule - 它主要是一个用于调整图像大小、修改和编码图像的库。
如果你想自己动手,这就是我的开始:
http://codebetter.com/blogs/brendan.tompkins/archive/2007/06/14/gif-image-color-quantizer-now-with-safe-goodness.aspx
阅读评论并根据我的评论修补指针算术错误......
不过,没有抖动,并且您可能会在低于完全信任的环境中运行原始版本时遇到问题。多年来我制作了很多补丁,但我不记得全部了。
It's called quantization, and it's complicated. I've worked extensively with this problem, and my best results have been using Octree quantization and a custom diffusion algorithm.
Your fastest point from A to B is grab my code (open-source, but $69 to download) and use the extremely simple API to set the color count to 16 and save as GIF or PNG. Should be about 2 lines of code if you want to do it via code-behind... or, you can use a querystring if it's on the filesystem:
If the image isn't already grayscale, you can do that using the ImageAttributes class of the module. The resulting GIF will automatically have a grayscale palette. Minimal work, great results.
Remember you don't have to use it as an HttpModule - it's primarily a library for resizing, modifying, and encoding images.
If you want to roll your own, here's what I started with:
http://codebetter.com/blogs/brendan.tompkins/archive/2007/06/14/gif-image-color-quantizer-now-with-safe-goodness.aspx
Read through the comments and patch the pointer arithmetic errors per my comments....
No dithering, though, and you may have trouble running the original in less than a full trust environment. I've made a lot of patches over the years, and I don't remember them all.
如果您不介意浏览一堆开源代码,另一种可能性是下载 Paint.Net。我相信它可以转换为灰度,但我可能是错的,因为我已经有一段时间没有需要使用它了。
Another possibility if you don't mind trolling through a bunch of open source code is to download Paint.Net. I believe it can convert to Grayscale, but I could be wrong as it's been a while since I've had a need to use it.
一旦你获得了工具集,这实际上一点也不难,而且我已经建立了很多这样的工具集。您需要的是:
调色板很简单。灰度值是红、绿、蓝具有相同值的颜色,对于 16 种颜色的颜色之间的相同亮度步长,该值只是从 0x00、0x11、0x22 等到 0xFF 的范围。应该不难做。
下一步是将图像颜色与调色板颜色进行匹配,并创建这些值的字节数组。 stackoverflow 上已经有多种方法可以获取最接近的匹配。这个问题有很多:
如何比较 Color 对象并获得 Color[] 中最接近的 Color?
接下来是棘手的部分:将实际图像数据转换为 4 位。
要记住的一件事是,图像是按行保存的,这样的线(称为“扫描线”)不一定与图像的宽度相同。例如,在每像素 4 位中,每个字节可以容纳 2 个像素,因此从逻辑上讲,步长是宽度除以 2。但是,如果宽度是奇数,则每行末尾都会有一个字节,即只填了一半。系统不会将下一行的第一个像素放在那里;相反,它只是留空。对于 8 位甚至 16 位图像,我知道步幅通常会将扫描线对齐到 4 字节的倍数。因此,永远不要假设宽度与扫描线长度相同。
对于我在此回复中进一步放置的功能,我使用所需的最小扫描线长度。由于这只是宽度乘以位长度除以八,如果该除法中有余数则加一,因此可以轻松计算为
((bpp * width) + 7) / 8
。现在,如果您生成了灰度调色板,然后创建了一个包含图像上每个像素最接近的调色板值的字节数组,则您可以将所有值提供给实际的 8 位到 4 位转换函数。
我编写了一个函数来将 8 位数据转换为任何给定的位长度。因此,您的 4 位图像需要
bitsLength=4
。BigEndian 参数将决定一个字节内的值是否交换。我不确定这里的 .Net 图像,但我知道很多 1BPP 格式使用大端位,而我遇到过以最低半字节开头的 4BPP 格式。
下一步是制作正确尺寸和像素格式的图像,在内存中打开其支持数组,并将数据转储到其中。 16 色图像的像素格式为 PixelFormat.Format4bppIndexed。
This is actually not hard at all, once you got the toolsets, and I built up quite a few of those. The things you need are:
The palette is easy. Gray values are colors with the same value for red, green and blue, and for equal brightness steps between colours on 16 colour, that value is just the range from 0x00, 0x11, 0x22 etc up to 0xFF. Shouldn't be hard to make.
The next step is matching the image colours to the palette colours, and making a byte array of these values. There are several methods for getting the closest match available on stackoverflow already. This question has a bunch of them:
How to compare Color object and get closest Color in an Color[]?
Next comes the tricky part: converting the actual image data to 4-bit.
One thing to keep in mind is that images are saved per line, and such a line (called a "scanline") isn't necessarily the same width as the image. For example, in 4 bits per pixel, you can fit 2 pixels in each byte, so logically, the stride is width divided by 2. However, if the width is an uneven number, each line will have a byte at the end that is only half-filled. The systems do not put the first pixel of the next line in there; instead it's simply left blank. And for 8-bit or even 16-bit images I know the stride often aligns the scanlines to multiple of 4 bytes. So never assume the width is the same as the scanline length.
For the function I put further down in this reply I use the minimum needed scanline length. Since this is just the width times times the bits length divided by eight, plus one if there was a remainder in that division, it can easily be calculated as
((bpp * width) + 7) / 8
.Now, if you generated your greyscale palette, and then made a byte array containing the nearest palette value for each pixel on the image, you have all values to feed to the actual 8-bit to 4-bit conversion function.
I wrote a function to convert 8-bit data to any given bits length. So this would need
bitsLength=4
for your 4-bit image.The BigEndian parameter will determine whether the values inside one byte are switched or not. I'm not sure about .Net images here, but I know a lot of 1BPP formats use big-endian bits, while I've encountered 4BPP formats that started with the lowest nibble instead.
The next step is to make an image of the correct dimensions and pixel format, open its backing array in memory, and dump your data into it. The pixel format for a 16 colour image is
PixelFormat.Format4bppIndexed
.