()
| 513 | |
| 514 | // After the image loads, redraw it through a canvas with duotone applied |
| 515 | const apply = () => { |
| 516 | const w = img.naturalWidth |
| 517 | const h = img.naturalHeight |
| 518 | if (!w || !h) return |
| 519 | |
| 520 | const canvas = document.createElement('canvas') |
| 521 | canvas.width = w |
| 522 | canvas.height = h |
| 523 | const c = canvas.getContext('2d') |
| 524 | if (!c) return |
| 525 | |
| 526 | c.drawImage(img, 0, 0) |
| 527 | const imageData = c.getImageData(0, 0, w, h) |
| 528 | const data = imageData.data |
| 529 | |
| 530 | for (let i = 0; i < data.length; i += 4) { |
| 531 | // Convert to grayscale using luminance weights |
| 532 | const gray = (0.2126 * data[i] + 0.7152 * data[i + 1] + 0.0722 * data[i + 2]) / 255 |
| 533 | // Linearly interpolate between color1 (dark) and color2 (light) |
| 534 | data[i] = Math.round(rgb1.r + (rgb2.r - rgb1.r) * gray) |
| 535 | data[i + 1] = Math.round(rgb1.g + (rgb2.g - rgb1.g) * gray) |
| 536 | data[i + 2] = Math.round(rgb1.b + (rgb2.b - rgb1.b) * gray) |
| 537 | // Alpha channel (data[i+3]) is preserved |
| 538 | } |
| 539 | |
| 540 | c.putImageData(imageData, 0, 0) |
| 541 | img.src = canvas.toDataURL() |
| 542 | } |
| 543 | |
| 544 | if (img.complete && img.naturalWidth) { |
| 545 | apply() |
no outgoing calls
no test coverage detected