5180 fichiers 38.5 Mo 0 téléchargements 246 vues mis à jour il y a 5 heures ui
nitro-v36-with-renderer-v36 / src / api / avatar / avatarThumbnailUrls.ts
avatarThumbnailUrls.ts 5.5 Ko Télécharger le fichier
export interface ThumbnailUrlEntry {
    url: string;
    bytes: number;
    revoke: () => void;
}

export const THUMBNAIL_REVOKE_GRACE_MS = 30_000;

const canUseObjectUrls = (): boolean =>
    typeof URL !== 'undefined' && typeof URL.createObjectURL === 'function' && typeof URL.revokeObjectURL === 'function';

const dataUrlEntry = (url: string): ThumbnailUrlEntry => ({ url, bytes: url.length * 2, revoke: () => undefined });

const blobEntry = (blob: Blob): ThumbnailUrlEntry => {
    const url = URL.createObjectURL(blob);

    return { url, bytes: blob.size, revoke: () => URL.revokeObjectURL(url) };
};

export const canvasToThumbnailUrl = async (canvas: HTMLCanvasElement): Promise<ThumbnailUrlEntry | null> => {
    if (!canvas || !canvas.width || !canvas.height) return null;

    if (canUseObjectUrls() && typeof canvas.toBlob === 'function') {
        const blob = await new Promise<Blob | null>((resolve) => {
            try {
                canvas.toBlob((result) => resolve(result), 'image/png');
            } catch {
                resolve(null);
            }
        });

        if (blob) return blobEntry(blob);
    }

    try {
        return dataUrlEntry(canvas.toDataURL('image/png'));
    } catch {
        return null;
    }
};

export const imageUrlToCanvas = async (imageUrl: string): Promise<HTMLCanvasElement | null> => {
    if (!imageUrl) return null;

    const image = new Image();

    try {
        await new Promise<void>((resolve, reject) => {
            image.onload = () => resolve();
            image.onerror = () => reject(new Error('thumbnail load failed'));
            image.src = imageUrl;
        });
    } catch {
        return null;
    }

    const width = image.naturalWidth || image.width;
    const height = image.naturalHeight || image.height;

    if (!width || !height) return null;

    const canvas = document.createElement('canvas');

    canvas.width = width;
    canvas.height = height;

    const context = canvas.getContext('2d');

    if (!context) return null;

    context.drawImage(image, 0, 0);

    return canvas;
};

export const trimCanvasToOpaqueBounds = (canvas: HTMLCanvasElement): HTMLCanvasElement => {
    const width = canvas?.width ?? 0;
    const height = canvas?.height ?? 0;

    if (!width || !height) return canvas;

    const context = canvas.getContext('2d', { willReadFrequently: true });

    if (!context) return canvas;

    let data: Uint8ClampedArray;

    try {
        data = context.getImageData(0, 0, width, height).data;
    } catch {
        return canvas;
    }

    let minX = width;
    let minY = height;
    let maxX = -1;
    let maxY = -1;

    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            if (data[(y * width + x) * 4 + 3] > 0) {
                if (x < minX) minX = x;
                if (x > maxX) maxX = x;
                if (y < minY) minY = y;
                if (y > maxY) maxY = y;
            }
        }
    }

    if (maxX < 0) return canvas;

    const trimmedWidth = maxX - minX + 1;
    const trimmedHeight = maxY - minY + 1;

    if (trimmedWidth === width && trimmedHeight === height) return canvas;

    const trimmed = document.createElement('canvas');

    trimmed.width = trimmedWidth;
    trimmed.height = trimmedHeight;

    const trimmedContext = trimmed.getContext('2d');

    if (!trimmedContext) return canvas;

    trimmedContext.drawImage(canvas, minX, minY, trimmedWidth, trimmedHeight, 0, 0, trimmedWidth, trimmedHeight);

    return trimmed;
};

export const centerCanvasIntoBox = (canvas: HTMLCanvasElement, box: number): HTMLCanvasElement => {
    const width = canvas?.width ?? 0;
    const height = canvas?.height ?? 0;

    if (!width || !height) return canvas;
    if (width === box && height === box) return canvas;

    const centered = document.createElement('canvas');

    centered.width = box;
    centered.height = box;

    const context = centered.getContext('2d');

    if (!context) return canvas;

    context.imageSmoothingEnabled = false;
    context.drawImage(canvas, Math.trunc((box - width) / 2), Math.trunc((box - height) / 2));

    return centered;
};

export class ThumbnailUrlCache {
    private _entries: Map<string, ThumbnailUrlEntry> = new Map();
    private _bytes: number = 0;

    constructor(private readonly _maxBytes: number) {}

    public get size(): number {
        return this._entries.size;
    }

    public get bytes(): number {
        return this._bytes;
    }

    public get(key: string): string | undefined {
        const entry = this._entries.get(key);

        if (!entry) return undefined;

        this._entries.delete(key);
        this._entries.set(key, entry);

        return entry.url;
    }

    public set(key: string, entry: ThumbnailUrlEntry): void {
        if (!entry) return;

        this.drop(key);

        while (this._bytes + entry.bytes > this._maxBytes && this._entries.size > 0) {
            const oldest = this._entries.keys().next().value as string;

            this.drop(oldest);
        }

        this._entries.set(key, entry);
        this._bytes += entry.bytes;
    }

    public clear(): void {
        for (const key of [...this._entries.keys()]) this.drop(key);

        this._bytes = 0;
    }

    private drop(key: string): void {
        const entry = this._entries.get(key);

        if (!entry) return;

        this._entries.delete(key);
        this._bytes -= entry.bytes;

        if (typeof setTimeout === 'function') setTimeout(entry.revoke, THUMBNAIL_REVOKE_GRACE_MS);
        else entry.revoke();
    }
}
15Sujets
1Dépôts
20Membres
2En ligne
Profile
Inscrit
Dernière visite
Messages
0
Votes positifs
0
Votes négatifs
0

Nous utilisons des cookies pour assurer le bon fonctionnement du site et améliorer votre expérience. en savoir plus

Autoriser