You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, defaultText = "Add Text!", defaultColor = "#ffffff", defaultSize = 40) {
// Main wrapper container
const container = document.createElement('div');
container.style.fontFamily = 'Arial, sans-serif';
container.style.display = 'inline-block';
container.style.maxWidth = '100%';
// Toolbar for controls
const toolbar = document.createElement('div');
toolbar.style.padding = '10px';
toolbar.style.backgroundColor = '#f4f4f4';
toolbar.style.border = '1px solid #ddd';
toolbar.style.borderBottom = 'none';
toolbar.style.display = 'flex';
toolbar.style.flexWrap = 'wrap';
toolbar.style.gap = '15px';
toolbar.style.alignItems = 'center';
toolbar.style.borderTopLeftRadius = '8px';
toolbar.style.borderTopRightRadius = '8px';
const addBtn = document.createElement('button');
addBtn.innerText = '+ Add Textbox';
addBtn.style.padding = '5px 10px';
addBtn.style.cursor = 'pointer';
const colorContainer = document.createElement('div');
colorContainer.innerHTML = '<span style="font-size: 14px;">Color: </span>';
const colorInput = document.createElement('input');
colorInput.type = 'color';
colorInput.value = defaultColor;
colorInput.style.cursor = 'pointer';
colorContainer.appendChild(colorInput);
const sizeContainer = document.createElement('div');
sizeContainer.innerHTML = '<span style="font-size: 14px;">Size: </span>';
const sizeInput = document.createElement('input');
sizeInput.type = 'number';
sizeInput.value = defaultSize;
sizeInput.style.width = '60px';
sizeInput.style.padding = '3px';
sizeContainer.appendChild(sizeInput);
const flattenBtn = document.createElement('button');
flattenBtn.innerText = 'Flatten Image';
flattenBtn.style.padding = '5px 15px';
flattenBtn.style.fontWeight = 'bold';
flattenBtn.style.backgroundColor = '#4CAF50';
flattenBtn.style.color = 'white';
flattenBtn.style.border = 'none';
flattenBtn.style.borderRadius = '4px';
flattenBtn.style.cursor = 'pointer';
flattenBtn.style.marginLeft = 'auto';
toolbar.appendChild(addBtn);
toolbar.appendChild(colorContainer);
toolbar.appendChild(sizeContainer);
toolbar.appendChild(flattenBtn);
container.appendChild(toolbar);
// Editing canvas and area
const workArea = document.createElement('div');
workArea.style.position = 'relative';
workArea.style.display = 'inline-block';
workArea.style.border = '1px solid #ddd';
workArea.style.maxWidth = '100%';
workArea.style.overflow = 'hidden'; // Ensure elements don't spill outside
// Backing canvas to allow flattening
const canvas = document.createElement('canvas');
canvas.width = originalImg.width;
canvas.height = originalImg.height;
canvas.style.display = 'block';
canvas.style.maxWidth = '100%';
canvas.style.height = 'auto';
const ctx = canvas.getContext('2d');
ctx.drawImage(originalImg, 0, 0);
workArea.appendChild(canvas);
container.appendChild(workArea);
const textBoxes = [];
// Helper to extract newlines split correctly
const createTextbox = () => {
const box = document.createElement('div');
box.style.position = 'absolute';
box.style.left = '50%';
box.style.top = '50%';
box.style.transform = 'translate(-50%, -50%)'; // Center initially
box.style.display = 'inline-block';
// Drag handle for easy grabbing
const handle = document.createElement('div');
handle.innerHTML = '✥'; // Cross arrow symbol
handle.style.position = 'absolute';
handle.style.left = '-12px';
handle.style.top = '-12px';
handle.style.width = '24px';
handle.style.height = '24px';
handle.style.backgroundColor = '#007BFF';
handle.style.color = 'white';
handle.style.borderRadius = '50%';
handle.style.textAlign = 'center';
handle.style.lineHeight = '24px';
handle.style.fontSize = '14px';
handle.style.cursor = 'move';
handle.style.zIndex = '10';
handle.style.boxShadow = '0 2px 4px rgba(0,0,0,0.3)';
handle.style.opacity = '0';
handle.style.transition = 'opacity 0.2s';
box.appendChild(handle);
const textNode = document.createElement('div');
textNode.contentEditable = true;
textNode.innerText = defaultText;
textNode.style.color = colorInput.value;
textNode.style.fontSize = sizeInput.value + 'px';
textNode.style.fontFamily = 'Arial, sans-serif';
textNode.style.fontWeight = 'bold';
// Stroke effect
textNode.style.textShadow = '2px 2px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000';
textNode.style.outline = 'none';
textNode.style.whiteSpace = 'pre'; // Force manual linebreaks to preserve exact structural alignment with canvas
textNode.style.minWidth = '20px';
textNode.style.minHeight = '30px';
textNode.style.padding = '5px';
textNode.style.border = '1px dashed transparent';
box.appendChild(textNode);
workArea.appendChild(box);
textBoxes.push({ box, textNode });
// Show/Hide drag handle on hover
box.addEventListener('mouseenter', () => { handle.style.opacity = '1'; textNode.style.border = '1px dashed #ccc'; });
box.addEventListener('mouseleave', () => { handle.style.opacity = '0'; textNode.style.border = '1px dashed transparent'; });
// Dragging Logic
let isDragging = false;
let startX, startY, initialLeft, initialTop;
handle.addEventListener('mousedown', (e) => {
isDragging = true;
startX = e.clientX;
startY = e.clientY;
// Convert transform centering into absolute positioning relative to parent to make dragging smooth
if (box.style.transform) {
const rect = box.getBoundingClientRect();
const parentRect = workArea.getBoundingClientRect();
box.style.transform = 'none';
box.style.left = (rect.left - parentRect.left) + 'px';
box.style.top = (rect.top - parentRect.top) + 'px';
}
initialLeft = parseFloat(box.style.left) || 0;
initialTop = parseFloat(box.style.top) || 0;
e.preventDefault(); // prevent unwanted selections
const onMouseMove = (ev) => {
if (!isDragging) return;
const dx = ev.clientX - startX;
const dy = ev.clientY - startY;
box.style.left = (initialLeft + dx) + 'px';
box.style.top = (initialTop + dy) + 'px';
};
const onMouseUp = () => {
isDragging = false;
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
};
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
});
// Removal logic if emptied out
textNode.addEventListener('blur', () => {
if (textNode.innerText.trim() === '') {
box.remove();
const idx = textBoxes.findIndex(t => t.box === box);
if (idx > -1) textBoxes.splice(idx, 1);
}
});
// Auto-select text on generation
setTimeout(() => textNode.focus(), 50);
};
addBtn.addEventListener('click', createTextbox);
flattenBtn.addEventListener('click', () => {
// Calculate scaling ratio if canvas is compressed via CSS max-width
const workRect = workArea.getBoundingClientRect();
const ratioX = canvas.width / workRect.width;
const ratioY = canvas.height / workRect.height;
textBoxes.forEach(({ box, textNode }) => {
const nodeRect = textNode.getBoundingClientRect();
const canvasRect = canvas.getBoundingClientRect();
// Calculate exact starting position scaled to natural canvas dimensions
const startX = (nodeRect.left - canvasRect.left + 5) * ratioX; // +5 intercepts DOM padding compensation
const startY = (nodeRect.top - canvasRect.top + 5) * ratioY;
const fontSize = parseFloat(textNode.style.fontSize) * Math.min(ratioX, ratioY);
ctx.font = `bold ${fontSize}px Arial, sans-serif`;
ctx.fillStyle = textNode.style.color;
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
// Meme-style heavy stroke
ctx.lineJoin = 'round';
ctx.lineWidth = Math.max(3, fontSize / 8);
ctx.strokeStyle = '#000000';
const lines = textNode.innerText.split('\n');
let currentY = startY;
lines.forEach(line => {
ctx.strokeText(line, startX, currentY);
ctx.fillText(line, startX, currentY);
currentY += fontSize * 1.15; // standard DOM line-height approximation
});
box.remove();
});
textBoxes.length = 0; // Reset array
// Clean layout down to just the finished image
container.innerHTML = '';
canvas.style.borderRadius = '8px';
canvas.style.boxShadow = '0 4px 8px rgba(0,0,0,0.1)';
container.appendChild(canvas);
});
// Populate the first textbox
createTextbox();
return container;
}
Apply Changes