import type { ScanSettings } from '../types/scanner-effect-type.js'; import type { AdjustColorsSettings } from '../types/adjust-colors-type.js'; export function applyGreyscale(imageData: ImageData): void { const data = imageData.data; for (let j = 0; j < data.length; j += 4) { const grey = Math.round( 0.299 * data[j] + 0.587 * data[j + 1] + 0.114 * data[j + 2] ); data[j] = grey; data[j + 1] = grey; data[j + 2] = grey; } } export function applyInvertColors(imageData: ImageData): void { const data = imageData.data; for (let j = 0; j < data.length; j += 4) { data[j] = 255 - data[j]; data[j + 1] = 255 - data[j + 1]; data[j + 2] = 255 - data[j + 2]; } } export function rgbToHsl( r: number, g: number, b: number ): [number, number, number] { r /= 255; g /= 255; b /= 255; const max = Math.max(r, g, b); const min = Math.min(r, g, b); const l = (max + min) / 2; let h = 0; let s = 0; if (max !== min) { const d = max - min; s = l > 0.5 ? d / (2 - max - min) : d / (max + min); if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6; else if (max === g) h = ((b - r) / d + 2) / 6; else h = ((r - g) / d + 4) / 6; } return [h, s, l]; } export function hslToRgb( h: number, s: number, l: number ): [number, number, number] { if (s === 0) { const v = Math.round(l * 255); return [v, v, v]; } const hue2rgb = (p: number, q: number, t: number): number => { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1 / 6) return p + (q - p) * 6 * t; if (t < 1 / 2) return q; if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; return p; }; const q = l < 0.5 ? l * (1 + s) : l + s - l * s; const p = 2 * l - q; return [ Math.round(hue2rgb(p, q, h + 1 / 3) * 255), Math.round(hue2rgb(p, q, h) * 255), Math.round(hue2rgb(p, q, h - 1 / 3) * 255), ]; } export function applyScannerEffect( sourceData: ImageData, canvas: HTMLCanvasElement, settings: ScanSettings, rotationAngle: number, scale: number = 1 ): void { const ctx = canvas.getContext('2d')!; const w = sourceData.width; const h = sourceData.height; const scaledBlur = settings.blur * scale; const scaledNoise = settings.noise * scale; const workCanvas = document.createElement('canvas'); workCanvas.width = w; workCanvas.height = h; const workCtx = workCanvas.getContext('2d')!; if (scaledBlur > 0) { workCtx.filter = `blur(${scaledBlur}px)`; } workCtx.putImageData(sourceData, 0, 0); if (scaledBlur > 0) { const tempCanvas = document.createElement('canvas'); tempCanvas.width = w; tempCanvas.height = h; const tempCtx = tempCanvas.getContext('2d')!; tempCtx.filter = `blur(${scaledBlur}px)`; tempCtx.drawImage(workCanvas, 0, 0); workCtx.filter = 'none'; workCtx.clearRect(0, 0, w, h); workCtx.drawImage(tempCanvas, 0, 0); } const imageData = workCtx.getImageData(0, 0, w, h); const data = imageData.data; const contrastFactor = settings.contrast !== 0 ? (259 * (settings.contrast + 255)) / (255 * (259 - settings.contrast)) : 1; for (let i = 0; i < data.length; i += 4) { let r = data[i]; let g = data[i + 1]; let b = data[i + 2]; if (settings.grayscale) { const grey = Math.round(0.299 * r + 0.587 * g + 0.114 * b); r = grey; g = grey; b = grey; } if (settings.brightness !== 0) { r += settings.brightness; g += settings.brightness; b += settings.brightness; } if (settings.contrast !== 0) { r = contrastFactor * (r - 128) + 128; g = contrastFactor * (g - 128) + 128; b = contrastFactor * (b - 128) + 128; } if (settings.yellowish > 0) { const intensity = settings.yellowish / 50; r += 20 * intensity; g += 12 * intensity; b -= 15 * intensity; } if (scaledNoise > 0) { const n = (Math.random() - 0.5) * scaledNoise; r += n; g += n; b += n; } data[i] = Math.max(0, Math.min(255, r)); data[i + 1] = Math.max(0, Math.min(255, g)); data[i + 2] = Math.max(0, Math.min(255, b)); } workCtx.putImageData(imageData, 0, 0); if (settings.border) { const borderSize = Math.max(w, h) * 0.02; const gradient1 = workCtx.createLinearGradient(0, 0, borderSize, 0); gradient1.addColorStop(0, 'rgba(0,0,0,0.3)'); gradient1.addColorStop(1, 'rgba(0,0,0,0)'); workCtx.fillStyle = gradient1; workCtx.fillRect(0, 0, borderSize, h); const gradient2 = workCtx.createLinearGradient(w, 0, w - borderSize, 0); gradient2.addColorStop(0, 'rgba(0,0,0,0.3)'); gradient2.addColorStop(1, 'rgba(0,0,0,0)'); workCtx.fillStyle = gradient2; workCtx.fillRect(w - borderSize, 0, borderSize, h); const gradient3 = workCtx.createLinearGradient(0, 0, 0, borderSize); gradient3.addColorStop(0, 'rgba(0,0,0,0.3)'); gradient3.addColorStop(1, 'rgba(0,0,0,0)'); workCtx.fillStyle = gradient3; workCtx.fillRect(0, 0, w, borderSize); const gradient4 = workCtx.createLinearGradient(0, h, 0, h - borderSize); gradient4.addColorStop(0, 'rgba(0,0,0,0.3)'); gradient4.addColorStop(1, 'rgba(0,0,0,0)'); workCtx.fillStyle = gradient4; workCtx.fillRect(0, h - borderSize, w, borderSize); } if (rotationAngle !== 0) { const rad = (rotationAngle * Math.PI) / 180; const cos = Math.abs(Math.cos(rad)); const sin = Math.abs(Math.sin(rad)); const newW = Math.ceil(w * cos + h * sin); const newH = Math.ceil(w * sin + h * cos); canvas.width = newW; canvas.height = newH; ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, newW, newH); ctx.translate(newW / 2, newH / 2); ctx.rotate(rad); ctx.drawImage(workCanvas, -w / 2, -h / 2); ctx.setTransform(1, 0, 0, 1, 0, 0); } else { canvas.width = w; canvas.height = h; ctx.drawImage(workCanvas, 0, 0); } } export function applyColorAdjustments( sourceData: ImageData, canvas: HTMLCanvasElement, settings: AdjustColorsSettings ): void { const ctx = canvas.getContext('2d')!; const w = sourceData.width; const h = sourceData.height; canvas.width = w; canvas.height = h; const imageData = new ImageData(new Uint8ClampedArray(sourceData.data), w, h); const data = imageData.data; const contrastFactor = settings.contrast !== 0 ? (259 * (settings.contrast + 255)) / (255 * (259 - settings.contrast)) : 1; const gammaCorrection = settings.gamma !== 1.0 ? 1 / settings.gamma : 1; const sepiaAmount = settings.sepia / 100; for (let i = 0; i < data.length; i += 4) { let r = data[i]; let g = data[i + 1]; let b = data[i + 2]; if (settings.brightness !== 0) { const adj = settings.brightness * 2.55; r += adj; g += adj; b += adj; } if (settings.contrast !== 0) { r = contrastFactor * (r - 128) + 128; g = contrastFactor * (g - 128) + 128; b = contrastFactor * (b - 128) + 128; } if (settings.saturation !== 0 || settings.hueShift !== 0) { const [hue, sat, lig] = rgbToHsl( Math.max(0, Math.min(255, r)), Math.max(0, Math.min(255, g)), Math.max(0, Math.min(255, b)) ); let newHue = hue; if (settings.hueShift !== 0) { newHue = (hue + settings.hueShift / 360) % 1; if (newHue < 0) newHue += 1; } let newSat = sat; if (settings.saturation !== 0) { const satAdj = settings.saturation / 100; newSat = satAdj > 0 ? sat + (1 - sat) * satAdj : sat * (1 + satAdj); newSat = Math.max(0, Math.min(1, newSat)); } [r, g, b] = hslToRgb(newHue, newSat, lig); } if (settings.temperature !== 0) { const t = settings.temperature / 50; r += 30 * t; b -= 30 * t; } if (settings.tint !== 0) { const t = settings.tint / 50; g += 30 * t; } if (settings.gamma !== 1.0) { r = Math.pow(Math.max(0, Math.min(255, r)) / 255, gammaCorrection) * 255; g = Math.pow(Math.max(0, Math.min(255, g)) / 255, gammaCorrection) * 255; b = Math.pow(Math.max(0, Math.min(255, b)) / 255, gammaCorrection) * 255; } if (settings.sepia > 0) { const sr = 0.393 * r + 0.769 * g + 0.189 * b; const sg = 0.349 * r + 0.686 * g + 0.168 * b; const sb = 0.272 * r + 0.534 * g + 0.131 * b; r = r + (sr - r) * sepiaAmount; g = g + (sg - g) * sepiaAmount; b = b + (sb - b) * sepiaAmount; } data[i] = Math.max(0, Math.min(255, r)); data[i + 1] = Math.max(0, Math.min(255, g)); data[i + 2] = Math.max(0, Math.min(255, b)); } ctx.putImageData(imageData, 0, 0); }