Please bookmark this page to avoid losing your image tool!

Image Dialogue Removal And Repair Tool For Manga Panels

(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.
/**
 * Removes dialogue from a manga panel by identifying color-based regions,
 * blanking them out (including text inside), and then inpainting the area
 * with surrounding background pixels.
 *
 * @param {Image} originalImg The original Image object of the manga panel.
 * @param {string} targetColorStr A comma-separated RGB string for the bubble color to target, e.g., "255,255,255" for white.
 * @param {number} colorThreshold The tolerance for color matching (0-765). A higher value is more lenient. 60 is a good starting point.
 * @returns {HTMLCanvasElement} A new canvas element with the dialogue removed and repaired.
 */
function processImage(originalImg, targetColorStr = '255,255,255', colorThreshold = 60) {
    // 1. --- Parameter Parsing and Canvas Setup ---
    const targetColor = targetColorStr.split(',').map(Number);
    if (targetColor.length !== 3 || targetColor.some(isNaN)) {
        console.error("Invalid targetColorStr. Using default '255,255,255'.");
        targetColor = [255, 255, 255];
    }
    const [targetR, targetG, targetB] = targetColor;

    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d', { willReadFrequently: true });
    const { width, height } = originalImg;
    canvas.width = width;
    canvas.height = height;
    ctx.drawImage(originalImg, 0, 0);
    const imageData = ctx.getImageData(0, 0, width, height);
    const data = imageData.data;

    // 2. --- Mask Creation: Identify potential bubble regions based on color ---
    // Mask states: 0 = source pixel, 1 = hole pixel (to be filled)
    const initialMask = new Uint8Array(width * height);
    for (let i = 0; i < data.length; i += 4) {
        const r = data[i];
        const g = data[i+1];
        const b = data[i+2];
        const diff = Math.abs(r - targetR) + Math.abs(g - targetG) + Math.abs(b - targetB);
        if (diff < colorThreshold) {
            initialMask[i / 4] = 1;
        }
    }

    // 3. --- Mask Refinement: Fill bounding boxes of connected components to include text ---
    const finalMask = new Uint8Array(width * height);
    const visited = new Uint8Array(width * height);
    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            const idx = y * width + x;
            if (initialMask[idx] === 1 && !visited[idx]) {
                // Found a new unvisited component, start a Breadth-First Search (BFS)
                const queue = [{x, y}];
                visited[idx] = 1;
                let head = 0;
                let minX = x, minY = y, maxX = x, maxY = y;

                while (head < queue.length) {
                    const current = queue[head++];
                    minX = Math.min(minX, current.x);
                    minY = Math.min(minY, current.y);
                    maxX = Math.max(maxX, current.x);
                    maxY = Math.max(maxY, current.y);

                    // Check 8 neighbors
                    for (let dy = -1; dy <= 1; dy++) {
                        for (let dx = -1; dx <= 1; dx++) {
                            if (dx === 0 && dy === 0) continue;
                            const nx = current.x + dx;
                            const ny = current.y + dy;
                            const nIdx = ny * width + nx;

                            if (nx >= 0 && nx < width && ny >= 0 && ny < height && initialMask[nIdx] === 1 && !visited[nIdx]) {
                                visited[nIdx] = 1;
                                queue.push({x: nx, y: ny});
                            }
                        }
                    }
                }
                
                // If the component is large enough, assume it's a dialogue bubble
                // and fill its entire bounding box in the final mask.
                if ((maxX - minX > 10) && (maxY - minY > 10)) {
                    for (let by = minY; by <= maxY; by++) {
                        for (let bx = minX; bx <= maxX; bx++) {
                            finalMask[by * width + bx] = 1;
                        }
                    }
                }
            }
        }
    }

    // 4. --- Inpainting: Fill the masked region using a diffusion method ---
    const workData = new Uint8ClampedArray(data);
    const frontier = [];
    // Final mask states: 0=source, 1=hole, 2=frontier
    for (let i = 0; i < finalMask.length; i++) {
        if (finalMask[i] === 1) { // If it's a hole pixel
            const y = Math.floor(i / width);
            const x = i % width;
            let isBoundary = false;
            // Check its 8 neighbors to see if it's on the edge of the hole
            for (let dy = -1; dy <= 1; dy++) {
                for (let dx = -1; dx <= 1; dx++) {
                    if (dx === 0 && dy === 0) continue;
                    const nx = x + dx, ny = y + dy;
                    if (nx >= 0 && nx < width && ny >= 0 && ny < height && finalMask[ny * width + nx] === 0) {
                        isBoundary = true;
                        break;
                    }
                }
                if (isBoundary) break;
            }
            if (isBoundary) {
                finalMask[i] = 2; // Mark as on the frontier
                frontier.push({x, y});
            }
        }
    }

    let head = 0;
    while (head < frontier.length) {
        const p = frontier[head++];
        const idx = p.y * width + p.x;

        // Calculate average color of surrounding known pixels
        let r_sum = 0, g_sum = 0, b_sum = 0, count = 0;
        for (let dy = -1; dy <= 1; dy++) {
            for (let dx = -1; dx <= 1; dx++) {
                const nx = p.x + dx, ny = p.y + dy;
                if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
                    const nIdx = ny * width + nx;
                    if (finalMask[nIdx] === 0) { // If neighbor is a source pixel
                        const dataIdx = nIdx * 4;
                        r_sum += workData[dataIdx];
                        g_sum += workData[dataIdx+1];
                        b_sum += workData[dataIdx+2];
                        count++;
                    }
                }
            }
        }

        // Fill the pixel with the average color
        if (count > 0) {
            const dataIdx = idx * 4;
            workData[dataIdx] = r_sum / count;
            workData[dataIdx+1] = g_sum / count;
            workData[dataIdx+2] = b_sum / count;
        }
        finalMask[idx] = 0; // Mark this pixel as filled (now a source)

        // Add its unprocessed neighbors to the frontier
        for (let dy = -1; dy <= 1; dy++) {
            for (let dx = -1; dx <= 1; dx++) {
                 if (dx === 0 && dy === 0) continue;
                const nx = p.x + dx, ny = p.y + dy;
                if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
                    const nIdx = ny * width + nx;
                    if (finalMask[nIdx] === 1) { // If neighbor is an untouched hole pixel
                        finalMask[nIdx] = 2; // Mark it as the new frontier
                        frontier.push({x: nx, y: ny});
                    }
                }
            }
        }
    }

    // 5. --- Final Output ---
    const resultCanvas = document.createElement('canvas');
    resultCanvas.width = width;
    resultCanvas.height = height;
    const resultCtx = resultCanvas.getContext('2d');
    resultCtx.putImageData(new ImageData(workData, width, height), 0, 0);

    return resultCanvas;
}

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 Dialogue Removal and Repair Tool for Manga Panels is designed to efficiently remove dialogue bubbles and text from manga panels. By identifying specific color regions associated with dialogue, this tool blanks them out and inpaints the surrounding areas to seamlessly blend the background, restoring the original artwork. This tool is particularly useful for editors, artists, or fans who want to create clean versions of manga panels, whether for restoration, redrawing, or creating fan edits without dialogue.

Leave a Reply

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