You can edit the below JavaScript code to customize the image tool.
/**
* Removes dialogue bubbles from an image like a manga panel.
*
* This function works by identifying a region of a specified color (e.g., the white of a speech bubble),
* expanding that region slightly to include any outlines, and then filling the entire area
* using a multi-pass inpainting algorithm. The inpainting works by averaging the colors of
* pixels on the border of the hole and progressively filling it inwards.
*
* @param {Image} originalImg The source Image object to process.
* @param {string} removeColorStr A comma-separated RGB string representing the base color of the area to remove (e.g., '255,255,255' for white speech bubbles).
* @param {number} threshold A number (0-255) that determines the tolerance for color matching. Higher values will include more color variations in the removal mask.
* @param {number} expand A radius in pixels to expand the masked area. This is useful for catching the black outlines around white speech bubbles.
* @param {number} inpaintPasses The number of inpainting layers to apply. Each pass fills one layer of pixels from the border inwards. Larger values can fill larger holes but increase processing time.
* @returns {Promise<HTMLCanvasElement>} A promise that resolves with a new canvas element containing the processed image.
*/
async function processImage(originalImg, removeColorStr = '255,255,255', threshold = 50, expand = 3, inpaintPasses = 150) {
// 1. Setup: Create a canvas and draw the original image onto it.
const canvas = document.createElement('canvas');
// Using { willReadFrequently: true } can provide performance optimizations for repeated getImageData/putImageData calls in some browsers.
const ctx = canvas.getContext('2d', {
willReadFrequently: true
});
canvas.width = originalImg.naturalWidth;
canvas.height = originalImg.naturalHeight;
ctx.drawImage(originalImg, 0, 0);
const {
width,
height
} = canvas;
const originalImageData = ctx.getImageData(0, 0, width, height);
let workingData = new Uint8ClampedArray(originalImageData.data);
// 2. Parse parameters and define helper function
const removeColor = removeColorStr.split(',').map(Number);
// Helper function to calculate the Euclidean distance between two colors.
const colorDistance = (r1, g1, b1, r2, g2, b2) => {
return Math.sqrt(Math.pow(r1 - r2, 2) + Math.pow(g1 - g2, 2) + Math.pow(b1 - b2, 2));
};
// 3. Create initial mask based on color similarity
let mask = new Uint8ClampedArray(width * height);
for (let i = 0; i < workingData.length; i += 4) {
const r = workingData[i];
const g = workingData[i + 1];
const b = workingData[i + 2];
const distance = colorDistance(r, g, b, removeColor[0], removeColor[1], removeColor[2]);
if (distance < threshold) {
mask[i / 4] = 1; // Mark as part of the hole (1 = hole, 0 = keep)
}
}
// 4. Expand (dilate) the mask to include outlines and nearby artifacts
let workingMask;
if (expand > 0) {
let expandedMask = new Uint8ClampedArray(width * height);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
if (mask[y * width + x]) {
for (let dy = -expand; dy <= expand; dy++) {
for (let dx = -expand; dx <= expand; dx++) {
const nx = x + dx;
const ny = y + dy;
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
expandedMask[ny * width + nx] = 1;
}
}
}
}
}
}
workingMask = expandedMask;
} else {
workingMask = mask;
}
// 5. Inpaint the masked area layer by layer from the outside in
for (let pass = 0; pass < inpaintPasses; pass++) {
const pixelsToFillThisPass = [];
// Find all hole pixels that are currently on the border
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const index = y * width + x;
if (workingMask[index] === 1) { // Is a hole pixel
let isBorder = false;
for (let dy = -1; dy <= 1; dy++) {
for (let dx = -1; dx <= 1; dx++) {
if (dx === 0 && dy === 0) continue;
const nx = x + dx;
const ny = y + dy;
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
if (workingMask[ny * width + nx] === 0) { // Has a non-hole neighbor
isBorder = true;
break;
}
}
}
if (isBorder) break;
}
if (isBorder) {
pixelsToFillThisPass.push(index);
}
}
}
}
if (pixelsToFillThisPass.length === 0) {
break; // No more border pixels to fill, inpainting is complete.
}
// Calculate the new colors for the border pixels without modifying the data yet
const newColors = new Map();
for (const index of pixelsToFillThisPass) {
let r_sum = 0,
g_sum = 0,
b_sum = 0,
count = 0;
const y = Math.floor(index / width);
const x = index % width;
for (let dy = -1; dy <= 1; dy++) {
for (let dx = -1; dx <= 1; dx++) {
if (dx === 0 && dy === 0) continue;
const nx = x + dx;
const ny = y + dy;
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
const neighborIndex = ny * width + nx;
if (workingMask[neighborIndex] === 0) { // Is a valid, non-hole neighbor
r_sum += workingData[neighborIndex * 4];
g_sum += workingData[neighborIndex * 4 + 1];
b_sum += workingData[neighborIndex * 4 + 2];
count++;
}
}
}
}
if (count > 0) {
newColors.set(index, [r_sum / count, g_sum / count, b_sum / count]);
}
}
// Apply the new colors and update the mask for the next pass
for (const [index, color] of newColors) {
const dataIndex = index * 4;
workingData[dataIndex] = color[0];
workingData[dataIndex + 1] = color[1];
workingData[dataIndex + 2] = color[2];
workingMask[index] = 0; // This pixel is now filled
}
}
// 6. Put the final inpainted data back on the canvas and return it
const resultImageData = new ImageData(workingData, width, height);
ctx.putImageData(resultImageData, 0, 0);
return canvas;
}
Free Image Tool Creator
Can't find the image tool you're looking for? Create one based on your own needs now!
The Image Dialogue Removal Tool for Manga Panels is designed to help users remove dialogue bubbles from images of manga panels effectively. This tool works by identifying and removing areas of specified color, such as the white of speech bubbles, and seamlessly fills in the resulting spaces using advanced inpainting techniques. It is useful for artists, editors, and manga enthusiasts who want to edit or repurpose manga images, create cleaner visuals, or prepare images for translation and adaptation by eliminating unnecessary text and graphics. The tool allows customization of parameters such as the color to remove, threshold for color matching, area expansion, and layers of inpainting to achieve optimal results.