@nmmty/lazycanvas
Version:
A simple way to interact with @napi-rs/canvas in an advanced way!
190 lines (189 loc) • 8.43 kB
JavaScript
"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;