feat: add view transform, photo loading and download helpers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
immortaly007andClaude Opus 5.5 committed 2026-10-15 14:45:38 +02:00
1 parent 0a97cb88af
commit 99cafc0b55
5 files changed
+102

No files matched your search

+16
View File
@@ -0,0 +1,16 @@
import { describe, expect, it } from 'vitest';
import { fitSize, MAX_SIDE } from '../src/lib/load-image';
describe('fitSize', () => {
it('downscales a 12 MP landscape photo to the max side', () => {
expect(fitSize(4000, 3000, MAX_SIDE)).toEqual({ width: 2000, height: 1500 });
});
it('downscales portrait photos by their height', () => {
expect(fitSize(3024, 4032, 2000)).toEqual({ width: 1500, height: 2000 });
});
it('never upscales small images', () => {
expect(fitSize(800, 600, 2000)).toEqual({ width: 800, height: 600 });
});
});
+32
View File
@@ -0,0 +1,32 @@
import { describe, expect, it } from 'vitest';
import { fitView, screenToImage, zoomAt, type View } from '../src/lib/view';
describe('view transform', () => {
it('maps screen to image coordinates', () => {
const view: View = { scale: 2, offsetX: 10, offsetY: 20 };
expect(screenToImage({ x: 30, y: 60 }, view)).toEqual({ x: 10, y: 20 });
});
it('keeps the point under the cursor fixed while zooming', () => {
const view: View = { scale: 0.5, offsetX: 40, offsetY: -15 };
const cursor = { x: 300, y: 200 };
const before = screenToImage(cursor, view);
const zoomed = zoomAt(view, cursor, 3);
expect(zoomed.scale).toBeCloseTo(1.5);
const after = screenToImage(cursor, zoomed);
expect(after.x).toBeCloseTo(before.x);
expect(after.y).toBeCloseTo(before.y);
});
it('clamps zoom', () => {
expect(zoomAt({ scale: 10, offsetX: 0, offsetY: 0 }, { x: 0, y: 0 }, 100).scale).toBe(20);
expect(zoomAt({ scale: 0.1, offsetX: 0, offsetY: 0 }, { x: 0, y: 0 }, 0.001).scale).toBe(0.05);
});
it('fits and centres the image in the viewport', () => {
const view = fitView(2000, 1000, 1016, 1016, 8);
expect(view.scale).toBeCloseTo(0.5);
expect(view.offsetX).toBeCloseTo(8);
expect(view.offsetY).toBeCloseTo(258);
});
});