You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, initialTopic = "Earth", textColor = "#FFFFFF", fontSize = 40) {
const container = document.createElement('div');
container.style.display = 'flex';
container.style.flexDirection = 'column';
container.style.alignItems = 'center';
container.style.fontFamily = 'Arial, sans-serif';
container.style.gap = '10px';
container.style.width = '100%';
// Toolbox UI
const toolbox = document.createElement('div');
toolbox.style.display = 'flex';
toolbox.style.gap = '10px';
toolbox.style.padding = '15px';
toolbox.style.backgroundColor = '#f1f1f1';
toolbox.style.borderRadius = '8px';
toolbox.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
toolbox.style.width = '100%';
toolbox.style.boxSizing = 'border-box';
toolbox.style.justifyContent = 'center';
toolbox.style.flexWrap = 'wrap';
const input = document.createElement('input');
input.type = 'text';
input.value = initialTopic;
input.placeholder = 'Search topic (e.g. Space, Cats)';
input.style.padding = '8px 12px';
input.style.border = '1px solid #ccc';
input.style.borderRadius = '4px';
input.style.fontSize = '16px';
input.style.flexGrow = '1';
input.style.maxWidth = '300px';
const btn = document.createElement('button');
btn.textContent = 'Search & Add Text';
btn.style.padding = '8px 16px';
btn.style.backgroundColor = '#007BFF';
btn.style.color = 'white';
btn.style.border = 'none';
btn.style.borderRadius = '4px';
btn.style.cursor = 'pointer';
btn.style.fontSize = '16px';
btn.style.fontWeight = 'bold';
btn.style.transition = 'background-color 0.2s';
btn.onmouseover = () => btn.style.backgroundColor = '#0056b3';
btn.onmouseout = () => btn.style.backgroundColor = '#007BFF';
const helpText = document.createElement('div');
helpText.textContent = 'Drag text to move it. Double-click/double-tap an item to remove it.';
helpText.style.width = '100%';
helpText.style.textAlign = 'center';
helpText.style.fontSize = '12px';
helpText.style.color = '#666';
toolbox.appendChild(input);
toolbox.appendChild(btn);
toolbox.appendChild(helpText);
// Canvas Wrapper
const canvasWrapper = document.createElement('div');
canvasWrapper.style.position = 'relative';
canvasWrapper.style.maxWidth = '100%';
const canvas = document.createElement('canvas');
canvas.width = originalImg.width;
canvas.height = originalImg.height;
canvas.style.maxWidth = '100%';
canvas.style.height = 'auto';
canvas.style.boxShadow = '0 4px 8px rgba(0,0,0,0.2)';
canvas.style.borderRadius = '4px';
canvasWrapper.appendChild(canvas);
container.appendChild(toolbox);
container.appendChild(canvasWrapper);
const ctx = canvas.getContext('2d');
let textBlocks = [];
// Simple text wrapping logic
function wrapText(context, text, maxWidth) {
let words = text.split(' ');
let lines = [];
let currentLine = words[0];
for (let i = 1; i < words.length; i++) {
let word = words[i];
let width = context.measureText(currentLine + " " + word).width;
if (width < maxWidth) {
currentLine += " " + word;
} else {
lines.push(currentLine);
currentLine = word;
}
}
lines.push(currentLine);
return lines;
}
function draw() {
// Redraw image
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(originalImg, 0, 0);
// Setup text styles
ctx.font = `bold ${fontSize}px Arial, sans-serif`;
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.fillStyle = textColor;
ctx.lineWidth = Math.max(2, fontSize * 0.1);
ctx.strokeStyle = '#000000'; // Make text legible on any background
ctx.shadowColor = 'rgba(0,0,0,0.8)';
ctx.shadowBlur = 5;
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
for (let block of textBlocks) {
if (!block.lines) {
block.lines = wrapText(ctx, block.text, canvas.width * 0.8);
block.width = 0;
block.lines.forEach(l => {
let w = ctx.measureText(l).width;
if (w > block.width) block.width = w;
});
block.height = block.lines.length * (fontSize * 1.2);
}
block.lines.forEach((line, index) => {
const lineY = block.y + index * (fontSize * 1.2);
ctx.strokeText(line, block.x, lineY);
ctx.fillText(line, block.x, lineY);
});
}
// Reset shadow to avoid affecting anything else later
ctx.shadowColor = 'transparent';
}
// Initial draw call
draw();
// Interaction State
let draggingBlock = null;
let dragOffsetX = 0;
let dragOffsetY = 0;
let lastClickTime = 0;
function getMousePos(e) {
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
return {
x: (e.clientX - rect.left) * scaleX,
y: (e.clientY - rect.top) * scaleY
};
}
function getBlockAtPos(pos) {
// Reverse array checking brings the topmost item first (the one written last)
for (let i = textBlocks.length - 1; i >= 0; i--) {
let block = textBlocks[i];
if (pos.x >= block.x - 10 && pos.x <= block.x + block.width + 10 &&
pos.y >= block.y - 10 && pos.y <= block.y + block.height + 10) {
return { block, index: i };
}
}
return null;
}
// Mouse Listeners
canvas.addEventListener('mousedown', (e) => {
const pos = getMousePos(e);
const found = getBlockAtPos(pos);
if (found) {
const currentTime = Date.now();
// Double click removal handling
if (currentTime - lastClickTime < 300) {
textBlocks.splice(found.index, 1);
draw();
lastClickTime = 0;
return;
}
lastClickTime = currentTime;
draggingBlock = found.block;
dragOffsetX = pos.x - found.block.x;
dragOffsetY = pos.y - found.block.y;
// Bring item to the front layer
textBlocks.splice(found.index, 1);
textBlocks.push(found.block);
draw();
}
});
canvas.addEventListener('mousemove', (e) => {
const pos = getMousePos(e);
if (draggingBlock) {
draggingBlock.x = pos.x - dragOffsetX;
draggingBlock.y = pos.y - dragOffsetY;
draw();
} else {
const found = getBlockAtPos(pos);
canvas.style.cursor = found ? 'move' : 'default';
}
});
canvas.addEventListener('mouseup', () => draggingBlock = null);
canvas.addEventListener('mouseleave', () => draggingBlock = null);
// Touch support hooks for Mobile compatibility
canvas.addEventListener('touchstart', (e) => {
if (e.touches.length > 0) {
const touch = e.touches[0];
const pos = getMousePos(touch);
const found = getBlockAtPos(pos);
if (found) {
e.preventDefault();
const currentTime = Date.now();
if (currentTime - lastClickTime < 300) {
textBlocks.splice(found.index, 1);
draw();
lastClickTime = 0;
return;
}
lastClickTime = currentTime;
draggingBlock = found.block;
dragOffsetX = pos.x - found.block.x;
dragOffsetY = pos.y - found.block.y;
textBlocks.splice(found.index, 1);
textBlocks.push(found.block);
draw();
}
}
}, { passive: false });
canvas.addEventListener('touchmove', (e) => {
if (draggingBlock && e.touches.length > 0) {
e.preventDefault();
const pos = getMousePos(e.touches[0]);
draggingBlock.x = pos.x - dragOffsetX;
draggingBlock.y = pos.y - dragOffsetY;
draw();
}
}, { passive: false });
canvas.addEventListener('touchend', () => draggingBlock = null);
canvas.addEventListener('touchcancel', () => draggingBlock = null);
// Wikipedia Fetching Logic
async function addTopicText(topic) {
btn.textContent = 'Searching...';
btn.disabled = true;
let textStr = topic;
try {
const url = `https://en.wikipedia.org/w/api.php?action=query&prop=extracts&exsentences=1&exlimit=1&titles=${encodeURIComponent(topic)}&explaintext=1&formatversion=2&origin=*&format=json`;
const res = await fetch(url);
const data = await res.json();
if (data.query && data.query.pages && data.query.pages[0].extract) {
const extract = data.query.pages[0].extract.trim();
textStr = extract ? extract : `No summary available for "${topic}".`;
} else {
textStr = `No data found for "${topic}".`;
}
} catch(err) {
textStr = `Error fetching topic: ${topic}`;
}
btn.textContent = 'Search & Add Text';
btn.disabled = false;
ctx.font = `bold ${fontSize}px Arial, sans-serif`;
textBlocks.push({
id: Date.now(),
text: textStr,
x: canvas.width * 0.1,
y: canvas.height * 0.1 + (textBlocks.length * (fontSize * 1.5)),
lines: null,
width: 0,
height: 0
});
draw();
}
btn.addEventListener('click', () => {
if (input.value.trim()) {
addTopicText(input.value.trim());
}
});
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
btn.click();
}
});
// Run automatically on load if string is present
if (initialTopic) {
addTopicText(initialTopic);
}
return container;
}
Apply Changes