Please bookmark this page to avoid losing your image tool!

Image Tablature Notation Filter Effect Tool

(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.
async function processImage(
    originalImg,
    blockSize = 10,
    charSet = " -|=0123456789", // Ordered by perceived visual density: light to dark
    fontName = "monospace",
    fontSize = 10,
    backgroundColor = "white",
    charColor = "black",
    invertProcessing = 0, // 0 for false (dark image part -> dense char), 1 for true (light image part -> dense char)
    drawStaffLinesParam = 1, // 0 for false, 1 for true
    staffLineColor = "darkgray",
    staffLineThickness = 1,
    numStaffLinesPerGroup = 6,
    staffGroupSpacing = 1 // Number of empty character rows between staff line groups
) {
    // Parameter validation and type conversion
    const invert = !!invertProcessing; // Convert 0/1 to boolean
    const drawStaffLines = !!drawStaffLinesParam; // Convert 0/1 to boolean

    if (!Number.isFinite(blockSize) || blockSize <= 0) blockSize = 10;
    if (typeof charSet !== 'string' || charSet.length === 0) charSet = " -|=0123456789";
    if (typeof fontName !== 'string' || fontName.trim() === '') fontName = "monospace";
    if (!Number.isFinite(fontSize) || fontSize <= 0) fontSize = 10;
    if (typeof backgroundColor !== 'string') backgroundColor = "white";
    if (typeof charColor !== 'string') charColor = "black";
    if (typeof staffLineColor !== 'string') staffLineColor = "darkgray";
    if (!Number.isFinite(staffLineThickness) || staffLineThickness <= 0) staffLineThickness = 1;
    
    if (!Number.isFinite(numStaffLinesPerGroup) || numStaffLinesPerGroup < 0) {
         numStaffLinesPerGroup = 6;
    }
    // If drawing lines is intended, ensure there's at least one line per group, or default to 6.
    if (drawStaffLines && numStaffLinesPerGroup === 0) {
        numStaffLinesPerGroup = 6; 
    }

    if (!Number.isFinite(staffGroupSpacing) || staffGroupSpacing < 0) staffGroupSpacing = 0;


    // 1. Create an input canvas to draw the original image and get its pixel data.
    const inputCanvas = document.createElement('canvas');
    // Use naturalWidth/Height for images that might have CSS scaling
    inputCanvas.width = originalImg.naturalWidth || originalImg.width;
    inputCanvas.height = originalImg.naturalHeight || originalImg.height;
    const inputCtx = inputCanvas.getContext('2d', { willReadFrequently: true });
    inputCtx.drawImage(originalImg, 0, 0, inputCanvas.width, inputCanvas.height);

    // 2. Calculate the number of character columns and rows based on blockSize.
    const numCols = Math.floor(inputCanvas.width / blockSize);
    const numRows = Math.floor(inputCanvas.height / blockSize);

    // Handle cases where the image is too small for the given blockSize.
    if (numCols === 0 || numRows === 0) {
        const fallbackCanvas = document.createElement('canvas');
        // Ensure minimum 1px canvas, or fontSize if larger
        fallbackCanvas.width = Math.max(1, numCols > 0 ? numCols * fontSize : fontSize); 
        fallbackCanvas.height = Math.max(1, numRows > 0 ? numRows * fontSize : fontSize);
        
        const fallbackCtx = fallbackCanvas.getContext('2d');
        fallbackCtx.fillStyle = backgroundColor;
        fallbackCtx.fillRect(0, 0, fallbackCanvas.width, fallbackCanvas.height);
        
        if (charSet.length > 0 && fontSize > 0) { // Only try to draw char if possible
            fallbackCtx.fillStyle = charColor;
            fallbackCtx.font = `${fontSize}px ${fontName}`;
            fallbackCtx.textAlign = "center";
            fallbackCtx.textBaseline = "middle";
            // Attempt to draw a character if there's space
            if (fallbackCanvas.width >= fontSize && fallbackCanvas.height >= fontSize) {
                 fallbackCtx.fillText(charSet[0], fallbackCanvas.width / 2, fallbackCanvas.height / 2);
            }
        }
        return fallbackCanvas;
    }

    // 3. Create the output canvas.
    const outputCanvas = document.createElement('canvas');
    outputCanvas.width = numCols * fontSize;
    outputCanvas.height = numRows * fontSize;
    const outputCtx = outputCanvas.getContext('2d');

    // 4. Fill the output canvas with the background color.
    outputCtx.fillStyle = backgroundColor;
    outputCtx.fillRect(0, 0, outputCanvas.width, outputCanvas.height);

    // 5. Draw staff lines if enabled and numStaffLinesPerGroup > 0.
    if (drawStaffLines && numStaffLinesPerGroup > 0) {
        outputCtx.strokeStyle = staffLineColor;
        outputCtx.lineWidth = staffLineThickness;
        outputCtx.beginPath();

        let linesDrawnInCurrentGroup = 0;
        let spacerRowsPassed = 0; 

        for (let r = 0; r < numRows; r++) { // Iterate through each character row
            if (linesDrawnInCurrentGroup < numStaffLinesPerGroup) {
                // This character row is part of a staff line group; draw a line.
                const yPos = r * fontSize + fontSize / 2; // Center line in the char cell.
                outputCtx.moveTo(0, yPos);
                outputCtx.lineTo(outputCanvas.width, yPos);
                linesDrawnInCurrentGroup++;
                spacerRowsPassed = 0; // Reset spacer count as we are drawing a line.
            } else {
                // Current group of lines finished, now in spacing period or starting next group.
                spacerRowsPassed++;
                if (spacerRowsPassed > staffGroupSpacing) { 
                     // staffGroupSpacing defines N "empty" rows. After N empty rows, the N+1th row starts a new line group.
                    linesDrawnInCurrentGroup = 0; // Reset for the new group.
                    spacerRowsPassed = 0;         // Reset spacer counter.
                    
                    // Draw the first line of the new group in this current character row 'r'.
                    const yPos = r * fontSize + fontSize / 2;
                    outputCtx.moveTo(0, yPos);
                    outputCtx.lineTo(outputCanvas.width, yPos);
                    linesDrawnInCurrentGroup++;
                }
            }
        }
        outputCtx.stroke(); // Apply all line drawing operations.
    }

    // 6. Set up text properties for drawing characters.
    outputCtx.fillStyle = charColor;
    outputCtx.font = `${fontSize}px ${fontName}`;
    outputCtx.textAlign = "center";
    outputCtx.textBaseline = "middle";

    // 7. Iterate over image blocks, calculate brightness, map to a character, and draw it.
    for (let r = 0; r < numRows; r++) { // Character row
        for (let c = 0; c < numCols; c++) { // Character column
            const sx = c * blockSize; // Source X in original image
            const sy = r * blockSize; // Source Y in original image

            const currentBWidth = Math.min(blockSize, inputCanvas.width - sx);
            const currentBHeight = Math.min(blockSize, inputCanvas.height - sy);

            if (currentBWidth <= 0 || currentBHeight <= 0) continue;

            const imageData = inputCtx.getImageData(sx, sy, currentBWidth, currentBHeight);
            const data = imageData.data;
            let totalBrightness = 0;
            let pixelCount = 0;

            for (let i = 0; i < data.length; i += 4) {
                const red = data[i];
                const green = data[i+1];
                const blue = data[i+2];
                const brightness = (0.299 * red + 0.587 * green + 0.114 * blue) / 255; // 0-1
                totalBrightness += brightness;
                pixelCount++;
            }

            let avgBrightness = (pixelCount > 0) ? totalBrightness / pixelCount : 0;

            if (invert) {
                avgBrightness = 1.0 - avgBrightness;
            }
            
            avgBrightness = Math.max(0, Math.min(1, avgBrightness)); // Clamp to [0,1]
            
            const charIdx = Math.round(avgBrightness * (charSet.length - 1));
            const character = charSet[charIdx];

            const drawX = c * fontSize + fontSize / 2; 
            const drawY = r * fontSize + fontSize / 2; 
            
            outputCtx.fillText(character, 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 Tablature Notation Filter Effect Tool transforms images into a unique character-based representation, resembling musical tablature or ASCII art. Users can customize various parameters including block size, character set, font family, font size, colors, and the option to add staff lines, resulting in a visually structured output that captures the essence of the original image. This tool is ideal for artistic projects, music sheet creation, visual representations for web and social media, or any scenario where an image needs to be creatively reinterpreted through text-based format.

Leave a Reply

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