Please bookmark this page to avoid losing your image tool!

Image To Punctuation Art Converter

(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, punctuationChars = "`.'`,-_^~:;/\\!|()[]{}<>?=+\"*#$%&@", fontSize = 10, invertColors = "false", contrast = 1.0, brightness = 0.0, outputWidthChars = 100) {
    // Parameter parsing
    const chars = String(punctuationChars);
    const parsedFontSize = Math.max(1, Number(fontSize) || 10); // Ensure font size is at least 1
    const parsedInvert = String(invertColors).toLowerCase() === 'true';
    
    let parsedContrast = Number(contrast);
    if (isNaN(parsedContrast)) parsedContrast = 1.0; // Default contrast
    parsedContrast = Math.max(0, parsedContrast); // Contrast should not be negative

    let parsedBrightness = Number(brightness); // expecting -1.0 to 1.0
    if (isNaN(parsedBrightness)) parsedBrightness = 0.0;
    parsedBrightness = Math.max(-1.0, Math.min(1.0, parsedBrightness)); // Clamp brightness

    const parsedWidthInChars = Math.max(10, Number(outputWidthChars) || 100);

    // Handle cases of invalid original image dimensions
    if (!originalImg || originalImg.width === 0 || originalImg.height === 0) {
        console.error("Original image is invalid or has zero width or height.");
        const errorCanvas = document.createElement('canvas');
        errorCanvas.width = parsedWidthInChars * parsedFontSize * 0.6; // Approx width
        errorCanvas.height = parsedFontSize * 5; // Some height for error message
        const eCtx = errorCanvas.getContext('2d');
        eCtx.fillStyle = 'red';
        eCtx.fillRect(0, 0, errorCanvas.width, errorCanvas.height);
        eCtx.fillStyle = 'white';
        eCtx.font = `${parsedFontSize}px monospace`;
        eCtx.textAlign = 'center';
        eCtx.textBaseline = 'middle';
        eCtx.fillText("Error: Invalid Image", errorCanvas.width / 2, errorCanvas.height / 2);
        return errorCanvas;
    }

    // Create a canvas to draw the original image for pixel manipulation
    const sourceCanvas = document.createElement('canvas');
    sourceCanvas.width = originalImg.width;
    sourceCanvas.height = originalImg.height;
    const sourceCtx = sourceCanvas.getContext('2d', { willReadFrequently: true });
    sourceCtx.drawImage(originalImg, 0, 0);

    // Apply contrast and brightness adjustments to the source canvas
    if (parsedContrast !== 1.0 || parsedBrightness !== 0.0) {
        const imageData = sourceCtx.getImageData(0, 0, sourceCanvas.width, sourceCanvas.height);
        const data = imageData.data;
        // Brightness value is mapped from -1.0->1.0 to an offset for 0-1 normalized pixel values
        const brightnessOffset = parsedBrightness; 

        for (let i = 0; i < data.length; i += 4) {
            // Normalize, apply brightness, then contrast, then clamp & denormalize
            for (let j = 0; j < 3; j++) { // R, G, B channels
                let val = data[i + j] / 255.0; // Normalize to 0-1
                val += brightnessOffset;      // Apply brightness
                                              // Apply contrast: val = (val - 0.5) * contrast + 0.5
                val = (val - 0.5) * parsedContrast + 0.5; 
                val = Math.max(0, Math.min(1, val)); // Clamp to 0-1
                data[i + j] = val * 255.0;    // Denormalize
            }
        }
        sourceCtx.putImageData(imageData, 0, 0);
    }
    
    // Determine dimensions of the output "character grid"
    const numCharsAcross = parsedWidthInChars;
    
    // Measure character dimensions using a temporary canvas
    const tempCtx = document.createElement('canvas').getContext('2d');
    tempCtx.font = `${parsedFontSize}px monospace`; // Monospace font is crucial
    const metrics = tempCtx.measureText("M"); // 'M' is often used for representative width
    const charPixelWidth = metrics.width; 
    const charPixelHeight = parsedFontSize; // Font size defines cell height

    const imageAspectRatio = originalImg.width / originalImg.height;
    // Calculate number of characters down to maintain image aspect ratio using character cell dimensions
    const numCharsDown = Math.max(1, Math.floor(numCharsAcross / imageAspectRatio * (charPixelHeight / charPixelWidth)));

    const blockWidthOnSource = sourceCanvas.width / numCharsAcross;
    const blockHeightOnSource = sourceCanvas.height / numCharsDown;

    // Create output canvas
    const outputCanvas = document.createElement('canvas');
    outputCanvas.width = Math.max(1, Math.floor(numCharsAcross * charPixelWidth));
    outputCanvas.height = Math.max(1, Math.floor(numCharsDown * charPixelHeight));
    const outCtx = outputCanvas.getContext('2d');

    // Set background and foreground (text) colors based on invertColors
    outCtx.fillStyle = parsedInvert ? 'black' : 'white';
    outCtx.fillRect(0, 0, outputCanvas.width, outputCanvas.height);
    
    outCtx.font = `${parsedFontSize}px monospace`;
    outCtx.fillStyle = parsedInvert ? 'white' : 'black';
    outCtx.textBaseline = 'top'; // Consistent text rendering aligned to top of cell

    const numPunctuationGlyphs = chars.length;

    if (numPunctuationGlyphs === 0) {
        console.error("Punctuation character string is empty!");
        outCtx.fillStyle = 'red';
        outCtx.fillRect(0, 0, outputCanvas.width, outputCanvas.height);
        outCtx.fillStyle = 'white';
        outCtx.textAlign = 'center';
        outCtx.textBaseline = 'middle';
        outCtx.fillText("Error: No punctuation characters.", outputCanvas.width / 2, outputCanvas.height / 2);
        return outputCanvas;
    }

    for (let y = 0; y < numCharsDown; y++) {
        for (let x = 0; x < numCharsAcross; x++) {
            const sx = Math.floor(x * blockWidthOnSource);
            const sy = Math.floor(y * blockHeightOnSource);
            // Use Math.ceil for sw, sh to cover fractional parts, but clip to canvas bounds
            const sw = Math.min(Math.ceil(blockWidthOnSource), sourceCanvas.width - sx);
            const sh = Math.min(Math.ceil(blockHeightOnSource), sourceCanvas.height - sy);

            if (sw <= 0 || sh <= 0) continue; 

            const blockImageData = sourceCtx.getImageData(sx, sy, sw, sh);
            const blockData = blockImageData.data;
            let totalBrightness = 0;
            let pixelCount = 0;

            for (let i = 0; i < blockData.length; i += 4) {
                const r = blockData[i];
                const g = blockData[i + 1];
                const b = blockData[i + 2];
                // Standard luminance calculation (grayscale)
                const brightness = 0.299 * r + 0.587 * g + 0.114 * b;
                totalBrightness += brightness;
                pixelCount++;
            }
            
            const averageBrightness = pixelCount > 0 ? totalBrightness / pixelCount : (parsedInvert ? 0 : 255);

            // Map brightness to a character index.
            // Assumes `chars` string is sorted from sparse (light) to dense (dark).
            // High brightness (e.g., 255, white) maps to sparse characters (low index).
            // Low brightness (e.g., 0, black) maps to dense characters (high index).
            let charIdxNormalized = 1.0 - (averageBrightness / 255.0); // 0.0 for white, 1.0 for black

            // If `invertColors` is true (dark mode: white text on black BG),
            // then bright image areas should map to dense characters (which become bright white text),
            // and dark image areas to sparse characters (fainter white text).
            // This means the density mapping effectively flips.
            if (parsedInvert) {
                charIdxNormalized = 1.0 - charIdxNormalized; // Invert mapping for dark mode
            }
            
            const charIndex = Math.round(charIdxNormalized * (numPunctuationGlyphs - 1));
            const selectedChar = chars[Math.max(0, Math.min(numPunctuationGlyphs - 1, charIndex))];

            const drawX = x * charPixelWidth;
            const drawY = y * charPixelHeight;
            outCtx.fillText(selectedChar, drawX, drawY);
        }
    }

    return outputCanvas;
}

Free Image Tool Creator

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

Description

The Image To Punctuation Art Converter transforms regular images into artwork composed of various punctuation characters. This tool allows users to customize the output by selecting which punctuation characters to use, adjusting the font size, inverting colors, and modifying the contrast and brightness of the image. It is ideal for creating unique textual art for digital displays, social media posts, or artistic projects where traditional imagery is replaced with creative designs. Users can generate an output that retains the essence of the original image while presenting it in a novel, typographic format.

Leave a Reply

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