如何计算位图的平均 RGB 颜色值

发布于 2024-07-25 17:09:44 字数 162 浏览 10 评论 0原文

在我的 C# (3.5) 应用程序中,我需要获取位图的红色、绿色和蓝色通道的平均颜色值。 最好不使用外部库。 这可以做到吗? 如果是这样,怎么办? 提前致谢。

尝试让事情变得更精确:位图中的每个像素都有一定的 RGB 颜色值。 我想获取图像中所有像素的平均 RGB 值。

In my C# (3.5) application I need to get the average color values for the red, green and blue channels of a bitmap. Preferably without using an external library. Can this be done? If so, how? Thanks in advance.

Trying to make things a little more precise: Each pixel in the bitmap has a certain RGB color value. I'd like to get the average RGB values for all pixels in the image.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

情愿 2024-08-01 17:09:44

最快的方法是使用不安全的代码:

BitmapData srcData = bm.LockBits(
            new Rectangle(0, 0, bm.Width, bm.Height), 
            ImageLockMode.ReadOnly, 
            PixelFormat.Format32bppArgb);

int stride = srcData.Stride;

IntPtr Scan0 = srcData.Scan0;

long[] totals = new long[] {0,0,0};

int width = bm.Width;
int height = bm.Height;

unsafe
{
  byte* p = (byte*) (void*) Scan0;

  for (int y = 0; y < height; y++)
  {
    for (int x = 0; x < width; x++)
    {
      for (int color = 0; color < 3; color++)
      {
        int idx = (y*stride) + x*4 + color;

        totals[color] += p[idx];
      }
    }
  }
}

int avgB = totals[0] / (width*height);
int avgG = totals[1] / (width*height);
int avgR = totals[2] / (width*height);

注意:我没有测试此代码...(我可能已经偷工减料)

此代码还假设 32 位图像。 对于 24 位图像。 将 x*4 更改为 x*3

The fastest way is by using unsafe code:

BitmapData srcData = bm.LockBits(
            new Rectangle(0, 0, bm.Width, bm.Height), 
            ImageLockMode.ReadOnly, 
            PixelFormat.Format32bppArgb);

int stride = srcData.Stride;

IntPtr Scan0 = srcData.Scan0;

long[] totals = new long[] {0,0,0};

int width = bm.Width;
int height = bm.Height;

unsafe
{
  byte* p = (byte*) (void*) Scan0;

  for (int y = 0; y < height; y++)
  {
    for (int x = 0; x < width; x++)
    {
      for (int color = 0; color < 3; color++)
      {
        int idx = (y*stride) + x*4 + color;

        totals[color] += p[idx];
      }
    }
  }
}

int avgB = totals[0] / (width*height);
int avgG = totals[1] / (width*height);
int avgR = totals[2] / (width*height);

Beware: I didn't test this code... (I may have cut some corners)

This code also asssumes a 32 bit image. For 24-bit images. Change the x*4 to x*3

残花月 2024-08-01 17:09:44

这是一种更简单的方法:

Bitmap bmp = new Bitmap(1, 1);
Bitmap orig = (Bitmap)Bitmap.FromFile("path");
using (Graphics g = Graphics.FromImage(bmp))
{
    // updated: the Interpolation mode needs to be set to 
    // HighQualityBilinear or HighQualityBicubic or this method
    // doesn't work at all.  With either setting, the results are
    // slightly different from the averaging method.
    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
    g.DrawImage(orig, new Rectangle(0, 0, 1, 1));
}
Color pixel = bmp.GetPixel(0, 0);
// pixel will contain average values for entire orig Bitmap
byte avgR = pixel.R; // etc.

基本上,您使用 DrawImage 将原始位图复制到 1 像素位图。 该 1 个像素的 RGB 值将代表整个原始图像的平均值。 GetPixel 相对较慢,但仅限于在大型位图上逐像素使用它时。 在这里调用一次没什么大不了的。

使用 LockBits 确实很快,但一些 Windows 用户具有防止执行“不安全”代码的安全策略。 我提到这一点是因为这个事实最近让我感到很不舒服。

