(mousePosition?: Vec2)
| 64 | } |
| 65 | |
| 66 | updateShader(mousePosition?: Vec2): string { |
| 67 | const w = this.options.width * this.canvasDPI |
| 68 | const h = this.options.height * this.canvasDPI |
| 69 | |
| 70 | let maxScale = 0 |
| 71 | const rawValues: number[] = [] |
| 72 | |
| 73 | // Calculate displacement values |
| 74 | for (let y = 0; y < h; y++) { |
| 75 | for (let x = 0; x < w; x++) { |
| 76 | const uv: Vec2 = { x: x / w, y: y / h } |
| 77 | |
| 78 | const pos = this.options.fragment(uv, mousePosition) |
| 79 | const dx = pos.x * w - x |
| 80 | const dy = pos.y * h - y |
| 81 | |
| 82 | maxScale = Math.max(maxScale, Math.abs(dx), Math.abs(dy)) |
| 83 | rawValues.push(dx, dy) |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | // Improved normalization to prevent artifacts while maintaining intensity |
| 88 | if (maxScale > 0) { |
| 89 | maxScale = Math.max(maxScale, 1) // Ensure minimum scale to prevent over-normalization |
| 90 | } else { |
| 91 | maxScale = 1 |
| 92 | } |
| 93 | |
| 94 | // Create ImageData and fill it |
| 95 | const imageData = this.context.createImageData(w, h) |
| 96 | const data = imageData.data |
| 97 | |
| 98 | // Convert to image data with smoother normalization |
| 99 | let rawIndex = 0 |
| 100 | for (let y = 0; y < h; y++) { |
| 101 | for (let x = 0; x < w; x++) { |
| 102 | const dx = rawValues[rawIndex++] |
| 103 | const dy = rawValues[rawIndex++] |
| 104 | |
| 105 | // Smooth the displacement values at edges to prevent hard transitions |
| 106 | const edgeDistance = Math.min(x, y, w - x - 1, h - y - 1) |
| 107 | const edgeFactor = Math.min(1, edgeDistance / 2) // Smooth within 2 pixels of edge |
| 108 | |
| 109 | const smoothedDx = dx * edgeFactor |
| 110 | const smoothedDy = dy * edgeFactor |
| 111 | |
| 112 | const r = smoothedDx / maxScale + 0.5 |
| 113 | const g = smoothedDy / maxScale + 0.5 |
| 114 | |
| 115 | const pixelIndex = (y * w + x) * 4 |
| 116 | data[pixelIndex] = Math.max(0, Math.min(255, r * 255)) // Red channel (X displacement) |
| 117 | data[pixelIndex + 1] = Math.max(0, Math.min(255, g * 255)) // Green channel (Y displacement) |
| 118 | data[pixelIndex + 2] = Math.max(0, Math.min(255, g * 255)) // Blue channel (Y displacement for SVG filter compatibility) |
| 119 | data[pixelIndex + 3] = 255 // Alpha channel |
| 120 | } |
| 121 | } |
| 122 | |
| 123 | this.context.putImageData(imageData, 0, 0) |
no outgoing calls
no test coverage detected