From 9ee05c3ada703ae247280bf87920691e5c3b1780 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Thu, 15 Dec 2022 00:20:58 +0100 Subject: [PATCH] Fixing face orientation on rotated photos. fixes #515 --- src/backend/model/threading/MetadataLoader.ts | 46 ++++++++++++++---- test/backend/assets/orientation/Portrait.json | 12 +++++ .../backend/assets/orientation/Portrait_0.jpg | Bin 248531 -> 252973 bytes .../backend/assets/orientation/Portrait_1.jpg | Bin 245684 -> 250126 bytes .../backend/assets/orientation/Portrait_2.jpg | Bin 246915 -> 251358 bytes .../backend/assets/orientation/Portrait_3.jpg | Bin 247276 -> 251718 bytes .../backend/assets/orientation/Portrait_4.jpg | Bin 246554 -> 250996 bytes .../backend/assets/orientation/Portrait_5.jpg | Bin 251487 -> 255930 bytes .../backend/assets/orientation/Portrait_6.jpg | Bin 251800 -> 256243 bytes .../backend/assets/orientation/Portrait_7.jpg | Bin 250892 -> 255334 bytes .../backend/assets/orientation/Portrait_8.jpg | Bin 251978 -> 256420 bytes 11 files changed, 48 insertions(+), 10 deletions(-) diff --git a/src/backend/model/threading/MetadataLoader.ts b/src/backend/model/threading/MetadataLoader.ts index 195306de..bf5df8d3 100644 --- a/src/backend/model/threading/MetadataLoader.ts +++ b/src/backend/model/threading/MetadataLoader.ts @@ -283,19 +283,21 @@ export class MetadataLoader { } } } + let orientation = OrientationTypes.TOP_LEFT; if (exif.Orientation) { - const orientation = parseInt( + orientation = parseInt( exif.Orientation.value as any, 10 ) as number; - if (OrientationTypes.BOTTOM_LEFT < orientation) { - // noinspection JSSuspiciousNameCombination - const height = metadata.size.width; - // noinspection JSSuspiciousNameCombination - metadata.size.width = metadata.size.height; - metadata.size.height = height; - } } + if (OrientationTypes.BOTTOM_LEFT < orientation) { + // noinspection JSSuspiciousNameCombination + const height = metadata.size.width; + // noinspection JSSuspiciousNameCombination + metadata.size.width = metadata.size.height; + metadata.size.height = height; + } + if (Config.Client.Faces.enabled) { const faces: FaceRegion[] = []; if ( @@ -314,11 +316,32 @@ export class MetadataLoader { x: string, y: string ) => { + if (OrientationTypes.BOTTOM_LEFT < orientation) { + [x, y] = [y, x]; + [w, h] = [h, w]; + } + let swapX = 0; + let swapY = 0; + switch (orientation) { + case OrientationTypes.TOP_RIGHT: + case OrientationTypes.RIGHT_TOP: + swapX = 1; + break; + case OrientationTypes.BOTTOM_RIGHT: + case OrientationTypes.RIGHT_BOTTOM: + swapX = 1; + swapY = 1; + break; + case OrientationTypes.BOTTOM_LEFT: + case OrientationTypes.LEFT_BOTTOM: + swapY = 1; + break; + } return { width: Math.round(parseFloat(w) * metadata.size.width), height: Math.round(parseFloat(h) * metadata.size.height), - left: Math.round(parseFloat(x) * metadata.size.width), - top: Math.round(parseFloat(y) * metadata.size.height), + left: Math.round(Math.abs(parseFloat(x) - swapX) * metadata.size.width), + top: Math.round(Math.abs(parseFloat(y) - swapY) * metadata.size.height), }; }; @@ -360,9 +383,12 @@ export class MetadataLoader { if (type !== 'Face' || !name) { continue; } + // convert center base box to corner based box box.left = Math.round(Math.max(0, box.left - box.width / 2)); box.top = Math.round(Math.max(0, box.top - box.height / 2)); + + faces.push({name, box}); } } diff --git a/test/backend/assets/orientation/Portrait.json b/test/backend/assets/orientation/Portrait.json index 6cf655b7..da403430 100644 --- a/test/backend/assets/orientation/Portrait.json +++ b/test/backend/assets/orientation/Portrait.json @@ -1,4 +1,16 @@ { + "faces": [ + { + "box": { + "height": 133, + "left": 468, + "top": 1090, + "width": 123 + }, + "name": "Joe" + } + ], + "keywords": [], "size": { "height": 1800, "width": 1200 diff --git a/test/backend/assets/orientation/Portrait_0.jpg b/test/backend/assets/orientation/Portrait_0.jpg index aa9632e5ed76fa0c3e09d5e9205010afb581fa83..1914e7fba7f66f847e38d77bcf5ca26e7492f9df 100644 GIT binary patch delta 4471 zcmeHL&5PSc6xV)`UP?Fg658e<6jJ(SYb4vTSHv6E_NGaR(`B=ekQU19Xlzeb(x@}z zm3A+phtN|YIrLvBz2sOZgc9hn*Zu{iw?gTE(DaRD+1_+F?S?(B5y}zH$=PSeb9_}E2 zWEii(eHpnmM+=_+cK*AOQn6>-k*s2$@6amYp`GH`cB;rWu3k%HOm?X8MkNRt(jL-FAd`?qzap97(~)b+g5Jfga2A= zTV{??yKQS-yEX6iH_a{=)NEE!g&>4X*g*dK$kaDGzbXiM`DIRi7I+i>z}xCwpSXfm z+tNX05LJ>S)udMCVrV<7tE)D0ZP%>;q$2kth0{tTFO?>GR1(2r#du_DlX!=ZRojA{ z79oUjzh78Bj;=7&aph>KHnKg?ei^%Rdq1Z37L|M~2;JDDk-Ajs`(&yck42D2;1in$ zGz6H0>^M_jM93+4E{$1e6C6^3D*`7KNtI&JP-~*8CS>U-%~=7I+X<6lWzLJHRj1L% z3MX9bg5jay<2WxKNk%lp(#`~w43Fg0n^Zj(zT96y3BYJ1weFM;sn!L>$0&!fT_|eI zR3!_<(fEK3M@n$0^O6h5tk>gYthv3}Yg?^mvx{oY+InpjIh{_UgPhe?(`|ISwNAZN zyP|2)I-7z@D)y*#mwccSEa>_WN~@f8n+@%^<87}gUigUy=Cy$Cg zh4`nEBADx#(D^`MmcUN+Nbz8+V5M1WHudIn5d?#(4sz=-3??Et7bK5Pc|5p;qFmm1}(c6cF5_ALMB~+IXOrQX(JBQMp{m**r1$Kk3a5-e^MA^Op3vMG|0g8u zl;LOmpE98*GM8k@7X>RJ^UtI1m@fX1E`9dPKgyE|+W-In delta 44 zcmZ3xga7hW{t4pzNv;){X$%a$z6@Fn3=A9-)qtb6TY;kgpzbUwN^)zbb)cgK3M7Z$%tNqoSDvSq+Zvk?5S+s!OrMVIfZ zxKgx`>&Y=_zm8q8cM=kFhX^+04p~1YfxMsVyUtiQ9C9y>z;#UG5g%XzvSW>XIb4jv zQ)x_m)4@LBxWI8#5JXBE^pz$WYeJST)0`AQz8pX57pA-@T2&fdEOEs65g6`sHVo6^ z5zc`4SeOZil3{@udlRxl{nz`;C;=D=gwh@JA!M5qe1)CMVotCj)Yga29&DH8!tz5O*byTl6D~)Ogt)fo3ftrdIrL)b6ApDrP z8`L9Dfb5pB&A8;4%FnH&co_C`cBN!js~;i@CV=YMECr9Lkc^*<#=tK5Vsz!vM299kmNs@&OScSxAyLLi zNZ*W8pfocY4G5ULdVK&(lykv)GQym+`%qf>tlJ(i$;5z#`c__w`kIG@ppI@?#ghJT zp9U_A#QV8BXMlq~fw61mZD*JesHx^u7PI+JI^4vu#qi*eIC4bED(;WTS^0#KOx>fo zq3pQ{H8au68sFZ@S~;DIA?AXp(bY2Y*`?AkLbk)uqr}}~on+j_KnAq59YNKUAa|qN z0o*nwx5Iz}K_QKam&SI%^A=?JvM93QKRvl~H6|0BdKf5wF7<|ZE{NC20-mTX^@u z$4m2?Hm@x%&A+WJzzZON0P`Tl-2LtMr(fUt`F3>nnfdCk^>=>wGJYP8|3T9;(rS0z+G!p3(Tf&IN8dST&UY>&^usfUW4uzZpmwI+hiP0&>{_DPZPXF$%3>!M3#Z%{WB(m`MGA^l4Bx{(Sb_(5YKD9;|fj zZrCIPdhd*ry|erK`0N3CgVh}7HkvSwynbg(%%QVI$7yq+P0G(`sinXH|`!fy;f)B3hwHL3Mzr9q9`gv z{8i~2A3mr(*R5(uR`=0nH^C+HGoKeRGMg&C_P9={mx(f&Yk z26diu0hx7L3=b8z+pW6MDK}T!)lR2TskF-NX1UgCZ?&qGYO_(R6jwTJMT^qeiCP)>El}z@_A087!vkaz zT*D?VR#r{qlA3vdk&Q{YT)}QqGjta_hm89ODv|oAPpqSmz?z_9oF-@CK7;(*llvWV z2*@eH7L$@e&WaOEPqBAS(g^wmzdMLVxsxebecT`m^B*A3K zhHfpwQQ0q*ol41BS$*Fw!URw~o2B3$6_WAO(HPhxpN+0On&{AkC(=P!wRGJO2ohzC zg!I)o1xhoc(SU%-%hw06L^&7iP)3N8MjuKmpLM$fCYczpP~XW*QEz%k2#WP$u~5?Q zKA?fiBJpnS4i8Y!Cop!cyzLAN0=3kf%3`+uNr#(YM+^^+2$myCworde&c;WKWa=Kx z4Q0&?0UR~GU(QsPJ9TjzuVbUp4wMAZpC>3k>7|4jI$M&QAK!Un?efMVpGv;N=4 zleFBdOO2$(djS=&l`B@#_XAb!8~I*;#dC5+{H)>E{%6hT)$CpKe>J{ljlSl8){I`w z-ZlSM<7?LFYyM}=Xl`%vRe}WYP8`8EhZlcnyOY=Ia>3TtHn+CBi|PZ2rrlbed!#MA zedojFc}<(w7MJJW)E3|cP+L=&J1OSoZ@)kM`o@oMM9)95p8eH+>-*2+$MM|bc>afY I;q{;Y28*B#WB>pF delta 49 zcmcb&n!mY|e}cGRl50g~8Uur`FM}2X0|N&G>%=OXi76t@mj&A|3o-&R)Aq}P%-lx+ Da+(h) diff --git a/test/backend/assets/orientation/Portrait_3.jpg b/test/backend/assets/orientation/Portrait_3.jpg index 5a5544f233d9168cd6bf399ff16367b5483be4b0..7f539fe61eaa1203fe464ddbc475dc3219263899 100644 GIT binary patch delta 4461 zcmeHL&5zqe6!&gd{m_=5G63a1dOIy8>B?#{ksSn7A#vS9YXU`3t1|8#Gqi^?< z4Kk)TA8~T`(VboVXb-mopom7v1RB5M(%Ztu5zp2 z?{DiHj1#?S+BIz3whkL8ejn=UWEDrvw*BJJr#}O{@nGoh^tPt1VAU~HP&q`Erm2~F zCgUT^X}8;!?OLu|14vCA#1dt-SX`-0^oYQmCX%vPS0>SbO=QP_ot7a)NxxrOK8~&^ zQgIb%Avg1V(0&!WV)q~+)(#PD!ZF$0C$YRz=?8eGn@o6EL=a$$gd_r(fb2LkUzm#- zcp;5>WMLE$j%pmGH9@4L@knXfGfl|SQJV7tsJ0WOquPQOMXO3a)unKvPOGeKR zsPl>o$gJOEc%rzy)$16W>#fFm)9dyeySHJl*XvuGy+*6O3jKq^*YR>I781pnsGWgdlhVL%AjffsB&c0) z4cawQujAUlL+u(K;9YP>D1`BVs*F2)$L|kdxpFSpP^O5J?g&b&nswV_CYczsM892?qP`I#At=@z$8_~; z_h=ljRJ>QY!($YW2#j5;YCFS%KrJ<=%9yQ}=?HS{i^k>bNvZPRXg&8FIXA%b9R)3M$9^_d7Ba<i?C5DvW~nY4p}1p#a^G`zcW+1QRGk^6sH@=l?U`#~OhfV+zj?j>i{3*6|V=X*w?B@{YlUHNfk~Rf9C&`2|b~?GyYFV*eS!$_&;Sr zPiXFp{}U2+%J4J(Pnpol-29^iiQ$zvg-;GI{?xYTZ`Gb@YpwOQTm2RF{X^3(U0!;i zoqOl%N0*m1ZCP8ny!@7S4qgBO1emJ@=HhR^-~amjk8h^WKee9yz4`WcUu2K7rN`Ow J57{~S^FKF43%~#X delta 49 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 E0E+GqlmGw# diff --git a/test/backend/assets/orientation/Portrait_6.jpg b/test/backend/assets/orientation/Portrait_6.jpg index 8fc576e0675ac9f7472af2f965d4edd865b7b8e7..fef27c58644c0f3efca42841590e157c91856b2b 100644 GIT binary patch delta 4462 zcmeHLy>Ht_6lZKB-I{Dc+YAJQ6zK=QB}*cr1hQo}F09m03?v8&6ld}zolWxCyOT+0 zAvy$|3M5Lu%VMD9Fd_}-(tyZ)mCzZGb=Z>cvkFN=~q!UCyT6vo`k+FD`VgKH@^HMPKqgSOU@ zZq*099d(^DtTqfw_btm(VFShQLsg#4;?OP2D-Lz?)4&@G2426rIdKK6wkCthA*wV@ zjnp+L8=7{j)iN!|bQ~QZIzNg9%Ji6Dt4wq;_Zdk9p|L7WqCGkmZ4Gu>h7cvaUTOI> zx}r$NRiL@p$PYpLRqXQJqXe6M%;}i2in>mA$p)sF&LLLq z9SgeEhNHIYxL(ci>WvR98zzA4*)j!p2p5zcO~$}I{Q2Z6B)JMrcqlyN%a(3y96_SA ziIBV=r$A|GG#z0udHVhUmP_ZH4n&F=?hK){s#&))qJr`fP1NnG6xH<*aZY^Iwhc$U zai7EiP5FD3JAZ`YA%?MQR&A%55U45VR2j4RG95wgdwjgNk9{$rWDO0c+c_)$Ee#m>@ez5)vHj(r!NOVju%r+DoBopoL^i zayf!!bG97E1PDqJRU8HJlkm-BLJqni^HMBp2u4td#J%}^=idKJ_^C$V#)!ZJg!v=lqwtvZ z|2Cc%oqUlwe?EXWANOxrIDGAmpM E0Elo8#sB~S diff --git a/test/backend/assets/orientation/Portrait_7.jpg b/test/backend/assets/orientation/Portrait_7.jpg index cfa04d66e06fe9e2286caaf473d4a11bcd099e9a..b38e78623712d4b76b50dbd12575624031590ff6 100644 GIT binary patch delta 4461 zcmeHL&1>UE6jwgj-kQBFZF3M>wroGFFWa##;w*{nbW`GONCH`wLK#aFdlE^b&Ws}^ z7tzDgOAEWuzoG1<$DWq5&||Ot3reAfLg|0d?HkFmoi^FlOO7!z(meg%d-HzpLqcCY zTl(#%r6;$Re(WUVP*J*F<$Xm_9zA_`cV4;i^zLf@Q14>it+}W7frit zNx{osZhkuuB618P_#m-AotH0-aC7+@17mI4NtqbrmiMV5=3c$1yYm154n>xwamH$*HUa~YK9TVO>JGe zRUdYD)D~q}t?6dTHBD264P?I$Re3V9qhy*+cBrGD2Hu!==0%e z9+!2>`i9kLGz`-=Y`X-I5FZf^N9 zx`IH)m7%%lq$i;LDt7t)X@rek%;}K1xO0L-al6p>+_7#nWPTQb=Nj0@0l+w9#~S-` z86ShE(wGK@ivrA0iJ`c}u@EHeOHFgE30b;Gb5a1sc7nKHn(`uPm1*>lKrv;88dJ zBuX0z$?I_nG3G|&0S1$o?+;+9bk6Cah!MlNa)d+zgM_(2Po`g7`sN%cA5c!8gfqMF&qD+!%JO<4|_-06(dSEP=8F$+Q$^< z>OLt9<@XJ!nTcNB_|9(L%Gq3u5aU>muBKMZE}4!Iay^QCg1vp(PRCsgWI#(hF;tBW zxf?A9u&hs(!;k<$5lOJ0#dgl~He~stDDvSyJ-G`tDpHi^-O{Tyvkq=YxAH!P_{Wkw zm`&v3=|EtXz)s~zabT;aZkcvvMQ%P5K`^Fn*2=ZoSOgC_Q!iI*4LcLT{6o&ElvS&Z zN_mp7sqGfj|0@Ya7#Z)Q=#6ti4!S;bVk}DtCXkQB-nn$A|1;m08i5-F0?!ZT4v35V z^Va{{cq*&SxYS5iyBAOaPjac3y&lM7Un?H delta 49 zcmaEMioa(C{{(TtB-e_}GzJD=Uj{7(1_llW)`?X%6H`Q*FA26^5@ZBortOymnWtO^ E0D<`s3jhEB diff --git a/test/backend/assets/orientation/Portrait_8.jpg b/test/backend/assets/orientation/Portrait_8.jpg index b2a50d6eb67453ace5c2d614af2050ad2a43257a..852fd98b4d027ec2294edb87da44c053db884e12 100644 GIT binary patch delta 4461 zcmeHL&2QX96nB%T+-PqV(1YcwsC=wF_HN=`tD9=Go6w+ciPA`|DkL;(Pj;uVJ!WP$ zc5<;eRGg67{{bN`NL)Bos1iqxaNvx%AtC+)${TyVyMd&TD#xt6vgi5t-kbM(AGZAC zx0k;B`O=dsmws5!=&q*q``WvjragT6_V$8y@#&45`R7zy0?7sVe+_@QdZKBs!FdTe zbz2Eu{CfGTk(7yNS+Ov2zy`$hS!886v1}7r+Ra;8g8e-r^#K{uxNZFT?75-Spl#e= z?xTLvB_n$Kkdu3dcX#~5J--zgw^sF=nU_UL9ubN4EDB@cWo@Id?!mPZTZUd>-j!tzl>e6bC3{gn+P`MK3U%6GTcH50xgGYC@Kd)0`DRwVfy(*5k!A!LUWe1dY6Sf!%EOjYubACC{| za3nc{I&%UQC+ApdB# zzC(5aIVV_SQZmSyJ;Q7(_Krw8@i2tQ=hV88I++6v@eaF>`OL{SPLHE%%9PiiR8$jjRrL3 zEMarot*HN35~?r?-Y3ypM}z`&L++(Sl@Lsz5X*Z<(w+a$e4l6pZj2~AKe#`lJ`Rss z|8L{Dsy6FHBSq~VLj^p^m0t0Bpo)F9deooxoS#{iBtIG@Zt|`YxY)MH<8v{Tixt0sqY_}cIE1Y zhuY#>*FU(rplJ)*($$4Gv_*IU1Q1}Z7nsYx{r=#Ki{HPVKL6Ny_SgEG-+Y!m&MrL8 L7QV|CfBNNbUxW(^ delta 49 zcmZ2-ng7%V{t4oONv;){X$%a$z6@Fn3=A9$tP`tjCZ>oqUlMG;B*+NFOxrIBGGDq5 E0E80|S^xk5