图形在索引图像上的应用

45

我遇到了以下错误:

"无法从具有索引像素格式的图像创建 Graphics 对象。"

发生在以下函数中:

public static void AdjustImage(ImageAttributes imageAttributes, Image image)
{
        Rectangle rect = new Rectangle(0, 0, image.Width, image.Height);

        Graphics g = Graphics.FromImage(image);       
        g.InterpolationMode = InterpolationMode.HighQualityBicubic;
        g.DrawImage(image, rect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, imageAttributes);
        g.Dispose();
}
我想询问你,我该如何修复它?
4个回答

50

参考这里的内容,可以通过创建一个与正确像素格式和相同尺寸的空位图,然后在该位图上进行绘制来解决问题。

// The original bitmap with the wrong pixel format. 
// You can check the pixel format with originalBmp.PixelFormat
Bitmap originalBmp = (Bitmap)Image.FromFile("YourFileName.gif");

// Create a blank bitmap with the same dimensions
Bitmap tempBitmap = new Bitmap(originalBmp.Width, originalBmp.Height);

// From this bitmap, the graphics can be obtained, because it has the right PixelFormat
using(Graphics g = Graphics.FromImage(tempBitmap))
{
    // Draw the original bitmap onto the graphics of the new bitmap
    g.DrawImage(originalBmp, 0, 0);
    // Use g to do whatever you like
    g.DrawLine(...);
}

// Use tempBitmap as you would have used originalBmp embedded in it
return tempBitmap;

8
但它不会写在原始图像上。它会创建一张空白图像并在上面书写。因此,最终数据不会被写入到原始图像上。 - Banketeshvar Narayan
4
这段代码需要再加入一些代码,以在画布上绘制原始图像。具体实现方式如下:Dim rect As New System.Drawing.Rectangle(0, 0, bm.width, bm.height) : g.DrawImage(bm, rect, 0, 0, bm.width, bm.Height, GraphicsUnit.Pixel) - rg89
4
这能起作用的唯一原因是它将新图像变成了高色彩。但这并不能让你在8位图像上进行绘制... - Nyerguds
1
我建议在位图源是索引格式(如灰度)时避免使用Graphic对象。最好使用Clone创建新的位图。Bitmap Clone(Rectangle rect, PixelFormat format) - Mauro Raymondi
2
不幸的是,正如指出的那样,这并不允许在原始图像上使用图形及其原始颜色空间。因此,如果您有一个单色(1bpp)高分辨率图像(例如70k x 40k像素),内存大小将会飙升... - Oak_3260548
显示剩余2条评论

16

最简单的方法是创建一个像这样的新图像:

Bitmap EditableImg = new Bitmap(IndexedImg);

它创建一个和原始图像完全相同的新图像,包括所有内容。


什么是 IndexedImg - AaA
如果索引像素图像的dpi高于默认值,则嵌入在可编辑图像的右上角的可编辑图像要小得多。 - SimonKravis
4
“with all its contents”... except the original colour palette. 这创建了一个32bppARGB图像。实际上它不允许编辑8位图像。 - Nyerguds
减小tiff文件的DPI,我们能否在不改变DPI的情况下完成? - sandeep sharma
@sandeepsharma 只需添加一行代码,将新图像对象的dpi设置为旧图像对象的dpi即可。im2.SetResolution(im1.HorizontalResolution, im1.VerticalResolution); - Nyerguds
确切的解决方案能够快速、轻松地达到所期望的结果。谢谢! - undefined

3
总体而言,如果你想要处理索引图像并保留其色深和调色板,这就意味着你需要编写专门的代码并进行显式检查。因为 Graphics 无法使用它们,因为它操作颜色,而索引图像的实际像素不包含任何颜色,只有索引。
对于那些多年后仍看到此信息的人...在现有(8位)索引图像上绘制图像的有效方法如下:
- 遍历要粘贴的图像的所有像素,并为每种颜色找到目标图像颜色调色板中最接近的匹配项,并将其索引保存到字节数组中。 - 使用LockBits打开索引图像的支持字节数组,并通过循环遍历相关索引来将匹配的字节粘贴到所需位置。
这不是一项容易的任务,但肯定是可行的。如果要粘贴的图像也是索引的,并且包含超过256个像素,则可以加快处理速度,方法是在调色板上进行颜色匹配而不是在实际图像数据上进行匹配,然后从另一个索引图像获取支持字节,并使用创建的映射重新映射它们。
请注意,所有这些仅适用于8位。如果您的图像为4位或1位,则处理它的最简单方法是先将其转换为8位,以便您可以将其视为每个像素一个字节进行处理,然后再进行转换。
更多信息,请参见如何处理1位和4位图像?

3

虽然被接受的答案起作用了,但是它从索引位图创建了一个新的32bpp ARGB图像。

为了直接操作索引位图,您可以使用this库(提示:无耻自我推广)。它的GetReadWriteBitmapData扩展允许即使对于索引像素格式也可以创建可写的托管访问器。

然后,您可以使用其中之一DrawInto方法,这些方法可以类似于Graphics.DrawImage进行使用。当然,由于目标位图是索引的,因此绘制操作必须使用目标调色板颜色量化像素,但是有一种重载形式可以使用抖动来保留更多的图像细节。

用法示例(请参见上面的链接以获取更多示例):

using (IReadWriteBitmapData indexedTarget = myIndexedBitmap.GetReadWriteBitmapData())
using (IReadableBitmapData source = someTrueColorBitmap.GetReadableBitmapData())
{
    // or DrawIntoAsync if you want to use async-await
    source.DrawInto(indexedTarget, targetRect, OrderedDitherer.Bayer8x8);
}

图像示例:

以下所有图像均使用PixelFormat.Format8bppIndexed格式和默认调色板创建,并在彼此之上绘制了一个256x256的图标和一个阿尔法渐变彩虹。请注意,尽可能地使用可用调色板进行混合。

图片 描述
没有抖动的8bpp alpha渐变图标 无抖动
使用有序抖动的8bpp alpha渐变图标 有序Bayer8x8抖动
使用误差扩散抖动的8bpp alpha渐变图标 Floyd-Steinberg误差扩散抖动

免责声明:当然,与Graphics相比,该库也有一些限制,例如没有形状绘制方法。但在最坏的情况下,您仍然可以使用被接受的答案,然后在最后调用ConvertPixelFormat方法,如果您需要生成一个索引结果。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接