const OUTPUT_PIXELS = 64; function positiveNumber(value, field) { const number = Number(value); if (!Number.isFinite(number) || number <= 0) { throw new TypeError(field + " must be a positive number"); } return number; } export function cropGeometry({ viewportWidth, viewportHeight, sourceWidth, sourceHeight, zoom, centerX, centerY, }) { const viewWidth = positiveNumber(viewportWidth, "viewportWidth"); const viewHeight = positiveNumber(viewportHeight, "viewportHeight"); const imageWidth = positiveNumber(sourceWidth, "sourceWidth"); const imageHeight = positiveNumber(sourceHeight, "sourceHeight"); const scaleZoom = positiveNumber(zoom, "zoom"); const x = Number(centerX), y = Number(centerY); if (!Number.isFinite(x) || !Number.isFinite(y)) { throw new TypeError("crop center must be finite"); } const scale = Math.min(viewWidth / imageWidth, viewHeight / imageHeight) * scaleZoom; const width = imageWidth * scale; const height = imageHeight * scale; return { width, height, left: viewWidth / 2 - x * width, top: viewHeight / 2 - y * height, }; } export function cropCenterBounds({ viewportWidth, viewportHeight, sourceWidth, sourceHeight, zoom, }) { const geometry = cropGeometry({ viewportWidth, viewportHeight, sourceWidth, sourceHeight, zoom, centerX: 0.5, centerY: 0.5, }); const visibleX = Math.min(Number(viewportWidth) / OUTPUT_PIXELS, geometry.width); const visibleY = Math.min(Number(viewportHeight) / OUTPUT_PIXELS, geometry.height); return { minX: (visibleX - Number(viewportWidth) / 2) / geometry.width, maxX: 1 + (Number(viewportWidth) / 2 - visibleX) / geometry.width, minY: (visibleY - Number(viewportHeight) / 2) / geometry.height, maxY: 1 + (Number(viewportHeight) / 2 - visibleY) / geometry.height, }; } export function clampCropCenter(value, bounds) { return { centerX: Math.max(bounds.minX, Math.min(bounds.maxX, Number(value.centerX))), centerY: Math.max(bounds.minY, Math.min(bounds.maxY, Number(value.centerY))), }; }