mirror of
https://github.com/immich-app/immich.git
synced 2026-01-31 01:04:49 -08:00
Compare commits
1 Commits
chore/no-m
...
fix/create
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ebba759d3 |
@@ -121,6 +121,4 @@ post_install do |installer|
|
||||
end
|
||||
# End of the permission_handler configuration
|
||||
end
|
||||
system("defaults write com.apple.dt.Xcode IDESkipPackagePluginFingerprintValidatation -bool YES")
|
||||
system("defaults write com.apple.dt.Xcode IDESkipMacroFingerprintValidation -bool YES")
|
||||
end
|
||||
|
||||
@@ -300,6 +300,6 @@ SPEC CHECKSUMS:
|
||||
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
||||
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
||||
|
||||
PODFILE CHECKSUM: 938abbae4114b9c2140c550a2a0d8f7c674f5dfe
|
||||
PODFILE CHECKSUM: 7ce312f2beab01395db96f6969d90a447279cf45
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/groue/GRDB.swift",
|
||||
"state" : {
|
||||
"revision" : "aa0079aeb82a4bf00324561a40bffe68c6fe1c26",
|
||||
"version" : "7.9.0"
|
||||
"revision" : "18497b68fdbb3a09528d260a0a0e1e7e61c8c53d",
|
||||
"version" : "7.8.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -24,8 +24,17 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/pointfreeco/sqlite-data",
|
||||
"state" : {
|
||||
"revision" : "05704b563ecb7f0bd7e49b6f360a6383a3e53e7d",
|
||||
"version" : "1.5.1"
|
||||
"revision" : "b66b894b9a5710f1072c8eb6448a7edfc2d743d9",
|
||||
"version" : "1.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-case-paths",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/pointfreeco/swift-case-paths",
|
||||
"state" : {
|
||||
"revision" : "6989976265be3f8d2b5802c722f9ba168e227c71",
|
||||
"version" : "1.7.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -123,8 +132,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/pointfreeco/swift-structured-queries",
|
||||
"state" : {
|
||||
"revision" : "d8163b3a98f3c8434c4361e85126db449d84bc66",
|
||||
"version" : "0.30.0"
|
||||
"revision" : "1447ea20550f6f02c4b48cc80931c3ed40a9c756",
|
||||
"version" : "0.25.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -136,6 +145,15 @@
|
||||
"version" : "602.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-tagged",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/pointfreeco/swift-tagged",
|
||||
"state" : {
|
||||
"revision" : "3907a9438f5b57d317001dc99f3f11b46882272b",
|
||||
"version" : "0.10.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "xctest-dynamic-overlay",
|
||||
"kind" : "remoteSourceControl",
|
||||
|
||||
@@ -1249,10 +1249,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
version: "1.16.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1942,10 +1942,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
||||
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.7"
|
||||
version: "0.7.6"
|
||||
thumbhash:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@@ -44,6 +44,7 @@ import { getDimensions } from 'src/utils/asset.util';
|
||||
import { ImmichFileResponse } from 'src/utils/file';
|
||||
import { mimeTypes } from 'src/utils/mime-types';
|
||||
import { isFacialRecognitionEnabled } from 'src/utils/misc';
|
||||
import { Point, transformPoints } from 'src/utils/transform';
|
||||
|
||||
@Injectable()
|
||||
export class PersonService extends BaseService {
|
||||
@@ -634,15 +635,50 @@ export class PersonService extends BaseService {
|
||||
this.requireAccess({ auth, permission: Permission.PersonRead, ids: [dto.personId] }),
|
||||
]);
|
||||
|
||||
const asset = await this.assetRepository.getById(dto.assetId, { edits: true, exifInfo: true });
|
||||
if (!asset) {
|
||||
throw new NotFoundException('Asset not found');
|
||||
}
|
||||
|
||||
const edits = asset.edits || [];
|
||||
|
||||
let p1: Point = { x: dto.x, y: dto.y };
|
||||
let p2: Point = { x: dto.x + dto.width, y: dto.y + dto.height };
|
||||
|
||||
// the coordinates received from the client are based on the edited preview image
|
||||
// we need to convert them to the coordinate space of the original unedited image
|
||||
if (edits.length > 0) {
|
||||
if (!asset.width || !asset.height || !asset.exifInfo?.exifImageWidth || !asset.exifInfo?.exifImageHeight) {
|
||||
throw new BadRequestException('Asset does not have valid dimensions');
|
||||
}
|
||||
|
||||
// convert from preview to full dimensions
|
||||
const scaleFactor = asset.width / dto.imageWidth;
|
||||
p1 = { x: p1.x * scaleFactor, y: p1.y * scaleFactor };
|
||||
p2 = { x: p2.x * scaleFactor, y: p2.y * scaleFactor };
|
||||
|
||||
const {
|
||||
points: [invertedP1, invertedP2],
|
||||
} = transformPoints([p1, p2], edits, { width: asset.width, height: asset.height }, { inverse: true });
|
||||
|
||||
// make sure p1 is top-left and p2 is bottom-right
|
||||
p1 = { x: Math.min(invertedP1.x, invertedP2.x), y: Math.min(invertedP1.y, invertedP2.y) };
|
||||
p2 = { x: Math.max(invertedP1.x, invertedP2.x), y: Math.max(invertedP1.y, invertedP2.y) };
|
||||
|
||||
// now coordinates are in original image space
|
||||
dto.imageHeight = asset.exifInfo.exifImageHeight;
|
||||
dto.imageWidth = asset.exifInfo.exifImageWidth;
|
||||
}
|
||||
|
||||
await this.personRepository.createAssetFace({
|
||||
personId: dto.personId,
|
||||
assetId: dto.assetId,
|
||||
imageHeight: dto.imageHeight,
|
||||
imageWidth: dto.imageWidth,
|
||||
boundingBoxX1: dto.x,
|
||||
boundingBoxX2: dto.x + dto.width,
|
||||
boundingBoxY1: dto.y,
|
||||
boundingBoxY2: dto.y + dto.height,
|
||||
boundingBoxX1: Math.round(p1.x),
|
||||
boundingBoxX2: Math.round(p2.x),
|
||||
boundingBoxY1: Math.round(p1.y),
|
||||
boundingBoxY2: Math.round(p2.y),
|
||||
sourceType: SourceType.Manual,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ export const createAffineMatrix = (
|
||||
);
|
||||
};
|
||||
|
||||
type Point = { x: number; y: number };
|
||||
export type Point = { x: number; y: number };
|
||||
|
||||
type TransformState = {
|
||||
points: Point[];
|
||||
@@ -73,29 +73,33 @@ type TransformState = {
|
||||
* Transforms an array of points through a series of edit operations (crop, rotate, mirror).
|
||||
* Points should be in absolute pixel coordinates relative to the starting dimensions.
|
||||
*/
|
||||
const transformPoints = (
|
||||
export const transformPoints = (
|
||||
points: Point[],
|
||||
edits: AssetEditActionItem[],
|
||||
startingDimensions: ImageDimensions,
|
||||
{ inverse = false } = {},
|
||||
): TransformState => {
|
||||
let currentWidth = startingDimensions.width;
|
||||
let currentHeight = startingDimensions.height;
|
||||
let transformedPoints = [...points];
|
||||
|
||||
// Handle crop first
|
||||
const crop = edits.find((edit) => edit.action === 'crop');
|
||||
if (crop) {
|
||||
const { x: cropX, y: cropY, width: cropWidth, height: cropHeight } = crop.parameters;
|
||||
transformedPoints = transformedPoints.map((p) => ({
|
||||
x: p.x - cropX,
|
||||
y: p.y - cropY,
|
||||
}));
|
||||
currentWidth = cropWidth;
|
||||
currentHeight = cropHeight;
|
||||
// Handle crop first if not inverting
|
||||
if (!inverse) {
|
||||
const crop = edits.find((edit) => edit.action === 'crop');
|
||||
if (crop) {
|
||||
const { x: cropX, y: cropY, width: cropWidth, height: cropHeight } = crop.parameters;
|
||||
transformedPoints = transformedPoints.map((p) => ({
|
||||
x: p.x - cropX,
|
||||
y: p.y - cropY,
|
||||
}));
|
||||
currentWidth = cropWidth;
|
||||
currentHeight = cropHeight;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply rotate and mirror transforms
|
||||
for (const edit of edits) {
|
||||
const editSequence = inverse ? edits.toReversed() : edits;
|
||||
for (const edit of editSequence) {
|
||||
let matrix: Matrix = identity();
|
||||
if (edit.action === 'rotate') {
|
||||
const angleDegrees = edit.parameters.angle;
|
||||
@@ -105,7 +109,7 @@ const transformPoints = (
|
||||
|
||||
matrix = compose(
|
||||
translate(newWidth / 2, newHeight / 2),
|
||||
rotate(angleRadians),
|
||||
rotate(inverse ? -angleRadians : angleRadians),
|
||||
translate(-currentWidth / 2, -currentHeight / 2),
|
||||
);
|
||||
|
||||
@@ -125,6 +129,18 @@ const transformPoints = (
|
||||
transformedPoints = transformedPoints.map((p) => applyToPoint(matrix, p));
|
||||
}
|
||||
|
||||
// Handle crop last if inverting
|
||||
if (inverse) {
|
||||
const crop = edits.find((edit) => edit.action === 'crop');
|
||||
if (crop) {
|
||||
const { x: cropX, y: cropY } = crop.parameters;
|
||||
transformedPoints = transformedPoints.map((p) => ({
|
||||
x: p.x + cropX,
|
||||
y: p.y + cropY,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
points: transformedPoints,
|
||||
currentWidth,
|
||||
|
||||
Reference in New Issue
Block a user