Контрастность изображения холста HTML5

Я писал программу обработки изображений, которая применяет эффекты с помощью обработки пикселей холста HTML5. Я выполнил манипуляции с пикселями Thresholding, Vintaging и ColorGradient, но, что невероятно, я не могу изменить контрастность изображения! Я пробовал несколько решений, но у меня всегда получается слишком много яркости изображения и меньше контрастного эффекта и я не планирую использовать какие-либо библиотеки Javascript, поскольку я пытаюсь добиться этих эффектов изначально.

Базовый код манипуляции с пикселями:

var data = imageData.data;
for (var i = 0; i < data.length; i += 4) {
 //Note: data[i], data[i+1], data[i+2] represent RGB respectively
data[i] = data[i];
data[i+1] = data[i+1];
data[i+2] = data[i+2];
}

Пример манипуляции с пикселями

Значения указаны в режиме RGB, что означает, что data [i] имеет красный цвет. Итак, если data [i] = data [i] * 2; яркость будет увеличена вдвое для красного канала этого пикселя. Пример:

var data = imageData.data;
for (var i = 0; i < data.length; i += 4) {
 //Note: data[i], data[i+1], data[i+2] represent RGB respectively
 //Increases brightness of RGB channel by 2
data[i] = data[i]*2;
data[i+1] = data[i+1]*2;
data[i+2] = data[i+2]*2;
}

* Примечание: я не прошу вас, ребята, завершить код! Это было бы просто одолжением! Я прошу алгоритм (даже псевдокод), который показывает, как возможна контрастность при манипуляции с пикселями! Я был бы рад, если бы кто-нибудь мог предоставить хороший алгоритм для контрастности изображения в холсте HTML5.


person Schahriar SaffarShargh    schedule 09.05.2012    source источник
comment
что вы от этого ожидаете? Вы вообще не меняете массив данных   -  person I82Much    schedule 09.05.2012
comment
Я думаю, он ожидает, что мы напишем это за него, что делает вопрос ужасным.   -  person karlphillip    schedule 09.05.2012
comment
Ставлю пиксели обратно на значение по умолчанию, отредактирую вопрос!   -  person Schahriar SaffarShargh    schedule 09.05.2012
comment
Проголосуйте за отказ от использования сторонних библиотек! Я тоже делаю нечто подобное! Не могли бы вы опубликовать формулу контраста с помощью JavaScript?   -  person Rutwick Gangurde    schedule 28.12.2012
comment
@RutwickGangurde Я добавил свое решение в качестве ответа! Удачи!   -  person Schahriar SaffarShargh    schedule 04.01.2013
comment
@SchahriarSfr Спасибо большое, дружище! Я добился контраста, используя формулу, которую я извлек из кода C # на SO!   -  person Rutwick Gangurde    schedule 04.01.2013


Ответы (7)


Более быстрый вариант (на основе подхода Эшера):

function contrastImage(imgData, contrast){  //input range [-100..100]
    var d = imgData.data;
    contrast = (contrast/100) + 1;  //convert to decimal & shift range: [0..2]
    var intercept = 128 * (1 - contrast);
    for(var i=0;i<d.length;i+=4){   //r,g,b,a
        d[i] = d[i]*contrast + intercept;
        d[i+1] = d[i+1]*contrast + intercept;
        d[i+2] = d[i+2]*contrast + intercept;
    }
    return imgData;
}

Вывод аналогичен приведенному ниже; эта версия математически такая же, но работает намного быстрее.


Оригинальный ответ

Вот упрощенная версия с объяснением подхода, уже обсуждавшегося (который был основан на эта статья ):

function contrastImage(imageData, contrast) {  // contrast as an integer percent  
    var data = imageData.data;  // original array modified, but canvas not updated
    contrast *= 2.55; // or *= 255 / 100; scale integer percent to full range
    var factor = (255 + contrast) / (255.01 - contrast);  //add .1 to avoid /0 error

    for(var i=0;i<data.length;i+=4)  //pixel values in 4-byte blocks (r,g,b,a)
    {
        data[i] = factor * (data[i] - 128) + 128;     //r value
        data[i+1] = factor * (data[i+1] - 128) + 128; //g value
        data[i+2] = factor * (data[i+2] - 128) + 128; //b value

    }
    return imageData;  //optional (e.g. for filter function chaining)
}

