You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
outputWidthCharsStr = "80",
fontSizeStr = "10",
charSetString = ' ,ㆍ,ᆢ,ㅣ,ㅡ,ㄱ,ㄴ,ㅇ,ㅅ,ㅏ,ㅗ,ㄷ,ㄹ,ㅈ,ㅁ,ㅂ,ㅎ,ㅊ,ㅋ,ㅌ,ㅍ,ㄲ,ㄸ,ㅃ,ㅆ,ㅉ',
invertColorsStr = "false",
backgroundColorStr = "white",
textColorStr = "black"
) {
// --- Parameter Parsing and Validation ---
const outputWidthChars = parseInt(outputWidthCharsStr, 10) || 80;
const fontSize = parseInt(fontSizeStr, 10) || 10;
let chars = charSetString.split(',');
// If the character set is empty or contains only empty strings (e.g., from " ,, "), use a fallback.
if (chars.length === 0 || chars.every(c => c === "")) {
chars = [' ', '·', '■']; // A simple fallback set: space, middle dot, black square
}
const invert = invertColorsStr === 'true';
const backgroundColor = backgroundColorStr || 'white';
const textColor = textColorStr || 'black';
const errorCanvas = document.createElement('canvas'); // Used for returning in case of error
if (originalImg.width === 0 || originalImg.height === 0) {
console.error("Original image has zero width or height.");
errorCanvas.width = 1; errorCanvas.height = 1;
return errorCanvas;
}
if (outputWidthChars <= 0) {
console.error("Output width in characters must be positive.");
errorCanvas.width = originalImg.width; errorCanvas.height = originalImg.height;
const ctx = errorCanvas.getContext('2d');
if (ctx) ctx.drawImage(originalImg, 0, 0);
return errorCanvas;
}
if (fontSize <= 0) {
console.error("Font size must be positive.");
errorCanvas.width = originalImg.width; errorCanvas.height = originalImg.height;
const ctx = errorCanvas.getContext('2d');
if (ctx) ctx.drawImage(originalImg, 0, 0);
return errorCanvas;
}
// --- Font Loading (Nanum Gothic Coding) ---
const FONT_NAME_TAG = 'NanumGothicCodingHangulArt'; // Unique name for our loaded font
let FONT_FAMILY_TO_USE = 'monospace'; // Default fallback
if (typeof FontFace === 'function' && document.fonts) { // Check if FontFace API and document.fonts are supported
try {
// Check if the font is already loaded and available under our custom tag
let fontAlreadyLoaded = false;
for (const font of document.fonts) {
if (font.family === FONT_NAME_TAG && font.status === 'loaded') {
fontAlreadyLoaded = true;
break;
}
}
if (fontAlreadyLoaded) {
FONT_FAMILY_TO_USE = FONT_NAME_TAG;
} else {
const fontFace = new FontFace(FONT_NAME_TAG, 'url(https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_one@1.0/NanumGothicCoding.woff)');
await fontFace.load();
document.fonts.add(fontFace);
FONT_FAMILY_TO_USE = FONT_NAME_TAG;
}
} catch (e) {
console.warn(`Failed to load ${FONT_NAME_TAG} font. Falling back to generic monospace. Error: ${e}`);
// FONT_FAMILY_TO_USE remains 'monospace'
}
} else {
console.warn("FontFace API or document.fonts not supported. Using generic monospace.");
}
// --- Determine Character Cell Dimensions ---
const tempMeasureCanvas = document.createElement('canvas');
const tempMeasureCtx = tempMeasureCanvas.getContext('2d');
if (!tempMeasureCtx) return document.createElement('canvas'); // Should not happen
tempMeasureCtx.font = `${fontSize}px ${FONT_FAMILY_TO_USE}`;
// Use '한' (a common full-width Hangul syllable) for width measurement.
// Fallback to fontSize, assuming square cell if measurement fails.
const charDisplayWidth = tempMeasureCtx.measureText('한').width || fontSize;
const charDisplayHeight = fontSize; // Font size is typically the primary measure of character height.
if (charDisplayWidth === 0) {
console.error("Character display width is zero (font measurement failed).");
errorCanvas.width = originalImg.width; errorCanvas.height = originalImg.height;
const ctx = errorCanvas.getContext('2d');
if (ctx) ctx.drawImage(originalImg, 0, 0);
return errorCanvas;
}
// --- Calculate Output Canvas Dimensions in Characters ---
const imageAspectRatio = originalImg.height / originalImg.width;
const charCellAspectRatio = charDisplayWidth / charDisplayHeight;
const outputHeightChars = Math.round(outputWidthChars * imageAspectRatio / charCellAspectRatio);
if (isNaN(outputHeightChars) || outputHeightChars <= 0) {
console.error("Calculated output height in characters is invalid (<=0 or NaN).");
errorCanvas.width = originalImg.width; errorCanvas.height = originalImg.height;
const ctx = errorCanvas.getContext('2d');
if(ctx) ctx.drawImage(originalImg,0,0);
return errorCanvas;
}
// --- Prepare Sampling Canvas ---
// This canvas is used to sample pixel colors from the original image, scaled down to character grid size.
const samplingCanvas = document.createElement('canvas');
samplingCanvas.width = outputWidthChars;
samplingCanvas.height = outputHeightChars;
const samplingCtx = samplingCanvas.getContext('2d');
if (!samplingCtx) return document.createElement('canvas');
samplingCtx.drawImage(originalImg, 0, 0, samplingCanvas.width, samplingCanvas.height);
let imageData;
try {
imageData = samplingCtx.getImageData(0, 0, samplingCanvas.width, samplingCanvas.height);
} catch (e) {
console.error("Could not getImageData (CORS issue if image is cross-origin and canvas is tainted):", e);
errorCanvas.width = originalImg.width; errorCanvas.height = originalImg.height;
const errCtx = errorCanvas.getContext('2d');
if(errCtx) errCtx.drawImage(originalImg, 0,0); // Return original image sketch on error
return errorCanvas;
}
const data = imageData.data;
// --- Create and Prepare Final Output Canvas ---
const outputCanvas = document.createElement('canvas');
outputCanvas.width = outputWidthChars * charDisplayWidth;
outputCanvas.height = outputHeightChars * charDisplayHeight;
const outputCtx = outputCanvas.getContext('2d');
if (!outputCtx) return document.createElement('canvas');
// Fill background
outputCtx.fillStyle = backgroundColor;
outputCtx.fillRect(0, 0, outputCanvas.width, outputCanvas.height);
// Set text properties
outputCtx.fillStyle = textColor;
outputCtx.font = `${fontSize}px ${FONT_FAMILY_TO_USE}`;
outputCtx.textBaseline = 'top'; // Simplifies y-coordinate setting for fillText
// --- Generate Hangul Art ---
// Loop through the pixels of the scaled-down sampling image
for (let y = 0; y < outputHeightChars; y++) {
for (let x = 0; x < outputWidthChars; x++) {
const pixelIndex = (y * samplingCanvas.width + x) * 4;
const r = data[pixelIndex];
const g = data[pixelIndex + 1];
const b = data[pixelIndex + 2];
// Alpha (data[pixelIndex + 3]) is ignored for this tool.
// Calculate grayscale value (luminance)
let gray = 0.299 * r + 0.587 * g + 0.114 * b;
if (invert) {
gray = 255 - gray;
}
// Map grayscale value to a character in the character set
let charIndex;
if (chars.length === 1) {
charIndex = 0;
} else {
// Map gray value [0, 255] to an index in [0, chars.length - 1]
charIndex = Math.floor((gray / 256) * chars.length);
// Clamp index to be safe, though Math.floor(255/256 * L) should be L-1 max
charIndex = Math.min(chars.length - 1, Math.max(0, charIndex));
}
const selectedChar = chars[charIndex];
if (selectedChar && selectedChar !== "") { // Draw character if it's not an empty string
outputCtx.fillText(selectedChar, x * charDisplayWidth, y * charDisplayHeight);
}
}
}
return outputCanvas;
}
Apply Changes