Please bookmark this page to avoid losing your image tool!

No Description Provided For An Image Tool

(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, gridSize = 4) {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    
    // Using the tool description "Любимая игра" ("Favorite game"), we transform 
    // the image into an interactive classic 15-Puzzle (Sliding Puzzle) game.
    
    // Validate inputs
    let cols = parseInt(gridSize);
    if (isNaN(cols) || cols < 2 || cols > 20) cols = 4;
    const rows = cols;
    const numTiles = cols * rows;

    // Determine canvas dimensions while maintaining interactivity limit
    const maxDim = 600; 
    let w = originalImg.width;
    let h = originalImg.height;
    
    if (w > maxDim || h > maxDim) {
        const aspect = w / h;
        if (w > h) { w = maxDim; h = maxDim / aspect; }
        else { h = maxDim; w = maxDim * aspect; }
    }
    
    // Ensure perfect division integers to avoid floating pixel gaps
    const tileW = Math.floor(w / cols);
    const tileH = Math.floor(h / rows);
    w = tileW * cols;
    h = tileH * rows;
    
    canvas.width = w;
    canvas.height = h;
    canvas.style.cursor = 'pointer';
    canvas.style.boxShadow = '0 4px 12px rgba(0,0,0,0.3)';
    canvas.style.borderRadius = '4px';
    
    let board = Array.from({ length: numTiles }, (_, i) => i);
    let emptyPos = numTiles - 1; // Start empty space at the very end
    let isSolved = false;

    function swap(i, j) {
        let temp = board[i];
        board[i] = board[j];
        board[j] = temp;
    }

    // Shuffle the board with valid simulated sliding moves to guarantee solvability
    function shuffle() {
        let lastPos = -1;
        // The higher the loops, the safer the shuffle but over 1000 is more than enough
        for (let i = 0; i < cols * cols * 100; i++) {
            let emptyCol = emptyPos % cols;
            let emptyRow = Math.floor(emptyPos / cols);
            let moves = [];
            
            if (emptyCol > 0) moves.push(emptyPos - 1);
            if (emptyCol < cols - 1) moves.push(emptyPos + 1);
            if (emptyRow > 0) moves.push(emptyPos - cols);
            if (emptyRow < rows - 1) moves.push(emptyPos + cols);
            
            // Do not undo the immediately previous move to ensure a deep shuffle
            moves = moves.filter(m => m !== lastPos);
            if (moves.length === 0) continue;
            
            let nextPos = moves[Math.floor(Math.random() * moves.length)];
            swap(emptyPos, nextPos);
            lastPos = emptyPos;
            emptyPos = nextPos;
        }
        
        let solvedCount = 0;
        for (let i = 0; i < numTiles; i++) {
            if (board[i] === i) solvedCount++;
        }
        // Very unlikely, but reshuffle if randomly perfectly solved
        if (solvedCount === numTiles) shuffle();
    }
    
    shuffle();

    function draw() {
        ctx.clearRect(0, 0, w, h);
        let correctCount = 0;
        
        for (let i = 0; i < numTiles; i++) {
            if (board[i] === i) correctCount++;
            let tileId = board[i];
            
            // Don't draw the empty slot until it's solved
            if (tileId === numTiles - 1 && !isSolved) {
                // Draw a dark background block for the hole
                ctx.fillStyle = '#111';
                ctx.fillRect((i % cols) * tileW, Math.floor(i / cols) * tileH, tileW, tileH);
                continue;
            }

            let sx = (tileId % cols) * (originalImg.width / cols);
            let sy = Math.floor(tileId / cols) * (originalImg.height / rows);
            let sWidth = originalImg.width / cols;
            let sHeight = originalImg.height / rows;

            let dx = (i % cols) * tileW;
            let dy = Math.floor(i / cols) * tileH;

            ctx.drawImage(originalImg, sx, sy, sWidth, sHeight, dx, dy, tileW, tileH);

            if (!isSolved) {
                // Draw tile borders to make it feel like 3D pieces
                ctx.strokeStyle = 'rgba(0, 0, 0, 0.8)';
                ctx.lineWidth = 1;
                ctx.strokeRect(dx, dy, tileW, tileH);
                
                ctx.strokeStyle = 'rgba(255, 255, 255, 0.4)';
                ctx.lineWidth = 1;
                ctx.beginPath();
                ctx.moveTo(dx + 1, dy + tileH - 1);
                ctx.lineTo(dx + 1, dy + 1);
                ctx.lineTo(dx + tileW - 1, dy + 1);
                ctx.stroke();
            }
        }

        // Check if solved
        if (correctCount === numTiles && !isSolved) {
            isSolved = true;
            // Draw one last time to fill the empty tile, then overlay winning text
            setTimeout(() => draw(), 50);
            return;
        }

        // If solved, overlay victory
        if (isSolved) {
            ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
            ctx.fillRect(0, 0, w, h);
            
            const fontSize = Math.max(30, Math.floor(w / 12));
            ctx.fillStyle = '#FFD700'; // Gold Color
            ctx.font = `bold ${fontSize}px "Segoe UI", sans-serif`;
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            // "ПОБЕДА" relates accurately to "Любимая игра"
            ctx.fillText('ПОБЕДА!', w / 2, h / 2 - fontSize * 0.3); 
            
            ctx.fillStyle = '#FFFFFF';
            ctx.font = `bold ${Math.max(14, Math.floor(fontSize / 2.5))}px "Segoe UI", sans-serif`;
            ctx.fillText('Click to play again', w / 2, h / 2 + fontSize * 0.8);
        }
    }

    draw();

    canvas.addEventListener('click', function(e) {
        if (isSolved) {
            // Reset and play again on click
            isSolved = false;
            shuffle();
            draw();
            return;
        }

        const rect = canvas.getBoundingClientRect();
        // Adjust coordinate mappings for responsive CSS scale factors
        const scaleX = canvas.width / rect.width;
        const scaleY = canvas.height / rect.height;
        
        const clickX = (e.clientX - rect.left) * scaleX;
        const clickY = (e.clientY - rect.top) * scaleY;

        const col = Math.floor(clickX / tileW);
        const row = Math.floor(clickY / tileH);
        
        // Prevent clicks physically out of canvas mathematical bounds
        if (col < 0 || col >= cols || row < 0 || row >= rows) return;

        const clickedPos = row * cols + col;
        const emptyCol = emptyPos % cols;
        const emptyRow = Math.floor(emptyPos / cols);

        // A valid slider piece allows exactly 1 block of manhattan distance to the empty piece
        const isAdjacent = Math.abs(col - emptyCol) + Math.abs(row - emptyRow) === 1;

        if (isAdjacent) {
            swap(clickedPos, emptyPos);
            emptyPos = clickedPos; // Update the memory state
            draw();
        }
    });

    // Provide some minimal help instructions on hover / title
    canvas.setAttribute('title', 'Sliding Game: Click adjacent tiles to move into the empty space!');

    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

This tool transforms any uploaded image into an interactive 15-puzzle (sliding puzzle) game. It divides your image into a grid of tiles and shuffles them, creating a playable challenge where you must click adjacent tiles to move them into the empty space and reconstruct the original image. This tool can be used for entertainment, creating personalized digital puzzles for gifts, or as an engaging way to interact with favorite photographs.

Leave a Reply

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