From e77e87b936786a6221eb2df2ceda1b245ea2f5dc Mon Sep 17 00:00:00 2001
From: Mert <101130780+mertalev@users.noreply.github.com>
Date: Sun, 16 Jun 2024 11:45:58 -0400
Subject: [PATCH] fix(server): orientation handling for person thumbnails
 (#10382)

fix orientation handling
---
 server/src/interfaces/media.interface.ts   |  4 +++
 server/src/services/person.service.spec.ts |  6 ++---
 server/src/services/person.service.ts      | 30 ++++++----------------
 server/test/fixtures/face.stub.ts          |  4 +--
 4 files changed, 17 insertions(+), 27 deletions(-)

diff --git a/server/src/interfaces/media.interface.ts b/server/src/interfaces/media.interface.ts
index 11ee525f8a..fdf70865ef 100644
--- a/server/src/interfaces/media.interface.ts
+++ b/server/src/interfaces/media.interface.ts
@@ -47,6 +47,10 @@ export interface ImageDimensions {
   height: number;
 }
 
+export interface InputDimensions extends ImageDimensions {
+  inputPath: string;
+}
+
 export interface VideoInfo {
   format: VideoFormat;
   videoStreams: VideoStreamInfo[];
diff --git a/server/src/services/person.service.spec.ts b/server/src/services/person.service.spec.ts
index bb76bc38a3..bf6fc8207e 100644
--- a/server/src/services/person.service.spec.ts
+++ b/server/src/services/person.service.spec.ts
@@ -917,9 +917,9 @@ describe(PersonService.name, () => {
           colorspace: Colorspace.P3,
           crop: {
             left: 0,
-            top: 428,
-            width: 1102,
-            height: 1102,
+            top: 85,
+            width: 510,
+            height: 510,
           },
         },
       );
diff --git a/server/src/services/person.service.ts b/server/src/services/person.service.ts
index e3e78b48f2..57940f3113 100644
--- a/server/src/services/person.service.ts
+++ b/server/src/services/person.service.ts
@@ -41,13 +41,12 @@ import {
 } from 'src/interfaces/job.interface';
 import { ILoggerRepository } from 'src/interfaces/logger.interface';
 import { BoundingBox, IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
-import { CropOptions, IMediaRepository, ImageDimensions } from 'src/interfaces/media.interface';
+import { CropOptions, IMediaRepository, ImageDimensions, InputDimensions } from 'src/interfaces/media.interface';
 import { IMoveRepository } from 'src/interfaces/move.interface';
 import { IPersonRepository, UpdateFacesData } from 'src/interfaces/person.interface';
 import { ISearchRepository } from 'src/interfaces/search.interface';
 import { IStorageRepository } from 'src/interfaces/storage.interface';
 import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
-import { Orientation } from 'src/services/metadata.service';
 import { CacheControl, ImmichFileResponse } from 'src/utils/file';
 import { mimeTypes } from 'src/utils/mime-types';
 import { isFacialRecognitionEnabled } from 'src/utils/misc';
@@ -520,7 +519,7 @@ export class PersonService {
       return JobStatus.FAILED;
     }
 
-    const { width, height, inputPath } = await this.getInputDimensions(asset);
+    const { width, height, inputPath } = await this.getInputDimensions(asset, { width: oldWidth, height: oldHeight });
 
     const thumbnailPath = StorageCore.getPersonThumbnailPath(person);
     this.storageCore.ensureFolders(thumbnailPath);
@@ -601,7 +600,7 @@ export class PersonService {
     return person;
   }
 
-  private async getInputDimensions(asset: AssetEntity): Promise<ImageDimensions & { inputPath: string }> {
+  private async getInputDimensions(asset: AssetEntity, oldDims: ImageDimensions): Promise<InputDimensions> {
     if (!asset.exifInfo?.exifImageHeight || !asset.exifInfo.exifImageWidth) {
       throw new Error(`Asset ${asset.id} dimensions are unknown`);
     }
@@ -611,10 +610,11 @@ export class PersonService {
     }
 
     if (asset.type === AssetType.IMAGE) {
-      const { width, height } = this.withOrientation(asset.exifInfo.orientation as Orientation, {
-        width: asset.exifInfo.exifImageWidth,
-        height: asset.exifInfo.exifImageHeight,
-      });
+      let { exifImageWidth: width, exifImageHeight: height } = asset.exifInfo;
+      if (oldDims.height > oldDims.width !== height > width) {
+        [width, height] = [height, width];
+      }
+
       return { width, height, inputPath: asset.originalPath };
     }
 
@@ -622,20 +622,6 @@ export class PersonService {
     return { width, height, inputPath: asset.previewPath };
   }
 
-  private withOrientation(orientation: Orientation, { width, height }: ImageDimensions): ImageDimensions {
-    switch (orientation) {
-      case Orientation.MirrorHorizontalRotate270CW:
-      case Orientation.Rotate90CW:
-      case Orientation.MirrorHorizontalRotate90CW:
-      case Orientation.Rotate270CW: {
-        return { width: height, height: width };
-      }
-      default: {
-        return { width, height };
-      }
-    }
-  }
-
   private getCrop(dims: { old: ImageDimensions; new: ImageDimensions }, { x1, y1, x2, y2 }: BoundingBox): CropOptions {
     const widthScale = dims.new.width / dims.old.width;
     const heightScale = dims.new.height / dims.old.height;
diff --git a/server/test/fixtures/face.stub.ts b/server/test/fixtures/face.stub.ts
index 2d2acec40d..5ecb5701ce 100644
--- a/server/test/fixtures/face.stub.ts
+++ b/server/test/fixtures/face.stub.ts
@@ -72,8 +72,8 @@ export const faceStub = {
     boundingBoxY1: 5,
     boundingBoxX2: 505,
     boundingBoxY2: 505,
-    imageHeight: 1000,
-    imageWidth: 1000,
+    imageHeight: 2880,
+    imageWidth: 2160,
   }),
   middle: Object.freeze<NonNullableProperty<AssetFaceEntity>>({
     id: 'assetFaceId6',