更新:当 InterpolationMode 设置为 HighQualityBicubic 时,此方法所需的时间大约是使用 LockBits 平均的两倍; 使用 HighQualityBilinear,它只需要比 LockBits 稍长的时间。 因此,除非您的用户有禁止不安全代码的安全策略,否则绝对不要使用我的方法。

更新2:随着时间的推移,我现在意识到为什么这种方法根本不起作用。 即使是最高质量的插值算法也只包含几个相邻像素,因此在不丢失信息的情况下可以压缩图像的程度是有限的。 无论您使用什么算法,将图像压缩到一个像素都远远超出了这一限制。

做到这一点的唯一方法是逐步缩小图像(可能每次缩小一半),直到将其缩小到一个像素的大小。 我无法用言语来表达写这样的东西是多么浪费时间,所以我很高兴当我想到这一点时我阻止了自己。 :)

拜托,没有人再投票支持这个答案 - 这可能是我有史以来最愚蠢的想法。

Here's a much simpler way:

Bitmap bmp = new Bitmap(1, 1);
Bitmap orig = (Bitmap)Bitmap.FromFile("path");
using (Graphics g = Graphics.FromImage(bmp))
{
    // updated: the Interpolation mode needs to be set to 
    // HighQualityBilinear or HighQualityBicubic or this method
    // doesn't work at all.  With either setting, the results are
    // slightly different from the averaging method.
    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
    g.DrawImage(orig, new Rectangle(0, 0, 1, 1));
}
Color pixel = bmp.GetPixel(0, 0);
// pixel will contain average values for entire orig Bitmap
byte avgR = pixel.R; // etc.

Basically, you use DrawImage to copy the original Bitmap into a 1-pixel Bitmap. The RGB values of that 1 pixel will then represent the averages for the entire original. GetPixel is relatively slow, but only when you use it on a large Bitmap, pixel-by-pixel. Calling it once here is no biggie.

Using LockBits is indeed fast, but some Windows users have security policies that prevent the execution of "unsafe" code. I mention this because this fact just bit me on the behind recently.

Update: with InterpolationMode set to HighQualityBicubic, this method takes about twice as long as averaging with LockBits; with HighQualityBilinear, it takes only slightly longer than LockBits. So unless your users have a security policy that prohibits unsafe code, definitely don't use my method.

Update 2: with the passage of time, I now realize why this approach doesn't work at all. Even the highest-quality interpolation algorithms incorporate only a few neighboring pixels, so there's a limit to how much an image can be squashed down without losing information. And squashing a image down to one pixel is well beyond this limit, no matter what algorithm you use.

The only way to do this would be to shrink the image in steps (maybe shrinking it by half each time) until you get it down to the size of one pixel. I can't express in mere words what an utter waste of time writing something like this would be, so I'm glad I stopped myself when I thought of it. :)

Please, nobody vote for this answer any more - it might be my stupidest idea ever.

长伴 2024-08-01 17:09:44

这种事情会起作用,但它可能不够快而没有那么有用。

public static Color GetDominantColor(Bitmap bmp)
{

       //Used for tally
       int r = 0;
       int g = 0;
       int b = 0;

     int total = 0;

     for (int x = 0; x < bmp.Width; x++)
     {
          for (int y = 0; y < bmp.Height; y++)
          {
               Color clr = bmp.GetPixel(x, y);

               r += clr.R;
               g += clr.G;
               b += clr.B;

               total++;
          }
     }

     //Calculate average
     r /= total;
     g /= total;
     b /= total;

     return Color.FromArgb(r, g, b);
}

This kind of thing will work but it may not be fast enough to be that useful.

public static Color GetDominantColor(Bitmap bmp)
{

       //Used for tally
       int r = 0;
       int g = 0;
       int b = 0;

     int total = 0;

     for (int x = 0; x < bmp.Width; x++)
     {
          for (int y = 0; y < bmp.Height; y++)
          {
               Color clr = bmp.GetPixel(x, y);

               r += clr.R;
               g += clr.G;
               b += clr.B;

               total++;
          }
     }

     //Calculate average
     r /= total;
     g /= total;
     b /= total;

     return Color.FromArgb(r, g, b);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文