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!
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.