Примечания

  1. Я решил использовать contrast диапазон +/- 100 вместо исходного +/- 255. Процентное значение кажется более интуитивным для пользователей или программистов, которые не понимают основных концепций. Кроме того, мое использование всегда связано с элементами управления пользовательского интерфейса; диапазон от -100% до + 100% позволяет мне маркировать и связывать контрольное значение напрямую, вместо того, чтобы изменять или объяснять его.

  2. Этот алгоритм не включает проверку диапазона, хотя допустимые значения могут намного превышать допустимые range - это потому, что массив, лежащий в основе объекта ImageData, - это Uint8ClampedArray. Как объясняет MSDN, с Uint8ClampedArray диапазоном проверка выполняется за вас:

«если вы указали значение, выходящее за пределы диапазона [0,255], вместо него будет установлено 0 или 255».

использование

Обратите внимание, что, хотя основная формула довольно симметрична (допускает циклическое переключение), данные теряются на высоких уровнях фильтрации, поскольку пиксели допускают только целочисленные значения. Например, к тому времени, когда вы обесцвечиваете изображение до экстремальных уровней (> 95% или около того), все пиксели в основном имеют однородный средний серый цвет (в пределах нескольких цифр от среднего возможного значения 128). Повторное увеличение контрастности приводит к сглаживанию цветового диапазона.

Кроме того, порядок операций важен при применении нескольких регулировок контрастности - насыщенные значения быстро «выдуваются» (превышают ограниченное максимальное значение 255), что означает сильное насыщение, а затем снижение насыщенности приведет к более темному изображению в целом. Однако обесцвечивание, а затем насыщение не приводят к такой большой потере данных, потому что значения светлых и темных участков приглушаются, а не обрезаются (см. Объяснение ниже).

Вообще говоря, при применении нескольких фильтров лучше начинать каждую операцию с исходными данными и повторно применять каждую настройку по очереди, чем пытаться отменить предыдущее изменение - по крайней мере, для качества изображения. Скорость работы или другие требования могут по-разному зависеть от ситуации.

Примеры контраста мандрила

Пример кода:

function contrastImage(imageData, contrast) {  // contrast input as percent; range [-1..1]
    var data = imageData.data;  // Note: original dataset modified directly!
    contrast *= 255;
    var factor = (contrast + 255) / (255.01 - contrast);  //add .1 to avoid /0 error.

    for(var i=0;i<data.length;i+=4)
    {
        data[i] = factor * (data[i] - 128) + 128;
        data[i+1] = factor * (data[i+1] - 128) + 128;
        data[i+2] = factor * (data[i+2] - 128) + 128;
    }
    return imageData;  //optional (e.g. for filter function chaining)
}

$(document).ready(function(){
  var ctxOrigMinus100 = document.getElementById('canvOrigMinus100').getContext("2d");
  var ctxOrigMinus50 = document.getElementById('canvOrigMinus50').getContext("2d");
  var ctxOrig = document.getElementById('canvOrig').getContext("2d");
  var ctxOrigPlus50 = document.getElementById('canvOrigPlus50').getContext("2d");
  var ctxOrigPlus100 = document.getElementById('canvOrigPlus100').getContext("2d");
  
  var ctxRoundMinus90 = document.getElementById('canvRoundMinus90').getContext("2d");
  var ctxRoundMinus50 = document.getElementById('canvRoundMinus50').getContext("2d");
  var ctxRound0 = document.getElementById('canvRound0').getContext("2d");
  var ctxRoundPlus50 = document.getElementById('canvRoundPlus50').getContext("2d");
  var ctxRoundPlus90 = document.getElementById('canvRoundPlus90').getContext("2d");
  
  
  var img = new Image();
  img.onload = function() {
    //draw orig
    ctxOrig.drawImage(img, 0, 0, img.width, img.height, 0, 0, 100, 100); //100 = canvas width, height
    
    //reduce contrast
    var origBits = ctxOrig.getImageData(0, 0, 100, 100);
    contrastImage(origBits, -.98);
    ctxOrigMinus100.putImageData(origBits, 0, 0);
    
    var origBits = ctxOrig.getImageData(0, 0, 100, 100);
    contrastImage(origBits, -.5);
    ctxOrigMinus50.putImageData(origBits, 0, 0);
    
    // add contrast
    var origBits = ctxOrig.getImageData(0, 0, 100, 100);
    contrastImage(origBits, .5);
    ctxOrigPlus50.putImageData(origBits, 0, 0);
    
    var origBits = ctxOrig.getImageData(0, 0, 100, 100);
    contrastImage(origBits, .98);
    ctxOrigPlus100.putImageData(origBits, 0, 0);
    
    
    //round-trip, de-saturate first
    origBits = ctxOrig.getImageData(0, 0, 100, 100);
    contrastImage(origBits, -.98);
    contrastImage(origBits, .98);
    ctxRoundMinus90.putImageData(origBits, 0, 0);
    
    origBits = ctxOrig.getImageData(0, 0, 100, 100);
    contrastImage(origBits, -.5);
    contrastImage(origBits, .5);
    ctxRoundMinus50.putImageData(origBits, 0, 0);
    
    //do nothing 100 times
    origBits = ctxOrig.getImageData(0, 0, 100, 100);
    for(i=0;i<100;i++){
      contrastImage(origBits, 0);
    }
    ctxRound0.putImageData(origBits, 0, 0);
    
    //round-trip, saturate first
    origBits = ctxOrig.getImageData(0, 0, 100, 100);
    contrastImage(origBits, .5);
    contrastImage(origBits, -.5);
    ctxRoundPlus50.putImageData(origBits, 0, 0);
    
    origBits = ctxOrig.getImageData(0, 0, 100, 100);
    contrastImage(origBits, .98);
    contrastImage(origBits, -.98);
    ctxRoundPlus90.putImageData(origBits, 0, 0);
  };
  
  img.src = "data:image/png;base64,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";
  
});
canvas {width: 100px; height: 100px}
div {text-align:center; width:120px; float:left}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div>
  <canvas id="canvOrigMinus100" width="100" height="100"></canvas>
  -98%
