export type PlatformImagePreviewSize = { width: number; height: number; }; export type PlatformImagePreviewTransform = { scale: number; offsetX: number; offsetY: number; }; export const MIN_PLATFORM_IMAGE_PREVIEW_SCALE = 1; export const MAX_PLATFORM_IMAGE_PREVIEW_SCALE = 4; export const PLATFORM_IMAGE_PREVIEW_SCALE_STEP = 0.5; export const DEFAULT_PLATFORM_IMAGE_PREVIEW_TRANSFORM: PlatformImagePreviewTransform = { scale: MIN_PLATFORM_IMAGE_PREVIEW_SCALE, offsetX: 0, offsetY: 0, }; function clampNumber(value: number, min: number, max: number) { if (!Number.isFinite(value)) { return min; } return Math.max(min, Math.min(max, value)); } function normalizePreviewSize(size: PlatformImagePreviewSize | null) { if (!size || size.width <= 0 || size.height <= 0) { return { width: 1, height: 1 }; } return size; } export function resolvePlatformImagePreviewContainedSize( viewportSize: PlatformImagePreviewSize, naturalSize: PlatformImagePreviewSize | null, ) { const viewport = normalizePreviewSize(viewportSize); const natural = normalizePreviewSize(naturalSize); const imageRatio = natural.width / natural.height; const viewportRatio = viewport.width / viewport.height; if (imageRatio > viewportRatio) { return { width: viewport.width, height: viewport.width / imageRatio, }; } return { width: viewport.height * imageRatio, height: viewport.height, }; } export function samePlatformImagePreviewTransform( left: PlatformImagePreviewTransform, right: PlatformImagePreviewTransform, ) { return ( left.scale === right.scale && left.offsetX === right.offsetX && left.offsetY === right.offsetY ); } export function clampPlatformImagePreviewTransform( transform: PlatformImagePreviewTransform, viewportSize: PlatformImagePreviewSize, naturalSize: PlatformImagePreviewSize | null, ): PlatformImagePreviewTransform { const viewport = normalizePreviewSize(viewportSize); const containedSize = resolvePlatformImagePreviewContainedSize( viewport, naturalSize, ); const scale = clampNumber( transform.scale, MIN_PLATFORM_IMAGE_PREVIEW_SCALE, MAX_PLATFORM_IMAGE_PREVIEW_SCALE, ); const scaledWidth = containedSize.width * scale; const scaledHeight = containedSize.height * scale; const panLimitX = Math.max(0, (scaledWidth - viewport.width) / 2); const panLimitY = Math.max(0, (scaledHeight - viewport.height) / 2); return { scale, offsetX: clampNumber(transform.offsetX, -panLimitX, panLimitX), offsetY: clampNumber(transform.offsetY, -panLimitY, panLimitY), }; }