Как рассчитать средние значения цвета rgb растрового изображения
В моем приложении С# (3.5) мне нужно получить средние значения цвета для красных, зеленых и синих каналов растрового изображения. Предпочтительно без использования внешней библиотеки. Это можно сделать? Если да, то как? Спасибо заранее.
Попытка сделать вещи немного точнее: каждый пиксель в растровом изображении имеет определенное значение цвета RGB. Я хотел бы получить средние значения RGB для всех пикселей на изображении.
Ответы
Ответ 1
Самый быстрый способ - использовать небезопасный код:
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
Ответ 2
Здесь гораздо проще:
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-пиксельный битмап. Значения RGB этого 1 пикселя будут представлять средние значения для всего оригинала. GetPixel относительно медленный, но только тогда, когда вы используете его на большом растровом изображении по пикселям. Вызов его один раз здесь не biggie.
Использование LockBits действительно быстро, но некоторые пользователи Windows имеют политики безопасности, которые предотвращают выполнение "небезопасного" кода. Я упоминаю об этом, потому что этот факт только немного меня поместил сзади.
Обновление: с InterpolationMode, установленным в HighQualityBicubic, этот метод занимает примерно в два раза больше, чем усреднение с помощью LockBits; с HighQualityBilinear, он занимает немного больше, чем LockBits. Поэтому, если у ваших пользователей нет политики безопасности, которая запрещает код unsafe
, определенно не используйте мой метод.
Update 2: с течением времени, теперь я понимаю, почему этот подход не работает вообще. Даже самые высококачественные алгоритмы интерполяции включают только несколько соседних пикселей, поэтому существует ограничение на то, как можно сжать изображение без потери информации. И раздавливание изображения до одного пикселя значительно превышает этот предел, независимо от того, какой алгоритм вы используете.
Единственный способ сделать это - сжать изображение поэтапно (возможно, сжимать его пополам каждый раз), пока вы не уменьшите его до размера одного пикселя. Я не могу выразить словами, что будет пустой тратой времени, написав что-то подобное, поэтому я рад, что остановился, когда подумал об этом.:)
Пожалуйста, никто больше не голосует за этот ответ - возможно, это была моя самая глупая идея.
Ответ 3
Этот вид работы будет работать, но он может быть недостаточно быстрым, чтобы быть полезным.
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);
}