| |
| |
| |
| |
| |
|
|
| const AIParser = { |
| |
| settings: { |
| noiseIntensity: 0.04, |
| displacementScale: 4, |
| blurAmount: 2, |
| noiseOpacity: 0.8 |
| }, |
|
|
| |
| canvas: null, |
| ctx: null, |
| imgElement: null, |
| points: [], |
| |
| |
| container: null, |
| canvasEl: null, |
| svgOverlay: null, |
|
|
| init: (imgElement, containerEl) => { |
| AIParser.imgElement = imgElement; |
| AIParser.container = containerEl; |
|
|
| |
| AIParser.canvas = document.createElement('canvas'); |
| AIParser.ctx = AIParser.canvas.getContext('2d'); |
| |
| |
| AIParser.svgOverlay = document.createElementNS("http://www.w3.org/2000/svg", "svg"); |
| AIParser.svgOverlay.style.position = 'absolute'; |
| AIParser.svgOverlay.style.top = '0'; |
| AIParser.svgOverlay.style.left = '0'; |
| AIParser.svgOverlay.style.pointerEvents = 'none'; |
| AIParser.svgOverlay.style.zIndex = '10'; |
| containerEl.appendChild(AIParser.svgOverlay); |
|
|
| |
| imgElement.onload = () => { |
| AIParser.resizeCanvas(); |
| }; |
| |
| |
| if (imgElement.complete) AIParser.resizeCanvas(); |
| }, |
|
|
| resizeCanvas: () => { |
| const img = AIParser.imgElement; |
| const container = AIParser.container; |
| |
| |
| const rect = container.getBoundingClientRect(); |
| AIParser.canvas.width = rect.width; |
| AIParser.canvas.height = rect.height; |
| |
| |
| if (AIParser.points.length > 0) { |
| AIParser.applyDistortion(); |
| } else { |
| AIParser.clearDistortion(); |
| } |
| }, |
|
|
| |
| addPoint: (xPercent, yPercent) => { |
| AIParser.points.push({ x: xPercent, y: yPercent }); |
| AIParser.drawSVG(); |
| AIParser.applyDistortion(); |
| }, |
|
|
| |
| drawSVG: () => { |
| const svg = AIParser.svgOverlay; |
| svg.innerHTML = ''; |
|
|
| if (AIParser.points.length === 0) return; |
|
|
| |
| let pathData = `M ${AIParser.points[0].x}% ${AIParser.points[0].y}%`; |
| for (let i = 1; i < AIParser.points.length; i++) { |
| pathData += ` L ${AIParser.points[i].x}% ${AIParser.points[i].y}%`; |
| } |
| pathData += ' Z'; |
|
|
| |
| const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); |
| path.setAttribute("d", pathData); |
| path.setAttribute("fill", "none"); |
| path.setAttribute("stroke", "#FFD700"); |
| path.setAttribute("stroke-width", "2"); |
| path.setAttribute("stroke-dasharray", "5,5"); |
| |
| |
| const filter = document.createElementNS("http://www.w3.org/2000/svg", "filter"); |
| filter.setAttribute("id", "glow"); |
| const blur = document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur"); |
| blur.setAttribute("stdDeviation", "2"); |
| blur.setAttribute("result", "coloredBlur"); |
| const merge = document.createElementNS("http://www.w3.org/2000/svg", "feMerge"); |
| const mergeNode1 = document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode"); |
| mergeNode1.setAttribute("in", "coloredBlur"); |
| const mergeNode2 = document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode"); |
| mergeNode2.setAttribute("in", "SourceGraphic"); |
| merge.appendChild(mergeNode1); |
| merge.appendChild(mergeNode2); |
| filter.appendChild(blur); |
| filter.appendChild(merge); |
| |
| svg.appendChild(filter); |
| path.setAttribute("filter", "url(#glow)"); |
| svg.appendChild(path); |
| }, |
|
|
| |
| applyDistortion: () => { |
| if (AIParser.points.length < 3) return; |
|
|
| const ctx = AIParser.ctx; |
| const w = AIParser.canvas.width; |
| const h = AIParser.canvas.height; |
| |
| |
| ctx.clearRect(0, 0, w, h); |
|
|
| |
| ctx.drawImage(AIParser.imgElement, 0, 0, w, h); |
|
|
| |
| ctx.beginPath(); |
| const startX = (AIParser.points[0].x / 100) * w; |
| const startY = (AIParser.points[0].y / 100) * h; |
| ctx.moveTo(startX, startY); |
|
|
| for (let i = 1; i < AIParser.points.length; i++) { |
| const x = (AIParser.points[i].x / 100) * w; |
| const y = (AIParser.points[i].y / 100) * h; |
| ctx.lineTo(x, y); |
| } |
| ctx.closePath(); |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| ctx.save(); |
| ctx.globalCompositeOperation = 'destination-out'; |
| ctx.fill(); |
| ctx.restore(); |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const tempCanvas = document.createElement('canvas'); |
| tempCanvas.width = w; |
| tempCanvas.height = h; |
| const tempCtx = tempCanvas.getContext('2d'); |
| |
| |
| tempCtx.drawImage(AIParser.imgElement, 0, 0, w, h); |
| |
| |
| AIParser.applyNoiseToContext(tempCtx, w, h); |
| |
| |
| |
| |
| |
| ctx.save(); |
| ctx.globalCompositeOperation = 'source-over'; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| ctx.clearRect(0, 0, w, h); |
| |
| |
| ctx.drawImage(AIParser.imgElement, 0, 0, w, h); |
| |
| |
| ctx.globalCompositeOperation = 'destination-out'; |
| ctx.beginPath(); |
| ctx.moveTo(startX, startY); |
| for (let i = 1; i < AIParser.points.length; i++) { |
| ctx.lineTo((AIParser.points[i].x / 100) * w, (AIParser.points[i].y / 100) * h); |
| } |
| ctx.closePath(); |
| ctx.fill(); |
| |
| |
| |
| |
| |
| ctx.globalCompositeOperation = 'source-over'; |
| ctx.drawImage(tempCanvas, 0, 0); |
| |
| |
| ctx.restore(); |
|
|
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| }, |
|
|
| |
| applyNoiseToContext: (ctx, w, h) => { |
| const imageData = ctx.getImageData(0, 0, w, h); |
| const data = imageData.data; |
| const len = data.length; |
|
|
| |
| const noiseMap = new Float32Array(w * h); |
| for (let i = 0; i < w * h; i++) { |
| noiseMap[i] = Math.random(); |
| } |
|
|
| for (let i = 0; i < len; i += 4) { |
| const x = (i / 4) % w; |
| const y = Math.floor((i / 4) / w); |
| |
| |
| const noise = noiseMap[x + y * w]; |
| const dx = (noise - 0.5) * AIParser.settings.displacementScale; |
| const dy = (noise - 0.5) * AIParser.settings.displacementScale; |
| |
| |
| const srcX = Math.min(w - 1, Math.max(0, x + dx)); |
| const srcY = Math.min(h - 1, Math.max(0, y + dy)); |
| const srcIdx = (Math.floor(srcY) * w + Math.floor(srcX)) * 4; |
| |
| |
| const noiseVal = (Math.random() - 0.5) * 30 * AIParser.settings.noiseIntensity; |
| |
| data[i] = data[srcIdx] + noiseVal; |
| data[i + 1] = data[srcIdx + 1] + noiseVal; |
| data[i + 2] = data[srcIdx + 2] + noiseVal; |
| |
| } |
| |
| ctx.putImageData(imageData, 0, 0); |
| }, |
|
|
| clearDistortion: () => { |
| if (AIParser.imgElement) { |
| |
| AIParser.ctx.clearRect(0, 0, AIParser.canvas.width, AIParser.canvas.height); |
| AIParser.ctx.drawImage(AIParser.imgElement, 0, 0); |
| } |
| } |
| }; |
|
|
| |
| document.addEventListener('DOMContentLoaded', () => { |
| const img = document.getElementById('canvasBaseImg'); |
| const wrapper = document.getElementById('canvasWrapper'); |
| |
| |
| AIParser.init(img, wrapper); |
| |
| |
| wrapper.addEventListener('click', (e) => { |
| if (e.target.tagName === 'path') return; |
| |
| const rect = wrapper.getBoundingClientRect(); |
| const x = ((e.clientX - rect.left) / rect.width) * 100; |
| const y = ((e.clientY - rect.top) / rect.height) * 100; |
| |
| AIParser.addPoint(x, y); |
| }); |
| }); |
| ``` |