'blend': function (requirements) {
const copyPixel = function (fr, tr, data) {
let fg = fr + 1,
fb = fg + 1,
fa = fb + 1,
tg = tr + 1,
tb = tg + 1,
ta = tb + 1;
oData[tr] = data[fr];
oData[tg] = data[fg];
oData[tb] = data[fb];
oData[ta] = data[fa];
};
const getLinePositions = function (x, y) {
let ix = x,
iy = y,
mx = x - offsetX,
my = y - offsetY;
let mPos = -1,
iPos = ((iy * iWidth) + ix) * 4;
if (mx >= 0 && mx < mWidth && my >= 0 && my < mHeight) mPos = ((my * mWidth) + mx) * 4;
return [iPos, mPos];
};
const getChannelNormals = function (irn, mrn) {
let ign = irn + 1,
ibn = ign + 1,
ian = ibn + 1,
mgn = mrn + 1,
mbn = mgn + 1,
man = mbn + 1;
return [
iData[irn] / 255,
iData[ign] / 255,
iData[ibn] / 255,
iData[ian] / 255,
mData[mrn] / 255,
mData[mgn] / 255,
mData[mbn] / 255,
mData[man] / 255
];
};
const alphaCalc = (dA, mA) => (dA + (mA * (1 - dA))) * 255;
const colorEngine = this.colorEngine;
let [input, output, mix] = this.getInputAndOutputLines(requirements);
let {width:iWidth, height:iHeight, data:iData} = input;
let {width:oWidth, height:oHeight, data:oData} = output;
let {width:mWidth, height:mHeight, data:mData} = mix;
let len = iData.length;
let {opacity, blend, offsetX, offsetY, lineOut} = requirements;
if (null == opacity) opacity = 1;
if (null == blend) blend = '';
if (null == offsetX) offsetX = 0;
if (null == offsetY) offsetY = 0;
let x, y, dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA, ir, ig, ib, ia, mr, mg, mb, ma, cr, cg, cb;
switch (blend) {
case 'color-burn' :
const colorburnCalc = (din, dmix) => {
if (dmix == 1) return 255;
else if (din == 0) return 0;
return (1 - Math.min(1, ((1 - dmix) / din ))) * 255;
};
for (y = 0; y < iHeight; y++) {
for (x = 0; x < iWidth; x++) {
[ir, mr] = getLinePositions(x, y);
ia = ir + 3;
ma = mr + 3;
if (iData[ia]) {
if (mr < 0) copyPixel(ir, ir, iData);
else if (!iData[ia]) copyPixel(mr, ir, mData);
else if (!mData[ma]) copyPixel(ir, ir, iData);
else {
[dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(ir, mr);
ig = ir + 1;
ib = ig + 1;
oData[ir] = colorburnCalc(dinR, dmixR);
oData[ig] = colorburnCalc(dinG, dmixG);
oData[ib] = colorburnCalc(dinB, dmixB);
oData[ia] = alphaCalc(dinA, dmixA);
}
}
}
}
break;
case 'color-dodge' :
const colordodgeCalc = (din, dmix) => {
if (dmix == 0) return 0;
else if (din == 1) return 255;
return Math.min(1, (dmix / (1 - din))) * 255;
};
for (y = 0; y < iHeight; y++) {
for (x = 0; x < iWidth; x++) {
[ir, mr] = getLinePositions(x, y);
ia = ir + 3;
ma = mr + 3;
if (iData[ia]) {
if (mr < 0) copyPixel(ir, ir, iData);
else if (!iData[ia]) copyPixel(mr, ir, mData);
else if (!mData[ma]) copyPixel(ir, ir, iData);
else {
[dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(ir, mr);
ig = ir + 1;
ib = ig + 1;
oData[ir] = colordodgeCalc(dinR, dmixR);
oData[ig] = colordodgeCalc(dinG, dmixG);
oData[ib] = colordodgeCalc(dinB, dmixB);
oData[ia] = alphaCalc(dinA, dmixA);
}
}
}
}
break;
case 'darken' :
const darkenCalc = (din, dmix) => (din < dmix) ? din : dmix;
for (y = 0; y < iHeight; y++) {
for (x = 0; x < iWidth; x++) {
[ir, mr] = getLinePositions(x, y);
ia = ir + 3;
ma = mr + 3;
if (iData[ia]) {
if (mr < 0) copyPixel(ir, ir, iData);
else if (!iData[ia]) copyPixel(mr, ir, mData);
else if (!mData[ma]) copyPixel(ir, ir, iData);
else {
ig = ir + 1;
ib = ig + 1;
mg = mr + 1;
mb = mg + 1;
oData[ir] = darkenCalc(iData[ir], mData[mr]);
oData[ig] = darkenCalc(iData[ig], mData[mg]);
oData[ib] = darkenCalc(iData[ib], mData[mb]);
oData[ia] = alphaCalc(iData[ia] / 255, mData[ma] / 255);
}
}
}
}
break;
case 'difference' :
const differenceCalc = (din, dmix) => Math.abs(din - dmix) * 255;
for (y = 0; y < iHeight; y++) {
for (x = 0; x < iWidth; x++) {
[ir, mr] = getLinePositions(x, y);
ia = ir + 3;
if (iData[ia]) {
if (mr < 0) copyPixel(ir, ir, iData);
else if (!iData[ia]) copyPixel(mr, ir, mData);
else {
[dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(ir, mr);
ig = ir + 1;
ib = ig + 1;
oData[ir] = differenceCalc(dinR, dmixR);
oData[ig] = differenceCalc(dinG, dmixG);
oData[ib] = differenceCalc(dinB, dmixB);
oData[ia] = alphaCalc(dinA, dmixA);
}
}
}
}
break;
case 'exclusion' :
const exclusionCalc = (din, dmix) => (din + dmix - (2 * dmix * din)) * 255;
for (y = 0; y < iHeight; y++) {
for (x = 0; x < iWidth; x++) {
[ir, mr] = getLinePositions(x, y);
ia = ir + 3;
if (iData[ia]) {
if (mr < 0) copyPixel(ir, ir, iData);
else if (!iData[ia]) copyPixel(mr, ir, mData);
else {
[dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(ir, mr);
ig = ir + 1;
ib = ig + 1;
oData[ir] = exclusionCalc(dinR, dmixR);
oData[ig] = exclusionCalc(dinG, dmixG);
oData[ib] = exclusionCalc(dinB, dmixB);
oData[ia] = alphaCalc(dinA, dmixA);
}
}
}
}
break;
case 'hard-light' :
const hardlightCalc = (din, dmix) => (din <= 0.5) ? (din * dmix) * 255 : (dmix + (din - (dmix * din))) * 255;
for (y = 0; y < iHeight; y++) {
for (x = 0; x < iWidth; x++) {
[ir, mr] = getLinePositions(x, y);
ia = ir + 3;
if (iData[ia]) {
if (mr < 0) copyPixel(ir, ir, iData);
else if (!iData[ia]) copyPixel(mr, ir, mData);
else {
[dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(ir, mr);
ig = ir + 1;
ib = ig + 1;
oData[ir] = hardlightCalc(dinR, dmixR);
oData[ig] = hardlightCalc(dinG, dmixG);
oData[ib] = hardlightCalc(dinB, dmixB);
oData[ia] = alphaCalc(dinA, dmixA);
}
}
}
}
break;
case 'lighten' :
const lightenCalc = (din, dmix) => (din > dmix) ? din : dmix;
for (y = 0; y < iHeight; y++) {
for (x = 0; x < iWidth; x++) {
[ir, mr] = getLinePositions(x, y);
ia = ir + 3;
if (iData[ia]) {
if (mr < 0) copyPixel(ir, ir, iData);
else if (!iData[ir]) copyPixel(mr, ir, mData);
else {
ig = ir + 1;
ib = ig + 1;
mg = mr + 1;
mb = mg + 1;
ma = mb + 1;
oData[ir] = lightenCalc(iData[ir], mData[mr]);
oData[ig] = lightenCalc(iData[ig], mData[mg]);
oData[ib] = lightenCalc(iData[ib], mData[mb]);
oData[ia] = alphaCalc(iData[ia] / 255, mData[ma] / 255);
}
}
}
}
break;
case 'lighter' :
const lighterCalc = (din, dmix) => (din + dmix) * 255;
for (y = 0; y < iHeight; y++) {
for (x = 0; x < iWidth; x++) {
[ir, mr] = getLinePositions(x, y);
ia = ir + 3;
if (iData[ia]) {
if (mr < 0) copyPixel(ir, ir, iData);
else if (!iData[ia]) copyPixel(mr, ir, mData);
else {
[dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(ir, mr);
ig = ir + 1;
ib = ig + 1;
oData[ir] = lighterCalc(dinR, dmixR);
oData[ig] = lighterCalc(dinG, dmixG);
oData[ib] = lighterCalc(dinB, dmixB);
oData[ia] = alphaCalc(dinA, dmixA);
}
}
}
}
break;
case 'multiply' :
const multiplyCalc = (din, dmix) => din * dmix * 255;
for (y = 0; y < iHeight; y++) {
for (x = 0; x < iWidth; x++) {
[ir, mr] = getLinePositions(x, y);
ia = ir + 3;
ma = mr + 3;
if (iData[ia]) {
if (mr < 0) copyPixel(ir, ir, iData);
else if (!iData[ia]) copyPixel(mr, ir, mData);
else if (!mData[ma]) copyPixel(ir, ir, iData);
else {
[dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(ir, mr);
ig = ir + 1;
ib = ig + 1;
oData[ir] = multiplyCalc(dinR, dmixR);
oData[ig] = multiplyCalc(dinG, dmixG);
oData[ib] = multiplyCalc(dinB, dmixB);
oData[ia] = alphaCalc(dinA, dmixA);
}
}
}
}
break;
case 'overlay' :
const overlayCalc = (din, dmix) => (din >= 0.5) ? (din * dmix) * 255 : (dmix + (din - (dmix * din))) * 255;
for (y = 0; y < iHeight; y++) {
for (x = 0; x < iWidth; x++) {
[ir, mr] = getLinePositions(x, y);
ia = ir + 3;
if (iData[ia]) {
if (mr < 0) copyPixel(ir, ir, iData);
else if (!iData[ia]) copyPixel(mr, ir, mData);
else {
[dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(ir, mr);
ig = ir + 1;
ib = ig + 1;
oData[ir] = overlayCalc(dinR, dmixR);
oData[ig] = overlayCalc(dinG, dmixG);
oData[ib] = overlayCalc(dinB, dmixB);
oData[ia] = alphaCalc(dinA, dmixA);
}
}
}
}
break;
case 'screen' :
const screenCalc = (din, dmix) => (dmix + (din - (dmix * din))) * 255;
for (y = 0; y < iHeight; y++) {
for (x = 0; x < iWidth; x++) {
[ir, mr] = getLinePositions(x, y);
ia = ir + 3;
if (iData[ia]) {
if (mr < 0) copyPixel(ir, ir, iData);
else if (!iData[ia]) copyPixel(mr, ir, mData);
else {
[dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(ir, mr);
ig = ir + 1;
ib = ig + 1;
oData[ir] = screenCalc(dinR, dmixR);
oData[ig] = screenCalc(dinG, dmixG);
oData[ib] = screenCalc(dinB, dmixB);
oData[ia] = alphaCalc(dinA, dmixA);
}
}
}
}
break;
case 'soft-light' :
const softlightCalc = (din, dmix) => {
let d = (dmix <= 0.25) ?
((((16 * dmix) - 12) * dmix) + 4) * dmix :
Math.sqrt(dmix);
if (din <= 0.5) return (dmix - ((1 - (2 * din)) * dmix * (1 - dmix))) * 255;
return (dmix + (((2 * din) - 1) * (d - dmix))) * 255;
};
for (y = 0; y < iHeight; y++) {
for (x = 0; x < iWidth; x++) {
[ir, mr] = getLinePositions(x, y);
ia = ir + 3;
ma = mr + 3;
if (iData[ia]) {
if (mr < 0) copyPixel(ir, ir, iData);
else if (!iData[ia]) copyPixel(mr, ir, mData);
else if (!mData[ma]) copyPixel(ir, ir, iData);
else {
[dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(ir, mr);
ig = ir + 1;
ib = ig + 1;
oData[ir] = softlightCalc(dinR, dmixR);
oData[ig] = softlightCalc(dinG, dmixG);
oData[ib] = softlightCalc(dinB, dmixB);
oData[ia] = alphaCalc(dinA, dmixA);
}
}
}
}
break;
case 'color' :
for (y = 0; y < iHeight; y++) {
for (x = 0; x < iWidth; x++) {
[ir, mr] = getLinePositions(x, y);
ig = ir + 1;
ib = ig + 1;
ia = ib + 1;
mg = mr + 1;
mb = mg + 1;
ma = mb + 1;
if (iData[ia]) {
if (mr < 0) copyPixel(ir, ir, iData);
else if (!iData[ia]) copyPixel(mr, ir, mData);
else if (!mData[ma]) copyPixel(ir, ir, iData);
else {
[cr, cg, cb] = colorEngine.calculateColorBlend(iData[ir], iData[ig], iData[ib], mData[mr], mData[mg], mData[mb]);
oData[ir] = cr;
oData[ig] = cg;
oData[ib] = cb;
oData[ia] = alphaCalc(iData[ia] / 255, mData[ma] / 255);
}
}
}
}
break;
case 'hue' :
for (y = 0; y < iHeight; y++) {
for (x = 0; x < iWidth; x++) {
[ir, mr] = getLinePositions(x, y);
ig = ir + 1;
ib = ig + 1;
ia = ib + 1;
mg = mr + 1;
mb = mg + 1;
ma = mb + 1;
if (iData[ia]) {
if (mr < 0) copyPixel(ir, ir, iData);
else if (!iData[ia]) copyPixel(mr, ir, mData);
else if (!mData[ma]) copyPixel(ir, ir, iData);
else {
[cr, cg, cb] = colorEngine.calculateHueBlend(iData[ir], iData[ig], iData[ib], mData[mr], mData[mg], mData[mb]);
oData[ir] = cr;
oData[ig] = cg;
oData[ib] = cb;
oData[ia] = alphaCalc(iData[ia] / 255, mData[ma] / 255);
}
}
}
}
break;
case 'luminosity' :
for (y = 0; y < iHeight; y++) {
for (x = 0; x < iWidth; x++) {
[ir, mr] = getLinePositions(x, y);
ig = ir + 1;
ib = ig + 1;
ia = ib + 1;
mg = mr + 1;
mb = mg + 1;
ma = mb + 1;
if (iData[ia]) {
if (mr < 0) copyPixel(ir, ir, iData);
else if (!iData[ia]) copyPixel(mr, ir, mData);
else if (!mData[ma]) copyPixel(ir, ir, iData);
else {
[cr, cg, cb] = colorEngine.calculateLuminosityBlend(iData[ir], iData[ig], iData[ib], mData[mr], mData[mg], mData[mb]);
oData[ir] = cr;
oData[ig] = cg;
oData[ib] = cb;
oData[ia] = alphaCalc(iData[ia] / 255, mData[ma] / 255);
}
}
}
}
break;
case 'saturation' :
for (y = 0; y < iHeight; y++) {
for (x = 0; x < iWidth; x++) {
[ir, mr] = getLinePositions(x, y);
ig = ir + 1;
ib = ig + 1;
ia = ib + 1;
mg = mr + 1;
mb = mg + 1;
ma = mb + 1;
if (iData[ia]) {
if (mr < 0) copyPixel(ir, ir, iData);
else if (!iData[ia]) copyPixel(mr, ir, mData);
else if (!mData[ma]) copyPixel(ir, ir, iData);
else {
[cr, cg, cb] = colorEngine.calculateSaturationBlend(iData[ir], iData[ig], iData[ib], mData[mr], mData[mg], mData[mb]);
oData[ir] = cr;
oData[ig] = cg;
oData[ib] = cb;
oData[ia] = alphaCalc(iData[ia] / 255, mData[ma] / 255);
}
}
}
}
break;
default:
const normalCalc = (Cs, As, Cb, Ab) => (As * Cs) + (Ab * Cb * (1 - As));
for (y = 0; y < iHeight; y++) {
for (x = 0; x < iWidth; x++) {
[ir, mr] = getLinePositions(x, y);
ia = ir + 3;
if (iData[ia]) {
if (mr < 0) copyPixel(ir, ir, iData);
else if (!iData[ia]) copyPixel(mr, ir, mData);
else {
ig = ir + 1;
ib = ig + 1;
mg = mr + 1;
mb = mg + 1;
ma = mb + 1;
dinA = iData[ia] / 255;
dmixA = mData[ma] / 255;
oData[ir] = normalCalc(iData[ir], dinA, mData[mr], dmixA);
oData[ig] = normalCalc(iData[ig], dinA, mData[mg], dmixA);
oData[ib] = normalCalc(iData[ib], dinA, mData[mb], dmixA);
oData[ia] = alphaCalc(dinA, dmixA)
}
}
}
}
}
if (lineOut) this.processResults(output, input, 1 - opacity);
else this.processResults(this.cache.work, output, opacity);
},