715 lines
35 KiB
JavaScript
715 lines
35 KiB
JavaScript
|
|
function applyImageAdjustments(canvas) {
|
|
const brightness = parseInt(document.getElementById('brightness').value);
|
|
const contrast = parseInt(document.getElementById('contrast').value);
|
|
const saturation = parseInt(document.getElementById('saturation').value);
|
|
const diffusion = parseInt(document.getElementById('diffusion').value) / 100;
|
|
const ctx = canvas.getContext('2d');
|
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
const data = imageData.data;
|
|
if (brightness !== 0) {
|
|
const brightnessFactor = brightness / 100;
|
|
for (let i = 0; i < data.length; i += 4) {
|
|
data[i] = clamp(data[i] + 255 * brightnessFactor, 0, 255);
|
|
data[i + 1] = clamp(data[i + 1] + 255 * brightnessFactor, 0, 255);
|
|
data[i + 2] = clamp(data[i + 2] + 255 * brightnessFactor, 0, 255);
|
|
}
|
|
}
|
|
if (contrast !== 0) {
|
|
const contrastFactor = (contrast + 100) / 100;
|
|
const contrastAdjust = (1 - contrastFactor) * 128;
|
|
for (let i = 0; i < data.length; i += 4) {
|
|
data[i] = clamp(data[i] * contrastFactor + contrastAdjust, 0, 255);
|
|
data[i + 1] = clamp(data[i + 1] * contrastFactor + contrastAdjust, 0, 255);
|
|
data[i + 2] = clamp(data[i + 2] * contrastFactor + contrastAdjust, 0, 255);
|
|
}
|
|
}
|
|
if (saturation !== 100) {
|
|
const saturationFactor = saturation / 100;
|
|
for (let i = 0; i < data.length; i += 4) {
|
|
const gray = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
|
|
data[i] = clamp(gray + (data[i] - gray) * saturationFactor, 0, 255);
|
|
data[i + 1] = clamp(gray + (data[i + 1] - gray) * saturationFactor, 0, 255);
|
|
data[i + 2] = clamp(gray + (data[i + 2] - gray) * saturationFactor, 0, 255);
|
|
}
|
|
}
|
|
ctx.putImageData(imageData, 0, 0);
|
|
return diffusion;
|
|
}
|
|
function clamp(value, min, max) {
|
|
return Math.min(Math.max(value, min), max);
|
|
}
|
|
function applyNoneDithering(ctx, width, height, threshold, isColor = false) {
|
|
const imageData = ctx.getImageData(0, 0, width, height);
|
|
const data = imageData.data;
|
|
for (let i = 0; i < data.length; i += 4) {
|
|
const gray = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
|
|
const value = gray < threshold ? 0 : 255;
|
|
data[i] = data[i + 1] = data[i + 2] = value;
|
|
}
|
|
ctx.putImageData(imageData, 0, 0);
|
|
}
|
|
function applyFloydSteinbergDithering(ctx, width, height, threshold, diffusion = 1.0, isColor = false) {
|
|
const imageData = ctx.getImageData(0, 0, width, height);
|
|
const data = imageData.data;
|
|
for (let y = 0; y < height; y++) {
|
|
for (let x = 0; x < width; x++) {
|
|
const idx = (y * width + x) * 4;
|
|
const oldR = data[idx];
|
|
const oldG = data[idx + 1];
|
|
const oldB = data[idx + 2];
|
|
const gray = 0.299 * oldR + 0.587 * oldG + 0.114 * oldB;
|
|
const newPixel = gray < threshold ? 0 : 255;
|
|
const err = (gray - newPixel) * diffusion;
|
|
data[idx] = data[idx + 1] = data[idx + 2] = newPixel;
|
|
if (x + 1 < width) {
|
|
addError(data, idx + 4, err, 7/16);
|
|
}
|
|
if (y + 1 < height) {
|
|
if (x - 1 >= 0) {
|
|
addError(data, idx + width * 4 - 4, err, 3/16);
|
|
}
|
|
addError(data, idx + width * 4, err, 5/16);
|
|
if (x + 1 < width) {
|
|
addError(data, idx + width * 4 + 4, err, 1/16);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ctx.putImageData(imageData, 0, 0);
|
|
}
|
|
function applyAtkinsonDithering(ctx, width, height, threshold, diffusion = 1.0, isColor = false) {
|
|
const imageData = ctx.getImageData(0, 0, width, height);
|
|
const data = imageData.data;
|
|
for (let y = 0; y < height; y++) {
|
|
for (let x = 0; x < width; x++) {
|
|
const idx = (y * width + x) * 4;
|
|
const oldR = data[idx];
|
|
const oldG = data[idx + 1];
|
|
const oldB = data[idx + 2];
|
|
const gray = 0.299 * oldR + 0.587 * oldG + 0.114 * oldB;
|
|
const newPixel = gray < threshold ? 0 : 255;
|
|
const err = (gray - newPixel) * diffusion;
|
|
data[idx] = data[idx + 1] = data[idx + 2] = newPixel;
|
|
if (x + 1 < width) {
|
|
addError(data, idx + 4, err, 1/8);
|
|
}
|
|
if (x + 2 < width) {
|
|
addError(data, idx + 8, err, 1/8);
|
|
}
|
|
if (y + 1 < height) {
|
|
if (x - 1 >= 0) {
|
|
addError(data, idx + width * 4 - 4, err, 1/8);
|
|
}
|
|
addError(data, idx + width * 4, err, 1/8);
|
|
if (x + 1 < width) {
|
|
addError(data, idx + width * 4 + 4, err, 1/8);
|
|
}
|
|
}
|
|
if (y + 2 < height) {
|
|
addError(data, idx + width * 8, err, 1/8);
|
|
}
|
|
}
|
|
}
|
|
ctx.putImageData(imageData, 0, 0);
|
|
}
|
|
function applyBayerDithering(ctx, width, height, threshold, diffusion = 1.0, isColor = false) {
|
|
const bayerThresholdMap = [
|
|
[15, 135, 45, 165],
|
|
[195, 75, 225, 105],
|
|
[60, 180, 30, 150],
|
|
[240, 120, 210, 90]
|
|
];
|
|
const imageData = ctx.getImageData(0, 0, width, height);
|
|
const data = imageData.data;
|
|
for (let i = 0; i < data.length; i += 4) {
|
|
const gray = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
|
|
const x = (i / 4) % width;
|
|
const y = Math.floor((i / 4) / width);
|
|
const mapValue = bayerThresholdMap[x % 4][y % 4];
|
|
const adjustedThreshold = threshold * (1 + (mapValue - 128) / 128 * diffusion);
|
|
const finalThreshold = clamp(adjustedThreshold, 0, 255);
|
|
const newPixel = gray < finalThreshold ? 0 : 255;
|
|
data[i] = data[i + 1] = data[i + 2] = newPixel;
|
|
}
|
|
ctx.putImageData(imageData, 0, 0);
|
|
}
|
|
function applyStuckiDithering(ctx, width, height, threshold, diffusion = 1.0, isColor = false) {
|
|
const imageData = ctx.getImageData(0, 0, width, height);
|
|
const data = imageData.data;
|
|
for (let y = 0; y < height; y++) {
|
|
for (let x = 0; x < width; x++) {
|
|
const idx = (y * width + x) * 4;
|
|
const oldR = data[idx];
|
|
const oldG = data[idx + 1];
|
|
const oldB = data[idx + 2];
|
|
const gray = 0.299 * oldR + 0.587 * oldG + 0.114 * oldB;
|
|
const newPixel = gray < threshold ? 0 : 255;
|
|
const err = (gray - newPixel) * diffusion;
|
|
data[idx] = data[idx + 1] = data[idx + 2] = newPixel;
|
|
if (x + 1 < width) {
|
|
addError(data, idx + 4, err, 8/42);
|
|
}
|
|
if (x + 2 < width) {
|
|
addError(data, idx + 8, err, 4/42);
|
|
}
|
|
if (y + 1 < height) {
|
|
if (x - 2 >= 0) {
|
|
addError(data, idx + width * 4 - 8, err, 2/42);
|
|
}
|
|
if (x - 1 >= 0) {
|
|
addError(data, idx + width * 4 - 4, err, 4/42);
|
|
}
|
|
addError(data, idx + width * 4, err, 8/42);
|
|
if (x + 1 < width) {
|
|
addError(data, idx + width * 4 + 4, err, 4/42);
|
|
}
|
|
if (x + 2 < width) {
|
|
addError(data, idx + width * 4 + 8, err, 2/42);
|
|
}
|
|
}
|
|
if (y + 2 < height) {
|
|
if (x - 2 >= 0) {
|
|
addError(data, idx + width * 8 - 8, err, 1/42);
|
|
}
|
|
if (x - 1 >= 0) {
|
|
addError(data, idx + width * 8 - 4, err, 2/42);
|
|
}
|
|
addError(data, idx + width * 8, err, 4/42);
|
|
if (x + 1 < width) {
|
|
addError(data, idx + width * 8 + 4, err, 2/42);
|
|
}
|
|
if (x + 2 < width) {
|
|
addError(data, idx + width * 8 + 8, err, 1/42);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ctx.putImageData(imageData, 0, 0);
|
|
}
|
|
function applyJarvisJudiceNinkeDithering(ctx, width, height, threshold, diffusion = 1.0, isColor = false) {
|
|
const imageData = ctx.getImageData(0, 0, width, height);
|
|
const data = imageData.data;
|
|
for (let y = 0; y < height; y++) {
|
|
for (let x = 0; x < width; x++) {
|
|
const idx = (y * width + x) * 4;
|
|
const oldR = data[idx];
|
|
const oldG = data[idx + 1];
|
|
const oldB = data[idx + 2];
|
|
const gray = 0.299 * oldR + 0.587 * oldG + 0.114 * oldB;
|
|
const newPixel = gray < threshold ? 0 : 255;
|
|
const err = (gray - newPixel) * diffusion;
|
|
data[idx] = data[idx + 1] = data[idx + 2] = newPixel;
|
|
if (x + 1 < width) {
|
|
addError(data, idx + 4, err, 7/48);
|
|
}
|
|
if (x + 2 < width) {
|
|
addError(data, idx + 8, err, 5/48);
|
|
}
|
|
if (y + 1 < height) {
|
|
if (x - 2 >= 0) {
|
|
addError(data, idx + width * 4 - 8, err, 3/48);
|
|
}
|
|
if (x - 1 >= 0) {
|
|
addError(data, idx + width * 4 - 4, err, 5/48);
|
|
}
|
|
addError(data, idx + width * 4, err, 7/48);
|
|
if (x + 1 < width) {
|
|
addError(data, idx + width * 4 + 4, err, 5/48);
|
|
}
|
|
if (x + 2 < width) {
|
|
addError(data, idx + width * 4 + 8, err, 3/48);
|
|
}
|
|
}
|
|
if (y + 2 < height) {
|
|
if (x - 2 >= 0) {
|
|
addError(data, idx + width * 8 - 8, err, 1/48);
|
|
}
|
|
if (x - 1 >= 0) {
|
|
addError(data, idx + width * 8 - 4, err, 3/48);
|
|
}
|
|
addError(data, idx + width * 8, err, 5/48);
|
|
if (x + 1 < width) {
|
|
addError(data, idx + width * 8 + 4, err, 3/48);
|
|
}
|
|
if (x + 2 < width) {
|
|
addError(data, idx + width * 8 + 8, err, 1/48);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ctx.putImageData(imageData, 0, 0);
|
|
}
|
|
function addError(data, idx, err, factor) {
|
|
data[idx] = clamp(data[idx] + err * factor, 0, 255);
|
|
data[idx + 1] = clamp(data[idx + 1] + err * factor, 0, 255);
|
|
data[idx + 2] = clamp(data[idx + 2] + err * factor, 0, 255);
|
|
}
|
|
function applyBWRNoneDithering(ctx, width, height, threshold, diffusion = 1.0) {
|
|
const imageData = ctx.getImageData(0, 0, width, height);
|
|
const data = imageData.data;
|
|
for (let i = 0; i < data.length; i += 4) {
|
|
const r = data[i];
|
|
const g = data[i + 1];
|
|
const b = data[i + 2];
|
|
if (r > g * 1.5 && r > b * 1.5 && r > threshold) {
|
|
data[i] = 255;
|
|
data[i + 1] = 0;
|
|
data[i + 2] = 0;
|
|
} else {
|
|
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
const value = gray < threshold ? 0 : 255;
|
|
data[i] = data[i + 1] = data[i + 2] = value;
|
|
}
|
|
}
|
|
ctx.putImageData(imageData, 0, 0);
|
|
}
|
|
function applyBWRFloydSteinbergDithering(ctx, width, height, threshold, diffusion = 1.0) {
|
|
const imageData = ctx.getImageData(0, 0, width, height);
|
|
const data = imageData.data;
|
|
for (let y = 0; y < height; y++) {
|
|
for (let x = 0; x < width; x++) {
|
|
const idx = (y * width + x) * 4;
|
|
const oldR = data[idx];
|
|
const oldG = data[idx + 1];
|
|
const oldB = data[idx + 2];
|
|
if (oldR > oldG * 1.5 && oldR > oldB * 1.5 && oldR > threshold) {
|
|
data[idx] = 255;
|
|
data[idx + 1] = 0;
|
|
data[idx + 2] = 0;
|
|
continue;
|
|
}
|
|
const gray = 0.299 * oldR + 0.587 * oldG + 0.114 * oldB;
|
|
const newPixel = gray < threshold ? 0 : 255;
|
|
const errR = (oldR - newPixel) * diffusion;
|
|
const errG = (oldG - newPixel) * diffusion;
|
|
const errB = (oldB - newPixel) * diffusion;
|
|
data[idx] = data[idx + 1] = data[idx + 2] = newPixel;
|
|
if (x + 1 < width) {
|
|
addColorError(data, idx + 4, errR, errG, errB, 7/16);
|
|
}
|
|
if (y + 1 < height) {
|
|
if (x - 1 >= 0) {
|
|
addColorError(data, idx + width * 4 - 4, errR, errG, errB, 3/16);
|
|
}
|
|
addColorError(data, idx + width * 4, errR, errG, errB, 5/16);
|
|
if (x + 1 < width) {
|
|
addColorError(data, idx + width * 4 + 4, errR, errG, errB, 1/16);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ctx.putImageData(imageData, 0, 0);
|
|
}
|
|
function applyBWRAtkinsonDithering(ctx, width, height, threshold, diffusion = 1.0) {
|
|
const imageData = ctx.getImageData(0, 0, width, height);
|
|
const data = imageData.data;
|
|
for (let y = 0; y < height; y++) {
|
|
for (let x = 0; x < width; x++) {
|
|
const idx = (y * width + x) * 4;
|
|
const oldR = data[idx];
|
|
const oldG = data[idx + 1];
|
|
const oldB = data[idx + 2];
|
|
if (oldR > oldG * 1.5 && oldR > oldB * 1.5 && oldR > threshold) {
|
|
data[idx] = 255;
|
|
data[idx + 1] = 0;
|
|
data[idx + 2] = 0;
|
|
continue;
|
|
}
|
|
const gray = 0.299 * oldR + 0.587 * oldG + 0.114 * oldB;
|
|
const newPixel = gray < threshold ? 0 : 255;
|
|
const errR = (oldR - newPixel) * diffusion;
|
|
const errG = (oldG - newPixel) * diffusion;
|
|
const errB = (oldB - newPixel) * diffusion;
|
|
data[idx] = data[idx + 1] = data[idx + 2] = newPixel;
|
|
if (x + 1 < width) {
|
|
addColorError(data, idx + 4, errR, errG, errB, 1/8);
|
|
}
|
|
if (x + 2 < width) {
|
|
addColorError(data, idx + 8, errR, errG, errB, 1/8);
|
|
}
|
|
if (y + 1 < height) {
|
|
if (x - 1 >= 0) {
|
|
addColorError(data, idx + width * 4 - 4, errR, errG, errB, 1/8);
|
|
}
|
|
addColorError(data, idx + width * 4, errR, errG, errB, 1/8);
|
|
if (x + 1 < width) {
|
|
addColorError(data, idx + width * 4 + 4, errR, errG, errB, 1/8);
|
|
}
|
|
}
|
|
if (y + 2 < height) {
|
|
addColorError(data, idx + width * 8, errR, errG, errB, 1/8);
|
|
}
|
|
}
|
|
}
|
|
ctx.putImageData(imageData, 0, 0);
|
|
}
|
|
function applyBWRBayerDithering(ctx, width, height, threshold, diffusion = 1.0) {
|
|
const bayerThresholdMap = [
|
|
[15, 135, 45, 165],
|
|
[195, 75, 225, 105],
|
|
[60, 180, 30, 150],
|
|
[240, 120, 210, 90]
|
|
];
|
|
const imageData = ctx.getImageData(0, 0, width, height);
|
|
const data = imageData.data;
|
|
for (let i = 0; i < data.length; i += 4) {
|
|
const r = data[i];
|
|
const g = data[i + 1];
|
|
const b = data[i + 2];
|
|
const x = (i / 4) % width;
|
|
const y = Math.floor((i / 4) / width);
|
|
const mapValue = bayerThresholdMap[x % 4][y % 4];
|
|
const adjustedThreshold = threshold * (1 + (mapValue - 128) / 128 * diffusion);
|
|
const finalThreshold = clamp(adjustedThreshold, 0, 255);
|
|
if (r > g * 1.5 && r > b * 1.5 && r > finalThreshold) {
|
|
data[i] = 255;
|
|
data[i + 1] = 0;
|
|
data[i + 2] = 0;
|
|
} else {
|
|
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
const value = gray < finalThreshold ? 0 : 255;
|
|
data[i] = data[i + 1] = data[i + 2] = value;
|
|
}
|
|
}
|
|
ctx.putImageData(imageData, 0, 0);
|
|
}
|
|
function applyBWRStuckiDithering(ctx, width, height, threshold, diffusion = 1.0) {
|
|
const imageData = ctx.getImageData(0, 0, width, height);
|
|
const data = imageData.data;
|
|
for (let y = 0; y < height; y++) {
|
|
for (let x = 0; x < width; x++) {
|
|
const idx = (y * width + x) * 4;
|
|
const oldR = data[idx];
|
|
const oldG = data[idx + 1];
|
|
const oldB = data[idx + 2];
|
|
if (oldR > oldG * 1.5 && oldR > oldB * 1.5 && oldR > threshold) {
|
|
data[idx] = 255;
|
|
data[idx + 1] = 0;
|
|
data[idx + 2] = 0;
|
|
continue;
|
|
}
|
|
const gray = 0.299 * oldR + 0.587 * oldG + 0.114 * oldB;
|
|
const newPixel = gray < threshold ? 0 : 255;
|
|
const errR = (oldR - newPixel) * diffusion;
|
|
const errG = (oldG - newPixel) * diffusion;
|
|
const errB = (oldB - newPixel) * diffusion;
|
|
data[idx] = data[idx + 1] = data[idx + 2] = newPixel;
|
|
if (x + 1 < width) {
|
|
addColorError(data, idx + 4, errR, errG, errB, 8/42);
|
|
}
|
|
if (x + 2 < width) {
|
|
addColorError(data, idx + 8, errR, errG, errB, 4/42);
|
|
}
|
|
if (y + 1 < height) {
|
|
if (x - 2 >= 0) {
|
|
addColorError(data, idx + width * 4 - 8, errR, errG, errB, 2/42);
|
|
}
|
|
if (x - 1 >= 0) {
|
|
addColorError(data, idx + width * 4 - 4, errR, errG, errB, 4/42);
|
|
}
|
|
addColorError(data, idx + width * 4, errR, errG, errB, 8/42);
|
|
if (x + 1 < width) {
|
|
addColorError(data, idx + width * 4 + 4, errR, errG, errB, 4/42);
|
|
}
|
|
if (x + 2 < width) {
|
|
addColorError(data, idx + width * 4 + 8, errR, errG, errB, 2/42);
|
|
}
|
|
}
|
|
if (y + 2 < height) {
|
|
if (x - 2 >= 0) {
|
|
addColorError(data, idx + width * 8 - 8, errR, errG, errB, 1/42);
|
|
}
|
|
if (x - 1 >= 0) {
|
|
addColorError(data, idx + width * 8 - 4, errR, errG, errB, 2/42);
|
|
}
|
|
addColorError(data, idx + width * 8, errR, errG, errB, 4/42);
|
|
if (x + 1 < width) {
|
|
addColorError(data, idx + width * 8 + 4, errR, errG, errB, 2/42);
|
|
}
|
|
if (x + 2 < width) {
|
|
addColorError(data, idx + width * 8 + 8, errR, errG, errB, 1/42);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ctx.putImageData(imageData, 0, 0);
|
|
}
|
|
function applyBWRJarvisJudiceNinkeDithering(ctx, width, height, threshold, diffusion = 1.0) {
|
|
const imageData = ctx.getImageData(0, 0, width, height);
|
|
const data = imageData.data;
|
|
for (let y = 0; y < height; y++) {
|
|
for (let x = 0; x < width; x++) {
|
|
const idx = (y * width + x) * 4;
|
|
const oldR = data[idx];
|
|
const oldG = data[idx + 1];
|
|
const oldB = data[idx + 2];
|
|
if (oldR > oldG * 1.5 && oldR > oldB * 1.5 && oldR > threshold) {
|
|
data[idx] = 255;
|
|
data[idx + 1] = 0;
|
|
data[idx + 2] = 0;
|
|
continue;
|
|
}
|
|
const gray = 0.299 * oldR + 0.587 * oldG + 0.114 * oldB;
|
|
const newPixel = gray < threshold ? 0 : 255;
|
|
const errR = (oldR - newPixel) * diffusion;
|
|
const errG = (oldG - newPixel) * diffusion;
|
|
const errB = (oldB - newPixel) * diffusion;
|
|
data[idx] = data[idx + 1] = data[idx + 2] = newPixel;
|
|
if (x + 1 < width) {
|
|
addColorError(data, idx + 4, errR, errG, errB, 7/48);
|
|
}
|
|
if (x + 2 < width) {
|
|
addColorError(data, idx + 8, errR, errG, errB, 5/48);
|
|
}
|
|
if (y + 1 < height) {
|
|
if (x - 2 >= 0) {
|
|
addColorError(data, idx + width * 4 - 8, errR, errG, errB, 3/48);
|
|
}
|
|
if (x - 1 >= 0) {
|
|
addColorError(data, idx + width * 4 - 4, errR, errG, errB, 5/48);
|
|
}
|
|
addColorError(data, idx + width * 4, errR, errG, errB, 7/48);
|
|
if (x + 1 < width) {
|
|
addColorError(data, idx + width * 4 + 4, errR, errG, errB, 5/48);
|
|
}
|
|
if (x + 2 < width) {
|
|
addColorError(data, idx + width * 4 + 8, errR, errG, errB, 3/48);
|
|
}
|
|
}
|
|
if (y + 2 < height) {
|
|
if (x - 2 >= 0) {
|
|
addColorError(data, idx + width * 8 - 8, errR, errG, errB, 1/48);
|
|
}
|
|
if (x - 1 >= 0) {
|
|
addColorError(data, idx + width * 8 - 4, errR, errG, errB, 3/48);
|
|
}
|
|
addColorError(data, idx + width * 8, errR, errG, errB, 5/48);
|
|
if (x + 1 < width) {
|
|
addColorError(data, idx + width * 8 + 4, errR, errG, errB, 3/48);
|
|
}
|
|
if (x + 2 < width) {
|
|
addColorError(data, idx + width * 8 + 8, errR, errG, errB, 1/48);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ctx.putImageData(imageData, 0, 0);
|
|
}
|
|
function addColorError(data, idx, errR, errG, errB, factor) {
|
|
data[idx] = clamp(data[idx] + errR * factor, 0, 255);
|
|
data[idx + 1] = clamp(data[idx + 1] + errG * factor, 0, 255);
|
|
data[idx + 2] = clamp(data[idx + 2] + errB * factor, 0, 255);
|
|
}
|
|
const bwrPalette = [
|
|
[0, 0, 0, 255],
|
|
[255, 255, 255, 255],
|
|
[255, 0, 0, 255]
|
|
]
|
|
const bwPalette = [
|
|
[0, 0, 0, 255],
|
|
[255, 255, 255, 255],
|
|
]
|
|
function dithering(ctx, width, height, threshold, type) {
|
|
const diffusion = parseInt(document.getElementById('diffusion').value) / 100;
|
|
if (type.startsWith('bwr_')) {
|
|
const bwrType = type.replace('bwr_', '');
|
|
switch(bwrType) {
|
|
case 'none':
|
|
applyBWRNoneDithering(ctx, width, height, threshold, diffusion);
|
|
break;
|
|
case 'floydsteinberg':
|
|
applyBWRFloydSteinbergDithering(ctx, width, height, threshold, diffusion);
|
|
break;
|
|
case 'atkinson':
|
|
applyBWRAtkinsonDithering(ctx, width, height, threshold, diffusion);
|
|
break;
|
|
case 'bayer':
|
|
applyBWRBayerDithering(ctx, width, height, threshold, diffusion);
|
|
break;
|
|
case 'stucki':
|
|
applyBWRStuckiDithering(ctx, width, height, threshold, diffusion);
|
|
break;
|
|
case 'jarvis':
|
|
applyBWRJarvisJudiceNinkeDithering(ctx, width, height, threshold, diffusion);
|
|
break;
|
|
default:
|
|
applyBWRNoneDithering(ctx, width, height, threshold, diffusion);
|
|
}
|
|
} else {
|
|
switch(type) {
|
|
case 'none':
|
|
applyNoneDithering(ctx, width, height, threshold, false);
|
|
break;
|
|
case 'floydsteinberg':
|
|
applyFloydSteinbergDithering(ctx, width, height, threshold, diffusion, false);
|
|
break;
|
|
case 'atkinson':
|
|
applyAtkinsonDithering(ctx, width, height, threshold, diffusion, false);
|
|
break;
|
|
case 'bayer':
|
|
applyBayerDithering(ctx, width, height, threshold, diffusion, false);
|
|
break;
|
|
case 'stucki':
|
|
applyStuckiDithering(ctx, width, height, threshold, diffusion, false);
|
|
break;
|
|
case 'jarvis':
|
|
applyJarvisJudiceNinkeDithering(ctx, width, height, threshold, diffusion, false);
|
|
break;
|
|
default:
|
|
applyNoneDithering(ctx, width, height, threshold, false);
|
|
}
|
|
}
|
|
}
|
|
function canvas2bytes(canvas, type = 'bw') {
|
|
const ctx = canvas.getContext("2d");
|
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
const arr = [];
|
|
let buffer = [];
|
|
for (let y = 0; y < canvas.height; y++) {
|
|
for (let x = 0; x < canvas.width; x++) {
|
|
const index = (canvas.width * 4 * y) + x * 4;
|
|
if (type !== 'bwr') {
|
|
buffer.push(imageData.data[index] > 0 && imageData.data[index + 1] > 0 && imageData.data[index + 2] > 0 ? 1 : 0);
|
|
} else {
|
|
buffer.push(imageData.data[index] > 0 && imageData.data[index + 1] === 0 && imageData.data[index + 2] === 0 ? 1 : 0);
|
|
}
|
|
if (buffer.length === 8) {
|
|
arr.push(parseInt(buffer.join(''), 2));
|
|
buffer = [];
|
|
}
|
|
}
|
|
}
|
|
return arr;
|
|
}
|
|
|
|
function canvas2bytes_bw(canvas, type='bw') {
|
|
const ctx = canvas.getContext("2d");
|
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
|
|
const arr = [];
|
|
let buffer = [];
|
|
|
|
|
|
for (let x =canvas.width-1; x >= 0; x--) {
|
|
for (let y = 0; y < canvas.height; y++) {
|
|
const index = (canvas.width * 4 * y) + x * 4;
|
|
|
|
if (type !== 'bwr') {
|
|
buffer.push(imageData.data[index] > 0 && imageData.data[index+1] > 0 && imageData.data[index+2] > 0 ? 1 : 0);
|
|
} else {
|
|
buffer.push(imageData.data[index] > 0 && imageData.data[index+1] === 0 && imageData.data[index+2] === 0 ? 1 : 0);
|
|
}
|
|
|
|
if (buffer.length === 8) {
|
|
arr.push(parseInt(buffer.join(''), 2));
|
|
buffer = [];
|
|
}
|
|
}
|
|
}
|
|
return arr;
|
|
}
|
|
function canvas2bytes_to(canvas, type = 'bw') {
|
|
const ctx = canvas.getContext("2d");
|
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
const arr = [];
|
|
let buffer = [];
|
|
for (let y = 0; y < canvas.width; y++) {
|
|
for (let x = 0; x < canvas.height; x++) {
|
|
const index = (canvas.width * 4 * x) + y * 4;
|
|
if (type !== 'bwr') {
|
|
buffer.push(imageData.data[index] > 0 && imageData.data[index + 1] > 0 && imageData.data[index + 2] > 0 ? 1 : 0);
|
|
} else {
|
|
buffer.push(imageData.data[index] > 0 && imageData.data[index + 1] === 0 && imageData.data[index + 2] === 0 ? 1 : 0);
|
|
}
|
|
if (buffer.length === 8) {
|
|
arr.push(parseInt(buffer.join(''), 2));
|
|
buffer = [];
|
|
}
|
|
}
|
|
}
|
|
return arr;
|
|
}
|
|
function getColorDistance(rgba1, rgba2) {
|
|
const [r1, b1, g1] = rgba1;
|
|
const [r2, b2, g2] = rgba2;
|
|
const rm = (r1 + r2) / 2;
|
|
const r = r1 - r2;
|
|
const g = g1 - g2;
|
|
const b = b1 - b2;
|
|
return Math.sqrt((2 + rm / 256) * r * r + 4 * g * g + (2 + (255 - rm) / 256) * b * b);
|
|
}
|
|
function getNearColor(pixel, palette) {
|
|
let minDistance = 255 * 255 * 3 + 1;
|
|
let paletteIndex = 0;
|
|
for (let i = 0; i < palette.length; i++) {
|
|
const targetColor = palette[i];
|
|
const distance = getColorDistance(pixel, targetColor);
|
|
if (distance < minDistance) {
|
|
minDistance = distance;
|
|
paletteIndex = i;
|
|
}
|
|
}
|
|
return palette[paletteIndex];
|
|
}
|
|
function getNearColorV2(color, palette) {
|
|
let minDistanceSquared = 255 * 255 + 255 * 255 + 255 * 255 + 1;
|
|
let bestIndex = 0;
|
|
for (let i = 0; i < palette.length; i++) {
|
|
let rdiff = (color[0] & 0xff) - (palette[i][0] & 0xff);
|
|
let gdiff = (color[1] & 0xff) - (palette[i][1] & 0xff);
|
|
let bdiff = (color[2] & 0xff) - (palette[i][2] & 0xff);
|
|
let distanceSquared = rdiff * rdiff + gdiff * gdiff + bdiff * bdiff;
|
|
if (distanceSquared < minDistanceSquared) {
|
|
minDistanceSquared = distanceSquared;
|
|
bestIndex = i;
|
|
}
|
|
}
|
|
return palette[bestIndex];
|
|
}
|
|
function updatePixel(imageData, index, color) {
|
|
imageData[index] = color[0];
|
|
imageData[index + 1] = color[1];
|
|
imageData[index + 2] = color[2];
|
|
imageData[index + 3] = color[3];
|
|
}
|
|
function getColorErr(color1, color2, rate) {
|
|
const res = [];
|
|
for (let i = 0; i < 3; i++) {
|
|
res.push(Math.floor((color1[i] - color2[i]) / rate));
|
|
}
|
|
return res;
|
|
}
|
|
function updatePixelErr(imageData, index, err, rate) {
|
|
imageData[index] += err[0] * rate;
|
|
imageData[index + 1] += err[1] * rate;
|
|
imageData[index + 2] += err[2] * rate;
|
|
}
|
|
function ditheringCanvasByPalette(canvas, palette, type) {
|
|
palette = palette || bwrPalette;
|
|
const ctx = canvas.getContext('2d');
|
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
const w = imageData.width;
|
|
for (let currentPixel = 0; currentPixel <= imageData.data.length; currentPixel += 4) {
|
|
const newColor = getNearColorV2(imageData.data.slice(currentPixel, currentPixel + 4), palette);
|
|
if (type === "bwr_floydsteinberg") {
|
|
const err = getColorErr(imageData.data.slice(currentPixel, currentPixel + 4), newColor, 16);
|
|
updatePixel(imageData.data, currentPixel, newColor);
|
|
updatePixelErr(imageData.data, currentPixel + 4, err, 7);
|
|
updatePixelErr(imageData.data, currentPixel + 4 * w - 4, err, 3);
|
|
updatePixelErr(imageData.data, currentPixel + 4 * w, err, 5);
|
|
updatePixelErr(imageData.data, currentPixel + 4 * w + 4, err, 1);
|
|
} else {
|
|
const err = getColorErr(imageData.data.slice(currentPixel, currentPixel + 4), newColor, 8);
|
|
updatePixel(imageData.data, currentPixel, newColor);
|
|
updatePixelErr(imageData.data, currentPixel + 4, err, 1);
|
|
updatePixelErr(imageData.data, currentPixel + 8, err, 1);
|
|
updatePixelErr(imageData.data, currentPixel + 4 * w - 4, err, 1);
|
|
updatePixelErr(imageData.data, currentPixel + 4 * w, err, 1);
|
|
updatePixelErr(imageData.data, currentPixel + 4 * w + 4, err, 1);
|
|
updatePixelErr(imageData.data, currentPixel + 8 * w, err, 1);
|
|
}
|
|
}
|
|
ctx.putImageData(imageData, 0, 0);
|
|
}
|
|
|