Source: packages/core/src/textures/resources/ImageBitmapResource.ts

import { ALPHA_MODES } from '@pixi/constants';
import { settings } from '@pixi/settings';
import { BaseImageResource } from './BaseImageResource';

import type { ICanvas } from '@pixi/settings';
import type { Renderer } from '../../Renderer';
import type { BaseTexture } from '../BaseTexture';
import type { GLTexture } from '../GLTexture';

export interface IImageBitmapResourceOptions
{
    /** Start loading process automatically when constructed. */
    autoLoad?: boolean;

    /** Load image using cross origin. */
    crossOrigin?: boolean;

    /** Alpha mode used when creating the ImageBitmap. */
    alphaMode?: ALPHA_MODES;
}

/**
 * Resource type for ImageBitmap.
 * @memberof PIXI
 */
export class ImageBitmapResource extends BaseImageResource
{
    /** URL of the image source. */
    url: string | null;

    /**
     * Load image using cross origin.
     * @default false
     */
    crossOrigin: boolean;

    /**
     * Controls texture alphaMode field
     * Copies from options
     * Default is `null`, copies option from baseTexture
     * @readonly
     */
    alphaMode: ALPHA_MODES | null;

    /**
     * Promise when loading.
     * @default null
     */
    private _load: Promise<ImageBitmapResource>;

    /**
     * @param source - ImageBitmap or URL to use
     * @param options
     * @param {boolean} [options.autoLoad=true] - Start loading process automatically when constructed.
     * @param {boolean} [options.crossOrigin=true] - Load image using cross origin.
     * @param {PIXI.ALPHA_MODES} [options.alphaMode=null] - Alpha mode used when creating the ImageBitmap.
     */
    constructor(source: ImageBitmap | string, options?: IImageBitmapResourceOptions)
    {
        options = options || {};

        let baseSource;
        let url;

        if (typeof source === 'string')
        {
            baseSource = ImageBitmapResource.EMPTY;
            url = source;
        }
        else
        {
            baseSource = source;
            url = null;
        }
        // Using super() in if() can cause transpilation problems in some cases, so take it out of if().
        // See https://github.com/pixijs/pixijs/pull/9093 for details.
        super(baseSource);
        this.url = url;

        this.crossOrigin = options.crossOrigin ?? true;
        this.alphaMode = typeof options.alphaMode === 'number' ? options.alphaMode : null;

        this._load = null;

        if (options.autoLoad !== false)
        {
            this.load();
        }
    }

    load(): Promise<ImageBitmapResource>
    {
        if (this._load)
        {
            return this._load;
        }

        this._load = new Promise(async (resolve, reject) =>
        {
            if (this.url === null)
            {
                resolve(this);

                return;
            }

            try
            {
                const response = await settings.ADAPTER.fetch(this.url, {
                    mode: this.crossOrigin ? 'cors' : 'no-cors'
                });

                if (this.destroyed) return;

                const imageBlob = await response.blob();

                if (this.destroyed) return;

                const imageBitmap = await createImageBitmap(imageBlob, {
                    premultiplyAlpha: this.alphaMode === null || this.alphaMode === ALPHA_MODES.UNPACK
                        ? 'premultiply' : 'none',
                });

                if (this.destroyed) return;

                this.source = imageBitmap;
                this.update();

                resolve(this);
            }
            catch (e)
            {
                if (this.destroyed) return;

                reject(e);
                this.onError.emit(e);
            }
        });

        return this._load;
    }

    /**
     * Upload the image bitmap resource to GPU.
     * @param renderer - Renderer to upload to
     * @param baseTexture - BaseTexture for this resource
     * @param glTexture - GLTexture to use
     * @returns {boolean} true is success
     */
    override upload(renderer: Renderer, baseTexture: BaseTexture, glTexture: GLTexture): boolean
    {
        if (!(this.source instanceof ImageBitmap))
        {
            this.load();

            return false;
        }

        if (typeof this.alphaMode === 'number')
        {
            baseTexture.alphaMode = this.alphaMode;
        }

        return super.upload(renderer, baseTexture, glTexture);
    }

    /** Destroys this resource. */
    override dispose(): void
    {
        if (this.source instanceof ImageBitmap)
        {
            this.source.close();
        }

        super.dispose();

        this._load = null;
    }

    /**
     * Used to auto-detect the type of resource.
     * @param {*} source - The source object
     * @returns {boolean} `true` if current environment support ImageBitmap, and source is string or ImageBitmap
     */
    static override test(source: unknown): source is string | ImageBitmap
    {
        return !!globalThis.createImageBitmap && typeof ImageBitmap !== 'undefined'
            && (typeof source === 'string' || source instanceof ImageBitmap);
    }

    /**
     * Cached empty placeholder canvas.
     * @see EMPTY
     */
    private static _EMPTY: ICanvas;

    /**
     * ImageBitmap cannot be created synchronously, so a empty placeholder canvas is needed when loading from URLs.
     * Only for internal usage.
     * @returns The cached placeholder canvas.
     */
    private static get EMPTY(): ICanvas
    {
        ImageBitmapResource._EMPTY = ImageBitmapResource._EMPTY ?? settings.ADAPTER.createCanvas(0, 0);

        return ImageBitmapResource._EMPTY;
    }
}