Source: rendering/renderers/shared/extract/GenerateTextureSystem.ts

import { Color, type ColorSource } from '../../../../color/Color';
import { ExtensionType } from '../../../../extensions/Extensions';
import { Matrix } from '../../../../maths/matrix/Matrix';
import { Rectangle } from '../../../../maths/shapes/Rectangle';
import { Bounds } from '../../../../scene/container/bounds/Bounds';
import { getLocalBounds } from '../../../../scene/container/bounds/getLocalBounds';
import { Container } from '../../../../scene/container/Container';
import { RenderTexture } from '../texture/RenderTexture';

import type { Renderer } from '../../types';
import type { System } from '../system/System';
import type { TextureSourceOptions } from '../texture/sources/TextureSource';
import type { Texture } from '../texture/Texture';

export type GenerateTextureSourceOptions = Omit<TextureSourceOptions, 'resource' | 'width' | 'height' | 'resolution'>;

/**
 * Options for generating a texture from a container.
 * @memberof rendering
 */
export type GenerateTextureOptions =
{
    /** The container to generate the texture from */
    target: Container;
    /**
     * The region of the container, that shall be rendered,
     * if no region is specified, defaults to the local bounds of the container.
     */
    frame?: Rectangle;
    /** The resolution of the texture being generated. */
    resolution?: number;
    /** The color used to clear the texture. */
    clearColor?: ColorSource;
    /** Whether to enable anti-aliasing. This may affect performance. */
    antialias?: boolean;
    /** The options passed to the texture source. */
    textureSourceOptions?: GenerateTextureSourceOptions,
};

const tempRect = new Rectangle();
const tempBounds = new Bounds();
const noColor: ColorSource = [0, 0, 0, 0];

/**
 * System that manages the generation of textures from the renderer
 *
 *
 * Do not instantiate these plugins directly. It is available from the `renderer.textureGenerator` property.
 * @memberof rendering
 */
export class GenerateTextureSystem implements System
{
    /** @ignore */
    public static extension = {
        type: [
            ExtensionType.WebGLSystem,
            ExtensionType.WebGPUSystem,
        ],
        name: 'textureGenerator',
    } as const;

    private readonly _renderer: Renderer;

    constructor(renderer: Renderer)
    {
        this._renderer = renderer;
    }

    /**
     * A Useful function that returns a texture of the display object that can then be used to create sprites
     * This can be quite useful if your container is complicated and needs to be reused multiple times.
     * @param {GenerateTextureOptions | Container} options - Generate texture options.
     * @param {Container} [options.container] - If not given, the renderer's resolution is used.
     * @param {Rectangle} options.region - The region of the container, that shall be rendered,
     * @param {number} [options.resolution] - The resolution of the texture being generated.
     *        if no region is specified, defaults to the local bounds of the container.
     * @param {GenerateTextureSourceOptions} [options.textureSourceOptions] - Texture options for GPU.
     * @returns a shiny new texture of the container passed in
     */
    public generateTexture(options: GenerateTextureOptions | Container): Texture
    {
        if (options instanceof Container)
        {
            options = {
                target: options,
                frame: undefined,
                textureSourceOptions: {},
                resolution: undefined,
            };
        }

        const resolution = options.resolution || this._renderer.resolution;
        const antialias = options.antialias || this._renderer.view.antialias;

        const container = options.target;

        let clearColor = options.clearColor;

        if (clearColor)
        {
            const isRGBAArray = Array.isArray(clearColor) && clearColor.length === 4;

            clearColor = isRGBAArray ? clearColor : Color.shared.setValue(clearColor).toArray();
        }
        else
        {
            clearColor = noColor;
        }

        const region = options.frame?.copyTo(tempRect)
            || getLocalBounds(container, tempBounds).rectangle;

        region.width = Math.max(region.width, 1 / resolution) | 0;
        region.height = Math.max(region.height, 1 / resolution) | 0;

        const target = RenderTexture.create({
            ...options.textureSourceOptions,
            width: region.width,
            height: region.height,
            resolution,
            antialias,
        });

        const transform = Matrix.shared.translate(-region.x, -region.y);

        this._renderer.render({
            container,
            transform,
            target,
            clearColor,
        });

        target.source.updateMipmaps();

        return target;
    }

    public destroy(): void
    {
        (this._renderer as null) = null;
    }
}