</div>

<div>
  <canvas id="canvOrigMinus50" width="100" height="100"></canvas>
  -50%
</div>

<div>
  <canvas id="canvOrig" width="100" height="100"></canvas>
  Original
</div>

<div>
  <canvas id="canvOrigPlus50" width="100" height="100"></canvas>
  +50%
</div>

<div>
  <canvas id="canvOrigPlus100" width="100" height="100"></canvas>
  +98%
</div>

  <hr/>

<div style="clear:left">
  <canvas id="canvRoundMinus90" width="100" height="100"></canvas>
  Round-trip <br/> (-98%, +98%)
</div>

<div>
  <canvas id="canvRoundMinus50" width="100" height="100"></canvas>
  Round-trip <br/> (-50%, +50%)
</div>

<div>
  <canvas id="canvRound0" width="100" height="100"></canvas>
  Round-trip <br/> (0% 100x)
</div>

<div>
  <canvas id="canvRoundPlus50" width="100" height="100"></canvas>
  Round-trip <br/> (+50%, -50%)
</div>

<div>
  <canvas id="canvRoundPlus90" width="100" height="100"></canvas>
  Round-trip <br/> (+98%, -98%)
</div>

Объяснение

(Заявление об ограничении ответственности - я не специалист по изображениям или математик. Я пытаюсь дать здравое объяснение с минимальными техническими деталями. Ниже приведены некоторые размахивания руками, например 255 = 256, чтобы избежать проблем с индексацией, и 127,5 = 128 , для упрощения чисел.)

Поскольку для данного пикселя возможное количество ненулевых значений для цветового канала равно 255, среднее значение пикселя без контраста равно 128. (или 127, или 127,5, если хотите поспорить, но разница незначительна). Для целей этого объяснения величина «контраста» - это расстояние от текущего значения до среднего значения (128). Регулировка контрастности означает увеличение или уменьшение разницы между текущим значением и средним значением.

Таким образом, алгоритм решает следующую проблему:

  1. Выберите постоянный коэффициент для регулировки контрастности
  2. Для каждого цветового канала каждого пикселя масштабируйте «контраст» (расстояние от среднего) на этот постоянный коэффициент.

Или, как указано в спецификации CSS, просто выбрав наклон и точку пересечения линии:

<feFuncR type="linear" slope="[amount]" intercept="-(0.5 * [amount]) + 0.5"/>

Обратите внимание на термин type='linear'; мы выполняем линейную настройку контрастности в цветовом пространстве RGB, в отличие от функции квадратичного масштабирования , на основе яркости, или сопоставление гистограммы.

