You can edit the below JavaScript code to customize the image tool.
Apply Changes
/**
* Translates text into a custom alphabet defined by a sprite sheet image.
* This function renders the translated text onto a new canvas, handling
* line wrapping and custom character spacing.
*
* The character mapping is defined by a JSON string (`mapJson`) which can be
* in one of two formats: "grid" or "explicit".
*
* 1. Grid Format:
* Assumes characters are in a uniform grid in the source image.
* - `config.type`: Must be "grid".
* - `config.charWidth`: The width of a single character cell.
* - `config.charHeight`: The height of a single character cell.
* - `config.spaceWidth`: (Optional) The width of a space character.
* - `map`: An object where keys are characters ('A', 'B', etc.) and
* values are [column, row] arrays indicating the character's
* position in the grid (e.g., "A": [0, 0]).
*
* 2. Explicit Format (default if `config.type` is not "grid"):
* Allows each character to have a different size and position.
* - `map`: An object where keys are characters and values are objects
* with {x, y, w, h} properties defining the source rectangle
* in the image (e.g., "A": {"x":0, "y":0, "w":10, "h":12}).
*
* @param {Image} originalImg The source image object containing the alphabet sprites.
* @param {string} textToTranslate The text to be rendered.
* @param {string} mapJson A JSON string describing the character map. If empty, a default grid-based map is used.
* @param {number} outputWidth The target width of the canvas. Text will wrap to fit this width.
* @param {number} letterSpacing The number of pixels of extra space between letters.
* @param {number} lineHeightMultiplier A multiplier for line height, based on the tallest character. E.g., 1.2 means 20% extra space between lines.
* @returns {HTMLCanvasElement} A new canvas element with the rendered text.
*/
async function processImage(
originalImg,
textToTranslate = 'The quick brown fox jumps over the lazy dog. 1234567890.',
mapJson = '',
outputWidth = 800,
letterSpacing = 1,
lineHeightMultiplier = 1.2
) {
const defaultMapJson = `{
"config": {
"type": "grid",
"charWidth": 32,
"charHeight": 32,
"spaceWidth": 16
},
"map": {
"A": [0,0], "B": [1,0], "C": [2,0], "D": [3,0], "E": [4,0], "F": [5,0], "G": [6,0], "H": [7,0], "I": [8,0], "J": [9,0],
"K": [0,1], "L": [1,1], "M": [2,1], "N": [3,1], "O": [4,1], "P": [5,1], "Q": [6,1], "R": [7,1], "S": [8,1], "T": [9,1],
"U": [0,2], "V": [1,2], "W": [2,2], "X": [3,2], "Y": [4,2], "Z": [5,2],
"a": [0,3], "b": [1,3], "c": [2,3], "d": [3,3], "e": [4,3], "f": [5,3], "g": [6,3], "h": [7,3], "i": [8,3], "j": [9,3],
"k": [0,4], "l": [1,4], "m": [2,4], "n": [3,4], "o": [4,4], "p": [5,4], "q": [6,4], "r": [7,4], "s": [8,4], "t": [9,4],
"u": [0,5], "v": [1,5], "w": [2,5], "x": [3,5], "y": [4,5], "z": [5,5],
"0": [0,6], "1": [1,6], "2": [2,6], "3": [3,6], "4": [4,6], "5": [5,6], "6": [6,6], "7": [7,6], "8": [8,6], "9": [9,6],
".": [0,7], ",": [1,7], "!": [2,7], "?": [3,7], "'": [4,7], "\\"": [5,7], "(": [6,7], ")": [7,7], "-": [8,7], ":": [9,7]
}
}`;
if (mapJson.trim() === '') {
mapJson = defaultMapJson;
}
let charMap;
try {
charMap = JSON.parse(mapJson);
} catch (e) {
const errorCanvas = document.createElement('canvas');
errorCanvas.width = Math.max(300, outputWidth);
errorCanvas.height = 120;
const ctx = errorCanvas.getContext('2d');
ctx.fillStyle = '#f8f8f8';
ctx.fillRect(0, 0, errorCanvas.width, errorCanvas.height);
ctx.fillStyle = 'red';
ctx.font = 'bold 16px sans-serif';
ctx.fillText('Error: Invalid Character Map JSON', 10, 25);
ctx.fillStyle = 'black';
ctx.font = '14px monospace';
ctx.fillText(e.message, 10, 50);
ctx.fillText('Please correct the JSON syntax and try again.', 10, 75);
return errorCanvas;
}
const fallbackCharSymbol = charMap.map ?. ['?'] ? '?' : null;
const getCharData = (char) => {
const mapConfig = charMap.config || {};
const mapData = charMap.map || {};
let charInfo = mapData[char];
if (!charInfo && fallbackCharSymbol) {
charInfo = mapData[fallbackCharSymbol];
}
if (!charInfo) return null;
if (mapConfig.type === 'grid') {
const cw = mapConfig.charWidth || 32;
const ch = mapConfig.charHeight || 32;
return {
sx: charInfo[0] * cw,
sy: charInfo[1] * ch,
sw: cw,
sh: ch
};
} else {
return {
sx: charInfo.x,
sy: charInfo.y,
sw: charInfo.w,
sh: charInfo.h
};
}
};
let maxCharHeight = 0;
if (charMap.config ?.type === 'grid' && charMap.config.charHeight) {
maxCharHeight = charMap.config.charHeight;
} else {
Object.keys(charMap.map || {}).forEach(char => {
const data = getCharData(char);
if (data && data.sh > maxCharHeight) maxCharHeight = data.sh;
});
}
if (maxCharHeight === 0) maxCharHeight = 32;
const lineHeight = Math.round(maxCharHeight * lineHeightMultiplier);
const spaceWidth = charMap.config ?.spaceWidth || Math.round(maxCharHeight / 2);
// --- Prepare characters for layout ---
const layoutItems = [];
for (const char of textToTranslate) {
if (char === '\n') {
layoutItems.push({
isNewline: true
});
continue;
}
if (/\s/.test(char)) {
layoutItems.push({
isSpace: true,
sw: spaceWidth
});
continue;
}
const data = getCharData(char);
if (data) {
layoutItems.push(data);
}
}
// --- Arrange characters into wrapped lines ---
const linesToDraw = [
[]
];
let currentLineIndex = 0;
let x = 0;
layoutItems.forEach(item => {
if (item.isNewline) {
linesToDraw.push([]);
currentLineIndex++;
x = 0;
return;
}
const itemWidth = item.sw + letterSpacing;
if (x > 0 && x + itemWidth > outputWidth && !item.isSpace) {
linesToDraw.push([]);
currentLineIndex++;
x = 0;
}
linesToDraw[currentLineIndex].push(item);
x += itemWidth;
});
// --- Create canvas and render lines ---
const canvasHeight = Math.max(1, linesToDraw.length) * lineHeight;
const canvas = document.createElement('canvas');
canvas.width = outputWidth;
canvas.height = canvasHeight;
const ctx = canvas.getContext('2d');
let currentY = 0;
linesToDraw.forEach(line => {
let drawX = 0;
line.forEach(item => {
if (item.isSpace || item.isNewline) {
drawX += item.sw + letterSpacing;
} else {
const yOffset = Math.floor((lineHeight - item.sh) / 2);
ctx.drawImage(originalImg, item.sx, item.sy, item.sw, item.sh, drawX, currentY + yOffset, item.sw, item.sh);
drawX += item.sw + letterSpacing;
}
});
currentY += lineHeight;
});
return canvas;
}
Apply Changes