UNPKG

image-js

Version:

Image processing and manipulation in JavaScript

108 lines 3.92 kB
import { getCanvasContext } from "../../utils/cross_platform.js"; import { getOutputImage } from "../../utils/getOutputImage.js"; import { validateValues } from "../../utils/validators/validators.js"; /** * Draws text on an image. * @param image - Image to write text on. * @param text - Text to write on the image. * @param options - Out Options * @returns Image with drawn text. */ export function drawText(image, text, options) { if (Array.isArray(text) && text.length === 0) { throw new Error('At least one text element must be provided'); } const newImage = getOutputImage(image, options, { clone: true }); const defaultFont = options?.font ?? '12px Helvetica'; const defaultColor = options?.fontColor ?? [255, 255, 255, 255]; const ctx = getCanvasContext(image.width, image.height); if (!Array.isArray(text)) { drawTextElement(image, ctx, text, defaultFont, defaultColor); } else { for (const label of text) { drawTextElement(image, ctx, label, defaultFont, defaultColor); } } layerCanvas(newImage.getRawImage().data, ctx.getImageData(0, 0, image.width, image.height).data, image.channels, image.bitDepth); return newImage; } function drawTextElement(image, ctx, text, defaultFont, defaultColor) { ctx.font = text.font ?? defaultFont; const fontColor = text.fontColor ?? defaultColor; validateValues(fontColor, image); const alpha = fontColor[3] ? fontColor[3] / 255 : 1; const normalizedColor = [ fontColor[0] ?? 0, fontColor[1] ?? 0, fontColor[2] ?? 0, alpha, ]; ctx.fillStyle = `rgba(${normalizedColor.join(',')})`; ctx.fillText(String(text.content), text.position.column, text.position.row); } /** * Draws labels on the image data from canvas. * @param imageData - Image data to draw text on. * @param canvasData - Canvas data to draw on the image. * @param numberOfChannels - Number of channels of the initial image. * @param bitDepth - Bit depth of the initial image. */ function layerCanvas(imageData, canvasData, numberOfChannels, bitDepth) { const config = CHANNEL_CONFIGS[numberOfChannels] || CHANNEL_CONFIGS[1]; const pixelCount = canvasData.length >>> 2; const bitShift = bitDepth - 8; let imageIndex = 0; let canvasIndex = 0; for (let pixel = 0; pixel < pixelCount; pixel++) { const canvasAlpha = canvasData[canvasIndex + 3] / 255; // Skip transparent canvas pixels completely if (canvasAlpha === 0) { imageIndex += numberOfChannels; canvasIndex += 4; continue; } const invAlpha = 1 - canvasAlpha; for (const channel of config.channelOffsets) { const targetIndex = imageIndex + channel; imageData[targetIndex] = Math.round(canvasData[canvasIndex + channel] * canvasAlpha + imageData[targetIndex] * invAlpha) << bitShift; } if (config.hasAlpha) { const alphaIndex = imageIndex + config.alphaOffset; const imageAlpha = (imageData[alphaIndex] >>> bitShift) / 255; const newAlpha = canvasAlpha + imageAlpha * (1 - canvasAlpha); imageData[alphaIndex] = Math.round(newAlpha * 255) << bitShift; } imageIndex += numberOfChannels; canvasIndex += 4; } } const CHANNEL_CONFIGS = { // GREY 1: { channelOffsets: [0], hasAlpha: false, alphaOffset: undefined, }, // GREYA 2: { channelOffsets: [0], hasAlpha: true, alphaOffset: 1, }, 3: { // RGB channelOffsets: [0, 1, 2], hasAlpha: false, alphaOffset: undefined, }, 4: { // RGBA channelOffsets: [0, 1, 2], hasAlpha: true, alphaOffset: 3, }, }; //# sourceMappingURL=draw_text.js.map