Download script.js from JSWAI/MKT: direct link, hf CLI and curl.
- Browser
- Download file 12.1 kB
-
https://huggingface.co/spaces/JSWAI/MKT/resolve/main/script.js
- Command line
-
hf download hf://spaces/JSWAI/MKT/script.js
-
curl -L -o script.js https://huggingface.co/spaces/JSWAI/MKT/resolve/main/script.js
12.1 kB
| /** | |
| * AI Masking & Distortion Engine | |
| * Handles drawing the mask, calculating the "outside" area, | |
| * and applying the distortion effect. | |
| */ | |
| const AIParser = { | |
| // Configuration for the distortion | |
| settings: { | |
| noiseIntensity: 0.04, // How much "grain" to add (visible to AI, invisible to human) | |
| displacementScale: 4, // How much to warp the outside pixels | |
| blurAmount: 2, // Slight blur outside to blend edges | |
| noiseOpacity: 0.8 // Opacity of the noise layer | |
| }, | |
| // State | |
| canvas: null, | |
| ctx: null, | |
| imgElement: null, | |
| points: [], // Array of {x, y} in percentage (0-100) | |
| // DOM References | |
| container: null, | |
| canvasEl: null, | |
| svgOverlay: null, | |
| init: (imgElement, containerEl) => { | |
| AIParser.imgElement = imgElement; | |
| AIParser.container = containerEl; | |
| // Create a hidden canvas for processing the distortion | |
| AIParser.canvas = document.createElement('canvas'); | |
| AIParser.ctx = AIParser.canvas.getContext('2d'); | |
| // We use an SVG overlay for the visible mask lines | |
| 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); | |
| // Wait for image to load before setting canvas size | |
| imgElement.onload = () => { | |
| AIParser.resizeCanvas(); | |
| }; | |
| // Fallback if image is already loaded | |
| if (imgElement.complete) AIParser.resizeCanvas(); | |
| }, | |
| resizeCanvas: () => { | |
| const img = AIParser.imgElement; | |
| const container = AIParser.container; | |
| // Set canvas size to match the container/display size, not necessarily natural size | |
| const rect = container.getBoundingClientRect(); | |
| AIParser.canvas.width = rect.width; | |
| AIParser.canvas.height = rect.height; | |
| // Redraw if points exist | |
| if (AIParser.points.length > 0) { | |
| AIParser.applyDistortion(); | |
| } else { | |
| AIParser.clearDistortion(); | |
| } | |
| }, | |
| // 1. Add a point to the mask | |
| addPoint: (xPercent, yPercent) => { | |
| AIParser.points.push({ x: xPercent, y: yPercent }); | |
| AIParser.drawSVG(); | |
| AIParser.applyDistortion(); | |
| }, | |
| // 2. Draw the visible yellow mask line (SVG) | |
| drawSVG: () => { | |
| const svg = AIParser.svgOverlay; | |
| svg.innerHTML = ''; // Clear previous | |
| if (AIParser.points.length === 0) return; | |
| // Create the path data | |
| 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'; | |
| // Create SVG Path Element | |
| const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); | |
| path.setAttribute("d", pathData); | |
| path.setAttribute("fill", "none"); | |
| path.setAttribute("stroke", "#FFD700"); // Yellow highlight | |
| path.setAttribute("stroke-width", "2"); | |
| path.setAttribute("stroke-dasharray", "5,5"); | |
| // Add a subtle glow for visibility | |
| 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); | |
| }, | |
| // 3. The Core: Distort the OUTSIDE area for AI | |
| applyDistortion: () => { | |
| if (AIParser.points.length < 3) return; | |
| const ctx = AIParser.ctx; | |
| const w = AIParser.canvas.width; | |
| const h = AIParser.canvas.height; | |
| // Clear canvas | |
| ctx.clearRect(0, 0, w, h); | |
| // A. Draw the original image cleanly | |
| ctx.drawImage(AIParser.imgElement, 0, 0, w, h); | |
| // B. Define the Mask Path | |
| 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(); | |
| // C. Isolate the INSIDE (so we don't distort the subject) | |
| // We use 'destination-out' to clear the inside, or we can just draw | |
| // a giant rectangle with a hole (evenodd rule) over the outside. | |
| // Strategy: Draw a giant rectangle covering everything, | |
| // then subtract the mask shape using 'evenodd' fill rule. | |
| // 1. Save context and clear the mask area from the current image (make it transparent) | |
| ctx.save(); | |
| ctx.globalCompositeOperation = 'destination-out'; | |
| ctx.fill(); // Removes the inside of the mask | |
| ctx.restore(); | |
| // 2. Now the "Inside" is transparent. The "Outside" has the original image. | |
| // We want to distort the "Outside" (which is currently visible). | |
| // We will draw the image again over the whole canvas, but | |
| // apply a filter that only affects the outside? | |
| // Actually, it's easier to just draw the distorted version on top | |
| // where the original is. | |
| // Let's just draw a distorted version of the image, but masked | |
| // so only the OUTSIDE shows. | |
| // Create a temporary canvas for the distorted image | |
| const tempCanvas = document.createElement('canvas'); | |
| tempCanvas.width = w; | |
| tempCanvas.height = h; | |
| const tempCtx = tempCanvas.getContext('2d'); | |
| // Draw original to temp | |
| tempCtx.drawImage(AIParser.imgElement, 0, 0, w, h); | |
| // Apply Noise/Displacement to Temp Canvas | |
| AIParser.applyNoiseToContext(tempCtx, w, h); | |
| // Now, we need to composite the Temp Canvas (distorted) | |
| // ONLY onto the Outside area of the Main Canvas. | |
| // Set composite mode: Only draw where there is existing content (the outside) | |
| ctx.save(); | |
| ctx.globalCompositeOperation = 'source-over'; | |
| // We need to ensure we only draw on the "Outside". | |
| // Since we cleared the Inside with destination-out, | |
| // we can just draw the temp canvas. But we need to mask it | |
| // so we don't draw over the inside. | |
| // Actually, simpler approach: | |
| // 1. We have the original image on the Main Canvas, but the INSIDE is transparent. | |
| // 2. We draw the Distorted Image on the Main Canvas. | |
| // 3. The Inside remains transparent (showing original inside? No, we need original inside). | |
| // REVISED STRATEGY: | |
| // 1. Draw Original Image to Main Canvas. | |
| // 2. Create a "Mask" of the outside area (white inside mask, black outside? No). | |
| // Let's just draw the Distorted version over the Original, but | |
| // use a global composite that respects the current pixel alpha? | |
| // Let's go with the "Hole" method. | |
| // Clear the Main Canvas. | |
| ctx.clearRect(0, 0, w, h); | |
| // Draw Original Image | |
| ctx.drawImage(AIParser.imgElement, 0, 0, w, h); | |
| // Cut out the inside of the mask (make it transparent) | |
| 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(); | |
| // Now Main Canvas has: Inside=Transparent, Outside=Original | |
| // Draw the Distorted Image | |
| // We want to draw it where the Outside is. | |
| ctx.globalCompositeOperation = 'source-over'; | |
| ctx.drawImage(tempCanvas, 0, 0); | |
| // Restore context | |
| ctx.restore(); | |
| // Apply a final global blur to the outside to blend edges if needed | |
| // (Optional, but helps AI ignore edge artifacts) | |
| // ctx.filter = `blur(${AIParser.settings.blurAmount}px)`; | |
| // ctx.drawImage(AIParser.canvas, 0, 0); | |
| // ctx.filter = 'none'; | |
| // Update the DOM image to show this canvas | |
| // Note: In a real tool, you might not update the DOM img immediately | |
| // but show it as a "preview" layer. Here we update the source for preview. | |
| // AIParser.imgElement.src = AIParser.canvas.toDataURL(); | |
| }, | |
| // Helper: Adds noise and displacement to a context | |
| applyNoiseToContext: (ctx, w, h) => { | |
| const imageData = ctx.getImageData(0, 0, w, h); | |
| const data = imageData.data; | |
| const len = data.length; | |
| // Generate a noise map (simple random) | |
| 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); | |
| // Random displacement | |
| const noise = noiseMap[x + y * w]; | |
| const dx = (noise - 0.5) * AIParser.settings.displacementScale; | |
| const dy = (noise - 0.5) * AIParser.settings.displacementScale; | |
| // Sample color from offset position | |
| 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; | |
| // Add noise grain | |
| const noiseVal = (Math.random() - 0.5) * 30 * AIParser.settings.noiseIntensity; | |
| data[i] = data[srcIdx] + noiseVal; // R | |
| data[i + 1] = data[srcIdx + 1] + noiseVal; // G | |
| data[i + 2] = data[srcIdx + 2] + noiseVal; // B | |
| // Alpha remains unchanged | |
| } | |
| ctx.putImageData(imageData, 0, 0); | |
| }, | |
| clearDistortion: () => { | |
| if (AIParser.imgElement) { | |
| // Reset to original | |
| AIParser.ctx.clearRect(0, 0, AIParser.canvas.width, AIParser.canvas.height); | |
| AIParser.ctx.drawImage(AIParser.imgElement, 0, 0); | |
| } | |
| } | |
| }; | |
| // --- Initialization Example --- | |
| document.addEventListener('DOMContentLoaded', () => { | |
| const img = document.getElementById('canvasBaseImg'); | |
| const wrapper = document.getElementById('canvasWrapper'); | |
| // Initialize the parser | |
| AIParser.init(img, wrapper); | |
| // Example: Add points on click | |
| wrapper.addEventListener('click', (e) => { | |
| if (e.target.tagName === 'path') return; // Ignore if clicking SVG paths | |
| 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); | |
| }); | |
| }); | |
| ``` |