UNPKG

@nmmty/lazycanvas

Version:

A simple way to interact with @napi-rs/canvas in an advanced way!

184 lines (183 loc) 8.03 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RenderManager = void 0; const types_1 = require("../../types"); const LazyUtil_1 = require("../../utils/LazyUtil"); // @ts-ignore const gifenc_1 = require("gifenc"); /** * Class responsible for managing rendering operations, including static and animated exports. */ class RenderManager { /** * The LazyCanvas instance used for rendering. */ lazyCanvas; /** * Whether debugging is enabled. */ debug; /** * Constructs a new RenderManager instance. * @param {LazyCanvas} [lazyCanvas] - The LazyCanvas instance to use for rendering. * @param {Object} [opts] - Optional settings for the RenderManager. * @param {boolean} [opts.debug] - Whether debugging is enabled. */ constructor(lazyCanvas, opts) { this.lazyCanvas = lazyCanvas; this.debug = opts?.debug || false; } /** * Merges multiple ImageData objects into a single ImageData object. * @param {SKRSContext2D} [ctx] - The canvas rendering context. * @param {ImageData[]} [imageDataList] - The list of ImageData objects to merge. * @param {number} [width] - The width of the resulting ImageData. * @param {number} [height] - The height of the resulting ImageData. * @returns {ImageData} The merged ImageData object. */ mergeImageData(ctx, imageDataList, width, height) { const mergedData = ctx.createImageData(width, height); const mergedPixels = mergedData.data; for (const imageData of imageDataList) { const pixels = imageData.data; for (let i = 0; i < pixels.length; i += 4) { const r = pixels[i]; const g = pixels[i + 1]; const b = pixels[i + 2]; const a = pixels[i + 3] / 255; const existingAlpha = mergedPixels[i + 3] / 255; const newAlpha = a + existingAlpha * (1 - a); if (newAlpha > 0) { mergedPixels[i] = (r * a + mergedPixels[i] * existingAlpha * (1 - a)) / newAlpha; mergedPixels[i + 1] = (g * a + mergedPixels[i + 1] * existingAlpha * (1 - a)) / newAlpha; mergedPixels[i + 2] = (b * a + mergedPixels[i + 2] * existingAlpha * (1 - a)) / newAlpha; mergedPixels[i + 3] = newAlpha * 255; } } } return mergedData; } /** * Renders a single layer or group of layers. * @param {AnyLayer | Group} [layer] - The layer or group to render. * @returns {Promise<SKRSContext2D>} The canvas rendering context after rendering. */ async renderLayer(layer) { if (this.debug) LazyUtil_1.LazyLog.log('info', `Rendering ${layer.id}...\nData:`, layer.toJSON()); if (layer.visible) { this.lazyCanvas.ctx.globalCompositeOperation = layer.props?.globalComposite || 'source-over'; await layer.draw(this.lazyCanvas.ctx, this.lazyCanvas.canvas, this.lazyCanvas.manager.layers, this.debug); this.lazyCanvas.ctx.shadowColor = 'transparent'; } return this.lazyCanvas.ctx; } /** * Renders all layers statically and exports the result in the specified format. * @param {AnyExport} [exportType] - The export format (e.g., buffer, SVG, or context). * @returns {Promise<Buffer | SKRSContext2D | string>} The rendered output in the specified format. */ async renderStatic(exportType) { if (this.debug) LazyUtil_1.LazyLog.log('info', `Rendering static...`); for (const layer of this.lazyCanvas.manager.layers.toArray()) { await this.renderLayer(layer); } switch (exportType) { case types_1.Export.BUFFER: case "buffer": case types_1.Export.SVG: case "svg": if ('getContent' in this.lazyCanvas.canvas) { return this.lazyCanvas.canvas.getContent().toString('utf8'); } return this.lazyCanvas.canvas.toBuffer('image/png'); case types_1.Export.CTX: case "ctx": return this.lazyCanvas.ctx; default: if ('getContent' in this.lazyCanvas.canvas) { return this.lazyCanvas.canvas.getContent().toString('utf8'); } return this.lazyCanvas.canvas.toBuffer('image/png'); } } /** * Renders an animated sequence of layers and exports it as a GIF. * @returns {Promise<Buffer>} The rendered animation as a Buffer. */ async renderAnimation() { const encoder = new gifenc_1.GIFEncoder(); if (this.debug) LazyUtil_1.LazyLog.log('info', `Rendering animation...\nData:`, this.lazyCanvas.manager.animation.options); const frameBuffer = []; const { width, height } = this.lazyCanvas.options; const delay = 1000 / this.lazyCanvas.manager.animation.options.frameRate; const { loop, colorSpace, maxColors, transparency, utils } = this.lazyCanvas.manager.animation.options; let frameNumber = 0; for (const layer of this.lazyCanvas.manager.layers.toArray()) { // onAnimationFrame hook this.lazyCanvas.manager.plugins.executeHook('onAnimationFrame', this.lazyCanvas, frameNumber); const ctx = await this.renderLayer(layer); frameBuffer.push(ctx.getImageData(0, 0, width, height)); if (frameBuffer.length > utils.buffer.size) { frameBuffer.shift(); } const mergeData = this.mergeImageData(ctx, frameBuffer, width, height); const palette = (0, gifenc_1.quantize)(mergeData.data, maxColors, { format: colorSpace }); const index = (0, gifenc_1.applyPalette)(mergeData.data, palette, colorSpace); encoder.writeFrame(index, width, height, { palette, transparent: transparency, delay, loop }); if (utils.clear) ctx.clearRect(0, 0, width, height); frameNumber++; } encoder.finish(); return encoder.bytesView(); } /** * Renders all layers and exports the result in the specified format. * @param {AnyExport} [format] - The export format (e.g., buffer, context, SVG, or canvas). * @returns {Promise<Buffer | SKRSContext2D | Canvas | SvgCanvas | string>} The rendered output in the specified format. */ async render(format) { let result; // beforeRender hook this.lazyCanvas.manager.plugins.executeHook('beforeRender', this.lazyCanvas); switch (format) { case types_1.Export.BUFFER: case "buffer": if (this.lazyCanvas.options.animated) { result = await this.renderAnimation(); } else { result = await this.renderStatic(types_1.Export.BUFFER); } break; case types_1.Export.CTX: case "ctx": result = this.lazyCanvas.ctx; break; case types_1.Export.SVG: case "svg": result = await this.renderStatic(types_1.Export.SVG); break; case types_1.Export.CANVAS: case "canvas": await this.renderStatic(this.lazyCanvas.options.exportType === 'svg' ? types_1.Export.SVG : types_1.Export.BUFFER); result = this.lazyCanvas.canvas; break; default: result = await this.renderStatic(types_1.Export.BUFFER); break; } // afterRender hook this.lazyCanvas.manager.plugins.executeHook('afterRender', this.lazyCanvas); return result; } } exports.RenderManager = RenderManager;