Если вы помните из класса геометрии, формула для линии - y=mx+b. y - это последнее значение, которое нам нужно, наклон m - это контраст (или factor), x - начальное значение пикселя, а b - точка пересечения оси Y (x = 0), которая сдвигает линию по вертикали. Напомним также, что, поскольку точка пересечения по оси Y не находится в начале координат (0,0), формула также может быть представлена ​​как y=m(x-a)+b, где a - смещение по оси x, смещающее линию по горизонтали.

Формула наклона прямой

Для наших целей этот график представляет входное значение (ось x) и результат (ось y). Мы уже знаем, что b, точка пересечения по оси Y (для m=0, без контраста) должно быть 128 (что мы можем сравнить с 0,5 из спецификации - 0,5 * полный диапазон 256 = 128). x - это наше исходное значение, поэтому все, что нам нужно, это вычислить наклон m и смещение по x a.

Во-первых, наклон m - это "подъем за пробегом", или (y2-y1)/(x2-x1), поэтому нам нужны 2 точки, которые, как известно, находятся на нужной линии. Чтобы найти эти точки, необходимо объединить несколько вещей:

  • Наша функция принимает форму графика с пересечением линий.
  • Пересечение оси Y находится на b = 128 - независимо от наклона (контраста).
  • Максимальное ожидаемое значение «y» - 255, а минимальное - 0.
  • Диапазон возможных значений 'x' - 256
  • Нейтральное значение всегда должно оставаться нейтральным: 128 => 128 независимо от наклона
  • Регулировка контрастности 0 не должна приводить к изменению между входом и выходом; то есть наклон 1: 1.

Взяв все это вместе, мы можем сделать вывод, что независимо от применяемого контраста (наклона) наша результирующая линия будет центрирована (и вращаться вокруг) 128,128. Поскольку наша точка пересечения по оси Y не равна нулю, точка пересечения по оси x также не равна нулю; мы знаем, что x-диапазон имеет ширину 256 и центрирован посередине, поэтому он должен быть смещен на половину возможного диапазона: 256/2 = 128.

Функция контрастности наклонов

Итак, теперь о y=m(x-a)+b, мы знаем все, кроме m. Напомним еще два важных момента из урока геометрии:

  • Линии имеют одинаковый уклон, даже если их расположение меняется; то есть m остается неизменным независимо от значений a и b.
  • Наклон линии можно найти, используя любые 2 точки на линии.

Чтобы упростить обсуждение наклона, давайте переместим начало координат в точку пересечения с осью x (-128) и на мгновение проигнорируем a и b. Наша исходная линия теперь будет разворачиваться через (0,0), и мы знаем, что вторая точка на линии лежит далеко от полного диапазона как x (вход), так и y (выход) в (255,255).

Мы позволим новой линии повернуться в точке (0,0), чтобы мы могли использовать это как одну из точек на новой линии, которая будет следовать за нашим окончательным наклоном контрастности m. Вторую точку можно определить, переместив текущий конец в (255,255) на некоторую величину; поскольку мы ограничены одним входом (contrast) и используем линейную функцию, эта вторая точка будет перемещаться одинаково в направлениях x и y на нашем графике.

«Регулировка

Координаты (x, y) 4 возможных новых точек будут 255 +/- contrast. Поскольку увеличение или уменьшение как x, так и y оставило бы нас на исходной линии 1: 1, давайте просто посмотрим на +x, -y и -x, +y, как показано.

Более крутая линия (-x, + y) связана с положительной contrast корректировкой; это координаты (x, y): (_48 _, _ 49_). Аналогичным образом определяются координаты более мелкой линии (отрицательные contrast). Обратите внимание, что самое большое значащее значение contrast будет 255 - максимальное значение, на которое может быть переведена начальная точка (255 255), прежде чем в результате получится вертикальная линия (полная контрастность, полностью черная или белая) или горизонтальная линия (без контраста, все серое).

Итак, теперь у нас есть координаты двух точек на нашей новой линии - (0,0) и (_52 _, _ 53_). Мы вставляем это в уравнение наклона, а затем вставляем его в уравнение полной линии, используя все предыдущие части:

y = m(x-a) + b

m = (y2-y1)/(x2-x1) =>
((255 + contrast) - 0)/((255 - contrast) - 0) =>
(255 + contrast)/(255 - contrast)

a = 128
b = 128

y = (255 + contrast)/(255 - contrast) * (x - 128) + 128 QED

