You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, defaultTitle = "ID Document Editor", headerColor = "#2563eb", defaultTextSize = 24) {
// Determine number format safely in case string is passed
const defaultFontSizeNum = typeof defaultTextSize === 'string' ? parseInt(defaultTextSize, 10) || 24 : defaultTextSize;
// Create the main wrapper
const wrapper = document.createElement('div');
wrapper.style.display = 'flex';
wrapper.style.flexDirection = 'column';
wrapper.style.fontFamily = 'system-ui, -apple-system, sans-serif';
wrapper.style.backgroundColor = '#f9fafb';
wrapper.style.border = '1px solid #e5e7eb';
wrapper.style.borderRadius = '8px';
wrapper.style.overflow = 'hidden';
wrapper.style.boxShadow = '0 4px 6px -1px rgba(0, 0, 0, 0.1)';
wrapper.style.width = '100%';
wrapper.style.maxWidth = '1000px';
wrapper.tabIndex = 0; // Make container focusable to catch keyboard events
wrapper.style.outline = 'none';
// Top Header
const header = document.createElement('div');
header.style.backgroundColor = headerColor;
header.style.color = '#ffffff';
header.style.padding = '12px 20px';
header.style.fontSize = '18px';
header.style.fontWeight = '600';
header.innerText = defaultTitle;
wrapper.appendChild(header);
// Toolbar
const toolbar = document.createElement('div');
toolbar.style.padding = '12px 20px';
toolbar.style.backgroundColor = '#ffffff';
toolbar.style.borderBottom = '1px solid #e5e7eb';
toolbar.style.display = 'flex';
toolbar.style.gap = '10px';
toolbar.style.flexWrap = 'wrap';
toolbar.style.alignItems = 'center';
function createBtn(text) {
const btn = document.createElement('button');
btn.innerText = text;
btn.style.padding = '6px 14px';
btn.style.backgroundColor = '#f3f4f6';
btn.style.border = '1px solid #d1d5db';
btn.style.borderRadius = '6px';
btn.style.cursor = 'pointer';
btn.style.fontSize = '14px';
btn.style.fontWeight = '500';
btn.style.color = '#374151';
btn.style.transition = 'background-color 0.2s';
btn.onmouseover = () => btn.style.backgroundColor = '#e5e7eb';
btn.onmouseout = () => btn.style.backgroundColor = '#f3f4f6';
return btn;
}
const addTextBtn = createBtn('+ Add Text');
// File Input for Photo Overlay
const addImgLabel = document.createElement('label');
addImgLabel.innerText = '+ Add Photo/Icon';
addImgLabel.style.padding = '6px 14px';
addImgLabel.style.backgroundColor = '#f3f4f6';
addImgLabel.style.border = '1px solid #d1d5db';
addImgLabel.style.borderRadius = '6px';
addImgLabel.style.cursor = 'pointer';
addImgLabel.style.fontSize = '14px';
addImgLabel.style.fontWeight = '500';
addImgLabel.style.color = '#374151';
addImgLabel.style.transition = 'background-color 0.2s';
addImgLabel.onmouseover = () => addImgLabel.style.backgroundColor = '#e5e7eb';
addImgLabel.onmouseout = () => addImgLabel.style.backgroundColor = '#f3f4f6';
const addImgInput = document.createElement('input');
addImgInput.type = 'file';
addImgInput.accept = 'image/*';
addImgInput.style.display = 'none';
addImgLabel.appendChild(addImgInput);
const deleteBtn = createBtn('Delete Selected');
deleteBtn.style.color = '#dc2626';
const exportBtn = createBtn('Export ID');
exportBtn.style.backgroundColor = headerColor;
exportBtn.style.color = '#ffffff';
exportBtn.style.border = 'none';
exportBtn.style.marginLeft = 'auto'; // push to the right
exportBtn.onmouseover = () => exportBtn.style.opacity = '0.9';
exportBtn.onmouseout = () => exportBtn.style.opacity = '1';
toolbar.appendChild(addTextBtn);
toolbar.appendChild(addImgLabel);
toolbar.appendChild(deleteBtn);
toolbar.appendChild(exportBtn);
wrapper.appendChild(toolbar);
// Properties Bar (appears contextually when an item is selected)
const propsBar = document.createElement('div');
propsBar.style.padding = '10px 20px';
propsBar.style.backgroundColor = '#f9fafb';
propsBar.style.borderBottom = '1px solid #e5e7eb';
propsBar.style.display = 'none';
propsBar.style.gap = '15px';
propsBar.style.alignItems = 'center';
propsBar.style.fontSize = '14px';
wrapper.appendChild(propsBar);
const textPropsDiv = document.createElement('div');
textPropsDiv.style.display = 'none';
textPropsDiv.style.gap = '10px';
textPropsDiv.style.alignItems = 'center';
const textInput = document.createElement('input');
textInput.type = 'text';
textInput.style.padding = '6px';
textInput.style.border = '1px solid #d1d5db';
textInput.style.borderRadius = '4px';
const colorInput = document.createElement('input');
colorInput.type = 'color';
colorInput.style.padding = '0';
colorInput.style.border = 'none';
colorInput.style.width = '30px';
colorInput.style.height = '30px';
colorInput.style.cursor = 'pointer';
const sizeInput = document.createElement('input');
sizeInput.type = 'number';
sizeInput.min = '8';
sizeInput.max = '200';
sizeInput.style.width = '60px';
sizeInput.style.padding = '6px';
const fontFamInput = document.createElement('select');
['Arial', 'Times New Roman', 'Courier New', 'Verdana', 'Georgia', 'Helvetica', 'Tahoma'].forEach(f => {
const opt = document.createElement('option');
opt.value = f;
opt.innerText = f;
fontFamInput.appendChild(opt);
});
fontFamInput.style.padding = '6px';
fontFamInput.style.borderRadius = '4px';
fontFamInput.style.border = '1px solid #d1d5db';
textPropsDiv.appendChild(document.createTextNode('Text:'));
textPropsDiv.appendChild(textInput);
textPropsDiv.appendChild(document.createTextNode('Color:'));
textPropsDiv.appendChild(colorInput);
textPropsDiv.appendChild(document.createTextNode('Size:'));
textPropsDiv.appendChild(sizeInput);
textPropsDiv.appendChild(document.createTextNode('Font:'));
textPropsDiv.appendChild(fontFamInput);
const imgMsgSpan = document.createElement('span');
imgMsgSpan.innerText = 'Resize image using the bottom-right handle.';
imgMsgSpan.style.fontStyle = 'italic';
imgMsgSpan.style.color = '#6b7280';
imgMsgSpan.style.display = 'none';
propsBar.appendChild(textPropsDiv);
propsBar.appendChild(imgMsgSpan);
// Canvas Container
const canvasContainer = document.createElement('div');
canvasContainer.style.padding = '20px';
canvasContainer.style.display = 'flex';
canvasContainer.style.justifyContent = 'center';
canvasContainer.style.overflow = 'auto'; // in case originalImg is massive
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = originalImg.width;
canvas.height = originalImg.height;
canvas.style.backgroundColor = 'transparent';
canvas.style.boxShadow = '0 4px 6px -1px rgba(0, 0, 0, 0.1)';
// Scale canvas to fit nicely inside the editor, maintaining native coordinate measurements
canvas.style.maxWidth = '100%';
canvas.style.height = 'auto';
canvas.style.cursor = 'crosshair';
canvasContainer.appendChild(canvas);
wrapper.appendChild(canvasContainer);
// Editor Variables
let elements = [];
let selectedItem = null;
let isDragging = false;
let isResizing = false;
let dragOffsetX = 0;
let dragOffsetY = 0;
// Add Initial default text elements for ease of use
elements.push({
type: 'text',
text: 'John Doe',
x: canvas.width * 0.1,
y: canvas.height * 0.1,
size: defaultFontSizeNum,
color: '#000000',
fontFamily: 'Arial',
w: 0, h: 0
});
// Render loop
function render(hideHelpers = false) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = 'source-over';
ctx.drawImage(originalImg, 0, 0);
elements.forEach(el => {
if (el.type === 'text') {
ctx.font = `${el.size}px ${el.fontFamily}`;
ctx.fillStyle = el.color;
ctx.textBaseline = 'top';
ctx.fillText(el.text, el.x, el.y);
el.w = ctx.measureText(el.text).width;
el.h = el.size * 1.2; // Approximate rendering box including descenders
} else if (el.type === 'image') {
ctx.drawImage(el.img, el.x, el.y, el.w, el.h);
}
// Draw bounding boxes for selected item
if (!hideHelpers && el === selectedItem) {
ctx.strokeStyle = headerColor;
ctx.lineWidth = 2;
ctx.setLineDash([5, 5]);
ctx.strokeRect(el.x - 4, el.y - 4, el.w + 8, el.h + 8);
ctx.setLineDash([]);
// Draw resize handle only for images
if (el.type === 'image') {
ctx.fillStyle = headerColor;
ctx.beginPath();
ctx.arc(el.x + el.w, el.y + el.h, 8, 0, Math.PI * 2);
ctx.fill();
}
}
});
}
function updatePropsBar() {
if (!selectedItem) {
propsBar.style.display = 'none';
return;
}
propsBar.style.display = 'flex';
if (selectedItem.type === 'text') {
textPropsDiv.style.display = 'flex';
imgMsgSpan.style.display = 'none';
textInput.value = selectedItem.text;
colorInput.value = selectedItem.color;
sizeInput.value = selectedItem.size;
fontFamInput.value = selectedItem.fontFamily;
} else if (selectedItem.type === 'image') {
textPropsDiv.style.display = 'none';
imgMsgSpan.style.display = 'block';
}
}
// Properties Events
const syncTextProps = () => {
if (selectedItem && selectedItem.type === 'text') {
selectedItem.text = textInput.value;
selectedItem.color = colorInput.value;
selectedItem.size = parseInt(sizeInput.value, 10) || defaultFontSizeNum;
selectedItem.fontFamily = fontFamInput.value;
render();
}
};
textInput.addEventListener('input', syncTextProps);
colorInput.addEventListener('input', syncTextProps);
sizeInput.addEventListener('input', syncTextProps);
fontFamInput.addEventListener('change', syncTextProps);
// Toolbar Buttons functionality
addTextBtn.onclick = () => {
elements.push({
type: 'text',
text: 'New Text',
x: canvas.width / 2,
y: canvas.height / 2,
size: defaultFontSizeNum,
color: '#000000',
fontFamily: 'Arial',
w: 0, h: 0
});
selectedItem = elements[elements.length - 1];
updatePropsBar();
render();
};
addImgInput.onchange = (e) => {
if (e.target.files && e.target.files[0]) {
const reader = new FileReader();
reader.onload = (event) => {
const img = new Image();
img.onload = () => {
const maxW = canvas.width * 0.25;
const scale = Math.min(1, maxW / img.width);
elements.push({
type: 'image',
img: img,
x: canvas.width / 2 - (img.width * scale) / 2,
y: canvas.height / 2 - (img.height * scale) / 2,
w: img.width * scale,
h: img.height * scale
});
selectedItem = elements[elements.length - 1];
updatePropsBar();
render();
};
img.src = event.target.result;
};
reader.readAsDataURL(e.target.files[0]);
}
};
deleteBtn.onclick = () => {
if (selectedItem) {
elements = elements.filter(el => el !== selectedItem);
selectedItem = null;
updatePropsBar();
render();
}
};
exportBtn.onclick = () => {
selectedItem = null;
updatePropsBar();
render(true); // render without helper overlays
try {
const dataUrl = canvas.toDataURL('image/png');
const a = document.createElement('a');
a.href = dataUrl;
a.download = 'edited_id_document.png';
a.click();
} catch (e) {
alert("Could not export image natively due to cross-origin resource restrictions. Host the source image on the same domain as your application to enable direct exports.");
}
render(); // render with defaults again
};
// Calculate canvas coordinates adjusting for CSS scaling
function getCanvasCoord(clientX, clientY) {
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
return {
x: (clientX - rect.left) * scaleX,
y: (clientY - rect.top) * scaleY
};
}
// Interactive Logic
function handleStart(x, y) {
// Focus wrapper to absorb keyboard events (like delete or arrows)
if (document.activeElement.tagName !== 'INPUT' && document.activeElement.tagName !== 'SELECT') {
wrapper.focus();
}
// Check if clicking resize handle of selected image
if (selectedItem && selectedItem.type === 'image') {
const hx = selectedItem.x + selectedItem.w;
const hy = selectedItem.y + selectedItem.h;
if (Math.abs(x - hx) < 20 && Math.abs(y - hy) < 20) {
isResizing = true;
return;
}
}
// Check if clicking on an element (start from top/last element z-index wise)
let found = null;
for (let i = elements.length - 1; i >= 0; i--) {
const el = elements[i];
if (x >= el.x && x <= el.x + el.w && y >= el.y && y <= el.y + el.h) {
found = el;
break;
}
}
selectedItem = found;
if (selectedItem) {
// Bring to front
elements = elements.filter(el => el !== selectedItem);
elements.push(selectedItem);
isDragging = true;
dragOffsetX = x - selectedItem.x;
dragOffsetY = y - selectedItem.y;
}
updatePropsBar();
render();
}
function handleMove(x, y) {
if (isDragging && selectedItem) {
selectedItem.x = x - dragOffsetX;
selectedItem.y = y - dragOffsetY;
render();
} else if (isResizing && selectedItem && selectedItem.type === 'image') {
selectedItem.w = Math.max(10, x - selectedItem.x);
selectedItem.h = Math.max(10, y - selectedItem.y);
render();
}
// Adjust mouse cursor nicely
let hoverHandle = false;
let hoverItem = null;
for (let i = elements.length - 1; i >= 0; i--) {
const el = elements[i];
if (el === selectedItem && el.type === 'image') {
const hx = el.x + el.w;
const hy = el.y + el.h;
if (Math.abs(x - hx) < 20 && Math.abs(y - hy) < 20) {
hoverHandle = true;
hoverItem = el;
break;
}
}
if (x >= el.x && x <= el.x + el.w && y >= el.y && y <= el.y + el.h) {
hoverItem = el;
break; // Front-most element
}
}
if (hoverHandle || isResizing) canvas.style.cursor = 'nwse-resize';
else if (hoverItem || isDragging) canvas.style.cursor = 'move';
else canvas.style.cursor = 'crosshair';
}
function handleEnd() {
isDragging = false;
isResizing = false;
}
// Mouse Listeners
canvas.addEventListener('mousedown', (e) => {
const coords = getCanvasCoord(e.clientX, e.clientY);
handleStart(coords.x, coords.y);
});
window.addEventListener('mousemove', (e) => {
if (isDragging || isResizing) e.preventDefault(); // prevents highlighting text outside canvas
const coords = getCanvasCoord(e.clientX, e.clientY);
handleMove(coords.x, coords.y);
});
window.addEventListener('mouseup', handleEnd);
// Touch Listeners
canvas.addEventListener('touchstart', (e) => {
if(e.touches.length > 1) return;
const coords = getCanvasCoord(e.touches[0].clientX, e.touches[0].clientY);
handleStart(coords.x, coords.y);
}, { passive: false });
window.addEventListener('touchmove', (e) => {
if(e.touches.length > 1) return;
if (isDragging || isResizing) e.preventDefault();
const coords = getCanvasCoord(e.touches[0].clientX, e.touches[0].clientY);
handleMove(coords.x, coords.y);
}, { passive: false });
window.addEventListener('touchend', handleEnd);
// Keyboard Shortcuts
wrapper.addEventListener('keydown', (e) => {
// Prevent action if user is inside a form input
if (['INPUT', 'SELECT', 'TEXTAREA'].includes(e.target.tagName)) return;
if (selectedItem) {
if (e.key === 'Delete' || e.key === 'Backspace') {
elements = elements.filter(el => el !== selectedItem);
selectedItem = null;
updatePropsBar();
render();
e.preventDefault();
} else if (e.key === 'ArrowUp') {
selectedItem.y -= 1; render(); e.preventDefault();
} else if (e.key === 'ArrowDown') {
selectedItem.y += 1; render(); e.preventDefault();
} else if (e.key === 'ArrowLeft') {
selectedItem.x -= 1; render(); e.preventDefault();
} else if (e.key === 'ArrowRight') {
selectedItem.x += 1; render(); e.preventDefault();
}
}
});
// Ensure native fonts are available before first calculate, and initially render UI
render();
setTimeout(() => render(), 100);
return wrapper;
}
Apply Changes