Please bookmark this page to avoid losing your image tool!

Image Hypnosis Effect Generator

(Free & Supports Bulk Upload)

Drag & drop your images here or

The result will appear here...
You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, intensity = 15.0, speed = 2.0, spiralArms = 6.0) {
    // Parse parameters to ensure they are valid numbers
    const twistIntensity = Number(intensity) || 15.0;
    const twistSpeed = Number(speed) || 2.0;
    const armsCount = Number(spiralArms) || 6.0;

    const canvas = document.createElement('canvas');
    canvas.width = originalImg.width;
    canvas.height = originalImg.height;
    
    // Attempt to get WebGL context
    const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');

    // Fallback if WebGL isn't supported
    if (!gl) {
        console.warn("WebGL not supported, returning unmodified image.");
        const ctx = canvas.getContext('2d');
        ctx.drawImage(originalImg, 0, 0);
        return canvas;
    }

    // Vertex Shader: passes positions and creates UV coordinates
    const vsSource = `
        attribute vec2 a_position;
        varying vec2 v_texCoord;
        void main() {
            gl_Position = vec4(a_position, 0.0, 1.0);
            v_texCoord = a_position * 0.5 + 0.5; // Map from [-1, 1] to [0, 1]
        }
    `;

    // Fragment Shader: core logic for the hypnosis effect
    const fsSource = `
        precision mediump float;
        varying vec2 v_texCoord;
        
        uniform sampler2D u_image;
        uniform vec2 u_resolution;
        uniform float u_time;
        uniform float u_intensity;
        uniform float u_speed;
        uniform float u_arms;

        void main() {
            vec2 center = vec2(0.5, 0.5);
            vec2 uv = v_texCoord;
            
            // Adjust to maintain a perfect circular effect regardless of aspect ratio
            float maxSide = max(u_resolution.x, u_resolution.y);
            vec2 aspect = u_resolution / maxSide; 
            
            vec2 rel = (uv - center) * aspect;
            float dist = length(rel);
            
            float angle = atan(rel.y, rel.x);
            
            // Create a twist based on distance (stronger in the center)
            float twistAmount = u_intensity * smoothstep(1.2, 0.0, dist);
            
            // Add animation time for continuous swirling
            float currentAngle = angle + twistAmount - (u_time * u_speed);
            
            // Calculate new UVs applied with the distortion
            vec2 newRel = vec2(cos(currentAngle), sin(currentAngle)) * dist;
            vec2 newUv = center + (newRel / aspect);
            
            // Mirrored wrap (ping-pong repetition) for out-of-bounds UVs
            vec2 mirroredUv = 1.0 - abs(mod(newUv, 2.0) - 1.0);
            
            vec4 color = texture2D(u_image, mirroredUv);
            
            // Create a counter-rotating spiral overlay for an extra hypnotic visual
            float spiral = sin(dist * 35.0 + angle * u_arms + u_time * u_speed * 4.0);
            float spiralOverlay = 0.85 + 0.15 * spiral;
            
            // Pulsing vignette (darkening) at edges
            float pulse = 0.5 + 0.5 * sin(u_time * 2.0);
            float vignette = smoothstep(1.1, 0.3, dist);
            
            // Composite the final color
            vec3 finalColor = color.rgb * spiralOverlay * (0.6 + 0.4 * vignette + 0.1 * pulse);
            
            gl_FragColor = vec4(finalColor, color.a);
        }
    `;

    // Helper to compile shaders
    function createShader(gl, type, source) {
        const shader = gl.createShader(type);
        gl.shaderSource(shader, source);
        gl.compileShader(shader);
        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
            console.error(gl.getShaderInfoLog(shader));
            gl.deleteShader(shader);
            return null;
        }
        return shader;
    }

    const vertexShader = createShader(gl, gl.VERTEX_SHADER, vsSource);
    const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fsSource);

    // Link shader program
    const program = gl.createProgram();
    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);
    gl.linkProgram(program);
    
    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
        console.error("Program link failed", gl.getProgramInfoLog(program));
        return canvas;
    }

    gl.useProgram(program);

    // Setup viewport quad positions
    const positionBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    const positions = [
        -1.0, -1.0,
         1.0, -1.0,
        -1.0,  1.0,
         1.0,  1.0,
    ];
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

    const positionLocation = gl.getAttribLocation(program, "a_position");
    gl.enableVertexAttribArray(positionLocation);
    gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

    // Upload Image Texture
    const texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); // Ensure Image is right side up in WebGL coordinates

    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, originalImg);

    // Map Uniforms
    const resolutionLocation = gl.getUniformLocation(program, "u_resolution");
    const timeLocation = gl.getUniformLocation(program, "u_time");
    const intensityLocation = gl.getUniformLocation(program, "u_intensity");
    const speedLocation = gl.getUniformLocation(program, "u_speed");
    const armsLocation = gl.getUniformLocation(program, "u_arms");

    gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
    gl.uniform1f(intensityLocation, twistIntensity);
    gl.uniform1f(speedLocation, twistSpeed);
    gl.uniform1f(armsLocation, armsCount);

    let startTime = performance.now();
    let animationFrameId;

    function render(now) {
        // Calculate elapsed time in seconds
        const time = (now - startTime) / 1000.0;
        gl.uniform1f(timeLocation, time);

        // Draw Frame
        gl.viewport(0, 0, canvas.width, canvas.height);
        gl.clearColor(0, 0, 0, 1);
        gl.clear(gl.COLOR_BUFFER_BIT);
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

        // Continuous Hypnosis loop
        animationFrameId = requestAnimationFrame(render);
    }

    // Attach a stop function in case the caller wants to clear resources cleanly
    canvas.stopHypnosis = () => {
        cancelAnimationFrame(animationFrameId);
    };

    // Start the hypnotic loop
    requestAnimationFrame(render);

    return canvas;
}

Free Image Tool Creator

Can't find the image tool you're looking for?
Create one based on your own needs now!

Description

The Image Hypnosis Effect Generator is a tool that transforms static images into dynamic, swirling visual experiences. By applying WebGL-powered shaders, it creates a continuous hypnotic motion through twisting distortions, rotating spiral overlays, and pulsing vignette effects. Users can customize the visual output by adjusting parameters such as the intensity of the twist, the speed of the animation, and the number of spiral arms. This tool is ideal for creating psychedelic digital art, unique background visuals for music videos, or engaging social media content and motion graphics.

Leave a Reply

Your email address will not be published. Required fields are marked *