UNPKG

@nmmty/lazycanvas

Version:

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

190 lines (189 loc) 8.43 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RenderManager = void 0; const types_1 = require("../../types"); const components_1 = require("../components"); 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 opts {Object} - Optional settings for the RenderManager. * @param opts.debug {boolean} - 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 ctx {SKRSContext2D} - The canvas rendering context. * @param imageDataList {ImageData[]} - The list of ImageData objects to merge. * @param width {number} - The width of the resulting ImageData. * @param height {number} - 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 layer {AnyLayer | Group} - 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) { if (layer instanceof components_1.Group) { for (const subLayer of layer.layers) { if (subLayer.visible) { if ('globalComposite' in subLayer.props && subLayer.props.globalComposite) { this.lazyCanvas.ctx.globalCompositeOperation = subLayer.props.globalComposite; } else { this.lazyCanvas.ctx.globalCompositeOperation = 'source-over'; } await subLayer.draw(this.lazyCanvas.ctx, this.lazyCanvas.canvas, this.lazyCanvas.manager.layers, this.debug); } } } else { if ('globalComposite' in layer.props && layer.props.globalComposite) { this.lazyCanvas.ctx.globalCompositeOperation = layer.props.globalComposite; } else { this.lazyCanvas.ctx.globalCompositeOperation = '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 exportType {AnyExport} - 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; for (const layer of this.lazyCanvas.manager.layers.toArray()) { 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); } encoder.finish(); return encoder.bytesView(); } /** * Renders all layers and exports the result in the specified format. * @param format {AnyExport} - 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) { switch (format) { case types_1.Export.BUFFER: case "buffer": if (this.lazyCanvas.options.animated) { return await this.renderAnimation(); } else { return await this.renderStatic(types_1.Export.BUFFER); } case types_1.Export.CTX: case "ctx": return this.lazyCanvas.ctx; case types_1.Export.SVG: case "svg": return this.renderStatic(types_1.Export.SVG); case types_1.Export.CANVAS: case "canvas": await this.renderStatic(this.lazyCanvas.options.exportType === 'svg' ? types_1.Export.SVG : types_1.Export.BUFFER); return this.lazyCanvas.canvas; default: return this.renderStatic(types_1.Export.BUFFER); } } } exports.RenderManager = RenderManager;