65.9K
CodeProject 正在变化。 阅读更多。
Home

图像逐像素枚举、像素格式转换及更多

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (25投票s)

2010年3月18日

CPOL

7分钟阅读

viewsIcon

90930

downloadIcon

3586

一组方便的扩展方法,可帮助您快速修改图像。

介绍  

本文将介绍一个通用的图像(C# 类)枚举器,它可以让您的生活变得更加轻松。它不基于 SetPixel/GetPixel 方法,因此速度快,但在 C# 托管环境中仍然安全。此类别可以帮助您读取或写入所有像素,同时您将有机会根据需要修改像素颜色。您有足够的空间来扩展此枚举器,并为自己制作一个——例如——快速转换为灰度的方法。可能性仅受您的想象力限制。

版本 2.0 与文章《一个简单而强大的调色板量化器》中的版本 5.0(有点)同步。它包含更多选项、速度优化、并行处理等等。快去看看吧!

免责声明:版本 2.0 使本文描述的大部分接口和 API 过时。如果您无法处理版本 2.0,则应使用版本 1.0。或者在下面的论坛中提问。我只包含了版本 2.0 示例。

ImagePixelEnumeratorVS2008

背景

我正在开发图像编辑器,我需要某种方式将图像从剪贴板粘贴到我的应用程序中。但不幸的是,我的编辑器将处理多种像素格式(实际上是很多),并且从剪贴板来的图像都是各种不同的格式。所以我需要一种方法来将许多不同的格式转换为我的许多格式。于是我决定编写一个通用的像素格式转换器。我琢磨着,实现这一点的最简单方法是,无论格式如何,以某种方式枚举所有像素。然后简单地从源图像中读取一个像素并将其粘贴到目标图像中。所以我正是这样做的。我在网上(当我搜索这种东西时)看到许多人试图简单地解析图像,或者解析它并对每个像素进行细微修改。因此,我准备了一些扩展方法,它们将帮助您快速实现目标,而无需研究 Microsoft 像素格式和其他沿途的干扰。

Using the Code

好处在于 System.Drawing.Image 类的扩展方法形式,因此一旦引用,它们将作为任何 Image 实例的标准方法出现。此外,还有一个 Pixel 类,它完成所有像素读取/写入工作。它包含任何像素格式的颜色值。我将在下面描述所有这些方法。

列表

以下是扩展方法的列表,其中一些只是从 Bitmap 类到 Image 类使用的桥梁。

  • BitmapData LockBits(this Image image, ImageLockMode lockMode)
  • void UnlockBits(this Image image, BitmapData data)
  • IEnumerable>Pixel< EnumerateImagePixels(this Image image, ImageLockMode accessMode)
  • Image ChangePixelFormat(this Image image, 
    	PixelFormat targetFormat, IColorQuantizer quantizer)
  • void AddColorsToQuantizer(this Image image, IColorQuantizer quantizer)
  • List<Color> GetPalette(this Image image)
  • void SetPalette(this Image image, List<Color> palette)

LockBits() 和 UnlockBits() 方法

这些方法与 Bitmap.LockBits()Bitmap.UnlockBits() 方法的功能相同。只是它们是在图像上提供的,并且它们还以 Image.PixelFormat 格式锁定数据。LockBits() 方法返回锁定的图像数据,必须通过 UnlockBits() 方法解锁才能使用。即使您只是从图像中读取,也必须解锁它。这两个方法主要用于内部使用,但仍然提供。所以希望您根本不需要这些方法。

EnumerateImagePixels() 方法

此方法是本文的核心方法。它允许您——根据提供的访问模式(ImageLockMode)——读取、写入或同时进行读写。只需提供一个图像,并通过一个结构 Pixel 在一个简单的循环中处理其所有像素。

foreach (Pixel pixel in myImage.EnumerateImagePixels(ImageLockMode.ReadWrite))
{
   // modify a pixel here
}

无论源图像是 4 位索引格式还是 32 位真彩色格式,像素结构都可以处理它们。一旦我完成了这项工作,似乎可以更进一步,添加一个方法来在两种像素格式之间进行转换,现在应该很容易了。有关更多信息,请查看像素结构部分。这带来了我们的下一个方法,也是像素枚举的第一个成果。

ChangePixelFormats() 方法

此方法允许您更改给定图像的格式,只要图像处于支持的格式(您可以使用 IsSupported 方法检查),并且目标格式也受支持。它基本上枚举源图像像素以进行读取,而目标图像则枚举以进行写入。然后从源读取每个像素并将其设置到目标。在此过程中,它会根据需要进行转换,或者在深色格式的情况下,按原样复制。您需要提供一个 IColorQuantizer 用于从非索引格式到索引格式的转换。它将自动从图像中收集信息,并执行量化。将创建一个调色板并将其设置到目标图像上。用法很简单,如下所示

Image targetImage = sourceImage.ChangePixelFormat
	(PixelFormat.Format8bppIndexed, myQuantizer);

其余方法

这些是 AddColorsToQuantizer()GetPalette()SetPalette()。第一个方法允许您处理多个图像以拥有一个公共调色板。它也使用枚举。它将所有像素颜色添加到量化器中,以便所有这些图像以后使用。GetPalette() 方法将随后从量化器中检索统一的调色板。最后一个方法 SetPalette() 将此调色板设置到您喜欢的任何图像。请参阅上一篇关于调色板量化的文章(此处),以获取有关其工作原理的更详细信息。

PixelFormat 扩展方法

在此过程中,我还为 PixelFormat 枚举添加了一些扩展。GetBithDepth 方法确定每种格式像素所需的位计数。GetColorCount 让您知道该像素格式有多少种颜色可用。此方法仅适用于索引格式。格式是否为索引格式可以通过另一个方法 IsIndexed 确定,该方法对所有索引格式返回 True,对所有高色和真彩色格式返回 False。还有 IsSupported 方法,我的演示使用了它。它基本上是检查格式是否受 Microsoft 支持的列表,因为并非所有列出的格式都实际受支持(例如 16bppGrayscale)。如果 HasAlpha 返回 True,则像素格式中还包含透明颜色分量。最后一个方法 IsDeepColor 确定格式是否为非标准格式。也就是说,如果它每个颜色分量使用超过 8 位。这些通常是特殊模式,应谨慎处理。

像素结构

这个类在内部处理所有这些格式差异,而在外部则几乎像旧的 SetPixel/GetPixel 组合一样运行。它包含三对方法和三个属性。

GetColor、SetColor 和 Color

前两种方法用于读取和写入非索引格式的颜色值。Color 基本上只是封装了这两种方法。

GetIndex、SetIndex 和 Index

这些方法和属性也是如此。只是它们用于索引像素格式。要获取颜色,您需要首先从图像的调色板中检索它,如下所示

Color color = myImage.Palette.Entries[pixel.Index]

我可以想象一个方法,一个包装器,用于其中一些扩展方法,它将进一步自动化该过程,但我总是留出一些空间让您自己去解决。将事情保持在基本状态。

GetValue、SetValue 和 Value

最后,这些用于处理 48 位和 64 位格式,因为它们不幸地不适合标准的 Color 结构。

一个简单示例

(仅适用于版本 1.0)

为了向您展示此功能的基使用理念,我编写了一个示例程序,它获取一张图像并将其转换为灰度。目前我只介绍这些。在编写此编辑器时,我肯定会很快发布另一篇文章,因为会出现许多不寻常的事情。

using System;
using System.Drawing;
using System.Drawing.Imaging;
using Extensions;
using Extensions.Helpers;

namespace Sample
{
    // this sample only works for non-indexed format images, 
    // but can be easily extended to support them
    static class Program
    {
        static void Main()
        {
            // loads the source image
            Image image = Image.FromFile("Source.png");

            // converts it to a grayscale
            foreach (Pixel pixel in image.EnumerateImagePixels(ImageLockMode.ReadWrite))
            {
                Color color = pixel.GetColor();
                Int32 gray = (color.R + color.G + color.B)/3;
                Color outputColor = Color.FromArgb(color.A, gray, gray, gray);
                pixel.SetColor(outputColor);
            }

            // saves the converted image
            image.Save("Output.png", ImageFormat.Png);
        }
    }
}

一个不那么简单的示例 

(仅适用于版本 2.0)

我还包含了版本 2.0 的示例,它试图模仿版本 1.0 中的示例,但存档中的代码实际上包含带有“真实”灰度计算的扩展版本,正如 nb2 所指出的。
using System;
using System.Drawing;
using System.Drawing.Imaging;
using ImagePixelEnumerator.Helpers;

namespace Sample
{
    static class Program
    {
        static void Main()
        {
            // loads the source image
            Image image = Image.FromFile("Source.png");

            // prepares our target image
            Image targetImage;

            // this sample only works for non-indexed format images, 
            // actually the code inside the archive contains such 2-pass version
            ImageBuffer.TransformImagePerPixel(image, PixelFormat.Format32bppArgb, null, out targetImage, null, 4, (passIndex, sourcePixel, targetPixel) =>
            {
                Color color = sourcePixel.GetColor();
                Int32 gray = (color.R + color.G + color.B)/3;
                Color grayColor = Color.FromArgb(color.A, gray, gray, gray);
                targetPixel.SetColor(grayColor, null);
                return true;
            });

            // saves the converted image
            targetImage.Save("Output.png", ImageFormat.Png);
        }
    }
}

关注点

  • Microsoft 实际上(正如他们所描述的“有点”)只支持 48 位和 64 位像素格式中每个颜色分量的最初 13 位。
  • 您确实应该释放 IEnumerator<> 类。

相关文章

历史

  • 2013-06-20:版本 2.0 发布
  • 2013-06-20:添加了“一个不那么简单的示例”部分
  • 2010-03-18:发布了初始文章
© . All rights reserved.