You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(
originalImg,
characterSet = " ・ヽーリハメカサタキヌア字画愛龍鬱", // User-provided string, ordered by visual density (lightest to densest)
resolutionFactor = 0.07, // Determines block size for sampling: block size = 1/resolutionFactor. Smaller val = coarser.
fontSize = 10, // Font size of characters in output canvas
fontFamily = "'MS Mincho', 'Yu Gothic', 'Meiryo', 'Noto Sans JP', 'Hiragino Kaku Gothic ProN', 'sans-serif'",
invertColorOutput = 0, // 0 for black text on white bg, 1 for white text on black bg
invertBrightnessMap = 0 // 0 for dark image areas -> dense chars, 1 for light image areas -> dense chars
) {
// --- 1. Parameter Sanitization ---
const parsedRF = parseFloat(resolutionFactor);
resolutionFactor = (isNaN(parsedRF) || parsedRF === 0) ? 0.07 : parsedRF;
resolutionFactor = Math.max(0.001, Math.min(1, resolutionFactor)); // Clamp to avoid extreme block sizes
const parsedFontSize = parseInt(fontSize);
fontSize = isNaN(parsedFontSize) || parsedFontSize <= 0 ? 10 : parsedFontSize;
fontSize = Math.max(1, fontSize); // Ensure font size is at least 1
invertColorOutput = (parseInt(invertColorOutput) === 1) ? 1 : 0;
invertBrightnessMap = (parseInt(invertBrightnessMap) === 1) ? 1 : 0;
let chars = characterSet.split('');
if (chars.length === 0) {
chars = ['?']; // Fallback for empty character set
}
const numChars = chars.length;
// --- 2. Prepare Source Image Data on a Temporary Canvas ---
// Using naturalWidth/Height for intrinsic dimensions
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
if (imgWidth === 0 || imgHeight === 0) {
console.error("Image has no dimensions.");
const errorCanvas = document.createElement('canvas');
errorCanvas.width = 200; errorCanvas.height = 30;
const errCtx = errorCanvas.getContext('2d');
errCtx.font = "12px Arial"; errCtx.fillStyle = "red";
errCtx.fillText("Error: Image has no dimensions.", 5, 20);
return errorCanvas;
}
const tempCanvas = document.createElement('canvas');
tempCanvas.width = imgWidth;
tempCanvas.height = imgHeight;
const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
// Fill with white background first to handle transparency in source image
// Transparent areas will be treated as "white" for brightness calculation.
tempCtx.fillStyle = 'white';
tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height);
tempCtx.drawImage(originalImg, 0, 0);
// --- 3. Calculate Output Dimensions ---
// samplingBlockSize: The width/height of a pixel block in the original image that maps to one character.
const samplingBlockSize = Math.max(1, Math.floor(1 / resolutionFactor));
const numCharCols = Math.floor(imgWidth / samplingBlockSize);
const numCharRows = Math.floor(imgHeight / samplingBlockSize);
if (numCharCols === 0 || numCharRows === 0) {
console.error("Output character grid is 0x0. Image too small or resolutionFactor too low for current image size.");
const errorCanvas = document.createElement('canvas');
errorCanvas.width = 300; errorCanvas.height = 30;
const errCtx = errorCanvas.getContext('2d');
errCtx.font = "12px Arial"; errCtx.fillStyle = "red";
errCtx.fillText("Error: Output grid is 0x0. Adjust image/resolution.", 5, 20);
return errorCanvas;
}
// --- 4. Prepare Output Canvas ---
const outputCanvas = document.createElement('canvas');
// Japanese characters are often full-width (square-ish).
// Use fontSize for both width and height of the character cell for layout.
const charCellWidth = fontSize;
const charCellHeight = fontSize;
outputCanvas.width = numCharCols * charCellWidth;
outputCanvas.height = numCharRows * charCellHeight;
const outCtx = outputCanvas.getContext('2d');
// --- 5. Set Up Drawing Style on Output Canvas ---
outCtx.font = `${fontSize}px ${fontFamily}`;
outCtx.textBaseline = 'middle'; // Align text vertically to the middle of the cell
outCtx.textAlign = 'center'; // Align text horizontally to the center of the cell
const bgColor = (invertColorOutput === 1) ? 'black' : 'white';
const textColor = (invertColorOutput === 1) ? 'white' : 'black';
outCtx.fillStyle = bgColor;
outCtx.fillRect(0, 0, outputCanvas.width, outputCanvas.height);
outCtx.fillStyle = textColor;
// --- 6. Process Image Blocks and Draw Characters ---
for (let r = 0; r < numCharRows; r++) { // Current character row
for (let c = 0; c < numCharCols; c++) { // Current character column
const imgBlockX = c * samplingBlockSize;
const imgBlockY = r * samplingBlockSize;
// Ensure block dimensions are within image bounds (especially for last row/col)
const blockW = Math.min(samplingBlockSize, imgWidth - imgBlockX);
const blockH = Math.min(samplingBlockSize, imgHeight - imgBlockY);
if (blockW <= 0 || blockH <= 0) continue; // Should not happen if numCharCols/Rows > 0
const imageData = tempCtx.getImageData(imgBlockX, imgBlockY, blockW, blockH);
const data = imageData.data;
let totalBrightness = 0;
let pixelCount = 0;
for (let i = 0; i < data.length; i += 4) {
const R = data[i];
const G = data[i + 1];
const B = data[i + 2];
// Using standard luminance formula (ITU-R BT.709)
const brightness = 0.2126 * R + 0.7152 * G + 0.0722 * B; // More perceptually accurate
// const brightness = 0.299 * R + 0.587 * G + 0.114 * B; // Older standard, also common
totalBrightness += brightness;
pixelCount++;
}
const avgBrightness = (pixelCount > 0) ? (totalBrightness / pixelCount) : 0; // Range: 0 (black) to 255 (white)
const normalizedBrightness = avgBrightness / 255; // Range: 0 to 1
let charSelectValue; // This value (0-1) determines character from "lightest" (0) to "densest" (1)
if (invertBrightnessMap === 1) {
// Light image areas map to "denser" characters (from end of characterSet)
charSelectValue = normalizedBrightness;
} else {
// Dark image areas map to "denser" characters (default behavior)
charSelectValue = 1.0 - normalizedBrightness;
}
// Select character from the characterSet (assumed to be ordered light to dense)
// Use Math.round for better distribution across characters
const charIndex = Math.max(0, Math.min(numChars - 1, Math.round(charSelectValue * (numChars - 1))));
const charToDraw = chars[charIndex];
// Calculate drawing position for the character (center of its cell)
const drawX = c * charCellWidth + charCellWidth * 0.5;
const drawY = r * charCellHeight + charCellHeight * 0.5;
outCtx.fillText(charToDraw, drawX, drawY);
}
}
return outputCanvas;
}
Apply Changes