Те, кто разбирается в математике, заметят, что результирующее m или factor является скалярным (безразмерным) значением; вы можете использовать любой диапазон для contrast, если он соответствует константе (255) в вычислении factor. Например, диапазон contrast от +/-100 и factor = (100 + contrast)/(100.01 - contrast), который я действительно использовал для исключения шага масштабирования до 255; Я просто оставил 255 в коде вверху, чтобы упростить объяснение.


Заметка о «магии» 259

Источник article использует «магию» 259, хотя автор признается, что не помнит, почему:

«Я не могу вспомнить, рассчитывал ли я это сам, читал ли я это в книге или в Интернете».

259 действительно должно быть 255 или 256 - количество возможных ненулевых значений для каждого канала каждого пикселя. Обратите внимание, что в исходном вычислении factor 259/255 отменяют - технически 1.01, но окончательные значения являются целыми числами, так что 1 для всех практических целей. Так что этот внешний термин можно отбросить. Однако на самом деле использование 255 в качестве константы в знаменателе вводит возможность ошибки деления на ноль в формуле; настройка на немного большее значение (скажем, 259) позволяет избежать этой проблемы, не внося значительных ошибок в результаты. Вместо этого я решил использовать 255.01, поскольку ошибка ниже и (надеюсь) кажется новичку менее «волшебной».

Насколько я могу судить, не имеет большого значения, какой вы используете - вы получаете идентичные значения, за исключением незначительных, симметричных различий в узкой полосе низких значений контрастности с низким положительным увеличением контраста. Мне было бы любопытно многократно проверять обе версии и сравнивать с исходными данными, но этот ответ уже занял слишком много времени. :)

person brichins    schedule 09.06.2016

Попробовав ответ Шахриара Саффара Шарга, он не вел себя так, как должен вести себя контраст. Наконец-то я наткнулся на этот алгоритм, и он отлично работает!

Для получения дополнительной информации об алгоритме прочтите эта статья и раздел комментариев.

function contrastImage(imageData, contrast) {

    var data = imageData.data;
    var factor = (259 * (contrast + 255)) / (255 * (259 - contrast));

    for(var i=0;i<data.length;i+=4)
    {
        data[i] = factor * (data[i] - 128) + 128;
        data[i+1] = factor * (data[i+1] - 128) + 128;
        data[i+2] = factor * (data[i+2] - 128) + 128;
    }
    return imageData;
}

Использование:

var newImageData = contrastImage(imageData, 30);

Надеюсь, это кому-то сэкономит время. Ваше здоровье!

person Brian    schedule 28.08.2013
comment
Это похоже на то, что мне нужно, как мне применить imageData-varialbe к изображению на холсте? - person Himmators; 19.02.2014
comment
Мне это на самом деле кажется неправильным - у вас (слишком много) неуместных скобок, вы вычли 128, а затем добавили их обратно. Вместо этого он должен быть написан так (для красного): factor*(d[i]-128) + 128. В противном случае просто избавьтесь от 128, потому что они ничего не делают! - person calipoop; 16.03.2014
comment
Ой. Вы абсолютно правы, calipoop. Я запишу это в разделе "О чем я думал?" - person Brian; 17.03.2014
comment
Откуда у 259 фактор контрастности? - person Alec Hewitt; 22.11.2015
comment
Первоначальный автор признает, что 259 - это магическое число без источника. Он не ведет себя так, как указано в спецификации drafts.fxtf.org/filters/#contrastEquivalent, что можно проверить, сравнив фильтр контрастности SVG или CSS с выходными данными этого алгоритма (попробуйте множитель высокой контрастности, такой как 3x). См. Мой ответ ниже для версии алгоритма контраста, совместимой с SVG / CSS3. - person Escher; 10.12.2015
comment
Добавлен источник. Также ответ на загадку 259 автор алгоритма объясняет в разделе комментариев. - person Brian; 10.12.2015
comment
259, хотя и упоминается в комментариях, не объясняется - автор говорит, что я не помню, но связывает его с генерацией диапазона [0..129,5] - который предназначен только для коэффициента коррекции контрастности и не представляет значение цвета. как таковой. Однако эти магические значения меня настолько раздражали, что я начал искать для них реальный ответ - оказалось, что ни одно из значений не является магией; просто достаточно близко. Объяснение обоих чисел и общей концепции в моем ответе - stackoverflow.com/a/37714937/957950. - person brichins; 09.06.2016
comment
Какой диапазон ввода здесь используется для контраста? Я не мог заставить это работать ни с [-100, 100], ни с [-1, 1] диапазонами. Ответ stackoverflow.com/a/37714937/134761 у меня сработал. - person angularsen; 20.03.2020

