You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, cellSize = 8, threshold = 128, invertColors = "false", fontFamily = "monospace", foregroundColor = "black", backgroundColor = "white") {
// 1. Parameter parsing and validation
let cellSizeNum = Number(cellSize);
if (isNaN(cellSizeNum) || cellSizeNum <= 0) {
cellSizeNum = 8; // Default cellSize
}
// Braille uses 2x4 dots. Minimum 1 pixel per dot segment.
// Cell needs to be at least 2 wide for 2 columns, and 4 high for 4 rows of dots.
// We take Math.max of these minimums if user provides smaller. So, effectively min cell size is 4x4.
cellSizeNum = Math.max(4, Math.floor(cellSizeNum));
let thresholdNum = Number(threshold);
if (isNaN(thresholdNum)) {
thresholdNum = 128;
}
thresholdNum = Math.max(0, Math.min(255, thresholdNum)); // Clamp to 0-255
const invert = String(invertColors).toLowerCase() === 'true';
const fontFamilyStr = String(fontFamily).trim() || "monospace";
const fgColorStr = String(foregroundColor).trim() || "black";
const bgColorStr = String(backgroundColor).trim() || "white";
// 2. Source canvas setup (to get pixel data)
const srcCanvas = document.createElement('canvas');
const srcCtx = srcCanvas.getContext('2d', { willReadFrequently: true });
srcCanvas.width = originalImg.width;
srcCanvas.height = originalImg.height;
if (originalImg.width === 0 || originalImg.height === 0) {
const zeroSizeCanvas = document.createElement('canvas');
zeroSizeCanvas.width = 100; // Minimal display canvas
zeroSizeCanvas.height = 50;
const zsCtx = zeroSizeCanvas.getContext('2d');
zsCtx.fillStyle = bgColorStr;
zsCtx.fillRect(0, 0, zeroSizeCanvas.width, zeroSizeCanvas.height);
zsCtx.fillStyle = fgColorStr;
zsCtx.font = "12px sans-serif";
zsCtx.textAlign = "center";
zsCtx.textBaseline = "middle";
zsCtx.fillText("Original image is empty.", zeroSizeCanvas.width / 2, zeroSizeCanvas.height / 2);
return zeroSizeCanvas;
}
srcCtx.drawImage(originalImg, 0, 0);
let imgData;
try {
imgData = srcCtx.getImageData(0, 0, srcCanvas.width, srcCanvas.height);
} catch (e) {
// Handle potential security exception if image is cross-origin and canvas is tainted
console.error("Error getting ImageData:", e);
const errorCanvas = document.createElement('canvas');
// Use a somewhat fixed size for error message or try to match original if very large.
errorCanvas.width = Math.min(300, Math.max(100, originalImg.width));
errorCanvas.height = Math.min(150, Math.max(50, originalImg.height));
const errCtx = errorCanvas.getContext('2d');
errCtx.fillStyle = bgColorStr;
errCtx.fillRect(0,0, errorCanvas.width, errorCanvas.height);
errCtx.fillStyle = fgColorStr;
errCtx.font = "12px sans-serif";
errCtx.textAlign = "center";
errCtx.textBaseline = "middle";
errCtx.fillText("Error: Cannot process cross-origin image.", errorCanvas.width / 2, errorCanvas.height / 2);
return errorCanvas;
}
const pixels = imgData.data;
// 3. Output canvas setup
const numCharsX = Math.floor(originalImg.width / cellSizeNum);
const numCharsY = Math.floor(originalImg.height / cellSizeNum);
if (numCharsX === 0 || numCharsY === 0) {
const fallbackCanvas = document.createElement('canvas');
fallbackCanvas.width = Math.max(cellSizeNum * 5, originalImg.width, 150);
fallbackCanvas.height = Math.max(cellSizeNum * 2, originalImg.height, 50);
const fbCtx = fallbackCanvas.getContext('2d');
fbCtx.fillStyle = bgColorStr;
fbCtx.fillRect(0, 0, fallbackCanvas.width, fallbackCanvas.height);
fbCtx.fillStyle = fgColorStr;
const fbFontSize = Math.max(10, Math.min(12, fallbackCanvas.height / 3, fallbackCanvas.width / 15));
fbCtx.font = `${fbFontSize}px sans-serif`;
fbCtx.textAlign = "center";
fbCtx.textBaseline = "middle";
fbCtx.fillText("Image too small or cell size too large for Braille grid.", fallbackCanvas.width / 2, fallbackCanvas.height / 2);
return fallbackCanvas;
}
const outCanvas = document.createElement('canvas');
const outCtx = outCanvas.getContext('2d');
outCanvas.width = numCharsX * cellSizeNum;
outCanvas.height = numCharsY * cellSizeNum;
outCtx.fillStyle = bgColorStr;
outCtx.fillRect(0, 0, outCanvas.width, outCanvas.height);
outCtx.fillStyle = fgColorStr;
const fontSize = cellSizeNum;
outCtx.font = `${fontSize}px ${fontFamilyStr}`; // Using cellSizeNum as font size
outCtx.textAlign = "center";
outCtx.textBaseline = "middle";
// 4. Main processing loop
for (let cy = 0; cy < numCharsY; cy++) {
for (let cx = 0; cx < numCharsX; cx++) {
let brailleValue = 0; // Accumulator for Braille dot bits
// Iterate over the 8 conceptual dots in a 2-column x 4-row grid for the Braille character
for (let dotIndex = 0; dotIndex < 8; dotIndex++) {
const dotCol = dotIndex % 2; // 0 for left column, 1 for right column
const dotRow = Math.floor(dotIndex / 2); // 0, 1, 2, 3 for dot row
// Define the sub-region in the original image for this specific dot
const subCellNominalWidth = cellSizeNum / 2;
const subCellNominalHeight = cellSizeNum / 4;
const imgRegionX = Math.floor((cx * cellSizeNum) + (dotCol * subCellNominalWidth));
const imgRegionY = Math.floor((cy * cellSizeNum) + (dotRow * subCellNominalHeight));
// Ensure sub-region dimensions are at least 1x1 pixel for sampling
const imgRegionW = Math.max(1, Math.floor(subCellNominalWidth));
const imgRegionH = Math.max(1, Math.floor(subCellNominalHeight));
// Calculate average grayscale intensity for this sub-region
let sumIntensity = 0;
let pixelCount = 0;
for (let srY = 0; srY < imgRegionH; srY++) {
for (let srX = 0; srX < imgRegionW; srX++) {
const currentPixelX = imgRegionX + srX;
const currentPixelY = imgRegionY + srY;
// Ensure pixel coordinates are within the original image bounds
if (currentPixelX >= 0 && currentPixelX < originalImg.width &&
currentPixelY >= 0 && currentPixelY < originalImg.height) {
const pixelStartIndex = (currentPixelY * originalImg.width + currentPixelX) * 4;
const r = pixels[pixelStartIndex];
const g = pixels[pixelStartIndex + 1];
const b = pixels[pixelStartIndex + 2];
const gray = (r * 0.299 + g * 0.587 + b * 0.114); // Luminosity method
sumIntensity += gray;
pixelCount++;
}
}
}
// Default intensity if no pixels found (e.g. region slightly outside image boundaries)
// Set to max or min intensity depending on invert to make it a non-dot.
const avgIntensity = (pixelCount > 0) ? (sumIntensity / pixelCount) : (invert ? 0 : 255);
// Determine if this dot should be active
let dotIsActive;
if (invert) { // Lighter parts of the image become dots
dotIsActive = avgIntensity > thresholdNum;
} else { // Darker parts of the image become dots (default behavior)
dotIsActive = avgIntensity < thresholdNum;
}
if (dotIsActive) {
let bitPosition = -1;
// Map visual dotIndex (0-7, our 2x4 grid) to Braille Unicode bit positions (0-7)
// Visual dot arrangement: Braille dot numbers: Corresponding bit:
// (0,0) dotIndex=0 dot 1 (top-left) bit 0
// (1,0) dotIndex=1 dot 4 (top-right) bit 3
// (0,1) dotIndex=2 dot 2 bit 1
// (1,1) dotIndex=3 dot 5 bit 4
// (0,2) dotIndex=4 dot 3 bit 2
// (1,2) dotIndex=5 dot 6 bit 5
// (0,3) dotIndex=6 dot 7 (bottom-left) bit 6
// (1,3) dotIndex=7 dot 8 (bottom-right) bit 7
if (dotIndex === 0) bitPosition = 0;
else if (dotIndex === 1) bitPosition = 3;
else if (dotIndex === 2) bitPosition = 1;
else if (dotIndex === 3) bitPosition = 4;
else if (dotIndex === 4) bitPosition = 2;
else if (dotIndex === 5) bitPosition = 5;
else if (dotIndex === 6) bitPosition = 6;
else if (dotIndex === 7) bitPosition = 7;
if (bitPosition !== -1) {
brailleValue |= (1 << bitPosition);
}
}
}
const brailleChar = String.fromCharCode(0x2800 + brailleValue);
// Calculate position to draw the character, centered in its allocated cell
const drawX = (cx + 0.5) * cellSizeNum;
const drawY = (cy + 0.5) * cellSizeNum;
outCtx.fillText(brailleChar, drawX, drawY);
}
}
return outCanvas;
}
Apply Changes