mirror of
https://github.com/immich-app/immich.git
synced 2026-01-25 02:44:46 -08:00
fix(web): edit order handling
This commit is contained in:
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -842,6 +842,9 @@ importers:
|
||||
thumbhash:
|
||||
specifier: ^0.1.1
|
||||
version: 0.1.1
|
||||
transformation-matrix:
|
||||
specifier: ^3.1.0
|
||||
version: 3.1.0
|
||||
uplot:
|
||||
specifier: ^1.6.32
|
||||
version: 1.6.32
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
"svelte-persisted-store": "^0.12.0",
|
||||
"tabbable": "^6.2.0",
|
||||
"thumbhash": "^0.1.1",
|
||||
"transformation-matrix": "^3.1.0",
|
||||
"uplot": "^1.6.32"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
import { editManager, type EditActions, type EditToolManager } from '$lib/managers/edit/edit-manager.svelte';
|
||||
import { getAssetMediaUrl } from '$lib/utils';
|
||||
import { getDimensions } from '$lib/utils/asset-utils';
|
||||
import { normalizeTransformEdits } from '$lib/utils/editor';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import {
|
||||
AssetEditAction,
|
||||
AssetMediaSize,
|
||||
MirrorAxis,
|
||||
type AssetResponseDto,
|
||||
type CropParameters,
|
||||
type MirrorParameters,
|
||||
type RotateParameters,
|
||||
} from '@immich/sdk';
|
||||
import { AssetEditAction, AssetMediaSize, MirrorAxis, type AssetResponseDto, type CropParameters } from '@immich/sdk';
|
||||
import { tick } from 'svelte';
|
||||
|
||||
export type CropAspectRatio =
|
||||
@@ -200,22 +193,14 @@ class TransformManager implements EditToolManager {
|
||||
|
||||
globalThis.addEventListener('mousemove', (e) => transformManager.handleMouseMove(e), { passive: true });
|
||||
|
||||
// set the rotation before loading the image
|
||||
const rotateEdit = edits.find((e) => e.action === 'rotate');
|
||||
if (rotateEdit) {
|
||||
this.imageRotation = (rotateEdit.parameters as RotateParameters).angle;
|
||||
}
|
||||
const transformEdits = edits.filter((e) => e.action === 'rotate' || e.action === 'mirror');
|
||||
|
||||
// set mirror state from edits
|
||||
const mirrorEdits = edits.filter((e) => e.action === 'mirror');
|
||||
for (const mirrorEdit of mirrorEdits) {
|
||||
const axis = (mirrorEdit.parameters as MirrorParameters).axis;
|
||||
if (axis === MirrorAxis.Horizontal) {
|
||||
this.mirrorHorizontal = true;
|
||||
} else if (axis === MirrorAxis.Vertical) {
|
||||
this.mirrorVertical = true;
|
||||
}
|
||||
}
|
||||
// Normalize rotation and mirror edits to single rotation and mirror state
|
||||
// This allows edits to be imported in any order and still produce correct state
|
||||
const normalizedTransfromation = normalizeTransformEdits(transformEdits);
|
||||
this.imageRotation = normalizedTransfromation.rotation;
|
||||
this.mirrorHorizontal = normalizedTransfromation.mirrorHorizontal;
|
||||
this.mirrorVertical = normalizedTransfromation.mirrorVertical;
|
||||
|
||||
await tick();
|
||||
|
||||
|
||||
59
web/src/lib/utils/editor.ts
Normal file
59
web/src/lib/utils/editor.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import type { EditActions } from '$lib/managers/edit/edit-manager.svelte';
|
||||
import type { MirrorParameters, RotateParameters } from '@immich/sdk';
|
||||
import { compose, flipX, flipY, identity, rotate } from 'transformation-matrix';
|
||||
|
||||
export function normalizeTransformEdits(edits: EditActions): {
|
||||
rotation: number;
|
||||
mirrorHorizontal: boolean;
|
||||
mirrorVertical: boolean;
|
||||
} {
|
||||
// construct an affine matrix from the edits
|
||||
// this is the same approach used in the backend to combine multiple transforms
|
||||
const matrix = compose(
|
||||
...edits.map((edit) => {
|
||||
switch (edit.action) {
|
||||
case 'rotate': {
|
||||
const parameters = edit.parameters as RotateParameters;
|
||||
const angleInRadians = (-parameters.angle * Math.PI) / 180;
|
||||
return rotate(angleInRadians);
|
||||
}
|
||||
case 'mirror': {
|
||||
const parameters = edit.parameters as MirrorParameters;
|
||||
return parameters.axis === 'horizontal' ? flipY() : flipX();
|
||||
}
|
||||
default: {
|
||||
return identity();
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let rotation = 0;
|
||||
let mirrorH = false;
|
||||
let mirrorV = false;
|
||||
|
||||
let { a, b, c, d } = matrix;
|
||||
// round to avoid floating point precision issues
|
||||
a = Math.round(a);
|
||||
b = Math.round(b);
|
||||
c = Math.round(c);
|
||||
d = Math.round(d);
|
||||
|
||||
// [ +/-1, 0, 0, +/-1 ] indicates a 0° or 180° rotation with possible mirrors
|
||||
// [ 0, +/-1, +/-1, 0 ] indicates a 90° or 270° rotation with possible mirrors
|
||||
if (Math.abs(a) == 1 && Math.abs(b) == 0 && Math.abs(c) == 0 && Math.abs(d) == 1) {
|
||||
rotation = a > 0 ? 0 : 180;
|
||||
mirrorH = rotation === 0 ? a < 0 : a > 0;
|
||||
mirrorV = rotation === 0 ? d < 0 : d > 0;
|
||||
} else if (Math.abs(a) == 0 && Math.abs(b) == 1 && Math.abs(c) == 1 && Math.abs(d) == 0) {
|
||||
rotation = c > 0 ? 90 : 270;
|
||||
mirrorH = rotation === 90 ? c < 0 : c > 0;
|
||||
mirrorV = rotation === 90 ? b > 0 : b < 0;
|
||||
}
|
||||
|
||||
return {
|
||||
rotation,
|
||||
mirrorHorizontal: mirrorH,
|
||||
mirrorVertical: mirrorV,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user