Please bookmark this page to avoid losing your image tool!

ID Document Image And Text Editor

(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.
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;
}

Free Image Tool Creator

Can't find the image tool you're looking for?
Create one based on your own needs now!

Description

The ID Document Image and Text Editor is a specialized tool designed for overlaying text and images onto existing document images. It allows users to add custom text with adjustable properties such as font family, size, and color, as well as upload and resize additional photos or icons. This tool is useful for creating mockups, adding annotations to digital identification layouts, or customizing visual elements on document templates. Once edits are complete, users can export the final result as a PNG image.

Leave a Reply

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