Я обнаружил, что вы должны использовать эффект, разделяя темные и светлые тона или технически все, что меньше 127 (среднее значение R + G + B / 3) в шкале rgb, является черным, а более 127 - белым, поэтому в зависимости от вашего уровня контрастности вы вычитаете значение, скажем, 10 контрастов от черного, и добавляете такое же значение к белому!

Вот пример: у меня есть два пикселя с цветами RGB, [105,40,200] | [255,200,150] Итак, я знаю, что для моего первого пикселя 105 + 40 + 200 = 345, 345/3 = 115 и 115 меньше моей половины 255, что составляет 127, поэтому я считаю пиксель ближе к [0,0,0] поэтому, если я хочу минус 10 контраста, я беру 10 из каждого цвета в среднем. Таким образом, я должен разделить значение каждого цвета на общее среднее значение, которое в данном случае составило 115, умножить его на мой контраст и вычесть окончательное значение из этот конкретный цвет:

Например, я возьму 105 (красный) из моего пикселя, поэтому я делю его на общее среднее значение RGB. что равно 115 и умножается на мое значение контраста 10, (105/115) * 10, что дает вам что-то около 9 (вам нужно округлить его!), а затем уберите это 9 из 105, чтобы цвет стал 96, поэтому мой красный после 10-кратного контраста на темном пикселе.

Итак, если я продолжу, значения моих пикселей станут [96,37,183]! (примечание: масштаб контрастности зависит от вас! но, в конце концов, вы должны преобразовать его в какой-то масштаб, например, от 1 до 255)

Для более светлых пикселей я тоже делаю то же самое, за исключением того, что вместо вычитания значения контрастности я добавляю его! и если вы достигнете предела 255 или 0, вы остановите сложение и вычитание для этого конкретного цвета! поэтому мой второй пиксель, который является более светлым, становится [255,210,157]

Чем больше контраста, тем светлее светлее, тем темнее темнее, и, следовательно, контрастность вашего изображения увеличивается!

Вот пример кода Javascript (я еще не пробовал):

var data = imageData.data;
for (var i = 0; i < data.length; i += 4) {
 var contrast = 10;
 var average = Math.round( ( data[i] + data[i+1] + data[i+2] ) / 3 );
  if (average > 127){
    data[i] += ( data[i]/average ) * contrast;
    data[i+1] += ( data[i+1]/average ) * contrast;
    data[i+2] += ( data[i+2]/average ) * contrast;
  }else{
    data[i] -= ( data[i]/average ) * contrast;
    data[i+1] -= ( data[i+1]/average ) * contrast;
    data[i+2] -= ( data[i+2]/average ) * contrast;
  }
}
person Schahriar SaffarShargh    schedule 04.01.2013
comment
Этот алгоритм регулирует яркость и насыщенность каждого отдельного пикселя, а не единообразную регулировку по всему изображению. jsfiddle.net/6530vxy7 Однако, если кто-то по какой-то причине захотел это сделать, им следует подумать об использовании относительная яркость вместо прямого среднего - 0,2126 * R + 0,7152 * G + 0,0722 * B. - person brichins; 13.06.2016

Эта реализация javascript соответствует определению SVG / CSS3 для «контраста» (и следующий код будет отображать ваше изображение холста идентично):

