You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, blockSizeParam = 10, fontFamily = 'monospace', characters = 'A,C,G,T', charColor = 'original', backgroundColor = '#FFFFFF') {
const blockSize = Math.max(1, Math.floor(blockSizeParam)); // Ensure integer blockSize >= 1
const imgWidth = originalImg.naturalWidth;
const imgHeight = originalImg.naturalHeight;
// Handle cases where the image might be empty or not loaded properly
if (imgWidth === 0 || imgHeight === 0) {
const emptyCanvas = document.createElement('canvas');
emptyCanvas.width = 0;
emptyCanvas.height = 0;
// Optionally, log a warning or return a minimal canvas indicating an issue
// console.warn("Input image has zero width or height.");
return emptyCanvas;
}
// 1. Create a source canvas to draw the original image and get pixel data
const srcCanvas = document.createElement('canvas');
srcCanvas.width = imgWidth;
srcCanvas.height = imgHeight;
const srcCtx = srcCanvas.getContext('2d', { willReadFrequently: true }); // Hint for optimization
try {
srcCtx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
} catch (e) {
// This can happen if originalImg is not a valid drawable source (e.g. broken image)
console.error("Error drawing image to source canvas:", e);
const errorCanvas = document.createElement('canvas');
errorCanvas.width = imgWidth; // Use original dimensions if available
errorCanvas.height = imgHeight;
const errorCtx = errorCanvas.getContext('2d');
errorCtx.fillStyle = 'lightgray';
errorCtx.fillRect(0, 0, errorCanvas.width, errorCanvas.height);
errorCtx.fillStyle = 'red';
errorCtx.font = '16px Arial';
errorCtx.textAlign = 'center';
errorCtx.textBaseline = 'middle';
errorCtx.fillText('Error: Invalid image source.', errorCanvas.width / 2, errorCanvas.height / 2);
return errorCanvas;
}
let imageData;
try {
imageData = srcCtx.getImageData(0, 0, imgWidth, imgHeight);
} catch (e) {
// This is a common issue with cross-origin images lacking CORS headers
console.error("Error getting image data (this can be due to CORS policy):", e);
const errorCanvas = document.createElement('canvas');
errorCanvas.width = imgWidth;
errorCanvas.height = imgHeight;
const errorCtx = errorCanvas.getContext('2d');
errorCtx.fillStyle = 'lightgray';
errorCtx.fillRect(0, 0, errorCanvas.width, errorCanvas.height);
errorCtx.fillStyle = 'red';
errorCtx.font = '16px Arial';
errorCtx.textAlign = 'center';
errorCtx.textBaseline = 'middle';
let errorMessage = 'Error: Could not process image pixels.';
if (e.name === 'SecurityError') {
errorMessage += ' (Cross-origin image?)';
}
errorCtx.fillText(errorMessage, errorCanvas.width / 2, errorCanvas.height / 2);
return errorCanvas;
}
const srcPixels = imageData.data;
// 2. Create the destination canvas
const destCanvas = document.createElement('canvas');
destCanvas.width = imgWidth;
destCanvas.height = imgHeight;
const destCtx = destCanvas.getContext('2d');
// 3. Set background color for destination canvas
destCtx.fillStyle = backgroundColor;
destCtx.fillRect(0, 0, destCanvas.width, destCanvas.height);
// 4. Parse characters parameter
const charArray = characters.split(',').map(c => c.trim()).filter(c => c.length > 0);
if (charArray.length === 0) {
charArray.push('X'); // Use 'X' as a fallback if no valid characters are provided
}
// 5. Set font properties for drawing characters
// Scale font size relative to block size, ensuring a minimum of 1px.
const fontSize = Math.max(1, Math.floor(blockSize * 0.8));
destCtx.font = `${fontSize}px ${fontFamily}`;
destCtx.textAlign = 'center';
destCtx.textBaseline = 'middle';
// 6. Process image in blocks
for (let yBlock = 0; yBlock < imgHeight; yBlock += blockSize) {
for (let xBlock = 0; xBlock < imgWidth; xBlock += blockSize) {
let rSum = 0, gSum = 0, bSum = 0;
let numPixelsInBlock = 0;
// Determine the actual width and height of the current block (may be smaller at image edges)
const currentBlockWidth = Math.min(blockSize, imgWidth - xBlock);
const currentBlockHeight = Math.min(blockSize, imgHeight - yBlock);
// Iterate over pixels within the current block to calculate average color
for (let y = yBlock; y < yBlock + currentBlockHeight; y++) {
for (let x = xBlock; x < xBlock + currentBlockWidth; x++) {
const pixelStartIndex = (y * imgWidth + x) * 4; // Each pixel is 4 bytes (R,G,B,A)
rSum += srcPixels[pixelStartIndex];
gSum += srcPixels[pixelStartIndex + 1];
bSum += srcPixels[pixelStartIndex + 2];
// Alpha (srcPixels[pixelStartIndex + 3]) is available but ignored for average R,G,B
numPixelsInBlock++;
}
}
if (numPixelsInBlock === 0) {
continue; // Should not happen with blockSize >= 1 and valid img dimensions
}
const avgR = rSum / numPixelsInBlock;
const avgG = gSum / numPixelsInBlock;
const avgB = bSum / numPixelsInBlock;
// Calculate brightness (simple average of R,G,B; range 0-255)
const brightness = (avgR + avgG + avgB) / 3;
// Select character based on brightness
// Map brightness [0, 255] to an index in charArray [0, charArray.length - 1]
const charIndex = Math.floor(brightness * charArray.length / 256);
// Ensure index is within bounds (primarily for brightness === 255 edge case)
const selectedChar = charArray[Math.min(charIndex, charArray.length - 1)];
// Determine the color for the character
let finalCharColor;
if (charColor.toLowerCase() === 'original') {
// Use the average color of the block for the character
finalCharColor = `rgb(${Math.round(avgR)}, ${Math.round(avgG)}, ${Math.round(avgB)})`;
} else {
// Use the user-specified fixed color string
finalCharColor = charColor;
}
destCtx.fillStyle = finalCharColor;
// Calculate the center coordinates of the block for text placement
const centerX = xBlock + currentBlockWidth / 2;
const centerY = yBlock + currentBlockHeight / 2;
destCtx.fillText(selectedChar, centerX, centerY);
}
}
return destCanvas;
}
Apply Changes