Контраст на изображението на HTML5 Canvas

Пишех програма за обработка на изображения, която прилага ефекти чрез обработка на пиксели на платно 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)


По-бърз вариант (въз основа на подхода на Escher) е:

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. Този алгоритъм не включва проверка на диапазон, въпреки че изчислените стойности могат далеч да надхвърлят допустимите диапазон - това е така, защото масивът, лежащ в основата на обекта 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 също е различна от нула; знаем, че х-диапазонът е широк 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) координати са (255 - contrast,255 + contrast). Координатите на по-плитката линия (минус contrast) се намират по същия начин. Забележете, че най-голямата смислена стойност на contrast ще бъде 255 - най-много началната точка на (255,255) може да бъде преведена, преди да доведе до вертикална линия (пълен контраст, изцяло черна или бяла) или хоризонтална линия (без контраст, всичко сиво).

Така че сега имаме координатите на две точки на нашата нова линия - (0,0) и (255 - contrast,255 + contrast). Вмъкваме това в уравнението на наклона и след това го включваме в уравнението на цялата линия, като използваме всички части от преди:

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

Източникът статия използва "магически" 259, въпреки че авторът признава, че не си спомня защо:

„Не мога да си спомня дали съм изчислил това сам или съм го прочел в книга или онлайн.“

259 наистина трябва да бъде 255 или може би 256 - броят на възможните ненулеви стойности за всеки канал на всеки пиксел. Обърнете внимание, че в първоначалното изчисление factor 259/255 се съкращава - технически 1,01, но крайните стойности са цели числа, така че 1 за всички практически цели. Така че този външен член може да бъде изхвърлен. Всъщност използването на 255 за константата в знаменателя обаче въвежда възможността за грешка при деление на нула във формулата; коригирането на малко по-голяма стойност (да речем, 259) избягва този проблем, без да въвежда значителна грешка в резултатите. Вместо това избрах да използвам 255.01, тъй като грешката е по-ниска и (надявам се) изглежда по-малко "магическо" за новодошъл.

Доколкото разбирам обаче, няма голяма разлика кое използвате - получавате идентични стойности с изключение на незначителни, симетрични разлики в тясна лента от стойности с нисък контраст с ниско положително увеличение на контраста. Бих бил любопитен да обходя двете версии многократно и да ги сравня с оригиналните данни, но този отговор вече отне твърде много време. :)

person brichins    schedule 09.06.2016

След като опитах отговора от Schahriar SaffarShargh, не се държаше така, както трябва да се държи контрастът. Най-накрая попаднах на този алгоритъм и той работи като чар!

За допълнителна информация относно алгоритъма прочетете тази статия и нейния раздел за коментари.

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] - което е само за коефициента на корекция на контраста и не представлява стойност на цвета per se. Въпреки това, тези магически стойности ме подразниха достатъчно, че се зарових да намеря истински отговор за тях - оказа се, че нито една стойност не е магическа; достатъчно близо. Обяснение на двете числа и цялостната концепция в моя отговор - 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.

(От Light illusion) Като пример, началото на 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 са Hex. Така че първо трябва да направите някакъв тип шестнадесетично преобразуване в намаляване на целия масив преди цялото това картографиране.

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