Повернуть оттенок с помощью ImageAttributes в C #

Как я могу повернуть оттенок изображения с помощью GDI + ImageAttributes (и, предположительно, ColorMatrix)?

Обратите внимание, что я хочу повернуть оттенок, а не подкрашивать изображение.

РЕДАКТИРОВАТЬ: поворачивая оттенок, я имею в виду, что каждый цвет в изображении должен быть сдвинут на другой цвет, в отличие от того, чтобы сделать все изображение оттенком одного цвета.

Например,

Оригинал: http://www.codeguru.com/img/legacy/gdi/Tinter03.jpg

Повернуто: http://www.codeguru.com/img/legacy/gdi/Tinter15.jpg или http://www.codeguru.com/img/legacy/gdi/Tinter17.jpg


person SLaks    schedule 03.07.2009    source источник
comment
Связанные изображения больше не существуют. В идеале вы должны вставлять изображения в вопрос здесь, в StackOverflow, вместо того, чтобы ссылаться на них в другом месте.   -  person ChrisW    schedule 09.07.2018


Ответы (8)


Я собрал это вместе для этого вопроса (ZIP-файл с проектом C #, ссылка на который приведена в нижней части сообщения). Он не использует ImageAttributes или ColorMatrix, но меняет оттенок, как вы описали:

//rotate hue for a pixel
private Color CalculateHueChange(Color oldColor, float hue)
{
    HLSRGB color = new HLSRGB(
        Convert.ToByte(oldColor.R),
        Convert.ToByte(oldColor.G),
        Convert.ToByte(oldColor.B));

    float startHue = color.Hue;
    color.Hue = startHue + hue;
    return color.Color;
}
person Rex M    schedule 03.07.2009

В итоге я перенес QColorMatrix на C # и использовал его RotateHue метод.

person SLaks    schedule 25.08.2009

Вы видели эту статью о CodeProject?

По общему признанию, беглый взгляд на страницу выглядит как четырехмерная математика. Вы можете использовать тот же подход к построению матриц, что и для 2D или 3D математики.

Возьмите серию исходных «точек» - в этом случае вам понадобится 4 - и соответствующие целевые «точки» и сгенерируйте матрицу. Затем это можно применить к любой «точке».

Чтобы сделать это в 2D (по памяти, так что я мог бы сделать из этого полноценного ревуна):

Исходные точки: (1, 0) и (0, 1). Цели: (0, -1) и (1,0). Вам понадобится матрица:

(0, -1, 0)
(1,  0, 0)
(0,  0, 1)

Где дополнительная информация предназначена для значения координаты "w".

Расширьте это до {R, G, B, A, w}, и вы получите матрицу. Возьмите 4 цвета: красный (1, 0, 0, 0, w), зеленый (0, 1, 0, 0, w), синий (0, 0, 1, 0, w) и прозрачный (0, 0, 0, 1, з). Определите, какие цвета они отображают в новой схеме, и постройте матрицу следующим образом:

(R 1, G 1, B 1, A 1, 0)
(R 2, G 2, B 2, A 2, 0)
(R 3 , G 3, B 3, A 3, 0)
(R 4, G 4, B 4, A 4, 0)
(0, 0, 0, 0, 1)

ПРИМЕЧАНИЕ. Порядок умножения (вектор * матрица или матрица * вектор) определяет, будут ли преобразованные точки переходить в эту матрицу по вертикали или горизонтали, поскольку умножение матриц некоммутативно. Я предполагаю вектор * матрицу.

person ChrisF    schedule 03.07.2009

Поскольку stackoverflow - это wikipedia + reddit, я хотел сохранить эти знания, пока они не были потеряны навсегда.

Матричные операции для обработки изображений

Пол Хэберли
Ноябрь 1993 г.


Введение

Матрицы четыре на четыре обычно используются для преобразования геометрии для 3D-рендеринга. Эти матрицы также могут использоваться для преобразования цветов RGB, для масштабирования цветов RGB и для управления оттенком, насыщенностью и контрастом. Наиболее важным преимуществом использования матриц является то, что любое количество преобразований цвета может быть составлено с использованием стандартного умножения матриц.

Обратите внимание, что для того, чтобы эти операции были правильными, мы действительно должны работать с линейными значениями яркости. Если входное изображение находится в нелинейном пространстве яркости, цвета RGB должны быть преобразованы в линейное пространство перед использованием этих матричных операций.

Преобразование цвета

Цвета RGB преобразуются матрицей четыре на четыре, как показано здесь:

xformrgb(mat, r, g, b, tr, tg, tb)
float mat[4][4];
float r,g,b;
float *tr,*tg,*tb;
{
    *tr = r*mat[0][0] + g*mat[1][0] + b*mat[2][0] + mat[3][0];
    *tg = r*mat[0][1] + g*mat[1][1] + b*mat[2][1] + mat[3][1];
    *tb = r*mat[0][2] + g*mat[1][2] + b*mat[2][2] + mat[3][2];
}

Личность

Это единичная матрица:

float mat[4][4] = {
    1.0,    0.0,    0.0,    0.0,
    0.0,    1.0,    0.0,    0.0,
    0.0,    0.0,    1.0,    0.0,
    0.0,    0.0,    0.0,    1.0,
};

Преобразование цветов с помощью матрицы идентичности оставит их неизменными.

Изменение яркости

Для масштабирования цветов RGB используется такая матрица:

float mat[4][4] = {
    rscale, 0.0,    0.0,    0.0,
    0.0,    gscale, 0.0,    0.0,
    0.0,    0.0,    bscale, 0.0,
    0.0,    0.0,    0.0,    1.0,
};

Где rscale, gscale и bscale указывают, насколько масштабировать компоненты цветов r, g и b. Это можно использовать для изменения цветового баланса изображения.

Фактически, это вычисляет:

tr = r*rscale;
tg = g*gscale;
tb = b*bscale;

Преобразование в яркость

Для преобразования цветного изображения в черно-белое изображение используется такая матрица:

float mat[4][4] = {
    rwgt,   rwgt,   rwgt,   0.0,
    gwgt,   gwgt,   gwgt,   0.0,
    bwgt,   bwgt,   bwgt,   0.0,
    0.0,    0.0,    0.0,    1.0,
};

Где

  • rwgt составляет 0,3086
  • gwgt - 0.6094
  • bwgt составляет 0,0820

Это вектор яркости. Обратите внимание, что мы не используем стандартные веса NTSC 0,299, 0,587 и 0,114. Веса NTSC применимы только к цветам RGB в цветовом пространстве гамма 2.2. Для линейных цветов RGB лучше использовать значения, указанные выше.

Фактически, это вычисляет:

tr = r*rwgt + g*gwgt + b*bwgt;
tg = r*rwgt + g*gwgt + b*bwgt;
tb = r*rwgt + g*gwgt + b*bwgt;

Изменение насыщенности

Для насыщения цветов RGB используется такая матрица:

 float mat[4][4] = {
    a,      b,      c,      0.0,
    d,      e,      f,      0.0,
    g,      h,      i,      0.0,
    0.0,    0.0,    0.0,    1.0,
};

Если константы получены из значения насыщенности s, как показано ниже:

a = (1.0-s)*rwgt + s;
b = (1.0-s)*rwgt;
c = (1.0-s)*rwgt;
d = (1.0-s)*gwgt;
e = (1.0-s)*gwgt + s;
f = (1.0-s)*gwgt;
g = (1.0-s)*bwgt;
h = (1.0-s)*bwgt;
i = (1.0-s)*bwgt + s;

Одним из хороших свойств этой матрицы насыщенности является то, что сохраняется яркость входных цветов RGB. Эту матрицу также можно использовать для дополнения цветов изображения, задав значение насыщенности -1,0.

Обратите внимание, что когда s установлено в 0,0, матрица в точности преобразована в матрицу яркости, описанную выше. Когда s установлено в 1.0, матрица становится тождественной. Все матрицы насыщения могут быть получены путем интерполяции между этими двумя матрицами или экстраполяции за их пределы.

Более подробно это обсуждается в примечании на Обработка изображений с помощью интерполяции и экстраполяции.

Применение смещений к цветовым компонентам

Эта матрица используется для смещения компонентов цветов r, g и b в изображении:

float mat[4][4] = {
    1.0,    0.0,    0.0,    0.0,
    0.0,    1.0,    0.0,    0.0,
    0.0,    0.0,    1.0,    0.0,
    roffset,goffset,boffset,1.0,
};

Это можно использовать вместе с масштабированием цвета для изменения контрастности изображений RGB.

Простое вращение оттенка

Чтобы повернуть оттенок, мы выполняем трехмерное вращение цветов RGB вокруг диагонального вектора [1.0 1.0 1.0]. Матрица преобразования выводится, как показано здесь:

Если у нас есть функции:

  • identmat(mat): это создает единичную матрицу.
  • xrotatemat(mat,rsin,rcos): умножает матрицу, вращающуюся вокруг оси x (красной).
  • yrotatemat(mat,rsin,rcos): умножает матрицу, вращающуюся вокруг оси y (зеленая).
  • zrotatemat(mat,rsin,rcos): умножает матрицу, вращающуюся вокруг оси z (синей).

Тогда матрица, которая вращается вокруг диагонали 1.0, 1.0, 1.0, может быть построена следующим образом:

Сначала делаем единичную матрицу

identmat(mat);

Поверните серый вектор на положительный Z

mag = sqrt(2.0);
xrs = 1.0/mag;
xrc = 1.0/mag;
xrotatemat(mat, xrs, xrc);

mag = sqrt(3.0);
yrs = -1.0/mag;
yrc = sqrt(2.0)/mag;
yrotatemat(mat, yrs, yrc);

Поверните оттенок

zrs = sin(rot*PI/180.0);
zrc = cos(rot*PI/180.0);
zrotatemat(mat, zrs, zrc);

Верните серый вектор на место

yrotatemat(mat, -yrs, yrc);
xrotatemat(mat, -xrs, xrc);

Результирующая матрица будет вращать оттенок входных цветов RGB. Поворот на 120,0 градусов точно преобразует красный в зеленый, зеленый в синий и синий в красный. У этого преобразования есть одна проблема, однако яркость входных цветов не сохраняется. Это можно исправить с помощью следующего уточнения:

Вращение оттенка с сохранением яркости

Составляем единичную матрицу

идентмат (ммат);

Поверните серый вектор на положительный Z

mag = sqrt(2.0);
xrs = 1.0/mag;
xrc = 1.0/mag;
xrotatemat(mmat, xrs, xrc);
mag = sqrt(3.0);
yrs = -1.0/mag;
yrc = sqrt(2.0)/mag;
yrotatemat(mmat, yrs, yrc);
matrixmult(mmat, mat, mat);

Срежьте пространство, чтобы плоскость яркости стала горизонтальной.

xformrgb(mmat,rwgt,gwgt,bwgt,&lx;,&ly;,&lz;);
zsx = lx/lz;
zsy = ly/lz;
zshearmat(mat,zsx,zsy);

Поверните оттенок

zrs = sin(rot*PI/180.0);
zrc = cos(rot*PI/180.0);
zrotatemat(mat,zrs,zrc);

Снимите пространство, чтобы вернуть плоскость яркости

zshearmat(mat,-zsx,-zsy);

Верните серый вектор на место

yrotatemat(mat, -yrs, yrc);
xrotatemat(mat, -xrs, xrc);

Вывод

Я представил несколько матричных преобразований, которые можно применить к цветам RGB. Каждое преобразование цвета представлено матрицей 4 на 4, аналогичной матрицам, обычно используемым для преобразования трехмерной геометрии.

Эти преобразования позволяют индивидуально настраивать контраст, яркость, оттенок и насыщенность изображения. Кроме того, преобразования цветовой матрицы сцепляются аналогично геометрическим преобразованиям. Любую последовательность операций можно объединить в единую матрицу с помощью умножения матриц.

person Ian Boyd    schedule 05.11.2020

Это старый вопрос, но опубликованные решения намного сложнее, чем простой ответ, который я нашел.

Простой:

  • нет внешней зависимости
  • без сложных вычислений (без определения угла поворота, без применения формулы косинуса)
  • на самом деле делает вращение цветов!

Повторяю проблему: что нам нужно?

Я подготовил иконки, тонированные в красный цвет. Некоторые области прозрачные, некоторые более или менее насыщенные, но все они имеют красный оттенок. Я думаю, это очень хорошо соответствует вашему варианту использования. Изображения могут быть другого цвета, они просто будут повернуты.

Как изобразить оттенок для нанесения? Самый простой ответ: поставьте Color.

Работа над решением

ColorMatrix представляют собой линейное преобразование.

Очевидно, что когда цвет красный, преобразование должно быть идентичным. Когда цвет зеленый, преобразование должно отображать красный цвет в зеленый, зеленый в синий, синий в красный.

ColorMatrix, который делает это:

0 1 0 0 0
0 0 1 0 0
1 0 0 0 0
0 0 0 1 0
0 0 0 0 1

Математическое решение

Уловка "Ага" состоит в том, чтобы распознать, что фактическая форма матрицы

R G B 0 0
B R G 0 0
G B R 0 0
0 0 0 1 0
0 0 0 0 1

где R, G и B - это просто составляющая тонирующего цвета!

Образец кода

Я взял пример кода на https://code.msdn.microsoft.com/ColorMatrix-Image-Filters-f6ed20ae.

Я скорректировал это и использую в своем проекте:

static class IconTinter
{
    internal static Bitmap TintedIcon(Image sourceImage, Color tintingColor)
    {
        // Following https://code.msdn.microsoft.com/ColorMatrix-Image-Filters-f6ed20ae
        Bitmap bmp32BppDest = new Bitmap(sourceImage.Width, sourceImage.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

        float cr = tintingColor.R / 255.0f;
        float cg = tintingColor.G / 255.0f;
        float cb = tintingColor.B / 255.0f;

        // See [Rotate Hue using ImageAttributes in C#](http://stackoverflow.com/a/26573948/1429390)
        ColorMatrix colorMatrix = new ColorMatrix(
            new float[][]
                       {new float[] { cr,  cg,  cb,  0,  0}, 
                        new float[] { cb,  cr,  cg,  0,  0}, 
                        new float[] { cg,  cb,  cr,  0,  0}, 
                        new float[] {  0,   0,   0,  1,  0}, 
                        new float[] {  0,   0,   0,  0,  1}
                       }
                       );

        using (Graphics graphics = Graphics.FromImage(bmp32BppDest))
        {
            ImageAttributes bmpAttributes = new ImageAttributes();
            bmpAttributes.SetColorMatrix(colorMatrix);

            graphics.DrawImage(sourceImage,
                new Rectangle(0, 0, sourceImage.Width, sourceImage.Height),
                0, 0,
                sourceImage.Width, sourceImage.Height,
                GraphicsUnit.Pixel, bmpAttributes);

        }

        return bmp32BppDest;
    }
}

Надеюсь это поможет.

Ограничение

  • Обратите внимание, что преобразование может стать насыщенным, если вы используете слишком яркие цвета. Чтобы гарантировать отсутствие насыщения, достаточно tt, чтобы R + G + B ‹= 1.
  • Вы можете нормализовать преобразование, разделив cr, cg, cb на cr + cg + cb, но обработайте случай, когда цвет тонировки черный, или вы разделите на ноль.
person Stéphane Gourichon    schedule 26.10.2014

Следующий код создает ColorMatrix для применения сдвига оттенка.

Я понял, что при сдвиге на 60 ° в цветовом пространстве:

  • красный -> зеленый
  • зеленый -> синий
  • синий -> красный

на самом деле это сдвиг на 45 ° в пространстве RGB:

введите описание изображения здесь

Таким образом, вы можете превратить некоторую долю сдвига на 120 ° в некоторую долю сдвига на 90 °.

Параметр HueShift должен иметь значение между -1..1.

Например, чтобы применить сдвиг на 60 °:

  • изменение красного на желтый,
  • изменение желтого на зеленый
  • изменение зеленого на голубой
  • изменение голубого на синий
  • изменение синего на пурпурный
  • изменение пурпурного на красный

ты звонишь:

var cm = GetHueShiftColorMatrix(60/360); //a value between 0..1

Реализация:

function GetHueShiftColorMatrix(HueShift: Real): TColorMatrix;
var
    theta: Real;
    c, s: Real;
const
    wedge = 120/360;
begin
    if HueShift > 1 then
        HueShift := 0
    else if HueShift < -1 then
        HueShift := 0
    else if Hueshift < 0 then
        HueShift := 1-HueShift;

    if (HueShift >= 0) and (HueShift <= wedge) then
    begin
        //Red..Green
        theta := HueShift / wedge*(pi/2);
        c := cos(theta);
        s := sin(theta);

        cm[0, 0] :=  c; cm[0, 1] :=  0; cm[0, 2] :=  s; cm[0, 3] :=  0; cm[0, 4] := 0;
        cm[1, 0] :=  s; cm[1, 1] :=  c; cm[1, 2] :=  0; cm[1, 3] :=  0; cm[1, 4] := 0;
        cm[2, 0] :=  0; cm[2, 1] :=  s; cm[2, 2] :=  c; cm[2, 3] :=  0; cm[2, 4] := 0;
        cm[3, 0] :=  0; cm[3, 1] :=  0; cm[3, 2] :=  0; cm[3, 3] :=  1; cm[3, 4] := 0;
        cm[4, 0] :=  0; cm[4, 1] :=  0; cm[4, 2] :=  0; cm[4, 3] :=  0; cm[4, 4] := 1;
    end
    else if (HueShift >= wedge) and (HueShift <= (2*wedge)) then
    begin
        //Green..Blue
        theta := (HueShift-wedge) / wedge*(pi/2);
        c := cos(theta);
        s := sin(theta);

        cm[0, 0] :=  0; cm[0, 1] :=  s; cm[0, 2] :=  c; cm[0, 3] :=  0; cm[0, 4] := 0;
        cm[1, 0] :=  c; cm[1, 1] :=  0; cm[1, 2] :=  s; cm[1, 3] :=  0; cm[1, 4] := 0;
        cm[2, 0] :=  s; cm[2, 1] :=  c; cm[2, 2] :=  0; cm[2, 3] :=  0; cm[2, 4] := 0;
        cm[3, 0] :=  0; cm[3, 1] :=  0; cm[3, 2] :=  0; cm[3, 3] :=  1; cm[3, 4] := 0;
        cm[4, 0] :=  0; cm[4, 1] :=  0; cm[4, 2] :=  0; cm[4, 3] :=  0; cm[4, 4] := 1;
    end
    else
    begin
        //Blue..Red
        theta := (HueShift-2*wedge) / wedge*(pi/2);
        c := cos(theta);
        s := sin(theta);

        cm[0, 0] :=  s; cm[0, 1] :=  c; cm[0, 2] :=  0; cm[0, 3] :=  0; cm[0, 4] := 0;
        cm[1, 0] :=  0; cm[1, 1] :=  s; cm[1, 2] :=  c; cm[1, 3] :=  0; cm[1, 4] := 0;
        cm[2, 0] :=  c; cm[2, 1] :=  0; cm[2, 2] :=  s; cm[2, 3] :=  0; cm[2, 4] := 0;
        cm[3, 0] :=  0; cm[3, 1] :=  0; cm[3, 2] :=  0; cm[3, 3] :=  1; cm[3, 4] := 0;
        cm[4, 0] :=  0; cm[4, 1] :=  0; cm[4, 2] :=  0; cm[4, 3] :=  0; cm[4, 4] := 1;
    end;

    Result := cm;
end;

Примечание. Любой код является общедоступным. Ссылка на авторство не требуется.

person Ian Boyd    schedule 09.12.2013

Я создаю метод, реализующий код @IanBoid на языке C #.

    public void setHueRotate(Bitmap bmpElement, float value) {

        const float wedge = 120f / 360;

        var hueDegree = -value % 1;
        if (hueDegree < 0) hueDegree += 1;

        var matrix = new float[5][];

        if (hueDegree <= wedge)
        {
            //Red..Green
            var theta = hueDegree / wedge * (Math.PI / 2);
            var c = (float)Math.Cos(theta);
            var s = (float)Math.Sin(theta);

            matrix[0] = new float[] { c, 0, s, 0, 0 };
            matrix[1] = new float[] { s, c, 0, 0, 0 };
            matrix[2] = new float[] { 0, s, c, 0, 0 };
            matrix[3] = new float[] { 0, 0, 0, 1, 0 };
            matrix[4] = new float[] { 0, 0, 0, 0, 1 };

        } else if (hueDegree <= wedge * 2)
        {
            //Green..Blue
            var theta = (hueDegree - wedge) / wedge * (Math.PI / 2);
            var c = (float) Math.Cos(theta);
            var s = (float) Math.Sin(theta);

            matrix[0] = new float[] {0, s, c, 0, 0};
            matrix[1] = new float[] {c, 0, s, 0, 0};
            matrix[2] = new float[] {s, c, 0, 0, 0};
            matrix[3] = new float[] {0, 0, 0, 1, 0};
            matrix[4] = new float[] {0, 0, 0, 0, 1};

        }
        else
        {
            //Blue..Red
            var theta = (hueDegree - 2 * wedge) / wedge * (Math.PI / 2);
            var c = (float)Math.Cos(theta);
            var s = (float)Math.Sin(theta);

            matrix[0] = new float[] {s, c, 0, 0, 0};
            matrix[1] = new float[] {0, s, c, 0, 0};
            matrix[2] = new float[] {c, 0, s, 0, 0};
            matrix[3] = new float[] {0, 0, 0, 1, 0};
            matrix[4] = new float[] {0, 0, 0, 0, 1};
        }

        Bitmap originalImage = bmpElement;

        var imageAttributes = new ImageAttributes();
        imageAttributes.ClearColorMatrix();
        imageAttributes.SetColorMatrix(new ColorMatrix(matrix), ColorMatrixFlag.Default, ColorAdjustType.Bitmap);

        grpElement.DrawImage(
            originalImage, elementArea,
            0, 0, originalImage.Width, originalImage.Height,
            GraphicsUnit.Pixel, imageAttributes
            );
    }
person riofly    schedule 07.05.2016

Я полагаю, что www.aforgenet.com может помочь

person Bogdan_Ch    schedule 03.07.2009