/*contrast filter function*/
//See definition at https://drafts.fxtf.org/filters/#contrastEquivalent
//pixels come from your getImageData() function call on your canvas image
contrast = function(pixels, value){
    var d = pixels.data;
    var intercept = 255*(-value/2 + 0.5);
    for(var i=0;i<d.length;i+=4){
        d[i] = d[i]*value + intercept;
        d[i+1] = d[i+1]*value + intercept;
        d[i+2] = d[i+2]*value + intercept;
        //implement clamping in a separate function if using in production
        if(d[i] > 255) d[i] = 255;
        if(d[i+1] > 255) d[i+1] = 255;
        if(d[i+2] > 255) d[i+2] = 255;
        if(d[i] < 0) d[i] = 0;
        if(d[i+1] < 0) d[i+1] = 0;
        if(d[i+2] < 0) d[i+2] = 0;
    }
    return pixels;
}
person Escher    schedule 10.12.2015
comment
Фиксация не требуется - об этом позаботится базовый тип массива Uint8ClampedArray. Хотя мне вообще не удалось заставить этот подход работать - это был путь, на который я тоже изначально смотрел. Хотелось бы увидеть рабочий пример. - person brichins; 09.06.2016
comment
Установите скрипку, и я посмотрю на нее, если у меня будет время. Мой проект по работе с изображениями пока приостановлен. - person Escher; 09.06.2016
comment
Разобрался - работает, просто нужно было повозиться с входами: 0: полностью ненасыщенный, 1: без изменений, 2: контрастность увеличена вдвое. Также -1: инвертированные цвета. jsfiddle.net/88k7zj3k/5. Фактическое манипулирование массивом выглядит быстрее (по крайней мере, меньше операций), я могу в конечном итоге перейти на этот подход после некоторого быстрого тестирования. - person brichins; 09.06.2016
comment
Отменил встречу и обновил свою скрипку с некоторым временем - этот подход (без зажима) постоянно в 10-20 раз быстрее (на образце изображения 100x100). jsfiddle.net/88k7zj3k/6 - person brichins; 09.06.2016

Вы можете взглянуть на документацию OpenCV, чтобы узнать, как этого добиться: Настройки яркости и контрастности.

Тогда есть демонстрационный код:

 double alpha; // Simple contrast control: value [1.0-3.0]
 int beta;     // Simple brightness control: value [0-100]

 for( int y = 0; y < image.rows; y++ )
 { 
      for( int x = 0; x < image.cols; x++ )
      { 
          for( int c = 0; c < 3; c++ )
          {
              new_image.at<Vec3b>(y,x)[c] = saturate_cast<uchar>( alpha*( image.at<Vec3b>(y,x)[c] ) + beta );
          }
      }
 }

который, как я полагаю, вы можете перевести на javascript.

person karlphillip    schedule 09.05.2012
comment
Спасибо! Это в основном тот же алгоритм, который я использовал, но моя проблема заключалась в том, что вы добавляете винтажность и контраст вместе, которые, похоже, не дают мне желаемого результата! Но это помогло! - person Schahriar SaffarShargh; 09.05.2012

Я предполагаю, что вы пытаетесь применить LUTS ... Недавно я пытался добавить цветовую обработку в окна холста. Если вы действительно хотите применить «LUTS» к окну холста, я считаю, что вам нужно фактически сопоставить массив, который возвращает imageData, с массивом RGB LUT.

(Из иллюзии света) В качестве примера начало 1D LUT может выглядеть примерно так: Примечание: строго говоря, это 3x 1D LUT, поскольку каждый цвет (R, G, B) является 1D LUT

R, G, B 
3, 0, 0 
5, 2, 1 
7, 5, 3 
9, 9, 9

Что обозначает:

For an input value of 0 for R, G, and B, the output is R=3, G=0, B=0 
For an input value of 1 for R, G, and B, the output is R=5, G=2, B=1 
For an input value of 2 for R, G, and B, the output is R=7, G=5, B=3 
For an input value of 3 for R, G, and B, the output is R=9, G=9, B=9

Это странная LUT, но вы видите, что для заданного значения входа R, G или B существует заданное значение выхода R, G и B.

Итак, если пиксель имел входное значение 3, 1, 0 для RGB, выходной пиксель был бы 9, 2, 0.

Во время этого я также понял, поиграв с imageData, что он возвращает Uint8Array и что значения в этом массиве являются десятичными. Большинство 3D LUTS являются шестнадцатеричными. Итак, вам сначала нужно выполнить преобразование из шестнадцатеричного в десятичное для всего массива перед всем этим отображением.

person Jay    schedule 22.04.2013

Это формула, которую вы ищете ...

var data = imageData.data;
if (contrast > 0) {

    for(var i = 0; i < data.length; i += 4) {
        data[i] += (255 - data[i]) * contrast / 255;            // red
        data[i + 1] += (255 - data[i + 1]) * contrast / 255;    // green
        data[i + 2] += (255 - data[i + 2]) * contrast / 255;    // blue
    }

} else if (contrast < 0) {
    for (var i = 0; i < data.length; i += 4) {
        data[i] += data[i] * (contrast) / 255;                  // red
        data[i + 1] += data[i + 1] * (contrast) / 255;          // green
        data[i + 2] += data[i + 2] * (contrast) / 255;          // blue
    }
}

Надеюсь, поможет!

person fforgoso    schedule 05.02.2014