UNPKG

cozy-iiif

Version:

A developer-friendly collection of abstractions and utilities built on top of @iiif/presentation-3 and @iiif/parser

114 lines (85 loc) 3.68 kB
import type { Level0ImageServiceResource } from '../types'; import { fetchImageInfo } from './fetch-image-info'; import { getThrottledLoader } from './throttled-loader'; import type { ImageInfo, Size, Tile } from './types'; const getBestScaleFactor = (info: ImageInfo, minSize?: Partial<Size>): number => { // Sort descending const scaleFactors = info.tiles[0].scaleFactors.sort((a, b) => b - a); if (!minSize) // Just return highest scale factor return scaleFactors[0]; const scaleX = minSize.width ? info.width / minSize.width : Infinity; const scaleY = minSize.height ? info.height / minSize.height : Infinity; const scale = Math.min(scaleX, scaleY); // Find the smallest scale factor that still meets our minimum size requirements for (const factor of scaleFactors) { if (factor <= scale) return factor; } // If no scale factor is small enough, return the smallest available return scaleFactors[scaleFactors.length - 1]; }; const getThumbnailDimensions = (info: ImageInfo, minSize?: Partial<Size>): Size => { const scaleFactor = getBestScaleFactor(info, minSize); let width = Math.ceil(info.width / scaleFactor); let height = Math.ceil(info.height / scaleFactor); if (minSize) { const aspectRatio = info.width / info.height; if (minSize.width && width < minSize.width) { width = minSize.width; height = Math.ceil(width / aspectRatio); } if (minSize.height && height < minSize.height) { height = minSize.height; width = Math.ceil(height * aspectRatio); } } return { width, height }; } const getThumbnailTiles = (info: ImageInfo, minSize?: Partial<Size>): Tile[] => { const scaleFactor = getBestScaleFactor(info, minSize); const tileWidth = info.tiles[0].width; const tileHeight = info.tiles[0].height || info.tiles[0].width; const cols = Math.ceil(info.width / (tileWidth * scaleFactor)); const rows = Math.ceil(info.height / (tileHeight * scaleFactor)); const tiles = []; for (let y = 0; y < rows; y++) { for (let x = 0; x < cols; x++) { const actualWidth = Math.min(tileWidth, (info.width - x * tileWidth * scaleFactor) / scaleFactor); const actualHeight = Math.min(tileHeight, (info.height - y * tileHeight * scaleFactor) / scaleFactor); if (actualWidth <= 0 || actualHeight <= 0) continue; tiles.push({ url: `${info.id}/${x * tileWidth * scaleFactor},${y * tileHeight * scaleFactor},${actualWidth * scaleFactor},${actualHeight * scaleFactor}/${Math.ceil(actualWidth)},/0/default.jpg`, width: Math.ceil(actualWidth), height: Math.ceil(actualHeight), x: x * tileWidth, y: y * tileHeight }); } } return tiles; } export const getThumbnail = async (resource: Level0ImageServiceResource, minSize?: Partial<Size>): Promise<Blob> => { const info = await fetchImageInfo(resource); const tiles = getThumbnailTiles(info, minSize); const dimensions = getThumbnailDimensions(info, minSize); const canvas = document.createElement('canvas'); canvas.width = dimensions.width; canvas.height = dimensions.height; const ctx = canvas.getContext('2d'); if (!ctx) throw new Error('Error creating canvas context'); const loader = getThrottledLoader(); await Promise.all(tiles.map(async (tile) => { const img = await loader.loadImage(tile.url); ctx.drawImage(img, tile.x, tile.y); })); return new Promise((resolve, reject) => { canvas.toBlob(blob => { if (blob) resolve(blob); else reject(new Error('Failed to create blob')); }, 'image/jpeg', 0.85); }); }