From 1d62f8aa3f70befb6ea027bb326f60fe6b08678f Mon Sep 17 00:00:00 2001 From: GTripathee Date: Thu, 7 Sep 2023 10:26:01 +0200 Subject: [PATCH 01/81] fix: change the minimum deployment from 8 to 11 to fix the build error --- phyphox-iOS/phyphox/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 5b80a883..d6be274b 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11283 + 11284 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 3ed8db1efbd2b5818848d49feddd6ff6d67318ba Mon Sep 17 00:00:00 2001 From: Sebastian Staacks Date: Tue, 8 Aug 2023 13:32:22 +0000 Subject: [PATCH 02/81] Translated using Weblate (Georgian) Currently translated at 100.0% (342 of 342 strings) Translation: phyphox/iOS Translate-URL: https://translate.phyphox.org/projects/phyphox/ios/ka/ --- phyphox-iOS/phyphox/ka.lproj/Localizable.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phyphox-iOS/phyphox/ka.lproj/Localizable.strings b/phyphox-iOS/phyphox/ka.lproj/Localizable.strings index 8d0ea431..6814cc34 100644 --- a/phyphox-iOS/phyphox/ka.lproj/Localizable.strings +++ b/phyphox-iOS/phyphox/ka.lproj/Localizable.strings @@ -152,7 +152,7 @@ "pick_exportFormat" = "ამოირჩიე მონაცემების ფორმატი."; "noDrawableData" = "არარის დახატვადი მონაცემები."; "categoryNewExperiment" = "მარტივი ექსპერიმენტები"; -"categoryRawSensor" = "სენსორის მონაცემები"; +"categoryRawSensor" = "დაუმუშავებელი სენსორები"; "categoryPhyphoxOrg" = "წვლილი შეიტანეთ ფაიფოქსში"; "categoryPhyphoxOrgHint" = "ფაიფოქსის დასახმარებლად ახალი ფუნქციებია სიის ბოლოს."; "experimentinfo_hint" = "შეამოწმეთ ექსპერიმენტის ინფორმაცია, იმისათვის რომ გაერკვეთ ექპერიმენტში."; From 98c2f233a0c27c2985f12a8fd04147b4ce1aa72e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=9A=CF=89=CF=83=CF=84=CE=AE=CF=82=20=CE=A7=CE=B1=CE=BB?= =?UTF-8?q?=CE=BA=CE=B9=CE=B1=CE=B4=CE=AC=CE=BA=CE=B7=CF=82?= Date: Wed, 16 Aug 2023 17:00:09 +0000 Subject: [PATCH 03/81] Translated using Weblate (Greek) Currently translated at 100.0% (342 of 342 strings) Translation: phyphox/iOS Translate-URL: https://translate.phyphox.org/projects/phyphox/ios/el/ --- phyphox-iOS/phyphox/el.lproj/Localizable.strings | 3 +++ 1 file changed, 3 insertions(+) diff --git a/phyphox-iOS/phyphox/el.lproj/Localizable.strings b/phyphox-iOS/phyphox/el.lproj/Localizable.strings index 94f72adc..55b642d2 100644 --- a/phyphox-iOS/phyphox/el.lproj/Localizable.strings +++ b/phyphox-iOS/phyphox/el.lproj/Localizable.strings @@ -337,3 +337,6 @@ "settings" = "Ρυθμίσεις"; "cameraFrontFacing" = "Μπροστινή κάμερα"; "cameraBackFacing" = "Οπίσθια κάμερα"; +"url_invalid" = "Άκυρο URL"; +"url_invalid_msg" = "Αυτό το πείραμα είναι απλά ένας σύνδεσμος, αλλά δεν βρέθηκε έγκυρο URL."; +"showQRCodeForRemoteURL" = "Κωδικός QR για την πρόσβαση σε URL"; From 60519c9a8420e260188b4587bf2c89eeb0939b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=9A=CF=89=CF=83=CF=84=CE=AE=CF=82=20=CE=A7=CE=B1=CE=BB?= =?UTF-8?q?=CE=BA=CE=B9=CE=B1=CE=B4=CE=AC=CE=BA=CE=B7=CF=82?= Date: Wed, 16 Aug 2023 17:04:47 +0000 Subject: [PATCH 04/81] Translated using Weblate (Greek) Currently translated at 100.0% (13 of 13 strings) Translation: phyphox/iOS Settings Translate-URL: https://translate.phyphox.org/projects/phyphox/ios-settings/el/ --- .../Settings.bundle/el.lproj/Root.strings | Bin 626 -> 1184 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/phyphox-iOS/phyphox/Settings.bundle/el.lproj/Root.strings b/phyphox-iOS/phyphox/Settings.bundle/el.lproj/Root.strings index 55920de67ee52ff94f811f25765684d5b07ed041..7a1b1f0f9076bd33d703977f210f8a24f46d0c57 100644 GIT binary patch literal 1184 zcmbW0%Sr=55Jd}Wm#-MIQV@SYTqp`IA_#s#FcQU>h>4>9g*%Zf1h+oWF~mG%67xbB z{Ur5N$2b`u2-0+Bx~J;iTem82uL(&=Qi`%JCDx|oCCh1vb6PSo$KK~HFKgUni1cKZ zos=_eYoG&t;#t+EUh0EZ^ih*C^CRMl>`FlnVAy7tU)~AFSh6Nf*4R z?kxIE#fGfHeiW;{Ozl41x_24YN%Oe>C(bsAx!7EnslfUXe(H{Rm*LF7y%*D5hPMOT qHQayu7@^HO52M-*$7kxy`%w?0=tchfCFj7`yffk(^wE>%H-vYCcp~-y delta 7 OcmZ3$`H5vi5fcCmT>``a From fd4dfa33cfd923eb87bef304d0e6d809e760afff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20H=C5=99eb=C3=ADk?= Date: Sun, 17 Sep 2023 20:39:24 +0000 Subject: [PATCH 05/81] Translated using Weblate (Czech) Currently translated at 97.3% (333 of 342 strings) Translation: phyphox/iOS Translate-URL: https://translate.phyphox.org/projects/phyphox/ios/cs/ --- phyphox-iOS/phyphox/cs.lproj/Localizable.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/phyphox-iOS/phyphox/cs.lproj/Localizable.strings b/phyphox-iOS/phyphox/cs.lproj/Localizable.strings index 4acfdd40..cb9a048d 100644 --- a/phyphox-iOS/phyphox/cs.lproj/Localizable.strings +++ b/phyphox-iOS/phyphox/cs.lproj/Localizable.strings @@ -333,3 +333,4 @@ "depthAggregationModeAverage" = "Average"; "sensorDepth" = "Depth (LiDAR)"; "depthAggregationModeWeighted" = "Weighted"; +"settings" = "Nastavení"; From 95ce2abb710c54476d769f3be02a111d6fed8cbc Mon Sep 17 00:00:00 2001 From: Sebastian Staacks Date: Mon, 9 Oct 2023 14:01:56 +0200 Subject: [PATCH 06/81] Added translation using Weblate (Tamil) --- phyphox-iOS/phyphox/ta.lproj/Localizable.strings | 1 + 1 file changed, 1 insertion(+) create mode 100644 phyphox-iOS/phyphox/ta.lproj/Localizable.strings diff --git a/phyphox-iOS/phyphox/ta.lproj/Localizable.strings b/phyphox-iOS/phyphox/ta.lproj/Localizable.strings new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/phyphox-iOS/phyphox/ta.lproj/Localizable.strings @@ -0,0 +1 @@ + From 44766e824451282299ed6a3115ebfd1d207073c2 Mon Sep 17 00:00:00 2001 From: Sebastian Staacks Date: Mon, 9 Oct 2023 14:02:04 +0200 Subject: [PATCH 07/81] Added translation using Weblate (Tamil) --- phyphox-iOS/phyphox/ta.lproj/InfoPlist.strings | 1 + 1 file changed, 1 insertion(+) create mode 100644 phyphox-iOS/phyphox/ta.lproj/InfoPlist.strings diff --git a/phyphox-iOS/phyphox/ta.lproj/InfoPlist.strings b/phyphox-iOS/phyphox/ta.lproj/InfoPlist.strings new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/phyphox-iOS/phyphox/ta.lproj/InfoPlist.strings @@ -0,0 +1 @@ + From eda594093c33c5e8ef2b73dbbf4588ff7c911a4b Mon Sep 17 00:00:00 2001 From: Sebastian Staacks Date: Mon, 9 Oct 2023 14:02:14 +0200 Subject: [PATCH 08/81] Added translation using Weblate (Tamil) --- .../phyphox/Settings.bundle/ta.lproj/Root.strings | Bin 0 -> 4 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 phyphox-iOS/phyphox/Settings.bundle/ta.lproj/Root.strings diff --git a/phyphox-iOS/phyphox/Settings.bundle/ta.lproj/Root.strings b/phyphox-iOS/phyphox/Settings.bundle/ta.lproj/Root.strings new file mode 100644 index 0000000000000000000000000000000000000000..ed60b89c47319bc2c37b5775c777dddd6a16d56b GIT binary patch literal 4 LcmezWkBb2S2Mz)V literal 0 HcmV?d00001 From 1de9b5e72962a7bbb2665615f8e74016f3fabd2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20H=C5=99eb=C3=ADk?= Date: Wed, 29 Nov 2023 22:10:16 +0000 Subject: [PATCH 09/81] Translated using Weblate (Czech) Currently translated at 100.0% (342 of 342 strings) Translation: phyphox/iOS Translate-URL: https://translate.phyphox.org/projects/phyphox/ios/cs/ --- .../phyphox/cs.lproj/Localizable.strings | 89 ++++++++++--------- 1 file changed, 48 insertions(+), 41 deletions(-) diff --git a/phyphox-iOS/phyphox/cs.lproj/Localizable.strings b/phyphox-iOS/phyphox/cs.lproj/Localizable.strings index cb9a048d..65ed5aef 100644 --- a/phyphox-iOS/phyphox/cs.lproj/Localizable.strings +++ b/phyphox-iOS/phyphox/cs.lproj/Localizable.strings @@ -55,7 +55,7 @@ "sensorGyroscope" = "Gyroskop"; "switchToPhoneLayout" = "Zobrazení: Úzké"; "sensorProximity" = "Senzor přiblížení"; -"unknown" = "neznámé"; +"unknown" = "Neznámé"; "next" = "Další"; "start" = "Start"; "switch_to_calibrated_magnetometer" = "Přepnout na kalibrovaný magnetometr"; @@ -67,7 +67,7 @@ "disableTimedRun" = "Zrušit načasování měření"; "categoryNewExperiment" = "Vlastní jednoduché experimenty"; "confirmDeleteTitle" = "Potvrdit smazání?"; -"pick_exportFormat" = "Zvolte formát dat pro export"; +"pick_exportFormat" = "Zvolte formát dat pro export."; "share_subject" = "Screenshot z phyphoxu"; "title_activity_experiment" = "phyphox"; "save_locally_done" = "Experiment úspěšně přidán do sbírky."; @@ -84,7 +84,7 @@ "remotePhyphoxOrgURL" = "http://phyphox.org/remote-control"; "confirmDelete" = "Chcete tento experiment smazat ze své sbírky?"; "creditsNames" = "Vývoj a koncept:\nDr. Sebastian Staacks\nKoncept:\nProf. Christoph Stampfer\nProgramování:\nGaurav Tripathee\nDominik Dorsel\nJonas Gessner\nCamilla Lummerzheim"; -"remoteServerWarning" = "Právě se chystáte povolit aplikaci vzdálený přístup k naměřeným datům. Prosím ověřte si, že jste připojeni k důvěryhodné síti! Zároveň vás chceme upozornit, že přímé spojení přístrojů není na mnoha univerzitních či firemních sítích povoleno a tedy ani umožněno. \nPro zajištění maximální bezpečnosti a výkonu doporučujeme připojení tohoto zařízení pomocí tetheringu (vytvoření mobilního hotspotu). \nPo zmáčknutí tlačítka Ok můžete svá měření zobrazit jakýmkoliv internetovým prohlížečem na stejné ethernetové síti."; +"remoteServerWarning" = "Právě se chystáte povolit aplikaci vzdálený přístup k naměřeným datům. Prosím ověřte si, že jste připojeni k důvěryhodné síti! Zároveň vás chceme upozornit, že přímé spojení přístrojů není na mnoha univerzitních či firemních sítích povoleno a tedy ani umožněno. \n\nPro zajištění maximální bezpečnosti a výkonu doporučujeme připojení tohoto zařízení pomocí tetheringu (vytvoření mobilního hotspotu). \n\nPo zmáčknutí tlačítka Ok můžete svá měření zobrazit jakýmkoliv internetovým prohlížečem na stejné ethernetové síti."; "export_pick_share" = "Vyberte si způsob exportu"; "sensorNotAvailableWarningTitle" = "Senzor není dostupný."; "sensorNotAvailableWarningMoreInfo" = "Více informací"; @@ -110,7 +110,7 @@ "rename" = "Přejmenovat"; "loadingBluetoothConnectionText" = "Připojuji Bluetoothové zařízení…"; "remoteServerNoNetwork" = "Nejdříve je třeba nastavit Wifi připojení ke vzdálenému zařízení. Prosím povolte Wifi nebo vytvořte mobilní hotspot."; -"fontSize" = "Velikost písma."; +"fontSize" = "Velikost písma"; "start_hint" = "Dotkněte se trojúhelníku pro spuštění experimentu."; "deviceInfo" = "Informace o zařízení"; "copyToClipboard" = "Zkopírovat do schránky"; @@ -144,13 +144,13 @@ "newExperimentBluetoothErrorTitle" = "Chyba Bluetooth skenu"; "newExperimentBluetoothLoadFromDevice" = "Načíst ze zařízení"; "newExperimentBluetoothLoadFromDeviceInfo" = "Toto zařízení obsahuje svůj vlastní phyphox experiment. Zvolte možnost načíst ze zařízení k jeho spuštění."; -"newExperimentQR" = "Přidat experiment pomocí QR kódu."; +"newExperimentQR" = "Přidat experiment pomocí QR kódu"; "newExperimentQRscan" = "Naskenujte QR kód obsahující phyphox experiment."; "newExperimentQRErrorTitle" = "Chyba QR kódu."; "newExperimentQRNoExperiment" = "Nepodařilo se najít QR kód obsahující experiment pro phyphox."; "newExperimentQRcrcMismatch" = "QR kód který jste právě naskenoval/a podle všeho nepatří k předchozím kódům. Prosím ujistěte se, že všechny kódy patří ke stejnému experimentu."; "newExperimentQRCodesMissing1" = "QR kód, který jste právě naskenoval/a, je součástí sady"; -"newExperimentQRCodesMissing2" = "kódů. Prosím stiskněte pokračovat k naskenování zbytku kódů. Stále chybějící kódy:"; +"newExperimentQRCodesMissing2" = "kódy. Prosím stiskněte pokračovat k naskenování zbývajících kódů. Stále chybějící kódy:"; "newExperimentQRBadCRC" = "Neplatný kontrolní součet. To znamená, že buď je QR poškozen nebo nesprávný nebo došlo k chybě při jeho skenování."; "newExperimentBTReadErrorTitle" = "Chyba bluetoothového experimentu."; "newExperimentBTReadErrorCorrupted" = "Přenášená data experimentu jsou poškozena."; @@ -195,7 +195,7 @@ "bt_more_info_link_text" = "Pokud vaše zařízení není podporováno, pak zvolte možnost \"více informací\" abyste se na našich stránkách dozvěděli, jak phyphox podporuje flexibilní Bluetooth Low Energy."; "bt_more_info_link_button" = "Více informací"; "bt_more_info_link_url" = "https://phyphox.org/ble"; -"common_direction_short_north" = "S"; +"common_direction_short_north" = "N"; "common_direction_short_south" = "J"; "common_direction_short_east" = "V"; "common_direction_short_west" = "Z"; @@ -203,33 +203,33 @@ "common_direction_short_south_east" = "JV"; "common_direction_short_north_west" = "SZ"; "common_direction_short_south_west" = "JZ"; -"apple_ban" = "This experiment is no longer available."; -"sensorAttitude" = "Attitude"; -"leave_experiment" = "Leave experiment"; -"leave_experiment_question" = "Leave this experiment and discard recorded data?"; -"leave" = "Leave"; -"export_empty" = "This experiment configuration does not define any data to be exported. If this is an error, please contact the source of the QR code or configuration file."; -"toggleBrightMode" = "Bright mode"; -"networkPrivacyWarning" = "Privacy warning"; -"networkVisitPrivacyURL" = "Privacy policy"; -"networkPrivacyInfo" = "This experiment uses a network connection, which means that experiment data can be transmitted to a network service. You can find more details by clicking the privacy policy button below, which will take you to a website with a privacy policy provided by the creator of this experiment. If the network service is not a local device, you should be aware that phyphox may transmit the following information if you start this experiment:"; -"networkPrivacyUniqueID" = "An id that is unique to this device and the network service, which allow to match all data send from this device through this experiment configuration."; -"networkPrivacySensorData" = "Data from the following sensors:"; -"networkPrivacySensorMicrophone" = "Recordings or data derived from recordings from the microphone."; -"networkPrivacySensorLocation" = "Location data (GPS data or similar)."; -"networkPrivacyDeviceInfo" = "Detailed information about the model of your device and/or the version of phyphox."; -"networkPrivacySensorInfo" = "Technical details about the following sensors:"; -"categoryPhyphoxOrg" = "Contribute to phyphox"; -"categoryPhyphoxOrgHint" = "There are new options to support phyphox at the bottom of the list."; -"graph_tools_system_time" = "Convert to system time"; -"timedRunBeeps" = "Acoustic signals"; -"beeperCountdown" = "Countdown sounds"; -"beeperStart" = "Start sound"; -"beeperRunning" = "Running sounds"; -"beeperStop" = "Stop sound"; -"activate_all" = "Activate all"; -"deactivate_all" = "Deactivate all"; -"common_unit_short_arbitrary_unit" = "a.u."; +"apple_ban" = "Tento experiment již není k dispozici."; +"sensorAttitude" = "Přístup"; +"leave_experiment" = "Opustit experiment"; +"leave_experiment_question" = "Opustit experiment a smazat neuložená data?"; +"leave" = "Opustit"; +"export_empty" = "Tato konfigurace experimentu nemá definována žádná data k exportu. Pokud se jedná o chybu, kontaktujte prosím zdroj QR kódu nebo konfiguračního souboru."; +"toggleBrightMode" = "Světlé prostředí"; +"networkPrivacyWarning" = "Upozornění ohledně soukromí"; +"networkVisitPrivacyURL" = "Zásady ochrany osobních údajů"; +"networkPrivacyInfo" = "Tento experiment používá síťové připojení, což znamená, že data experimentu lze přenést do síťové služby. Další podrobnosti naleznete po kliknutí na tlačítko zásad ochrany osobních údajů níže, které vás přesměruje na webovou stránku se zásadami ochrany osobních údajů poskytnutými tvůrcem tohoto experimentu. Pokud síťová služba není místní zařízení, měli byste si být vědomi toho, že phyphox může přenášet následující informace, pokud spustíte tento experiment:"; +"networkPrivacyUniqueID" = "ID, které je jedinečné pro toto zařízení a síťovou službu, což umožňuje porovnat všechna data odeslaná z tohoto zařízení prostřednictvím této konfigurace experimentu."; +"networkPrivacySensorData" = "Data z následujících senzorů:"; +"networkPrivacySensorMicrophone" = "Nahrávky nebo data odvozená z nahrávek prostřednictvím mikrofonu."; +"networkPrivacySensorLocation" = "Údaje o poloze (GPS nebo podobná data)."; +"networkPrivacyDeviceInfo" = "Detailní informace o modelu vašeho zařízení a/nebo verzi phyphox."; +"networkPrivacySensorInfo" = "Technické informace o následujících senzorech:"; +"categoryPhyphoxOrg" = "Přispějte k phyphox"; +"categoryPhyphoxOrgHint" = "Ve spodní části seznamu jsou nové možnosti podpory phyphox."; +"graph_tools_system_time" = "Převést na systémový čas"; +"timedRunBeeps" = "Akustické signály"; +"beeperCountdown" = "Zvuky odpočítávání"; +"beeperStart" = "Startovací zvuk"; +"beeperRunning" = "Běžící zvuky"; +"beeperStop" = "Zvuk zastavení"; +"activate_all" = "Aktivovat vše"; +"deactivate_all" = "Deaktivovat vše"; +"common_unit_short_arbitrary_unit" = "lib. jednotka"; "common_unit_short_second" = "s"; "common_unit_short_milli_second" = "ms"; "common_unit_short_hertz" = "Hz"; @@ -326,11 +326,18 @@ "common_musical_note_gb7" = "F#7 / Gb7"; "common_musical_note_g7" = "G7"; "common_musical_note_ab7" = "G#7 / Ab7"; -"remoteDepthGUIWarning" = "Previewing and controlling the LiDAR/ToF sensor on the remote interface is not supported."; -"depthAggregationMode" = "Aggregation mode"; -"depthAggregationModePrompt" = "Select the method to aggregate depth information within the selected region."; -"depthAggregationModeClosest" = "Closest"; -"depthAggregationModeAverage" = "Average"; -"sensorDepth" = "Depth (LiDAR)"; -"depthAggregationModeWeighted" = "Weighted"; +"remoteDepthGUIWarning" = "Náhled a ovládání LiDAR/ToF senzoru na dálkovém rozhraní není podporováno."; +"depthAggregationMode" = "Režim agregace"; +"depthAggregationModePrompt" = "Vyberte metodu pro agregaci informací o hloubce ve vybrané oblasti."; +"depthAggregationModeClosest" = "Nejbližší"; +"depthAggregationModeAverage" = "Průměr"; +"sensorDepth" = "Hloubka (LiDAR/ToF)"; +"depthAggregationModeWeighted" = "Vážený průměr"; "settings" = "Nastavení"; +"sensorGravity" = "Gravitace"; +"sensorCamera" = "Fotoaparát"; +"cameraFrontFacing" = "Přední fotoaparát"; +"cameraBackFacing" = "Zadní fotoaparát"; +"url_invalid" = "Neplatná URL"; +"url_invalid_msg" = "Tento experiment je pouze odkaz, ale nebyla nalezena žádná platná adresa URL."; +"showQRCodeForRemoteURL" = "QR kód k přístupu na URL"; From bd5632a41828f9d69b09b7434df068624ce5f05d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20H=C5=99eb=C3=ADk?= Date: Wed, 29 Nov 2023 22:03:25 +0000 Subject: [PATCH 10/81] Translated using Weblate (Czech) Currently translated at 100.0% (13 of 13 strings) Translation: phyphox/iOS Settings Translate-URL: https://translate.phyphox.org/projects/phyphox/ios-settings/cs/ --- .../Settings.bundle/cs.lproj/Root.strings | Bin 4 -> 1068 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/phyphox-iOS/phyphox/Settings.bundle/cs.lproj/Root.strings b/phyphox-iOS/phyphox/Settings.bundle/cs.lproj/Root.strings index ed60b89c47319bc2c37b5775c777dddd6a16d56b..59e3181b5cf8d30fb6fbcab16bf15521b6c0c893 100644 GIT binary patch literal 1068 zcmbW0F;4@V`=0P+6mXGvr)R zv@n;2+u51--n`j+f7{ZQcJ!pS3Z4h$>aaU!pDNQa>zTJ)Eg~72K}W0|H8fCHBYwA& z2lX^3cDNC%;aOv_`>a9}T~O7cI-=cZ#%Y^a&`{SP-h#Q%OVWmW@ek}%?5xuz%^sr*Gnzyb56sx9@%K2UQ@RqEBxoR-=rni8FWTX>E+9Po0Y zdpasnWvn^T7Lh`=B-auTjAP&~bu~!(j+BFCt}MXr@t!$SU}tedKKR2S>!?`sU==Z^ zZGNU|3g!%Kb9+f-#b(+|-mA$P+fa3C%!fF=x?YKF&N@Zo2&FOmk}gv?=kVbOr4r%7!sDNr5n-nzo(r~myg{8e<0j!JXK=Qo6*3Db0~yDN4!E#~3V z3@EW_-{H-sdh4#o6P4CIGTV?{2Rj41bS>4l)x`H}Zhq!>|1X!K?h|(?a;{W&nd(oC TXBxv`J$$c Date: Mon, 4 Dec 2023 08:57:29 +0100 Subject: [PATCH 11/81] Added translation using Weblate (Bengali) --- phyphox-iOS/phyphox/bn.lproj/Localizable.strings | 1 + 1 file changed, 1 insertion(+) create mode 100644 phyphox-iOS/phyphox/bn.lproj/Localizable.strings diff --git a/phyphox-iOS/phyphox/bn.lproj/Localizable.strings b/phyphox-iOS/phyphox/bn.lproj/Localizable.strings new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/phyphox-iOS/phyphox/bn.lproj/Localizable.strings @@ -0,0 +1 @@ + From e5767d26a026d5803ff6b972f6f02596613d4316 Mon Sep 17 00:00:00 2001 From: Sebastian Staacks Date: Mon, 4 Dec 2023 08:57:44 +0100 Subject: [PATCH 12/81] Added translation using Weblate (Bengali) --- phyphox-iOS/phyphox/bn.lproj/InfoPlist.strings | 1 + 1 file changed, 1 insertion(+) create mode 100644 phyphox-iOS/phyphox/bn.lproj/InfoPlist.strings diff --git a/phyphox-iOS/phyphox/bn.lproj/InfoPlist.strings b/phyphox-iOS/phyphox/bn.lproj/InfoPlist.strings new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/phyphox-iOS/phyphox/bn.lproj/InfoPlist.strings @@ -0,0 +1 @@ + From 9d1bd4a4359c5b7f75e7c213408d1d3754a1e136 Mon Sep 17 00:00:00 2001 From: Sebastian Staacks Date: Mon, 4 Dec 2023 08:57:48 +0100 Subject: [PATCH 13/81] Added translation using Weblate (Bengali) --- .../phyphox/Settings.bundle/bn.lproj/Root.strings | Bin 0 -> 4 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 phyphox-iOS/phyphox/Settings.bundle/bn.lproj/Root.strings diff --git a/phyphox-iOS/phyphox/Settings.bundle/bn.lproj/Root.strings b/phyphox-iOS/phyphox/Settings.bundle/bn.lproj/Root.strings new file mode 100644 index 0000000000000000000000000000000000000000..ed60b89c47319bc2c37b5775c777dddd6a16d56b GIT binary patch literal 4 LcmezWkBb2S2Mz)V literal 0 HcmV?d00001 From a6b43a94ef6dc327ee4817be7d4ab4c0f8406815 Mon Sep 17 00:00:00 2001 From: Ananda Dasgupta Date: Mon, 4 Dec 2023 16:46:44 +0000 Subject: [PATCH 14/81] Translated using Weblate (Bengali) Currently translated at 18.4% (63 of 342 strings) Translation: phyphox/iOS Translate-URL: https://translate.phyphox.org/projects/phyphox/ios/bn/ --- .../phyphox/bn.lproj/Localizable.strings | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/phyphox-iOS/phyphox/bn.lproj/Localizable.strings b/phyphox-iOS/phyphox/bn.lproj/Localizable.strings index 8b137891..e397fd06 100644 --- a/phyphox-iOS/phyphox/bn.lproj/Localizable.strings +++ b/phyphox-iOS/phyphox/bn.lproj/Localizable.strings @@ -1 +1,66 @@ + +"app_name" = "ফিফক্স"; +"action_settings" = "সেটিংস"; +"title_activity_experiment" = "ফিফক্স"; +"newExperimentSimple" = "সহজ পরীক্ষা যোগ করুন"; +"newExperimentPhyphoxInfo" = "আরো উন্নতমানের পরীক্ষা বা ডাটা বিশ্লেষণ যোগ করতে হলে phyphox.org সাইটে যান। আপনার সহকর্মী, বন্ধু আর ছাত্রদের সাথে ভাগ করে নিন।"; +"newExperimentInputTitle" = "শীর্ষক"; +"newExperimentInputBufferSize" = "বাফারের আকার (যতগুলি রাশি সংগ্রহ করা হবে তার সংখ্যা)"; +"newExperimentInputRate" = "সেন্সরের হার (rate) (হার্ৎসে, 0 = যতো বেশি সম্ভব"; +"newExperimentInputSensors" = "সক্রিয় সেন্সর"; +"warning" = "সাবধান"; +"donotshowagain" = "পুনরায় দেখাবেন না"; +"damageWarning" = "পরীক্ষা করার সময় যাতে আপনার ফোনের কোনো রকম ক্ষতি না হয় সেদিকে দৃষ্টি দেবেন। বিশেষ করে খেয়াল রাখবেন যেন ফোন কোনো শক্ত জায়গায় না পড়ে বা অতিরিক্ত প্রাবল্যের চৌম্বক ক্ষেত্রের সংস্পর্শে না আসে। আপনার ফোনের কোনো ক্ষতির জন্য ফিফক্স প্রণেতারা দায়ী থাকবেন না।"; +"info" = "তথ্য"; +"credits" = "স্বীকৃতি"; +"creditsNames" = "মূল ধারণা এবং প্রস্তুতি :\nডাঃ. সেবাস্টিয়ান স্ট্যাকস\nমূল ধারণা:\nপ্রফেসর ক্রিস্টোফ স্ট্যাম্পফার\nপ্রোগ্রামিং:\nগৌরব ত্রিপাঠী\nডমিনিক ডরসেল\nজোনাস গেসনার\nক্যামিলা লুমারঝেইম"; +"help" = "তথ্য এবং সহায়তা"; +"experimentsPhyphoxOrg" = "পরীক্ষার ধারণা ও প্রণালী"; +"experimentsPhyphoxOrgURL" = "http://phyphox.org/experiments"; +"faqPhyphoxOrg" = "প্রায়শই জিজ্ঞাসা করা প্রশ্ন"; +"faqPhyphoxOrgURL" = "http://phyphox.org/faq"; +"remotePhyphoxOrg" = "রিমোট কন্ট্রোল সংক্রান্ত সহায়তা"; +"remotePhyphoxOrgURL" = "http://phyphox.org/remote-control"; +"deviceInfo" = "ডিভাইসের বিবরণ"; +"copyToClipboard" = "ক্লিপবোর্ডে কপি করুন"; +"deviceInfoCopied" = "ডিভাইস সম্বন্ধে তথ্য আপনার ক্লিপবোর্ডে কপি করা হয়েছে।"; +"graph_tools_pan_and_zoom" = "আঙুল দিয়ে জুম"; +"graph_tools_pick" = "ডাটা নেওয়া"; +"graph_tools_more" = "আরো টুলস"; +"graph_tools_system_time" = "সিস্টেমের সময়ে পাল্টান"; +"graph_tools_reset" = "জুম রিসেট করুন"; +"graph_tools_follow" = "নতুন ডাটা ফলো করুন"; +"graph_tools_linear_fit" = "রৈখিক ফিট"; +"graph_tools_export" = "এই ডেটাসেট রপ্তানি করুন"; +"graph_tools_log_x" = "লগারিদমিক x অক্ষ"; +"graph_tools_log_y" = "লগারিদমিক y অক্ষ"; +"graph_point_label" = "বিন্দু"; +"graph_slope_label" = "নতি"; +"graph_difference_label" = "তফাত"; +"graph_fit_label" = "রৈখিক ফিট: y = a x + b"; +"applyZoomTitle" = "জুম করুন"; +"applyZoomExplanation" = "আপনি ইন্টার‍্যাক্টিভ মোড ছেড়ে বেরোচ্ছেন। আপনি চাইলে বর্তমান জুম স্তর বজায় রাখতে পারেন।"; +"applyZoomAdvanced" = "উন্নত (Advanced) বিকল্প"; +"applyZoomApply" = "প্রয়োগ করুন…"; +"applyZoomReset" = "ডিফল্টে রিসেট"; +"applyZoomKeep" = "পছন্দ হিসেবে রাখুন"; +"applyZoomFollow" = "নতুন ডাটা রাখুন আর তাকে অনুসরণ করুন"; +"applyZoomThis" = "কেবল এই গ্রাফ"; +"applyZoomSameVariable" = "অন্য গ্রাফেওঃ একই চলরাশি (variable)"; +"applyZoomSameUnit" = "অন্য গ্রাফেঃ একই একক"; +"applyZoomSameAxis" = "অন্য গ্রাফেঃ একই অক্ষ"; +"newExperimentBluetooth" = "ব্লু-টুথ যন্ত্রের জন্য পরীক্ষা যোগ করুন"; +"newExperimentBluetoothErrorTitle" = "ব্লু-টুথ স্ক্যানে ত্রুটি"; +"newExperimentBluetoothLoadFromDevice" = "যন্ত্র থেকে লোড করুন"; +"newExperimentQR" = "QR কোড থেকে পরীক্ষা যোগ করুন"; +"newExperimentQRscan" = "ফিফক্স পরীক্ষা সম্বলিত QR কোড স্ক্যান করুন।"; +"newExperimentQRErrorTitle" = "QR কোড ত্রুটি।"; +"newExperimentQRNoExperiment" = "ফিফক্স পরীক্ষা সম্বলিত QR কোড পাওয়া যায় নি।"; +"newExperimentQRcrcMismatch" = "আপনি এখন যে QR কোড স্ক্যান করেছেন সেটি আগের কোডের সাথে মিলছে না। দয়া করে একই পরীক্ষা সংক্রান্ত QR কোড স্ক্যান করুন।"; +"newExperimentQRCodesMissing1" = "যে QR কোডটি আপনি এখন স্ক্যান করেছেন সেটি একটি কোডের"; +"newExperimentQRCodesMissing2" = "সংগ্রহের অংশ। বাকি কোডের জন্য \"এগিয়ে যান\" দাবান। অবশিষ্ট কোড:"; +"newExperimentQRBadCRC" = "অবৈধ চেকসাম। এর অর্থ হয় QR কোডটি খারাপ নাহয় স্ক্যান করার সময় কিছু সমস্যা হয়েছে।"; +"newExperimentBTReadErrorTitle" = "ব্লুটুথ পরীক্ষায় ত্রুটি।"; +"newExperimentBTReadErrorCorrupted" = "প্রেরিত পরীক্ষার ডাটা বিকৃত হয়েছে।"; +"generalErrorDialog" = "কোনো সমস্যা হয়েছে।"; From 36bc4a10a6f4db90636625dc53f3c71bb85f90ae Mon Sep 17 00:00:00 2001 From: Bidyut Ghosh Date: Tue, 5 Dec 2023 15:08:57 +0000 Subject: [PATCH 15/81] Translated using Weblate (Bengali) Currently translated at 21.6% (74 of 342 strings) Translation: phyphox/iOS Translate-URL: https://translate.phyphox.org/projects/phyphox/ios/bn/ --- phyphox-iOS/phyphox/bn.lproj/Localizable.strings | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/phyphox-iOS/phyphox/bn.lproj/Localizable.strings b/phyphox-iOS/phyphox/bn.lproj/Localizable.strings index e397fd06..52567474 100644 --- a/phyphox-iOS/phyphox/bn.lproj/Localizable.strings +++ b/phyphox-iOS/phyphox/bn.lproj/Localizable.strings @@ -20,7 +20,7 @@ "experimentsPhyphoxOrgURL" = "http://phyphox.org/experiments"; "faqPhyphoxOrg" = "প্রায়শই জিজ্ঞাসা করা প্রশ্ন"; "faqPhyphoxOrgURL" = "http://phyphox.org/faq"; -"remotePhyphoxOrg" = "রিমোট কন্ট্রোল সংক্রান্ত সহায়তা"; +"remotePhyphoxOrg" = "রিমোট কন্ট্রোল সহায়তা"; "remotePhyphoxOrgURL" = "http://phyphox.org/remote-control"; "deviceInfo" = "ডিভাইসের বিবরণ"; "copyToClipboard" = "ক্লিপবোর্ডে কপি করুন"; @@ -64,3 +64,14 @@ "newExperimentBTReadErrorTitle" = "ব্লুটুথ পরীক্ষায় ত্রুটি।"; "newExperimentBTReadErrorCorrupted" = "প্রেরিত পরীক্ষার ডাটা বিকৃত হয়েছে।"; "generalErrorDialog" = "কোনো সমস্যা হয়েছে।"; +"sensorLinearAcceleration" = "রৈখিক ত্বরণ"; +"sensorLight" = "লাইট"; +"sensorGyroscope" = "জাইরোস্কোপ"; +"sensorAccelerometer" = "ত্বরণমাপি"; +"sensorMagneticField" = "চুম্বক ক্ষেত্র"; +"sensorPressure" = "চাপ"; +"sensorProximity" = "নৈকট্য"; +"sensorTemperature" = "তাপমাত্রা"; +"sensorHumidity" = "আদ্রতা"; +"sensorAttitude" = "দেহভঙ্গিমা"; +"sensorGravity" = "গুরুত্ব"; From e666ceaa0ffad8acffb1256cac9e7827a8b87ec6 Mon Sep 17 00:00:00 2001 From: Gaurav Tripathee Date: Wed, 6 Sep 2023 09:46:00 +0200 Subject: [PATCH 16/81] Show battery info when connected with the bluetooth devices (#12) * feat: show connected device information while experiment loaded from the bluetooth device * fix showing battery level eeventhougth there is no battery service * refactor: change the name dependecy in bluetooth connected device * ui: fix the graph border color when experiment opened from connected bluetooth device * refactor: remove extra if condition in updating connected bluetooth device --- phyphox-iOS/phyphox.xcodeproj/project.pbxproj | 6 +- .../cellular_level_0.imageset/Contents.json | 12 ++ .../cellularbars.level.0.svg | 14 ++ .../cellular_level_1.imageset/Contents.json | 12 ++ .../cellularbars.level.1.svg | 14 ++ .../cellular_level_2.imageset/Contents.json | 12 ++ .../cellularbars.level.2.svg | 14 ++ .../cellular_level_3.imageset/Contents.json | 12 ++ .../cellularbars.level.3.svg | 14 ++ .../cellular_level_4.imageset/Contents.json | 12 ++ .../cellularbars.svg | 11 ++ .../phyphox/Experiments/BluetoothDevice.swift | 128 ++++++++++++- phyphox-iOS/phyphox/Info.plist | 2 +- ...etoothScanResultsTableViewController.swift | 41 +++-- ...nnectedBluetoothDeviceViewController.swift | 174 ++++++++++++++++++ .../ExperimentPageViewController.swift | 76 ++++++-- .../ViewModules/Dynamic/ApplyZoomDialog.swift | 3 + .../Dynamic/ExperimentGraphView.swift | 2 +- .../Dynamic/Graph/Grid/GraphGridView.swift | 27 ++- .../Image.imageset/Contents.json | 20 ++ .../backgroundDark.colorset/Contents.json | 50 +++++ 21 files changed, 613 insertions(+), 43 deletions(-) create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/cellular_level_0.imageset/Contents.json create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/cellular_level_0.imageset/cellularbars.level.0.svg create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/cellular_level_1.imageset/Contents.json create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/cellular_level_1.imageset/cellularbars.level.1.svg create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/cellular_level_2.imageset/Contents.json create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/cellular_level_2.imageset/cellularbars.level.2.svg create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/cellular_level_3.imageset/Contents.json create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/cellular_level_3.imageset/cellularbars.level.3.svg create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/cellular_level_4.imageset/Contents.json create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/cellular_level_4.imageset/cellularbars.svg create mode 100644 phyphox-iOS/phyphox/UI/MainView/ExperimentView/ConnectedBluetoothDeviceViewController.swift create mode 100644 phyphox-iOS/phyphox/color.xcassets/Image.imageset/Contents.json create mode 100644 phyphox-iOS/phyphox/color.xcassets/backgroundDark.colorset/Contents.json diff --git a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj index 3a0b2faa..c4c03017 100644 --- a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj +++ b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -156,6 +156,7 @@ 6BF82BC82147FF6F00175659 /* SemanticVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BF82BC72147FF6F00175659 /* SemanticVersion.swift */; }; 6BFE372C20442AB400301CA3 /* KeyboardTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BFE372B20442AB400301CA3 /* KeyboardTracker.swift */; }; 866632E92A29E1D90068762D /* UIColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 866632E82A29E1D90068762D /* UIColorExtensions.swift */; }; + A101AB0829BB801600B82311 /* ConnectedBluetoothDeviceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A101AB0729BB801600B82311 /* ConnectedBluetoothDeviceViewController.swift */; }; A1A6A72F297685DF00090309 /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1A6A72E297685DF00090309 /* Utility.swift */; }; A1A80D3D29AE049E00AD756D /* SettingsBundleHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1A80D3C29AE049E00AD756D /* SettingsBundleHelper.swift */; }; A1E03A7F29B61A9000CE93A6 /* UIImageExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1E03A7E29B61A9000CE93A6 /* UIImageExt.swift */; }; @@ -513,6 +514,7 @@ 86C183D029F131BC0089E396 /* hi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hi; path = hi.lproj/InfoPlist.strings; sourceTree = ""; }; 86C183D129F132020089E396 /* ka */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ka; path = ka.lproj/Localizable.strings; sourceTree = ""; }; 86C183D229F132020089E396 /* ka */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ka; path = ka.lproj/InfoPlist.strings; sourceTree = ""; }; + A101AB0729BB801600B82311 /* ConnectedBluetoothDeviceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedBluetoothDeviceViewController.swift; sourceTree = ""; }; A1A6A72E297685DF00090309 /* Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = ""; }; A1A80D3C29AE049E00AD756D /* SettingsBundleHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsBundleHelper.swift; sourceTree = ""; }; A1E03A7E29B61A9000CE93A6 /* UIImageExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageExt.swift; sourceTree = ""; }; @@ -966,6 +968,7 @@ 6B26F6FC1CAAEDFB00EA9367 /* ExperimentExportSetSelectionView.swift */, C07BBC1E26209CAC0097C57A /* ExperimentTimedRunDialogView.swift */, 6B2E0F9E20593A01008FAA91 /* ViewModules */, + A101AB0729BB801600B82311 /* ConnectedBluetoothDeviceViewController.swift */, ); path = ExperimentView; sourceTree = ""; @@ -1686,6 +1689,7 @@ C0AE8A982294558000A3EC48 /* ConfigConversion.swift in Sources */, C0A97FF92477E39000A630EE /* InterpolateAnalysis.swift in Sources */, 6B612FE9207E5ABC001DA8D9 /* LinkElementHandler.swift in Sources */, + A101AB0829BB801600B82311 /* ConnectedBluetoothDeviceViewController.swift in Sources */, C05CD336258BA0DF00662BDF /* ExperimentTimeReference.swift in Sources */, 6B7BF7F81C1374A6007408FB /* ExperimentAnalysisModule.swift in Sources */, 6B7BF8001C139649007408FB /* DivisionAnalysis.swift in Sources */, diff --git a/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_0.imageset/Contents.json b/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_0.imageset/Contents.json new file mode 100644 index 00000000..26191d1d --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_0.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "cellularbars.level.0.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_0.imageset/cellularbars.level.0.svg b/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_0.imageset/cellularbars.level.0.svg new file mode 100644 index 00000000..0329b71a --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_0.imageset/cellularbars.level.0.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_1.imageset/Contents.json b/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_1.imageset/Contents.json new file mode 100644 index 00000000..ff377dac --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_1.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "cellularbars.level.1.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_1.imageset/cellularbars.level.1.svg b/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_1.imageset/cellularbars.level.1.svg new file mode 100644 index 00000000..6e423b83 --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_1.imageset/cellularbars.level.1.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_2.imageset/Contents.json b/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_2.imageset/Contents.json new file mode 100644 index 00000000..6274f66f --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_2.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "cellularbars.level.2.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_2.imageset/cellularbars.level.2.svg b/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_2.imageset/cellularbars.level.2.svg new file mode 100644 index 00000000..60ce3bdb --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_2.imageset/cellularbars.level.2.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_3.imageset/Contents.json b/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_3.imageset/Contents.json new file mode 100644 index 00000000..09e7a299 --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_3.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "cellularbars.level.3.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_3.imageset/cellularbars.level.3.svg b/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_3.imageset/cellularbars.level.3.svg new file mode 100644 index 00000000..31c73083 --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_3.imageset/cellularbars.level.3.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_4.imageset/Contents.json b/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_4.imageset/Contents.json new file mode 100644 index 00000000..8fb9572c --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_4.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "cellularbars.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_4.imageset/cellularbars.svg b/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_4.imageset/cellularbars.svg new file mode 100644 index 00000000..2701cd8f --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/cellular_level_4.imageset/cellularbars.svg @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/phyphox-iOS/phyphox/Experiments/BluetoothDevice.swift b/phyphox-iOS/phyphox/Experiments/BluetoothDevice.swift index 23120d04..645e5adc 100644 --- a/phyphox-iOS/phyphox/Experiments/BluetoothDevice.swift +++ b/phyphox-iOS/phyphox/Experiments/BluetoothDevice.swift @@ -14,6 +14,7 @@ let phyphoxServiceUUID: UUID = UUID(uuidString: "cddf0001-30f7-4671-8b43-5e40ba5 let phyphoxExperimentCharacteristicUUID: UUID = UUID(uuidString: "cddf0002-30f7-4671-8b43-5e40ba53514a")! let phyphoxExperimentControlCharacteristicUUID: UUID = UUID(uuidString: "cddf0003-30f7-4671-8b43-5e40ba53514a")! let phyphoxEventCharacteristicUUID: UUID = UUID(uuidString: "cddf0004-30f7-4671-8b43-5e40ba53514a")! +let batteryServiceUUID: String = "2A19" public extension CBUUID { convenience init(uuidString: String) throws { @@ -76,6 +77,10 @@ protocol BluetoothDeviceDelegate { func dataFromCharacteristic(uuid: CBUUID, data: Data) } +protocol UpdateConnectedDeviceDelegate { + func showUpdatedConnectedDevices(connectedDevice: [ConnectedDevicesDataModel]) +} + enum BluetoothDeviceError: Error { case generic(String) } @@ -100,6 +105,26 @@ class ExperimentBluetoothDevice: BluetoothScan, DeviceIsChosenDelegate { let hud: JGProgressHUD var feedbackViewController: UIViewController? + static var updateDelegate: UpdateConnectedDeviceDelegate? + + var batteryLevel = -1 + var connectedDeviceName: String = "" + var signalLevel = 100 + var connectedDevices: [ConnectedDevicesDataModel] = [ConnectedDevicesDataModel]() + + init(delegate: UpdateConnectedDeviceDelegate) { + ExperimentBluetoothDevice.updateDelegate = delegate + + self.id = "" + self.deviceName = "" + self.advertiseUUID = nil + + self.hud = JGProgressHUD(style: .dark) + + super.init(scanDirectly: false, filterByName: "", filterByUUID: nil, checkExperiments: false, autoConnect: false) + } + + init(id: String?, name: String?, uuid: CBUUID?, autoConnect: Bool) { self.id = id self.deviceName = name @@ -129,7 +154,7 @@ class ExperimentBluetoothDevice: BluetoothScan, DeviceIsChosenDelegate { let alert = UIAlertController(title: localize("warning"), message: msg, preferredStyle: .alert) alert.addAction(UIAlertAction(title: localize("cancel"), style: .default, handler: { _ in - + })) if retry { alert.addAction(UIAlertAction(title: localize("tryagain"), style: .default, handler: { _ in @@ -288,7 +313,7 @@ class ExperimentBluetoothDevice: BluetoothScan, DeviceIsChosenDelegate { override func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { print("Connected: \(peripheral.name ?? "No Name")") - + peripheral.readRSSI() peripheral.discoverServices(nil) after(10) { if self.characteristics_map.count == 0 { @@ -299,6 +324,36 @@ class ExperimentBluetoothDevice: BluetoothScan, DeviceIsChosenDelegate { } } + func peripheral(_ peripheral: CBPeripheral,didReadRSSI RSSI: NSNumber, error: Error?){ + signalLevel = Int(RSSI) + connectedDeviceName = peripheral.name ?? "" + let connectedDevice = ConnectedDevicesDataModel(deviceIdentifier: peripheral.identifier, + signalStrength: signalLevel, + batteryLabel: batteryLevel, + deviceName: connectedDeviceName) + + // append or update the list as per the current state + if(connectedDevices.isEmpty){ + connectedDevices.append(connectedDevice) + + } else { + if(connectedDevices.filter { $0.getDeviceIdentifier() == peripheral.identifier}.isEmpty){ + // if the current device name is not in list than add it to the list + connectedDevices.append(connectedDevice) + } else{ + // Get row index of the current device info to update its elements + if let row = connectedDevices.firstIndex(where: {$0.getDeviceIdentifier() == peripheral.identifier}){ + connectedDevices[row] = connectedDevice + } + } + } + + ExperimentBluetoothDevice + .updateDelegate? + .showUpdatedConnectedDevices(connectedDevice: connectedDevices) + + } + override func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { self.disconnect() self.showError(msg: localize("bt_exception_connection")) @@ -365,8 +420,19 @@ class ExperimentBluetoothDevice: BluetoothScan, DeviceIsChosenDelegate { } override func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { + peripheral.readRSSI() if let newData = characteristic.value { for delegate in delegates { + if(characteristic.uuid.uuidString == batteryServiceUUID){ + guard let newBatteryLevel = characteristic.value?.first else { + return + } + if(batteryLevel != newBatteryLevel) { + batteryLevel = Int(newBatteryLevel) + peripheral.readValue(for: characteristic) + } + + } delegate.dataFromCharacteristic(uuid: characteristic.uuid, data: newData) } } @@ -374,10 +440,10 @@ class ExperimentBluetoothDevice: BluetoothScan, DeviceIsChosenDelegate { func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { /* We ignore errors because it seems possible that devices do not set up a proper config descriptor while still working as expected. - if let error = error { - self.disconnect() - self.showError(msg: localize("bt_exception_notification_fail_enable") + " \(characteristic.uuid) " + localize("bt_exception_notification_fail") + " (\(error.localizedDescription))") - } + if let error = error { + self.disconnect() + self.showError(msg: localize("bt_exception_notification_fail_enable") + " \(characteristic.uuid) " + localize("bt_exception_notification_fail") + " (\(error.localizedDescription))") + } */ } @@ -393,9 +459,18 @@ class ExperimentBluetoothDevice: BluetoothScan, DeviceIsChosenDelegate { } for characteristic in characteristics { + if characteristic.uuid.uuid128String == phyphoxEventCharacteristicUUID.uuidString { eventCharacteristic = characteristic } + + if(characteristic.uuid.uuidString == batteryServiceUUID){ + //Battery Label + peripheral.readValue(for: characteristic) + + } + + characteristics_map[characteristic.uuid.uuid128String] = characteristic } @@ -405,10 +480,10 @@ class ExperimentBluetoothDevice: BluetoothScan, DeviceIsChosenDelegate { } print("Service discovery completed. Writing config data to device.") - + do { writeEventCharacteristic(timeMapping: nil) - + for delegate in delegates { try delegate.writeConfigData() } @@ -473,3 +548,40 @@ class ExperimentBluetoothDevice: BluetoothScan, DeviceIsChosenDelegate { } +class ConnectedDevicesDataModel { + private var deviceIdentifier: UUID? + private var signalStrength: Int? + private var batteryLabel: Int? + private var deviceName: String? + + init(deviceIdentifier: UUID, signalStrength: Int, batteryLabel: Int, deviceName: String) { + self.deviceIdentifier = deviceIdentifier + self.signalStrength = signalStrength + self.batteryLabel = batteryLabel + self.deviceName = deviceName + } + + init(signalStrength: Int) { + self.signalStrength = signalStrength + } + + func getDeviceIdentifier() -> UUID { + return deviceIdentifier ?? UUID() + } + + func getSignalStrength() -> Int{ + return signalStrength ?? 100 + } + + func getBatteryLabel() -> Int{ + return batteryLabel ?? 100 + } + + func getDeviceName() -> String{ + return deviceName ?? "" + } + +} + + + diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index d6be274b..74f9157f 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11284 + 11111 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace diff --git a/phyphox-iOS/phyphox/UI/MainView/BluetoothScanResultsTableViewController.swift b/phyphox-iOS/phyphox/UI/MainView/BluetoothScanResultsTableViewController.swift index dc6d3b41..2dd13508 100644 --- a/phyphox-iOS/phyphox/UI/MainView/BluetoothScanResultsTableViewController.swift +++ b/phyphox-iOS/phyphox/UI/MainView/BluetoothScanResultsTableViewController.swift @@ -34,15 +34,19 @@ class BluetoothScanResultsTableViewController: UITableViewController, ScanResult init(filterByName: String?, filterByUUID: CBUUID?, checkExperiments: Bool, autoConnect: Bool) { ble = BluetoothScan(scanDirectly: true, filterByName: filterByName, filterByUUID: filterByUUID, checkExperiments: checkExperiments, autoConnect: autoConnect) - + signalImages = [] super.init(style: .plain) for i in 0..<5 { - self.showTheSignalImageByAdjustingWithAppMode(i: i) + if #available(iOS 13.0, *) { + signalImages.append(BluetoothScanResultsTableViewController.showTheSignalImageByAdjustingWithAppMode(i: i)) + } else { + signalImages.append(UIImage(named: "cellular_level_\(i)")!) + } } - + ble.scanResultsDelegate = self } @@ -63,7 +67,7 @@ class BluetoothScanResultsTableViewController: UITableViewController, ScanResult override func viewWillDisappear(_ animated: Bool) { ble.stopScan() } - + override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } @@ -125,24 +129,21 @@ class BluetoothScanResultsTableViewController: UITableViewController, ScanResult } } - func showTheSignalImageByAdjustingWithAppMode(i: Int){ - if #available(iOS 13.0, *) { - if(SettingBundleHelper.getAppMode() == Utility.LIGHT_MODE){ - signalImages.append((UIImage(named: "bluetooth_signal_\(i)")?.withTintColor(.black, renderingMode: .alwaysOriginal))!) - } else if(SettingBundleHelper.getAppMode() == Utility.DARK_MODE){ - signalImages.append(UIImage(named: "bluetooth_signal_\(i)")!) + @available(iOS 13.0, *) + static func showTheSignalImageByAdjustingWithAppMode(i: Int) -> UIImage{ + if(SettingBundleHelper.getAppMode() == Utility.LIGHT_MODE){ + return (UIImage(named: "cellular_level_\(i)")?.withTintColor(.black, renderingMode: .alwaysOriginal))! + } else if(SettingBundleHelper.getAppMode() == Utility.DARK_MODE){ + return (UIImage(named: "cellular_level_\(i)")?.withTintColor(.white, renderingMode: .alwaysOriginal))! + } else { + if(UIScreen.main.traitCollection.userInterfaceStyle == .light){ + return (UIImage(named: "cellular_level_\(i)")?.withTintColor(.black, renderingMode: .alwaysOriginal))! } else { - if(UIScreen.main.traitCollection.userInterfaceStyle == .light){ - signalImages.append((UIImage(named: "bluetooth_signal_\(i)")?.withTintColor(.black, renderingMode: .alwaysOriginal))!) - } else { - signalImages.append(UIImage(named: "bluetooth_signal_\(i)")!) - } - + return (UIImage(named: "cellular_level_\(i)")?.withTintColor(.white, renderingMode: .alwaysOriginal))! } - } - else { - signalImages.append(UIImage(named: "bluetooth_signal_\(i)")!) + } } - + + } diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ConnectedBluetoothDeviceViewController.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ConnectedBluetoothDeviceViewController.swift new file mode 100644 index 00000000..5a2f9b0b --- /dev/null +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ConnectedBluetoothDeviceViewController.swift @@ -0,0 +1,174 @@ +// +// ConnectedBluetoothDeviceViewController.swift +// phyphox +// +// Created by Gaurav Tripathee on 10.03.23. +// Copyright © 2023 RWTH Aachen. All rights reserved. +// + +import Foundation + +class ConnectedBluetoothDevicesViewController: UICollectionView, UICollectionViewDataSource { + + private var data: [ConnectedDevicesDataModel] = [ConnectedDevicesDataModel]() + let cellIdentifier: String = "ConnectedBleDeviceCell" + + init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout, data: [ConnectedDevicesDataModel]) { + super.init(frame: frame, collectionViewLayout: layout) + self.register(ConnectedBleDeviceCell.self, forCellWithReuseIdentifier: cellIdentifier) + self.dataSource = self + self.data = data + layout.collectionView?.backgroundColor = UIColor(named: "backgroundDark") + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return data.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier , for: indexPath) as? ConnectedBleDeviceCell else { + return UICollectionViewCell() + } + + cell.contentView.backgroundColor = UIColor(named: "backgroundDark") + + let dataModel: ConnectedDevicesDataModel = ConnectedDevicesDataModel( + deviceIdentifier:data[indexPath.row].getDeviceIdentifier(), + signalStrength:data[indexPath.row].getSignalStrength(), + batteryLabel: data[indexPath.row].getBatteryLabel(), + deviceName: data[indexPath.row].getDeviceName()) + + cell.configure(model: dataModel) + + return cell + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + // handle cell selection + } + +} + +class ConnectedBleDeviceCell: UICollectionViewCell { + + let batteryImageView = UIImageView() + let signalImageView = UIImageView() + let deviceLabel = UILabel() + + override init(frame: CGRect) { + super.init(frame: frame) + + + deviceLabel.font = UIFont.systemFont(ofSize: 14) + deviceLabel.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(deviceLabel) + NSLayoutConstraint.activate([ + deviceLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8), + deviceLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), + deviceLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8), + ]) + + batteryImageView.contentMode = .scaleAspectFit + + batteryImageView.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(batteryImageView) + NSLayoutConstraint.activate([ + batteryImageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8), + batteryImageView.leadingAnchor.constraint(equalTo: deviceLabel.trailingAnchor, constant: 8), + + batteryImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8) + ]) + + signalImageView.contentMode = .scaleAspectFit + signalImageView.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(signalImageView) + NSLayoutConstraint.activate([ + signalImageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8), + signalImageView.leadingAnchor.constraint(equalTo: batteryImageView.trailingAnchor, constant: 8), + signalImageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16), + signalImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8) + ]) + + } + + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @available(iOS 13.0, *) + func getSignal(rssi: Int) -> UIImage{ + + if rssi > -35 { + return (UIImage(named: "cellular_level_4")?.withTintColor(.black, renderingMode: .alwaysOriginal))! + } else if rssi > -55 { + return (UIImage(named: "cellular_level_3")?.withTintColor(.black, renderingMode: .alwaysOriginal))! + } else if rssi > -70 { + return (UIImage(named: "cellular_level_2")?.withTintColor(.black, renderingMode: .alwaysOriginal))! + } else if rssi > -90 { + return (UIImage(named: "cellular_level_1")?.withTintColor(.black, renderingMode: .alwaysOriginal))! + } else { + return (UIImage(named: "cellular_level_0")?.withTintColor(.black, renderingMode: .alwaysOriginal))! + } + + + } + + @available(iOS 13.0, *) + func getBatteryLevel(level: Int) -> UIImage{ + let config = UIImage.SymbolConfiguration( + pointSize: 25, weight: .medium, scale: .default) + + if level == -1 { + return UIImage() + + } else if level < 5 { + return UIImage(systemName: "battery.0", withConfiguration: config)! + } + else if level < 25 { + return UIImage(systemName: "battery.25", withConfiguration: config)! + } else if level < 50 { + return UIImage(systemName: "battery.50", withConfiguration: config)! + } else if level < 75 { + return UIImage(systemName: "battery.75", withConfiguration: config)! + } else if level < 100 { + return UIImage(systemName: "battery.100", withConfiguration: config)! + } else{ + return UIImage(systemName: "battery.100", withConfiguration: config)! + } + + } + + + @available(iOS 13.0, *) + func getImageAsDeviceMode(image: UIImage) -> UIImage{ + if(SettingBundleHelper.getAppMode() == Utility.LIGHT_MODE){ + return image.withTintColor(.black, renderingMode: .alwaysOriginal) + } else if(SettingBundleHelper.getAppMode() == Utility.DARK_MODE){ + return image.withTintColor(UIColor(white: 1.0, alpha: 1.0), renderingMode: .alwaysOriginal) + } else { + if(UIScreen.main.traitCollection.userInterfaceStyle == .light){ + return image.withTintColor(.black, renderingMode: .alwaysOriginal) + } else { + return image.withTintColor(UIColor(white: 1.0, alpha: 1.0), renderingMode: .alwaysOriginal) + } + } + } + + func configure(model: ConnectedDevicesDataModel){ + deviceLabel.text = model.getDeviceName() + if #available(iOS 13.0, *) { + signalImageView.image = getSignal(rssi: model.getSignalStrength()) + let getBatteryLevelImage = getBatteryLevel(level: model.getBatteryLabel()) + batteryImageView.image = getBatteryLevelImage.withTintColor(.black, renderingMode: .alwaysOriginal) + } else { + // Fallback on earlier versions + } + } + +} diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift index 69c15dd2..e5c32403 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift @@ -17,7 +17,8 @@ protocol ExportDelegate { protocol StopExperimentDelegate { func stopExperiment() } -final class ExperimentPageViewController: UIViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate, UIPopoverPresentationControllerDelegate, ExperimentWebServerDelegate, ExportDelegate, StopExperimentDelegate, BluetoothScanDialogDismissedDelegate, NetworkScanDialogDismissedDelegate, NetworkConnectionDataPolicyInfoDelegate { + +final class ExperimentPageViewController: UIViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate, UIPopoverPresentationControllerDelegate, ExperimentWebServerDelegate, ExportDelegate, StopExperimentDelegate, BluetoothScanDialogDismissedDelegate, NetworkScanDialogDismissedDelegate, NetworkConnectionDataPolicyInfoDelegate, UpdateConnectedDeviceDelegate{ var actionItem: UIBarButtonItem? var playItem: UIBarButtonItem? @@ -42,6 +43,10 @@ final class ExperimentPageViewController: UIViewController, UIPageViewController private let viewModules: [[UIView]] + let flowLayout = UICollectionViewFlowLayout() + + var numOfConnectedDevices = 0 + var timerRunning: Bool { return experimentRunTimer != nil } @@ -148,6 +153,16 @@ final class ExperimentPageViewController: UIViewController, UIPageViewController } } + + flowLayout.scrollDirection = .vertical + flowLayout.minimumLineSpacing = 10 + flowLayout.minimumInteritemSpacing = 10 + + flowLayout.itemSize = CGSize(width: self.view.frame.width, height: 40) + + ExperimentBluetoothDevice.updateDelegate = self + + self.navigationItem.title = experiment.displayTitle let backButton = UIBarButtonItem(title: "‹", style: .plain, target: self, action: #selector(leaveExperiment)) @@ -188,7 +203,6 @@ final class ExperimentPageViewController: UIViewController, UIPageViewController override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - if isMovingToParent { experiment.willBecomeActive { DispatchQueue.main.async { @@ -265,18 +279,35 @@ final class ExperimentPageViewController: UIViewController, UIPageViewController var pageViewControlerRect = CGRect(x: 0, y: offsetTop, width: self.view.frame.width, height: self.view.frame.height-offsetTop) + var adjustableHeightS1 = 0.0 + var adjustableHeightS2 = 0.0 + if( numOfConnectedDevices == 1){ + adjustableHeightS1 = 30.0 + adjustableHeightS2 = 20.0 + } else if(numOfConnectedDevices > 1){ + adjustableHeightS1 = 82.0 + adjustableHeightS2 = 72.0 + } + if let label = self.serverLabel, let labelBackground = self.serverLabelBackground { let s = label.sizeThatFits(CGSize(width: offsetFrame.width, height: 300)) - pageViewControlerRect = CGRect(origin: pageViewControlerRect.origin, size: CGSize(width: pageViewControlerRect.width, height: pageViewControlerRect.height-s.height-offsetBottom)) + pageViewControlerRect = CGRect(origin: pageViewControlerRect.origin, size: CGSize(width: pageViewControlerRect.width, height: pageViewControlerRect.height-s.height-offsetBottom - adjustableHeightS1)) - let labelBackgroundFrame = CGRect(x: 0, y: self.view.frame.height - s.height - offsetBottom, width: pageViewControlerRect.width, height: s.height + offsetBottom) - let labelFrame = CGRect(x: offsetFrame.minX, y: self.view.frame.height - s.height - offsetBottom, width: offsetFrame.width, height: s.height) + let labelBackgroundFrame = CGRect(x: 0, y: self.view.frame.height - s.height - offsetBottom - adjustableHeightS1 , width: pageViewControlerRect.width, height: s.height + offsetBottom - adjustableHeightS2) + let labelFrame = CGRect(x: offsetFrame.minX, y: self.view.frame.height - s.height - offsetBottom - adjustableHeightS2 , width: offsetFrame.width, height: s.height ) label.frame = labelFrame labelBackground.frame = labelBackgroundFrame label.autoresizingMask = [.flexibleTopMargin, .flexibleWidth] labelBackground.autoresizingMask = [.flexibleTopMargin, .flexibleWidth] } + if self.serverQRIcon != nil { + NSLayoutConstraint.activate([ + self.serverQRIcon!.trailingAnchor.constraint(equalTo: self.serverLabelBackground!.trailingAnchor, constant: -15.0), + self.serverQRIcon!.bottomAnchor.constraint(equalTo: self.serverLabelBackground!.bottomAnchor, constant: -20.0 ) + ]) + } + self.pageViewControler.view.frame = pageViewControlerRect } @@ -340,6 +371,8 @@ final class ExperimentPageViewController: UIViewController, UIPageViewController pageViewControler.setViewControllers([experimentViewControllers[0]], direction: .forward, animated: false, completion: nil) pageViewControler.view.autoresizingMask = [.flexibleHeight, .flexibleWidth] + + updateLayout() self.addChild(pageViewControler) @@ -349,6 +382,7 @@ final class ExperimentPageViewController: UIViewController, UIPageViewController updateSelectedViewCollection() + } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { @@ -631,11 +665,20 @@ final class ExperimentPageViewController: UIViewController, UIPageViewController self.serverLabel!.inputAccessoryView = UIView() self.serverQRIcon = UIButton(type: .system) - let image = UIImage(named: "new_experiment_qr")!.resize(size: CGSize(width: 30, height: 30)) + + var image = UIImage(named: "new_experiment_qr")!.resize(size: CGSize(width: 30, height: 30)) + if #available(iOS 13.0, *) { + let config = UIImage.SymbolConfiguration( + pointSize: 25, weight: .medium, scale: .default) + + image = UIImage(systemName: "info.circle.fill", withConfiguration: config)! + } else { + // Fallback on earlier versions + } + self.serverQRIcon?.setImage(image, for: .normal) self.serverQRIcon?.addTarget(self, action: #selector(showQr), for: .touchUpInside) self.serverQRIcon?.imageView?.contentMode = .scaleAspectFit - self.serverLabelBackground = UIView() self.serverLabelBackground!.backgroundColor = UIColor(named: "lightBackgroundColor") ?? kLightBackgroundColor @@ -645,15 +688,12 @@ final class ExperimentPageViewController: UIViewController, UIPageViewController // set view1 constraints self.serverQRIcon!.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - self.serverQRIcon!.trailingAnchor.constraint(equalTo: self.serverLabelBackground!.trailingAnchor, constant: -20.0), - self.serverQRIcon!.bottomAnchor.constraint(equalTo: self.serverLabelBackground!.bottomAnchor, constant: -25.0) - ]) updateLayout() } } + @objc func showQr(){ let data = remoteUrl.data(using: .utf8) @@ -1444,6 +1484,20 @@ final class ExperimentPageViewController: UIViewController, UIPageViewController // Fallback on earlier versions } } + + func showUpdatedConnectedDevices(connectedDevice: [ConnectedDevicesDataModel]) { + numOfConnectedDevices = connectedDevice.count + var adjustedHeight = 48.0 + if( numOfConnectedDevices == 0){ + return + } else if(numOfConnectedDevices > 1){ + adjustedHeight = 100.0 + } + + let customCollectionView = ConnectedBluetoothDevicesViewController(frame: CGRect(x: 0, y: self.view.frame.height - adjustedHeight, width: self.view.frame.width, height: adjustedHeight ), collectionViewLayout: flowLayout, data: connectedDevice) + view.addSubview(customCollectionView) + + } } extension ExperimentPageViewController: ExperimentAnalysisDelegate { diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Dynamic/ApplyZoomDialog.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Dynamic/ApplyZoomDialog.swift index 6a0171ca..0bcb3a24 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Dynamic/ApplyZoomDialog.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Dynamic/ApplyZoomDialog.swift @@ -229,6 +229,7 @@ class ApplyZoomDialog: UIViewController, UITableViewDataSource, UITableViewDeleg let titleView = UILabel() titleView.translatesAutoresizingMaskIntoConstraints = false titleView.text = localize("applyZoomTitle") + titleView.textColor = UIColor(named: "textColor") titleView.font = UIFont.preferredFont(forTextStyle: .headline) dialogView.addSubview(titleView) @@ -244,6 +245,7 @@ class ApplyZoomDialog: UIViewController, UITableViewDataSource, UITableViewDeleg let explView = UILabel() explView.translatesAutoresizingMaskIntoConstraints = false explView.text = localize("applyZoomExplanation") + explView.textColor = UIColor(named: "textColor") explView.lineBreakMode = .byWordWrapping explView.numberOfLines = 0 scrollContentView.addSubview(explView) @@ -266,6 +268,7 @@ class ApplyZoomDialog: UIViewController, UITableViewDataSource, UITableViewDeleg let advancedSwitchLabel = UILabel() advancedSwitchLabel.translatesAutoresizingMaskIntoConstraints = false advancedSwitchLabel.text = localize("applyZoomAdvanced") + advancedSwitchLabel.textColor = UIColor(named: "textColor") dialogView.addSubview(advancedSwitchLabel) let okButton = UIButton() diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Dynamic/ExperimentGraphView.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Dynamic/ExperimentGraphView.swift index 826df7ea..5736c035 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Dynamic/ExperimentGraphView.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Dynamic/ExperimentGraphView.swift @@ -287,7 +287,7 @@ final class ExperimentGraphView: UIView, DynamicViewModule, ResizableViewModule, } if #available(iOS 13.0, *) { let config = UIImage.SymbolConfiguration( - pointSize: 25, weight: .medium, scale: .default) + pointSize: 25, weight: .regular, scale: .default) unfoldLessImageView = UIImageView(image: UIImage(systemName: "arrow.down.right.and.arrow.up.left", withConfiguration: config)) unfoldMoreImageView = UIImageView(image: UIImage(systemName: "arrow.up.left.and.arrow.down.right", withConfiguration: config)) diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Dynamic/Graph/Grid/GraphGridView.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Dynamic/Graph/Grid/GraphGridView.swift index 6773b646..77b466c9 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Dynamic/Graph/Grid/GraphGridView.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Dynamic/Graph/Grid/GraphGridView.swift @@ -19,6 +19,16 @@ final class GraphGridView: UIView { var isZScale: Bool = false + + override init(frame: CGRect) { + super.init(frame: frame) + + borderView.layer.borderColor = getAdjustedBorderColor() + borderView.layer.borderWidth = SettingBundleHelper.getGraphSettingWidth()/UIScreen.main.scale + + addSubview(borderView) + } + var gridInset: CGPoint = .zero { didSet { setNeedsLayout() @@ -358,11 +368,26 @@ final class GraphGridView: UIView { self.layoutSubviews() } + + private func getAdjustedBorderColor() -> CGColor{ + var borderColor = UIColor(white: 1.0, alpha: 1.0).cgColor + if(SettingBundleHelper.getAppMode() == Utility.LIGHT_MODE){ + borderColor = UIColor(white: 0.0, alpha: 1.0).cgColor + } else if(SettingBundleHelper.getAppMode() == Utility.SYSTEM_MODE){ + if #available(iOS 12.0, *) { + if(UIScreen.main.traitCollection.userInterfaceStyle == .light) { + borderColor = UIColor(white: 0.0, alpha: 1.0).cgColor + } + } + } + return borderColor + } + @objc func reloadBorderWidth(){ borderView.layer.borderWidth = SettingBundleHelper.getGraphSettingWidth()/UIScreen.main.scale self.layoutSubviews() self.setNeedsDisplay() } - + } diff --git a/phyphox-iOS/phyphox/color.xcassets/Image.imageset/Contents.json b/phyphox-iOS/phyphox/color.xcassets/Image.imageset/Contents.json new file mode 100644 index 00000000..a19a5492 --- /dev/null +++ b/phyphox-iOS/phyphox/color.xcassets/Image.imageset/Contents.json @@ -0,0 +1,20 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/phyphox-iOS/phyphox/color.xcassets/backgroundDark.colorset/Contents.json b/phyphox-iOS/phyphox/color.xcassets/backgroundDark.colorset/Contents.json new file mode 100644 index 00000000..863f7dbc --- /dev/null +++ b/phyphox-iOS/phyphox/color.xcassets/backgroundDark.colorset/Contents.json @@ -0,0 +1,50 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "gray-gamma-22", + "components" : { + "alpha" : "1.000", + "white" : "0.753" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "light" + } + ], + "color" : { + "color-space" : "gray-gamma-22", + "components" : { + "alpha" : "1.000", + "white" : "0.753" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "gray-gamma-22", + "components" : { + "alpha" : "1.000", + "white" : "0.110" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} From 2c81de845c9ebd6385516c5e89347310c1e9d5d6 Mon Sep 17 00:00:00 2001 From: Sebastian Staacks Date: Wed, 27 Sep 2023 15:35:26 +0200 Subject: [PATCH 17/81] Replace our CocoaMQTT fork with upstream version to fix incompatibility with iOS 16 --- phyphox-iOS/CocoaMQTT | 1 - phyphox-iOS/phyphox.xcodeproj/project.pbxproj | 98 ++++--------------- phyphox-iOS/phyphox/Info.plist | 2 +- 3 files changed, 20 insertions(+), 81 deletions(-) delete mode 160000 phyphox-iOS/CocoaMQTT diff --git a/phyphox-iOS/CocoaMQTT b/phyphox-iOS/CocoaMQTT deleted file mode 160000 index 0b5c50b9..00000000 --- a/phyphox-iOS/CocoaMQTT +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0b5c50b90d55e264ca837934395c3ec13929c32f diff --git a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj index c4c03017..bd0574f9 100644 --- a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj +++ b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj @@ -156,6 +156,7 @@ 6BF82BC82147FF6F00175659 /* SemanticVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BF82BC72147FF6F00175659 /* SemanticVersion.swift */; }; 6BFE372C20442AB400301CA3 /* KeyboardTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BFE372B20442AB400301CA3 /* KeyboardTracker.swift */; }; 866632E92A29E1D90068762D /* UIColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 866632E82A29E1D90068762D /* UIColorExtensions.swift */; }; + 867E2BD82AC464D000ABB472 /* CocoaMQTT in Frameworks */ = {isa = PBXBuildFile; productRef = 867E2BD72AC464D000ABB472 /* CocoaMQTT */; }; A101AB0829BB801600B82311 /* ConnectedBluetoothDeviceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A101AB0729BB801600B82311 /* ConnectedBluetoothDeviceViewController.swift */; }; A1A6A72F297685DF00090309 /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1A6A72E297685DF00090309 /* Utility.swift */; }; A1A80D3D29AE049E00AD756D /* SettingsBundleHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1A80D3C29AE049E00AD756D /* SettingsBundleHelper.swift */; }; @@ -204,7 +205,6 @@ C07B530D22942CC300BA085A /* InputConversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07B530C22942CC300BA085A /* InputConversion.swift */; }; C07BBC1F26209CAC0097C57A /* ExperimentTimedRunDialogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07BBC1E26209CAC0097C57A /* ExperimentTimedRunDialogView.swift */; }; C0804DC52471C48600AD32F4 /* CRC32InputStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0804DC42471C48600AD32F4 /* CRC32InputStream.swift */; }; - C086C6F22707560100348772 /* CocoaMQTT.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C086C6EA270755E100348772 /* CocoaMQTT.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C08725112241460500079279 /* ColorElementHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C08725102241460500079279 /* ColorElementHandler.swift */; }; C08F3909218AFB0800AB5CE0 /* LegacyStateSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C08F3908218AFB0800AB5CE0 /* LegacyStateSerializer.swift */; }; C0914ED922688F1E00000A2F /* FormulaParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0914ED822688F1E00000A2F /* FormulaParser.swift */; }; @@ -220,7 +220,6 @@ C0AD8542229E7E7A00421738 /* MenuTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0AD8541229E7E7A00421738 /* MenuTableViewController.swift */; }; C0AD8552229EA1CF00421738 /* HintBubbleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0AD8551229EA1CF00421738 /* HintBubbleViewController.swift */; }; C0AE8A982294558000A3EC48 /* ConfigConversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0AE8A972294558000A3EC48 /* ConfigConversion.swift */; }; - C0BD4DEA27079C0D0020A5AF /* CocoaMQTT.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C086C6EA270755E100348772 /* CocoaMQTT.framework */; }; C0BD4DF627079C600020A5AF /* JGProgressHUD in Frameworks */ = {isa = PBXBuildFile; productRef = C0BD4DF527079C600020A5AF /* JGProgressHUD */; }; C0C529002254C01E001E5EFD /* ReduceAnalysis.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C528FF2254C01E001E5EFD /* ReduceAnalysis.swift */; }; C0C529102254F2F3001E5EFD /* MapAnalysis.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C5290F2254F2F3001E5EFD /* MapAnalysis.swift */; }; @@ -246,34 +245,6 @@ remoteGlobalIDString = 6B7BF7791C130425007408FB; remoteInfo = phyphox; }; - C086C6E9270755E100348772 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C086C6E2270755E100348772 /* CocoaMQTT.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 8225B532227C2E8700E4DB51; - remoteInfo = "iOS CocoaMQTT"; - }; - C086C6EB270755E100348772 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C086C6E2270755E100348772 /* CocoaMQTT.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 8225B54C227C32C500E4DB51; - remoteInfo = "CocoaMQTT-Tests"; - }; - C086C6ED270755E100348772 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C086C6E2270755E100348772 /* CocoaMQTT.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 111124B224BB515E00E2DFBC; - remoteInfo = "Mac CocoaMQTT"; - }; - C086C6EF270755E100348772 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C086C6E2270755E100348772 /* CocoaMQTT.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 111124DC24BB516400E2DFBC; - remoteInfo = "tvOS CocoaMQTT"; - }; C0FC14852707C15200512064 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C0FC14812707C15200512064 /* JXLSiOS.xcodeproj */; @@ -340,7 +311,6 @@ dstSubfolderSpec = 10; files = ( C0FC14A22707C1AE00512064 /* ZipZap.framework in Embed Frameworks */, - C086C6F22707560100348772 /* CocoaMQTT.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -586,7 +556,6 @@ C07B530C22942CC300BA085A /* InputConversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputConversion.swift; sourceTree = ""; }; C07BBC1E26209CAC0097C57A /* ExperimentTimedRunDialogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperimentTimedRunDialogView.swift; sourceTree = ""; }; C0804DC42471C48600AD32F4 /* CRC32InputStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CRC32InputStream.swift; sourceTree = ""; }; - C086C6E2270755E100348772 /* CocoaMQTT.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CocoaMQTT.xcodeproj; path = CocoaMQTT/CocoaMQTT.xcodeproj; sourceTree = ""; }; C08725102241460500079279 /* ColorElementHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorElementHandler.swift; sourceTree = ""; }; C08F3908218AFB0800AB5CE0 /* LegacyStateSerializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyStateSerializer.swift; sourceTree = ""; }; C0914ED822688F1E00000A2F /* FormulaParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormulaParser.swift; sourceTree = ""; }; @@ -622,7 +591,7 @@ C0FC14A12707C1AE00512064 /* ZipZap.framework in Frameworks */, 6B0815441CABF61B00EE2791 /* libc++.tbd in Frameworks */, C06BCD372707AAFE00B29595 /* libz.tbd in Frameworks */, - C0BD4DEA27079C0D0020A5AF /* CocoaMQTT.framework in Frameworks */, + 867E2BD82AC464D000ABB472 /* CocoaMQTT in Frameworks */, C06BCD4D2707B09700B29595 /* ImageIO.framework in Frameworks */, C0F430C628E5A2DB00BC98DE /* BEMCheckBox in Frameworks */, C0BD4DF627079C600020A5AF /* JGProgressHUD in Frameworks */, @@ -1197,22 +1166,10 @@ C06BCD262707AADC00B29595 /* ImageIO.framework */, C0FC14882707C19900512064 /* ZipZap.xcodeproj */, C0FC14812707C15200512064 /* JXLSiOS.xcodeproj */, - C086C6E2270755E100348772 /* CocoaMQTT.xcodeproj */, ); name = Frameworks; sourceTree = ""; }; - C086C6E3270755E100348772 /* Products */ = { - isa = PBXGroup; - children = ( - C086C6EA270755E100348772 /* CocoaMQTT.framework */, - C086C6EC270755E100348772 /* CocoaMQTT-Tests.xctest */, - C086C6EE270755E100348772 /* CocoaMQTT.framework */, - C086C6F0270755E100348772 /* CocoaMQTT.framework */, - ); - name = Products; - sourceTree = ""; - }; C0FC14822707C15200512064 /* Products */ = { isa = PBXGroup; children = ( @@ -1257,6 +1214,7 @@ C0BD4DF527079C600020A5AF /* JGProgressHUD */, C0F430C228E5A16D00BC98DE /* GCDWebServers */, C0F430C528E5A2DB00BC98DE /* BEMCheckBox */, + 867E2BD72AC464D000ABB472 /* CocoaMQTT */, ); productName = phyphox; productReference = 6B7BF77A1C130425007408FB /* phyphox.app */; @@ -1364,14 +1322,11 @@ C0BD4DF427079C600020A5AF /* XCRemoteSwiftPackageReference "JGProgressHUD" */, C0F430C128E5A16D00BC98DE /* XCRemoteSwiftPackageReference "GCDWebServer" */, C0F430C428E5A2DB00BC98DE /* XCRemoteSwiftPackageReference "BEMCheckBox" */, + 867E2BD62AC464D000ABB472 /* XCRemoteSwiftPackageReference "CocoaMQTT" */, ); productRefGroup = 6B7BF77B1C130425007408FB /* Products */; projectDirPath = ""; projectReferences = ( - { - ProductGroup = C086C6E3270755E100348772 /* Products */; - ProjectRef = C086C6E2270755E100348772 /* CocoaMQTT.xcodeproj */; - }, { ProductGroup = C0FC14822707C15200512064 /* Products */; ProjectRef = C0FC14812707C15200512064 /* JXLSiOS.xcodeproj */; @@ -1391,34 +1346,6 @@ /* End PBXProject section */ /* Begin PBXReferenceProxy section */ - C086C6EA270755E100348772 /* CocoaMQTT.framework */ = { - isa = PBXReferenceProxy; - fileType = wrapper.framework; - path = CocoaMQTT.framework; - remoteRef = C086C6E9270755E100348772 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - C086C6EC270755E100348772 /* CocoaMQTT-Tests.xctest */ = { - isa = PBXReferenceProxy; - fileType = wrapper.cfbundle; - path = "CocoaMQTT-Tests.xctest"; - remoteRef = C086C6EB270755E100348772 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - C086C6EE270755E100348772 /* CocoaMQTT.framework */ = { - isa = PBXReferenceProxy; - fileType = wrapper.framework; - path = CocoaMQTT.framework; - remoteRef = C086C6ED270755E100348772 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - C086C6F0270755E100348772 /* CocoaMQTT.framework */ = { - isa = PBXReferenceProxy; - fileType = wrapper.framework; - path = CocoaMQTT.framework; - remoteRef = C086C6EF270755E100348772 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; C0FC14862707C15200512064 /* libJXLS.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; @@ -1990,7 +1917,7 @@ "@executable_path/Frameworks", ); LIBRARY_SEARCH_PATHS = ""; - MARKETING_VERSION = 1.1.12; + MARKETING_VERSION = 1.1.13; OTHER_LDFLAGS = ""; "OTHER_SWIFT_FLAGS[arch=*]" = "-DDEBUG"; PRODUCT_BUNDLE_IDENTIFIER = "de.rwth-aachen.physics.phyphox"; @@ -2051,7 +1978,7 @@ "@executable_path/Frameworks", ); LIBRARY_SEARCH_PATHS = ""; - MARKETING_VERSION = 1.1.12; + MARKETING_VERSION = 1.1.13; OTHER_LDFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = "de.rwth-aachen.physics.phyphox"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2228,6 +2155,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 867E2BD62AC464D000ABB472 /* XCRemoteSwiftPackageReference "CocoaMQTT" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/emqx/CocoaMQTT"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.0.0; + }; + }; C0BD4DF427079C600020A5AF /* XCRemoteSwiftPackageReference "JGProgressHUD" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/JonasGessner/JGProgressHUD"; @@ -2255,6 +2190,11 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 867E2BD72AC464D000ABB472 /* CocoaMQTT */ = { + isa = XCSwiftPackageProductDependency; + package = 867E2BD62AC464D000ABB472 /* XCRemoteSwiftPackageReference "CocoaMQTT" */; + productName = CocoaMQTT; + }; C0BD4DF527079C600020A5AF /* JGProgressHUD */ = { isa = XCSwiftPackageProductDependency; package = C0BD4DF427079C600020A5AF /* XCRemoteSwiftPackageReference "JGProgressHUD" */; diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 74f9157f..0c1ad660 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11111 + 11114 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From a3c4a85beb8e3e26c09754cc8334d4ed7ea95f4c Mon Sep 17 00:00:00 2001 From: Sebastian Staacks Date: Thu, 28 Sep 2023 09:07:11 +0200 Subject: [PATCH 18/81] Adapt BLE status bar icons to color mode. --- phyphox-iOS/phyphox/Info.plist | 2 +- .../ConnectedBluetoothDeviceViewController.swift | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 0c1ad660..eb28e286 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11114 + 11118 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ConnectedBluetoothDeviceViewController.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ConnectedBluetoothDeviceViewController.swift index 5a2f9b0b..be7bec02 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ConnectedBluetoothDeviceViewController.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ConnectedBluetoothDeviceViewController.swift @@ -105,15 +105,15 @@ class ConnectedBleDeviceCell: UICollectionViewCell { func getSignal(rssi: Int) -> UIImage{ if rssi > -35 { - return (UIImage(named: "cellular_level_4")?.withTintColor(.black, renderingMode: .alwaysOriginal))! + return UIImage(named: "cellular_level_4")! } else if rssi > -55 { - return (UIImage(named: "cellular_level_3")?.withTintColor(.black, renderingMode: .alwaysOriginal))! + return UIImage(named: "cellular_level_3")! } else if rssi > -70 { - return (UIImage(named: "cellular_level_2")?.withTintColor(.black, renderingMode: .alwaysOriginal))! + return UIImage(named: "cellular_level_2")! } else if rssi > -90 { - return (UIImage(named: "cellular_level_1")?.withTintColor(.black, renderingMode: .alwaysOriginal))! + return UIImage(named: "cellular_level_1")! } else { - return (UIImage(named: "cellular_level_0")?.withTintColor(.black, renderingMode: .alwaysOriginal))! + return UIImage(named: "cellular_level_0")! } @@ -163,9 +163,8 @@ class ConnectedBleDeviceCell: UICollectionViewCell { func configure(model: ConnectedDevicesDataModel){ deviceLabel.text = model.getDeviceName() if #available(iOS 13.0, *) { - signalImageView.image = getSignal(rssi: model.getSignalStrength()) - let getBatteryLevelImage = getBatteryLevel(level: model.getBatteryLabel()) - batteryImageView.image = getBatteryLevelImage.withTintColor(.black, renderingMode: .alwaysOriginal) + signalImageView.image = getImageAsDeviceMode(image: getSignal(rssi: model.getSignalStrength())) + batteryImageView.image = getImageAsDeviceMode(image: getBatteryLevel(level: model.getBatteryLabel())) } else { // Fallback on earlier versions } From 4742013111c9ef3a88f76187684a9a0708df3409 Mon Sep 17 00:00:00 2001 From: Sebastian Staacks Date: Fri, 5 Jan 2024 12:17:54 +0100 Subject: [PATCH 19/81] Increase minimum iOS version to 12 (XCode requirement) and Adjust calculation of colors for bright mode to avoid unreadable colors if luminance differs massively from lightness. (i.e. fully saturated yellow was not adjusted) --- phyphox-experiments | 2 +- phyphox-iOS/phyphox.xcodeproj/project.pbxproj | 4 ++-- .../phyphox/Helper/ColorConverterHelper.swift | 24 ++++++++++++++++++- phyphox-iOS/phyphox/Info.plist | 2 +- phyphox-webinterface | 2 +- 5 files changed, 28 insertions(+), 6 deletions(-) diff --git a/phyphox-experiments b/phyphox-experiments index 4e647196..641fbeee 160000 --- a/phyphox-experiments +++ b/phyphox-experiments @@ -1 +1 @@ -Subproject commit 4e647196f66b29ed4c853df402dabbf9b6bee158 +Subproject commit 641fbeeef6548e90ddd23eccaa496e85ca1e1a30 diff --git a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj index bd0574f9..0d315bb9 100644 --- a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj +++ b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj @@ -1911,7 +1911,7 @@ GCC_WARN_UNUSED_PARAMETER = NO; HEADER_SEARCH_PATHS = ../ZipZap; INFOPLIST_FILE = phyphox/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1972,7 +1972,7 @@ GCC_WARN_UNUSED_PARAMETER = NO; HEADER_SEARCH_PATHS = ../ZipZap; INFOPLIST_FILE = phyphox/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/phyphox-iOS/phyphox/Helper/ColorConverterHelper.swift b/phyphox-iOS/phyphox/Helper/ColorConverterHelper.swift index b6b85d2d..4f125fd4 100644 --- a/phyphox-iOS/phyphox/Helper/ColorConverterHelper.swift +++ b/phyphox-iOS/phyphox/Helper/ColorConverterHelper.swift @@ -39,7 +39,29 @@ class ColorConverterHelper { var l = (2.0 - hsv.saturation) * hsv.value / 2.0 let s = l > 0 && l < 1 ? hsv.saturation * hsv.value / (l < 0.5 ? l * 2.0 : 2.0 - l * 2.0) : 0.0 - l = 1.0 - l + + // While flipping HSL lightness (l = 1.0 - l) is a good first approximation, it fails for + // colors where luminance differs massively from lightness. Extreme example: + // ff0000 (yellow) has a lightness of 0.5 and would remain unchanged although it is + // perceived as very bright and has a good contrast to black but almost no contrast to + // white. + // Directly calculating HSL or RGB for a target luminance seems to be tricky according to + // comments in https://stackoverflow.com/a/61761862/8068814, but we do not have to be exact. + // Instead lets flip the luminance and use that for lightness + // (in linear space, though, so need to adjust gamma) + let lum = colorName.linearLuminance + let pivot = 0.21404114 //Math.pow((0.5+0.055)/1.055, 2.4) + var gammaL = pivot * (1 - lum) / (1 - pivot) + if gammaL < 0 { + gammaL = 0.0 + } + l = 1.055 * pow(gammaL, 1.0/2.4) - 0.055; + if l < 0 { + l = 0 + } else if l > 1 { + l = 1 + } + let t = s * (l < 0.5 ? l : 1.0 - l) hsv.value = l + t hsv.saturation = l > 0 ? 2 * t / hsv.value : 0.0 diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index eb28e286..7e619275 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11118 + 11119 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace diff --git a/phyphox-webinterface b/phyphox-webinterface index 650a5934..65a531fe 160000 --- a/phyphox-webinterface +++ b/phyphox-webinterface @@ -1 +1 @@ -Subproject commit 650a593490dbfe47bd98f9fac59b387f8fbc8d92 +Subproject commit 65a531fe56a42b54de65bebe90c8cbb5b22bab4d From 684f54bc27aa14134376998fec4bb398bf943d03 Mon Sep 17 00:00:00 2001 From: Sebastian Staacks Date: Fri, 5 Jan 2024 12:52:19 +0100 Subject: [PATCH 20/81] Simplify three-dot menu color on main screen to simple black/white and follow bright mode setting. --- phyphox-iOS/phyphox/Info.plist | 2 +- phyphox-iOS/phyphox/UI/MainView/ExperimentCell.swift | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 7e619275..dca3ac18 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11119 + 11120 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentCell.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentCell.swift index 728ba620..d1ec91af 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentCell.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentCell.swift @@ -31,8 +31,7 @@ class ExperimentCell: UICollectionViewCell { return } - let expColor = experiment?.color ?? kHighlightColor - let color = expColor.linearLuminance > 0.1 ? expColor : kTextColor + let color = kTextColor.autoLightColor() optionsButton.setTintColor(color, for: UIControl.State()) optionsButton.setTintColor(color.interpolating(to: UIColor.black, byFraction: 0.5), for: .highlighted) } From 1e13926e44ae16c07019f3e5fde0dcf59b8bbaa4 Mon Sep 17 00:00:00 2001 From: Sebastian Staacks Date: Wed, 10 Jan 2024 17:17:29 +0100 Subject: [PATCH 21/81] Fix crash on renaming saved state. --- phyphox-iOS/phyphox/Info.plist | 2 +- .../UI/MainView/ExperimentsCollectionViewController.swift | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index dca3ac18..8773c4f2 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11120 + 11128 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentsCollectionViewController.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentsCollectionViewController.swift index 7c530735..511bf97f 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentsCollectionViewController.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentsCollectionViewController.swift @@ -370,9 +370,8 @@ final class ExperimentsCollectionViewController: CollectionViewController, Exper private func showStateTitleEditForExperiment(_ experiment: Experiment, button: UIButton, oldTitle: String) { - - UIAlertController.PhyphoxUIAlertBuilder() - .title(title: localize("rename")) + let alertBuilder = UIAlertController.PhyphoxUIAlertBuilder() + alertBuilder.title(title: localize("rename")) .message(message: "") .preferredStyle(style: .alert) .addTextField(configHandler: {(textfield: UITextField!) -> Void in @@ -381,7 +380,7 @@ final class ExperimentsCollectionViewController: CollectionViewController, Exper }) .addActionWithTitle(localize("rename"), style: .default, handler: { [unowned self] action in do { - let textField = UIAlertController.PhyphoxUIAlertBuilder().getTextFieldValue() + let textField = alertBuilder.getTextFieldValue() if let newTitle = textField.text, newTitle.replacingOccurrences(of: " ", with: "") != "" { try ExperimentManager.shared.renameExperiment(experiment, newTitle: newTitle) From eabb898149525c37ebda4a8d21bc9c32d0e4aa5d Mon Sep 17 00:00:00 2001 From: Sebastian Staacks Date: Fri, 12 Jan 2024 15:42:22 +0100 Subject: [PATCH 22/81] Switch repository for Swift-packaged version of GCDWebServer --- phyphox-experiments | 2 +- phyphox-iOS/phyphox.xcodeproj/project.pbxproj | 34 +++++++++---------- phyphox-iOS/phyphox/Info.plist | 2 +- .../ExperimentPageViewController.swift | 2 +- .../WebServer/ExperimentWebServer.swift | 2 +- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/phyphox-experiments b/phyphox-experiments index 641fbeee..4e647196 160000 --- a/phyphox-experiments +++ b/phyphox-experiments @@ -1 +1 @@ -Subproject commit 641fbeeef6548e90ddd23eccaa496e85ca1e1a30 +Subproject commit 4e647196f66b29ed4c853df402dabbf9b6bee158 diff --git a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj index 0d315bb9..a3126bdb 100644 --- a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj +++ b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj @@ -155,6 +155,7 @@ 6BEA94592080B2F30077274A /* MultilineTextElementHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BEA94582080B2F30077274A /* MultilineTextElementHandler.swift */; }; 6BF82BC82147FF6F00175659 /* SemanticVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BF82BC72147FF6F00175659 /* SemanticVersion.swift */; }; 6BFE372C20442AB400301CA3 /* KeyboardTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BFE372B20442AB400301CA3 /* KeyboardTracker.swift */; }; + 860E13F32B514703008FAFC6 /* GCDWebServer in Frameworks */ = {isa = PBXBuildFile; productRef = 860E13F22B514703008FAFC6 /* GCDWebServer */; }; 866632E92A29E1D90068762D /* UIColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 866632E82A29E1D90068762D /* UIColorExtensions.swift */; }; 867E2BD82AC464D000ABB472 /* CocoaMQTT in Frameworks */ = {isa = PBXBuildFile; productRef = 867E2BD72AC464D000ABB472 /* CocoaMQTT */; }; A101AB0829BB801600B82311 /* ConnectedBluetoothDeviceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A101AB0729BB801600B82311 /* ConnectedBluetoothDeviceViewController.swift */; }; @@ -223,7 +224,6 @@ C0BD4DF627079C600020A5AF /* JGProgressHUD in Frameworks */ = {isa = PBXBuildFile; productRef = C0BD4DF527079C600020A5AF /* JGProgressHUD */; }; C0C529002254C01E001E5EFD /* ReduceAnalysis.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C528FF2254C01E001E5EFD /* ReduceAnalysis.swift */; }; C0C529102254F2F3001E5EFD /* MapAnalysis.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C5290F2254F2F3001E5EFD /* MapAnalysis.swift */; }; - C0F430C328E5A16D00BC98DE /* GCDWebServers in Frameworks */ = {isa = PBXBuildFile; productRef = C0F430C228E5A16D00BC98DE /* GCDWebServers */; }; C0F430C628E5A2DB00BC98DE /* BEMCheckBox in Frameworks */ = {isa = PBXBuildFile; productRef = C0F430C528E5A2DB00BC98DE /* BEMCheckBox */; }; C0FC14872707C16300512064 /* libJXLS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C0FC14862707C15200512064 /* libJXLS.a */; }; C0FC14A12707C1AE00512064 /* ZipZap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0FC14932707C19900512064 /* ZipZap.framework */; }; @@ -593,11 +593,11 @@ C06BCD372707AAFE00B29595 /* libz.tbd in Frameworks */, 867E2BD82AC464D000ABB472 /* CocoaMQTT in Frameworks */, C06BCD4D2707B09700B29595 /* ImageIO.framework in Frameworks */, + 860E13F32B514703008FAFC6 /* GCDWebServer in Frameworks */, C0F430C628E5A2DB00BC98DE /* BEMCheckBox in Frameworks */, C0BD4DF627079C600020A5AF /* JGProgressHUD in Frameworks */, C0FC14872707C16300512064 /* libJXLS.a in Frameworks */, C06BCD4C2707B07100B29595 /* MobileCoreServices.framework in Frameworks */, - C0F430C328E5A16D00BC98DE /* GCDWebServers in Frameworks */, C06BCD4F2707B09C00B29595 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1212,9 +1212,9 @@ name = phyphox; packageProductDependencies = ( C0BD4DF527079C600020A5AF /* JGProgressHUD */, - C0F430C228E5A16D00BC98DE /* GCDWebServers */, C0F430C528E5A2DB00BC98DE /* BEMCheckBox */, 867E2BD72AC464D000ABB472 /* CocoaMQTT */, + 860E13F22B514703008FAFC6 /* GCDWebServer */, ); productName = phyphox; productReference = 6B7BF77A1C130425007408FB /* phyphox.app */; @@ -1320,9 +1320,9 @@ mainGroup = 6B7BF7711C130425007408FB; packageReferences = ( C0BD4DF427079C600020A5AF /* XCRemoteSwiftPackageReference "JGProgressHUD" */, - C0F430C128E5A16D00BC98DE /* XCRemoteSwiftPackageReference "GCDWebServer" */, C0F430C428E5A2DB00BC98DE /* XCRemoteSwiftPackageReference "BEMCheckBox" */, 867E2BD62AC464D000ABB472 /* XCRemoteSwiftPackageReference "CocoaMQTT" */, + 860E13F12B514703008FAFC6 /* XCRemoteSwiftPackageReference "GCDWebServer" */, ); productRefGroup = 6B7BF77B1C130425007408FB /* Products */; projectDirPath = ""; @@ -2155,6 +2155,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 860E13F12B514703008FAFC6 /* XCRemoteSwiftPackageReference "GCDWebServer" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/yene/GCDWebServer"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 3.5.7; + }; + }; 867E2BD62AC464D000ABB472 /* XCRemoteSwiftPackageReference "CocoaMQTT" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/emqx/CocoaMQTT"; @@ -2171,14 +2179,6 @@ kind = branch; }; }; - C0F430C128E5A16D00BC98DE /* XCRemoteSwiftPackageReference "GCDWebServer" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/JacobHearst/GCDWebServer"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 3.0.0; - }; - }; C0F430C428E5A2DB00BC98DE /* XCRemoteSwiftPackageReference "BEMCheckBox" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/Boris-Em/BEMCheckBox"; @@ -2190,6 +2190,11 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 860E13F22B514703008FAFC6 /* GCDWebServer */ = { + isa = XCSwiftPackageProductDependency; + package = 860E13F12B514703008FAFC6 /* XCRemoteSwiftPackageReference "GCDWebServer" */; + productName = GCDWebServer; + }; 867E2BD72AC464D000ABB472 /* CocoaMQTT */ = { isa = XCSwiftPackageProductDependency; package = 867E2BD62AC464D000ABB472 /* XCRemoteSwiftPackageReference "CocoaMQTT" */; @@ -2200,11 +2205,6 @@ package = C0BD4DF427079C600020A5AF /* XCRemoteSwiftPackageReference "JGProgressHUD" */; productName = JGProgressHUD; }; - C0F430C228E5A16D00BC98DE /* GCDWebServers */ = { - isa = XCSwiftPackageProductDependency; - package = C0F430C128E5A16D00BC98DE /* XCRemoteSwiftPackageReference "GCDWebServer" */; - productName = GCDWebServers; - }; C0F430C528E5A2DB00BC98DE /* BEMCheckBox */ = { isa = XCSwiftPackageProductDependency; package = C0F430C428E5A2DB00BC98DE /* XCRemoteSwiftPackageReference "BEMCheckBox" */; diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 8773c4f2..ea0eb315 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11128 + 11129 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift index e5c32403..d6e6804c 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift @@ -7,7 +7,7 @@ // import Foundation -import GCDWebServers +import GCDWebServer import SwiftUI protocol ExportDelegate { diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/WebServer/ExperimentWebServer.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/WebServer/ExperimentWebServer.swift index 9c15028e..2865cf37 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/WebServer/ExperimentWebServer.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/WebServer/ExperimentWebServer.swift @@ -7,7 +7,7 @@ // import Foundation -import GCDWebServers +import GCDWebServer protocol ExperimentWebServerDelegate: AnyObject { var timerRunning: Bool { get } From da8c6c76e8499b98c5afca69ed446c8cf0bec0f9 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Wed, 17 Jan 2024 12:12:14 +0100 Subject: [PATCH 23/81] fix: clear the connected devices list when bluetooth disconnected --- phyphox-iOS/phyphox/Experiments/BluetoothDevice.swift | 2 ++ phyphox-iOS/phyphox/Info.plist | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/phyphox-iOS/phyphox/Experiments/BluetoothDevice.swift b/phyphox-iOS/phyphox/Experiments/BluetoothDevice.swift index 645e5adc..bc77718a 100644 --- a/phyphox-iOS/phyphox/Experiments/BluetoothDevice.swift +++ b/phyphox-iOS/phyphox/Experiments/BluetoothDevice.swift @@ -299,7 +299,9 @@ class ExperimentBluetoothDevice: BluetoothScan, DeviceIsChosenDelegate { } characteristics_map = [:] servicesToBeDiscovered = [] + connectedDevices = [] centralManager?.stopScan() + } override func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index ea0eb315..828b35f8 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11129 + 11131 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 0ce3822b2bba9f33831211fe183cb84f6fcc3e03 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Wed, 17 Jan 2024 15:57:31 +0100 Subject: [PATCH 24/81] fix: refresh the theme in experiment view as per the system appearance --- phyphox-iOS/phyphox/Info.plist | 2 +- .../ExperimentPageViewController.swift | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 828b35f8..1a74735d 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11131 + 11132 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift index d6e6804c..3258fa4f 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift @@ -317,7 +317,7 @@ final class ExperimentPageViewController: UIViewController, UIPageViewController self.automaticallyAdjustsScrollViewInsets = false self.edgesForExtendedLayout = UIRectEdge() - refreshTheAdjustedGraphColorForLightMode() + refreshAppTheme() actionItem = UIBarButtonItem(image: generateDots(20.0), landscapeImagePhone: generateDots(15.0), style: .plain, target: self, action: #selector(action(_:))) actionItem?.accessibilityLabel = localize("actions") @@ -388,11 +388,12 @@ final class ExperimentPageViewController: UIViewController, UIPageViewController override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) updateSelectedViewCollection() - refreshTheAdjustedGraphColorForLightMode() + refreshAppTheme() if (experiment.viewDescriptors!.count > 1) { updateSegControlDesign() tabBar!.backgroundColor = SettingBundleHelper.getLightBackgroundColorWhenDarkModeNotSupported() } + } class NetworkServiceRequestCallbackWrapper: NetworkServiceRequestCallback { @@ -1470,7 +1471,7 @@ final class ExperimentPageViewController: UIViewController, UIPageViewController } } - func refreshTheAdjustedGraphColorForLightMode(){ + func refreshAppTheme(){ if #available(iOS 12.0, *) { if(SettingBundleHelper.getAppMode() == Utility.LIGHT_MODE || UIScreen.main.traitCollection.userInterfaceStyle == .light){ @@ -1479,6 +1480,13 @@ final class ExperimentPageViewController: UIViewController, UIPageViewController } else { // Fallback on earlier versions } + } else if(SettingBundleHelper.getAppMode() == Utility.DARK_MODE || + UIScreen.main.traitCollection.userInterfaceStyle == .dark){ + if #available(iOS 13.0, *) { + view.overrideUserInterfaceStyle = .dark + } else { + // Fallback on earlier versions + } } } else { // Fallback on earlier versions From 137a4acb3bf9a845d4c8b2690c17415f6ac3904b Mon Sep 17 00:00:00 2001 From: GTripathee Date: Wed, 17 Jan 2024 19:22:53 +0100 Subject: [PATCH 25/81] fix: rebase to development --- phyphox-iOS/phyphox/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 1a74735d..0ef6b5af 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11132 + 11133 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 5e723e285df20adea64702c3596af20f07168dfb Mon Sep 17 00:00:00 2001 From: Sebastian Staacks Date: Mon, 4 Dec 2023 08:57:29 +0100 Subject: [PATCH 26/81] Added translation using Weblate (Bengali) --- phyphox-iOS/phyphox/bn.lproj/Localizable.strings | 1 - 1 file changed, 1 deletion(-) diff --git a/phyphox-iOS/phyphox/bn.lproj/Localizable.strings b/phyphox-iOS/phyphox/bn.lproj/Localizable.strings index 52567474..6958bb6f 100644 --- a/phyphox-iOS/phyphox/bn.lproj/Localizable.strings +++ b/phyphox-iOS/phyphox/bn.lproj/Localizable.strings @@ -1,5 +1,4 @@ - "app_name" = "ফিফক্স"; "action_settings" = "সেটিংস"; "title_activity_experiment" = "ফিফক্স"; From 0884a94dbc44a382f5ccda4bcf3b74909c6329f7 Mon Sep 17 00:00:00 2001 From: Ananda Dasgupta Date: Mon, 4 Dec 2023 16:46:44 +0000 Subject: [PATCH 27/81] Translated using Weblate (Bengali) Currently translated at 18.4% (63 of 342 strings) Translation: phyphox/iOS Translate-URL: https://translate.phyphox.org/projects/phyphox/ios/bn/ --- phyphox-iOS/phyphox/bn.lproj/Localizable.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/phyphox-iOS/phyphox/bn.lproj/Localizable.strings b/phyphox-iOS/phyphox/bn.lproj/Localizable.strings index 6958bb6f..9c1858c6 100644 --- a/phyphox-iOS/phyphox/bn.lproj/Localizable.strings +++ b/phyphox-iOS/phyphox/bn.lproj/Localizable.strings @@ -20,6 +20,7 @@ "faqPhyphoxOrg" = "প্রায়শই জিজ্ঞাসা করা প্রশ্ন"; "faqPhyphoxOrgURL" = "http://phyphox.org/faq"; "remotePhyphoxOrg" = "রিমোট কন্ট্রোল সহায়তা"; +"remotePhyphoxOrg" = "রিমোট কন্ট্রোল সংক্রান্ত সহায়তা"; "remotePhyphoxOrgURL" = "http://phyphox.org/remote-control"; "deviceInfo" = "ডিভাইসের বিবরণ"; "copyToClipboard" = "ক্লিপবোর্ডে কপি করুন"; From 267400421cd109e3f400e9c9da0b6a2d3c430a10 Mon Sep 17 00:00:00 2001 From: Gaurav Tripathee Date: Wed, 6 Sep 2023 09:46:00 +0200 Subject: [PATCH 28/81] Show battery info when connected with the bluetooth devices (#12) * feat: show connected device information while experiment loaded from the bluetooth device * fix showing battery level eeventhougth there is no battery service * refactor: change the name dependecy in bluetooth connected device * ui: fix the graph border color when experiment opened from connected bluetooth device * refactor: remove extra if condition in updating connected bluetooth device --- phyphox-iOS/phyphox.xcodeproj/project.pbxproj | 8 +++++--- phyphox-iOS/phyphox/Info.plist | 2 +- .../ConnectedBluetoothDeviceViewController.swift | 1 + 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj index a3126bdb..a49a6607 100644 --- a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj +++ b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj @@ -141,8 +141,7 @@ 6BE3209F1CB19E0300DCC1ED /* CreatePresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE3209C1CB19E0300DCC1ED /* CreatePresentationController.swift */; }; 6BE320A01CB19E0300DCC1ED /* CreateViewControllerTransitioningDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE3209D1CB19E0300DCC1ED /* CreateViewControllerTransitioningDelegate.swift */; }; 6BE320A11CB19E0300DCC1ED /* CreateViewControllerTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE3209E1CB19E0300DCC1ED /* CreateViewControllerTransition.swift */; }; - 6BE320AA1CB2B55700DCC1ED /* TextFieldTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE320A91CB2B55700DCC1ED /* TextFieldTableViewCell.swift */; }; - 6BE320AE1CB2D36D00DCC1ED /* SimpleExperimentSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE320AD1CB2D36D00DCC1ED /* SimpleExperimentSerializer.swift */; }; + 6BE320AA1CB2B55700DCC1ED /* TextFieldTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE320A91CB2B55700DCC1ED /* TextFieldTableViewCell.swift */; }; 6BE320AE1CB2D36D00DCC1ED /* SimpleExperimentSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE320AD1CB2D36D00DCC1ED /* SimpleExperimentSerializer.swift */; }; 6BE320B21CB3C0DE00DCC1ED /* PTButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BE320B11CB3C0DE00DCC1ED /* PTButton.m */; }; 6BE320B41CB3E09C00DCC1ED /* ShrinkingAnimaitonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE320B31CB3E09C00DCC1ED /* ShrinkingAnimaitonViewController.swift */; }; 6BEA944A207FC5280077274A /* ExportElementHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BEA943B207FC5280077274A /* ExportElementHandler.swift */; }; @@ -159,6 +158,7 @@ 866632E92A29E1D90068762D /* UIColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 866632E82A29E1D90068762D /* UIColorExtensions.swift */; }; 867E2BD82AC464D000ABB472 /* CocoaMQTT in Frameworks */ = {isa = PBXBuildFile; productRef = 867E2BD72AC464D000ABB472 /* CocoaMQTT */; }; A101AB0829BB801600B82311 /* ConnectedBluetoothDeviceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A101AB0729BB801600B82311 /* ConnectedBluetoothDeviceViewController.swift */; }; + 866632E92A29E1D90068762D /* UIColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 866632E82A29E1D90068762D /* UIColorExtensions.swift */; }; A1A6A72F297685DF00090309 /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1A6A72E297685DF00090309 /* Utility.swift */; }; A1A80D3D29AE049E00AD756D /* SettingsBundleHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1A80D3C29AE049E00AD756D /* SettingsBundleHelper.swift */; }; A1E03A7F29B61A9000CE93A6 /* UIImageExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1E03A7E29B61A9000CE93A6 /* UIImageExt.swift */; }; @@ -479,12 +479,14 @@ 6BEA94582080B2F30077274A /* MultilineTextElementHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineTextElementHandler.swift; sourceTree = ""; }; 6BF82BC72147FF6F00175659 /* SemanticVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SemanticVersion.swift; sourceTree = ""; }; 6BFE372B20442AB400301CA3 /* KeyboardTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardTracker.swift; sourceTree = ""; }; + + A101AB0729BB801600B82311 /* ConnectedBluetoothDeviceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedBluetoothDeviceViewController.swift; sourceTree = ""; }; + 866632E82A29E1D90068762D /* UIColorExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColorExtensions.swift; sourceTree = ""; }; 86C183CF29F131BC0089E396 /* hi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hi; path = hi.lproj/Localizable.strings; sourceTree = ""; }; 86C183D029F131BC0089E396 /* hi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hi; path = hi.lproj/InfoPlist.strings; sourceTree = ""; }; 86C183D129F132020089E396 /* ka */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ka; path = ka.lproj/Localizable.strings; sourceTree = ""; }; 86C183D229F132020089E396 /* ka */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ka; path = ka.lproj/InfoPlist.strings; sourceTree = ""; }; - A101AB0729BB801600B82311 /* ConnectedBluetoothDeviceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedBluetoothDeviceViewController.swift; sourceTree = ""; }; A1A6A72E297685DF00090309 /* Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = ""; }; A1A80D3C29AE049E00AD756D /* SettingsBundleHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsBundleHelper.swift; sourceTree = ""; }; A1E03A7E29B61A9000CE93A6 /* UIImageExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageExt.swift; sourceTree = ""; }; diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 0ef6b5af..74f9157f 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11133 + 11111 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ConnectedBluetoothDeviceViewController.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ConnectedBluetoothDeviceViewController.swift index be7bec02..4ce3fc66 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ConnectedBluetoothDeviceViewController.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ConnectedBluetoothDeviceViewController.swift @@ -105,6 +105,7 @@ class ConnectedBleDeviceCell: UICollectionViewCell { func getSignal(rssi: Int) -> UIImage{ if rssi > -35 { +<<<<<<< HEAD return UIImage(named: "cellular_level_4")! } else if rssi > -55 { return UIImage(named: "cellular_level_3")! From 4070c104aaa699a9bd3932288e004f18af9a28d4 Mon Sep 17 00:00:00 2001 From: Sebastian Staacks Date: Wed, 27 Sep 2023 15:35:26 +0200 Subject: [PATCH 29/81] Replace our CocoaMQTT fork with upstream version to fix incompatibility with iOS 16 --- phyphox-iOS/phyphox.xcodeproj/project.pbxproj | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj index a49a6607..3b70cc51 100644 --- a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj +++ b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj @@ -479,14 +479,12 @@ 6BEA94582080B2F30077274A /* MultilineTextElementHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineTextElementHandler.swift; sourceTree = ""; }; 6BF82BC72147FF6F00175659 /* SemanticVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SemanticVersion.swift; sourceTree = ""; }; 6BFE372B20442AB400301CA3 /* KeyboardTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardTracker.swift; sourceTree = ""; }; - - A101AB0729BB801600B82311 /* ConnectedBluetoothDeviceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedBluetoothDeviceViewController.swift; sourceTree = ""; }; - 866632E82A29E1D90068762D /* UIColorExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColorExtensions.swift; sourceTree = ""; }; 86C183CF29F131BC0089E396 /* hi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hi; path = hi.lproj/Localizable.strings; sourceTree = ""; }; 86C183D029F131BC0089E396 /* hi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hi; path = hi.lproj/InfoPlist.strings; sourceTree = ""; }; 86C183D129F132020089E396 /* ka */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ka; path = ka.lproj/Localizable.strings; sourceTree = ""; }; 86C183D229F132020089E396 /* ka */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ka; path = ka.lproj/InfoPlist.strings; sourceTree = ""; }; + A101AB0729BB801600B82311 /* ConnectedBluetoothDeviceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedBluetoothDeviceViewController.swift; sourceTree = ""; }; A1A6A72E297685DF00090309 /* Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = ""; }; A1A80D3C29AE049E00AD756D /* SettingsBundleHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsBundleHelper.swift; sourceTree = ""; }; A1E03A7E29B61A9000CE93A6 /* UIImageExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageExt.swift; sourceTree = ""; }; @@ -1216,7 +1214,10 @@ C0BD4DF527079C600020A5AF /* JGProgressHUD */, C0F430C528E5A2DB00BC98DE /* BEMCheckBox */, 867E2BD72AC464D000ABB472 /* CocoaMQTT */, +<<<<<<< HEAD 860E13F22B514703008FAFC6 /* GCDWebServer */, +======= +>>>>>>> 26e36c4 (Replace our CocoaMQTT fork with upstream version to fix incompatibility with iOS 16) ); productName = phyphox; productReference = 6B7BF77A1C130425007408FB /* phyphox.app */; @@ -1324,7 +1325,10 @@ C0BD4DF427079C600020A5AF /* XCRemoteSwiftPackageReference "JGProgressHUD" */, C0F430C428E5A2DB00BC98DE /* XCRemoteSwiftPackageReference "BEMCheckBox" */, 867E2BD62AC464D000ABB472 /* XCRemoteSwiftPackageReference "CocoaMQTT" */, +<<<<<<< HEAD 860E13F12B514703008FAFC6 /* XCRemoteSwiftPackageReference "GCDWebServer" */, +======= +>>>>>>> 26e36c4 (Replace our CocoaMQTT fork with upstream version to fix incompatibility with iOS 16) ); productRefGroup = 6B7BF77B1C130425007408FB /* Products */; projectDirPath = ""; @@ -2192,6 +2196,7 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 860E13F22B514703008FAFC6 /* GCDWebServer */ = { isa = XCSwiftPackageProductDependency; package = 860E13F12B514703008FAFC6 /* XCRemoteSwiftPackageReference "GCDWebServer" */; From eefc8da941700c323bc3f51c64992763287d9d0f Mon Sep 17 00:00:00 2001 From: Sebastian Staacks Date: Thu, 28 Sep 2023 09:07:11 +0200 Subject: [PATCH 30/81] Adapt BLE status bar icons to color mode. --- .../ExperimentView/ConnectedBluetoothDeviceViewController.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ConnectedBluetoothDeviceViewController.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ConnectedBluetoothDeviceViewController.swift index 4ce3fc66..be7bec02 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ConnectedBluetoothDeviceViewController.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ConnectedBluetoothDeviceViewController.swift @@ -105,7 +105,6 @@ class ConnectedBleDeviceCell: UICollectionViewCell { func getSignal(rssi: Int) -> UIImage{ if rssi > -35 { -<<<<<<< HEAD return UIImage(named: "cellular_level_4")! } else if rssi > -55 { return UIImage(named: "cellular_level_3")! From 4ae0162cfd6a11230c277580019a10b0bc276c18 Mon Sep 17 00:00:00 2001 From: Sebastian Staacks Date: Fri, 5 Jan 2024 12:17:54 +0100 Subject: [PATCH 31/81] Increase minimum iOS version to 12 (XCode requirement) and Adjust calculation of colors for bright mode to avoid unreadable colors if luminance differs massively from lightness. (i.e. fully saturated yellow was not adjusted) --- phyphox-experiments | 2 +- phyphox-iOS/phyphox/Info.plist | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/phyphox-experiments b/phyphox-experiments index 4e647196..641fbeee 160000 --- a/phyphox-experiments +++ b/phyphox-experiments @@ -1 +1 @@ -Subproject commit 4e647196f66b29ed4c853df402dabbf9b6bee158 +Subproject commit 641fbeeef6548e90ddd23eccaa496e85ca1e1a30 diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 74f9157f..7e619275 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11111 + 11119 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From d46eab47c8a67e7b62090f9ace107b872f9aca65 Mon Sep 17 00:00:00 2001 From: Sebastian Staacks Date: Fri, 5 Jan 2024 12:52:19 +0100 Subject: [PATCH 32/81] Simplify three-dot menu color on main screen to simple black/white and follow bright mode setting. --- phyphox-iOS/phyphox/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 7e619275..dca3ac18 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11119 + 11120 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 7239bb856d3c3ed599f33492457cdd869314d326 Mon Sep 17 00:00:00 2001 From: Sebastian Staacks Date: Wed, 10 Jan 2024 17:17:29 +0100 Subject: [PATCH 33/81] Fix crash on renaming saved state. --- phyphox-iOS/phyphox/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index dca3ac18..8773c4f2 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11120 + 11128 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 98c2ec39ecd2499190b328b76ac17de1f598a31c Mon Sep 17 00:00:00 2001 From: Sebastian Staacks Date: Fri, 12 Jan 2024 15:42:22 +0100 Subject: [PATCH 34/81] Switch repository for Swift-packaged version of GCDWebServer --- phyphox-experiments | 2 +- phyphox-iOS/phyphox.xcodeproj/project.pbxproj | 10 +++++----- phyphox-iOS/phyphox/Info.plist | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/phyphox-experiments b/phyphox-experiments index 641fbeee..4e647196 160000 --- a/phyphox-experiments +++ b/phyphox-experiments @@ -1 +1 @@ -Subproject commit 641fbeeef6548e90ddd23eccaa496e85ca1e1a30 +Subproject commit 4e647196f66b29ed4c853df402dabbf9b6bee158 diff --git a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj index 3b70cc51..a18c6ce1 100644 --- a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj +++ b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj @@ -1214,10 +1214,14 @@ C0BD4DF527079C600020A5AF /* JGProgressHUD */, C0F430C528E5A2DB00BC98DE /* BEMCheckBox */, 867E2BD72AC464D000ABB472 /* CocoaMQTT */, +<<<<<<< HEAD <<<<<<< HEAD 860E13F22B514703008FAFC6 /* GCDWebServer */, ======= >>>>>>> 26e36c4 (Replace our CocoaMQTT fork with upstream version to fix incompatibility with iOS 16) +======= + 860E13F22B514703008FAFC6 /* GCDWebServer */, +>>>>>>> 70f0b50 (Switch repository for Swift-packaged version of GCDWebServer) ); productName = phyphox; productReference = 6B7BF77A1C130425007408FB /* phyphox.app */; @@ -1325,11 +1329,8 @@ C0BD4DF427079C600020A5AF /* XCRemoteSwiftPackageReference "JGProgressHUD" */, C0F430C428E5A2DB00BC98DE /* XCRemoteSwiftPackageReference "BEMCheckBox" */, 867E2BD62AC464D000ABB472 /* XCRemoteSwiftPackageReference "CocoaMQTT" */, -<<<<<<< HEAD 860E13F12B514703008FAFC6 /* XCRemoteSwiftPackageReference "GCDWebServer" */, -======= ->>>>>>> 26e36c4 (Replace our CocoaMQTT fork with upstream version to fix incompatibility with iOS 16) - ); + productRefGroup = 6B7BF77B1C130425007408FB /* Products */; projectDirPath = ""; projectReferences = ( @@ -2196,7 +2197,6 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 860E13F22B514703008FAFC6 /* GCDWebServer */ = { isa = XCSwiftPackageProductDependency; package = 860E13F12B514703008FAFC6 /* XCRemoteSwiftPackageReference "GCDWebServer" */; diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 8773c4f2..ea0eb315 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11128 + 11129 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 6886d500dcc17910fd415f5784e9394850d0f20d Mon Sep 17 00:00:00 2001 From: GTripathee Date: Wed, 17 Jan 2024 12:12:14 +0100 Subject: [PATCH 35/81] fix: clear the connected devices list when bluetooth disconnected --- phyphox-iOS/phyphox/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index ea0eb315..828b35f8 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11129 + 11131 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 83a05e49930550bdde31fcce21acb942d7e1ce87 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Wed, 17 Jan 2024 15:57:31 +0100 Subject: [PATCH 36/81] fix: refresh the theme in experiment view as per the system appearance --- phyphox-iOS/phyphox/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 828b35f8..1a74735d 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11131 + 11132 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 662f8091f4e3c6ac3bf1ab2a54455f1d0e90add1 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Wed, 17 Jan 2024 19:22:53 +0100 Subject: [PATCH 37/81] fix: rebase to development --- phyphox-iOS/phyphox/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 1a74735d..0ef6b5af 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11132 + 11133 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 684e09de9693288982b70211c71474dafe931347 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Thu, 7 Sep 2023 10:26:01 +0200 Subject: [PATCH 38/81] ix: change the minimum deployment from 8 to 11 to fix the build error --- phyphox-iOS/phyphox/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 0ef6b5af..d6be274b 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11133 + 11284 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 588fdc400057b06627d36a84f346444bf9ebf6e2 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Mon, 11 Sep 2023 16:56:47 +0200 Subject: [PATCH 39/81] it status --- .gitmodules | 3 - phyphox-iOS/phyphox.xcodeproj/project.pbxproj | 20 +++ .../Data-Input/ExperimentCameraInput.swift | 112 ++++++++++++ .../ExperimentCameraInputSession.swift | 128 ++++++++++++++ .../phyphox/Experiments/Experiment.swift | 6 +- .../Handlers/InputElementHandler.swift | 59 ++++++- .../Handlers/PhyphoxElementHandler.swift | 23 ++- .../CameraViewElementHandler.swift | 40 +++++ .../Handlers/ViewsElementHandler.swift | 5 + .../CameraViewDescriptor.swift | 27 +++ phyphox-iOS/phyphox/Info.plist | 2 +- .../ExperimentPageViewController.swift | 8 + .../ExperimentViewModuleFactory.swift | 3 + .../Static/ExperimentCameraGUIView.swift | 166 ++++++++++++++++++ .../phyphox/en.lproj/Localizable.strings | 2 + 15 files changed, 594 insertions(+), 10 deletions(-) create mode 100644 phyphox-iOS/phyphox/Experiments/Data-Input/ExperimentCameraInput.swift create mode 100644 phyphox-iOS/phyphox/Experiments/Data-Input/ExperimentCameraInputSession.swift create mode 100644 phyphox-iOS/phyphox/Experiments/Serialization/Handlers/ViewHandlers/CameraViewElementHandler.swift create mode 100644 phyphox-iOS/phyphox/Experiments/ViewDescriptors/CameraViewDescriptor.swift create mode 100644 phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentCameraGUIView.swift diff --git a/.gitmodules b/.gitmodules index 78c717a7..09dbc9ac 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,9 +4,6 @@ [submodule "phyphox-webinterface"] path = phyphox-webinterface url = https://github.com/Kuhlen/phyphox-webinterface -[submodule "phyphox-iOS/CocoaMQTT"] - path = phyphox-iOS/CocoaMQTT - url = https://github.com/phyphox/CocoaMQTT.git [submodule "phyphox-iOS/ZipZap"] path = phyphox-iOS/ZipZap url = https://github.com/pixelglow/ZipZap.git diff --git a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj index a18c6ce1..5f98a4fe 100644 --- a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj +++ b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj @@ -161,6 +161,11 @@ 866632E92A29E1D90068762D /* UIColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 866632E82A29E1D90068762D /* UIColorExtensions.swift */; }; A1A6A72F297685DF00090309 /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1A6A72E297685DF00090309 /* Utility.swift */; }; A1A80D3D29AE049E00AD756D /* SettingsBundleHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1A80D3C29AE049E00AD756D /* SettingsBundleHelper.swift */; }; + A1B9C1BC2AA9E45900238AF6 /* ExperimentCameraInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B9C1BB2AA9E45900238AF6 /* ExperimentCameraInput.swift */; }; + A1B9C1BE2AA9E59D00238AF6 /* CameraViewDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B9C1BD2AA9E59D00238AF6 /* CameraViewDescriptor.swift */; }; + A1B9C1C02AA9E76C00238AF6 /* CameraViewElementHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B9C1BF2AA9E76C00238AF6 /* CameraViewElementHandler.swift */; }; + A1B9C1C22AA9E87300238AF6 /* ExperimentCameraInputSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B9C1C12AA9E87300238AF6 /* ExperimentCameraInputSession.swift */; }; + A1B9C1C42AA9EC9600238AF6 /* ExperimentCameraGUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B9C1C32AA9EC9600238AF6 /* ExperimentCameraGUIView.swift */; }; A1E03A7F29B61A9000CE93A6 /* UIImageExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1E03A7E29B61A9000CE93A6 /* UIImageExt.swift */; }; A1FA9A00299FCFBC00E0FDBF /* color.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A1FA99FF299FCFBC00E0FDBF /* color.xcassets */; }; A1FA9A0229A5309200E0FDBF /* PhyphoxUIAlertBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1FA9A0129A5309200E0FDBF /* PhyphoxUIAlertBuilder.swift */; }; @@ -487,6 +492,11 @@ A101AB0729BB801600B82311 /* ConnectedBluetoothDeviceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedBluetoothDeviceViewController.swift; sourceTree = ""; }; A1A6A72E297685DF00090309 /* Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = ""; }; A1A80D3C29AE049E00AD756D /* SettingsBundleHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsBundleHelper.swift; sourceTree = ""; }; + A1B9C1BB2AA9E45900238AF6 /* ExperimentCameraInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperimentCameraInput.swift; sourceTree = ""; }; + A1B9C1BD2AA9E59D00238AF6 /* CameraViewDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraViewDescriptor.swift; sourceTree = ""; }; + A1B9C1BF2AA9E76C00238AF6 /* CameraViewElementHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraViewElementHandler.swift; sourceTree = ""; }; + A1B9C1C12AA9E87300238AF6 /* ExperimentCameraInputSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperimentCameraInputSession.swift; sourceTree = ""; }; + A1B9C1C32AA9EC9600238AF6 /* ExperimentCameraGUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperimentCameraGUIView.swift; sourceTree = ""; }; A1E03A7E29B61A9000CE93A6 /* UIImageExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageExt.swift; sourceTree = ""; }; A1FA99FF299FCFBC00E0FDBF /* color.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = color.xcassets; sourceTree = ""; }; A1FA9A0129A5309200E0FDBF /* PhyphoxUIAlertBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhyphoxUIAlertBuilder.swift; sourceTree = ""; }; @@ -633,6 +643,7 @@ 4BE7CAF81E4CD73D00C31090 /* ExperimentSeparatorView.swift */, 4B5568B41DD7D1F4000C033E /* ExperimentButtonView.swift */, C034B0102716F9E500C0EF00 /* ExperimentDepthGUIView.swift */, + A1B9C1C32AA9EC9600238AF6 /* ExperimentCameraGUIView.swift */, C034B02227171E0100C0EF00 /* DepthGUI */, ); path = Static; @@ -773,6 +784,7 @@ 6BEA9454207FE8990077274A /* ButtonViewElementHandler.swift */, 6BEA9456207FF8CE0077274A /* GraphViewElementHandler.swift */, C034B01E2716FBC800C0EF00 /* DepthGUIViewElementHandler.swift */, + A1B9C1BF2AA9E76C00238AF6 /* CameraViewElementHandler.swift */, ); path = ViewHandlers; sourceTree = ""; @@ -954,6 +966,7 @@ C034B0202716FDE400C0EF00 /* DepthGUIDescriptor.swift */, 4BE7CADF1E4CD20F00C31090 /* SeparatorViewDescriptor.swift */, 4B5568B61DD7D846000C033E /* ButtonViewDescriptor.swift */, + A1B9C1BD2AA9E59D00238AF6 /* CameraViewDescriptor.swift */, ); path = ViewDescriptors; sourceTree = ""; @@ -1011,6 +1024,8 @@ C07B530C22942CC300BA085A /* InputConversion.swift */, C097E4442297DA1800FB62B9 /* OutputConversion.swift */, C0AE8A972294558000A3EC48 /* ConfigConversion.swift */, + A1B9C1BB2AA9E45900238AF6 /* ExperimentCameraInput.swift */, + A1B9C1C12AA9E87300238AF6 /* ExperimentCameraInputSession.swift */, ); path = "Data-Input"; sourceTree = ""; @@ -1508,6 +1523,7 @@ C063C666238ECF7A00327AE7 /* NetworkConversion.swift in Sources */, 6B612FF4207EA4E5001DA8D9 /* OutputElementHandler.swift in Sources */, 6BCD496720505DA70079E569 /* ExperimentGraphUtilities.swift in Sources */, + A1B9C1BE2AA9E59D00238AF6 /* CameraViewDescriptor.swift in Sources */, 6B612FFD207F70D9001DA8D9 /* ExperimentAnalysisFactory.swift in Sources */, C0A97FE72477DCBF00A630EE /* SortAnalysis.swift in Sources */, 4B47B0371DA6AF2400CCBA6E /* SinhAnalysis.swift in Sources */, @@ -1535,11 +1551,13 @@ 6BE3209F1CB19E0300DCC1ED /* CreatePresentationController.swift in Sources */, 4B3E495E1CF7716D00C43837 /* MatchAnalysis.swift in Sources */, 6BEA944D207FCA2F0077274A /* ValueViewElementHandler.swift in Sources */, + A1B9C1C22AA9E87300238AF6 /* ExperimentCameraInputSession.swift in Sources */, 6BB52BC61C1B11CA00217CF1 /* ExperimentTranslation.swift in Sources */, 6BE320AA1CB2B55700DCC1ED /* TextFieldTableViewCell.swift in Sources */, 6B7BF80D1C14723D007408FB /* CosAnalysis.swift in Sources */, 6BE320A11CB19E0300DCC1ED /* CreateViewControllerTransition.swift in Sources */, C0A3BE5B2390070900951C23 /* NetworkElementHandler.swift in Sources */, + A1B9C1C02AA9E76C00238AF6 /* CameraViewElementHandler.swift in Sources */, 6BBD28AC1C20BF29009E3757 /* InfoViewDescriptor.swift in Sources */, 4B09C6C51CD78E79004C7FA1 /* MinAnalysis.swift in Sources */, 6B7BF8231C147D09007408FB /* FFTAnalysis.swift in Sources */, @@ -1626,6 +1644,7 @@ A101AB0829BB801600B82311 /* ConnectedBluetoothDeviceViewController.swift in Sources */, C05CD336258BA0DF00662BDF /* ExperimentTimeReference.swift in Sources */, 6B7BF7F81C1374A6007408FB /* ExperimentAnalysisModule.swift in Sources */, + A1B9C1C42AA9EC9600238AF6 /* ExperimentCameraGUIView.swift in Sources */, 6B7BF8001C139649007408FB /* DivisionAnalysis.swift in Sources */, C07B530B229426AD00BA085A /* ExperimentBluetoothOutput.swift in Sources */, 6BBA063A1C518BC900E4B1B0 /* UpdateValueAnalysis.swift in Sources */, @@ -1647,6 +1666,7 @@ C034B02727172BF500C0EF00 /* Shaders.metal in Sources */, 6B6A4C202056A367007586D0 /* GLGraphShaderProgram.swift in Sources */, 6B8AA3F21C5E2A6400E59685 /* ExperimentComplexUpdateValueAnalysis.swift in Sources */, + A1B9C1BC2AA9E45900238AF6 /* ExperimentCameraInput.swift in Sources */, 6B422E2C1CA4A4BF00E945BC /* GraphGridView.swift in Sources */, 4B486EE11EC4B34D00F1045E /* CreditsView.swift in Sources */, 4B47B03D1DA6B12500CCBA6E /* BinningAnalysis.swift in Sources */, diff --git a/phyphox-iOS/phyphox/Experiments/Data-Input/ExperimentCameraInput.swift b/phyphox-iOS/phyphox/Experiments/Data-Input/ExperimentCameraInput.swift new file mode 100644 index 00000000..b71af450 --- /dev/null +++ b/phyphox-iOS/phyphox/Experiments/Data-Input/ExperimentCameraInput.swift @@ -0,0 +1,112 @@ +// +// ExperimentCameraInput.swift +// phyphox +// +// Created by Gaurav Tripathee on 07.09.23. +// Copyright © 2023 RWTH Aachen. All rights reserved. +// + + +import Foundation +import ARKit + + +final class ExperimentCameraInput { + + + enum CameraOrientation: String, LosslessStringConvertible, CaseIterable { + case front + case back + } + + let initx1: Float + let initx2: Float + let inity1: Float + let inity2: Float + + let smooth: Bool + + let timeReference: ExperimentTimeReference? + let zBuffer: DataBuffer? + let tBuffer: DataBuffer? + + lazy var session: Any? = nil + + private var queue: DispatchQueue? + + + init(timeReference: ExperimentTimeReference, zBuffer: DataBuffer?, tBuffer: DataBuffer?, x1: Float, x2: Float, y1: Float, y2: Float, smooth: Bool) { + + self.initx1 = x1 + self.initx2 = x2 + self.inity1 = y1 + self.inity2 = y2 + self.zBuffer = zBuffer + self.tBuffer = tBuffer + self.smooth = smooth + self.timeReference = timeReference + + + if #available(iOS 14.0, *) { + session = ExperimentCameraInputSession() + guard let session = session as? ExperimentCameraInputSession else { + return + } + + session.x1 = x1 + session.x2 = x2 + session.y1 = y1 + session.y2 = y2 + session.zBuffer = zBuffer + session.tBuffer = tBuffer + session.timeReference = timeReference + session.smooth = smooth + } + } + + + func start(queue: DispatchQueue) throws { + guard #available(iOS 14.0, *) else { + return + } + guard let session = session as? ExperimentCameraInputSession else { + return + } + try session.start(queue: queue) + } + + func stop() { + guard #available(iOS 14.0, *) else { + return + } + guard let session = session as? ExperimentCameraInputSession else { + return + } + session.stop() + } + + func clear() { + guard #available(iOS 14.0, *) else { + return + } + guard let session = session as? ExperimentCameraInputSession else { + return + } + session.clear() + } + +} + +extension ExperimentCameraInput: Equatable { + static func ==(lhs: ExperimentCameraInput, rhs: ExperimentCameraInput) -> Bool { + return lhs.initx1 == rhs.initx1 && + lhs.initx2 == rhs.initx2 && + lhs.inity1 == rhs.inity1 && + lhs.inity2 == rhs.inity2 && + lhs.timeReference == rhs.timeReference && + lhs.zBuffer == rhs.zBuffer && + lhs.tBuffer == rhs.tBuffer && + lhs.smooth == rhs.smooth + } +} + diff --git a/phyphox-iOS/phyphox/Experiments/Data-Input/ExperimentCameraInputSession.swift b/phyphox-iOS/phyphox/Experiments/Data-Input/ExperimentCameraInputSession.swift new file mode 100644 index 00000000..92e9003f --- /dev/null +++ b/phyphox-iOS/phyphox/Experiments/Data-Input/ExperimentCameraInputSession.swift @@ -0,0 +1,128 @@ +// +// ExperimentCameraInputSession.swift +// phyphox +// +// Created by Gaurav Tripathee on 07.09.23. +// Copyright © 2023 RWTH Aachen. All rights reserved. +// + +import Foundation +import AVFoundation + +@available(iOS 14.0, *) +final class ExperimentCameraInputSession: CameraGUISelectionDelegate { + + + var x1: Float = 0.4 + var x2: Float = 0.6 + var y1: Float = 0.4 + var y2: Float = 0.6 + + var running = false + + var frontCamera: Bool + + var timeReference: ExperimentTimeReference? + var zBuffer: DataBuffer? + var tBuffer: DataBuffer? + + var smooth: Bool = true + + var delegate: CameraGUIDelegate? + + var measuring = false + + private var queue: DispatchQueue? + + var cameraSession = AVCaptureSession() + + var screenRect: CGRect! = nil // For view dimensions + + init(){ + frontCamera = false + } + + + func runSession(){ + + + guard let videoDevice = AVCaptureDevice.default(.builtInDualWideCamera, for: AVMediaType.video, position: .back) else { return } + + guard let videoDeviceInput = try? AVCaptureDeviceInput(device: videoDevice) else { return } + + guard cameraSession.canAddInput(videoDeviceInput) else { return } + cameraSession.addInput(videoDeviceInput) + + cameraSession.startRunning() + + delegate?.updateFrame(captureSession: cameraSession) + } + + func stopSession() { + running = false + } + + func start(queue: DispatchQueue) throws { + self.queue = queue + measuring = true + } + + func stop() { + measuring = false + } + + func clear() { + + } + + func session() { + + } + + func frameIn(depthMap: UnsafeMutablePointer, confidenceMap: UnsafeMutablePointer?, w: Int, h: Int, t: TimeInterval) { + + let xp1 = Int(round(Float(w) * x1)) + let xp2 = Int(round(Float(w) * x2)) + let yp1 = Int(round(Float(h) * y1)) + let yp2 = Int(round(Float(h) * y2)) + let xi1 = max(min(xp1, xp2, w), 0) + let xi2 = min(max(xp1, xp2, 0), w) + let yi1 = max(min(yp1, yp2, h), 0) + let yi2 = min(max(yp1, yp2, 0), h) + + var z: Double + var sum = 0.0 + + } + + public func attachDelegate(delegate: CameraGUIDelegate) { + self.delegate = delegate + runSession() + } + + private func writeToBuffers(z: Double, t: TimeInterval) { + if let zBuffer = zBuffer { + zBuffer.append(z) + } + + if let tBuffer = tBuffer { + tBuffer.append(t) + } + } + + private func dataIn(z: Double, time: TimeInterval) { + guard let timeReference = timeReference else { + print("Error: time reference not set") + return + } + let t = timeReference.getExperimentTimeFromEvent(eventTime: time) + if t >= timeReference.timeMappings.last?.experimentTime ?? 0.0 { + queue?.async { + autoreleasepool(invoking: { + self.writeToBuffers(z: z, t: t) + }) + } + } + } +} + diff --git a/phyphox-iOS/phyphox/Experiments/Experiment.swift b/phyphox-iOS/phyphox/Experiments/Experiment.swift index ec50498e..b0049025 100644 --- a/phyphox-iOS/phyphox/Experiments/Experiment.swift +++ b/phyphox-iOS/phyphox/Experiments/Experiment.swift @@ -91,6 +91,7 @@ final class Experiment { let sensorInputs: [ExperimentSensorInput] let depthInput: ExperimentDepthInput? + let cameraInput: ExperimentCameraInput? let gpsInputs: [ExperimentGPSInput] let audioInputs: [ExperimentAudioInput] @@ -116,7 +117,7 @@ final class Experiment { private let queue = DispatchQueue(label: "de.rwth-aachen.phyphox.analysis", attributes: []) - init(title: String, stateTitle: String?, description: String?, links: [ExperimentLink], category: String, icon: ExperimentIcon, color: UIColor?, appleBan: Bool, isLink: Bool, translation: ExperimentTranslationCollection?, buffers: [String: DataBuffer], timeReference: ExperimentTimeReference, sensorInputs: [ExperimentSensorInput], depthInput: ExperimentDepthInput?, gpsInputs: [ExperimentGPSInput], audioInputs: [ExperimentAudioInput], audioOutput: ExperimentAudioOutput?, bluetoothDevices: [ExperimentBluetoothDevice], bluetoothInputs: [ExperimentBluetoothInput], bluetoothOutputs: [ExperimentBluetoothOutput], networkConnections: [NetworkConnection], viewDescriptors: [ExperimentViewCollectionDescriptor]?, analysis: ExperimentAnalysis, export: ExperimentExport?) { + init(title: String, stateTitle: String?, description: String?, links: [ExperimentLink], category: String, icon: ExperimentIcon, color: UIColor?, appleBan: Bool, isLink: Bool, translation: ExperimentTranslationCollection?, buffers: [String: DataBuffer], timeReference: ExperimentTimeReference, sensorInputs: [ExperimentSensorInput], depthInput: ExperimentDepthInput?, cameraInput: ExperimentCameraInput?, gpsInputs: [ExperimentGPSInput], audioInputs: [ExperimentAudioInput], audioOutput: ExperimentAudioOutput?, bluetoothDevices: [ExperimentBluetoothDevice], bluetoothInputs: [ExperimentBluetoothInput], bluetoothOutputs: [ExperimentBluetoothOutput], networkConnections: [NetworkConnection], viewDescriptors: [ExperimentViewCollectionDescriptor]?, analysis: ExperimentAnalysis, export: ExperimentExport?) { self.title = title self.stateTitle = stateTitle @@ -141,6 +142,7 @@ final class Experiment { self.buffers = buffers self.sensorInputs = sensorInputs self.depthInput = depthInput + self.cameraInput = cameraInput self.gpsInputs = gpsInputs self.audioInputs = audioInputs @@ -172,7 +174,7 @@ final class Experiment { } convenience init(file: String, error: String) { - self.init(title: file, stateTitle: nil, description: error, links: [], category: localize("unknown"), icon: ExperimentIcon.string("!"), color: UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0), appleBan: false, isLink: false, translation: nil, buffers: [:], timeReference: ExperimentTimeReference(), sensorInputs: [], depthInput: nil, gpsInputs: [], audioInputs: [], audioOutput: nil, bluetoothDevices: [], bluetoothInputs: [], bluetoothOutputs: [], networkConnections: [], viewDescriptors: nil, analysis: ExperimentAnalysis(modules: [], sleep: 0.0, dynamicSleep: nil, onUserInput: false, requireFill: nil, requireFillThreshold: 1, requireFillDynamic: nil, timedRun: false, timedRunStartDelay: 0.0, timedRunStopDelay: 0.0, timeReference: ExperimentTimeReference(), sensorInputs: [], audioInputs: []), export: nil) + self.init(title: file, stateTitle: nil, description: error, links: [], category: localize("unknown"), icon: ExperimentIcon.string("!"), color: UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0), appleBan: false, isLink: false, translation: nil, buffers: [:], timeReference: ExperimentTimeReference(), sensorInputs: [], depthInput: nil, cameraInput: nil, gpsInputs: [], audioInputs: [], audioOutput: nil, bluetoothDevices: [], bluetoothInputs: [], bluetoothOutputs: [], networkConnections: [], viewDescriptors: nil, analysis: ExperimentAnalysis(modules: [], sleep: 0.0, dynamicSleep: nil, onUserInput: false, requireFill: nil, requireFillThreshold: 1, requireFillDynamic: nil, timedRun: false, timedRunStartDelay: 0.0, timedRunStopDelay: 0.0, timeReference: ExperimentTimeReference(), sensorInputs: [], audioInputs: []), export: nil) invalid = true; } diff --git a/phyphox-iOS/phyphox/Experiments/Serialization/Handlers/InputElementHandler.swift b/phyphox-iOS/phyphox/Experiments/Serialization/Handlers/InputElementHandler.swift index bb088901..04c966fe 100644 --- a/phyphox-iOS/phyphox/Experiments/Serialization/Handlers/InputElementHandler.swift +++ b/phyphox-iOS/phyphox/Experiments/Serialization/Handlers/InputElementHandler.swift @@ -118,6 +118,57 @@ private final class DepthElementHandler: ResultElementHandler, LookupElementHand } } +struct CameraInputDescriptor: SensorDescriptor { + let x1: Float + let x2: Float + let y1: Float + let y2: Float + let smooth: Bool + let outputs: [SensorOutputDescriptor] +} + +private final class CameraElementHandler: ResultElementHandler, LookupElementHandler { + var results = [CameraInputDescriptor]() + + private let outputHandler = SensorOutputElementHandler() + + var childHandlers: [String : ElementHandler] + + init() { + childHandlers = ["output": outputHandler] + } + + func startElement(attributes: AttributeContainer) throws {} + + private enum Attribute: String, AttributeKey { + case mode + case x1 + case x2 + case y1 + case y2 + case smooth + } + + func endElement(text: String, attributes: AttributeContainer) throws { + let attributes = attributes.attributes(keyedBy: Attribute.self) + + let x1user: Float = try attributes.optionalValue(for: .x1) ?? 0.4 + let x2user: Float = try attributes.optionalValue(for: .x2) ?? 0.6 + let y1user: Float = try attributes.optionalValue(for: .y1) ?? 0.4 + let y2user: Float = try attributes.optionalValue(for: .y2) ?? 0.6 + + //Careful: We will now switch from the user coordinate system to the camera coordinate system: x -> -y, y -> -x + let x1 = 1.0-y1user + let x2 = 1.0-y2user + let y1 = 1.0-x1user + let y2 = 1.0-x2user + + let smooth: Bool = try attributes.optionalValue(for: .smooth) ?? true + + results.append(CameraInputDescriptor( x1: x1, x2: x2, y1: y1, y2: y2, smooth: smooth, outputs: outputHandler.results)) + } +} + struct SensorInputDescriptor: SensorDescriptor { let sensor: SensorType let rate: Double @@ -413,7 +464,7 @@ private final class BluetoothElementHandler: ResultElementHandler, LookupElement } final class InputElementHandler: ResultElementHandler, LookupElementHandler, AttributelessElementHandler { - typealias Result = (sensors: [SensorInputDescriptor], depth: [DepthInputDescriptor], audio: [AudioInputDescriptor], location: [LocationInputDescriptor], bluetooth: [BluetoothInputBlockDescriptor]) + typealias Result = (sensors: [SensorInputDescriptor], depth: [DepthInputDescriptor], camera: [CameraInputDescriptor], audio: [AudioInputDescriptor], location: [LocationInputDescriptor], bluetooth: [BluetoothInputBlockDescriptor]) var results = [Result]() @@ -422,11 +473,12 @@ final class InputElementHandler: ResultElementHandler, LookupElementHandler, Att private let audioHandler = AudioElementHandler() private let locationHandler = LocationElementHandler() private let bluetoothHandler = BluetoothElementHandler() + private let cameraHandler = CameraElementHandler() var childHandlers: [String: ElementHandler] init() { - childHandlers = ["sensor": sensorHandler, "depth": depthHandler, "audio": audioHandler, "location": locationHandler, "bluetooth": bluetoothHandler] + childHandlers = ["sensor": sensorHandler, "depth": depthHandler, "camera": cameraHandler, "audio": audioHandler, "location": locationHandler, "bluetooth": bluetoothHandler] } func endElement(text: String, attributes: AttributeContainer) throws { @@ -434,9 +486,10 @@ final class InputElementHandler: ResultElementHandler, LookupElementHandler, Att let location = locationHandler.results let sensors = sensorHandler.results let depth = depthHandler.results + let camera = cameraHandler.results let bluetooth = bluetoothHandler.results - results.append((sensors, depth, audio, location, bluetooth)) + results.append((sensors, depth, camera, audio, location, bluetooth)) } } diff --git a/phyphox-iOS/phyphox/Experiments/Serialization/Handlers/PhyphoxElementHandler.swift b/phyphox-iOS/phyphox/Experiments/Serialization/Handlers/PhyphoxElementHandler.swift index 14cbfe56..0b483a82 100644 --- a/phyphox-iOS/phyphox/Experiments/Serialization/Handlers/PhyphoxElementHandler.swift +++ b/phyphox-iOS/phyphox/Experiments/Serialization/Handlers/PhyphoxElementHandler.swift @@ -38,6 +38,15 @@ private extension ExperimentDepthInput { } } +private extension ExperimentCameraInput { + convenience init(descriptor: CameraInputDescriptor, timeReference: ExperimentTimeReference, buffers: [String: DataBuffer]) { + let zBuffer = descriptor.buffer(for: "z", from: buffers) + let tBuffer = descriptor.buffer(for: "t", from: buffers) + + self.init(timeReference: timeReference, zBuffer: zBuffer, tBuffer: tBuffer, x1: descriptor.x1, x2: descriptor.x2, y1: descriptor.y1, y2: descriptor.y2, smooth: descriptor.smooth) + } +} + private extension ExperimentGPSInput { convenience init(descriptor: LocationInputDescriptor, timeReference: ExperimentTimeReference, buffers: [String: DataBuffer]) { let latBuffer = descriptor.buffer(for: "lat", from: buffers) @@ -207,6 +216,13 @@ final class PhyphoxElementHandler: ResultElementHandler, LookupElementHandler { throw ElementHandlerError.message("Depth is only allowed once.") } let depthInput = depthInputs?.first + + let cameraInputs = inputDescriptor?.camera.map {ExperimentCameraInput(descriptor: $0, timeReference: timeReference, buffers: buffers) } + if cameraInputs != nil && depthInputs!.count > 1 { + throw ElementHandlerError.message("Camera is only allowed once.") + } + let cameraInput = cameraInputs?.first + let gpsInputs = inputDescriptor?.location.map { ExperimentGPSInput(descriptor: $0, timeReference: timeReference, buffers: buffers) } ?? [] let audioInputs = try inputDescriptor?.audio.map { try ExperimentAudioInput(descriptor: $0, buffers: buffers) } ?? [] @@ -343,7 +359,7 @@ final class PhyphoxElementHandler: ResultElementHandler, LookupElementHandler { let viewDescriptors = try viewCollectionDescriptors?.map { ExperimentViewCollectionDescriptor(label: $0.label, translation: translations, views: try $0.views.map { try makeViewDescriptor(from: $0, timeReference: timeReference, buffers: buffers, translations: translations) }) } - let experiment = Experiment(title: title, stateTitle: stateTitle, description: description, links: links, category: category, icon: icon, color: color, appleBan: appleBan, isLink: isLink, translation: translations, buffers: buffers, timeReference: timeReference, sensorInputs: sensorInputs, depthInput: depthInput, gpsInputs: gpsInputs, audioInputs: audioInputs, audioOutput: audioOutput, bluetoothDevices: bluetoothDevices, bluetoothInputs: bluetoothInputs, bluetoothOutputs: bluetoothOutputs, networkConnections: networkConnections, viewDescriptors: viewDescriptors, analysis: analysis, export: export) + let experiment = Experiment(title: title, stateTitle: stateTitle, description: description, links: links, category: category, icon: icon, color: color, appleBan: appleBan, isLink: isLink, translation: translations, buffers: buffers, timeReference: timeReference, sensorInputs: sensorInputs, depthInput: depthInput, cameraInput: cameraInput, gpsInputs: gpsInputs, audioInputs: audioInputs, audioOutput: audioOutput, bluetoothDevices: bluetoothDevices, bluetoothInputs: bluetoothInputs, bluetoothOutputs: bluetoothOutputs, networkConnections: networkConnections, viewDescriptors: viewDescriptors, analysis: analysis, export: export) results.append(experiment) } @@ -431,7 +447,12 @@ final class PhyphoxElementHandler: ResultElementHandler, LookupElementHandler { return GraphViewDescriptor(label: descriptor.label, translation: translations, xLabel: descriptor.xLabel, yLabel: descriptor.yLabel, zLabel: descriptor.zLabel, xUnit: descriptor.xUnit, yUnit: descriptor.yUnit, zUnit: descriptor.zUnit, yxUnit: descriptor.yxUnit, timeReference: timeReference, timeOnX: descriptor.timeOnX, timeOnY: descriptor.timeOnY, systemTime: descriptor.systemTime, linearTime: descriptor.linearTime, hideTimeMarkers: descriptor.hideTimeMarkers, xInputBuffers: xBuffers, yInputBuffers: yBuffers, zInputBuffers: zBuffers, logX: descriptor.logX, logY: descriptor.logY, logZ: descriptor.logZ, xPrecision: descriptor.xPrecision, yPrecision: descriptor.yPrecision, zPrecision: descriptor.zPrecision, scaleMinX: descriptor.scaleMinX, scaleMaxX: descriptor.scaleMaxX, scaleMinY: descriptor.scaleMinY, scaleMaxY: descriptor.scaleMaxY, scaleMinZ: descriptor.scaleMinZ, scaleMaxZ: descriptor.scaleMaxZ, minX: descriptor.minX, maxX: descriptor.maxX, minY: descriptor.minY, maxY: descriptor.maxY, minZ: descriptor.minZ, maxZ: descriptor.maxZ, followX: descriptor.followX, aspectRatio: descriptor.aspectRatio, partialUpdate: descriptor.partialUpdate, history: descriptor.history, style: descriptor.style, lineWidth: descriptor.lineWidth, color: descriptor.color, mapWidth: descriptor.mapWidth, colorMap: descriptor.colorMap) case .depthGUI(let descriptor): return DepthGUIViewDescriptor(label: descriptor.label, aspectRatio: descriptor.aspectRatio, translation: translations) + + + case .camera(let descriptor): + return CameraViewDescriptor(label: descriptor.label, aspectRatio: descriptor.aspectRatio, translation: translations) } + } private func makeAudioOutput(from descriptor: AudioOutputDescriptor?, buffers: [String: DataBuffer]) throws -> ExperimentAudioOutput? { diff --git a/phyphox-iOS/phyphox/Experiments/Serialization/Handlers/ViewHandlers/CameraViewElementHandler.swift b/phyphox-iOS/phyphox/Experiments/Serialization/Handlers/ViewHandlers/CameraViewElementHandler.swift new file mode 100644 index 00000000..245d9e2e --- /dev/null +++ b/phyphox-iOS/phyphox/Experiments/Serialization/Handlers/ViewHandlers/CameraViewElementHandler.swift @@ -0,0 +1,40 @@ +// +// CameraViewElementHandler.swift +// phyphox +// +// Created by Gaurav Tripathee on 07.09.23. +// Copyright © 2023 RWTH Aachen. All rights reserved. +// + +import Foundation + +struct CameraViewElementDescriptor { + let label: String + let aspectRatio: CGFloat +} + +final class CameraViewElementHandler: ResultElementHandler, ChildlessElementHandler, ViewComponentElementHandler { + var results = [ViewElementDescriptor]() + + func startElement(attributes: AttributeContainer) throws {} + + private enum Attribute: String, AttributeKey { + case label + case aspectRatio + } + + func endElement(text: String, attributes: AttributeContainer) throws { + let attributes = attributes.attributes(keyedBy: Attribute.self) + + let label = attributes.optionalString(for: .label) ?? "" + + let aspectRatio: CGFloat = try attributes.optionalValue(for: .aspectRatio) ?? 2.5 + + results.append(.camera(CameraViewElementDescriptor(label: label, aspectRatio: aspectRatio))) + } + + func nextResult() throws -> ViewElementDescriptor { + guard !results.isEmpty else { throw ElementHandlerError.missingElement("") } + return results.removeFirst() + } +} diff --git a/phyphox-iOS/phyphox/Experiments/Serialization/Handlers/ViewsElementHandler.swift b/phyphox-iOS/phyphox/Experiments/Serialization/Handlers/ViewsElementHandler.swift index 04386224..3da40354 100644 --- a/phyphox-iOS/phyphox/Experiments/Serialization/Handlers/ViewsElementHandler.swift +++ b/phyphox-iOS/phyphox/Experiments/Serialization/Handlers/ViewsElementHandler.swift @@ -18,6 +18,7 @@ enum ViewElementDescriptor { case button(ButtonViewElementDescriptor) case graph(GraphViewElementDescriptor) case depthGUI(DepthGUIViewElementDescriptor) + case camera(CameraViewElementDescriptor) } protocol ViewComponentElementHandler: ElementHandler { @@ -39,6 +40,7 @@ private final class ViewElementHandler: ResultElementHandler { private let buttonhandler = ButtonViewElementHandler() private let graphHandler = GraphViewElementHandler() private let depthGUIHandler = DepthGUIViewElementHandler() + private let cameraHandler = CameraViewElementHandler() private var elementOrder = [ViewComponentElementHandler]() @@ -62,6 +64,8 @@ private final class ViewElementHandler: ResultElementHandler { handler = graphHandler case "depth-gui": handler = depthGUIHandler + case "camera-gui": + handler = cameraHandler default: throw ElementHandlerError.unexpectedChildElement(elementName) } @@ -95,6 +99,7 @@ private final class ViewElementHandler: ResultElementHandler { buttonhandler.clear() graphHandler.clear() depthGUIHandler.clear() + cameraHandler.clear() } } diff --git a/phyphox-iOS/phyphox/Experiments/ViewDescriptors/CameraViewDescriptor.swift b/phyphox-iOS/phyphox/Experiments/ViewDescriptors/CameraViewDescriptor.swift new file mode 100644 index 00000000..c282c496 --- /dev/null +++ b/phyphox-iOS/phyphox/Experiments/ViewDescriptors/CameraViewDescriptor.swift @@ -0,0 +1,27 @@ +// +// CameraViewDescriptor.swift +// phyphox +// +// Created by Gaurav Tripathee on 07.09.23. +// Copyright © 2023 RWTH Aachen. All rights reserved. +// + +import Foundation + +struct CameraViewDescriptor: ViewDescriptor, Equatable { + let label: String + let aspectRatio: CGFloat + + let translation: ExperimentTranslationCollection? + + init(label: String, aspectRatio: CGFloat, translation: ExperimentTranslationCollection?) { + self.label = label + self.aspectRatio = aspectRatio + self.translation = translation + } + + func generateViewHTMLWithID(_ id: Int) -> String { + let warningText = localize("remoteCameraWarning").replacingOccurrences(of: "\"", with: "\\\"") + return "
\(localizedLabel)
" + } +} diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index d6be274b..70e00407 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11284 + 11316 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift index 3258fa4f..6663af36 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift @@ -510,6 +510,14 @@ final class ExperimentPageViewController: UIViewController, UIPageViewController session.attachDelegate(delegate: depthGUI) depthGUI.depthGUISelectionDelegate = session } + + if let cameraGUI = view as? ExperimentCameraGUIView { + guard let session = experiment.cameraInput?.session as? ExperimentCameraInputSession else { + continue + } + session.attachDelegate(delegate: cameraGUI) + cameraGUI.depthGUISelectionDelegate = session + } } } } diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/ExperimentViewModuleFactory.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/ExperimentViewModuleFactory.swift index f2111310..06d94b92 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/ExperimentViewModuleFactory.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/ExperimentViewModuleFactory.swift @@ -50,6 +50,9 @@ final class ExperimentViewModuleFactory { //Should not happen as the depth input is marked as unavailable below iOS 14 } } + else if let descriptor = descriptor as? CameraViewDescriptor { + views.append(ExperimentCameraGUIView(descriptor: descriptor)) + } else { print("Error! Invalid view descriptor: \(descriptor)") } diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentCameraGUIView.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentCameraGUIView.swift new file mode 100644 index 00000000..8508c040 --- /dev/null +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentCameraGUIView.swift @@ -0,0 +1,166 @@ +// +// ExperimentCameraView.swift +// phyphox +// +// Created by Gaurav Tripathee on 07.09.23. +// Copyright © 2023 RWTH Aachen. All rights reserved. +// + +import Foundation +import AVFoundation + +protocol CameraGUIDelegate { + func updateFrame(captureSession: AVCaptureSession) + func updateResolution(resolution: CGSize) +} + +protocol CameraGUISelectionDelegate { + var x1: Float { get set } + var x2: Float { get set } + var y1: Float { get set } + var y2: Float { get set } + var frontCamera: Bool { get set } +} + + +final class ExperimentCameraGUIView: UIView, CameraGUIDelegate { + + var videoPreviewLayer: AVCaptureVideoPreviewLayer? + + func updateFrame(captureSession: AVCaptureSession) { + videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession) + layoutSubviews() + } + + var resolution: CGSize? + var depthGUISelectionDelegate: CameraGUISelectionDelegate? + + func updateResolution(resolution: CGSize) { + self.resolution = resolution + setNeedsLayout() + } + + + var layoutDelegate: ModuleExclusiveLayoutDelegate? = nil + var resizableState: ResizableViewModuleState = .normal + + let unfoldMoreImageView: UIImageView + let unfoldLessImageView: UIImageView + + let descriptor: CameraViewDescriptor + let spacing: CGFloat = 1.0 + private let sideMargins:CGFloat = 10.0 + private let buttonPadding: CGFloat = 20.0 + + + var screenRect: CGRect! = nil // For view dimensions + private let label = UILabel() + + + private let aggregationBtn = UIButton() + private let cameraBtn = UIButton() + + var panGestureRecognizer: UIPanGestureRecognizer? = nil + + required init?(descriptor: CameraViewDescriptor) { + self.descriptor = descriptor + + unfoldLessImageView = UIImageView(image: UIImage(named: "unfold_less")) + unfoldMoreImageView = UIImageView(image: UIImage(named: "unfold_more")) + + + aggregationBtn.backgroundColor = UIColor(named: "lightBackgroundColor") + aggregationBtn.setTitle(localize("depthAggregationMode"), for: UIControl.State()) + aggregationBtn.isHidden = true + + cameraBtn.backgroundColor = UIColor(named: "lightBackgroundColor") + cameraBtn.setTitle(localize("sensorCamera"), for: UIControl.State()) + cameraBtn.isHidden = true + + super.init(frame: .zero) + + label.numberOfLines = 0 + label.text = descriptor.localizedLabel + label.font = UIFont.preferredFont(forTextStyle: .body) + label.textColor = UIColor(named: "textColor") + + addSubview(label) + + + + let unfoldRect = CGRect(x: 5, y: 5, width: 20, height: 20) + unfoldMoreImageView.frame = unfoldRect + unfoldLessImageView.frame = unfoldRect + unfoldLessImageView.isHidden = true + unfoldMoreImageView.isHidden = false + + addSubview(unfoldMoreImageView) + addSubview(unfoldLessImageView) + + addSubview(aggregationBtn) + addSubview(cameraBtn) + + + } + + + @available(*, unavailable) + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func sizeThatFits(_ size: CGSize) -> CGSize { + switch resizableState { + case .exclusive: + return size + case .hidden: + return CGSize.init(width: 0, height: 0) + default: + let labelh = label.sizeThatFits(size).height + + return CGSize(width: size.width, height: Swift.min((size.width-2*sideMargins)/descriptor.aspectRatio + labelh + 2*spacing, size.height)) + } + } + + override func layoutSubviews() { + super.layoutSubviews() + + videoPreviewLayer?.frame = bounds + videoPreviewLayer?.videoGravity = .resizeAspectFill + + if(videoPreviewLayer != nil){ + layer.addSublayer(videoPreviewLayer!) + } + + + + } + + func resizableStateChanged(_ newState: ResizableViewModuleState) { + + } + + @objc func tapped(_ sender: UITapGestureRecognizer) { + if resizableState == .normal { + layoutDelegate?.presentExclusiveLayout(self) + } else { + layoutDelegate?.restoreLayout() + } + } + + var panningIndexX: Int = 0 + var panningIndexY: Int = 0 + @objc func panned (_ sender: UIPanGestureRecognizer) { + guard var del = depthGUISelectionDelegate else { + return + } + } + + @objc private func aggregationBtnPressed() { + } + + + @objc private func cameraBtnPressed() { + } + +} diff --git a/phyphox-iOS/phyphox/en.lproj/Localizable.strings b/phyphox-iOS/phyphox/en.lproj/Localizable.strings index 4946a38d..bcee3e88 100644 --- a/phyphox-iOS/phyphox/en.lproj/Localizable.strings +++ b/phyphox-iOS/phyphox/en.lproj/Localizable.strings @@ -152,6 +152,7 @@ "graph_fit_label" = "Linear fit: y = a x + b"; "remoteColorMapWarning" = "The color plot in the remote interface is only an approximation. In contrast to the in-app plot, at the moment it cannot handle non-equidistant data or logarithmic scaling on the x and y axis. So, data at varying intervals may appear at the wrong location."; "remoteDepthGUIWarning" = "Previewing and controlling the LiDAR/ToF sensor on the remote interface is not supported."; +"remoteCameraWarning" = "Previewing and controlling the camera sensor on the remote interface is not supported."; "applyZoomTitle" = "Apply zoom"; "applyZoomExplanation" = "As you are closing the interactive mode of this graph, you can select if you want to keep your zoom."; "applyZoomAdvanced" = "Advanced options"; @@ -262,6 +263,7 @@ "common_unit_short_hecto_pascal" = "hPa"; "common_unit_short_decibel" = "dB"; "common_unit_short_percent" = "%"; +"common_unit_short_luminance" = "lum"; "common_quantity_short_time" = "t"; "common_quantity_short_angular_velocity" = "ω"; "common_quantity_short_magnetic_field" = "B"; From 8d59faaf123f3c293dda369f9629501b25a3b34f Mon Sep 17 00:00:00 2001 From: GTripathee Date: Tue, 2 Jan 2024 09:45:40 +0100 Subject: [PATCH 40/81] feature: setup and show camera (preview not displaying) --- .../Image 1.imageset/Contents.json | 20 + .../flip_camera.imageset/Contents.json | 32 ++ .../ic_flip_camera_android-4 1.svg | 1 + .../ic_flip_camera_android-4.svg | 1 + .../ic_flip_camera_android.svg | 1 + .../ic_auto_exposure.imageset/Contents.json | 32 ++ .../ic_auto_exposure-3 1.svg | 1 + .../ic_auto_exposure-3.svg | 1 + .../ic_auto_exposure.svg | 1 + .../ic_camera_iso.imageset/Contents.json | 32 ++ .../ic_camera_iso-2 1.svg | 1 + .../ic_camera_iso-2.svg | 1 + .../ic_camera_iso.imageset/ic_camera_iso.svg | 1 + .../ic_exposure.imageset/Contents.json | 32 ++ .../ic_exposure.imageset/ic_exposure-2 1.svg | 1 + .../ic_exposure.imageset/ic_exposure-2.svg | 1 + .../ic_exposure.imageset/ic_exposure.svg | 1 + .../ic_shutter_speed.imageset/Contents.json | 32 ++ .../baseline_shutter_speed_24-2 1.svg | 1 + .../baseline_shutter_speed_24-2.svg | 1 + .../baseline_shutter_speed_24.svg | 1 + .../ic_white_balance.imageset/Contents.json | 31 ++ .../ic_white_balance.png | Bin 0 -> 12385 bytes .../white-balance-2.png | Bin 0 -> 14592 bytes .../ic_zoom.imageset/Contents.json | 32 ++ .../ic_zoom.imageset/ic_zoom-2 1.svg | 1 + .../ic_zoom.imageset/ic_zoom-2.svg | 1 + .../ic_zoom.imageset/ic_zoom.svg | 1 + phyphox-iOS/phyphox/Camera/CameraInput.swift | 11 + phyphox-iOS/phyphox/Camera/CameraModel.swift | 65 +++ .../phyphox/Camera/CameraPreview.swift | 41 ++ .../phyphox/Camera/CameraService+Enums.swift | 19 + .../phyphox/Camera/CameraService.swift | 471 ++++++++++++++++++ phyphox-iOS/phyphox/Camera/CameraView.swift | 363 ++++++++++++++ .../phyphox/Camera/MetalRenderer.swift | 319 ++++++++++++ phyphox-iOS/phyphox/Camera/MetalView.swift | 47 ++ .../ExperimentCameraInputSession.swift | 98 +++- phyphox-iOS/phyphox/Info.plist | 2 +- .../ExperimentPageViewController.swift | 11 +- .../ExperimentViewModuleFactory.swift | 8 +- .../DepthGUI/ExperimentDepthGUIRenderer.swift | 2 +- .../Static/ExperimentCameraGUIView.swift | 219 ++++---- .../Static/ExperimentDepthGUIView.swift | 2 +- 43 files changed, 1828 insertions(+), 111 deletions(-) create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/Image 1.imageset/Contents.json create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/flip_camera.imageset/Contents.json create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/flip_camera.imageset/ic_flip_camera_android-4 1.svg create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/flip_camera.imageset/ic_flip_camera_android-4.svg create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/flip_camera.imageset/ic_flip_camera_android.svg create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/ic_auto_exposure.imageset/Contents.json create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/ic_auto_exposure.imageset/ic_auto_exposure-3 1.svg create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/ic_auto_exposure.imageset/ic_auto_exposure-3.svg create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/ic_auto_exposure.imageset/ic_auto_exposure.svg create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/ic_camera_iso.imageset/Contents.json create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/ic_camera_iso.imageset/ic_camera_iso-2 1.svg create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/ic_camera_iso.imageset/ic_camera_iso-2.svg create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/ic_camera_iso.imageset/ic_camera_iso.svg create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/ic_exposure.imageset/Contents.json create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/ic_exposure.imageset/ic_exposure-2 1.svg create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/ic_exposure.imageset/ic_exposure-2.svg create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/ic_exposure.imageset/ic_exposure.svg create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/ic_shutter_speed.imageset/Contents.json create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/ic_shutter_speed.imageset/baseline_shutter_speed_24-2 1.svg create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/ic_shutter_speed.imageset/baseline_shutter_speed_24-2.svg create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/ic_shutter_speed.imageset/baseline_shutter_speed_24.svg create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/ic_white_balance.imageset/Contents.json create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/ic_white_balance.imageset/ic_white_balance.png create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/ic_white_balance.imageset/white-balance-2.png create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/ic_zoom.imageset/Contents.json create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/ic_zoom.imageset/ic_zoom-2 1.svg create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/ic_zoom.imageset/ic_zoom-2.svg create mode 100644 phyphox-iOS/phyphox/Assets.xcassets/ic_zoom.imageset/ic_zoom.svg create mode 100644 phyphox-iOS/phyphox/Camera/CameraInput.swift create mode 100644 phyphox-iOS/phyphox/Camera/CameraModel.swift create mode 100644 phyphox-iOS/phyphox/Camera/CameraPreview.swift create mode 100644 phyphox-iOS/phyphox/Camera/CameraService+Enums.swift create mode 100644 phyphox-iOS/phyphox/Camera/CameraService.swift create mode 100644 phyphox-iOS/phyphox/Camera/CameraView.swift create mode 100644 phyphox-iOS/phyphox/Camera/MetalRenderer.swift create mode 100644 phyphox-iOS/phyphox/Camera/MetalView.swift diff --git a/phyphox-iOS/phyphox/Assets.xcassets/Image 1.imageset/Contents.json b/phyphox-iOS/phyphox/Assets.xcassets/Image 1.imageset/Contents.json new file mode 100644 index 00000000..a19a5492 --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/Image 1.imageset/Contents.json @@ -0,0 +1,20 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/phyphox-iOS/phyphox/Assets.xcassets/flip_camera.imageset/Contents.json b/phyphox-iOS/phyphox/Assets.xcassets/flip_camera.imageset/Contents.json new file mode 100644 index 00000000..a2b28095 --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/flip_camera.imageset/Contents.json @@ -0,0 +1,32 @@ +{ + "images" : [ + { + "filename" : "ic_flip_camera_android-4 1.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "light" + } + ], + "filename" : "ic_flip_camera_android.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "ic_flip_camera_android-4.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/phyphox-iOS/phyphox/Assets.xcassets/flip_camera.imageset/ic_flip_camera_android-4 1.svg b/phyphox-iOS/phyphox/Assets.xcassets/flip_camera.imageset/ic_flip_camera_android-4 1.svg new file mode 100644 index 00000000..58202e49 --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/flip_camera.imageset/ic_flip_camera_android-4 1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phyphox-iOS/phyphox/Assets.xcassets/flip_camera.imageset/ic_flip_camera_android-4.svg b/phyphox-iOS/phyphox/Assets.xcassets/flip_camera.imageset/ic_flip_camera_android-4.svg new file mode 100644 index 00000000..58202e49 --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/flip_camera.imageset/ic_flip_camera_android-4.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phyphox-iOS/phyphox/Assets.xcassets/flip_camera.imageset/ic_flip_camera_android.svg b/phyphox-iOS/phyphox/Assets.xcassets/flip_camera.imageset/ic_flip_camera_android.svg new file mode 100644 index 00000000..76ab9374 --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/flip_camera.imageset/ic_flip_camera_android.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phyphox-iOS/phyphox/Assets.xcassets/ic_auto_exposure.imageset/Contents.json b/phyphox-iOS/phyphox/Assets.xcassets/ic_auto_exposure.imageset/Contents.json new file mode 100644 index 00000000..e0cbab24 --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/ic_auto_exposure.imageset/Contents.json @@ -0,0 +1,32 @@ +{ + "images" : [ + { + "filename" : "ic_auto_exposure-3 1.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "light" + } + ], + "filename" : "ic_auto_exposure.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "ic_auto_exposure-3.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/phyphox-iOS/phyphox/Assets.xcassets/ic_auto_exposure.imageset/ic_auto_exposure-3 1.svg b/phyphox-iOS/phyphox/Assets.xcassets/ic_auto_exposure.imageset/ic_auto_exposure-3 1.svg new file mode 100644 index 00000000..0cdd1482 --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/ic_auto_exposure.imageset/ic_auto_exposure-3 1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phyphox-iOS/phyphox/Assets.xcassets/ic_auto_exposure.imageset/ic_auto_exposure-3.svg b/phyphox-iOS/phyphox/Assets.xcassets/ic_auto_exposure.imageset/ic_auto_exposure-3.svg new file mode 100644 index 00000000..0cdd1482 --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/ic_auto_exposure.imageset/ic_auto_exposure-3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phyphox-iOS/phyphox/Assets.xcassets/ic_auto_exposure.imageset/ic_auto_exposure.svg b/phyphox-iOS/phyphox/Assets.xcassets/ic_auto_exposure.imageset/ic_auto_exposure.svg new file mode 100644 index 00000000..8c1e2c63 --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/ic_auto_exposure.imageset/ic_auto_exposure.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phyphox-iOS/phyphox/Assets.xcassets/ic_camera_iso.imageset/Contents.json b/phyphox-iOS/phyphox/Assets.xcassets/ic_camera_iso.imageset/Contents.json new file mode 100644 index 00000000..9db96c28 --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/ic_camera_iso.imageset/Contents.json @@ -0,0 +1,32 @@ +{ + "images" : [ + { + "filename" : "ic_camera_iso-2.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "light" + } + ], + "filename" : "ic_camera_iso.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "ic_camera_iso-2 1.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/phyphox-iOS/phyphox/Assets.xcassets/ic_camera_iso.imageset/ic_camera_iso-2 1.svg b/phyphox-iOS/phyphox/Assets.xcassets/ic_camera_iso.imageset/ic_camera_iso-2 1.svg new file mode 100644 index 00000000..d816efe8 --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/ic_camera_iso.imageset/ic_camera_iso-2 1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phyphox-iOS/phyphox/Assets.xcassets/ic_camera_iso.imageset/ic_camera_iso-2.svg b/phyphox-iOS/phyphox/Assets.xcassets/ic_camera_iso.imageset/ic_camera_iso-2.svg new file mode 100644 index 00000000..d816efe8 --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/ic_camera_iso.imageset/ic_camera_iso-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phyphox-iOS/phyphox/Assets.xcassets/ic_camera_iso.imageset/ic_camera_iso.svg b/phyphox-iOS/phyphox/Assets.xcassets/ic_camera_iso.imageset/ic_camera_iso.svg new file mode 100644 index 00000000..a7c0737b --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/ic_camera_iso.imageset/ic_camera_iso.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phyphox-iOS/phyphox/Assets.xcassets/ic_exposure.imageset/Contents.json b/phyphox-iOS/phyphox/Assets.xcassets/ic_exposure.imageset/Contents.json new file mode 100644 index 00000000..9776bd02 --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/ic_exposure.imageset/Contents.json @@ -0,0 +1,32 @@ +{ + "images" : [ + { + "filename" : "ic_exposure-2 1.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "light" + } + ], + "filename" : "ic_exposure.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "ic_exposure-2.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/phyphox-iOS/phyphox/Assets.xcassets/ic_exposure.imageset/ic_exposure-2 1.svg b/phyphox-iOS/phyphox/Assets.xcassets/ic_exposure.imageset/ic_exposure-2 1.svg new file mode 100644 index 00000000..2c2bce97 --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/ic_exposure.imageset/ic_exposure-2 1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phyphox-iOS/phyphox/Assets.xcassets/ic_exposure.imageset/ic_exposure-2.svg b/phyphox-iOS/phyphox/Assets.xcassets/ic_exposure.imageset/ic_exposure-2.svg new file mode 100644 index 00000000..2c2bce97 --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/ic_exposure.imageset/ic_exposure-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phyphox-iOS/phyphox/Assets.xcassets/ic_exposure.imageset/ic_exposure.svg b/phyphox-iOS/phyphox/Assets.xcassets/ic_exposure.imageset/ic_exposure.svg new file mode 100644 index 00000000..733e1956 --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/ic_exposure.imageset/ic_exposure.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phyphox-iOS/phyphox/Assets.xcassets/ic_shutter_speed.imageset/Contents.json b/phyphox-iOS/phyphox/Assets.xcassets/ic_shutter_speed.imageset/Contents.json new file mode 100644 index 00000000..59fa48aa --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/ic_shutter_speed.imageset/Contents.json @@ -0,0 +1,32 @@ +{ + "images" : [ + { + "filename" : "baseline_shutter_speed_24-2 1.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "light" + } + ], + "filename" : "baseline_shutter_speed_24.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "baseline_shutter_speed_24-2.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/phyphox-iOS/phyphox/Assets.xcassets/ic_shutter_speed.imageset/baseline_shutter_speed_24-2 1.svg b/phyphox-iOS/phyphox/Assets.xcassets/ic_shutter_speed.imageset/baseline_shutter_speed_24-2 1.svg new file mode 100644 index 00000000..7159ccaf --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/ic_shutter_speed.imageset/baseline_shutter_speed_24-2 1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phyphox-iOS/phyphox/Assets.xcassets/ic_shutter_speed.imageset/baseline_shutter_speed_24-2.svg b/phyphox-iOS/phyphox/Assets.xcassets/ic_shutter_speed.imageset/baseline_shutter_speed_24-2.svg new file mode 100644 index 00000000..7159ccaf --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/ic_shutter_speed.imageset/baseline_shutter_speed_24-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phyphox-iOS/phyphox/Assets.xcassets/ic_shutter_speed.imageset/baseline_shutter_speed_24.svg b/phyphox-iOS/phyphox/Assets.xcassets/ic_shutter_speed.imageset/baseline_shutter_speed_24.svg new file mode 100644 index 00000000..fc4c3727 --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/ic_shutter_speed.imageset/baseline_shutter_speed_24.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phyphox-iOS/phyphox/Assets.xcassets/ic_white_balance.imageset/Contents.json b/phyphox-iOS/phyphox/Assets.xcassets/ic_white_balance.imageset/Contents.json new file mode 100644 index 00000000..1b622127 --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/ic_white_balance.imageset/Contents.json @@ -0,0 +1,31 @@ +{ + "images" : [ + { + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "light" + } + ], + "filename" : "ic_white_balance.png", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "white-balance-2.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/phyphox-iOS/phyphox/Assets.xcassets/ic_white_balance.imageset/ic_white_balance.png b/phyphox-iOS/phyphox/Assets.xcassets/ic_white_balance.imageset/ic_white_balance.png new file mode 100644 index 0000000000000000000000000000000000000000..6b9343a6a8cd484a06597e9c7ecfddc67fed258a GIT binary patch literal 12385 zcmcJ$i9eKW^gn*jj9myJyBHKAvhQV^NywHZBC-}n_K=yUB4is$b}?a;Eo&n4lqiL4 zk#&UZjD5}gF3;!t`TYgoZ?Afdxt!~4*SXJqo%cD#UNbdfVd7(gAczHx(zk#hIQS6` zG0=gJ&CvdR@Ie!B8EwS?{zNd`egeKTVo-Jg5X9Pg{128U&B_lJc>@h>11Hqoh%y3FC8frPyl zc?jo@7gc4J5_y>aq-MM9+9k~MomczUUkB9ML2|1$uX-O5vU5;R< zvr*h4*e7=-k{muwqUk{Q274K+z=jFn@o#EqR;<7URLUkJK-bS{lD6b=Dh6G zxL9-U91z~GIq2@W(Fkd!|Shjo)9B*q4J6<+Sdg@%{B?Kf-6JYuE^Jw8f>P+!bGXO(p>zJQ0bO>W7mT z-%#hV)+C#?+?IYOL=PiMYQTXEIomjoOt!oiY!I@2RQ*>RjYt09{%e&?>|s=F6>z@c z%tEon$m090_*1%SlcwHXzf=S_f_Wg=V0wivyck)G;)U~4m;MVOc~U)U_Gi!Ra16E@ zD~3B~&RFVrlgXjI(Wy1|HyxSuLQ$Of^Csh{zQ^qFA~wC1K&(?uRNU9?-7JzQ*8(>diL;o^s;W>3<{6 z#bCQ#*+tM(ydpVb2U^|bu$vC|R=i7yfe*STQXurgO?I23!YTXg(=rQ?zQ4u61_XChn4f`(Vp4>Y zDcD+T-6c)!kuQFuLil5+dn8u^=!TIyY1Fx)4}={8U8b>`lf^OJB|Dp=M5yGX@wwG+ z3n(n1NNS@aiXk*VyWZN#_xiX@hU#$PFeHnu{cw%ixAYvo!_uCIyMg=s$zK}7@B6E?e=jQW zo<gLfGg(0$Qh&_f}>~)^Qh_GMA=*eOT+2i zvy}T5|4xN8+jV@H)!{IMD$Y*+B^n&zUU0rLqwgOHbe1FgkbV&+*XO$GOREGLXm%kl zL^;cQozc=KZ#@o@5-p8Gqr5Xpyn`8bof%inKEI%EqT5xZF!yRv<`QemtN0q=yQeS= z^HmN5pXg)vx>UVO?-s;iFOh$6jOX32M$9u&IV_5~#{89)enK{QLx$~-oRQXohUy+^ zfB#l)*aVyF-3TEkds=^|V}+Yaj(tm9IX%L+gZF(cU_6?7(thx&%LC4FDQm9+L-@FT zTUKAgpH%hCc#2d@ToqkIwjxLMFPglU>lzvBI^h+E){Re6u-~qPdGN`{*AzG8JZN?q zpm+q!bn%}ZnR#|#7d}&Q!7YhKGx5%iS!K>pL5w!#>{?DnkCWyV`|pdW-i4SB4_Pl{ z(VOm{(ov74!;$$*w6Vi3lDz?Y+C_t-p-&Y3Ttl9H@_n{rcYmg$<7@!ZxdlIN$^RY6 zvgUO8`x`eg*>ajhF_Sqf`R(HD?k?#lYg0Qh*!#4tXui84WW|oL`JNJYP5p&yhmKpT zzow~8rtMu$TxsD*=j2+ACSv#jULq!}*6%4#daBTB&BOW5Khu9Bn$~S*W&{{_9b;Du z&$%bNKXlTIpm*&TE0-7(dJ)^0wixm!t;Jhx5QQ~{hxp3QXHb-VYg|uphNNB>9Hj|b z&^tv{3!SMLK6fAKtX642T+Po_7VrGybSZ+~MN#ZILYPk3F!k37TSNc$ap|am>nUQe zRVlTd#tcOE<;;Grv0JA*pK=j?_qxRlu4}@GpqxKEbM>||aVjc#m-VBk zk*02=5?Siu6hHI<9o0T09i`L;%|Gh-wzN@=HtzLytj2~QjGwHYd>Y)=Owz!lmalym z&3WIbRrHXg<1$-8r~a%fq{H5DfqZyI^VcO(S!)OsEw4XJ+^{_?OH|TTj|ozjbe6|p z^b0KP-0{I%@1NGxQHO;8wI=*JTJhYz9S+kGJ9^}Idg^lpw&k*`kD+?8aQKKzTn7;tGTI18pQ3ucW7oU46@)i*r@Lw zDT+2KD2k!e6d!KNM5QEg@fz}0;jL;KUnC=g<_$=~u{tUIhC2S7zGQKOc^36m2MPZdQ)xIW&1 zq&nzGQFv1;{Q9mG#oa<)Wa~3r*Ikl1%lt++q0-mLZ3$_q&`d z)O_h~G9JpSvk#=FLIX*&@D@^+lhjBbs--y+h>Ns?N^D|a>F^u+lA^W!MFh86C!U-q z3tx+&{`e4t)5oB1uRQxupLg$((}7FeWuX2tORO3Bkew(+hecpnaKd!61$h|5$N4$|X<~|MQMtuMnIEp$GW6|({`th(}sFasWK8-Ep>6k}jnrM32f;$}IN5mI!nk#|DR-M%Ad#U{n-Qo$Gf03$e z)>vAsNZB&=|Sd{h)jzqLZY4f9Pj)t?C%s?SkAvwYfpR<3r$ogn^D<#@Fk z^33BUy(3*L;7qY%)JS>$cU(lBJ?&dDG(6jaeOQ>9Y;|rVm77T&9DG_kJl}B~eSg{v z-*%sya+30TPTz`E5?0r~fiuR<10R&WQ6fkp9`NNnSqF?`%CHkBsOQj&0dkc*7>_{F zj&Eper;4B|zeW@~=NDelJk;=bQ4Q+cp(R%Wa|b%=2u6q&5EtuJkY}CBBp(%jzgmSN zNzRuTMD5~j~%-S<_! zisjG?gVzc1al+uVT*8?1!?x)kJT26~$6aJ-dK&0!u4fA=erqFH6U4Q|iKr0xdjy*y zpbSkLl5uJUF)cI#dFV^N{H;o#uQpDT&6o3tU^v2Sd%{65JBEo2gbXn?2<>b>e>S1u z%;O-{u-!!a<3#bTSvjJ>2DTgQJhFrOCniZRd(bHFG%-q(W%xL}q&|%BlIjB--me~l z=%#rNM2@R6R*svBY3<0N-9{K*usI0iJh?x`pc~noTyr)i?4 zR7>hR-hN*tg`45Nj5>Qz`weJo^QOb1lZDbrNcM$pb=rSnx zQs(KBObl5404)jTJVGYCg?U{jKL$Y`kO=jE%OzF#|D{6@Tx8JXX;1)6+Y$-dblC3S z9umaNqU0{~o3=k6<)9-4SeE#~?y~j`%s=;hY>5M(WXOij@8|T9UK~+$!SL(A*;@fL z`9h{30a$3=WfKt2c_cB*pu5!x^M%y`)$r$dG zJ~&ArwQ>9*2!#7t^i5*wj0xR<-SFv#giRVOa^TlJGLg^&S%X?*U~^|^&)7Tq9m&53 zJ@-`rR+2kU2&ax1N{XUs-=!+@kgMkOYn}tbfD=HuOgDaV$sDpq)(9${l_J}LnXPFH4G{ zS_L=`p#9egBS{f-1`AmXmo~|ut8|xPl=eHwsd$AObaz=aIdV`VYVd0;6`z0)HNGQQ zlT1McXTA1kjs{88&)twB-GJ7D=^g;rBGc~jXzo0|M9{a-O|OT|K1^Q2o3EvRJ=q7M$Kdvd5hps=CPobfsMvIQ``Y4tKysx>Ak>(F$3frJ zlFdFv`JT8-tC_ICRq>G2Ng5;R5pHz~MpNHp#^7#XHKM81I#MEOsq(3W^8QAYrrtpx zPK_`yuMFdcEH5@CO4Ma;YS?ju_h7V2O7Lfewj?f+5l%}&^}&r4WwS;i{M|Up{@dg^ z&gn);ioCNM?Y+Os6z{f{EyaM{yc{Q-rsTn4K;|X;{6A;OisWe4b*23zSB*UkL1I^E zO{bx>EhF&3fTo~ohGI&|lBNyJkG3f2#iAv)8on+7KY}zXK54>obq*)=r}s@tRlMH{ zVwf;mYb{AdZ6WGMiUhtaHci7rwsLQIhQn2%r1=f3xcHw&nxWztO?s@Q@%I3RUH18( z=z3+|BfoElhg37Ru;zB(V zxsqp3k>7p~(}v5)hztrwk91@#nw5+bx=fqn6kjUrtMotGwMcKjS(*mx9j5l^)JT06 zskG6d4|+B~-5e6hnKQAG_uUB}a(ZuycIGSQhMEp__SB0zk63VhvDEVeTE2_hB;%lU zncq>jt>caN)eFqFpFe%a-5lUr9=>!3d!FfgK>Va~!1Vqn_{P)i`@M0&JuO7__Hswz zpshFMpL~P1I_7Ak<_`ZEd5;YgJvS_Zx%IEl&1;x&u?MD^CAQDN8;j|#UyYxKx5`Hq zbb$HFQ&U14>l4#_6B(#uCjH>6tXxC$d(_VD_+VzQlswVDZ^+w*ry{Suy!p#7%eIhc)tn?riH z2Dmc<>w7w{zrknDE0TA5(EX2Sb-~&?KWUaK{t6UAV_R>}=bEX9NQp6H4}&F^%Vg11 zmQjHmeU`gSnqum)Mb*%k)nDKtEqyA9GNbjpm#c9REcxw#^kLnRL0SNEoa^UkGP~d+ zIsi&?@aWsyu9n8}V*{`%7G3I_Z1DIip*f8kqZt{rx(}{bSp;>0)Sd`^xH7!8Rz~Xo zUJ>s5XFH1o7rDrcZBmBSC4g&9yE45ty~By`-Mi{K9YevEca6ro^S6NB_pb-S@%Pu6 zEF`rwEDu5uD(>Fz&(UFMl%&Dt-~>}$i|%cj57||Jr4{PNPH{uLcF^<;1J=sV2rk#% zzipB+?87k&yL}s$JlI#rncQE`=H@+k14<2$&wmQo+n)y5*oS47rR0}mHxz1qDdy^3 zd<%W?psNfDqaXB(5%|4j<(21vd4-W+Pj<7ZsQ#%U{fG{Om4YmzHoObf8FjR z;PX*aTrZ2w!?QZ}-sl5%Zb&U`^@?}mE8gKz5xp#v8WD9|(J}8!z#^sqn`q_P+kli? zU-HK>UzsPsmRqvle*76d;iH^VNX)Q>rmIENVd%#V^t*1+!Fic4J0&Y-toeVq_knZTT23PIDAfL~xw@>}=?;LUG+JGuK3o`h`7C^6B42P0 zD%#%p+V9)IIQyI?gb2uSMiX7BYMG-C*d!>>n!vg))iw?Vl$!eq>RH4h_Q`M)kkTTr^L#5~h zU|JBpaNqRrDGZ;yAl23=d4HFBuE>xHOC+gaJ?N3O+N-c}jvRfqQpzpk(pkIgK-aco6kAO~kC z?|dDNB}&60hcB~hzZ231*&uyk+b!+cpki+y<3i{6Ihw}JTcwd_cD0>XPLZ#bQlHM4 z_ut`oLW?Ezn9^=PH7#gc2sF(xYMi*~mW7D9dggqHT~Hr7sjJ^0=ffNP`fzIC=U3y_ zd*1ioAc#i77|JQwu$%OeyI zV!c5L6eC%MoL<<09>&0z%8?7F$iraQvpY^J0t=hK#_ortlu+{@73zX5!<$4)EOSBi z9Lbw}(1Q+qY3g(x9rl-Xw-}_Oa#J4bi9(@B60Ie619wnxF{AYtX?+gacrAv_$s$bK zbU4kVnb@Zmq(7I!6{V`0v1Mm4+)-)zpRftOUi@NEhRAiL*9Dw&y$8hY zlFu<%F0pV}w&5_mA#R-YP)$8n)_U(@`l%@%Qhi}+39-@9>U>cn|F6H$ZiwxB-XEMK ze$-BB=`<&4z6ZUT?+bLC*)F}E^XN@JPHptq3j{?q;m+EoQ_LVwwcOen%Ke2Ics1j^XX z7o98aYXh%G46?Pj=eQr3P2-NAp%MC_I`BM-W9Tu=6cl(Kw<3F8X<;eo^KY9 zbH#F^A{WXDt8>{WDz;8z*uay?&=6D*D^?OAh(nPH|QB%}5ww`Fe;`LX`!)&`;l~HYj4gc(GjR+AJ;s*xs;CF1l3$-nBe3&9)BEsKS&^<^c zE*J3<##15bZ*Y+$CDL0^5pKD$dQIkLWDHZ?ih{G;&wc7HduhgP3|y zM=iKejb{>(eSHY`;IC4a5a!-rm5~t=b!|S`a4|D{CWw=azYOjr?ZUn07azo7g`H>f`j{1J-id-&h2rF8<%OjG z*>O?@Up^jw4sNJUU1aL?bn*BR&JL38$)&@9VF|q~>5DbLK!QGgSxlwZT~vFXF0^b& zb_)#AaK1g%@}?`Ql{YX-!@JGmE=^J0kFvvyS)?z13Dj#@CQnGQ&fn`Efba zCshWz`m-%6d)iciI9cdk*-!}P-!Nr)+2y@CZ;NsGm&G<YkbV zW;V9O=&!cJ`l5B`fkN$jOArdv(IDb)CXBW#{S*#C+}H_ydQinY(7Z0Z=-kn=POlo* zlo!crkO*aGoHN48@x1V`oO+a-RDSAi)bA+WpjapNzJI-3!XdWQoq+C_B>)_AzFs77 zCgG$Zb){73;6L>@c%wMdj{R*7Upyx$(7r6B^&EtHX#+!r~9k zmL*OuW7pG^mUPsIJuQ0`NIfl8rA8xp+F!eFKzok-Il3lc8u<=BLfdj9J<}72LdF7{ z>1X?O7uV7PbHrRGv+6QDgIW~|e2P-ITjGu!2hQa7Ovyi&a)lc5c7-i-K8?Wk^iP1M zL0y>GT1&BdYa}6{*eaZdo%*meyx$G!Ea8=!tNx^ET{u=b!_fYh?0&sfjA~1}>l;~s zkO=6290N1VWi44pk?2Tjq%<;F-`-;_aFzISxs9-*q9u#%{<(3gO#bW2Jht9N8D>F}< z8|W72P*M8aFze;R7|-p(o82N>UEGmR7r84IW}JhgAo+1DGH6EEQh|}SS zy1RH z1|5m5SuVVn*?UPyl-@tBe8zNjeaN^rKy^*0rcS@bKzF4p3K@46z5r}%Z9j;hx{V-q zW!Kuv&}*6H+Aw1DpUEk#TU!#&L}UN<&syFY?OJ(;ro8do_$XKJg!;IU3sc=L^Wb*L@9eSaZb_QFi&xEA~CQ8B$ z^dU`g>u2X%AGA}b6WqJZrB&~K*aC5>(Yq=Vj)FP~+lRrcVhL@ddW}KXYj_E1vwu>3 zxwO*uO;K-NTa%uHyNv#D(Rs-fsmA2M&*#uS!9rg5YOc7y;-7tU4C=tU5>4~V0Blpu zK3`QP#j0U!!7Frzg<1hh;)LVne>R`KL)H$2RBo`j!cH!>fI^6%JI=%cz)stXsEa*W zjgpN>lj#S&oT{(Eg-qHAa?zrT&{AhaGf}vf4)xUFC;cSJJG*u$?{p8iiar2;0JQU- zy+IU^NcEUiZLoR3uCV~XQv@x(o^1nT)v);#P0As>x+zZl`W(Mq9f|}962RE_glM~- znqJ0Vp(rgKO8*NWydjnbJ{?YUrxMK)s{?><0icGzgA>+22EERI>}pYvfl6okjzJ^C zyf{+T;+TdmkJQy-@gFEhI!_8dMzAieK*5j@ zz{li8s)hTGHsC4S8U8Cont9#Gc3b8$dpZdOMKD@A9ZLWhHf($%4Q0$xx8Y@%=9? zad}3PFaLu>m1%sTx?_Y-!e6@bVx_t|ds$upv@mH-es-)QVzsTSg&XLY{tf%LAT^o5 z=)lyZs?HeH2G75_4VAa1@S_JdXexQUMw|w)g6QT$*4-@@e?NuBt?k>bhs(NR@@KPKbVIr!_I=P4YBqE6d|w9xYY*GW@VlRp&@19 z@v;D(N@lOcdjrY!(1MQ|wwG~eZ!nWq_irSy-H)#K<6Kxy3@=tGTwHTILsLwhR!n86g=2f*dH9Gt!Pn*iL7f#gedMm)Kpz*PKwHnk&4I27ZY!Acq{9{V!*l~BP1_id zPvFPSBG>@(e$Ni78HAJ536eC(+P+AVHt9S7_#?Dwf8316*`z+-f7Y*^1PA5xggsC+ zSe&9hs%`6?b&2fRdp6&`qZrzx@14TYA0M}(B*F>6I#n_d&AgN|Q)Lv0=(h(gq~e0q zYnseZ!Y<>5ctw6&i;IzTs!HmN)}RSzc)&hS{GVXKIMJDcBr$LcmS6RO`7ZNuZze;7kwc0IE z)Pi{oA)r6}^Tt9ZSglR28T!%%;_du;IN6%?o2+T&DoaCnFm`455=HXgFqF@lYpM(xj zfNoZFpeG$Wn8H1dC)nQ?tHjr6zq@sMF^P+ib4YlTjmUp1L-QdqixIuJafi%85zokdMO}^JFG~rqyP!7|kxhs_B{5U~VPB1da z1I#A8A~;^CE$_s!AqLl{UF)K2rY6w@Q77*=pRgnIOSj(&gvyowRLy;e)nSkNsPgp8 z&+_S6zaU2hVMOHH{GqB4AD;NE2clf+r8w71rUzi{W{Mrcz(5l!>AFD(WT@fFNPM`_ zvGvtGrORJhi>T_q=W@Z`2*kfwiP!kh-;&(|~T*Im55ukFw|(-|odV!6)Y3 z4e|YJ-9|b4CqGBACeoBB$9-5A7PLv<)y{NLr`Y%2hxZ7^nI|>GDZY+UU-%g`y?0fB z@!HHxBcRH9zU`ao;nZt-w^=jcmZFZVq?U`fQD4^iF9&<79Qhp`VKX}F!E%c%N<+Sc z)USH2A?k*AHBtLxD{=d^l6yLGMsO=NHwSRld?4OjtUQDb3@394aCzQvK-(L+C`)x5 zJ=^%D1{j@P?1(}JOU?S!b^)X;bj&zn_V#ovJ$ht)CHmgb<8^IgBlxNE@6zBR?%%ty z(&2$1Gmss6+phNHJO}ecC$dfKqr>Ssykw_V{qWL2{}*p74r5SR2_>5Rc!|f6T(;;4 z5BEUs*QivjByla)()Gh<_jK+6^%?=8(eGA;@$5FGctt0&Xz%u`o}BC|-#2GEXH=gD zikF_2G(YlKMPaXS`p@X-+y8KX2!^dBRQN;BQS2N%&nm2(N$6_gwwZq&mqBp++i@fd zJhN0#en^kgyAm3;(8)^Ro#9D|&MVYtD{7k}cx;)$z*yD;>*nYe#ojIiCNVsa{qaB;@VA<5HE5qc1CECy~C{Z%<W~)f3>D)daw*&%U_#e zyZ66Vgz?=GE;fH}LSKjA#xwnsqQe7ar7Fk74T~(m$VZd5R~P1ts9_pjzjKFupk09( zowf)?N+;f_`ucxEe;Ci$irKW5My1EKk4f%@M>|EIsCV^#j+^C~?T>XkFB2MLaP6px zcdtH0?VR<3Pl?>o*r#u6b#B`sxW}GmR88M}Qqo`T7TWEE-?!e*#?pTayWw5B_iV>4 zsIG-jBz-Us?U`)1zx316tRT7n=8E?fLWVdIU6a%{hkDbDYp zNRczmTuK9Wp)+!5wP`RZW*EDw#}b6Rxo2!4`_2%acy`02B%=tjnFJeVEH+UjB|<=b ze?3p?^JkkKPWbh+-bgTVT7VC*`P9r=aVUPSSQL!gncx5E{dmgu{cZ1Jd4`A+K=eW@ zgkc(6B{jP5wxqrdW1l~=kaYU8h)ZuLoI}?JLzHlJjlr8NO&5^HwOyxrPxSfWmC?L^ z4I)me@`nm;jkQ_hOP!@C_j}cXmNI2_Qo8<)nR=u{n`vOLJY-#rJeT*25U_kaj`Dwd zv=)iBprYH=f8vou4`#I9-d}F$HBRt< zX0PY?Hzz=j8NBj8lh{IICi_`3LWn=<`>aSe74hSSj>gFs=FZ`LxOASOTmn-il$_~N zpZULI&NtjV0~Y_!2)0sp_6Z-~XD|Po)dq9pXP6;0-u??*7f;}R3ne3ok7c0$m&;&0 zTvIPA>Hi-aUjY!GBk%CDQ$Pjp+|Np0mcUt_M$RQ!h$YKL-CV0D{m4ruyZVo&Wj&0F;vX#V)lduNyDc~+u@nISz52MquK^a!|~B>+%> zmlVJ`YVcz#Z1e#9pnP}@VSNt#i8^;T0lYsS0KfGR02sQ@{va8W44mLY_8|S6L010m zL7}$;?*XBqq0*jy-Vg8G4!9@nALx;{s=)yOd;mgE*E%eJWh%7ALqB?NeYeBmXJ>ke zL>r&!GmeD1S9}uPkN7@6%D(*MriBkNPJH7!)Gg5s$}h|M%JK>mi~eiA`;>YWI+t$t z;cvNSJ)sI&J3f}(b(hJKYR(_(7hfCS?5}UdMJ+t+oDugJ%x6cBveYB5qv}yh$aok& z7HEbqXbcrsFPr9ByyL6`B}O(M##pEmF)7i?JsX(3{Ah3g);)9U?z{$ zzIreid)0}-WXRbId?M{3zXRSESEm`6P;Ud{&t zEDud+dLIVr6k=!s0X~E!Ocg*2mB)c*m{DB@KnSr28exQj(NF+^Q3D+bA-HD&9SoM? zf6d(T@CtqJLolZQLTDT+fP_oa^jcYh)nj+wtMLtaQ*5LQAy{&$G^n@i(lZ&s$o2Cf zXoAQ20D~v{1t@PYOs~iLSoPiY>0@mgpPc4@V1sC!WUey;4FA)NexhO?;fjnth%cznLcV>Cit2;i2~l|#agRe%Ut^zH#0@;NShyD zc`S^;2>oyWTBn{bq5XbQ9w~)n!To*mO#tDmm^S)D3*}oylLK(kVqh4i(77%nqb?*aqVRQ<9o%k=*BWtiho-k=o$L z4`*omTNBwamiW}tLW7MsE|X~7c^z}qhLPpS7GMaKWOs6Cb0c=8N$lzx?B3xj?0gmn zAnfyah(bN%KVtYI%g4MTQw_5I?%&vfx^(Lzg{sUL%ageL)!Qg0_GL(Xz>#Tix>nRe}3pz56;yL!01nDh21!qc*9bC)Y<_I6}&!=b2BT!6G`AS$Z@3 zjHV9m&k#}y&o#6y!AgU5tJPyY0P959*WEj3HV$*`n`^MuA!gf$;Wr0bkGaC4ONSzq zud=J;Z$UE++yrVr)HeLa6`{>6^59;`1-hfDORfamX$)nU4>6XJA*=}CakshN+S(u> z!PjWUNDjB;%=$JYA7d-13fyd7OZ5i;G6P;V%aF*Y^379~HWeRAC9qno3PM4z){N#M@xUy~kqWLQf3up8VoO76fD(u}s7d7n#iE z0$@jwO^BvgD}h+LUj`>Lwxl3QvLG>xn1qF-bZv)xJ?@XaT1Dxc!><+GYbZWqaS)JBD5S#`IRlz&4j<|6}zfonv4J`o%rLd@P&3 zoneoT?0}ai=1tjPo}ec`oCenHTk?G*(_r-(kyH{8~u^wpjI}O+6~n8~pCDUApbD z5uF9PQVJZTTrLzZmCiQG{i9tZSD9%#hrfrrjioB6=rQiyoC;s4A$(P2u6Mn`vkbxJH3|+y6j)akGj6Nx4qAfmGa!aJA!0{>{@hQ zzZxt*Mc>ZLNU|^R#5Q?1j5jduc`P5*lLZzUdCB2KPr<#=EHvwA)KhEO^ew<&$nb)e55s3nQ+%18MN_kbsCtInR)Lte#F!d_U(-(QtjPU@%88 zl7{EB<=*|oIKbo27ck!vt8yzD^p_xq;2DBs01<}61v?6D%1+={R`dPF-7MS!e7veM zGmWF~zwfG_rH8hL%^3SOni}$Hqz4z}`T9OpyTfZ1lO0;^;M>iy-!A&O75-`Ou_&B0)Uz3-Et-DCx>y(|3Qsv|ugt*Sn1Jpv&#^_= z6!1{Q&>S(7Nmo(D2z?kbODL(Ai>N@TcR3Zrr68W?73xQ> zg;icf1r_%nB8$+~PNcP(XQQxuy8h}_ON3=x`_mAgsYpSR<~$UYli~c*?^cS!^#BS_ zZavaka68G2h%~A}3n~6`$L)&E=%grar`pPOF@_m@9yh4;fpVQcdg?yJRB+zMa=kGX z+Y`pzI3v)XUI2-;5J<$ zbKxsXzKNe6+S|2WnvkBCgImVLUO%28b*2RE;d^AWbQEjPlONzioep-JnzpV6z`rY^ zu~$Se)4!#simRoj;1SXO5#JRpj!pPAd0LV;y1E)mtjMWIEH!~^Pil^HSg}MBc}yDo zeJ!HG8Q-H391}?tz(b5NqvvGY$I63)jbnwTj^7K{P;a9{Uba8l=o%U;iG=$P!Y!+~ zsWr^@4IYNIh8O#U9NDKi<97y5jrV7k$Xk>FL5}}03L#cV{~MhzxeEQLPLdmVbU6|c zxXJPbXBL+)!4Qns-Dsj+x<6NZ={+n}f|Hh?-Cy~npmC>W(quy3S{xipZN);+k=%QFHjk=?fEe&Hfd`sCLdJ+i+BBmVcRsi2jYZj9|$Ij$m_~jrHVemu+QEm4c1! zIc)?d2LhjMQhIc)Oc*N_RU)FAzBMUzD|8T2Y5 z`w0s3<5hgaqZVtyU`=vAS*%2^iK_agL^!egk5!9(>> zp$EXdrmG#$390K*mIa!$Ed@v0{=&7fPxpkzMqMz;%mY(+%LLy zgoKYAd1luCZs#M;RQMA9dW4Nk=y5xcjp5fqP-V1yQn}!=nPyM{+oRiGB8@+!6fJ+j zi0-^SF;#I?#w#)ZY6ggF|CnElQ0M*tz zx!M) z4v~>f=V;bx@Mo#zYf8GYO!)3*3*9&rp0qB$p~%qH-J{#*doe(3e{p|3{3AwzM}~i9 zy>p!6;l^WO&+J<`o7wUuNtEU$cz0!#E+a?_hy^W_O>>mE*LUpT>IOz&<9#vU{Q64wb_;h) z`haY=-C_w&={*JJlAAZ4qwwF($MLulIwg@IilXLu8o1?~X66~^D<3)_*Z>#NPPw8C zDGr3{?1acsgU>LWdyB2RXe*SHXOF+pG&%00Ek)ZL4SW&9Lv(YK4B)LbtVRmm+m`z? z!_~?ZN}D0E>;9*V993rL)ohjMZ^+QcnluU-K{_N>H8iJ%u%?eXKZL9A={E_}B5EXz zD{%1%yp3Jb4hk3}HRRsTZ1m4psrEk90m^k5+il>rHEdWkVfwmPn^4jx!^EmLj1+nI zo*P(l`_NnwY4TN-a8R(M9T4fnoH#Yz7aIHeySqth_xFWA_-t-tAF5-`?|I29Hup>#hUv}Ze~WkgbxH!Jzyc5 z z1z9qe%2h-7LuR90J1vA1C-M@!;}4Jpn@CW2HE|VEW#L+vbA7ZcK*@sUrSx| z=kWa2|AwYH7PN1~edum9WCWJxX!R?ad!HyP|EZ5fyq1UT$v;@Z)KD5hW3D^uM|$+A z2sM5=Oz_TeXQJn?Nn+02$)|XDh$b>VP_o}dqm}N&hs)f`zBGdU z60(SV@c`bq0sSNK?h=rIZ_HiVvck5@|X;(qR zoms)pMh)(pH@A-sN3*^lYT7ifqWvJY)KO-fQS^Ix$S7XH=q^U8S*fwX$pi=M%`Eo6 z33AZAa{;QADeDYGNnBG2jY@YD?kiS~UdDqrfSr7Mv5tSF6ZamQ920hn7cvNOc6b|9 zX#z_YSbe`qzy}h&h?7_+g=X61MufA@uV8Hjuq|$A^Os(9N|jIG^QT;OySuK%5LGC* zcF`PSMp1bA_N_I5LBhfe>5F`BVA1no%Ny@wa2}TC9v&Ea?Twyye%%v;{{Ns_g?0HrB*pVD7C#PQCzJVF zx~&E!>PL+wGiY~2>%rdE>u$&KF99nUHKLB2gaz+aZI{a#>QC1{$I6DgQ;0y>Uev#R zPwh1%q-n?Pt(ryAc2{C)_^+5pVvz85m?LKLN4L|+G1%2C7aA*noX*m{a+493YUfx} z+;e>TuFzUn)s8}jL3$wqtDo>!YALC^XzJu*#r}yg_92RD%mrir+&=Gt(I6l8R|HbO z+q!Vqkku0+7cUj-Rz6>gm)Cs)Xr zBysEa2e(UkFj@KyiIo1?f|w6oHq6+0Metsq!@VnXd~j>`oWuepektB2V{;XyA=v)* zV_>FP8C1TqL!)sk=+6(YEY!KANX5`~G27c?aEHtxw zzO)=Gg!tG$D7>)*RCJbpfBP=8fjNMG&iQr!+>7EUZs6`Lz*lsudrJRUOl*crU^TNf z-^04oRKnZSA$9svj!O$y>MKG%+i=nsu|^_js)<5C_D>QU6#cAxna~SQA>jt;@?L9? zL^2frv{l3nAd4`evCs1T06&J_P&rZ+-@UW9-iVJDQO@NVk8U1_%(8E@>gb(S;pAgX za8~_87pJBoBR0cEJy&^~3z%ize(iSv=y=sHKt$7}_0Vsc%6ylud3}TE@GL=w&iC%# zz&{d1YzGUGR??gH{qd}wHk0((9O#;-4*>L+hlx4VM=aYKA8qgwnpjOC_bh4n=gd@w zixbKxl z`)DH=K%=0@FmN>J*f?BM%X6@lHxVj`_$gusFv4aJuMv))!lhJzgl1@Mj85n2bv0d6 z%ZKfdiP9i`iH1knz9?m&iMH47I%vXHcfd=0RU9S!IR|KO2FqE;ST)tfT<8M;CjW)v zC>e%6JIlAq!_@<*SA`H zS6L}%*%k4b4G~#@GSzHbuuh?{e!q!9!%GfuEDhepUn{EEKXU1l`7_$q#LdVxtmF#; zp=~=b<_2Ne@!eLWhH%x?03>{nuD2emO+BlM+EOc~0E?LwgIR`|5cX|t6dUCog~qUjh9k$HfOO3U-t_DtZ8q!20U%=@goQ$2M@i(&n3SpxC$~ z^(Xi%=#T~mq|Q^#k^r5)H8D3rpElL(Ey72nhBAnTpZy!UI)%RNchHMIoWv>J@KaT> z8H|9Rr4rqLrE-bcrdp1G*ql7G3TO7k^i%)14JpzpQey$V1GV@JcFfJ9p!9oVtBk!? zcq`v?`NqF)D||!Ez~-DR^*G09`{{d|H$kg}+;^=qXWiF+2|S=oG*!g_AaU1Z>cU{w98L^}NbB&~dGNQQtxr6dXl-N+z_{3$q= zIDV1+IWV|3yMf_on_;C8vW>Tuv*mI73Tc29O+Jr>S3&qRzM zk|~N@GH!uGT&LnDCU>R2gqBHc(V*n3p=j6h(Xx)jqXKQ5AI=;p&#v_MY7lUhZBLPW z5H)B2S{(Nkmsmn8XKzl zTqQNICak$z^Li+i^`8Qw`@Z6zbScEX8lcDz;% zD&ETMXA{^9%kGrMq%}HYr#gx~9n!VC7a!E)8w>&7vgvy#FjrfdTHbA!+u>KQ7dj~D zbOShcp!|JuS%La#m09p-OFaE_GiU_wg09(fzFxn62Lp);|3r{(!KLzJ+X+&szQ=OTXEa!9atOdLT0xo{HYUN`OI=%p4 zwJHbiX))YX3uDD)!3}}&;Z&Ry=*{TN+pkT8dQFoP*c8AOY$xpJG2I@`TQ$1-!en=R zvQ2QHK!6e0S-RaR!CoK-wxO1}GXzdc@J{NeiXi@{`%q8s&}Pp%|~?bc`!NsP7i$XW_XcWci+fqSFC(9R*HP_56Iy}2b`4XHQq2Sg1ixi z0kW&KeW9PgCATV&pKOPtLKUq?5#Bc&T640Dm4@U(k(Zw*=1hZAT;Jaq;#l{}l2%5@ zD&6H_gY66TwXJ7!pfv3iJ`MdrPYZ#OJ!<{>uM!6)`^CXNAnj# zc|MCerSwtgun7f z^*4S@WE%YXv>&HaRU7IQ+!2u=;h9Y~I)${*jY|xGz!VpHAycJ8-O`4R;+6WTF8Hy0 zbjRs3dZ8a&*>V95DZZH}|Fa0Of^}px#AO?}Ys4WP3r>sI&usrn(e%o@foDD?QTOJF zggo)z0H5V(N|Wyr@M^bbnn$H*xXP^1kV9p5@d(xtH(bQtr5vtyD*9-%rY)g4g3!c> z1D92BLpK@D0Q}LT2LuZ1GJ-;%4o##slz)S}Q&6uRh0;k4=XS)W4pFvK>}iFs9rUgJ;h?{4@e)@y$d7re%_*q5Sn{qu_WazhK97)YKR#`cv zVaZ|e-CM~jPZ1&uwqxmGrtspb^sBcQ;&rDkSq#2$qBd4dS*#fzxFv{|2;v{JOGp@} zH74Y4MX7ptgwUH8p3hb$hpK+Nir=Ct)HpYhcT{k?mcV>tx&j6a(Mv2aN=kcpK7Vt< zaV+OeY5Ann$mCO9%pjkfN=7@ky>_KL#bVfkwg&wmWqJ$9V>)-v*ZBPs=yTWxh#tCg zM|VHZUGco`Ry~~ckec6ybk0rU^QvlGuPd|LD$kFM>X)6NC!ak&T|`HI_@5LT^DQM<7` zq^&Mq$U_S41vXT8R;{;AVKatcQC=6Lw>|XP_Iqb^cf3U7#6ZLRxX^38?yI?qfIK|X zo9kTE^DV!aaJHI@sYE6ahs0ERub4c*1fe-`M2N#&W*yC0jSzUv*eRMBYxi^cOguER||3 z7EK>mDhiLcY($JIeVrL%*Ry+1&6o0qUfcO%x<7Y;;qf@7hJn3lC$A^tsajiM!{;^4 z5?VaJXliw2p5BVlL0)lG$+ELazu3O0YTTQ|R&XI=XpexaM#AZ1NP$(-8HGE%6$k=AWyTk_If*l3pjagAU(e-{d0-ldT|&`UwplkmWR@LYu~Cgf2WIONjwN4x)DXPG z6`UT)bd@j|Ymum{Qvh!3{Q1*Y-w5IbT))xOSWWqu_XkJ52-1ohRqGNe#dGDzC*<}i zp1bV5pe?q*SG)@D?s&y!ZrK%_m>k09c!lN(#~NVgni(=%c}96CCGt?dXx`*azr*zX zF&KGG(i=4=Dj#POOGTM+!uuHBe)h-dBhua0xmvC|^1q&~ZHhlX{50f2jKx+Hx6*B$&v@^}>(vS#O6#e8#%Gr)70Nf`gXm)n_Xi_2 zNVkc;HZIv5hv#I+*MH>e!iDnw9F~>X_>waM9jx? zAAb*$CqE#Brmx*RO3n6;@&n-&%(Ri?o$X&Q9w-b{!s!R(3L~a6Aqy5M^(>{StijGb zi)#t~qQ0M6ZOSRA^UNZIYD@P7M*@jPL~Yb3l<%WEz8JO(p;I&YvJCY978Ptg>yd4! za!7a)b-N(njMA-?(OQ35@+s(|{OEq&#j2kM*hcP6Bn%$xQEA!>({ry z9aDU?ve*nej`Pt*=BTN^h-U7y00WCoOWy%X9)5SdXwNf@b7ro)!RFh)p4CLWRa8og zzT(i=%kkJ9LQ+eJgqMi5AW;zcu9G=?w+*_PIm*38(?1D8s(H&jSa>N@_EY^6Lc(7j zFQR$DceDeS9`*9RiU-ga!L?^=F>RH={odHCOE6*b{-QVb!;{fllg!OOzaF>X4Db^s z9zK)zQxsOvAeP%HEl`c=5WT~NA^bh=ZZu}Q`{H^g(g%{dM{l;jyXYnyM|Xq95k0-Hd+4a z0XqY_YGLk#N=~pW)PA#a)6; z^aOd(%jOZPr7u+eL0$yhs|kT^e)@_~B#A5p=>qy7u{uF=^MC%^(M@0#n017JusFH;_#%+GzqJogaWWQteo zr_N+j=7^;Xi#S8~e^L-{{2}DQP`A#2d?ItVR?49n2w;y*{OoR0TcqeM0%VtF|W9+!aO{{_!(w|JE!)HzR81@&E*lndK9&KwBz)Qc zg`Z$2MusY+|56%u{byl=m`tnFP0IeS;O$tL;yO!5QNuj0>QPBV9wI`d=(XvD@)_m! z$7I8Lj~5*xly0r9iMuuHN!xkGxGmM5ug%tAIsJFW#1-7>9ije}vUX(LxM90teZr!$ zEjl(3)9uf!l|79CtzVOUHxz&rVCWlIS>t3wCt5lg~zx$=i|Jc@+3y+BBK zKmypp7ms{#2WNyKtBi>0$ijW56S&$v6U&%-ROMHzaXX60+4IKuFfQ{#gO|K5$Ogqa zY?KH@Thv8UTiX04rU#_h#P>Pk{UIH<1ky&0JGZrt!$cBBRoLRH@=iPjtALoUCQ3oS+asj3BI1vjM}Z(XVSdTC>otejmf z%q=8RE;ygIcB$?qNx=2f9vN$pLwkfd+Fs{zT>>fSik$p~1V+iDx>VJgDrMkwwlGxFrFQaNXXeD`3XaZXN425w2NTeN&Wa7|7rI}>sUD%mPtA0vW` z^`;)amPVW|-0V}(lSHNvy&o+m^AIoNox&2;cFouMjl6QrElYJ5VS^tsdcC@GaJSMc zJx)1zUtvWSA)VLW_mE;y1s`9h_e~^k^*NOvZ2PN>hb-p}s2D@IBS#kOQ*z8A#z4HP zSCHSt9O6%CLub+zsMApL;*&^Cigp<_i*mPk439^~TXpU3<745ZPn1OObl=_s)Eb17 z@{2^$I9J~?cx)&zi%1tm{KeDeo9r8`gb?q2s583L$X1)Mu|4~(g=lX1Bm!w})%jpa zu}z*t?}M)d8?A8>)HX=0^VY}Q%C>$ zl@&E8xz?Ker6xVHYj?2joht^ds*x;>nq<8?;TF|L1BqP0j&=UdxP4X zC-zBMbo!|;W2rZ4A0=yyPw1zLLF^kmxF7MNK`zqW^_N$(C+-WS0uo zDvoW#;*lxTLn#1C_K=Ek-KlkA|^AomfKfT@}HJE1kidT zxaG1&%QO9=4D+g~w4RML1izCaGmiQjFQ#Js|5nFf)E28oV{iP(#jl}W2bk|%WJU7~ zIhyUye7K1}#_?ykupqp$O_B2AGh(3h3vvpLLN`p@kZpo#>Di0f9jNn~A28HD0JUHk zj+HT7Rl0ximpnfK&3G+z!)4Pu@<|P=e<+D+eX%uvO+$f0yND-3<@)YWQLCEQc3;vm zp$lp5u#ivn=YF^gkh8FDM@4D>qfcwNxOb6(TG&%B1Xf)Z_17slB0DI5sSYPe3Msov zJHfdZi;Eq3n{g;;8Y@(g#6Ol?qfkC50$mfVBKvt8zQ+Vfe<;C?K}kmVkG)DifiPB3 z?=4RID2}h!s|sWa5U9OJdNlg$77fN{zUG{jhhl_!fFTWRp?ALSiIJ3>q?=QzX=YNcq^CPDdJd1@v~4Ov zL~t9KE9EqdA9AO={JXd4-WuP)K5VVh9k1be6$J`d4I|JBQodih{5UbxMmA27 z9u%W0Hq)Sf{L4JutKaSz9{OL`_8GcI`-5QRe|H-?Q`M6fyI`QzWt%gVd8e~NL>fGD zL-`nBx9SX{3J@bTzaD=7G<&T(2N-h(fu`>bx=r^93;MYC4GZ}Yt45LlQE_7U#h8X$ z7q16}wZnhK+-gjl+=`FKoq663tynzvB^?qj5=&G-j^ z@(6XzS{tae|9qx<7cG!b5;VR$rAVa$sL+Nz-YCfQjPhjnEZRRN?Wj_z_6zvR7A&=a zI{DADoglmvlLP9e?Nb`WmN^?X-ySrT&S5_n#>>6a{AW`@`}NRCow;~95NwUo(1mkm zm5NCX&O}F|Y+opdR+O<|8@b}&E$Rr)g;MT!fz6w4ny7vngMX@ICD&aRoX4=Nul92| z>1kg{8@yf~_D4E8ST-ZbF?e<6kvxRPZgH?HB`C9L(NhxtY~vwfWT-^FrwIf>))sl` zm!HwcICUh;`H!hE7hf$=1HFb|y`HjTgP}!2!L|(NdJ1`{V(7UT+i6^WdSg;<%q#!p z)8Ris$!Xs&Ys&Q%Ao*=aW5@o1(&IZ--Vq5~T&H)ihF7c%W8d2_m+)llKzRq4iqvLn zP#LqQ{OY;BMLc+C1{n18T7*w6dZWO9HAp^S5T~eedBTrt+qn4qt@+bEj-kPO zpmv|3rMCkzRF3NoxsHK($!mQO|?WzeUJ^U9E!!K0j{KY;60C#bq#-)!F;zrX#?i?8-K z`n2N%PTL{HW@uyqy?A`rR;t1Q)QlbsXsVDyQ!uPocj_u$cg5aZq(36`Astz%Fj$PjQa={jdcYgiW6+bM3v-?9prT ziKnQ-M+IG2KTzyWoWH@5_4z-8=Y{86l))^H%C$cQT|)W^`R7$M?{ImQ$B_qJFt79m zD7nz`=NheK0;s0a{m|DN5qXkhI~Slp6D z9#Dxhr;u^@2LI$Bc64qc! z*Ckg-fExMo_pYZ~mq9{5CP%;sgO%ub-WYoH2}C`}0>T+Wh>Sf2!L_~kGG^LzJB(1$ zNE>_Pf_C=w|49*JMVD@TQ3t~4Ce$AO6h`1L7Yy#HpMKaH=)adGgt&7xZ&>QU!7hR; z`~RIO1`z@EBSJ(LmU+=~>i=@Y9$w!_w+6Gm%A=LQ?5ui_r(^;1zPyUq|6|bq%g*}$ zBRTBp60%Vp}_tqfOP!lmrOT^EubZI2k#~pG=Q9~6%S*S$bZH9~k3xGCd hCaZV>_U(ywH1x-{TMmq-V74NF&^ObozUKPq{{f{`p)~*i literal 0 HcmV?d00001 diff --git a/phyphox-iOS/phyphox/Assets.xcassets/ic_zoom.imageset/Contents.json b/phyphox-iOS/phyphox/Assets.xcassets/ic_zoom.imageset/Contents.json new file mode 100644 index 00000000..6ecf52bd --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/ic_zoom.imageset/Contents.json @@ -0,0 +1,32 @@ +{ + "images" : [ + { + "filename" : "ic_zoom-2 1.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "light" + } + ], + "filename" : "ic_zoom.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "ic_zoom-2.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/phyphox-iOS/phyphox/Assets.xcassets/ic_zoom.imageset/ic_zoom-2 1.svg b/phyphox-iOS/phyphox/Assets.xcassets/ic_zoom.imageset/ic_zoom-2 1.svg new file mode 100644 index 00000000..29bcd036 --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/ic_zoom.imageset/ic_zoom-2 1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phyphox-iOS/phyphox/Assets.xcassets/ic_zoom.imageset/ic_zoom-2.svg b/phyphox-iOS/phyphox/Assets.xcassets/ic_zoom.imageset/ic_zoom-2.svg new file mode 100644 index 00000000..29bcd036 --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/ic_zoom.imageset/ic_zoom-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phyphox-iOS/phyphox/Assets.xcassets/ic_zoom.imageset/ic_zoom.svg b/phyphox-iOS/phyphox/Assets.xcassets/ic_zoom.imageset/ic_zoom.svg new file mode 100644 index 00000000..c4fcf5e7 --- /dev/null +++ b/phyphox-iOS/phyphox/Assets.xcassets/ic_zoom.imageset/ic_zoom.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phyphox-iOS/phyphox/Camera/CameraInput.swift b/phyphox-iOS/phyphox/Camera/CameraInput.swift new file mode 100644 index 00000000..6eafdcb1 --- /dev/null +++ b/phyphox-iOS/phyphox/Camera/CameraInput.swift @@ -0,0 +1,11 @@ +// +// CameraInput.swift +// phyphox +// +// Created by Gaurav Tripathee on 13.11.23. +// Copyright © 2023 RWTH Aachen. All rights reserved. +// + +import Foundation + + diff --git a/phyphox-iOS/phyphox/Camera/CameraModel.swift b/phyphox-iOS/phyphox/Camera/CameraModel.swift new file mode 100644 index 00000000..49dcf2f4 --- /dev/null +++ b/phyphox-iOS/phyphox/Camera/CameraModel.swift @@ -0,0 +1,65 @@ +// +// CameraModel.swift +// phyphox +// +// Created by Gaurav Tripathee on 13.11.23. +// Copyright © 2023 RWTH Aachen. All rights reserved. +// + +import Foundation +import AVFoundation +import MetalKit + +@available(iOS 14.0, *) +final class CameraModel: ObservableObject { + private let service = CameraService() + + var session: AVCaptureSession + + + init() { + self.session = service.session + } + + + func configure(){ + service.checkForPermisssion() + service.configure() + service.setUpMetal() + } + + func getScale() -> CGFloat { + service.zoomScale + } + + func setScale(scale: CGFloat) { + service.zoomScale = scale + } + + func switchCamera(){ + service.changeCamera() + } + + func getMetalView() -> MTKView { + return service.metalView + } + + func getCIImage() -> CGImage { + return service.image! + } + + func autoExposure() {} + + func shutterSpeed() {} + + func aperture() {} + + func iso() {} + + func exposure() {} + + func zoom() {} + + func whiteBalance() {} + +} diff --git a/phyphox-iOS/phyphox/Camera/CameraPreview.swift b/phyphox-iOS/phyphox/Camera/CameraPreview.swift new file mode 100644 index 00000000..028c34c5 --- /dev/null +++ b/phyphox-iOS/phyphox/Camera/CameraPreview.swift @@ -0,0 +1,41 @@ +// +// CameraPreview.swift +// phyphox +// +// Created by Gaurav Tripathee on 13.11.23. +// Copyright © 2023 RWTH Aachen. All rights reserved. +// + +import SwiftUI +import AVFoundation + +@available(iOS 13.0, *) +struct PhyphoxCameraPreview: UIViewRepresentable { + class VideoPreviewView: UIView { + override class var layerClass: AnyClass { + AVCaptureVideoPreviewLayer.self + } + + var videoPreviewLayer: AVCaptureVideoPreviewLayer { + return layer as! AVCaptureVideoPreviewLayer + } + } + + let session: AVCaptureSession + + func makeUIView(context: Context) -> VideoPreviewView { + let view = VideoPreviewView() + view.backgroundColor = .black + view.videoPreviewLayer.cornerRadius = 0 + view.videoPreviewLayer.session = session + view.videoPreviewLayer.connection?.videoOrientation = .portrait + + return view + } + + func updateUIView(_ uiView: VideoPreviewView, context: Context) { + + } +} + + diff --git a/phyphox-iOS/phyphox/Camera/CameraService+Enums.swift b/phyphox-iOS/phyphox/Camera/CameraService+Enums.swift new file mode 100644 index 00000000..cbb3915d --- /dev/null +++ b/phyphox-iOS/phyphox/Camera/CameraService+Enums.swift @@ -0,0 +1,19 @@ +// +// CameraService+Enums.swift +// phyphox +// +// Created by Gaurav Tripathee on 13.11.23. +// Copyright © 2023 RWTH Aachen. All rights reserved. +// + +import Foundation + +@available(iOS 13.0, *) +extension CameraService{ + + enum SessionSetupResult { + case success + case notAuthorized + case configurationFailed + } +} diff --git a/phyphox-iOS/phyphox/Camera/CameraService.swift b/phyphox-iOS/phyphox/Camera/CameraService.swift new file mode 100644 index 00000000..c544a21d --- /dev/null +++ b/phyphox-iOS/phyphox/Camera/CameraService.swift @@ -0,0 +1,471 @@ +// +// CameraService.swift +// phyphox +// +// Created by Gaurav Tripathee on 13.11.23. +// Copyright © 2023 RWTH Aachen. All rights reserved. +// +//263a + + +import AVFoundation +import MetalKit +import CoreMedia + +@available(iOS 13.0, *) +public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate { + + public let session = AVCaptureSession() + + private let sessionQueue = DispatchQueue(label: "session queue", attributes: [], autoreleaseFrequency: .workItem) + + private let metadataObjectsQueue = DispatchQueue(label: "sample buffer", attributes: []) + + @objc dynamic var videoDeviceInput: AVCaptureDeviceInput! + + var setupResult: SessionSetupResult = .success + + weak var delegate: CameraCaptureHelperDelegate? + + public var alertError: AlertError = AlertError() + + var isSessionRunning = false + + var isConfigured = false + + @Published public var shouldShowAlertView = false + + @Published public var isCameraUnavailable = true + + private var queue = DispatchQueue(label: "video output queue") + + var metalRender : MetalRenderer? + + let metalView: MTKView = MTKView() + + var image: CGImage? + + var flag: Bool = true + + var defaultVideoDevice: AVCaptureDevice? + + @Published var zoomScale: CGFloat = 1.0 + + // MARK: Device Configuration Properties + private let videoDeviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera, .builtInDualCamera, .builtInTrueDepthCamera], mediaType: .video, position: .unspecified) + + + public func checkForPermisssion(){ + + switch AVCaptureDevice.authorizationStatus(for: .video) { + + case .authorized: + print("CameraService: checkForPermission: is authorizes") + break + case .notDetermined: + print("CameraService: checkForPermission: is notDetermined") + sessionQueue.suspend() + AVCaptureDevice.requestAccess(for: .video, completionHandler: { granted in + if !granted { + self.setupResult = .notAuthorized + } + self.sessionQueue.resume() + }) + default: + print("CameraService: checkForPermission: is default") + setupResult = .notAuthorized + + DispatchQueue.main.async { + self.alertError = AlertError(title: "Camera Access", message: "SwiftCamera doesn't have access to use your camera, please update your privacy settings.", primaryButtonTitle: "Settings", secondaryButtonTitle: nil, primaryAction: { + UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, + options: [:], completionHandler: nil) + + }, secondaryAction: nil) + self.shouldShowAlertView = true + self.isCameraUnavailable = true + } + + + } + } + + public func configure(){ + print("CameraService: configure") + sessionQueue.async { + self.configureSession() + } + + } + + public func setUpMetal(){ + + metalRender = CameraMetalView().makeCoordinator() + + + //metalView.backgroundColor = UIColor.clear + + //metalRender = MetalRenderer(renderer: metalView) + //metalView.delegate = metalRender + + // metalRender?.drawRectResized(size: metalView.bounds.size) + + + + } + + + private func configureSession(){ + + if setupResult != .success { + return + } + + session.beginConfiguration() + session.sessionPreset = .medium + + + + do { + + if let backCameraDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) { + // If a rear dual camera is not available, default to the rear wide angle camera. + defaultVideoDevice = backCameraDevice + } else if let frontCameraDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) { + // If the rear wide angle camera isn't available, default to the front wide angle camera. + defaultVideoDevice = frontCameraDevice + } + + + guard let videoDevice = defaultVideoDevice else { + print("Default video device is unavailable.") + setupResult = .configurationFailed + session.commitConfiguration() + return + } + + do { + try defaultVideoDevice?.lockForConfiguration() + defaultVideoDevice?.videoZoomFactor = max(1.0, min(zoomScale, (defaultVideoDevice?.activeFormat.videoMaxZoomFactor)!)) + defaultVideoDevice?.unlockForConfiguration() + } catch { + print("Error setting camera zoom: \(error.localizedDescription)") + } + + let videoDeviceInput = try AVCaptureDeviceInput(device: videoDevice) + + if session.canAddInput(videoDeviceInput) { + session.addInput(videoDeviceInput) + self.videoDeviceInput = videoDeviceInput + + } else { + print("Couldn't add video device input to the session.") + setupResult = .configurationFailed + session.commitConfiguration() + return + } + + // setup output, add output to our capture session + let captureOutput = AVCaptureVideoDataOutput() + + captureOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)] + + captureOutput.alwaysDiscardsLateVideoFrames = true + + + //captureOutput.videoSettings = + + let captureSessionQueue = DispatchQueue(label: "CameraSessionQueue", attributes: []) + captureOutput.setSampleBufferDelegate(self, queue: captureSessionQueue) + //captureOutput.alwaysDiscardsLateVideoFrames = true + if session.canAddOutput(captureOutput) { + session.addOutput(captureOutput) + } else { + print("Error: Cannot add the Output to the session") + } + + + } catch { + print("Couldn't create video device input: \(error)") + setupResult = .configurationFailed + session.commitConfiguration() + return + } + + session.commitConfiguration() + + print("CameraService: session configured") + self.isConfigured = true + + self.start() + + } + + private func texture(sampleBuffer: CMSampleBuffer?, textureCache: CVMetalTextureCache?, planeIndex: Int = 0, pixelFormat: MTLPixelFormat = .bgra8Unorm) throws -> MTLTexture { + guard let sampleBuffer = sampleBuffer else { + throw MetalCameraSessionError.missingSampleBuffer + } + guard let textureCache = textureCache else { + throw MetalCameraSessionError.failedToCreateTextureCache + } + guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { + throw MetalCameraSessionError.failedToGetImageBuffer + } + + let isPlanar = CVPixelBufferIsPlanar(imageBuffer) + let width = isPlanar ? CVPixelBufferGetWidthOfPlane(imageBuffer, planeIndex) : CVPixelBufferGetWidth(imageBuffer) + let height = isPlanar ? CVPixelBufferGetHeightOfPlane(imageBuffer, planeIndex) : CVPixelBufferGetHeight(imageBuffer) + + var imageTexture: CVMetalTexture? + + let result = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, textureCache, imageBuffer, nil, pixelFormat, width, height, planeIndex, &imageTexture) + + guard + let unwrappedImageTexture = imageTexture, + let texture = CVMetalTextureGetTexture(unwrappedImageTexture), + result == kCVReturnSuccess + else { + throw MetalCameraSessionError.failedToCreateTextureFromImage + } + + return texture + } + + public func start() { +// We use our capture session queue to ensure our UI runs smoothly on the main thread. + sessionQueue.async { + if !self.isSessionRunning && self.isConfigured { + switch self.setupResult { + case .success: + self.session.startRunning() + self.isSessionRunning = self.session.isRunning + print("CameraService: session running") + if self.session.isRunning { + DispatchQueue.main.async { + self.isCameraUnavailable = false + } + } + + case .configurationFailed, .notAuthorized: + print("Application not authorized to use camera") + + DispatchQueue.main.async { + self.alertError = AlertError(title: "Camera Error", message: "Camera configuration failed. Either your device camera is not available or its missing permissions", primaryButtonTitle: "Accept", secondaryButtonTitle: nil, primaryAction: nil, secondaryAction: nil) + self.shouldShowAlertView = true + self.isCameraUnavailable = true + } + } + } + } + } + + /// - Tag: ChangeCamera + public func changeCamera() { + + + sessionQueue.async { + let currentVideoDevice = self.videoDeviceInput.device + let currentPosition = currentVideoDevice.position + + let preferredPosition: AVCaptureDevice.Position + let preferredDeviceType: AVCaptureDevice.DeviceType + + switch currentPosition { + case .unspecified, .front: + preferredPosition = .back + preferredDeviceType = .builtInWideAngleCamera + + case .back: + preferredPosition = .front + preferredDeviceType = .builtInWideAngleCamera + + @unknown default: + print("Unknown capture position. Defaulting to back, dual-camera.") + preferredPosition = .back + preferredDeviceType = .builtInWideAngleCamera + } + let devices = self.videoDeviceDiscoverySession.devices + var newVideoDevice: AVCaptureDevice? = nil + + // First, seek a device with both the preferred position and device type. Otherwise, seek a device with only the preferred position. + if let device = devices.first(where: { $0.position == preferredPosition && $0.deviceType == preferredDeviceType }) { + newVideoDevice = device + } else if let device = devices.first(where: { $0.position == preferredPosition }) { + newVideoDevice = device + } + + if let videoDevice = newVideoDevice { + do { + let videoDeviceInput = try AVCaptureDeviceInput(device: videoDevice) + + self.session.beginConfiguration() + + // Remove the existing device input first, because AVCaptureSession doesn't support + // simultaneous use of the rear and front cameras. + self.session.removeInput(self.videoDeviceInput) + + if self.session.canAddInput(videoDeviceInput) { + self.session.addInput(videoDeviceInput) + self.videoDeviceInput = videoDeviceInput + } else { + self.session.addInput(self.videoDeviceInput) + } + + + self.session.commitConfiguration() + } catch { + print("Error occurred while creating video device input: \(error)") + } + } + + + } + } + + public func captureOutput(_ output: AVCaptureOutput, didDrop sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { + let totalSampleSize = CMSampleBufferGetTotalSampleSize(sampleBuffer) + print("CameraService: didDrop Total Sample Size: \(totalSampleSize)") + + print("CameraService: didDrop captureOutput sampleBuffer totalSampleSize", sampleBuffer.totalSampleSize) + + } + + + public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { + + + + guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { + print("imagebuffer is nil") + return + } + + self.metalRender?.updateFrame(imageBuffer: imageBuffer, selectionState: MetalRenderer.SelectionStruct(x1: 0.4, x2: 0.6, y1: 0.4, y2: 0.6, editable: true)) + + let isPlanar = CVPixelBufferIsPlanar(imageBuffer) + let width = isPlanar ? CVPixelBufferGetWidthOfPlane(imageBuffer, 0) : CVPixelBufferGetWidth(imageBuffer) + print("imagebuffer width: ", width) + let height = isPlanar ? CVPixelBufferGetHeightOfPlane(imageBuffer, 1) : CVPixelBufferGetHeight(imageBuffer) + print("imagebuffer height: ", height) + } + + private let context = CIContext() + + // MARK: Sample buffer to UIImage conversion + private func imageFromSampleBuffer(sampleBuffer: CMSampleBuffer) -> UIImage? { + guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return nil } + let ciImage = CIImage(cvPixelBuffer: imageBuffer) + + //context.render(ciImage, to: imageBuffer) + + //print("CameraSerivce: after Render: imagebuffer: ", imageBuffer. ) + self.metalRender?.updateFrame(imageBuffer: imageBuffer, selectionState: MetalRenderer.SelectionStruct(x1: 0.4, x2: 0.6, y1: 0.4, y2: 0.6, editable: true)) + guard let cgImage = context.createCGImage(ciImage, from: ciImage.extent) else { return nil } + //print("imageBuffer: bytes per row: ",imageBuffer) + return UIImage(cgImage: cgImage) + } + + + func convert(cmage:CIImage) -> UIImage { + let context:CIContext = CIContext.init(options: nil) + let cgImage:CGImage = context.createCGImage(cmage, from: cmage.extent)! + let image:UIImage = UIImage.init(cgImage: cgImage) + return image + } +} + +protocol CameraCaptureHelperDelegate: class +{ + @available(iOS 13.0, *) + func newCameraImage(_ cameraCaptureHelper: CameraService, image: CIImage) +} + + + + +public enum MetalCameraSessionState { + case ready + case streaming + case stopped + case waiting + case error +} + +public enum MetalCameraPixelFormat { + case rgb + case yCbCr + + var coreVideoType: OSType { + switch self { + case .rgb: + return kCVPixelFormatType_32BGRA + case .yCbCr: + return kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange + } + } +} + +/** + Streaming error + */ +public enum MetalCameraSessionError: Error { + /** + * Streaming errors + */// + case noHardwareAccess + case failedToAddCaptureInputDevice + case failedToAddCaptureOutput + case requestedHardwareNotFound + case inputDeviceNotAvailable + case captureSessionRuntimeError + + /** + * Conversion errors + */// + case failedToCreateTextureCache + case missingSampleBuffer + case failedToGetImageBuffer + case failedToCreateTextureFromImage + case failedToRetrieveTimestamp + + /** + Indicates if the error is related to streaming the media. + + - returns: True if the error is related to streaming, false otherwise + */ + public func isStreamingError() -> Bool { + switch self { + case .noHardwareAccess, .failedToAddCaptureInputDevice, .failedToAddCaptureOutput, .requestedHardwareNotFound, .inputDeviceNotAvailable, .captureSessionRuntimeError: + return true + default: + return false + } + } + + public var localizedDescription: String { + switch self { + case .noHardwareAccess: + return "Failed to get access to the hardware for a given media type" + case .failedToAddCaptureInputDevice: + return "Failed to add a capture input device to the capture session" + case .failedToAddCaptureOutput: + return "Failed to add a capture output data channel to the capture session" + case .requestedHardwareNotFound: + return "Specified hardware is not available on this device" + case .inputDeviceNotAvailable: + return "Capture input device cannot be opened, probably because it is no longer available or because it is in use" + case .captureSessionRuntimeError: + return "AVCaptureSession runtime error" + case .failedToCreateTextureCache: + return "Failed to initialize texture cache" + case .missingSampleBuffer: + return "No sample buffer to convert the image from" + case .failedToGetImageBuffer: + return "Failed to retrieve an image buffer from camera's output sample buffer" + case .failedToCreateTextureFromImage: + return "Failed to convert the frame to a Metal texture" + case .failedToRetrieveTimestamp: + return "Failed to retrieve timestamp from the sample buffer" + } + } +} + diff --git a/phyphox-iOS/phyphox/Camera/CameraView.swift b/phyphox-iOS/phyphox/Camera/CameraView.swift new file mode 100644 index 00000000..2e48e548 --- /dev/null +++ b/phyphox-iOS/phyphox/Camera/CameraView.swift @@ -0,0 +1,363 @@ +// +// CameraView.swift +// phyphox +// +// Created by Gaurav Tripathee on 13.11.23. +// Copyright © 2023 RWTH Aachen. All rights reserved. +// + +import Foundation +import SwiftUI +import Combine + + +@available(iOS 14.0, *) +struct PhyphoxCameraView: View { + + @StateObject var model = CameraModel() + + @State private var isMinimized = true + + @State private var overlayWidth: CGFloat = 50 + @State private var overlayHeight: CGFloat = 50 + + @State private var scale: CGFloat = 1.0 + @State private var currentPosition: CGSize = .zero + @State private var newPosition: CGSize = .zero + + @State private var height: CGFloat = 200.0 + @State private var width: CGFloat = 200.0 + @State private var startPosition: CGFloat = 0.0 + + @State private var rotation: Double = 0.0 + @State private var isFlipped: Bool = false + @State private var autoExposureOff : Bool = true + + @State private var zoomClicked: Bool = false + + + var mimimizeCameraButton: some View { + Button(action: { + self.isMinimized.toggle() + }, label: { + Image(systemName: isMinimized ? "arrow.down.right.and.arrow.up.left" : "arrow.up.left.and.arrow.down.right") + .font(.title2) + .padding() + .background(Color.black) + .foregroundColor(.white) + }) + } + + + var flipCameraButton: some View { + Button(action: { + if(isFlipped){ + withAnimation(Animation.linear(duration: 0.3)) { + self.rotation = -90.0 + } + isFlipped = false + } else { + withAnimation(Animation.linear(duration: 0.3)) { + self.rotation = 90.0 + } + isFlipped = true + } + + model.switchCamera() + }, label: { + Circle() + .opacity(isMinimized ? 1.0 : 0.0) + .foregroundColor(Color.gray.opacity(0.0)) + .frame(width: 45, height: 45, alignment: .center) + .overlay( + Image("flip_camera") + .resizable() + .scaledToFit() + .frame(width: 30, height: 30) + .rotationEffect(.degrees(rotation)) + .clipShape(/*@START_MENU_TOKEN@*/Circle()/*@END_MENU_TOKEN@*/) + ) + + }) + } + + @State private var zoomScale: CGFloat = 1.0 + + var zoomSlider: some View { + Button(action: { + zoomClicked = !zoomClicked + model.zoom() + }, label: { + Circle() + .opacity(isMinimized ? 1.0 : 0.0) + .foregroundColor(Color.gray.opacity(0.0)) + .frame(width: 45, height: 45, alignment: .center) + .overlay( + Image("ic_zoom") + .resizable() + .scaledToFit() + .frame(width: 30, height: 30) + .clipShape(/*@START_MENU_TOKEN@*/Circle()/*@END_MENU_TOKEN@*/)) + + }) + } + + var autoExposure: some View { + Button(action: { + autoExposureOff = !autoExposureOff + model.autoExposure() + }, label: { + Circle() + .opacity(isMinimized ? 1.0 : 0.0) + .foregroundColor(Color.gray.opacity(0.0)) + .frame(width: 45, height: 45, alignment: .center) + .overlay( + Image("ic_auto_exposure") + .resizable() + .scaledToFit() + .frame(width: 25, height: 25)) + + }) + } + + + var isoSetting: some View { + Button(action: { + model.iso() + }, label: { + Circle() + .opacity(isMinimized ? 1.0 : 0.0) + .foregroundColor(Color.gray.opacity(0.0)) + .frame(width: 45, height: 45, alignment: .center) + .overlay( + Image("ic_camera_iso") + .resizable() + .scaledToFit() + .frame(width: 30, height: 30) + .clipShape(/*@START_MENU_TOKEN@*/Circle()/*@END_MENU_TOKEN@*/)) + + }) + } + + var shutterSpeedSetting: some View { + Button(action: { + model.shutterSpeed() + }, label: { + Circle() + .opacity(isMinimized ? 1.0 : 0.0) + .foregroundColor(Color.gray.opacity(0.0)) + .frame(width: 45, height: 45, alignment: .center) + .overlay( + Image("ic_shutter_speed") + .resizable() + .scaledToFit() + .frame(width: 30, height: 30) + .clipShape(/*@START_MENU_TOKEN@*/Circle()/*@END_MENU_TOKEN@*/)) + }) + } + + var apertureSetting: some View { + Button(action: { + model.shutterSpeed() + }, label: { + Circle() + .opacity(isMinimized ? 1.0 : 0.0) + .foregroundColor(Color.gray.opacity(0.0)) + .frame(width: 45, height: 45, alignment: .center) + .overlay( + Image(systemName: "camera.aperture") + .resizable() + .scaledToFit() + .frame(width: 25, height: 25) + .clipShape(/*@START_MENU_TOKEN@*/Circle()/*@END_MENU_TOKEN@*/) + .foregroundColor(.white)) + }) + } + + @State private var speed = 50.0 + @State private var isEditing = false + + @State private var isZooming = false + + + var body: some View { + GeometryReader { reader in + ZStack { + //UIColor(named: "")?.edgesIgnoringSafeArea(.all) + + VStack(alignment: .center, spacing: 5) { + + HStack(spacing: 0){ + mimimizeCameraButton + .frame(maxWidth: .infinity, alignment: .leading) + + Text("Preview") + .foregroundColor(.white) + .frame(maxWidth: .infinity, alignment: .leading) + + Circle().frame(width: 50, height: 50).opacity(0.0) + + + } + + ZStack{ + + CameraMetalView() + .foregroundColor(.gray) + .onAppear{ + model.configure() + } + .foregroundColor(/*@START_MENU_TOKEN@*/.blue/*@END_MENU_TOKEN@*/) + .frame(width: !isMinimized ? reader.size.width / 2 : reader.size.width , + height: !isMinimized ? reader.size.height / 3 : reader.size.height / 1.5, + alignment: .topTrailing) + + + + + + } + + HStack { + + VStack(spacing: 0) { + flipCameraButton + .frame(maxWidth: .infinity) + + Text(isFlipped ? "Front" : "Back") + .font(.caption2) + } + + + VStack(spacing: 0) { + autoExposure + .frame(maxWidth: .infinity) + + Text(autoExposureOff ? "Off" : "On") + .font(.caption2) + + } + + + + VStack(spacing: 0) { + + isoSetting + .opacity(autoExposureOff ? 1.0 : 0.4 ) + .frame(maxWidth: .infinity) + .disabled(autoExposureOff ? false : true) + + Text("100").font(.caption2) + .opacity(autoExposureOff ? 1.0 : 0.4 ) + } + + + + VStack(spacing: 0) { + + shutterSpeedSetting + .opacity(autoExposureOff ? 1.0 : 0.4 ) + .frame(maxWidth: .infinity) + .disabled(autoExposureOff ? false : true) + + Text("1/60") + .font(.caption2) + .opacity(autoExposureOff ? 1.0 : 0.4 ) + } + + + + VStack(spacing: 0) { + + apertureSetting + .opacity(autoExposureOff ? 1.0 : 0.4 ) + .frame(maxWidth: .infinity) + .disabled(autoExposureOff ? false : true) + + Text("f/1.85") + .font(.caption2) + .opacity(autoExposureOff ? 1.0 : 0.4 ) + } + + + VStack(spacing: 0) { + + zoomSlider + .opacity(isMinimized ? 1.0 : 0.0) + .frame(maxWidth: .infinity) + + Text("Zoom").font(.caption2) + } + + + + + + } + .frame(maxWidth: .infinity) + .opacity(isMinimized ? 1.0 : 0.0) + .padding(.horizontal, 1) + .preferredColorScheme(.dark) + + + Spacer().frame(width: 10.0, height: 20.0) + + + + VStack { + Slider( + value: Binding(get: { + Double(self.model.getScale()) + }, set: { newValue in + self.model.setScale(scale: CGFloat(newValue)) + }), + in: 0...100, + step: 1 + ) { + Text("Speed") + } minimumValueLabel: { + Text("0") + } maximumValueLabel: { + Text("100") + } onEditingChanged: { editing in + isEditing = editing + }.opacity(isMinimized ? 1.0 : 0.0) + + }.opacity(zoomClicked ? 1.0 : 0.0) + + } + + } + }.background(Color.black) + } + +} + + +struct PhyphoxCameraView_Previews: PreviewProvider { + @available(iOS 13.0, *) + static var previews: some View { + if #available(iOS 14.0, *) { + PhyphoxCameraView() + } else { + // Fallback on earlier versions + } + } +} + +public struct AlertError { + public var title: String = "" + public var message: String = "" + public var primaryButtonTitle = "Accept" + public var secondaryButtonTitle: String? + public var primaryAction: (() -> ())? + public var secondaryAction: (() -> ())? + + public init(title: String = "", message: String = "", primaryButtonTitle: String = "Accept", secondaryButtonTitle: String? = nil, primaryAction: (() -> ())? = nil, secondaryAction: (() -> ())? = nil) { + self.title = title + self.message = message + self.primaryAction = primaryAction + self.primaryButtonTitle = primaryButtonTitle + self.secondaryAction = secondaryAction + } +} diff --git a/phyphox-iOS/phyphox/Camera/MetalRenderer.swift b/phyphox-iOS/phyphox/Camera/MetalRenderer.swift new file mode 100644 index 00000000..f6d3c8c6 --- /dev/null +++ b/phyphox-iOS/phyphox/Camera/MetalRenderer.swift @@ -0,0 +1,319 @@ +// +// MetalRenderer.swift +// phyphox +// +// Created by Gaurav Tripathee on 24.11.23. +// Copyright © 2023 RWTH Aachen. All rights reserved. +// + +import Foundation +import MetalKit + + +@available(iOS 13.0, *) +class MetalRenderer: NSObject, MTKViewDelegate { + + var metalDevice: MTLDevice! + var metalCommandQueue: MTLCommandQueue! + var imagePlaneVertexBuffer: MTLBuffer! + var renderDestination: RenderDestinationProvider + + // The current viewport size. + var viewportSize: CGSize = CGSize() + + // Flag for viewport size changes. + var viewportSizeDidChange: Bool = false + + // An object that defines the Metal shaders that render the camera image. + var pipelineState: MTLRenderPipelineState! + + // Captured image texture cache. + var cameraImageTextureCache: CVMetalTextureCache! + + let inFlightSemaphore = DispatchSemaphore(value: kMaxBuffersInFlight) + + struct SelectionStruct { + var x1, x2, y1, y2: Float + var editable: Bool + } + + var displayToCameraTransform: CGAffineTransform = .identity + var selectionState = SelectionStruct(x1: 0.4, x2: 0.6, y1: 0.4, y2: 0.6, editable: false) + + + // Textures used to transfer the current camera image to the GPU for rendering. + var cameraImageTextureY: CVMetalTexture? + var cameraImageTextureCbCr: CVMetalTexture? + + var cvImageBuffer : CVImageBuffer? + + + init(parent: CameraMetalView ,renderer: RenderDestinationProvider) { + print("Metal Renderer : init") + + self.renderDestination = renderer + + if let metalDevice = MTLCreateSystemDefaultDevice() { + self.metalDevice = metalDevice + } + + + super.init() + + loadMetal() + + + } + + func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { + drawRectResized(size: size) + + } + + func draw(in view: MTKView) { + + update() + + } + + // Schedule a draw to happen at a new size. + func drawRectResized(size: CGSize) { + viewportSize = size + viewportSizeDidChange = true + print("Metal Renderer: drawRectResized") + } + + + func updateFrame(imageBuffer: CVImageBuffer, selectionState: SelectionStruct) { + print("Metal Renderer: updateFrame") + self.cvImageBuffer = imageBuffer + self.selectionState = selectionState + } + + func loadMetal(){ + print("Metal Renderer: Load Metal") + // Set the default formats needed to render. + renderDestination.colorPixelFormat = .bgra8Unorm + renderDestination.sampleCount = 1 + + // Create a vertex buffer with our image plane vertex data. + let imagePlaneVertexDataCount = kImagePlaneVertexData.count * MemoryLayout.size + imagePlaneVertexBuffer = metalDevice.makeBuffer(bytes: kImagePlaneVertexData, length: imagePlaneVertexDataCount, options: []) + imagePlaneVertexBuffer.label = "ImagePlaneVertexBuffer" + + // Load all the shader files with a metal file extension in the project. + let defaultLibrary = metalDevice.makeDefaultLibrary()! + + // Create a vertex descriptor for our image plane vertex buffer. + let imagePlaneVertexDescriptor = MTLVertexDescriptor() + + // Positions. + imagePlaneVertexDescriptor.attributes[0].format = .float2 + imagePlaneVertexDescriptor.attributes[0].offset = 0 + imagePlaneVertexDescriptor.attributes[0].bufferIndex = Int(kBufferIndexMeshPositions.rawValue) + + // Texture coordinates. + imagePlaneVertexDescriptor.attributes[1].format = .float2 + imagePlaneVertexDescriptor.attributes[1].offset = 8 + imagePlaneVertexDescriptor.attributes[1].bufferIndex = Int(kBufferIndexMeshPositions.rawValue) + + // Buffer Layout. + imagePlaneVertexDescriptor.layouts[0].stride = 16 + imagePlaneVertexDescriptor.layouts[0].stepRate = 1 + imagePlaneVertexDescriptor.layouts[0].stepFunction = .perVertex + + // Create camera image texture cache. + var textureCache: CVMetalTextureCache? + CVMetalTextureCacheCreate(nil, nil, metalDevice, nil, &textureCache) + cameraImageTextureCache = textureCache + + // Define the shaders that will render the camera image on the GPU. + let vertexFunction = defaultLibrary.makeFunction(name: "vertexTransform")! + let fragmentFunction = defaultLibrary.makeFunction(name: "fragmentShader")! + let pipelineStateDescriptor = MTLRenderPipelineDescriptor() + pipelineStateDescriptor.label = "MyPipeline" + pipelineStateDescriptor.sampleCount = renderDestination.sampleCount + pipelineStateDescriptor.vertexFunction = vertexFunction + pipelineStateDescriptor.fragmentFunction = fragmentFunction + pipelineStateDescriptor.vertexDescriptor = imagePlaneVertexDescriptor + pipelineStateDescriptor.colorAttachments[0].pixelFormat = renderDestination.colorPixelFormat + + // Initialize the pipeline. + do { + try pipelineState = metalDevice.makeRenderPipelineState(descriptor: pipelineStateDescriptor) + } catch let error { + print("Failed to create pipeline state, error \(error)") + } + + // Create the command queue for one frame of rendering work. + metalCommandQueue = metalDevice.makeCommandQueue() + + print("Metal Renderer: Metal loaded") + + } + + + + func update() { + print("MetalRenderer: Update") + // Wait to ensure only kMaxBuffersInFlight are getting proccessed by any stage in the Metal + // pipeline (App, Metal, Drivers, GPU, etc). + _ = inFlightSemaphore.wait(timeout: DispatchTime.distantFuture) + + // Create a new command buffer for each renderpass to the current drawable. + if let commandBuffer = metalCommandQueue.makeCommandBuffer() { + commandBuffer.label = "MyCommand" + + // Add completion hander which signal _inFlightSemaphore when Metal and the GPU has fully + // finished proccssing the commands we're encoding this frame. This indicates when the + // dynamic buffers, that we're writing to this frame, will no longer be needed by Metal + // and the GPU. + commandBuffer.addCompletedHandler { [weak self] commandBuffer in + if let strongSelf = self { + strongSelf.inFlightSemaphore.signal() + } + } + + updateAppState() + + if let renderPassDescriptor = renderDestination.currentRenderPassDescriptor, let currentDrawable = renderDestination.currentDrawable { + + if let renderEncoding = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) { + + // Set a label to identify this render pass in a captured Metal frame. + renderEncoding.label = "DepthGUICameraPreview" + + // Schedule the camera image to be drawn to the screen. + doRenderPass(renderEncoder: renderEncoding) + + // Finish encoding commands. + renderEncoding.endEncoding() + } + + // Schedule a present once the framebuffer is complete using the current drawable. + commandBuffer.present(currentDrawable) + } + + // Finalize rendering here & push the command buffer to the GPU. + commandBuffer.commit() + + print("Metal Renderer: updated") + } + } + + // Schedules the camera image to be rendered on the GPU. + func doRenderPass(renderEncoder: MTLRenderCommandEncoder) { + guard let cameraImageY = cameraImageTextureY, let cameraImageCbCr = cameraImageTextureCbCr else { + return + } + + // Push a debug group that enables you to identify this render pass in a Metal frame capture. + renderEncoder.pushDebugGroup("CameraPass") + + // Set render command encoder state. + renderEncoder.setCullMode(.none) + renderEncoder.setRenderPipelineState(pipelineState) + + let p1 = CGPoint(x: CGFloat(selectionState.x1), y: CGFloat(selectionState.y1)).applying(displayToCameraTransform.inverted()) + let p2 = CGPoint(x: CGFloat(selectionState.x2), y: CGFloat(selectionState.y2)).applying(displayToCameraTransform.inverted()) + var scaledSelectionState = SelectionStruct(x1: Float(min(p1.x, p2.x)*viewportSize.width), x2: Float(max(p1.x, p2.x)*viewportSize.width), y1: Float(min(p1.y, p2.y)*viewportSize.height), y2: Float(max(p1.y, p2.y)*viewportSize.height), editable: selectionState.editable) + renderEncoder.setFragmentBytes(&scaledSelectionState, length: MemoryLayout.stride, index: 2) + + // Setup plane vertex buffers. + renderEncoder.setVertexBuffer(imagePlaneVertexBuffer, offset: 0, index: 0) + renderEncoder.setVertexBuffer(imagePlaneVertexBuffer, offset: 0, index: 1) + + // Setup textures for the camera fragment shader. + renderEncoder.setFragmentTexture(CVMetalTextureGetTexture(cameraImageY), index: 0) + renderEncoder.setFragmentTexture(CVMetalTextureGetTexture(cameraImageCbCr), index: 1) + + // Draw final quad to display + renderEncoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4) + renderEncoder.popDebugGroup() + } + + // Updates any app state. + func updateAppState() { + + print("updateAppState before") + // Get the AR session's current frame. + guard let currentFrame = cvImageBuffer else { + return + } + + // imageBuffer getting nil here + print("updateAppState after") + // Prepare the current frame's camera image for transfer to the GPU. + updateCameraImageTextures(frame: currentFrame) + + // Update the destination-rendering vertex info if the size of the screen changed. + if viewportSizeDidChange { + viewportSizeDidChange = false + updateImagePlane(frame: currentFrame) + } + } + + // Creates two textures (Y and CbCr) to transfer the current frame's camera image to the GPU for rendering. + func updateCameraImageTextures(frame: CVImageBuffer) { + if CVPixelBufferGetPlaneCount(frame) < 2 { + print("updateCameraImageTextures less than 2") + return + } + cameraImageTextureY = createTexture(fromPixelBuffer: frame, pixelFormat: .r8Unorm, planeIndex: 0) + cameraImageTextureCbCr = createTexture(fromPixelBuffer: frame, pixelFormat: .rg8Unorm, planeIndex: 1) + } + + // Creates a Metal texture with the argument pixel format from a CVPixelBuffer at the argument plane index. + func createTexture(fromPixelBuffer pixelBuffer: CVImageBuffer, pixelFormat: MTLPixelFormat, planeIndex: Int) -> CVMetalTexture? { + let width = CVPixelBufferGetWidthOfPlane(pixelBuffer, planeIndex) + let height = CVPixelBufferGetHeightOfPlane(pixelBuffer, planeIndex) + + var texture: CVMetalTexture? = nil + let status = CVMetalTextureCacheCreateTextureFromImage(nil, cameraImageTextureCache, pixelBuffer, nil, pixelFormat, + width, height, planeIndex, &texture) + + if status != kCVReturnSuccess { + texture = nil + } + + return texture + } + + // Sets up vertex data (source and destination rectangles) rendering. + func updateImagePlane(frame: CVImageBuffer) { + // Update the texture coordinates of the image plane to aspect fill the viewport. + let orientation = UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.windowScene?.interfaceOrientation ?? .portrait + let cgImageOrientation = CGImagePropertyOrientation(interfaceOrientation: orientation) + // Convert the image buffer to a CIImage + let ciImage = CIImage(cvPixelBuffer: frame) + displayToCameraTransform = ciImage.orientationTransform(for: cgImageOrientation).inverted() + let vertexData = imagePlaneVertexBuffer.contents().assumingMemoryBound(to: Float.self) + for index in 0...3 { + let textureCoordIndex = 4 * index + 2 + let textureCoord = CGPoint(x: CGFloat(kImagePlaneVertexData[textureCoordIndex]), y: CGFloat(kImagePlaneVertexData[textureCoordIndex + 1])) + let transformedCoord = textureCoord.applying(displayToCameraTransform) + vertexData[textureCoordIndex] = Float(transformedCoord.x) + vertexData[textureCoordIndex + 1] = Float(transformedCoord.y) + } + } + + + +} + +extension CGImagePropertyOrientation { + init(interfaceOrientation: UIInterfaceOrientation) { + switch interfaceOrientation { + case .portrait: + self = .up + case .portraitUpsideDown: + self = .down + case .landscapeLeft: + self = .left + case .landscapeRight: + self = .right + default: + self = .up + } + } +} diff --git a/phyphox-iOS/phyphox/Camera/MetalView.swift b/phyphox-iOS/phyphox/Camera/MetalView.swift new file mode 100644 index 00000000..ad895e17 --- /dev/null +++ b/phyphox-iOS/phyphox/Camera/MetalView.swift @@ -0,0 +1,47 @@ +// +// MetalView.swift +// phyphox +// +// Created by Gaurav Tripathee on 24.11.23. +// Copyright © 2023 RWTH Aachen. All rights reserved. +// + +import Foundation +import MetalKit +import SwiftUI + + +@available(iOS 13.0, *) +struct CameraMetalView: UIViewRepresentable { + + let metalView: MTKView = MTKView() + + + func makeCoordinator() -> MetalRenderer { + print("MetalView: makeCoordinator") + return MetalRenderer(parent: self, renderer: metalView) + } + + func makeUIView(context: UIViewRepresentableContext) -> MTKView { + print("MetalView: makeUIView") + metalView.delegate = context.coordinator + + metalView.preferredFramesPerSecond = 60 + //metalView.enableSetNeedsDisplay = true + + //metalView.framebufferOnly = false + metalView.clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0) + + metalView.drawableSize = CGSize(width: 100.0, height: 100.0) + + return metalView + } + + func updateUIView(_ uiView: MTKView, context: UIViewRepresentableContext) { + + } + + + +} + diff --git a/phyphox-iOS/phyphox/Experiments/Data-Input/ExperimentCameraInputSession.swift b/phyphox-iOS/phyphox/Experiments/Data-Input/ExperimentCameraInputSession.swift index 92e9003f..0d9f3ca4 100644 --- a/phyphox-iOS/phyphox/Experiments/Data-Input/ExperimentCameraInputSession.swift +++ b/phyphox-iOS/phyphox/Experiments/Data-Input/ExperimentCameraInputSession.swift @@ -10,8 +10,7 @@ import Foundation import AVFoundation @available(iOS 14.0, *) -final class ExperimentCameraInputSession: CameraGUISelectionDelegate { - +final class ExperimentCameraInputSession: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate, CameraGUISelectionDelegate, ObservableObject { var x1: Float = 0.4 var x2: Float = 0.6 @@ -32,34 +31,109 @@ final class ExperimentCameraInputSession: CameraGUISelectionDelegate { var measuring = false - private var queue: DispatchQueue? + private var queue: DispatchQueue? = DispatchQueue.global(qos: .userInteractive) + private var queueOutput: DispatchQueue? = DispatchQueue.global(qos: .userInteractive) + // Communicate with the session and other session objects on this queue. + private let sessionQueue = DispatchQueue(label: "session queue") - var cameraSession = AVCaptureSession() + @Published var cameraSession = AVCaptureSession() var screenRect: CGRect! = nil // For view dimensions - init(){ + + var isSessionRunning = false +// 12 + var isConfigured = false +// 13 + //var setupResult: SessionSetupResult = .success + + @objc dynamic var videoDeviceInput: AVCaptureDeviceInput! + + // MARK: Alert properties + public var alertError: AlertError = AlertError() + + + @Published public var shouldShowAlertView = false +// 3. + @Published public var shouldShowSpinner = false +// 4. + @Published public var willCapturePhoto = false +// 5. + @Published public var isCameraButtonDisabled = true +// 6. + @Published public var isCameraUnavailable = true + + + override init(){ frontCamera = false } - - func runSession(){ + + func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { + // Process pixel buffer bytes here + guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { + return + } + + + + // Convert the image buffer to a CIImage + let ciImage = CIImage(cvPixelBuffer: imageBuffer) + + + // Convert the CIImage to a UIImage + let context = CIContext(options: nil) + let cgImage = context.createCGImage(ciImage, from: ciImage.extent) + let width = cgImage?.width + let height = cgImage?.height - guard let videoDevice = AVCaptureDevice.default(.builtInDualWideCamera, for: AVMediaType.video, position: .back) else { return } + let uiImage = UIImage(cgImage: cgImage!) + // Example: Print a message in a background queue + queueOutput?.async { + + } + } + + + func runSession(){ + // get camera device + guard let videoDevice = AVCaptureDevice.default(for: .video) else { return } + + // plug camera device into device input which talkes with capture session via AVCaptureConnection guard let videoDeviceInput = try? AVCaptureDeviceInput(device: videoDevice) else { return } + cameraSession.sessionPreset = .high + if let existingInputs = cameraSession.inputs as? [AVCaptureInput] { + for input in existingInputs { + cameraSession.removeInput(input) + } + } guard cameraSession.canAddInput(videoDeviceInput) else { return } cameraSession.addInput(videoDeviceInput) - cameraSession.startRunning() + // setup output, add output to our capture session + let captureOutput = AVCaptureVideoDataOutput() + captureOutput.alwaysDiscardsLateVideoFrames = true + captureOutput.setSampleBufferDelegate(self, queue: queue) + cameraSession.addOutput(captureOutput) - delegate?.updateFrame(captureSession: cameraSession) + self.delegate?.updateFrame(captureSession: self.cameraSession) + + queue?.async { + print("runSession Queue") + if (self.cameraSession.isRunning == false) { + self.cameraSession.startRunning() + } + } } func stopSession() { running = false + if (self.cameraSession.isRunning == true) { + self.cameraSession.stopRunning() + } } func start(queue: DispatchQueue) throws { @@ -69,8 +143,10 @@ final class ExperimentCameraInputSession: CameraGUISelectionDelegate { func stop() { measuring = false + } + func clear() { } @@ -124,5 +200,7 @@ final class ExperimentCameraInputSession: CameraGUISelectionDelegate { } } } + + } diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 70e00407..f8d673db 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11316 + 12875 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift index 6663af36..d1e91f36 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift @@ -511,13 +511,6 @@ final class ExperimentPageViewController: UIViewController, UIPageViewController depthGUI.depthGUISelectionDelegate = session } - if let cameraGUI = view as? ExperimentCameraGUIView { - guard let session = experiment.cameraInput?.session as? ExperimentCameraInputSession else { - continue - } - session.attachDelegate(delegate: cameraGUI) - cameraGUI.depthGUISelectionDelegate = session - } } } } @@ -541,6 +534,10 @@ final class ExperimentPageViewController: UIViewController, UIPageViewController if let session = experiment.depthInput?.session as? ExperimentDepthInputSession { session.stopSession() } + + if let camSession = experiment.cameraInput?.session as? ExperimentCameraInputSession { + camSession.stopSession() + } } disconnectFromBluetoothDevices() disconnectFromNetworkDevices() diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/ExperimentViewModuleFactory.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/ExperimentViewModuleFactory.swift index 06d94b92..1f653d4b 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/ExperimentViewModuleFactory.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/ExperimentViewModuleFactory.swift @@ -7,6 +7,7 @@ // import UIKit +import SwiftUI final class ExperimentViewModuleFactory { @@ -51,7 +52,12 @@ final class ExperimentViewModuleFactory { } } else if let descriptor = descriptor as? CameraViewDescriptor { - views.append(ExperimentCameraGUIView(descriptor: descriptor)) + if #available(iOS 14.0, *) { + // TODO need to pass descriptor in view argument. + views.append(UIHostingController(rootView: PhyphoxCameraView()).view) + } else { + // Fallback on earlier versions + } } else { print("Error! Invalid view descriptor: \(descriptor)") diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/DepthGUI/ExperimentDepthGUIRenderer.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/DepthGUI/ExperimentDepthGUIRenderer.swift index 55f01833..aaa61f59 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/DepthGUI/ExperimentDepthGUIRenderer.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/DepthGUI/ExperimentDepthGUIRenderer.swift @@ -265,7 +265,7 @@ class ExperimentDepthGUIRenderer { let p2 = CGPoint(x: CGFloat(selectionState.x2), y: CGFloat(selectionState.y2)).applying(displayToCameraTransform.inverted()) var scaledSelectionState = SelectionStruct(x1: Float(min(p1.x, p2.x)*viewportSize.width), x2: Float(max(p1.x, p2.x)*viewportSize.width), y1: Float(min(p1.y, p2.y)*viewportSize.height), y2: Float(max(p1.y, p2.y)*viewportSize.height), editable: selectionState.editable) renderEncoder.setFragmentBytes(&scaledSelectionState, length: MemoryLayout.stride, index: 2) - + // Setup plane vertex buffers. renderEncoder.setVertexBuffer(imagePlaneVertexBuffer, offset: 0, index: 0) renderEncoder.setVertexBuffer(imagePlaneVertexBuffer, offset: 0, index: 1) diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentCameraGUIView.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentCameraGUIView.swift index 8508c040..0215c25b 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentCameraGUIView.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentCameraGUIView.swift @@ -8,6 +8,7 @@ import Foundation import AVFoundation +import SwiftUI protocol CameraGUIDelegate { func updateFrame(captureSession: AVCaptureSession) @@ -23,86 +24,37 @@ protocol CameraGUISelectionDelegate { } + +@available(iOS 13.0, *) final class ExperimentCameraGUIView: UIView, CameraGUIDelegate { var videoPreviewLayer: AVCaptureVideoPreviewLayer? - - func updateFrame(captureSession: AVCaptureSession) { - videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession) - layoutSubviews() - } - - var resolution: CGSize? var depthGUISelectionDelegate: CameraGUISelectionDelegate? - func updateResolution(resolution: CGSize) { - self.resolution = resolution - setNeedsLayout() - } - + var resolution: CGSize? var layoutDelegate: ModuleExclusiveLayoutDelegate? = nil var resizableState: ResizableViewModuleState = .normal - let unfoldMoreImageView: UIImageView - let unfoldLessImageView: UIImageView + var showMoreButton: UIButton = UIButton() - let descriptor: CameraViewDescriptor - let spacing: CGFloat = 1.0 - private let sideMargins:CGFloat = 10.0 - private let buttonPadding: CGFloat = 20.0 + var showLessButton: UIButton = UIButton() + let videoPreviewUIView = UIView() - var screenRect: CGRect! = nil // For view dimensions private let label = UILabel() - - private let aggregationBtn = UIButton() - private let cameraBtn = UIButton() - - var panGestureRecognizer: UIPanGestureRecognizer? = nil - - required init?(descriptor: CameraViewDescriptor) { - self.descriptor = descriptor - - unfoldLessImageView = UIImageView(image: UIImage(named: "unfold_less")) - unfoldMoreImageView = UIImageView(image: UIImage(named: "unfold_more")) - - - aggregationBtn.backgroundColor = UIColor(named: "lightBackgroundColor") - aggregationBtn.setTitle(localize("depthAggregationMode"), for: UIControl.State()) - aggregationBtn.isHidden = true - - cameraBtn.backgroundColor = UIColor(named: "lightBackgroundColor") - cameraBtn.setTitle(localize("sensorCamera"), for: UIControl.State()) - cameraBtn.isHidden = true + required init() { super.init(frame: .zero) - label.numberOfLines = 0 - label.text = descriptor.localizedLabel - label.font = UIFont.preferredFont(forTextStyle: .body) - label.textColor = UIColor(named: "textColor") - - addSubview(label) - - - - let unfoldRect = CGRect(x: 5, y: 5, width: 20, height: 20) - unfoldMoreImageView.frame = unfoldRect - unfoldLessImageView.frame = unfoldRect - unfoldLessImageView.isHidden = true - unfoldMoreImageView.isHidden = false - - addSubview(unfoldMoreImageView) - addSubview(unfoldLessImageView) - - addSubview(aggregationBtn) - addSubview(cameraBtn) + setUpCameraViews() + addSubview(showMoreButton) + addSubview(showLessButton) + //horizontalCameraSettingsStackView() } - @available(*, unavailable) required init?(coder aDecoder: NSCoder) { @@ -110,57 +62,144 @@ final class ExperimentCameraGUIView: UIView, CameraGUIDelegate { } override func sizeThatFits(_ size: CGSize) -> CGSize { - switch resizableState { - case .exclusive: - return size - case .hidden: - return CGSize.init(width: 0, height: 0) - default: - let labelh = label.sizeThatFits(size).height - - return CGSize(width: size.width, height: Swift.min((size.width-2*sideMargins)/descriptor.aspectRatio + labelh + 2*spacing, size.height)) - } + return size } override func layoutSubviews() { super.layoutSubviews() - videoPreviewLayer?.frame = bounds - videoPreviewLayer?.videoGravity = .resizeAspectFill + print("layoutSubviews") + var cameraWidth : CGFloat + var cameraHeight : CGFloat + var previewXPosition: CGFloat - if(videoPreviewLayer != nil){ - layer.addSublayer(videoPreviewLayer!) + + switch resizableState { + case .normal: + cameraWidth = (UIScreen.main.bounds.width - 20) / 2 + cameraHeight = UIScreen.main.bounds.height / 3 + previewXPosition = (UIScreen.main.bounds.width / 2) - ((videoPreviewLayer?.frame.width ?? 0)/2) + case .exclusive: + cameraWidth = UIScreen.main.bounds.width - 20 + cameraHeight = UIScreen.main.bounds.height / 2 + previewXPosition = 10 + case .hidden: + cameraWidth = 0 + cameraHeight = 0 + previewXPosition = 10 } - + if(videoPreviewLayer != nil){ + print("updateFrame: layoutSubviews") + videoPreviewLayer?.frame = CGRect(x: previewXPosition, y: 40, width: cameraWidth, height: cameraHeight) + videoPreviewLayer?.contents = + videoPreviewLayer?.videoGravity = .resizeAspectFill + videoPreviewUIView.layer.addSublayer(videoPreviewLayer!) + addSubview(videoPreviewUIView) + + } } - func resizableStateChanged(_ newState: ResizableViewModuleState) { + func updateFrame(captureSession: AVCaptureSession) { + //videoPreviewLayer = ScannerOverlayPreviewLayer(session: captureSession) + videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession) + layoutSubviews() } - @objc func tapped(_ sender: UITapGestureRecognizer) { - if resizableState == .normal { - layoutDelegate?.presentExclusiveLayout(self) - } else { - layoutDelegate?.restoreLayout() - } + func updateResolution(resolution: CGSize) { + self.resolution = resolution + setNeedsLayout() } - var panningIndexX: Int = 0 - var panningIndexY: Int = 0 - @objc func panned (_ sender: UIPanGestureRecognizer) { - guard var del = depthGUISelectionDelegate else { - return + func setUpCameraViews() { + + let unfoldRect = CGRectMake(5, 5, 40, 40) + + if #available(iOS 13.0, *) { + showMoreButton.setImage(UIImage(systemName: "arrow.up.left.and.arrow.down.right"), for: .normal) + } else { + showMoreButton.setImage(UIImage(named: "unfold_more"), for: .normal) + } + showMoreButton.frame = unfoldRect + showMoreButton.contentMode = .scaleAspectFill + showMoreButton.isHidden = false + showMoreButton.addTarget(self, action: #selector(maximizeCamera), for: .touchUpInside) + + if #available(iOS 13.0, *) { + showLessButton.setImage(UIImage(systemName: "arrow.down.right.and.arrow.up.left"), for: .normal) + } else { + showLessButton.setImage(UIImage(named: "unfold_less"), for: .normal) } + showLessButton.frame = unfoldRect + showLessButton.isHidden = true + showLessButton.addTarget(self, action: #selector(minimizeCamera), for: .touchUpInside) + + } - @objc private func aggregationBtnPressed() { - } + @objc private func maximizeCamera() { + print("maximizeCamera") + + showLessButton.isHidden = false + showMoreButton.isHidden = true + + resizableState = .exclusive + layoutSubviews() + } + + @objc private func minimizeCamera() { + print("minimizeCamera") + + showLessButton.isHidden = true + showMoreButton.isHidden = false + + resizableState = .normal + layoutSubviews() + } + + private func horizontalCameraSettingsStackView(){ + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.alignment = .fill + stackView.distribution = .fillEqually + stackView.spacing = 10 + stackView.translatesAutoresizingMaskIntoConstraints = false + + // Create buttons + for i in 1...5 { + let button = UIButton() + button.setTitle("Button \(i)", for: .normal) + button.setTitleColor( UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0), for: .normal) + button.setImage(UIImage(named: "new_experiment_simple"), for: .normal) + button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside) + button.translatesAutoresizingMaskIntoConstraints = false + stackView.addArrangedSubview(button) + } + + addSubview(stackView) + + NSLayoutConstraint.activate([ + // Video preview view constraints + videoPreviewUIView.topAnchor.constraint(equalTo: topAnchor), + videoPreviewUIView.leadingAnchor.constraint(equalTo: leadingAnchor), + videoPreviewUIView.trailingAnchor.constraint(equalTo: trailingAnchor), + videoPreviewUIView.bottomAnchor.constraint(equalTo: bottomAnchor), + + // Button 1 constraints + stackView.topAnchor.constraint(equalTo: videoPreviewUIView.bottomAnchor, constant: 20), + stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), + + ]) - @objc private func cameraBtnPressed() { } + + + @objc func buttonTapped(_ sender: UIButton) { + print("Button tapped: \(sender.titleLabel?.text ?? "")") + } + } diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentDepthGUIView.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentDepthGUIView.swift index 094bce66..e6a624e8 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentDepthGUIView.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentDepthGUIView.swift @@ -89,7 +89,7 @@ final class ExperimentDepthGUIView: UIView, DescriptorBoundViewModule, Resizable arView.device = MTLCreateSystemDefaultDevice() arView.backgroundColor = UIColor.clear - + renderer = ExperimentDepthGUIRenderer(metalDevice: arView.device!, renderDestination: arView) aggregationBtn.backgroundColor = UIColor(named: "lightBackgroundColor") From 767cf31b155155e8c94351283e8efb942cc4e08f Mon Sep 17 00:00:00 2001 From: GTripathee Date: Tue, 2 Jan 2024 12:47:26 +0100 Subject: [PATCH 41/81] ix: add project file --- phyphox-iOS/phyphox.xcodeproj/project.pbxproj | 51 +++++++++++++++++-- .../phyphox/Camera/CameraService.swift | 2 - .../phyphox/Camera/MetalRenderer.swift | 19 +++++-- phyphox-iOS/phyphox/Camera/MetalView.swift | 5 +- phyphox-iOS/phyphox/Info.plist | 2 +- 5 files changed, 64 insertions(+), 15 deletions(-) diff --git a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj index 5f98a4fe..b3262756 100644 --- a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj +++ b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj @@ -159,6 +159,14 @@ 867E2BD82AC464D000ABB472 /* CocoaMQTT in Frameworks */ = {isa = PBXBuildFile; productRef = 867E2BD72AC464D000ABB472 /* CocoaMQTT */; }; A101AB0829BB801600B82311 /* ConnectedBluetoothDeviceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A101AB0729BB801600B82311 /* ConnectedBluetoothDeviceViewController.swift */; }; 866632E92A29E1D90068762D /* UIColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 866632E82A29E1D90068762D /* UIColorExtensions.swift */; }; + A15891F02B10BFD000A169C3 /* MetalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A15891EF2B10BFD000A169C3 /* MetalView.swift */; }; + A15891F22B10BFED00A169C3 /* MetalRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A15891F12B10BFED00A169C3 /* MetalRenderer.swift */; }; + A182CEDD2B025B23000385D2 /* CameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A182CEDC2B025B23000385D2 /* CameraView.swift */; }; + A182CEDF2B025B41000385D2 /* CameraModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A182CEDE2B025B41000385D2 /* CameraModel.swift */; }; + A182CEE12B025B53000385D2 /* CameraService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A182CEE02B025B53000385D2 /* CameraService.swift */; }; + A182CEE32B025B6C000385D2 /* CameraInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = A182CEE22B025B6C000385D2 /* CameraInput.swift */; }; + A182CEE52B028525000385D2 /* CameraPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = A182CEE42B028525000385D2 /* CameraPreview.swift */; }; + A182CEE72B02DF10000385D2 /* CameraService+Enums.swift in Sources */ = {isa = PBXBuildFile; fileRef = A182CEE62B02DF10000385D2 /* CameraService+Enums.swift */; }; A1A6A72F297685DF00090309 /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1A6A72E297685DF00090309 /* Utility.swift */; }; A1A80D3D29AE049E00AD756D /* SettingsBundleHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1A80D3C29AE049E00AD756D /* SettingsBundleHelper.swift */; }; A1B9C1BC2AA9E45900238AF6 /* ExperimentCameraInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B9C1BB2AA9E45900238AF6 /* ExperimentCameraInput.swift */; }; @@ -490,6 +498,14 @@ 86C183D129F132020089E396 /* ka */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ka; path = ka.lproj/Localizable.strings; sourceTree = ""; }; 86C183D229F132020089E396 /* ka */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ka; path = ka.lproj/InfoPlist.strings; sourceTree = ""; }; A101AB0729BB801600B82311 /* ConnectedBluetoothDeviceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedBluetoothDeviceViewController.swift; sourceTree = ""; }; + A15891EF2B10BFD000A169C3 /* MetalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetalView.swift; sourceTree = ""; }; + A15891F12B10BFED00A169C3 /* MetalRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetalRenderer.swift; sourceTree = ""; }; + A182CEDC2B025B23000385D2 /* CameraView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraView.swift; sourceTree = ""; }; + A182CEDE2B025B41000385D2 /* CameraModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraModel.swift; sourceTree = ""; }; + A182CEE02B025B53000385D2 /* CameraService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraService.swift; sourceTree = ""; }; + A182CEE22B025B6C000385D2 /* CameraInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraInput.swift; sourceTree = ""; }; + A182CEE42B028525000385D2 /* CameraPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPreview.swift; sourceTree = ""; }; + A182CEE62B02DF10000385D2 /* CameraService+Enums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraService+Enums.swift"; sourceTree = ""; }; A1A6A72E297685DF00090309 /* Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = ""; }; A1A80D3C29AE049E00AD756D /* SettingsBundleHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsBundleHelper.swift; sourceTree = ""; }; A1B9C1BB2AA9E45900238AF6 /* ExperimentCameraInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperimentCameraInput.swift; sourceTree = ""; }; @@ -741,6 +757,7 @@ 6B7BF77C1C130425007408FB /* phyphox */ = { isa = PBXGroup; children = ( + A182CEDB2B025AE8000385D2 /* Camera */, A1E03AA129B779A400CE93A6 /* Helper */, 6B7BF7C11C13048B007408FB /* AppDelegate.swift */, 6B0C71541C1C706A00500FEE /* Constants.swift */, @@ -1123,6 +1140,21 @@ path = "Complex-Update-Value"; sourceTree = ""; }; + A182CEDB2B025AE8000385D2 /* Camera */ = { + isa = PBXGroup; + children = ( + A182CEDC2B025B23000385D2 /* CameraView.swift */, + A182CEDE2B025B41000385D2 /* CameraModel.swift */, + A182CEE02B025B53000385D2 /* CameraService.swift */, + A182CEE22B025B6C000385D2 /* CameraInput.swift */, + A182CEE42B028525000385D2 /* CameraPreview.swift */, + A182CEE62B02DF10000385D2 /* CameraService+Enums.swift */, + A15891EF2B10BFD000A169C3 /* MetalView.swift */, + A15891F12B10BFED00A169C3 /* MetalRenderer.swift */, + ); + path = Camera; + sourceTree = ""; + }; A1E03AA129B779A400CE93A6 /* Helper */ = { isa = PBXGroup; children = ( @@ -1291,7 +1323,6 @@ 6B7BF7791C130425007408FB = { CreatedOnToolsVersion = 7.1.1; LastSwiftMigration = 1230; - ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.BackgroundModes = { enabled = 1; @@ -1500,6 +1531,7 @@ 6BC011AD1CAA86670053D0DF /* ExperimentTranslationCollection.swift in Sources */, C07B53092294267400BA085A /* ExperimentBluetoothInput.swift in Sources */, 6B63199B1C4969DA0038BE22 /* MainNavigationBar.swift in Sources */, + A15891F02B10BFD000A169C3 /* MetalView.swift in Sources */, 6B63196A1C4849900038BE22 /* CollectionView.swift in Sources */, C07BBC1F26209CAC0097C57A /* ExperimentTimedRunDialogView.swift in Sources */, 6B7BF80F1C147244007408FB /* TanAnalysis.swift in Sources */, @@ -1522,6 +1554,7 @@ 6B7BF7BF1C130471007408FB /* MainView.swift in Sources */, C063C666238ECF7A00327AE7 /* NetworkConversion.swift in Sources */, 6B612FF4207EA4E5001DA8D9 /* OutputElementHandler.swift in Sources */, + A182CEE12B025B53000385D2 /* CameraService.swift in Sources */, 6BCD496720505DA70079E569 /* ExperimentGraphUtilities.swift in Sources */, A1B9C1BE2AA9E59D00238AF6 /* CameraViewDescriptor.swift in Sources */, 6B612FFD207F70D9001DA8D9 /* ExperimentAnalysisFactory.swift in Sources */, @@ -1529,6 +1562,7 @@ 4B47B0371DA6AF2400CCBA6E /* SinhAnalysis.swift in Sources */, C060904E21E28727000BEE30 /* ExperimentPickerViewController.swift in Sources */, 4BE7CAF91E4CD73D00C31090 /* ExperimentSeparatorView.swift in Sources */, + A182CEE32B025B6C000385D2 /* CameraInput.swift in Sources */, A1FA9A0429A7ACE800E0FDBF /* ColorConverterHelper.swift in Sources */, C0914ED922688F1E00000A2F /* FormulaParser.swift in Sources */, 4BE1DEFB1E5096F400F10C71 /* SubrangeAnalysis.swift in Sources */, @@ -1540,6 +1574,7 @@ 6B00F0B01C4684F100971941 /* ThresholdAnalysis.swift in Sources */, 6B2E0FC4205AC5E1008FAA91 /* UIColor+Expanded.m in Sources */, 6B00F0B71C46F23000971941 /* ExperimentExport.swift in Sources */, + A182CEE52B028525000385D2 /* CameraPreview.swift in Sources */, 6B94A6211CA015A4008D9ACF /* GLGraphView.swift in Sources */, A1E03A7F29B61A9000CE93A6 /* UIImageExt.swift in Sources */, 6B612FF6207EB052001DA8D9 /* AnalysisElementHandler.swift in Sources */, @@ -1597,6 +1632,7 @@ 4B47B03B1DA6AF6900CCBA6E /* TanhAnalysis.swift in Sources */, 6B00F0A21C457B5D00971941 /* ExperimentValueView.swift in Sources */, C08725112241460500079279 /* ColorElementHandler.swift in Sources */, + A15891F22B10BFED00A169C3 /* MetalRenderer.swift in Sources */, C05CA3262715D0BD00452FB7 /* ExperimentDepthInput.swift in Sources */, 6B7BF8171C147B8F007408FB /* RampGeneratorAnalysis.swift in Sources */, 6B7BF8211C147CF3007408FB /* AutocorrelationAnalysis.swift in Sources */, @@ -1619,6 +1655,7 @@ C05CD472258C904C00662BDF /* EventsElementHandler.swift in Sources */, 866632E92A29E1D90068762D /* UIColorExtensions.swift in Sources */, 4B5568B51DD7D1F4000C033E /* ExperimentButtonView.swift in Sources */, + A182CEDF2B025B41000385D2 /* CameraModel.swift in Sources */, 6B7BF81B1C147CA8007408FB /* GaussSmoothAnalysis.swift in Sources */, 6B00F0BA1C46F24400971941 /* ExperimentExportSet.swift in Sources */, C01F80FC226A05C5001B0DF8 /* FormulaAnalysis.swift in Sources */, @@ -1628,6 +1665,7 @@ 6BE320B21CB3C0DE00DCC1ED /* PTButton.m in Sources */, 4B47B0331DA6A87A00CCBA6E /* CountAnalysis.swift in Sources */, A1A6A72F297685DF00090309 /* Utility.swift in Sources */, + A182CEE72B02DF10000385D2 /* CameraService+Enums.swift in Sources */, 6B7BF81F1C147CE0007408FB /* DifferentiationAnalysis.swift in Sources */, 6B7BF8051C146B23007408FB /* GCDAnalysis.swift in Sources */, 6BEA9457207FF8CE0077274A /* GraphViewElementHandler.swift in Sources */, @@ -1665,6 +1703,7 @@ 6B26F6FD1CAAEDFB00EA9367 /* ExperimentExportSetSelectionView.swift in Sources */, C034B02727172BF500C0EF00 /* Shaders.metal in Sources */, 6B6A4C202056A367007586D0 /* GLGraphShaderProgram.swift in Sources */, + A182CEDD2B025B23000385D2 /* CameraView.swift in Sources */, 6B8AA3F21C5E2A6400E59685 /* ExperimentComplexUpdateValueAnalysis.swift in Sources */, A1B9C1BC2AA9E45900238AF6 /* ExperimentCameraInput.swift in Sources */, 6B422E2C1CA4A4BF00E945BC /* GraphGridView.swift in Sources */, @@ -1912,8 +1951,8 @@ CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = NO; CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES; CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = NO; - CODE_SIGN_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 8453; DEVELOPMENT_TEAM = 44GTV4NJA9; ENABLE_BITCODE = NO; @@ -1950,6 +1989,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "de.rwth-aachen.physics.phyphox"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = NO; SWIFT_OBJC_BRIDGING_HEADER = "phyphox/phyphox-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -1978,8 +2018,8 @@ CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = NO; CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES; CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = NO; - CODE_SIGN_IDENTITY = "iPhone Distribution"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 8453; DEVELOPMENT_TEAM = 44GTV4NJA9; ENABLE_BITCODE = NO; @@ -2010,6 +2050,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "de.rwth-aachen.physics.phyphox"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = NO; SWIFT_OBJC_BRIDGING_HEADER = "phyphox/phyphox-Bridging-Header.h"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; diff --git a/phyphox-iOS/phyphox/Camera/CameraService.swift b/phyphox-iOS/phyphox/Camera/CameraService.swift index c544a21d..2f6605bf 100644 --- a/phyphox-iOS/phyphox/Camera/CameraService.swift +++ b/phyphox-iOS/phyphox/Camera/CameraService.swift @@ -332,8 +332,6 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { - - guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { print("imagebuffer is nil") return diff --git a/phyphox-iOS/phyphox/Camera/MetalRenderer.swift b/phyphox-iOS/phyphox/Camera/MetalRenderer.swift index f6d3c8c6..c78fc622 100644 --- a/phyphox-iOS/phyphox/Camera/MetalRenderer.swift +++ b/phyphox-iOS/phyphox/Camera/MetalRenderer.swift @@ -47,6 +47,8 @@ class MetalRenderer: NSObject, MTKViewDelegate { var cvImageBuffer : CVImageBuffer? + var arrCVImageBuffer: [CVImageBuffer] + init(parent: CameraMetalView ,renderer: RenderDestinationProvider) { print("Metal Renderer : init") @@ -57,7 +59,7 @@ class MetalRenderer: NSObject, MTKViewDelegate { self.metalDevice = metalDevice } - + arrCVImageBuffer = [CVImageBuffer]() super.init() loadMetal() @@ -84,9 +86,16 @@ class MetalRenderer: NSObject, MTKViewDelegate { } - func updateFrame(imageBuffer: CVImageBuffer, selectionState: SelectionStruct) { + func updateFrame(imageBuffer: CVImageBuffer!, selectionState: SelectionStruct) { print("Metal Renderer: updateFrame") - self.cvImageBuffer = imageBuffer + + if imageBuffer != nil { + print("Metal Renderer: imageBuffer not nil") + arrCVImageBuffer.append(imageBuffer) + print("Metal Renderer: imageBuffer size: ", arrCVImageBuffer.count) + self.cvImageBuffer = imageBuffer + } + self.selectionState = selectionState } @@ -236,8 +245,10 @@ class MetalRenderer: NSObject, MTKViewDelegate { func updateAppState() { print("updateAppState before") + + print("Metal Renderer: updateAppState imageBuffer size: ", arrCVImageBuffer.count) // Get the AR session's current frame. - guard let currentFrame = cvImageBuffer else { + guard let currentFrame = self.cvImageBuffer else { return } diff --git a/phyphox-iOS/phyphox/Camera/MetalView.swift b/phyphox-iOS/phyphox/Camera/MetalView.swift index ad895e17..c7324e62 100644 --- a/phyphox-iOS/phyphox/Camera/MetalView.swift +++ b/phyphox-iOS/phyphox/Camera/MetalView.swift @@ -17,10 +17,9 @@ struct CameraMetalView: UIViewRepresentable { let metalView: MTKView = MTKView() - func makeCoordinator() -> MetalRenderer { + func makeCoordinator() -> Coordinator { print("MetalView: makeCoordinator") - return MetalRenderer(parent: self, renderer: metalView) - } + return Coordinator(renderer: MetalRenderer(parent: self, renderer: metalView)) func makeUIView(context: UIViewRepresentableContext) -> MTKView { print("MetalView: makeUIView") diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index f8d673db..f78de305 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 12875 + 12877 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 962a5dd6ce13d07e724559f190e96bc9c7a25e5d Mon Sep 17 00:00:00 2001 From: GTripathee Date: Wed, 3 Jan 2024 13:49:51 +0100 Subject: [PATCH 42/81] fix: remove multiple instance creation of metalRenderer --- phyphox-iOS/phyphox.xcodeproj/project.pbxproj | 4 -- phyphox-iOS/phyphox/Camera/CameraModel.swift | 7 +++- .../phyphox/Camera/CameraPreview.swift | 41 ------------------- .../phyphox/Camera/CameraService.swift | 15 ------- phyphox-iOS/phyphox/Camera/CameraView.swift | 2 +- .../phyphox/Camera/MetalRenderer.swift | 25 ++++++----- phyphox-iOS/phyphox/Camera/MetalView.swift | 20 ++++----- phyphox-iOS/phyphox/Info.plist | 2 +- 8 files changed, 32 insertions(+), 84 deletions(-) delete mode 100644 phyphox-iOS/phyphox/Camera/CameraPreview.swift diff --git a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj index b3262756..10615097 100644 --- a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj +++ b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj @@ -165,7 +165,6 @@ A182CEDF2B025B41000385D2 /* CameraModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A182CEDE2B025B41000385D2 /* CameraModel.swift */; }; A182CEE12B025B53000385D2 /* CameraService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A182CEE02B025B53000385D2 /* CameraService.swift */; }; A182CEE32B025B6C000385D2 /* CameraInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = A182CEE22B025B6C000385D2 /* CameraInput.swift */; }; - A182CEE52B028525000385D2 /* CameraPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = A182CEE42B028525000385D2 /* CameraPreview.swift */; }; A182CEE72B02DF10000385D2 /* CameraService+Enums.swift in Sources */ = {isa = PBXBuildFile; fileRef = A182CEE62B02DF10000385D2 /* CameraService+Enums.swift */; }; A1A6A72F297685DF00090309 /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1A6A72E297685DF00090309 /* Utility.swift */; }; A1A80D3D29AE049E00AD756D /* SettingsBundleHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1A80D3C29AE049E00AD756D /* SettingsBundleHelper.swift */; }; @@ -504,7 +503,6 @@ A182CEDE2B025B41000385D2 /* CameraModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraModel.swift; sourceTree = ""; }; A182CEE02B025B53000385D2 /* CameraService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraService.swift; sourceTree = ""; }; A182CEE22B025B6C000385D2 /* CameraInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraInput.swift; sourceTree = ""; }; - A182CEE42B028525000385D2 /* CameraPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPreview.swift; sourceTree = ""; }; A182CEE62B02DF10000385D2 /* CameraService+Enums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraService+Enums.swift"; sourceTree = ""; }; A1A6A72E297685DF00090309 /* Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = ""; }; A1A80D3C29AE049E00AD756D /* SettingsBundleHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsBundleHelper.swift; sourceTree = ""; }; @@ -1147,7 +1145,6 @@ A182CEDE2B025B41000385D2 /* CameraModel.swift */, A182CEE02B025B53000385D2 /* CameraService.swift */, A182CEE22B025B6C000385D2 /* CameraInput.swift */, - A182CEE42B028525000385D2 /* CameraPreview.swift */, A182CEE62B02DF10000385D2 /* CameraService+Enums.swift */, A15891EF2B10BFD000A169C3 /* MetalView.swift */, A15891F12B10BFED00A169C3 /* MetalRenderer.swift */, @@ -1574,7 +1571,6 @@ 6B00F0B01C4684F100971941 /* ThresholdAnalysis.swift in Sources */, 6B2E0FC4205AC5E1008FAA91 /* UIColor+Expanded.m in Sources */, 6B00F0B71C46F23000971941 /* ExperimentExport.swift in Sources */, - A182CEE52B028525000385D2 /* CameraPreview.swift in Sources */, 6B94A6211CA015A4008D9ACF /* GLGraphView.swift in Sources */, A1E03A7F29B61A9000CE93A6 /* UIImageExt.swift in Sources */, 6B612FF6207EB052001DA8D9 /* AnalysisElementHandler.swift in Sources */, diff --git a/phyphox-iOS/phyphox/Camera/CameraModel.swift b/phyphox-iOS/phyphox/Camera/CameraModel.swift index 49dcf2f4..6ca33e0f 100644 --- a/phyphox-iOS/phyphox/Camera/CameraModel.swift +++ b/phyphox-iOS/phyphox/Camera/CameraModel.swift @@ -13,19 +13,24 @@ import MetalKit @available(iOS 14.0, *) final class CameraModel: ObservableObject { private let service = CameraService() + var metalView = CameraMetalView() + var metalRenderer: MetalRenderer var session: AVCaptureSession init() { self.session = service.session + self.metalRenderer = MetalRenderer(parent: metalView, renderer: metalView.metalView) } func configure(){ service.checkForPermisssion() service.configure() - service.setUpMetal() + + metalView.metalView.delegate = metalRenderer + service.metalRender = metalRenderer } func getScale() -> CGFloat { diff --git a/phyphox-iOS/phyphox/Camera/CameraPreview.swift b/phyphox-iOS/phyphox/Camera/CameraPreview.swift deleted file mode 100644 index 028c34c5..00000000 --- a/phyphox-iOS/phyphox/Camera/CameraPreview.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// CameraPreview.swift -// phyphox -// -// Created by Gaurav Tripathee on 13.11.23. -// Copyright © 2023 RWTH Aachen. All rights reserved. -// - -import SwiftUI -import AVFoundation - -@available(iOS 13.0, *) -struct PhyphoxCameraPreview: UIViewRepresentable { - class VideoPreviewView: UIView { - override class var layerClass: AnyClass { - AVCaptureVideoPreviewLayer.self - } - - var videoPreviewLayer: AVCaptureVideoPreviewLayer { - return layer as! AVCaptureVideoPreviewLayer - } - } - - let session: AVCaptureSession - - func makeUIView(context: Context) -> VideoPreviewView { - let view = VideoPreviewView() - view.backgroundColor = .black - view.videoPreviewLayer.cornerRadius = 0 - view.videoPreviewLayer.session = session - view.videoPreviewLayer.connection?.videoOrientation = .portrait - - return view - } - - func updateUIView(_ uiView: VideoPreviewView, context: Context) { - - } -} - - diff --git a/phyphox-iOS/phyphox/Camera/CameraService.swift b/phyphox-iOS/phyphox/Camera/CameraService.swift index 2f6605bf..468888ad 100644 --- a/phyphox-iOS/phyphox/Camera/CameraService.swift +++ b/phyphox-iOS/phyphox/Camera/CameraService.swift @@ -97,21 +97,6 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega } - public func setUpMetal(){ - - metalRender = CameraMetalView().makeCoordinator() - - - //metalView.backgroundColor = UIColor.clear - - //metalRender = MetalRenderer(renderer: metalView) - //metalView.delegate = metalRender - - // metalRender?.drawRectResized(size: metalView.bounds.size) - - - - } private func configureSession(){ diff --git a/phyphox-iOS/phyphox/Camera/CameraView.swift b/phyphox-iOS/phyphox/Camera/CameraView.swift index 2e48e548..324a7214 100644 --- a/phyphox-iOS/phyphox/Camera/CameraView.swift +++ b/phyphox-iOS/phyphox/Camera/CameraView.swift @@ -202,7 +202,7 @@ struct PhyphoxCameraView: View { ZStack{ - CameraMetalView() + model.metalView .foregroundColor(.gray) .onAppear{ model.configure() diff --git a/phyphox-iOS/phyphox/Camera/MetalRenderer.swift b/phyphox-iOS/phyphox/Camera/MetalRenderer.swift index c78fc622..f11d569f 100644 --- a/phyphox-iOS/phyphox/Camera/MetalRenderer.swift +++ b/phyphox-iOS/phyphox/Camera/MetalRenderer.swift @@ -8,15 +8,16 @@ import Foundation import MetalKit +import AVFoundation @available(iOS 13.0, *) -class MetalRenderer: NSObject, MTKViewDelegate { +class MetalRenderer: NSObject, MTKViewDelegate{ var metalDevice: MTLDevice! var metalCommandQueue: MTLCommandQueue! var imagePlaneVertexBuffer: MTLBuffer! - var renderDestination: RenderDestinationProvider + var renderDestination: MTKView // The current viewport size. var viewportSize: CGSize = CGSize() @@ -50,7 +51,7 @@ class MetalRenderer: NSObject, MTKViewDelegate { var arrCVImageBuffer: [CVImageBuffer] - init(parent: CameraMetalView ,renderer: RenderDestinationProvider) { + init(parent: CameraMetalView ,renderer: MTKView) { print("Metal Renderer : init") self.renderDestination = renderer @@ -64,20 +65,18 @@ class MetalRenderer: NSObject, MTKViewDelegate { loadMetal() - } func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { drawRectResized(size: size) - } func draw(in view: MTKView) { - update() - } + + // Schedule a draw to happen at a new size. func drawRectResized(size: CGSize) { viewportSize = size @@ -184,11 +183,13 @@ class MetalRenderer: NSObject, MTKViewDelegate { } updateAppState() + + print("Metal Renderer: updateAppState next") if let renderPassDescriptor = renderDestination.currentRenderPassDescriptor, let currentDrawable = renderDestination.currentDrawable { - + print("Metal Renderer: renderPassDescriptor") if let renderEncoding = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) { - + print("Metal Renderer: renderEncoding") // Set a label to identify this render pass in a captured Metal frame. renderEncoding.label = "DepthGUICameraPreview" @@ -212,6 +213,7 @@ class MetalRenderer: NSObject, MTKViewDelegate { // Schedules the camera image to be rendered on the GPU. func doRenderPass(renderEncoder: MTLRenderCommandEncoder) { + print("Metal Renderer: doRenderPass started") guard let cameraImageY = cameraImageTextureY, let cameraImageCbCr = cameraImageTextureCbCr else { return } @@ -239,6 +241,8 @@ class MetalRenderer: NSObject, MTKViewDelegate { // Draw final quad to display renderEncoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4) renderEncoder.popDebugGroup() + + print("Metal Renderer: doRenderPass ended") } // Updates any app state. @@ -276,6 +280,7 @@ class MetalRenderer: NSObject, MTKViewDelegate { // Creates a Metal texture with the argument pixel format from a CVPixelBuffer at the argument plane index. func createTexture(fromPixelBuffer pixelBuffer: CVImageBuffer, pixelFormat: MTLPixelFormat, planeIndex: Int) -> CVMetalTexture? { + print("Metal Renderer: createTexture started") let width = CVPixelBufferGetWidthOfPlane(pixelBuffer, planeIndex) let height = CVPixelBufferGetHeightOfPlane(pixelBuffer, planeIndex) @@ -286,7 +291,7 @@ class MetalRenderer: NSObject, MTKViewDelegate { if status != kCVReturnSuccess { texture = nil } - + print("Metal Renderer: createTexture ended") return texture } diff --git a/phyphox-iOS/phyphox/Camera/MetalView.swift b/phyphox-iOS/phyphox/Camera/MetalView.swift index c7324e62..e1d08370 100644 --- a/phyphox-iOS/phyphox/Camera/MetalView.swift +++ b/phyphox-iOS/phyphox/Camera/MetalView.swift @@ -13,25 +13,25 @@ import SwiftUI @available(iOS 13.0, *) struct CameraMetalView: UIViewRepresentable { - let metalView: MTKView = MTKView() - - func makeCoordinator() -> Coordinator { - print("MetalView: makeCoordinator") - return Coordinator(renderer: MetalRenderer(parent: self, renderer: metalView)) - func makeUIView(context: UIViewRepresentableContext) -> MTKView { print("MetalView: makeUIView") - metalView.delegate = context.coordinator + //metalView.delegate = context.coordinator metalView.preferredFramesPerSecond = 60 - //metalView.enableSetNeedsDisplay = true + if let metalDevice = MTLCreateSystemDefaultDevice() { + metalView.device = metalDevice + } + + metalView.preferredFramesPerSecond = 60 + //metalView.framebufferOnly = false + metalView.clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0) - metalView.drawableSize = CGSize(width: 100.0, height: 100.0) + metalView.drawableSize = metalView.frame.size return metalView } @@ -40,7 +40,5 @@ struct CameraMetalView: UIViewRepresentable { } - - } diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index f78de305..99cfd388 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 12877 + 12890 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 028bafb68ae5e3ae381fc30f3f3e33daf89b0b08 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Mon, 8 Jan 2024 11:52:48 +0100 Subject: [PATCH 43/81] fix: adjust the drawableSize of MTKView to resolve frame dropping issue --- phyphox-iOS/phyphox/Camera/MetalRenderer.swift | 12 ++---------- phyphox-iOS/phyphox/Camera/MetalView.swift | 3 ++- phyphox-iOS/phyphox/Info.plist | 2 +- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/phyphox-iOS/phyphox/Camera/MetalRenderer.swift b/phyphox-iOS/phyphox/Camera/MetalRenderer.swift index f11d569f..8f4717d8 100644 --- a/phyphox-iOS/phyphox/Camera/MetalRenderer.swift +++ b/phyphox-iOS/phyphox/Camera/MetalRenderer.swift @@ -48,8 +48,6 @@ class MetalRenderer: NSObject, MTKViewDelegate{ var cvImageBuffer : CVImageBuffer? - var arrCVImageBuffer: [CVImageBuffer] - init(parent: CameraMetalView ,renderer: MTKView) { print("Metal Renderer : init") @@ -60,7 +58,6 @@ class MetalRenderer: NSObject, MTKViewDelegate{ self.metalDevice = metalDevice } - arrCVImageBuffer = [CVImageBuffer]() super.init() loadMetal() @@ -90,8 +87,6 @@ class MetalRenderer: NSObject, MTKViewDelegate{ if imageBuffer != nil { print("Metal Renderer: imageBuffer not nil") - arrCVImageBuffer.append(imageBuffer) - print("Metal Renderer: imageBuffer size: ", arrCVImageBuffer.count) self.cvImageBuffer = imageBuffer } @@ -250,13 +245,10 @@ class MetalRenderer: NSObject, MTKViewDelegate{ print("updateAppState before") - print("Metal Renderer: updateAppState imageBuffer size: ", arrCVImageBuffer.count) - // Get the AR session's current frame. guard let currentFrame = self.cvImageBuffer else { return } - // imageBuffer getting nil here print("updateAppState after") // Prepare the current frame's camera image for transfer to the GPU. updateCameraImageTextures(frame: currentFrame) @@ -281,8 +273,8 @@ class MetalRenderer: NSObject, MTKViewDelegate{ // Creates a Metal texture with the argument pixel format from a CVPixelBuffer at the argument plane index. func createTexture(fromPixelBuffer pixelBuffer: CVImageBuffer, pixelFormat: MTLPixelFormat, planeIndex: Int) -> CVMetalTexture? { print("Metal Renderer: createTexture started") - let width = CVPixelBufferGetWidthOfPlane(pixelBuffer, planeIndex) - let height = CVPixelBufferGetHeightOfPlane(pixelBuffer, planeIndex) + let width = CVPixelBufferGetWidthOfPlane(pixelBuffer, planeIndex) // 480 //240 + let height = CVPixelBufferGetHeightOfPlane(pixelBuffer, planeIndex) // 360 //180 var texture: CVMetalTexture? = nil let status = CVMetalTextureCacheCreateTextureFromImage(nil, cameraImageTextureCache, pixelBuffer, nil, pixelFormat, diff --git a/phyphox-iOS/phyphox/Camera/MetalView.swift b/phyphox-iOS/phyphox/Camera/MetalView.swift index e1d08370..bbc35e33 100644 --- a/phyphox-iOS/phyphox/Camera/MetalView.swift +++ b/phyphox-iOS/phyphox/Camera/MetalView.swift @@ -31,7 +31,8 @@ struct CameraMetalView: UIViewRepresentable { metalView.clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0) - metalView.drawableSize = metalView.frame.size + //Fix: As the imagebuffer width * height is 480 and 180, we need to set the drawable size of MTKView same. Else, the drawable size will be 1080 * 1256 (for example) and due to the large render buffer, the frame starts dropping. TODO: Need to test it and remove the hard dependency. + metalView.drawableSize = CGSize(width: 480, height: 180) return metalView } diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 99cfd388..3b434456 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 12890 + 12896 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From a05f4151fa9e0b5905c9d03ccc2e971bc1616297 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Mon, 8 Jan 2024 12:08:24 +0100 Subject: [PATCH 44/81] fix: resolve the video preview orientation --- phyphox-iOS/phyphox/Camera/MetalView.swift | 3 +++ phyphox-iOS/phyphox/Info.plist | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/phyphox-iOS/phyphox/Camera/MetalView.swift b/phyphox-iOS/phyphox/Camera/MetalView.swift index bbc35e33..a6f17a30 100644 --- a/phyphox-iOS/phyphox/Camera/MetalView.swift +++ b/phyphox-iOS/phyphox/Camera/MetalView.swift @@ -34,6 +34,9 @@ struct CameraMetalView: UIViewRepresentable { //Fix: As the imagebuffer width * height is 480 and 180, we need to set the drawable size of MTKView same. Else, the drawable size will be 1080 * 1256 (for example) and due to the large render buffer, the frame starts dropping. TODO: Need to test it and remove the hard dependency. metalView.drawableSize = CGSize(width: 480, height: 180) + //This trasformation is done to rotate the videoPreview in right degree. + metalView.transform = CGAffineTransform(rotationAngle: CGFloat.pi / 2.0) + return metalView } diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 3b434456..df0e1745 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 12896 + 12903 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 95d7884345df7cb1d4162c4f6658567e89cf57b0 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Wed, 10 Jan 2024 07:49:15 +0100 Subject: [PATCH 45/81] fix: stop the session after the view is closed and add the gesture to the camera preview --- phyphox-iOS/phyphox/Camera/CameraModel.swift | 75 ++++++++++++++++++- .../phyphox/Camera/CameraService+Enums.swift | 2 +- .../phyphox/Camera/CameraService.swift | 55 ++++---------- phyphox-iOS/phyphox/Camera/CameraView.swift | 27 ++++++- .../phyphox/Camera/MetalRenderer.swift | 4 + phyphox-iOS/phyphox/Info.plist | 2 +- .../ExperimentPageViewController.swift | 2 + 7 files changed, 123 insertions(+), 44 deletions(-) diff --git a/phyphox-iOS/phyphox/Camera/CameraModel.swift b/phyphox-iOS/phyphox/Camera/CameraModel.swift index 6ca33e0f..41738fc8 100644 --- a/phyphox-iOS/phyphox/Camera/CameraModel.swift +++ b/phyphox-iOS/phyphox/Camera/CameraModel.swift @@ -11,7 +11,18 @@ import AVFoundation import MetalKit @available(iOS 14.0, *) -final class CameraModel: ObservableObject { +final class CameraModel{ + + var x1: Float = 0.4 + var x2: Float = 0.6 + var y1: Float = 0.4 + var y2: Float = 0.6 + + var panningIndexX: Int = 0 + var panningIndexY: Int = 0 + + var modelGesture: GestureState = .none + private let service = CameraService() var metalView = CameraMetalView() @@ -33,6 +44,14 @@ final class CameraModel: ObservableObject { service.metalRender = metalRenderer } + func initModel(model: CameraModel){ + service.initializeModel(model: model) + } + + func endSession(){ + service.session.stopRunning() + } + func getScale() -> CGFloat { service.zoomScale } @@ -53,6 +72,7 @@ final class CameraModel: ObservableObject { return service.image! } + func autoExposure() {} func shutterSpeed() {} @@ -67,4 +87,57 @@ final class CameraModel: ObservableObject { func whiteBalance() {} + func pannned (locationY: CGFloat, locationX: CGFloat , state: GestureState) { + let pr = CGPoint(x: locationX / metalView.metalView.frame.width, y: locationY / metalView.metalView.frame.height) + let ps = pr.applying(metalRenderer.displayToCameraTransform) + let x = Float(ps.x) + let y = Float(ps.y) + + if state == .begin { + let d11 = (x - x1)*(x - x1) + (y - y1)*(y - y1) + let d12 = (x - x1)*(x - x1) + (y - y2)*(y - y2) + let d21 = (x - x2)*(x - x2) + (y - y1)*(y - y1) + let d22 = (x - x2)*(x - x2) + (y - y2)*(y - y2) + let dist:Float = 0.01 + if d11 < dist && d11 < d12 && d11 < d21 && d11 < d22 { + panningIndexX = 1 + panningIndexY = 1 + } else if d12 < dist && d12 < d21 && d12 < d22 { + panningIndexX = 1 + panningIndexY = 2 + } else if d21 < dist && d21 < d22 { + panningIndexX = 2 + panningIndexY = 1 + } else if d22 < dist { + panningIndexX = 2 + panningIndexY = 2 + } else { + panningIndexX = 0 + panningIndexY = 0 + } + } else if state == .end { + if panningIndexX == 1 { + x1 = x + } else if panningIndexX == 2 { + x2 = x + } + if panningIndexY == 1 { + y1 = y + } else if panningIndexY == 2 { + y2 = y + } + } else { + + } + } + // 136 166 + // d11 = 136 - 0.4 * 136 - 0.4 + 166 - 0.4 * 166 - 0.4 + + enum GestureState { + case begin + case end + case none + } + } + diff --git a/phyphox-iOS/phyphox/Camera/CameraService+Enums.swift b/phyphox-iOS/phyphox/Camera/CameraService+Enums.swift index cbb3915d..02faca3f 100644 --- a/phyphox-iOS/phyphox/Camera/CameraService+Enums.swift +++ b/phyphox-iOS/phyphox/Camera/CameraService+Enums.swift @@ -8,7 +8,7 @@ import Foundation -@available(iOS 13.0, *) +@available(iOS 14.0, *) extension CameraService{ enum SessionSetupResult { diff --git a/phyphox-iOS/phyphox/Camera/CameraService.swift b/phyphox-iOS/phyphox/Camera/CameraService.swift index 468888ad..5bcd09df 100644 --- a/phyphox-iOS/phyphox/Camera/CameraService.swift +++ b/phyphox-iOS/phyphox/Camera/CameraService.swift @@ -12,7 +12,7 @@ import AVFoundation import MetalKit import CoreMedia -@available(iOS 13.0, *) +@available(iOS 14.0, *) public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate { public let session = AVCaptureSession() @@ -25,8 +25,6 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega var setupResult: SessionSetupResult = .success - weak var delegate: CameraCaptureHelperDelegate? - public var alertError: AlertError = AlertError() var isSessionRunning = false @@ -49,6 +47,8 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega var defaultVideoDevice: AVCaptureDevice? + var cameraModel: CameraModel? + @Published var zoomScale: CGFloat = 1.0 // MARK: Device Configuration Properties @@ -97,7 +97,10 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega } - + @available(iOS 14.0, *) + func initializeModel(model: CameraModel){ + cameraModel = model + } private func configureSession(){ @@ -108,8 +111,6 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega session.beginConfiguration() session.sessionPreset = .medium - - do { if let backCameraDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) { @@ -322,45 +323,19 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega return } - self.metalRender?.updateFrame(imageBuffer: imageBuffer, selectionState: MetalRenderer.SelectionStruct(x1: 0.4, x2: 0.6, y1: 0.4, y2: 0.6, editable: true)) + print("captureOutput imagebuffer x1: ", cameraModel?.x1) + print("captureOutput imagebuffer x2: ", cameraModel?.x2) + print("captureOutput imagebuffer y1: ", cameraModel?.y1) + print("captureOutput imagebuffer y2: ", cameraModel?.y2) - let isPlanar = CVPixelBufferIsPlanar(imageBuffer) - let width = isPlanar ? CVPixelBufferGetWidthOfPlane(imageBuffer, 0) : CVPixelBufferGetWidth(imageBuffer) - print("imagebuffer width: ", width) - let height = isPlanar ? CVPixelBufferGetHeightOfPlane(imageBuffer, 1) : CVPixelBufferGetHeight(imageBuffer) - print("imagebuffer height: ", height) - } + self.metalRender?.updateFrame(imageBuffer: imageBuffer, selectionState: MetalRenderer.SelectionStruct( + x1: cameraModel?.x1 ?? 0, x2: cameraModel?.x2 ?? 0, y1: cameraModel?.y1 ?? 0, y2: cameraModel?.y2 ?? 0, editable: true)) - private let context = CIContext() - - // MARK: Sample buffer to UIImage conversion - private func imageFromSampleBuffer(sampleBuffer: CMSampleBuffer) -> UIImage? { - guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return nil } - let ciImage = CIImage(cvPixelBuffer: imageBuffer) - - //context.render(ciImage, to: imageBuffer) - - //print("CameraSerivce: after Render: imagebuffer: ", imageBuffer. ) - self.metalRender?.updateFrame(imageBuffer: imageBuffer, selectionState: MetalRenderer.SelectionStruct(x1: 0.4, x2: 0.6, y1: 0.4, y2: 0.6, editable: true)) - guard let cgImage = context.createCGImage(ciImage, from: ciImage.extent) else { return nil } - //print("imageBuffer: bytes per row: ",imageBuffer) - return UIImage(cgImage: cgImage) - } - - - func convert(cmage:CIImage) -> UIImage { - let context:CIContext = CIContext.init(options: nil) - let cgImage:CGImage = context.createCGImage(cmage, from: cmage.extent)! - let image:UIImage = UIImage.init(cgImage: cgImage) - return image } + + } -protocol CameraCaptureHelperDelegate: class -{ - @available(iOS 13.0, *) - func newCameraImage(_ cameraCaptureHelper: CameraService, image: CIImage) -} diff --git a/phyphox-iOS/phyphox/Camera/CameraView.swift b/phyphox-iOS/phyphox/Camera/CameraView.swift index 324a7214..382649dc 100644 --- a/phyphox-iOS/phyphox/Camera/CameraView.swift +++ b/phyphox-iOS/phyphox/Camera/CameraView.swift @@ -14,7 +14,7 @@ import Combine @available(iOS 14.0, *) struct PhyphoxCameraView: View { - @StateObject var model = CameraModel() + var model = CameraModel() @State private var isMinimized = true @@ -174,13 +174,33 @@ struct PhyphoxCameraView: View { }) } + @State private var speed = 50.0 @State private var isEditing = false @State private var isZooming = false + @State private var viewState = CGPoint.zero + + @Environment(\.scenePhase) var scenePhase + var body: some View { + + let dragGesture = DragGesture() + .onChanged{ value in + viewState = value.location + model.pannned(locationY: viewState.y, locationX: viewState.x , state: CameraModel.GestureState.begin) + print("dragGesture: onChanged", value) + + } + .onEnded{ value in + model.pannned(locationY: viewState.y, locationX: viewState.x , state: CameraModel.GestureState.end) + self.viewState = .zero + print("dragGesture: onEnded") + } + + GeometryReader { reader in ZStack { //UIColor(named: "")?.edgesIgnoringSafeArea(.all) @@ -206,7 +226,10 @@ struct PhyphoxCameraView: View { .foregroundColor(.gray) .onAppear{ model.configure() + model.initModel(model: model) } + + .gesture(dragGesture) .foregroundColor(/*@START_MENU_TOKEN@*/.blue/*@END_MENU_TOKEN@*/) .frame(width: !isMinimized ? reader.size.width / 2 : reader.size.width , height: !isMinimized ? reader.size.height / 3 : reader.size.height / 1.5, @@ -327,6 +350,8 @@ struct PhyphoxCameraView: View { } + }.onDisappear{ + model.endSession() } }.background(Color.black) } diff --git a/phyphox-iOS/phyphox/Camera/MetalRenderer.swift b/phyphox-iOS/phyphox/Camera/MetalRenderer.swift index 8f4717d8..6b7ff899 100644 --- a/phyphox-iOS/phyphox/Camera/MetalRenderer.swift +++ b/phyphox-iOS/phyphox/Camera/MetalRenderer.swift @@ -84,6 +84,10 @@ class MetalRenderer: NSObject, MTKViewDelegate{ func updateFrame(imageBuffer: CVImageBuffer!, selectionState: SelectionStruct) { print("Metal Renderer: updateFrame") + print("Metal Renderer: updateFrame selectionState: x1 ", selectionState.x1) + print("Metal Renderer: updateFrame selectionState: x2", selectionState.x2) + print("Metal Renderer: updateFrame selectionState: y1 ", selectionState.y1) + print("Metal Renderer: updateFrame selectionState: y2 ", selectionState.y2) if imageBuffer != nil { print("Metal Renderer: imageBuffer not nil") diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index df0e1745..14a76fb6 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 12903 + 12924 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift index d1e91f36..339a5312 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift @@ -512,6 +512,8 @@ final class ExperimentPageViewController: UIViewController, UIPageViewController } } + + } } if let networkConnection = experiment.networkConnections.first { From f895a979e80958605d259b1f5727ee9299fdc022 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Wed, 10 Jan 2024 14:12:02 +0100 Subject: [PATCH 46/81] fix: make the drag gesture work on camera preview and resolve the frame transformation --- phyphox-iOS/phyphox/Camera/CameraModel.swift | 35 ++++++++++++++----- .../phyphox/Camera/MetalRenderer.swift | 11 +++--- phyphox-iOS/phyphox/Camera/MetalView.swift | 5 +-- phyphox-iOS/phyphox/Info.plist | 2 +- 4 files changed, 36 insertions(+), 17 deletions(-) diff --git a/phyphox-iOS/phyphox/Camera/CameraModel.swift b/phyphox-iOS/phyphox/Camera/CameraModel.swift index 41738fc8..20f6dd63 100644 --- a/phyphox-iOS/phyphox/Camera/CameraModel.swift +++ b/phyphox-iOS/phyphox/Camera/CameraModel.swift @@ -98,23 +98,33 @@ final class CameraModel{ let d12 = (x - x1)*(x - x1) + (y - y2)*(y - y2) let d21 = (x - x2)*(x - x2) + (y - y1)*(y - y1) let d22 = (x - x2)*(x - x2) + (y - y2)*(y - y2) - let dist:Float = 0.01 - if d11 < dist && d11 < d12 && d11 < d21 && d11 < d22 { + + let dist:Float = 0.1 // it was 0.01 for depth, after removing it from if else, it worked. Need to come again for this + if d11 < d12 && d11 < d21 && d11 < d22 { panningIndexX = 1 panningIndexY = 1 - } else if d12 < dist && d12 < d21 && d12 < d22 { + } else if d12 < d21 && d12 < d22 { panningIndexX = 1 panningIndexY = 2 - } else if d21 < dist && d21 < d22 { + } else if d21 < d22 { panningIndexX = 2 panningIndexY = 1 - } else if d22 < dist { + } else { panningIndexX = 2 panningIndexY = 2 - } else { - panningIndexX = 0 - panningIndexY = 0 } + + if panningIndexX == 1 { + x1 = x + } else if panningIndexX == 2 { + x2 = x + } + if panningIndexY == 1 { + y1 = y + } else if panningIndexY == 2 { + y2 = y + } + } else if state == .end { if panningIndexX == 1 { x1 = x @@ -126,6 +136,15 @@ final class CameraModel{ } else if panningIndexY == 2 { y2 = y } + + print("pannned x1 ", x1) + print("pannned x2 ", x2) + print("pannned y1 ", y1) + print("pannned y2 ", y2) + + print("pannned x. ", x) + print("pannned y. ", y) + } else { } diff --git a/phyphox-iOS/phyphox/Camera/MetalRenderer.swift b/phyphox-iOS/phyphox/Camera/MetalRenderer.swift index 6b7ff899..8cb15873 100644 --- a/phyphox-iOS/phyphox/Camera/MetalRenderer.swift +++ b/phyphox-iOS/phyphox/Camera/MetalRenderer.swift @@ -294,11 +294,14 @@ class MetalRenderer: NSObject, MTKViewDelegate{ // Sets up vertex data (source and destination rectangles) rendering. func updateImagePlane(frame: CVImageBuffer) { // Update the texture coordinates of the image plane to aspect fill the viewport. - let orientation = UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.windowScene?.interfaceOrientation ?? .portrait - let cgImageOrientation = CGImagePropertyOrientation(interfaceOrientation: orientation) + //let orientation = UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.windowScene?.interfaceOrientation ?? .portrait + //let cgImageOrientation = CGImagePropertyOrientation(interfaceOrientation: orientation) // Convert the image buffer to a CIImage - let ciImage = CIImage(cvPixelBuffer: frame) - displayToCameraTransform = ciImage.orientationTransform(for: cgImageOrientation).inverted() + //let ciImage = CIImage(cvPixelBuffer: frame) + //displayToCameraTransform = ciImage.orientationTransform(for: cgImageOrientation).inverted() + + // Just a work-around + displayToCameraTransform = CGAffineTransform(a: 0.0, b: -1.0, c: 1.0, d: 0.0, tx: 0.0, ty: 1.0) let vertexData = imagePlaneVertexBuffer.contents().assumingMemoryBound(to: Float.self) for index in 0...3 { let textureCoordIndex = 4 * index + 2 diff --git a/phyphox-iOS/phyphox/Camera/MetalView.swift b/phyphox-iOS/phyphox/Camera/MetalView.swift index a6f17a30..60f516b7 100644 --- a/phyphox-iOS/phyphox/Camera/MetalView.swift +++ b/phyphox-iOS/phyphox/Camera/MetalView.swift @@ -33,10 +33,7 @@ struct CameraMetalView: UIViewRepresentable { //Fix: As the imagebuffer width * height is 480 and 180, we need to set the drawable size of MTKView same. Else, the drawable size will be 1080 * 1256 (for example) and due to the large render buffer, the frame starts dropping. TODO: Need to test it and remove the hard dependency. metalView.drawableSize = CGSize(width: 480, height: 180) - - //This trasformation is done to rotate the videoPreview in right degree. - metalView.transform = CGAffineTransform(rotationAngle: CGFloat.pi / 2.0) - + return metalView } diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 14a76fb6..b2d6febd 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 12924 + 12971 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 59482dc28238f6e69a56942a7e8b957ebd3b8c34 Mon Sep 17 00:00:00 2001 From: Ananda Dasgupta Date: Mon, 4 Dec 2023 16:46:44 +0000 Subject: [PATCH 47/81] Translated using Weblate (Bengali) Currently translated at 18.4% (63 of 342 strings) Translation: phyphox/iOS Translate-URL: https://translate.phyphox.org/projects/phyphox/ios/bn/ --- phyphox-iOS/phyphox/bn.lproj/Localizable.strings | 1 - 1 file changed, 1 deletion(-) diff --git a/phyphox-iOS/phyphox/bn.lproj/Localizable.strings b/phyphox-iOS/phyphox/bn.lproj/Localizable.strings index 9c1858c6..db15a16b 100644 --- a/phyphox-iOS/phyphox/bn.lproj/Localizable.strings +++ b/phyphox-iOS/phyphox/bn.lproj/Localizable.strings @@ -19,7 +19,6 @@ "experimentsPhyphoxOrgURL" = "http://phyphox.org/experiments"; "faqPhyphoxOrg" = "প্রায়শই জিজ্ঞাসা করা প্রশ্ন"; "faqPhyphoxOrgURL" = "http://phyphox.org/faq"; -"remotePhyphoxOrg" = "রিমোট কন্ট্রোল সহায়তা"; "remotePhyphoxOrg" = "রিমোট কন্ট্রোল সংক্রান্ত সহায়তা"; "remotePhyphoxOrgURL" = "http://phyphox.org/remote-control"; "deviceInfo" = "ডিভাইসের বিবরণ"; From 06c54a7a9bba4272039a64a8260658d8fcd07071 Mon Sep 17 00:00:00 2001 From: Bidyut Ghosh Date: Tue, 5 Dec 2023 15:08:57 +0000 Subject: [PATCH 48/81] Translated using Weblate (Bengali) Currently translated at 21.6% (74 of 342 strings) Translation: phyphox/iOS Translate-URL: https://translate.phyphox.org/projects/phyphox/ios/bn/ --- phyphox-iOS/phyphox/bn.lproj/Localizable.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phyphox-iOS/phyphox/bn.lproj/Localizable.strings b/phyphox-iOS/phyphox/bn.lproj/Localizable.strings index db15a16b..6958bb6f 100644 --- a/phyphox-iOS/phyphox/bn.lproj/Localizable.strings +++ b/phyphox-iOS/phyphox/bn.lproj/Localizable.strings @@ -19,7 +19,7 @@ "experimentsPhyphoxOrgURL" = "http://phyphox.org/experiments"; "faqPhyphoxOrg" = "প্রায়শই জিজ্ঞাসা করা প্রশ্ন"; "faqPhyphoxOrgURL" = "http://phyphox.org/faq"; -"remotePhyphoxOrg" = "রিমোট কন্ট্রোল সংক্রান্ত সহায়তা"; +"remotePhyphoxOrg" = "রিমোট কন্ট্রোল সহায়তা"; "remotePhyphoxOrgURL" = "http://phyphox.org/remote-control"; "deviceInfo" = "ডিভাইসের বিবরণ"; "copyToClipboard" = "ক্লিপবোর্ডে কপি করুন"; From a2a891d2e4a2e317d1577abaefb8f5d1db6da43b Mon Sep 17 00:00:00 2001 From: Gaurav Tripathee Date: Wed, 6 Sep 2023 09:46:00 +0200 Subject: [PATCH 49/81] Show battery info when connected with the bluetooth devices (#12) * feat: show connected device information while experiment loaded from the bluetooth device * fix showing battery level eeventhougth there is no battery service * refactor: change the name dependecy in bluetooth connected device * ui: fix the graph border color when experiment opened from connected bluetooth device * refactor: remove extra if condition in updating connected bluetooth device --- phyphox-iOS/phyphox.xcodeproj/project.pbxproj | 7 ------- 1 file changed, 7 deletions(-) diff --git a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj index 10615097..ba254aea 100644 --- a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj +++ b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj @@ -1258,14 +1258,7 @@ C0BD4DF527079C600020A5AF /* JGProgressHUD */, C0F430C528E5A2DB00BC98DE /* BEMCheckBox */, 867E2BD72AC464D000ABB472 /* CocoaMQTT */, -<<<<<<< HEAD -<<<<<<< HEAD 860E13F22B514703008FAFC6 /* GCDWebServer */, -======= ->>>>>>> 26e36c4 (Replace our CocoaMQTT fork with upstream version to fix incompatibility with iOS 16) -======= - 860E13F22B514703008FAFC6 /* GCDWebServer */, ->>>>>>> 70f0b50 (Switch repository for Swift-packaged version of GCDWebServer) ); productName = phyphox; productReference = 6B7BF77A1C130425007408FB /* phyphox.app */; From 00bc338a59ca94adb4d9a5eb5b19c109b5be583c Mon Sep 17 00:00:00 2001 From: Sebastian Staacks Date: Wed, 27 Sep 2023 15:35:26 +0200 Subject: [PATCH 50/81] Replace our CocoaMQTT fork with upstream version to fix incompatibility with iOS 16 --- phyphox-iOS/phyphox.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj index ba254aea..5328fecb 100644 --- a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj +++ b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj @@ -1366,7 +1366,7 @@ C0F430C428E5A2DB00BC98DE /* XCRemoteSwiftPackageReference "BEMCheckBox" */, 867E2BD62AC464D000ABB472 /* XCRemoteSwiftPackageReference "CocoaMQTT" */, 860E13F12B514703008FAFC6 /* XCRemoteSwiftPackageReference "GCDWebServer" */, - + ); productRefGroup = 6B7BF77B1C130425007408FB /* Products */; projectDirPath = ""; projectReferences = ( From 9a6acf972b9da869dd5e81f669cadb2579272c87 Mon Sep 17 00:00:00 2001 From: Sebastian Staacks Date: Fri, 5 Jan 2024 12:17:54 +0100 Subject: [PATCH 51/81] Increase minimum iOS version to 12 (XCode requirement) and Adjust calculation of colors for bright mode to avoid unreadable colors if luminance differs massively from lightness. (i.e. fully saturated yellow was not adjusted) --- phyphox-experiments | 2 +- phyphox-iOS/phyphox/Info.plist | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/phyphox-experiments b/phyphox-experiments index 4e647196..641fbeee 160000 --- a/phyphox-experiments +++ b/phyphox-experiments @@ -1 +1 @@ -Subproject commit 4e647196f66b29ed4c853df402dabbf9b6bee158 +Subproject commit 641fbeeef6548e90ddd23eccaa496e85ca1e1a30 diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index b2d6febd..7e619275 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 12971 + 11119 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 393c5d3cf51724216f8f30f1960a68f3eb182c0d Mon Sep 17 00:00:00 2001 From: Sebastian Staacks Date: Fri, 5 Jan 2024 12:52:19 +0100 Subject: [PATCH 52/81] Simplify three-dot menu color on main screen to simple black/white and follow bright mode setting. --- phyphox-iOS/phyphox/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 7e619275..dca3ac18 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11119 + 11120 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 8defd30a72b582ae1899936028b8c3c496261e1c Mon Sep 17 00:00:00 2001 From: Sebastian Staacks Date: Wed, 10 Jan 2024 17:17:29 +0100 Subject: [PATCH 53/81] Fix crash on renaming saved state. --- phyphox-iOS/phyphox/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index dca3ac18..8773c4f2 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11120 + 11128 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 02f4e0bb8613a5f984a8e3a30efaeee0469ff1f6 Mon Sep 17 00:00:00 2001 From: Sebastian Staacks Date: Fri, 12 Jan 2024 15:42:22 +0100 Subject: [PATCH 54/81] Switch repository for Swift-packaged version of GCDWebServer --- phyphox-experiments | 2 +- phyphox-iOS/phyphox/Info.plist | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/phyphox-experiments b/phyphox-experiments index 641fbeee..4e647196 160000 --- a/phyphox-experiments +++ b/phyphox-experiments @@ -1 +1 @@ -Subproject commit 641fbeeef6548e90ddd23eccaa496e85ca1e1a30 +Subproject commit 4e647196f66b29ed4c853df402dabbf9b6bee158 diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 8773c4f2..ea0eb315 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11128 + 11129 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From c5c529a59e45980686bf2bff21b7fb71b492243c Mon Sep 17 00:00:00 2001 From: GTripathee Date: Wed, 17 Jan 2024 12:12:14 +0100 Subject: [PATCH 55/81] fix: clear the connected devices list when bluetooth disconnected --- phyphox-iOS/phyphox/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index ea0eb315..828b35f8 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11129 + 11131 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 00623b58eb621f4088b2257427b875d99c917f06 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Wed, 17 Jan 2024 15:57:31 +0100 Subject: [PATCH 56/81] fix: refresh the theme in experiment view as per the system appearance --- phyphox-iOS/phyphox/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 828b35f8..1a74735d 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11131 + 11132 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 861d072f36bf5def4a0357d2957eb9869d7aaaf7 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Wed, 17 Jan 2024 19:22:53 +0100 Subject: [PATCH 57/81] fix: rebase to development --- phyphox-iOS/phyphox/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 1a74735d..0ef6b5af 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11132 + 11133 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 306b9e0f002f2df723c58dff415c64272b990cf5 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Thu, 7 Sep 2023 10:26:01 +0200 Subject: [PATCH 58/81] ix: change the minimum deployment from 8 to 11 to fix the build error --- phyphox-iOS/phyphox/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 0ef6b5af..d6be274b 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11133 + 11284 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 4707245f96cc5aaf82591953e15cd263efb18b06 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Mon, 11 Sep 2023 16:56:47 +0200 Subject: [PATCH 59/81] it status --- phyphox-iOS/phyphox/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index d6be274b..70e00407 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11284 + 11316 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From e32d68c63e80c6ee82be4170aa5a6ad28e18a1a8 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Tue, 2 Jan 2024 09:45:40 +0100 Subject: [PATCH 60/81] feature: setup and show camera (preview not displaying) --- .../phyphox/Camera/CameraPreview.swift | 41 +++++++++++++++++++ phyphox-iOS/phyphox/Info.plist | 2 +- 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 phyphox-iOS/phyphox/Camera/CameraPreview.swift diff --git a/phyphox-iOS/phyphox/Camera/CameraPreview.swift b/phyphox-iOS/phyphox/Camera/CameraPreview.swift new file mode 100644 index 00000000..028c34c5 --- /dev/null +++ b/phyphox-iOS/phyphox/Camera/CameraPreview.swift @@ -0,0 +1,41 @@ +// +// CameraPreview.swift +// phyphox +// +// Created by Gaurav Tripathee on 13.11.23. +// Copyright © 2023 RWTH Aachen. All rights reserved. +// + +import SwiftUI +import AVFoundation + +@available(iOS 13.0, *) +struct PhyphoxCameraPreview: UIViewRepresentable { + class VideoPreviewView: UIView { + override class var layerClass: AnyClass { + AVCaptureVideoPreviewLayer.self + } + + var videoPreviewLayer: AVCaptureVideoPreviewLayer { + return layer as! AVCaptureVideoPreviewLayer + } + } + + let session: AVCaptureSession + + func makeUIView(context: Context) -> VideoPreviewView { + let view = VideoPreviewView() + view.backgroundColor = .black + view.videoPreviewLayer.cornerRadius = 0 + view.videoPreviewLayer.session = session + view.videoPreviewLayer.connection?.videoOrientation = .portrait + + return view + } + + func updateUIView(_ uiView: VideoPreviewView, context: Context) { + + } +} + + diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 70e00407..f8d673db 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 11316 + 12875 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 4b50c34b5d13d065bb95a66cc45587807db10072 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Tue, 2 Jan 2024 12:47:26 +0100 Subject: [PATCH 61/81] ix: add project file --- phyphox-iOS/phyphox/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index f8d673db..f78de305 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 12875 + 12877 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 1f6c878a4592c8ba5869af3642dda8fecf6c16f8 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Wed, 3 Jan 2024 13:49:51 +0100 Subject: [PATCH 62/81] fix: remove multiple instance creation of metalRenderer --- .../phyphox/Camera/CameraPreview.swift | 41 ------------------- phyphox-iOS/phyphox/Info.plist | 2 +- 2 files changed, 1 insertion(+), 42 deletions(-) delete mode 100644 phyphox-iOS/phyphox/Camera/CameraPreview.swift diff --git a/phyphox-iOS/phyphox/Camera/CameraPreview.swift b/phyphox-iOS/phyphox/Camera/CameraPreview.swift deleted file mode 100644 index 028c34c5..00000000 --- a/phyphox-iOS/phyphox/Camera/CameraPreview.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// CameraPreview.swift -// phyphox -// -// Created by Gaurav Tripathee on 13.11.23. -// Copyright © 2023 RWTH Aachen. All rights reserved. -// - -import SwiftUI -import AVFoundation - -@available(iOS 13.0, *) -struct PhyphoxCameraPreview: UIViewRepresentable { - class VideoPreviewView: UIView { - override class var layerClass: AnyClass { - AVCaptureVideoPreviewLayer.self - } - - var videoPreviewLayer: AVCaptureVideoPreviewLayer { - return layer as! AVCaptureVideoPreviewLayer - } - } - - let session: AVCaptureSession - - func makeUIView(context: Context) -> VideoPreviewView { - let view = VideoPreviewView() - view.backgroundColor = .black - view.videoPreviewLayer.cornerRadius = 0 - view.videoPreviewLayer.session = session - view.videoPreviewLayer.connection?.videoOrientation = .portrait - - return view - } - - func updateUIView(_ uiView: VideoPreviewView, context: Context) { - - } -} - - diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index f78de305..99cfd388 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 12877 + 12890 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 1bfdd16152a18d9784160cec683f652715c7fce7 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Mon, 8 Jan 2024 11:52:48 +0100 Subject: [PATCH 63/81] fix: adjust the drawableSize of MTKView to resolve frame dropping issue --- phyphox-iOS/phyphox/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 99cfd388..3b434456 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 12890 + 12896 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From fd5a46dac613d159d9eb30237065f19aca89ae1e Mon Sep 17 00:00:00 2001 From: GTripathee Date: Mon, 8 Jan 2024 12:08:24 +0100 Subject: [PATCH 64/81] ix: resolve the video preview orientation --- phyphox-iOS/phyphox/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 3b434456..df0e1745 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 12896 + 12903 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 5a3ec142ed3e8fd8d87e21476581e1c75a91f67d Mon Sep 17 00:00:00 2001 From: GTripathee Date: Wed, 10 Jan 2024 07:49:15 +0100 Subject: [PATCH 65/81] ix: stop the session after the view is closed and add the gesture to the camera preview --- phyphox-iOS/phyphox/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index df0e1745..14a76fb6 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 12903 + 12924 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 835bcb6bcc8f2e1ace57df9aaafc5ea0ba453035 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Wed, 10 Jan 2024 14:12:02 +0100 Subject: [PATCH 66/81] fix: make the drag gesture work on camera preview and resolve the frame transformation --- phyphox-iOS/phyphox/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 14a76fb6..b2d6febd 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 12924 + 12971 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 1e394d61d8e5ff57b15cea995e15e0f8a4b8d78e Mon Sep 17 00:00:00 2001 From: GTripathee Date: Mon, 5 Feb 2024 16:10:06 +0100 Subject: [PATCH 67/81] feat: show exposure values and able to change it manually --- phyphox-iOS/phyphox/AppDelegate.swift | 1 + phyphox-iOS/phyphox/Camera/CameraModel.swift | 211 +++++++-- .../phyphox/Camera/CameraService.swift | 282 +++++++++++- phyphox-iOS/phyphox/Camera/CameraView.swift | 434 ++++++++++-------- .../phyphox/Camera/MetalRenderer.swift | 35 +- phyphox-iOS/phyphox/Info.plist | 2 +- 6 files changed, 701 insertions(+), 264 deletions(-) diff --git a/phyphox-iOS/phyphox/AppDelegate.swift b/phyphox-iOS/phyphox/AppDelegate.swift index dd382bcd..3d31cc49 100644 --- a/phyphox-iOS/phyphox/AppDelegate.swift +++ b/phyphox-iOS/phyphox/AppDelegate.swift @@ -13,6 +13,7 @@ let ResignActiveNotification = "ResignActiveNotification" let DidBecomeActiveNotification = "DidBecomeActiveNotification" @UIApplicationMain + final class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? diff --git a/phyphox-iOS/phyphox/Camera/CameraModel.swift b/phyphox-iOS/phyphox/Camera/CameraModel.swift index 20f6dd63..7c57c6ec 100644 --- a/phyphox-iOS/phyphox/Camera/CameraModel.swift +++ b/phyphox-iOS/phyphox/Camera/CameraModel.swift @@ -10,8 +10,132 @@ import Foundation import AVFoundation import MetalKit + @available(iOS 14.0, *) -final class CameraModel{ +class CameraSettingsModel: ObservableObject { + + var cameraSettingLevel: CameraSettingLevel = .ADVANCE + + var cameraSettingMode: CameraSettingMode = .NONE + + @Published var maxOpticalZoom: Int = 1 + var ultraWideCamera: Bool = false + @Published var minZoom: Int = 1 + @Published var maxZoom: Int = 1 + var currentZoom: Int = 1 + + var minShutterSpeed: Double = 1.0 // min is more less + var maxShutterSpeed: Double = 1.0 // max is near or less more than 1.0 + @Published var currentShutterSpeed: CMTime? + + var minIso: Float = 30.0 + var maxIso: Float = 100.0 + @Published var currentIso: Float = 30.0 + + var apertureValue: Float = 1.0 + @Published var currentApertureValue: Float = 1.0 + + var minExposureValue: Float = 0.0 + var maxExposureValue: Float = 1.0 + @Published var currentExposureValue: Float = 0.0 + + var autoAxposureEnable: Bool = true + + var minwhiteBalance: Float = 1.0 + var maxWhiteBalance: Float = 1.0 + @Published var currentWhiteBalance: Float = 1.0 + + private var zoomScale: Float = 1.0 + + var service: CameraService? + + @available(iOS 14.0, *) + init(service: CameraService){ + self.service = service + } + + init(){} + + + func getScale() -> CGFloat { + service?.zoomScale ?? 1.0 + + } + + func setScale(scale: CGFloat) { + // service.zoomScale = scale + service?.updateZoom(scale: scale) + } + + func setZoomScale(scale: Float) { + zoomScale = scale + } + + func getZoomScale() -> Float { + return zoomScale + } + + func switchCamera(){ + service?.changeCamera() + } + + func getLisOfCameraSettingsValue(cameraSettingMode: CameraSettingMode) -> [Int] { + service?.getSelectableValuesForCameraSettings(cameraSettingMode: cameraSettingMode) ?? [] + } + + + func autoExposure(auto: Bool) { + service?.setExposureTo(auto: auto) + } + + func shutterSpeed() {} + + func aperture() {} + + func iso(value: Float) { + service?.changeISO(value) + } + + func exposure(value: Double) { + //service?.changeExpoDuration() + service?.changeExposureDuration(value) + } + + func zoom() {} + + func whiteBalance() {} + + var defaultCameraSetting: AVCaptureDevice? { + + // Find the built-in Dual Camera, if it exists. + if let device = AVCaptureDevice.default(.builtInTripleCamera, + for: .video, + position: .back) { + return device + } + + // Find the built-in Dual Wide Camera, if it exists. (consist of wide and ultra wide camera) + if let device = AVCaptureDevice.default(.builtInDualWideCamera, + for: .video, + position: .back) { + return device + } + + // Find the built-in Wide-Angle Camera, if it exists. + if let device = AVCaptureDevice.default(.builtInWideAngleCamera, + for: .video, + position: .back) { + return device + } + + return nil + } + +} + + +@available(iOS 14.0, *) +final class CameraModel: ObservableObject{ var x1: Float = 0.4 var x2: Float = 0.6 @@ -25,14 +149,44 @@ final class CameraModel{ private let service = CameraService() var metalView = CameraMetalView() + var cameraSettingsModel : CameraSettingsModel var metalRenderer: MetalRenderer var session: AVCaptureSession + /// The app's default camera. + var defaultCamera: AVCaptureDevice? { + + // Find the built-in Dual Camera, if it exists. + if let device = AVCaptureDevice.default(.builtInTripleCamera, + for: .video, + position: .back) { + return device + } + + // Find the built-in Dual Wide Camera, if it exists. (consist of wide and ultra wide camera) + if let device = AVCaptureDevice.default(.builtInDualWideCamera, + for: .video, + position: .back) { + return device + } + + // Find the built-in Wide-Angle Camera, if it exists. + if let device = AVCaptureDevice.default(.builtInWideAngleCamera, + for: .video, + position: .back) { + return device + } + + return nil + } + init() { self.session = service.session self.metalRenderer = MetalRenderer(parent: metalView, renderer: metalView.metalView) + + cameraSettingsModel = CameraSettingsModel(service: service) } @@ -51,18 +205,7 @@ final class CameraModel{ func endSession(){ service.session.stopRunning() } - - func getScale() -> CGFloat { - service.zoomScale - } - - func setScale(scale: CGFloat) { - service.zoomScale = scale - } - - func switchCamera(){ - service.changeCamera() - } + func getMetalView() -> MTKView { return service.metalView @@ -73,20 +216,6 @@ final class CameraModel{ } - func autoExposure() {} - - func shutterSpeed() {} - - func aperture() {} - - func iso() {} - - func exposure() {} - - func zoom() {} - - func whiteBalance() {} - func pannned (locationY: CGFloat, locationX: CGFloat , state: GestureState) { let pr = CGPoint(x: locationX / metalView.metalView.frame.width, y: locationY / metalView.metalView.frame.height) let ps = pr.applying(metalRenderer.displayToCameraTransform) @@ -136,14 +265,6 @@ final class CameraModel{ } else if panningIndexY == 2 { y2 = y } - - print("pannned x1 ", x1) - print("pannned x2 ", x2) - print("pannned y1 ", y1) - print("pannned y2 ", y2) - - print("pannned x. ", x) - print("pannned y. ", y) } else { @@ -160,3 +281,23 @@ final class CameraModel{ } + +enum CameraSettingMode { + case NONE + case ZOOM + case EXPOSURE + case AUTO_EXPOSURE + case SWITCH_LENS + case ISO + case SHUTTER_SPEED + case WHITE_BAlANCE + +} + + +enum CameraSettingLevel { + case BASIC // auto exposure ON (Level 1) + case INTERMEDIATE // auto exposure OFF, only adjust exposure (Level 2) + case ADVANCE // auto exposure OFF, can adjust ISO, Shutter Speed and Aperture (Level 3) +} + diff --git a/phyphox-iOS/phyphox/Camera/CameraService.swift b/phyphox-iOS/phyphox/Camera/CameraService.swift index 5bcd09df..eb1ffe6a 100644 --- a/phyphox-iOS/phyphox/Camera/CameraService.swift +++ b/phyphox-iOS/phyphox/Camera/CameraService.swift @@ -31,6 +31,8 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega var isConfigured = false + var configLocked = false + @Published public var shouldShowAlertView = false @Published public var isCameraUnavailable = true @@ -49,6 +51,8 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega var cameraModel: CameraModel? + var cameraSetting: CameraSettingsModel = CameraSettingsModel() + @Published var zoomScale: CGFloat = 1.0 // MARK: Device Configuration Properties @@ -60,10 +64,10 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega switch AVCaptureDevice.authorizationStatus(for: .video) { case .authorized: - print("CameraService: checkForPermission: is authorizes") + break case .notDetermined: - print("CameraService: checkForPermission: is notDetermined") + sessionQueue.suspend() AVCaptureDevice.requestAccess(for: .video, completionHandler: { granted in if !granted { @@ -72,7 +76,7 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega self.sessionQueue.resume() }) default: - print("CameraService: checkForPermission: is default") + setupResult = .notAuthorized DispatchQueue.main.async { @@ -90,7 +94,7 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega } public func configure(){ - print("CameraService: configure") + sessionQueue.async { self.configureSession() } @@ -102,6 +106,232 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega cameraModel = model } + private func changeExposureMode(mode: AVCaptureDevice.ExposureMode){ + lockConfig { () -> () in + if ((self.defaultVideoDevice?.isExposureModeSupported(mode)) != nil) { + self.defaultVideoDevice?.exposureMode = mode + } + } + + } + // optical zoom range, normal zoom range. + // iphone 12 mini: Dual 12MP, wide and ultra wide, wide : f/1,6, ultra wide: f/2.4 120 degree field of view, 2x optical zoom out , digital zoom upto 5x, + + func updateZoom(scale: CGFloat){ + lockConfig { () -> () in + defaultVideoDevice?.videoZoomFactor = max(1.0, min(zoomScale, (defaultVideoDevice?.activeFormat.videoMaxZoomFactor)!)) + zoomScale = scale + } + } + + + func lockConfig(complete: () -> ()) { + if isConfigured { + configLocked = true + do{ + try defaultVideoDevice?.lockForConfiguration() + complete() + defaultVideoDevice?.unlockForConfiguration() + self.postChangeCameraSetting() + configLocked = false + } catch { + configLocked = false + + } + } + } + + func postChangeCameraSetting(){} + + func getAvailableOpticalZoomList(maxOpticalZoom_: Int?) -> [Int] { + guard let maxOpticalZoom = maxOpticalZoom_ else { + return [] + } + + if maxOpticalZoom == 1 { + return [] + } + + if maxOpticalZoom < 5 { + return [1,2] + } + + if maxOpticalZoom < 10 { + return [1,2,5] + } + + if maxOpticalZoom < 15 { + return [1,2,5,10] + } + + return [] + } + + func shutterSpeedRange(min: Int, max: Int) -> [Int] { + let shutterSpeedRange = [1, 2, 4, 8, 15, 30, 60, 125, 250, 500, 1000, 2000, 4000, 8000] + + let filteredShutterSpeedRange = shutterSpeedRange.filter{$0 >= min && $0 <= max} + + return filteredShutterSpeedRange + } + + func getShutterSpeedRange() -> [Int] { + let shutters: [Float] = [1, 2, 4, 8, 15, 30, 60, 125, 250, 500, 1000, 2000, 4000, 8000] + var shutters_available: [Float] = [] + + let min_seconds = CMTimeGetSeconds((cameraModel?.defaultCamera?.activeFormat.minExposureDuration)!) + let max_seconds = CMTimeGetSeconds((cameraModel?.defaultCamera?.activeFormat.maxExposureDuration)!) + + for one_shutter in shutters { + let seconds = 1.0 / Float64(one_shutter) + if seconds >= min_seconds && seconds <= max_seconds { + shutters_available.append(one_shutter) + } + } + + return shutters_available.map { Int($0) } + + } + + + func isoRange(min: Int, max: Int) -> [Int] { + let isoRange = [25, 50, 100, 200, 400, 800, 1600, 3200, 6400, 12800, 25600, 51200] + + let filteredIsoRange = isoRange.filter { $0 >= min && $0 <= max } + + return filteredIsoRange + } + + func findIsoNearestNumber(input: Int, numbers: [Int]) -> Int { + var nearestNumber = numbers[0] + var difference = abs(input - nearestNumber) + + for number in numbers { + let currentDifference = abs(input - number) + if currentDifference < difference { + difference = currentDifference + nearestNumber = number + } + } + + return nearestNumber + } + + func getSelectableValuesForCameraSettings(cameraSettingMode: CameraSettingMode) -> [Int] { + switch cameraSettingMode { + case .ZOOM: + var zoomList : [Float] = [] + if(cameraModel?.cameraSettingsModel.ultraWideCamera == true){ + zoomList.append(0.5) + } + + if(cameraModel?.cameraSettingsModel.maxOpticalZoom != nil ){ + let zoomValue = getAvailableOpticalZoomList(maxOpticalZoom_: cameraModel?.cameraSettingsModel.maxOpticalZoom) + for value in zoomValue{ + zoomList.append(Float(value)) + } + //zoomList.append(Float((cameraModel?.cameraSettingsModel.maxOpticalZoom)! * 3 )) + } + return zoomList.map{Int($0)} + + case .EXPOSURE: + return [] + case .AUTO_EXPOSURE: + return [] + case .SWITCH_LENS: + return [] + case .ISO: + return isoRange(min: Int((cameraModel?.cameraSettingsModel.minIso)!), max: Int((cameraModel?.cameraSettingsModel.maxIso)!)) + case .SHUTTER_SPEED: + return getShutterSpeedRange() + case .WHITE_BAlANCE: + return [] + case .NONE: + return [] + } + } + + + func getCameraSettingsInfo(){ + + cameraModel?.cameraSettingsModel.minIso = cameraModel?.defaultCamera?.activeFormat.minISO ?? 30.0 + cameraModel?.cameraSettingsModel.maxIso = cameraModel?.defaultCamera?.activeFormat.maxISO ?? 100.0 + + cameraModel?.cameraSettingsModel.minShutterSpeed = CMTimeGetSeconds((cameraModel?.defaultCamera?.activeFormat.minExposureDuration)!) + cameraModel?.cameraSettingsModel.maxShutterSpeed = CMTimeGetSeconds((cameraModel?.defaultCamera?.activeFormat.maxExposureDuration)!) + + cameraModel?.cameraSettingsModel.apertureValue = (cameraModel?.defaultCamera!.lensAperture)! + + //cameraModel?.cameraSettingsModel.minZoom = (cameraModel?.defaultCamera?.minAvailableVideoZoomFactor.rounded().hashValue)! + //cameraModel?.cameraSettingsModel.maxZoom = (cameraModel?.defaultCamera?.maxAvailableVideoZoomFactor.rounded().hashValue)! + + cameraModel?.cameraSettingsModel.maxOpticalZoom = cameraModel?.defaultCamera!.virtualDeviceSwitchOverVideoZoomFactors.last?.intValue ?? 1 + + if(cameraModel?.defaultCamera?.deviceType == AVCaptureDevice.DeviceType.builtInDualWideCamera || + cameraModel?.defaultCamera?.deviceType == AVCaptureDevice.DeviceType.builtInTripleCamera){ + cameraModel?.cameraSettingsModel.ultraWideCamera = true + } + } + + func changeISO(_ iso: Float) { + + let duration_seconds = (cameraModel?.cameraSettingsModel.currentShutterSpeed)! + + if (defaultVideoDevice?.isExposureModeSupported(.locked) == true){ + lockConfig { () -> () in + defaultVideoDevice?.exposureMode = .custom + defaultVideoDevice?.setExposureModeCustom(duration: duration_seconds , iso: iso, completionHandler: nil) + cameraModel?.cameraSettingsModel.currentIso = iso + + } + } else { + print("custom exposure setting not supported") + } + + } + + func changeExposureDuration(_ p: Double) { + let seconds = 1.0 / Float64(p) + let duration_seconds = CMTimeMakeWithSeconds(seconds, preferredTimescale: 1000*1000*1000 ) + if (defaultVideoDevice?.isExposureModeSupported(.locked) == true){ + lockConfig { () -> () in + defaultVideoDevice?.exposureMode = .custom + defaultVideoDevice?.setExposureModeCustom(duration: duration_seconds , iso: AVCaptureDevice.currentISO, completionHandler: nil) + cameraModel?.cameraSettingsModel.currentShutterSpeed = duration_seconds + } + } else { + print("custom exposure setting not supported") + } + + } + +func changeExpoDuration(){ + if (defaultVideoDevice?.isExposureModeSupported(.locked) == true){ + do { + try defaultVideoDevice?.lockForConfiguration() + defaultVideoDevice?.exposureMode = .custom + defaultVideoDevice?.unlockForConfiguration() + } catch { + print("Error setting camera zoom: \(error.localizedDescription)") + } + } + else { + print("custom exposure setting not supported") + } +} + + func setExposureTo(auto: Bool){ + do { + try defaultVideoDevice?.lockForConfiguration() + defaultVideoDevice?.exposureMode = auto ? .autoExpose : .custom + defaultVideoDevice?.unlockForConfiguration() + } catch { + print("Error setting camera zoom: \(error.localizedDescription)") + } + print("is already in autoexposure") + } + + private func configureSession(){ if setupResult != .success { @@ -111,8 +341,12 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega session.beginConfiguration() session.sessionPreset = .medium + getCameraSettingsInfo() + + do { - + // builtInDualWideCamera -m virtualDeviceSwitchOverVideoZoomFactors [2] + let defaultCameraType = cameraModel?.defaultCamera?.deviceType ?? AVCaptureDevice.DeviceType.builtInWideAngleCamera if let backCameraDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) { // If a rear dual camera is not available, default to the rear wide angle camera. defaultVideoDevice = backCameraDevice @@ -122,6 +356,28 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega } + + cameraModel?.cameraSettingsModel.currentApertureValue = defaultVideoDevice?.lensAperture ?? 1.0 + cameraModel?.cameraSettingsModel.currentIso = defaultVideoDevice?.iso ?? 30.0 + print("setting deviceType ", defaultCameraType) + + print("setting iso ", defaultVideoDevice?.iso ?? 30.0) + + cameraModel?.cameraSettingsModel.currentShutterSpeed = (defaultVideoDevice?.exposureDuration)! + print("setting shutter", Double((defaultVideoDevice?.exposureDuration.timescale)!)) + + + cameraModel?.cameraSettingsModel.minZoom = Int((defaultVideoDevice?.minAvailableVideoZoomFactor)!) + + if(defaultVideoDevice?.virtualDeviceSwitchOverVideoZoomFactors.isEmpty == true){ + cameraModel?.cameraSettingsModel.maxZoom = Int((defaultVideoDevice?.maxAvailableVideoZoomFactor)!) / 10 + } else { + cameraModel?.cameraSettingsModel.maxZoom = (defaultVideoDevice?.virtualDeviceSwitchOverVideoZoomFactors.last?.intValue ?? 1) * 3 + } + + cameraModel?.cameraSettingsModel.maxOpticalZoom = defaultVideoDevice?.virtualDeviceSwitchOverVideoZoomFactors.last?.intValue ?? 1 + + guard let videoDevice = defaultVideoDevice else { print("Default video device is unavailable.") setupResult = .configurationFailed @@ -179,7 +435,7 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega session.commitConfiguration() - print("CameraService: session configured") + self.isConfigured = true self.start() @@ -224,7 +480,7 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega case .success: self.session.startRunning() self.isSessionRunning = self.session.isRunning - print("CameraService: session running") + if self.session.isRunning { DispatchQueue.main.async { self.isCameraUnavailable = false @@ -309,9 +565,6 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega public func captureOutput(_ output: AVCaptureOutput, didDrop sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { let totalSampleSize = CMSampleBufferGetTotalSampleSize(sampleBuffer) - print("CameraService: didDrop Total Sample Size: \(totalSampleSize)") - - print("CameraService: didDrop captureOutput sampleBuffer totalSampleSize", sampleBuffer.totalSampleSize) } @@ -319,15 +572,10 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { - print("imagebuffer is nil") + return } - - print("captureOutput imagebuffer x1: ", cameraModel?.x1) - print("captureOutput imagebuffer x2: ", cameraModel?.x2) - print("captureOutput imagebuffer y1: ", cameraModel?.y1) - print("captureOutput imagebuffer y2: ", cameraModel?.y2) - + self.metalRender?.updateFrame(imageBuffer: imageBuffer, selectionState: MetalRenderer.SelectionStruct( x1: cameraModel?.x1 ?? 0, x2: cameraModel?.x2 ?? 0, y1: cameraModel?.y1 ?? 0, y2: cameraModel?.y2 ?? 0, editable: true)) diff --git a/phyphox-iOS/phyphox/Camera/CameraView.swift b/phyphox-iOS/phyphox/Camera/CameraView.swift index 382649dc..50bb86bf 100644 --- a/phyphox-iOS/phyphox/Camera/CameraView.swift +++ b/phyphox-iOS/phyphox/Camera/CameraView.swift @@ -9,12 +9,13 @@ import Foundation import SwiftUI import Combine +import CoreMedia @available(iOS 14.0, *) struct PhyphoxCameraView: View { - var model = CameraModel() + @ObservedObject var cameraModel = CameraModel() @State private var isMinimized = true @@ -28,14 +29,13 @@ struct PhyphoxCameraView: View { @State private var height: CGFloat = 200.0 @State private var width: CGFloat = 200.0 @State private var startPosition: CGFloat = 0.0 + + @State private var speed = 50.0 + + @State private var viewState = CGPoint.zero + @Environment(\.scenePhase) var scenePhase - @State private var rotation: Double = 0.0 - @State private var isFlipped: Bool = false - @State private var autoExposureOff : Bool = true - - @State private var zoomClicked: Bool = false - - + var mimimizeCameraButton: some View { Button(action: { self.isMinimized.toggle() @@ -49,8 +49,96 @@ struct PhyphoxCameraView: View { } + var body: some View { + + let dragGesture = DragGesture() + .onChanged{ value in + viewState = value.location + cameraModel.pannned(locationY: viewState.y, locationX: viewState.x , state: CameraModel.GestureState.begin) + print("dragGesture: onChanged", value) + + } + .onEnded{ value in + cameraModel.pannned(locationY: viewState.y, locationX: viewState.x , state: CameraModel.GestureState.end) + self.viewState = .zero + print("dragGesture: onEnded") + } + + + GeometryReader { reader in + ZStack { + //UIColor(named: "")?.edgesIgnoringSafeArea(.all) + + VStack(alignment: .center, spacing: 5) { + + HStack(spacing: 0){ + mimimizeCameraButton + .frame(maxWidth: .infinity, alignment: .leading) + + Text("Preview") + .foregroundColor(.white) + .frame(maxWidth: .infinity, alignment: .leading) + + Circle().frame(width: 50, height: 50).opacity(0.0) + + + } + + ZStack{ + + cameraModel.metalView + .foregroundColor(.gray) + .onAppear{ + cameraModel.configure() + cameraModel.initModel(model: cameraModel) + } + + .gesture(dragGesture) + .foregroundColor(/*@START_MENU_TOKEN@*/.blue/*@END_MENU_TOKEN@*/) + .frame(width: !isMinimized ? reader.size.width / 2 : reader.size.width , + height: !isMinimized ? reader.size.height / 3 : reader.size.height / 1.5, + alignment: .topTrailing) + + + + } + + CameraSettingView(cameraSettingModel: cameraModel.cameraSettingsModel).opacity(isMinimized ? 1.0 : 0.0) + + + + + } + + }.onDisappear{ + cameraModel.endSession() + } + }.background(Color.black) + } +} + +@available(iOS 14.0, *) +struct CameraSettingView: View { + @ObservedObject var cameraSettingModel = CameraSettingsModel() + + @State private var cameraSettingMode: CameraSettingMode = .NONE + + @State private var isEditing = false + @State private var isZooming = false + + @State private var rotation: Double = 0.0 + @State private var isFlipped: Bool = false + @State private var autoExposureOff : Bool = true + + @State private var zoomClicked: Bool = false + + @State private var isListVisible: Bool = false + + var flipCameraButton: some View { Button(action: { + cameraSettingMode = .SWITCH_LENS + isListVisible = false if(isFlipped){ withAnimation(Animation.linear(duration: 0.3)) { self.rotation = -90.0 @@ -63,10 +151,9 @@ struct PhyphoxCameraView: View { isFlipped = true } - model.switchCamera() + cameraSettingModel.switchCamera() }, label: { Circle() - .opacity(isMinimized ? 1.0 : 0.0) .foregroundColor(Color.gray.opacity(0.0)) .frame(width: 45, height: 45, alignment: .center) .overlay( @@ -84,12 +171,13 @@ struct PhyphoxCameraView: View { @State private var zoomScale: CGFloat = 1.0 var zoomSlider: some View { + Button(action: { + cameraSettingMode = .ZOOM + isListVisible.toggle() zoomClicked = !zoomClicked - model.zoom() }, label: { Circle() - .opacity(isMinimized ? 1.0 : 0.0) .foregroundColor(Color.gray.opacity(0.0)) .frame(width: 45, height: 45, alignment: .center) .overlay( @@ -104,11 +192,13 @@ struct PhyphoxCameraView: View { var autoExposure: some View { Button(action: { + cameraSettingMode = .AUTO_EXPOSURE autoExposureOff = !autoExposureOff - model.autoExposure() + isListVisible = false + zoomClicked = false + cameraSettingModel.autoExposure(auto: !autoExposureOff) }, label: { Circle() - .opacity(isMinimized ? 1.0 : 0.0) .foregroundColor(Color.gray.opacity(0.0)) .frame(width: 45, height: 45, alignment: .center) .overlay( @@ -123,10 +213,11 @@ struct PhyphoxCameraView: View { var isoSetting: some View { Button(action: { - model.iso() + cameraSettingMode = .ISO + isListVisible.toggle() + zoomClicked = false }, label: { Circle() - .opacity(isMinimized ? 1.0 : 0.0) .foregroundColor(Color.gray.opacity(0.0)) .frame(width: 45, height: 45, alignment: .center) .overlay( @@ -141,10 +232,11 @@ struct PhyphoxCameraView: View { var shutterSpeedSetting: some View { Button(action: { - model.shutterSpeed() + cameraSettingMode = .SHUTTER_SPEED + isListVisible.toggle() + zoomClicked = false }, label: { Circle() - .opacity(isMinimized ? 1.0 : 0.0) .foregroundColor(Color.gray.opacity(0.0)) .frame(width: 45, height: 45, alignment: .center) .overlay( @@ -158,10 +250,11 @@ struct PhyphoxCameraView: View { var apertureSetting: some View { Button(action: { - model.shutterSpeed() + cameraSettingMode = .NONE + isListVisible = false + zoomClicked = false }, label: { Circle() - .opacity(isMinimized ? 1.0 : 0.0) .foregroundColor(Color.gray.opacity(0.0)) .frame(width: 45, height: 45, alignment: .center) .overlay( @@ -173,189 +266,130 @@ struct PhyphoxCameraView: View { .foregroundColor(.white)) }) } - - - @State private var speed = 50.0 - @State private var isEditing = false - - @State private var isZooming = false - - @State private var viewState = CGPoint.zero - - @Environment(\.scenePhase) var scenePhase - + var body: some View { - - let dragGesture = DragGesture() - .onChanged{ value in - viewState = value.location - model.pannned(locationY: viewState.y, locationX: viewState.x , state: CameraModel.GestureState.begin) - print("dragGesture: onChanged", value) + HStack { + VStack(spacing: 0) { + flipCameraButton + .frame(maxWidth: .infinity) + + Text(isFlipped ? "Front" : "Back") + .font(.caption2) } - .onEnded{ value in - model.pannned(locationY: viewState.y, locationX: viewState.x , state: CameraModel.GestureState.end) - self.viewState = .zero - print("dragGesture: onEnded") + + + VStack(spacing: 0) { + autoExposure + .frame(maxWidth: .infinity) + + Text(autoExposureOff ? "Off" : "On") + .font(.caption2) + } - - - GeometryReader { reader in - ZStack { - //UIColor(named: "")?.edgesIgnoringSafeArea(.all) + + + + VStack(spacing: 0) { - VStack(alignment: .center, spacing: 5) { - - HStack(spacing: 0){ - mimimizeCameraButton - .frame(maxWidth: .infinity, alignment: .leading) - - Text("Preview") - .foregroundColor(.white) - .frame(maxWidth: .infinity, alignment: .leading) - - Circle().frame(width: 50, height: 50).opacity(0.0) - - - } + isoSetting + .opacity(autoExposureOff ? 1.0 : 0.4 ) + .frame(maxWidth: .infinity) + .disabled(autoExposureOff ? false : true) + + Text(String(Int(cameraSettingModel.currentIso))).font(.caption2) + .opacity(autoExposureOff ? 1.0 : 0.4 ) + } + + + VStack(spacing: 0) { - ZStack{ - - model.metalView - .foregroundColor(.gray) - .onAppear{ - model.configure() - model.initModel(model: model) - } + shutterSpeedSetting + .opacity(autoExposureOff ? 1.0 : 0.4 ) + .frame(maxWidth: .infinity) + .disabled(autoExposureOff ? false : true) + let exposureDuration = CMTimeGetSeconds(cameraSettingModel.currentShutterSpeed ?? CMTime(seconds: 30.0, preferredTimescale: 1000)) + Text("1/" + String(Int((1 / exposureDuration)))) + .font(.caption2) + .opacity(autoExposureOff ? 1.0 : 0.4 ) + } + + - .gesture(dragGesture) - .foregroundColor(/*@START_MENU_TOKEN@*/.blue/*@END_MENU_TOKEN@*/) - .frame(width: !isMinimized ? reader.size.width / 2 : reader.size.width , - height: !isMinimized ? reader.size.height / 3 : reader.size.height / 1.5, - alignment: .topTrailing) - - - - - - } - - HStack { - - VStack(spacing: 0) { - flipCameraButton - .frame(maxWidth: .infinity) - - Text(isFlipped ? "Front" : "Back") - .font(.caption2) - } - - - VStack(spacing: 0) { - autoExposure - .frame(maxWidth: .infinity) - - Text(autoExposureOff ? "Off" : "On") - .font(.caption2) - - } - - - - VStack(spacing: 0) { - - isoSetting - .opacity(autoExposureOff ? 1.0 : 0.4 ) - .frame(maxWidth: .infinity) - .disabled(autoExposureOff ? false : true) - - Text("100").font(.caption2) - .opacity(autoExposureOff ? 1.0 : 0.4 ) - } - - - - VStack(spacing: 0) { - - shutterSpeedSetting - .opacity(autoExposureOff ? 1.0 : 0.4 ) - .frame(maxWidth: .infinity) - .disabled(autoExposureOff ? false : true) - - Text("1/60") - .font(.caption2) - .opacity(autoExposureOff ? 1.0 : 0.4 ) - } - - - - VStack(spacing: 0) { - - apertureSetting - .opacity(autoExposureOff ? 1.0 : 0.4 ) - .frame(maxWidth: .infinity) - .disabled(autoExposureOff ? false : true) - - Text("f/1.85") - .font(.caption2) - .opacity(autoExposureOff ? 1.0 : 0.4 ) - } - - - VStack(spacing: 0) { - - zoomSlider - .opacity(isMinimized ? 1.0 : 0.0) - .frame(maxWidth: .infinity) - - Text("Zoom").font(.caption2) - } - - - - - - } + VStack(spacing: 0) { + + apertureSetting + .opacity(autoExposureOff ? 1.0 : 0.4 ) .frame(maxWidth: .infinity) - .opacity(isMinimized ? 1.0 : 0.0) - .padding(.horizontal, 1) - .preferredColorScheme(.dark) - - - Spacer().frame(width: 10.0, height: 20.0) - - - - VStack { - Slider( - value: Binding(get: { - Double(self.model.getScale()) - }, set: { newValue in - self.model.setScale(scale: CGFloat(newValue)) - }), - in: 0...100, - step: 1 - ) { - Text("Speed") - } minimumValueLabel: { - Text("0") - } maximumValueLabel: { - Text("100") - } onEditingChanged: { editing in - isEditing = editing - }.opacity(isMinimized ? 1.0 : 0.0) - - }.opacity(zoomClicked ? 1.0 : 0.0) - - } + .disabled(autoExposureOff ? false : true) - }.onDisappear{ - model.endSession() + Text("f/" + String(cameraSettingModel.currentApertureValue)) + .font(.caption2) + .opacity(autoExposureOff ? 1.0 : 0.4 ) } - }.background(Color.black) + + + VStack(spacing: 0) { + + zoomSlider + .frame(maxWidth: .infinity) + + Text("Zoom").font(.caption2) + } + + } + .frame(maxWidth: .infinity) + .padding(.horizontal, 1) + .preferredColorScheme(.dark) + + + VStack { + var cameraSettingsValues = cameraSettingModel.getLisOfCameraSettingsValue(cameraSettingMode: cameraSettingMode) + + ScrollView(.horizontal, showsIndicators: false) { + HStack { + ForEach(cameraSettingsValues , id: \.self) { title in + if(cameraSettingMode == .SHUTTER_SPEED){ + TextButton(text: "1/" + String(title)) { + cameraSettingModel.exposure(value: Double(title)) + + } + } else if(cameraSettingMode == .ISO){ + TextButton(text: String(title)) { + //cameraSettingModel.exposure(value: Double(title)) + cameraSettingModel.iso(value: Float(title)) + } + + } + } + } + }.opacity(!isListVisible ? 0.0 : 1.0) + + Spacer().frame(width: 10.0, height: 10.0) + + let sliderRange: ClosedRange = Double(1)...Double((cameraSettingModel.defaultCameraSetting?.virtualDeviceSwitchOverVideoZoomFactors.last?.intValue ?? 1) * 3) + + Slider( + value: Binding(get: { + Double(self.cameraSettingModel.getScale()) + }, set: { newValue in + self.cameraSettingModel.setScale(scale: CGFloat(newValue)) + }), + in: sliderRange, + step: 0.1 + ) { + Text("") + } minimumValueLabel: { + Text(String(cameraSettingModel.minZoom)) + } maximumValueLabel: { + Text(String(cameraSettingModel.maxZoom)) + } onEditingChanged: { editing in + isEditing = editing + }.opacity(zoomClicked ? 1.0 : 0.0) + } + } - } @@ -386,3 +420,21 @@ public struct AlertError { self.secondaryAction = secondaryAction } } + +@available(iOS 13.0.0, *) +struct TextButton: View { + var text: String + var action: () -> Void + + @available(iOS 13.0.0, *) + var body: some View { + Button(action: action) { + Text(text) + .padding(EdgeInsets(top: 2, leading: 8, bottom: 2, trailing: 8)) + .foregroundColor(.black) + .background(Color.white) + .cornerRadius(10) + } + } +} + diff --git a/phyphox-iOS/phyphox/Camera/MetalRenderer.swift b/phyphox-iOS/phyphox/Camera/MetalRenderer.swift index 8cb15873..8500d79b 100644 --- a/phyphox-iOS/phyphox/Camera/MetalRenderer.swift +++ b/phyphox-iOS/phyphox/Camera/MetalRenderer.swift @@ -83,14 +83,9 @@ class MetalRenderer: NSObject, MTKViewDelegate{ func updateFrame(imageBuffer: CVImageBuffer!, selectionState: SelectionStruct) { - print("Metal Renderer: updateFrame") - print("Metal Renderer: updateFrame selectionState: x1 ", selectionState.x1) - print("Metal Renderer: updateFrame selectionState: x2", selectionState.x2) - print("Metal Renderer: updateFrame selectionState: y1 ", selectionState.y1) - print("Metal Renderer: updateFrame selectionState: y2 ", selectionState.y2) - + if imageBuffer != nil { - print("Metal Renderer: imageBuffer not nil") + self.cvImageBuffer = imageBuffer } @@ -98,7 +93,7 @@ class MetalRenderer: NSObject, MTKViewDelegate{ } func loadMetal(){ - print("Metal Renderer: Load Metal") + // Set the default formats needed to render. renderDestination.colorPixelFormat = .bgra8Unorm renderDestination.sampleCount = 1 @@ -155,14 +150,14 @@ class MetalRenderer: NSObject, MTKViewDelegate{ // Create the command queue for one frame of rendering work. metalCommandQueue = metalDevice.makeCommandQueue() - print("Metal Renderer: Metal loaded") + } func update() { - print("MetalRenderer: Update") + // Wait to ensure only kMaxBuffersInFlight are getting proccessed by any stage in the Metal // pipeline (App, Metal, Drivers, GPU, etc). _ = inFlightSemaphore.wait(timeout: DispatchTime.distantFuture) @@ -183,12 +178,12 @@ class MetalRenderer: NSObject, MTKViewDelegate{ updateAppState() - print("Metal Renderer: updateAppState next") + if let renderPassDescriptor = renderDestination.currentRenderPassDescriptor, let currentDrawable = renderDestination.currentDrawable { - print("Metal Renderer: renderPassDescriptor") + if let renderEncoding = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) { - print("Metal Renderer: renderEncoding") + // Set a label to identify this render pass in a captured Metal frame. renderEncoding.label = "DepthGUICameraPreview" @@ -206,13 +201,13 @@ class MetalRenderer: NSObject, MTKViewDelegate{ // Finalize rendering here & push the command buffer to the GPU. commandBuffer.commit() - print("Metal Renderer: updated") + } } // Schedules the camera image to be rendered on the GPU. func doRenderPass(renderEncoder: MTLRenderCommandEncoder) { - print("Metal Renderer: doRenderPass started") + guard let cameraImageY = cameraImageTextureY, let cameraImageCbCr = cameraImageTextureCbCr else { return } @@ -241,19 +236,19 @@ class MetalRenderer: NSObject, MTKViewDelegate{ renderEncoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4) renderEncoder.popDebugGroup() - print("Metal Renderer: doRenderPass ended") + } // Updates any app state. func updateAppState() { - print("updateAppState before") + guard let currentFrame = self.cvImageBuffer else { return } - print("updateAppState after") + // Prepare the current frame's camera image for transfer to the GPU. updateCameraImageTextures(frame: currentFrame) @@ -276,7 +271,7 @@ class MetalRenderer: NSObject, MTKViewDelegate{ // Creates a Metal texture with the argument pixel format from a CVPixelBuffer at the argument plane index. func createTexture(fromPixelBuffer pixelBuffer: CVImageBuffer, pixelFormat: MTLPixelFormat, planeIndex: Int) -> CVMetalTexture? { - print("Metal Renderer: createTexture started") + let width = CVPixelBufferGetWidthOfPlane(pixelBuffer, planeIndex) // 480 //240 let height = CVPixelBufferGetHeightOfPlane(pixelBuffer, planeIndex) // 360 //180 @@ -287,7 +282,7 @@ class MetalRenderer: NSObject, MTKViewDelegate{ if status != kCVReturnSuccess { texture = nil } - print("Metal Renderer: createTexture ended") + return texture } diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index b2d6febd..fb60bfa3 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 12971 + 13128 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 652f98c27f667a38983120c67db8fdf77b427c65 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Tue, 6 Feb 2024 11:37:11 +0100 Subject: [PATCH 68/81] fix: merge conflict --- phyphox-iOS/phyphox.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj index 5328fecb..477eb143 100644 --- a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj +++ b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj @@ -141,7 +141,8 @@ 6BE3209F1CB19E0300DCC1ED /* CreatePresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE3209C1CB19E0300DCC1ED /* CreatePresentationController.swift */; }; 6BE320A01CB19E0300DCC1ED /* CreateViewControllerTransitioningDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE3209D1CB19E0300DCC1ED /* CreateViewControllerTransitioningDelegate.swift */; }; 6BE320A11CB19E0300DCC1ED /* CreateViewControllerTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE3209E1CB19E0300DCC1ED /* CreateViewControllerTransition.swift */; }; - 6BE320AA1CB2B55700DCC1ED /* TextFieldTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE320A91CB2B55700DCC1ED /* TextFieldTableViewCell.swift */; }; 6BE320AE1CB2D36D00DCC1ED /* SimpleExperimentSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE320AD1CB2D36D00DCC1ED /* SimpleExperimentSerializer.swift */; }; + 6BE320AA1CB2B55700DCC1ED /* TextFieldTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE320A91CB2B55700DCC1ED /* TextFieldTableViewCell.swift */; }; + 6BE320AE1CB2D36D00DCC1ED /* SimpleExperimentSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE320AD1CB2D36D00DCC1ED /* SimpleExperimentSerializer.swift */; }; 6BE320B21CB3C0DE00DCC1ED /* PTButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BE320B11CB3C0DE00DCC1ED /* PTButton.m */; }; 6BE320B41CB3E09C00DCC1ED /* ShrinkingAnimaitonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE320B31CB3E09C00DCC1ED /* ShrinkingAnimaitonViewController.swift */; }; 6BEA944A207FC5280077274A /* ExportElementHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BEA943B207FC5280077274A /* ExportElementHandler.swift */; }; @@ -158,7 +159,6 @@ 866632E92A29E1D90068762D /* UIColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 866632E82A29E1D90068762D /* UIColorExtensions.swift */; }; 867E2BD82AC464D000ABB472 /* CocoaMQTT in Frameworks */ = {isa = PBXBuildFile; productRef = 867E2BD72AC464D000ABB472 /* CocoaMQTT */; }; A101AB0829BB801600B82311 /* ConnectedBluetoothDeviceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A101AB0729BB801600B82311 /* ConnectedBluetoothDeviceViewController.swift */; }; - 866632E92A29E1D90068762D /* UIColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 866632E82A29E1D90068762D /* UIColorExtensions.swift */; }; A15891F02B10BFD000A169C3 /* MetalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A15891EF2B10BFD000A169C3 /* MetalView.swift */; }; A15891F22B10BFED00A169C3 /* MetalRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A15891F12B10BFED00A169C3 /* MetalRenderer.swift */; }; A182CEDD2B025B23000385D2 /* CameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A182CEDC2B025B23000385D2 /* CameraView.swift */; }; From d97efbe6472ec02f9af9b5515fb856d27ae1d1b4 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Tue, 13 Feb 2024 08:43:27 +0100 Subject: [PATCH 69/81] feat: show luma value after averaging over all the pixels --- phyphox-iOS/phyphox.xcodeproj/project.pbxproj | 12 +- .../Camera/ExperimentCameraInput.swift | 51 +++++ .../phyphox/Camera/MetalRenderer.swift | 94 ++++++++ .../Data-Input/ExperimentCameraInput.swift | 112 ---------- .../ExperimentCameraInputSession.swift | 206 ------------------ phyphox-iOS/phyphox/Info.plist | 2 +- .../ExperimentPageViewController.swift | 7 +- 7 files changed, 153 insertions(+), 331 deletions(-) create mode 100644 phyphox-iOS/phyphox/Camera/ExperimentCameraInput.swift delete mode 100644 phyphox-iOS/phyphox/Experiments/Data-Input/ExperimentCameraInput.swift delete mode 100644 phyphox-iOS/phyphox/Experiments/Data-Input/ExperimentCameraInputSession.swift diff --git a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj index 477eb143..4292ecee 100644 --- a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj +++ b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj @@ -159,6 +159,7 @@ 866632E92A29E1D90068762D /* UIColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 866632E82A29E1D90068762D /* UIColorExtensions.swift */; }; 867E2BD82AC464D000ABB472 /* CocoaMQTT in Frameworks */ = {isa = PBXBuildFile; productRef = 867E2BD72AC464D000ABB472 /* CocoaMQTT */; }; A101AB0829BB801600B82311 /* ConnectedBluetoothDeviceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A101AB0729BB801600B82311 /* ConnectedBluetoothDeviceViewController.swift */; }; + A14B3A472B7AD2960016E380 /* ExperimentCameraInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = A14B3A462B7AD2960016E380 /* ExperimentCameraInput.swift */; }; A15891F02B10BFD000A169C3 /* MetalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A15891EF2B10BFD000A169C3 /* MetalView.swift */; }; A15891F22B10BFED00A169C3 /* MetalRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A15891F12B10BFED00A169C3 /* MetalRenderer.swift */; }; A182CEDD2B025B23000385D2 /* CameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A182CEDC2B025B23000385D2 /* CameraView.swift */; }; @@ -168,10 +169,8 @@ A182CEE72B02DF10000385D2 /* CameraService+Enums.swift in Sources */ = {isa = PBXBuildFile; fileRef = A182CEE62B02DF10000385D2 /* CameraService+Enums.swift */; }; A1A6A72F297685DF00090309 /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1A6A72E297685DF00090309 /* Utility.swift */; }; A1A80D3D29AE049E00AD756D /* SettingsBundleHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1A80D3C29AE049E00AD756D /* SettingsBundleHelper.swift */; }; - A1B9C1BC2AA9E45900238AF6 /* ExperimentCameraInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B9C1BB2AA9E45900238AF6 /* ExperimentCameraInput.swift */; }; A1B9C1BE2AA9E59D00238AF6 /* CameraViewDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B9C1BD2AA9E59D00238AF6 /* CameraViewDescriptor.swift */; }; A1B9C1C02AA9E76C00238AF6 /* CameraViewElementHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B9C1BF2AA9E76C00238AF6 /* CameraViewElementHandler.swift */; }; - A1B9C1C22AA9E87300238AF6 /* ExperimentCameraInputSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B9C1C12AA9E87300238AF6 /* ExperimentCameraInputSession.swift */; }; A1B9C1C42AA9EC9600238AF6 /* ExperimentCameraGUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B9C1C32AA9EC9600238AF6 /* ExperimentCameraGUIView.swift */; }; A1E03A7F29B61A9000CE93A6 /* UIImageExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1E03A7E29B61A9000CE93A6 /* UIImageExt.swift */; }; A1FA9A00299FCFBC00E0FDBF /* color.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A1FA99FF299FCFBC00E0FDBF /* color.xcassets */; }; @@ -497,6 +496,7 @@ 86C183D129F132020089E396 /* ka */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ka; path = ka.lproj/Localizable.strings; sourceTree = ""; }; 86C183D229F132020089E396 /* ka */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ka; path = ka.lproj/InfoPlist.strings; sourceTree = ""; }; A101AB0729BB801600B82311 /* ConnectedBluetoothDeviceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedBluetoothDeviceViewController.swift; sourceTree = ""; }; + A14B3A462B7AD2960016E380 /* ExperimentCameraInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperimentCameraInput.swift; sourceTree = ""; }; A15891EF2B10BFD000A169C3 /* MetalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetalView.swift; sourceTree = ""; }; A15891F12B10BFED00A169C3 /* MetalRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetalRenderer.swift; sourceTree = ""; }; A182CEDC2B025B23000385D2 /* CameraView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraView.swift; sourceTree = ""; }; @@ -506,10 +506,8 @@ A182CEE62B02DF10000385D2 /* CameraService+Enums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraService+Enums.swift"; sourceTree = ""; }; A1A6A72E297685DF00090309 /* Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = ""; }; A1A80D3C29AE049E00AD756D /* SettingsBundleHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsBundleHelper.swift; sourceTree = ""; }; - A1B9C1BB2AA9E45900238AF6 /* ExperimentCameraInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperimentCameraInput.swift; sourceTree = ""; }; A1B9C1BD2AA9E59D00238AF6 /* CameraViewDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraViewDescriptor.swift; sourceTree = ""; }; A1B9C1BF2AA9E76C00238AF6 /* CameraViewElementHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraViewElementHandler.swift; sourceTree = ""; }; - A1B9C1C12AA9E87300238AF6 /* ExperimentCameraInputSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperimentCameraInputSession.swift; sourceTree = ""; }; A1B9C1C32AA9EC9600238AF6 /* ExperimentCameraGUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperimentCameraGUIView.swift; sourceTree = ""; }; A1E03A7E29B61A9000CE93A6 /* UIImageExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageExt.swift; sourceTree = ""; }; A1FA99FF299FCFBC00E0FDBF /* color.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = color.xcassets; sourceTree = ""; }; @@ -1039,8 +1037,6 @@ C07B530C22942CC300BA085A /* InputConversion.swift */, C097E4442297DA1800FB62B9 /* OutputConversion.swift */, C0AE8A972294558000A3EC48 /* ConfigConversion.swift */, - A1B9C1BB2AA9E45900238AF6 /* ExperimentCameraInput.swift */, - A1B9C1C12AA9E87300238AF6 /* ExperimentCameraInputSession.swift */, ); path = "Data-Input"; sourceTree = ""; @@ -1148,6 +1144,7 @@ A182CEE62B02DF10000385D2 /* CameraService+Enums.swift */, A15891EF2B10BFD000A169C3 /* MetalView.swift */, A15891F12B10BFED00A169C3 /* MetalRenderer.swift */, + A14B3A462B7AD2960016E380 /* ExperimentCameraInput.swift */, ); path = Camera; sourceTree = ""; @@ -1567,6 +1564,7 @@ 6B94A6211CA015A4008D9ACF /* GLGraphView.swift in Sources */, A1E03A7F29B61A9000CE93A6 /* UIImageExt.swift in Sources */, 6B612FF6207EB052001DA8D9 /* AnalysisElementHandler.swift in Sources */, + A14B3A472B7AD2960016E380 /* ExperimentCameraInput.swift in Sources */, 6BAEC1DC1C1F6EFC00FBD979 /* ViewDescriptor.swift in Sources */, 4B47B02D1DA6A1E100CCBA6E /* AcosAnalysis.swift in Sources */, 6B9E3DA0205296E0008073D5 /* ExperimentViewModuleTableViewCell.swift in Sources */, @@ -1575,7 +1573,6 @@ 6BE3209F1CB19E0300DCC1ED /* CreatePresentationController.swift in Sources */, 4B3E495E1CF7716D00C43837 /* MatchAnalysis.swift in Sources */, 6BEA944D207FCA2F0077274A /* ValueViewElementHandler.swift in Sources */, - A1B9C1C22AA9E87300238AF6 /* ExperimentCameraInputSession.swift in Sources */, 6BB52BC61C1B11CA00217CF1 /* ExperimentTranslation.swift in Sources */, 6BE320AA1CB2B55700DCC1ED /* TextFieldTableViewCell.swift in Sources */, 6B7BF80D1C14723D007408FB /* CosAnalysis.swift in Sources */, @@ -1694,7 +1691,6 @@ 6B6A4C202056A367007586D0 /* GLGraphShaderProgram.swift in Sources */, A182CEDD2B025B23000385D2 /* CameraView.swift in Sources */, 6B8AA3F21C5E2A6400E59685 /* ExperimentComplexUpdateValueAnalysis.swift in Sources */, - A1B9C1BC2AA9E45900238AF6 /* ExperimentCameraInput.swift in Sources */, 6B422E2C1CA4A4BF00E945BC /* GraphGridView.swift in Sources */, 4B486EE11EC4B34D00F1045E /* CreditsView.swift in Sources */, 4B47B03D1DA6B12500CCBA6E /* BinningAnalysis.swift in Sources */, diff --git a/phyphox-iOS/phyphox/Camera/ExperimentCameraInput.swift b/phyphox-iOS/phyphox/Camera/ExperimentCameraInput.swift new file mode 100644 index 00000000..73331675 --- /dev/null +++ b/phyphox-iOS/phyphox/Camera/ExperimentCameraInput.swift @@ -0,0 +1,51 @@ +// +// ExperimentCameraInput.swift +// phyphox +// +// Created by Gaurav Tripathee on 12.02.24. +// Copyright © 2024 RWTH Aachen. All rights reserved. +// + +import Foundation + +final class ExperimentCameraInput { + + let initx1: Float + let initx2: Float + let inity1: Float + let inity2: Float + + let timeReference: ExperimentTimeReference? + let zBuffer: DataBuffer? + let tBuffer: DataBuffer? + + + + init(timeReference: ExperimentTimeReference, zBuffer: DataBuffer?, tBuffer: DataBuffer?, x1: Float, x2: Float, y1: Float, y2: Float, smooth: Bool) { + self.initx1 = x1 + self.initx2 = x2 + self.inity1 = y1 + self.inity2 = y2 + self.zBuffer = zBuffer + self.tBuffer = tBuffer + self.timeReference = timeReference + + print("camera input: initx1: ", x1) + print("camera input: initx2: ", x2) + print("camera input: inity1: ", y1) + print("camera input: inity2: ", y2) + + } +} + +extension ExperimentCameraInput: Equatable { + static func ==(lhs: ExperimentCameraInput, rhs: ExperimentCameraInput) -> Bool { + return lhs.initx1 == rhs.initx1 && + lhs.initx2 == rhs.initx2 && + lhs.inity1 == rhs.inity1 && + lhs.inity2 == rhs.inity2 && + lhs.timeReference == rhs.timeReference && + lhs.zBuffer == rhs.zBuffer && + lhs.tBuffer == rhs.tBuffer + } +} diff --git a/phyphox-iOS/phyphox/Camera/MetalRenderer.swift b/phyphox-iOS/phyphox/Camera/MetalRenderer.swift index 8500d79b..821378fd 100644 --- a/phyphox-iOS/phyphox/Camera/MetalRenderer.swift +++ b/phyphox-iOS/phyphox/Camera/MetalRenderer.swift @@ -9,6 +9,7 @@ import Foundation import MetalKit import AVFoundation +import Accelerate @available(iOS 13.0, *) @@ -49,6 +50,14 @@ class MetalRenderer: NSObject, MTKViewDelegate{ var cvImageBuffer : CVImageBuffer? + + var timeReference: ExperimentTimeReference? + var zBuffer: DataBuffer? + var tBuffer: DataBuffer? + + private var queue: DispatchQueue? + + init(parent: CameraMetalView ,renderer: MTKView) { print("Metal Renderer : init") @@ -90,6 +99,91 @@ class MetalRenderer: NSObject, MTKViewDelegate{ } self.selectionState = selectionState + + getLuma() + } + + func start(queue: DispatchQueue) throws { + self.queue = queue + } + + func getLuma(){ + if let pixelBuffer = self.cvImageBuffer{ + // Now you can use pixelBuffer as a CVPixelBuffer + + var luma = 0 + let luminance = 00 + + //TODO croping image as per the overlay + CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags.readOnly) + let lumaBaseAddress = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0) + let lumaWidth = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0) + let lumaHeight = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0) + let lumaRowBytes = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0) + + var lData = vImage_Buffer(data: lumaBaseAddress, height: vImagePixelCount(lumaHeight), width: vImagePixelCount(lumaWidth), rowBytes: lumaRowBytes) + + + let chromaBaseAddress = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1) + let chromaWidth = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1) + let chromaHeight = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1) + let chromaRowBytes = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1) + + var cData = vImage_Buffer(data: chromaBaseAddress, height: vImagePixelCount(chromaHeight), width: vImagePixelCount(chromaWidth), rowBytes: chromaRowBytes) + + CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags.readOnly) + + let byteBuffer = UnsafeMutableRawPointer(lumaBaseAddress) + + let bufferPointer = byteBuffer?.assumingMemoryBound(to: Pixel_8.self) + + + for y in 0...lData.height { + for x in 0...lData.width { + var l = (bufferPointer?[Int(x)] ?? 0) & 0xFF + luma += Int(l) + + } + } + + let xmin = Int(self.selectionState.x1 * Float(lData.width)) + let xmax = Int(self.selectionState.x2 * Float(lData.width)) + let ymin = Int(self.selectionState.y1 * Float(lData.height)) + let ymax = Int(self.selectionState.y2 * Float(lData.height)) + + + let analysisArea = (xmax - xmin) * (ymax - ymin) + + luma /= analysisArea * 255 + print("analysis area: ", analysisArea) + print("luma value: ", luma) + + + } else { + // If the CVImageBuffer is not a CVPixelBuffer, you may need to perform conversion + + } + } + + private func writeToBuffers(z: Double, t: TimeInterval) { + if let zBuffer = zBuffer { + zBuffer.append(z) + } + + if let tBuffer = tBuffer { + tBuffer.append(t) + } + } + + private func dataIn(z: Double, time: TimeInterval) { + guard let timeReference = timeReference else { + print("Error: time reference not set") + return + } + let t = timeReference.getExperimentTimeFromEvent(eventTime: time) + if t >= timeReference.timeMappings.last?.experimentTime ?? 0.0 { + self.writeToBuffers(z: z, t: t) + } } func loadMetal(){ diff --git a/phyphox-iOS/phyphox/Experiments/Data-Input/ExperimentCameraInput.swift b/phyphox-iOS/phyphox/Experiments/Data-Input/ExperimentCameraInput.swift deleted file mode 100644 index b71af450..00000000 --- a/phyphox-iOS/phyphox/Experiments/Data-Input/ExperimentCameraInput.swift +++ /dev/null @@ -1,112 +0,0 @@ -// -// ExperimentCameraInput.swift -// phyphox -// -// Created by Gaurav Tripathee on 07.09.23. -// Copyright © 2023 RWTH Aachen. All rights reserved. -// - - -import Foundation -import ARKit - - -final class ExperimentCameraInput { - - - enum CameraOrientation: String, LosslessStringConvertible, CaseIterable { - case front - case back - } - - let initx1: Float - let initx2: Float - let inity1: Float - let inity2: Float - - let smooth: Bool - - let timeReference: ExperimentTimeReference? - let zBuffer: DataBuffer? - let tBuffer: DataBuffer? - - lazy var session: Any? = nil - - private var queue: DispatchQueue? - - - init(timeReference: ExperimentTimeReference, zBuffer: DataBuffer?, tBuffer: DataBuffer?, x1: Float, x2: Float, y1: Float, y2: Float, smooth: Bool) { - - self.initx1 = x1 - self.initx2 = x2 - self.inity1 = y1 - self.inity2 = y2 - self.zBuffer = zBuffer - self.tBuffer = tBuffer - self.smooth = smooth - self.timeReference = timeReference - - - if #available(iOS 14.0, *) { - session = ExperimentCameraInputSession() - guard let session = session as? ExperimentCameraInputSession else { - return - } - - session.x1 = x1 - session.x2 = x2 - session.y1 = y1 - session.y2 = y2 - session.zBuffer = zBuffer - session.tBuffer = tBuffer - session.timeReference = timeReference - session.smooth = smooth - } - } - - - func start(queue: DispatchQueue) throws { - guard #available(iOS 14.0, *) else { - return - } - guard let session = session as? ExperimentCameraInputSession else { - return - } - try session.start(queue: queue) - } - - func stop() { - guard #available(iOS 14.0, *) else { - return - } - guard let session = session as? ExperimentCameraInputSession else { - return - } - session.stop() - } - - func clear() { - guard #available(iOS 14.0, *) else { - return - } - guard let session = session as? ExperimentCameraInputSession else { - return - } - session.clear() - } - -} - -extension ExperimentCameraInput: Equatable { - static func ==(lhs: ExperimentCameraInput, rhs: ExperimentCameraInput) -> Bool { - return lhs.initx1 == rhs.initx1 && - lhs.initx2 == rhs.initx2 && - lhs.inity1 == rhs.inity1 && - lhs.inity2 == rhs.inity2 && - lhs.timeReference == rhs.timeReference && - lhs.zBuffer == rhs.zBuffer && - lhs.tBuffer == rhs.tBuffer && - lhs.smooth == rhs.smooth - } -} - diff --git a/phyphox-iOS/phyphox/Experiments/Data-Input/ExperimentCameraInputSession.swift b/phyphox-iOS/phyphox/Experiments/Data-Input/ExperimentCameraInputSession.swift deleted file mode 100644 index 0d9f3ca4..00000000 --- a/phyphox-iOS/phyphox/Experiments/Data-Input/ExperimentCameraInputSession.swift +++ /dev/null @@ -1,206 +0,0 @@ -// -// ExperimentCameraInputSession.swift -// phyphox -// -// Created by Gaurav Tripathee on 07.09.23. -// Copyright © 2023 RWTH Aachen. All rights reserved. -// - -import Foundation -import AVFoundation - -@available(iOS 14.0, *) -final class ExperimentCameraInputSession: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate, CameraGUISelectionDelegate, ObservableObject { - - var x1: Float = 0.4 - var x2: Float = 0.6 - var y1: Float = 0.4 - var y2: Float = 0.6 - - var running = false - - var frontCamera: Bool - - var timeReference: ExperimentTimeReference? - var zBuffer: DataBuffer? - var tBuffer: DataBuffer? - - var smooth: Bool = true - - var delegate: CameraGUIDelegate? - - var measuring = false - - private var queue: DispatchQueue? = DispatchQueue.global(qos: .userInteractive) - private var queueOutput: DispatchQueue? = DispatchQueue.global(qos: .userInteractive) - // Communicate with the session and other session objects on this queue. - private let sessionQueue = DispatchQueue(label: "session queue") - - @Published var cameraSession = AVCaptureSession() - - var screenRect: CGRect! = nil // For view dimensions - - - var isSessionRunning = false -// 12 - var isConfigured = false -// 13 - //var setupResult: SessionSetupResult = .success - - @objc dynamic var videoDeviceInput: AVCaptureDeviceInput! - - // MARK: Alert properties - public var alertError: AlertError = AlertError() - - - @Published public var shouldShowAlertView = false -// 3. - @Published public var shouldShowSpinner = false -// 4. - @Published public var willCapturePhoto = false -// 5. - @Published public var isCameraButtonDisabled = true -// 6. - @Published public var isCameraUnavailable = true - - - override init(){ - frontCamera = false - } - - - func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { - // Process pixel buffer bytes here - guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { - return - } - - - - // Convert the image buffer to a CIImage - let ciImage = CIImage(cvPixelBuffer: imageBuffer) - - - // Convert the CIImage to a UIImage - let context = CIContext(options: nil) - let cgImage = context.createCGImage(ciImage, from: ciImage.extent) - - let width = cgImage?.width - let height = cgImage?.height - - let uiImage = UIImage(cgImage: cgImage!) - - // Example: Print a message in a background queue - queueOutput?.async { - - } - } - - - func runSession(){ - // get camera device - guard let videoDevice = AVCaptureDevice.default(for: .video) else { return } - - // plug camera device into device input which talkes with capture session via AVCaptureConnection - guard let videoDeviceInput = try? AVCaptureDeviceInput(device: videoDevice) else { return } - - cameraSession.sessionPreset = .high - if let existingInputs = cameraSession.inputs as? [AVCaptureInput] { - for input in existingInputs { - cameraSession.removeInput(input) - } - } - guard cameraSession.canAddInput(videoDeviceInput) else { return } - cameraSession.addInput(videoDeviceInput) - - // setup output, add output to our capture session - let captureOutput = AVCaptureVideoDataOutput() - captureOutput.alwaysDiscardsLateVideoFrames = true - captureOutput.setSampleBufferDelegate(self, queue: queue) - cameraSession.addOutput(captureOutput) - - self.delegate?.updateFrame(captureSession: self.cameraSession) - - queue?.async { - print("runSession Queue") - if (self.cameraSession.isRunning == false) { - self.cameraSession.startRunning() - } - } - } - - func stopSession() { - running = false - if (self.cameraSession.isRunning == true) { - self.cameraSession.stopRunning() - } - } - - func start(queue: DispatchQueue) throws { - self.queue = queue - measuring = true - } - - func stop() { - measuring = false - - } - - - func clear() { - - } - - func session() { - - } - - func frameIn(depthMap: UnsafeMutablePointer, confidenceMap: UnsafeMutablePointer?, w: Int, h: Int, t: TimeInterval) { - - let xp1 = Int(round(Float(w) * x1)) - let xp2 = Int(round(Float(w) * x2)) - let yp1 = Int(round(Float(h) * y1)) - let yp2 = Int(round(Float(h) * y2)) - let xi1 = max(min(xp1, xp2, w), 0) - let xi2 = min(max(xp1, xp2, 0), w) - let yi1 = max(min(yp1, yp2, h), 0) - let yi2 = min(max(yp1, yp2, 0), h) - - var z: Double - var sum = 0.0 - - } - - public func attachDelegate(delegate: CameraGUIDelegate) { - self.delegate = delegate - runSession() - } - - private func writeToBuffers(z: Double, t: TimeInterval) { - if let zBuffer = zBuffer { - zBuffer.append(z) - } - - if let tBuffer = tBuffer { - tBuffer.append(t) - } - } - - private func dataIn(z: Double, time: TimeInterval) { - guard let timeReference = timeReference else { - print("Error: time reference not set") - return - } - let t = timeReference.getExperimentTimeFromEvent(eventTime: time) - if t >= timeReference.timeMappings.last?.experimentTime ?? 0.0 { - queue?.async { - autoreleasepool(invoking: { - self.writeToBuffers(z: z, t: t) - }) - } - } - } - - -} - diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index fb60bfa3..1578f0f6 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 13128 + 13145 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift index 339a5312..bcc8bfef 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift @@ -126,7 +126,6 @@ final class ExperimentPageViewController: UIViewController, UIPageViewController if let descriptors = experiment.viewDescriptors { for collection in descriptors { let m = ExperimentViewModuleFactory.createViews(collection) - modules.append(m) experimentViewControllers.append(ExperimentViewController(modules: m)) @@ -537,9 +536,9 @@ final class ExperimentPageViewController: UIViewController, UIPageViewController session.stopSession() } - if let camSession = experiment.cameraInput?.session as? ExperimentCameraInputSession { - camSession.stopSession() - } + //if let camSession = experiment.cameraInput?.session as? ExperimentCameraInputSession { + // camSession.stopSession() + //} } disconnectFromBluetoothDevices() disconnectFromNetworkDevices() From 1525ef4aa50c4d2dc8449b081686f2ebf0d01f65 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Wed, 21 Feb 2024 20:32:17 +0100 Subject: [PATCH 70/81] feat: show the luma value in the simple tab and also make the camera session independent to the camera gui --- phyphox-iOS/phyphox.xcodeproj/project.pbxproj | 8 + phyphox-iOS/phyphox/Camera/CameraModel.swift | 98 ++++-------- .../phyphox/Camera/CameraService.swift | 6 +- phyphox-iOS/phyphox/Camera/CameraUIView.swift | 111 ++++++++++++++ phyphox-iOS/phyphox/Camera/CameraView.swift | 145 +++++++++++++++--- .../Camera/ExperimentCameraInput.swift | 22 ++- .../Camera/ExperimentCameraInputSession.swift | 51 ++++++ .../phyphox/Camera/MetalRenderer.swift | 28 +++- .../Data-Input/ExperimentDepthInput.swift | 2 +- phyphox-iOS/phyphox/Info.plist | 2 +- .../ExperimentPageViewController.swift | 19 ++- .../ExperimentViewModuleFactory.swift | 4 +- .../Static/ExperimentCameraGUIView.swift | 1 - .../Static/ExperimentDepthGUIView.swift | 2 +- 14 files changed, 385 insertions(+), 114 deletions(-) create mode 100644 phyphox-iOS/phyphox/Camera/CameraUIView.swift create mode 100644 phyphox-iOS/phyphox/Camera/ExperimentCameraInputSession.swift diff --git a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj index 4292ecee..cccc902a 100644 --- a/phyphox-iOS/phyphox.xcodeproj/project.pbxproj +++ b/phyphox-iOS/phyphox.xcodeproj/project.pbxproj @@ -167,6 +167,8 @@ A182CEE12B025B53000385D2 /* CameraService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A182CEE02B025B53000385D2 /* CameraService.swift */; }; A182CEE32B025B6C000385D2 /* CameraInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = A182CEE22B025B6C000385D2 /* CameraInput.swift */; }; A182CEE72B02DF10000385D2 /* CameraService+Enums.swift in Sources */ = {isa = PBXBuildFile; fileRef = A182CEE62B02DF10000385D2 /* CameraService+Enums.swift */; }; + A18E13582B7CAFF4006EB323 /* ExperimentCameraInputSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = A18E13572B7CAFF4006EB323 /* ExperimentCameraInputSession.swift */; }; + A18E135A2B7F563B006EB323 /* CameraUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A18E13592B7F563B006EB323 /* CameraUIView.swift */; }; A1A6A72F297685DF00090309 /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1A6A72E297685DF00090309 /* Utility.swift */; }; A1A80D3D29AE049E00AD756D /* SettingsBundleHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1A80D3C29AE049E00AD756D /* SettingsBundleHelper.swift */; }; A1B9C1BE2AA9E59D00238AF6 /* CameraViewDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B9C1BD2AA9E59D00238AF6 /* CameraViewDescriptor.swift */; }; @@ -504,6 +506,8 @@ A182CEE02B025B53000385D2 /* CameraService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraService.swift; sourceTree = ""; }; A182CEE22B025B6C000385D2 /* CameraInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraInput.swift; sourceTree = ""; }; A182CEE62B02DF10000385D2 /* CameraService+Enums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraService+Enums.swift"; sourceTree = ""; }; + A18E13572B7CAFF4006EB323 /* ExperimentCameraInputSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperimentCameraInputSession.swift; sourceTree = ""; }; + A18E13592B7F563B006EB323 /* CameraUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraUIView.swift; sourceTree = ""; }; A1A6A72E297685DF00090309 /* Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = ""; }; A1A80D3C29AE049E00AD756D /* SettingsBundleHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsBundleHelper.swift; sourceTree = ""; }; A1B9C1BD2AA9E59D00238AF6 /* CameraViewDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraViewDescriptor.swift; sourceTree = ""; }; @@ -1145,6 +1149,8 @@ A15891EF2B10BFD000A169C3 /* MetalView.swift */, A15891F12B10BFED00A169C3 /* MetalRenderer.swift */, A14B3A462B7AD2960016E380 /* ExperimentCameraInput.swift */, + A18E13572B7CAFF4006EB323 /* ExperimentCameraInputSession.swift */, + A18E13592B7F563B006EB323 /* CameraUIView.swift */, ); path = Camera; sourceTree = ""; @@ -1530,6 +1536,7 @@ C063C662238ECCD000327AE7 /* NetworkDiscovery.swift in Sources */, 6B63C8E41CC14A96007DC09B /* WebServerUtilities.swift in Sources */, 6B0C71501C1C6FFE00500FEE /* GraphViewDescriptor.swift in Sources */, + A18E13582B7CAFF4006EB323 /* ExperimentCameraInputSession.swift in Sources */, 6B6319681C48457B0038BE22 /* CollectionViewController.swift in Sources */, 6BAEC1DE1C1F749000FBD979 /* ValueViewDescriptor.swift in Sources */, 6B06C2161CC770200030BB7D /* ExperimentWebServer.swift in Sources */, @@ -1576,6 +1583,7 @@ 6BB52BC61C1B11CA00217CF1 /* ExperimentTranslation.swift in Sources */, 6BE320AA1CB2B55700DCC1ED /* TextFieldTableViewCell.swift in Sources */, 6B7BF80D1C14723D007408FB /* CosAnalysis.swift in Sources */, + A18E135A2B7F563B006EB323 /* CameraUIView.swift in Sources */, 6BE320A11CB19E0300DCC1ED /* CreateViewControllerTransition.swift in Sources */, C0A3BE5B2390070900951C23 /* NetworkElementHandler.swift in Sources */, A1B9C1C02AA9E76C00238AF6 /* CameraViewElementHandler.swift in Sources */, diff --git a/phyphox-iOS/phyphox/Camera/CameraModel.swift b/phyphox-iOS/phyphox/Camera/CameraModel.swift index 7c57c6ec..080f344e 100644 --- a/phyphox-iOS/phyphox/Camera/CameraModel.swift +++ b/phyphox-iOS/phyphox/Camera/CameraModel.swift @@ -11,6 +11,24 @@ import AVFoundation import MetalKit +protocol CameraSelectionDelegate { + var x1: Float { get set } + var x2: Float { get set } + var y1: Float { get set } + var y2: Float { get set } + var timeReference: ExperimentTimeReference? { get set } + var zBuffer: DataBuffer? { get set } + var tBuffer: DataBuffer? { get set } +} + +@available(iOS 14.0, *) +protocol CameraViewDelegate { + var metalView: CameraMetalView { get set } + var metalRenderer: MetalRenderer { get set } + var cameraSettingsModel : CameraSettingsModel { get set } +} + + @available(iOS 14.0, *) class CameraSettingsModel: ObservableObject { @@ -135,17 +153,14 @@ class CameraSettingsModel: ObservableObject { @available(iOS 14.0, *) -final class CameraModel: ObservableObject{ +final class CameraModel: ObservableObject, CameraViewDelegate, CameraSelectionDelegate{ + var x1: Float = 0.4 var x2: Float = 0.6 var y1: Float = 0.4 var y2: Float = 0.6 - var panningIndexX: Int = 0 - var panningIndexY: Int = 0 - - var modelGesture: GestureState = .none private let service = CameraService() var metalView = CameraMetalView() @@ -154,6 +169,10 @@ final class CameraModel: ObservableObject{ var metalRenderer: MetalRenderer var session: AVCaptureSession + var timeReference: ExperimentTimeReference? + var zBuffer: DataBuffer? + var tBuffer: DataBuffer? + /// The app's default camera. var defaultCamera: AVCaptureDevice? { @@ -187,6 +206,10 @@ final class CameraModel: ObservableObject{ self.metalRenderer = MetalRenderer(parent: metalView, renderer: metalView.metalView) cameraSettingsModel = CameraSettingsModel(service: service) + + configure() + + initModel(model: self) } @@ -215,70 +238,7 @@ final class CameraModel: ObservableObject{ return service.image! } - - func pannned (locationY: CGFloat, locationX: CGFloat , state: GestureState) { - let pr = CGPoint(x: locationX / metalView.metalView.frame.width, y: locationY / metalView.metalView.frame.height) - let ps = pr.applying(metalRenderer.displayToCameraTransform) - let x = Float(ps.x) - let y = Float(ps.y) - - if state == .begin { - let d11 = (x - x1)*(x - x1) + (y - y1)*(y - y1) - let d12 = (x - x1)*(x - x1) + (y - y2)*(y - y2) - let d21 = (x - x2)*(x - x2) + (y - y1)*(y - y1) - let d22 = (x - x2)*(x - x2) + (y - y2)*(y - y2) - - let dist:Float = 0.1 // it was 0.01 for depth, after removing it from if else, it worked. Need to come again for this - if d11 < d12 && d11 < d21 && d11 < d22 { - panningIndexX = 1 - panningIndexY = 1 - } else if d12 < d21 && d12 < d22 { - panningIndexX = 1 - panningIndexY = 2 - } else if d21 < d22 { - panningIndexX = 2 - panningIndexY = 1 - } else { - panningIndexX = 2 - panningIndexY = 2 - } - - if panningIndexX == 1 { - x1 = x - } else if panningIndexX == 2 { - x2 = x - } - if panningIndexY == 1 { - y1 = y - } else if panningIndexY == 2 { - y2 = y - } - - } else if state == .end { - if panningIndexX == 1 { - x1 = x - } else if panningIndexX == 2 { - x2 = x - } - if panningIndexY == 1 { - y1 = y - } else if panningIndexY == 2 { - y2 = y - } - - } else { - - } - } - // 136 166 - // d11 = 136 - 0.4 * 136 - 0.4 + 166 - 0.4 * 166 - 0.4 - - enum GestureState { - case begin - case end - case none - } - + } diff --git a/phyphox-iOS/phyphox/Camera/CameraService.swift b/phyphox-iOS/phyphox/Camera/CameraService.swift index eb1ffe6a..4cc50799 100644 --- a/phyphox-iOS/phyphox/Camera/CameraService.swift +++ b/phyphox-iOS/phyphox/Camera/CameraService.swift @@ -5,7 +5,7 @@ // Created by Gaurav Tripathee on 13.11.23. // Copyright © 2023 RWTH Aachen. All rights reserved. // -//263a +// import AVFoundation @@ -571,13 +571,15 @@ func changeExpoDuration(){ public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { + var time = CMSampleBufferGetDuration(sampleBuffer) + guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } self.metalRender?.updateFrame(imageBuffer: imageBuffer, selectionState: MetalRenderer.SelectionStruct( - x1: cameraModel?.x1 ?? 0, x2: cameraModel?.x2 ?? 0, y1: cameraModel?.y1 ?? 0, y2: cameraModel?.y2 ?? 0, editable: true)) + x1: cameraModel?.x1 ?? 0, x2: cameraModel?.x2 ?? 0, y1: cameraModel?.y1 ?? 0, y2: cameraModel?.y2 ?? 0, editable: true), time: time.seconds) } diff --git a/phyphox-iOS/phyphox/Camera/CameraUIView.swift b/phyphox-iOS/phyphox/Camera/CameraUIView.swift new file mode 100644 index 00000000..6224abb3 --- /dev/null +++ b/phyphox-iOS/phyphox/Camera/CameraUIView.swift @@ -0,0 +1,111 @@ +// +// CameraUIView.swift +// phyphox +// +// Created by Gaurav Tripathee on 16.02.24. +// Copyright © 2024 RWTH Aachen. All rights reserved. +// + +import Foundation +import AVFoundation +import SwiftUI + +@available(iOS 14.0, *) +final class ExperimentCameraUIView: UIView { + + private let cameraModel = CameraModel() + private var isMinimized = true + + private var overlayWidth: CGFloat = 50 + private var overlayHeight: CGFloat = 50 + private var scale: CGFloat = 1.0 + private var currentPosition: CGSize = .zero + private var newPosition: CGSize = .zero + private var height: CGFloat = 200.0 + private var width: CGFloat = 200.0 + private var startPosition: CGFloat = 0.0 + private var speed = 50.0 + private var viewState = CGPoint.zero + + required init?(coder: NSCoder) { + super.init(coder: coder) + + addSubview(minimizeCameraButton) + } + + + private var minimizeCameraButton: UIButton { + let minimizedButton = UIButton(type: .system) + minimizedButton.addTarget(self, action: #selector(minimizeButtonTapped), for: .touchUpInside) + + let imageName = isMinimized ? "arrow.down.right.and.arrow.up.left" : "arrow.up.left.and.arrow.down.right" + let image = UIImage(systemName: imageName)?.withRenderingMode(.alwaysOriginal) + + minimizedButton.setImage(image, for: .normal) + minimizedButton.titleLabel?.font = UIFont.systemFont(ofSize: 20) + minimizedButton.tintColor = UIColor(named: "textColor") + minimizedButton.backgroundColor = .black + minimizedButton.contentEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) + + return minimizedButton + } + + @objc private func minimizeButtonTapped(){ + isMinimized.toggle() + } + +} + + +@available(iOS 14.0, *) +final class CameraSettingUIView: UIView { + private var cameraSettingModel = CameraSettingsModel() + + private var cameraSettingMode: CameraSettingMode = .NONE + + private var isEditing = false + private var isZooming = false + + private var rotation: Double = 0.0 + private var isFlipped: Bool = false + private var autoExposureOff : Bool = true + + private var zoomClicked: Bool = false + + private var isListVisible: Bool = false + + + private var flipCameraBUtton: UIButton { + let flipCameraButton = UIButton(type: .system) + flipCameraButton.addTarget(self, action: #selector(flipCameraButtonTapped), for: .touchUpInside) + + + let image = UIImage(named: "flip_camera")?.withRenderingMode(.alwaysOriginal) + + flipCameraButton.setImage(image, for: .normal) + flipCameraButton.imageView?.contentMode = .scaleAspectFit + flipCameraButton.backgroundColor = UIColor.gray.withAlphaComponent(0.0) + + return flipCameraButton + } + + + @objc private func flipCameraButtonTapped() { + cameraSettingMode = .SWITCH_LENS + isListVisible = false + if isFlipped { + withAnimation(Animation.linear(duration: 0.3)) { + self.rotation = -90.0 + } + isFlipped = false + } else { + withAnimation(Animation.linear(duration: 0.3)) { + self.rotation = 90.0 + } + isFlipped = true + } + + // Call your camera setting model method here + cameraSettingModel.switchCamera() + } +} diff --git a/phyphox-iOS/phyphox/Camera/CameraView.swift b/phyphox-iOS/phyphox/Camera/CameraView.swift index 50bb86bf..b0dcce4d 100644 --- a/phyphox-iOS/phyphox/Camera/CameraView.swift +++ b/phyphox-iOS/phyphox/Camera/CameraView.swift @@ -15,7 +15,13 @@ import CoreMedia @available(iOS 14.0, *) struct PhyphoxCameraView: View { - @ObservedObject var cameraModel = CameraModel() + var cameraSelectionDelegate: CameraSelectionDelegate? + var cameraViewDelegete: CameraViewDelegate? + + @State private var panningIndexX: Int = 0 + @State private var panningIndexY: Int = 0 + + @State private var modelGesture: CameraGestureState = .none @State private var isMinimized = true @@ -33,8 +39,7 @@ struct PhyphoxCameraView: View { @State private var speed = 50.0 @State private var viewState = CGPoint.zero - @Environment(\.scenePhase) var scenePhase - + var mimimizeCameraButton: some View { Button(action: { @@ -54,21 +59,18 @@ struct PhyphoxCameraView: View { let dragGesture = DragGesture() .onChanged{ value in viewState = value.location - cameraModel.pannned(locationY: viewState.y, locationX: viewState.x , state: CameraModel.GestureState.begin) - print("dragGesture: onChanged", value) + pannned(locationY: viewState.y, locationX: viewState.x , state: CameraGestureState.begin) } .onEnded{ value in - cameraModel.pannned(locationY: viewState.y, locationX: viewState.x , state: CameraModel.GestureState.end) + pannned(locationY: viewState.y, locationX: viewState.x , state: CameraGestureState.end) self.viewState = .zero - print("dragGesture: onEnded") } GeometryReader { reader in ZStack { - //UIColor(named: "")?.edgesIgnoringSafeArea(.all) - + VStack(alignment: .center, spacing: 5) { HStack(spacing: 0){ @@ -83,16 +85,12 @@ struct PhyphoxCameraView: View { } - + + ZStack{ - cameraModel.metalView + cameraViewDelegete?.metalView .foregroundColor(.gray) - .onAppear{ - cameraModel.configure() - cameraModel.initModel(model: cameraModel) - } - .gesture(dragGesture) .foregroundColor(/*@START_MENU_TOKEN@*/.blue/*@END_MENU_TOKEN@*/) .frame(width: !isMinimized ? reader.size.width / 2 : reader.size.width , @@ -103,18 +101,96 @@ struct PhyphoxCameraView: View { } - CameraSettingView(cameraSettingModel: cameraModel.cameraSettingsModel).opacity(isMinimized ? 1.0 : 0.0) - - - - + CameraSettingView(cameraSettingModel: cameraViewDelegete?.cameraSettingsModel ?? CameraSettingsModel()).opacity(isMinimized ? 1.0 : 0.0) + } - }.onDisappear{ - cameraModel.endSession() } }.background(Color.black) } + + + func pannned (locationY: CGFloat, locationX: CGFloat , state: CameraGestureState) { + + guard var del = cameraSelectionDelegate else { + print("camera selection delegate is not accessable") + return + } + + guard let viewDelegate = cameraViewDelegete else { + print("camera view delegate is not accessable") + return + } + + + + let pr = CGPoint(x: locationX / (viewDelegate.metalView.metalView.frame.width ), y: locationY / (viewDelegate.metalView.metalView.frame.height )) + let ps = pr.applying(viewDelegate.metalRenderer.displayToCameraTransform) + let x = Float(ps.x) + let y = Float(ps.y) + + if state == .begin { + let x1Square = (x - del.x1) * (x - del.x1) + let x2Square = (x - del.x2) * (x - del.x2) + let y1Square = (y - del.y1) * (y - del.y1) + let y2Square = (y - del.y2) * (y - del.y2) + + let d11 = x1Square + y1Square + let d12 = x1Square + y2Square + let d21 = x2Square + y1Square + let d22 = x2Square + y2Square + + let _:Float = 0.1 // it was 0.01 for depth, after removing it from if else, it worked. Need to come again for this + if d11 < d12 && d11 < d21 && d11 < d22 { + panningIndexX = 1 + panningIndexY = 1 + } else if d12 < d21 && d12 < d22 { + panningIndexX = 1 + panningIndexY = 2 + } else if d21 < d22 { + panningIndexX = 2 + panningIndexY = 1 + } else { + panningIndexX = 2 + panningIndexY = 2 + } + + if panningIndexX == 1 { + del.x1 = x + } else if panningIndexX == 2 { + del.x2 = x + } + if panningIndexY == 1 { + del.y1 = y + } else if panningIndexY == 2 { + del.y2 = y + } + + } else if state == .end { + if panningIndexX == 1 { + del.x1 = x + } else if panningIndexX == 2 { + del.x2 = x + } + if panningIndexY == 1 { + del.y1 = y + } else if panningIndexY == 2 { + del.y2 = y + } + + } else { + + } + + + } + + enum CameraGestureState { + case begin + case end + case none + } + } @available(iOS 14.0, *) @@ -438,3 +514,26 @@ struct TextButton: View { } } +@available(iOS 14.0, *) +class PhyphoxCameraHostingController: UIHostingController{ + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented. Use init(rootView:) instead.") + } + + init(){ + super.init(rootView: PhyphoxCameraView()) + } + + override func viewDidLoad() { + super.viewDidLoad() + // Additional setup if needed + } + + func getUIView() -> UIView { + return self.view + } +} + + + + diff --git a/phyphox-iOS/phyphox/Camera/ExperimentCameraInput.swift b/phyphox-iOS/phyphox/Camera/ExperimentCameraInput.swift index 73331675..b4ddfa46 100644 --- a/phyphox-iOS/phyphox/Camera/ExperimentCameraInput.swift +++ b/phyphox-iOS/phyphox/Camera/ExperimentCameraInput.swift @@ -19,8 +19,9 @@ final class ExperimentCameraInput { let zBuffer: DataBuffer? let tBuffer: DataBuffer? + lazy var session: Any? = nil - + init(timeReference: ExperimentTimeReference, zBuffer: DataBuffer?, tBuffer: DataBuffer?, x1: Float, x2: Float, y1: Float, y2: Float, smooth: Bool) { self.initx1 = x1 self.initx2 = x2 @@ -30,12 +31,23 @@ final class ExperimentCameraInput { self.tBuffer = tBuffer self.timeReference = timeReference - print("camera input: initx1: ", x1) - print("camera input: initx2: ", x2) - print("camera input: inity1: ", y1) - print("camera input: inity2: ", y2) + session = ExperimentCameraInputSession() + guard let session = session as? ExperimentCameraInputSession else { + return + } + + session.x1 = x1 + session.x2 = x2 + session.y1 = y1 + session.y2 = y2 + session.zBuffer = zBuffer + session.tBuffer = tBuffer + session.timeReference = timeReference + + session.transferData() } + } extension ExperimentCameraInput: Equatable { diff --git a/phyphox-iOS/phyphox/Camera/ExperimentCameraInputSession.swift b/phyphox-iOS/phyphox/Camera/ExperimentCameraInputSession.swift new file mode 100644 index 00000000..b6ae3fae --- /dev/null +++ b/phyphox-iOS/phyphox/Camera/ExperimentCameraInputSession.swift @@ -0,0 +1,51 @@ +// +// ExperimentCameraInputSession.swift +// phyphox +// +// Created by Gaurav Tripathee on 14.02.24. +// Copyright © 2024 RWTH Aachen. All rights reserved. +// + +import Foundation + +class ExperimentCameraInputSession: NSObject { + var timeReference: ExperimentTimeReference? + + var zBuffer: DataBuffer? + + var tBuffer: DataBuffer? + + var x1: Float = 0.4 + var x2: Float = 0.6 + var y1: Float = 0.4 + var y2: Float = 0.6 + + lazy var cameraModel: Any? = nil + + func transferData(){ + + + if #available(iOS 14.0, *) { + cameraModel = CameraModel() + + guard let cameraModel = cameraModel as? CameraModel else { + return + } + + cameraModel.x1 = x1 + cameraModel.x2 = x2 + cameraModel.y1 = y1 + cameraModel.y2 = y2 + + cameraModel.metalRenderer.timeReference = timeReference + cameraModel.metalRenderer.zBuffer = zBuffer + cameraModel.metalRenderer.tBuffer = tBuffer + } else { + // Fallback on earlier versions + } + } + + + + +} diff --git a/phyphox-iOS/phyphox/Camera/MetalRenderer.swift b/phyphox-iOS/phyphox/Camera/MetalRenderer.swift index 821378fd..b7a3a1c9 100644 --- a/phyphox-iOS/phyphox/Camera/MetalRenderer.swift +++ b/phyphox-iOS/phyphox/Camera/MetalRenderer.swift @@ -34,11 +34,6 @@ class MetalRenderer: NSObject, MTKViewDelegate{ let inFlightSemaphore = DispatchSemaphore(value: kMaxBuffersInFlight) - struct SelectionStruct { - var x1, x2, y1, y2: Float - var editable: Bool - } - var displayToCameraTransform: CGAffineTransform = .identity var selectionState = SelectionStruct(x1: 0.4, x2: 0.6, y1: 0.4, y2: 0.6, editable: false) @@ -57,6 +52,11 @@ class MetalRenderer: NSObject, MTKViewDelegate{ private var queue: DispatchQueue? + struct SelectionStruct { + var x1, x2, y1, y2: Float + var editable: Bool + } + init(parent: CameraMetalView ,renderer: MTKView) { print("Metal Renderer : init") @@ -91,7 +91,7 @@ class MetalRenderer: NSObject, MTKViewDelegate{ } - func updateFrame(imageBuffer: CVImageBuffer!, selectionState: SelectionStruct) { + func updateFrame(imageBuffer: CVImageBuffer!, selectionState: SelectionStruct, time: TimeInterval) { if imageBuffer != nil { @@ -100,17 +100,18 @@ class MetalRenderer: NSObject, MTKViewDelegate{ self.selectionState = selectionState - getLuma() + getLuma(time: time) } func start(queue: DispatchQueue) throws { self.queue = queue } - func getLuma(){ + func getLuma(time: Double){ if let pixelBuffer = self.cvImageBuffer{ // Now you can use pixelBuffer as a CVPixelBuffer + var luma = 0 let luminance = 00 @@ -158,6 +159,8 @@ class MetalRenderer: NSObject, MTKViewDelegate{ print("analysis area: ", analysisArea) print("luma value: ", luma) + dataIn(z: Double(luma), time: time) + } else { // If the CVImageBuffer is not a CVPixelBuffer, you may need to perform conversion @@ -165,6 +168,7 @@ class MetalRenderer: NSObject, MTKViewDelegate{ } } + private func writeToBuffers(z: Double, t: TimeInterval) { if let zBuffer = zBuffer { zBuffer.append(z) @@ -176,6 +180,14 @@ class MetalRenderer: NSObject, MTKViewDelegate{ } private func dataIn(z: Double, time: TimeInterval) { + guard let zBuffer = zBuffer else { + print("Error: zBuffer not set") + return + } + guard let tBuffer = tBuffer else { + print("Error: tBuffer not set") + return + } guard let timeReference = timeReference else { print("Error: time reference not set") return diff --git a/phyphox-iOS/phyphox/Experiments/Data-Input/ExperimentDepthInput.swift b/phyphox-iOS/phyphox/Experiments/Data-Input/ExperimentDepthInput.swift index 345cd3a9..cd036635 100644 --- a/phyphox-iOS/phyphox/Experiments/Data-Input/ExperimentDepthInput.swift +++ b/phyphox-iOS/phyphox/Experiments/Data-Input/ExperimentDepthInput.swift @@ -61,7 +61,7 @@ final class ExperimentDepthInput { session.mode = mode session.x1 = x1 session.x2 = x2 - session.y1 = y1 + session.y1 = y1 session.y2 = y2 session.zBuffer = zBuffer session.tBuffer = tBuffer diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 1578f0f6..1853b654 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 13145 + 13187 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift index bcc8bfef..fef1d00f 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift @@ -510,10 +510,25 @@ final class ExperimentPageViewController: UIViewController, UIPageViewController depthGUI.depthGUISelectionDelegate = session } - } - + + if let cameraUI = view as? _UIHostingView{ + guard let session = experiment.cameraInput?.session as? ExperimentCameraInputSession else { + continue + } + + // TODO remove the dependability with iOS 16 + if #available(iOS 16.0, *) { + print("race : viewDidApprear") + cameraUI.rootView.cameraSelectionDelegate = session.cameraModel as? any CameraSelectionDelegate + cameraUI.rootView.cameraViewDelegete = session.cameraModel as? any CameraViewDelegate + } else { + // Fallback on earlier versions + } + } + } } + } if let networkConnection = experiment.networkConnections.first { var sensorList: [String] = [] diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/ExperimentViewModuleFactory.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/ExperimentViewModuleFactory.swift index 1f653d4b..227c28c4 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/ExperimentViewModuleFactory.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/ExperimentViewModuleFactory.swift @@ -54,7 +54,9 @@ final class ExperimentViewModuleFactory { else if let descriptor = descriptor as? CameraViewDescriptor { if #available(iOS 14.0, *) { // TODO need to pass descriptor in view argument. - views.append(UIHostingController(rootView: PhyphoxCameraView()).view) + let hostingController = PhyphoxCameraHostingController() + views.append(hostingController.getUIView()) + //views.append(UIHostingController(rootView: PhyphoxCameraView()).view) } else { // Fallback on earlier versions } diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentCameraGUIView.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentCameraGUIView.swift index 0215c25b..f8ffe1e7 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentCameraGUIView.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentCameraGUIView.swift @@ -29,7 +29,6 @@ protocol CameraGUISelectionDelegate { final class ExperimentCameraGUIView: UIView, CameraGUIDelegate { var videoPreviewLayer: AVCaptureVideoPreviewLayer? - var depthGUISelectionDelegate: CameraGUISelectionDelegate? var resolution: CGSize? diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentDepthGUIView.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentDepthGUIView.swift index e6a624e8..f0f807e0 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentDepthGUIView.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentDepthGUIView.swift @@ -73,7 +73,7 @@ final class ExperimentDepthGUIView: UIView, DescriptorBoundViewModule, Resizable private let buttonPadding: CGFloat = 20.0 - private let label = UILabel() + private let label = UILabel() private let arView = MTKView() let renderer: ExperimentDepthGUIRenderer private let aggregationBtn = UIButton() From d220e0856dde727171ac468a73b8c851ab323558 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Thu, 22 Feb 2024 16:01:38 +0100 Subject: [PATCH 71/81] feat: control the camera session and analysis of the camera frame --- phyphox-iOS/phyphox/Camera/CameraModel.swift | 10 +++++ .../phyphox/Camera/CameraService.swift | 44 +++++++++--------- .../Camera/ExperimentCameraInput.swift | 32 ++++++++++++- .../Camera/ExperimentCameraInputSession.swift | 45 ++++++++++++++++++- .../phyphox/Camera/MetalRenderer.swift | 6 ++- .../phyphox/Experiments/Experiment.swift | 3 ++ phyphox-iOS/phyphox/Info.plist | 2 +- .../ExperimentPageViewController.swift | 8 ++-- 8 files changed, 121 insertions(+), 29 deletions(-) diff --git a/phyphox-iOS/phyphox/Camera/CameraModel.swift b/phyphox-iOS/phyphox/Camera/CameraModel.swift index 080f344e..d1857784 100644 --- a/phyphox-iOS/phyphox/Camera/CameraModel.swift +++ b/phyphox-iOS/phyphox/Camera/CameraModel.swift @@ -225,6 +225,16 @@ final class CameraModel: ObservableObject, CameraViewDelegate, CameraSelectionDe service.initializeModel(model: model) } + + func startSession(){ + service.metalRender?.measuring = true + } + + + func stopSession(){ + service.metalRender?.measuring = false + } + func endSession(){ service.session.stopRunning() } diff --git a/phyphox-iOS/phyphox/Camera/CameraService.swift b/phyphox-iOS/phyphox/Camera/CameraService.swift index 4cc50799..690573ca 100644 --- a/phyphox-iOS/phyphox/Camera/CameraService.swift +++ b/phyphox-iOS/phyphox/Camera/CameraService.swift @@ -11,7 +11,7 @@ import AVFoundation import MetalKit import CoreMedia - + @available(iOS 14.0, *) public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate { @@ -55,6 +55,8 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega @Published var zoomScale: CGFloat = 1.0 + + // MARK: Device Configuration Properties private let videoDeviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera, .builtInDualCamera, .builtInTrueDepthCamera], mediaType: .video, position: .unspecified) @@ -305,20 +307,20 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega } -func changeExpoDuration(){ - if (defaultVideoDevice?.isExposureModeSupported(.locked) == true){ - do { - try defaultVideoDevice?.lockForConfiguration() - defaultVideoDevice?.exposureMode = .custom - defaultVideoDevice?.unlockForConfiguration() - } catch { - print("Error setting camera zoom: \(error.localizedDescription)") + func changeExpoDuration(){ + if (defaultVideoDevice?.isExposureModeSupported(.locked) == true){ + do { + try defaultVideoDevice?.lockForConfiguration() + defaultVideoDevice?.exposureMode = .custom + defaultVideoDevice?.unlockForConfiguration() + } catch { + print("Error setting camera zoom: \(error.localizedDescription)") + } + } + else { + print("custom exposure setting not supported") } } - else { - print("custom exposure setting not supported") - } -} func setExposureTo(auto: Bool){ do { @@ -571,16 +573,16 @@ func changeExpoDuration(){ public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { - var time = CMSampleBufferGetDuration(sampleBuffer) - - guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { + var time = CMSampleBufferGetDuration(sampleBuffer) - return - } - - self.metalRender?.updateFrame(imageBuffer: imageBuffer, selectionState: MetalRenderer.SelectionStruct( - x1: cameraModel?.x1 ?? 0, x2: cameraModel?.x2 ?? 0, y1: cameraModel?.y1 ?? 0, y2: cameraModel?.y2 ?? 0, editable: true), time: time.seconds) + guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { + + return + } + self.metalRender?.updateFrame(imageBuffer: imageBuffer, selectionState: MetalRenderer.SelectionStruct( + x1: cameraModel?.x1 ?? 0, x2: cameraModel?.x2 ?? 0, y1: cameraModel?.y1 ?? 0, y2: cameraModel?.y2 ?? 0, editable: true), time: time.seconds) + } diff --git a/phyphox-iOS/phyphox/Camera/ExperimentCameraInput.swift b/phyphox-iOS/phyphox/Camera/ExperimentCameraInput.swift index b4ddfa46..578837ef 100644 --- a/phyphox-iOS/phyphox/Camera/ExperimentCameraInput.swift +++ b/phyphox-iOS/phyphox/Camera/ExperimentCameraInput.swift @@ -44,10 +44,40 @@ final class ExperimentCameraInput { session.tBuffer = tBuffer session.timeReference = timeReference - session.transferData() } + func start() throws { + guard #available(iOS 14.0, *) else { + return + } + guard let session = session as? ExperimentCameraInputSession else { + return + } + + session.startSession() + } + + func stop() { + guard #available(iOS 14.0, *) else { + return + } + guard let session = session as? ExperimentCameraInputSession else { + return + } + session.stopSession() + } + + func clear() { + guard #available(iOS 14.0, *) else { + return + } + guard let session = session as? ExperimentCameraInputSession else { + return + } + session.clear() + } + } extension ExperimentCameraInput: Equatable { diff --git a/phyphox-iOS/phyphox/Camera/ExperimentCameraInputSession.swift b/phyphox-iOS/phyphox/Camera/ExperimentCameraInputSession.swift index b6ae3fae..61ed6968 100644 --- a/phyphox-iOS/phyphox/Camera/ExperimentCameraInputSession.swift +++ b/phyphox-iOS/phyphox/Camera/ExperimentCameraInputSession.swift @@ -22,7 +22,7 @@ class ExperimentCameraInputSession: NSObject { lazy var cameraModel: Any? = nil - func transferData(){ + func initializeCameraModel(){ if #available(iOS 14.0, *) { @@ -45,6 +45,49 @@ class ExperimentCameraInputSession: NSObject { } } + func startSession(){ + if #available(iOS 14.0, *) { + guard let cameraModel = cameraModel as? CameraModel else { + return + } + + cameraModel.startSession() + + } else { + // Fallback on earlier versions + } + } + + func stopSession(){ + if #available(iOS 14.0, *) { + guard let cameraModel = cameraModel as? CameraModel else { + return + } + + cameraModel.stopSession() + + } else { + // Fallback on earlier versions + } + } + + func endSession(){ + if #available(iOS 14.0, *) { + guard let cameraModel = cameraModel as? CameraModel else { + return + } + + cameraModel.endSession() + + } else { + // Fallback on earlier versions + } + } + + + func clear() { + + } diff --git a/phyphox-iOS/phyphox/Camera/MetalRenderer.swift b/phyphox-iOS/phyphox/Camera/MetalRenderer.swift index b7a3a1c9..f75649a6 100644 --- a/phyphox-iOS/phyphox/Camera/MetalRenderer.swift +++ b/phyphox-iOS/phyphox/Camera/MetalRenderer.swift @@ -44,6 +44,7 @@ class MetalRenderer: NSObject, MTKViewDelegate{ var cvImageBuffer : CVImageBuffer? + var measuring: Bool = false var timeReference: ExperimentTimeReference? @@ -100,7 +101,10 @@ class MetalRenderer: NSObject, MTKViewDelegate{ self.selectionState = selectionState - getLuma(time: time) + if measuring { + getLuma(time: time) + } + } func start(queue: DispatchQueue) throws { diff --git a/phyphox-iOS/phyphox/Experiments/Experiment.swift b/phyphox-iOS/phyphox/Experiments/Experiment.swift index b0049025..71997016 100644 --- a/phyphox-iOS/phyphox/Experiments/Experiment.swift +++ b/phyphox-iOS/phyphox/Experiments/Experiment.swift @@ -364,6 +364,7 @@ final class Experiment { sensorInputs.forEach{ $0.configureMotionSession() } sensorInputs.forEach { $0.start(queue: queue) } try depthInput?.start(queue: queue) + try cameraInput?.start() gpsInputs.forEach { $0.start(queue: queue) } bluetoothInputs.forEach { $0.start(queue: queue) } networkConnections.forEach { $0.start() } @@ -382,6 +383,7 @@ final class Experiment { sensorInputs.forEach { $0.stop() } depthInput?.stop() + cameraInput?.stop() gpsInputs.forEach { $0.stop() } bluetoothInputs.forEach { $0.stop() } networkConnections.forEach { $0.stop() } @@ -409,6 +411,7 @@ final class Experiment { sensorInputs.forEach { $0.clear() } depthInput?.clear() + cameraInput?.clear() gpsInputs.forEach { $0.clear() } if byUser { diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 1853b654..b461fc55 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 13187 + 13197 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift index fef1d00f..f2efb555 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift @@ -515,7 +515,7 @@ final class ExperimentPageViewController: UIViewController, UIPageViewController guard let session = experiment.cameraInput?.session as? ExperimentCameraInputSession else { continue } - + session.initializeCameraModel() // TODO remove the dependability with iOS 16 if #available(iOS 16.0, *) { print("race : viewDidApprear") @@ -551,9 +551,9 @@ final class ExperimentPageViewController: UIViewController, UIPageViewController session.stopSession() } - //if let camSession = experiment.cameraInput?.session as? ExperimentCameraInputSession { - // camSession.stopSession() - //} + if let camSession = experiment.cameraInput?.session as? ExperimentCameraInputSession { + camSession.endSession() + } } disconnectFromBluetoothDevices() disconnectFromNetworkDevices() From 800b1694502d3fe07289bcc58abda44749b903d9 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Tue, 27 Feb 2024 12:19:04 +0100 Subject: [PATCH 72/81] fix: show the continuous values of lum in the graph and value field --- phyphox-iOS/CocoaMQTT | 1 + phyphox-iOS/phyphox/Camera/CameraService.swift | 7 +++---- phyphox-iOS/phyphox/Camera/MetalRenderer.swift | 11 +++-------- phyphox-iOS/phyphox/Info.plist | 2 +- phyphox-iOS/phyphox/en.lproj/Localizable.strings | 1 + 5 files changed, 9 insertions(+), 13 deletions(-) create mode 160000 phyphox-iOS/CocoaMQTT diff --git a/phyphox-iOS/CocoaMQTT b/phyphox-iOS/CocoaMQTT new file mode 160000 index 00000000..0b5c50b9 --- /dev/null +++ b/phyphox-iOS/CocoaMQTT @@ -0,0 +1 @@ +Subproject commit 0b5c50b90d55e264ca837934395c3ec13929c32f diff --git a/phyphox-iOS/phyphox/Camera/CameraService.swift b/phyphox-iOS/phyphox/Camera/CameraService.swift index 690573ca..74492102 100644 --- a/phyphox-iOS/phyphox/Camera/CameraService.swift +++ b/phyphox-iOS/phyphox/Camera/CameraService.swift @@ -567,21 +567,20 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega public func captureOutput(_ output: AVCaptureOutput, didDrop sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { let totalSampleSize = CMSampleBufferGetTotalSampleSize(sampleBuffer) - } public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { - var time = CMSampleBufferGetDuration(sampleBuffer) + var presentationTimestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer) + let seconds = CMTimeGetSeconds(presentationTimestamp) guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { - return } self.metalRender?.updateFrame(imageBuffer: imageBuffer, selectionState: MetalRenderer.SelectionStruct( - x1: cameraModel?.x1 ?? 0, x2: cameraModel?.x2 ?? 0, y1: cameraModel?.y1 ?? 0, y2: cameraModel?.y2 ?? 0, editable: true), time: time.seconds) + x1: cameraModel?.x1 ?? 0, x2: cameraModel?.x2 ?? 0, y1: cameraModel?.y1 ?? 0, y2: cameraModel?.y2 ?? 0, editable: true), time: seconds) } diff --git a/phyphox-iOS/phyphox/Camera/MetalRenderer.swift b/phyphox-iOS/phyphox/Camera/MetalRenderer.swift index f75649a6..3f504c4a 100644 --- a/phyphox-iOS/phyphox/Camera/MetalRenderer.swift +++ b/phyphox-iOS/phyphox/Camera/MetalRenderer.swift @@ -60,7 +60,6 @@ class MetalRenderer: NSObject, MTKViewDelegate{ init(parent: CameraMetalView ,renderer: MTKView) { - print("Metal Renderer : init") self.renderDestination = renderer @@ -88,7 +87,6 @@ class MetalRenderer: NSObject, MTKViewDelegate{ func drawRectResized(size: CGSize) { viewportSize = size viewportSizeDidChange = true - print("Metal Renderer: drawRectResized") } @@ -101,7 +99,7 @@ class MetalRenderer: NSObject, MTKViewDelegate{ self.selectionState = selectionState - if measuring { + if measuring { getLuma(time: time) } @@ -113,13 +111,10 @@ class MetalRenderer: NSObject, MTKViewDelegate{ func getLuma(time: Double){ if let pixelBuffer = self.cvImageBuffer{ - // Now you can use pixelBuffer as a CVPixelBuffer - var luma = 0 let luminance = 00 - //TODO croping image as per the overlay CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags.readOnly) let lumaBaseAddress = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0) let lumaWidth = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0) @@ -160,8 +155,6 @@ class MetalRenderer: NSObject, MTKViewDelegate{ let analysisArea = (xmax - xmin) * (ymax - ymin) luma /= analysisArea * 255 - print("analysis area: ", analysisArea) - print("luma value: ", luma) dataIn(z: Double(luma), time: time) @@ -196,7 +189,9 @@ class MetalRenderer: NSObject, MTKViewDelegate{ print("Error: time reference not set") return } + let t = timeReference.getExperimentTimeFromEvent(eventTime: time) + if t >= timeReference.timeMappings.last?.experimentTime ?? 0.0 { self.writeToBuffers(z: z, t: t) } diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index b461fc55..26327fe3 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 13197 + 13216 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace diff --git a/phyphox-iOS/phyphox/en.lproj/Localizable.strings b/phyphox-iOS/phyphox/en.lproj/Localizable.strings index bcee3e88..e4aa50e9 100644 --- a/phyphox-iOS/phyphox/en.lproj/Localizable.strings +++ b/phyphox-iOS/phyphox/en.lproj/Localizable.strings @@ -279,6 +279,7 @@ "common_quantity_short_latitude" = "φ"; "common_quantity_short_longitude" = "λ"; "common_quantity_short_altitude" = "z"; +"common_quantity_short_luminance" = "Lv"; "common_direction_short_north" = "N"; "common_direction_short_south" = "S"; "common_direction_short_east" = "E"; From 1b5a3147ec204527495ffce7244978d5fb2e2af5 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Thu, 29 Feb 2024 15:48:29 +0100 Subject: [PATCH 73/81] feat: show the camera setting buttons as per the exposure setting levels --- phyphox-iOS/phyphox/Camera/CameraModel.swift | 16 +- phyphox-iOS/phyphox/Camera/CameraView.swift | 284 ++++++++---------- .../Camera/ExperimentCameraInput.swift | 19 +- .../Camera/ExperimentCameraInputSession.swift | 8 + .../Handlers/InputElementHandler.swift | 23 +- .../Handlers/PhyphoxElementHandler.swift | 2 +- phyphox-iOS/phyphox/Info.plist | 2 +- 7 files changed, 194 insertions(+), 160 deletions(-) diff --git a/phyphox-iOS/phyphox/Camera/CameraModel.swift b/phyphox-iOS/phyphox/Camera/CameraModel.swift index d1857784..32cb03b4 100644 --- a/phyphox-iOS/phyphox/Camera/CameraModel.swift +++ b/phyphox-iOS/phyphox/Camera/CameraModel.swift @@ -19,6 +19,7 @@ protocol CameraSelectionDelegate { var timeReference: ExperimentTimeReference? { get set } var zBuffer: DataBuffer? { get set } var tBuffer: DataBuffer? { get set } + var exposureSettingLevel: Int { get set } } @available(iOS 14.0, *) @@ -29,13 +30,22 @@ protocol CameraViewDelegate { } +protocol CameraSettingDelegate { + var exposureSettingLevel: Int { get set } + var locked: String { get set } +} + + @available(iOS 14.0, *) -class CameraSettingsModel: ObservableObject { +class CameraSettingsModel: ObservableObject, CameraSettingDelegate { var cameraSettingLevel: CameraSettingLevel = .ADVANCE var cameraSettingMode: CameraSettingMode = .NONE + var exposureSettingLevel: Int = 0 + var locked: String = "" + @Published var maxOpticalZoom: Int = 1 var ultraWideCamera: Bool = false @Published var minZoom: Int = 1 @@ -154,13 +164,13 @@ class CameraSettingsModel: ObservableObject { @available(iOS 14.0, *) final class CameraModel: ObservableObject, CameraViewDelegate, CameraSelectionDelegate{ - - + var x1: Float = 0.4 var x2: Float = 0.6 var y1: Float = 0.4 var y2: Float = 0.6 + var exposureSettingLevel: Int = 3 private let service = CameraService() var metalView = CameraMetalView() diff --git a/phyphox-iOS/phyphox/Camera/CameraView.swift b/phyphox-iOS/phyphox/Camera/CameraView.swift index b0dcce4d..525738be 100644 --- a/phyphox-iOS/phyphox/Camera/CameraView.swift +++ b/phyphox-iOS/phyphox/Camera/CameraView.swift @@ -101,7 +101,10 @@ struct PhyphoxCameraView: View { } - CameraSettingView(cameraSettingModel: cameraViewDelegete?.cameraSettingsModel ?? CameraSettingsModel()).opacity(isMinimized ? 1.0 : 0.0) + CameraSettingView( + cameraSettingModel: cameraViewDelegete?.cameraSettingsModel ?? CameraSettingsModel(), + exposureSettingLevel: cameraSelectionDelegate?.exposureSettingLevel ?? 0 + ).opacity(isMinimized ? 1.0 : 0.0) } @@ -193,9 +196,37 @@ struct PhyphoxCameraView: View { } + +@available(iOS 14.0, *) +struct CameraSettingButton: View { + let action: () -> Void + let image: String + let size: CGFloat + + var body: some View { + + Button(action: { + action() + }){ + Circle() + .foregroundColor(Color.gray.opacity(0.0)) + .frame(width: 45, height: 45, alignment: .center) + .overlay( + Image(image) + .resizable() + .scaledToFit() + .frame(width: size, height: size) + .clipShape(Circle()) + ) + } + + } +} + @available(iOS 14.0, *) struct CameraSettingView: View { @ObservedObject var cameraSettingModel = CameraSettingsModel() + var exposureSettingLevel: Int @State private var cameraSettingMode: CameraSettingMode = .NONE @@ -210,9 +241,11 @@ struct CameraSettingView: View { @State private var isListVisible: Bool = false + @State private var zoomScale: CGFloat = 1.0 + var flipCameraButton: some View { - Button(action: { + CameraSettingButton(action: { cameraSettingMode = .SWITCH_LENS isListVisible = false if(isFlipped){ @@ -228,196 +261,141 @@ struct CameraSettingView: View { } cameraSettingModel.switchCamera() - }, label: { - Circle() - .foregroundColor(Color.gray.opacity(0.0)) - .frame(width: 45, height: 45, alignment: .center) - .overlay( - Image("flip_camera") - .resizable() - .scaledToFit() - .frame(width: 30, height: 30) - .rotationEffect(.degrees(rotation)) - .clipShape(/*@START_MENU_TOKEN@*/Circle()/*@END_MENU_TOKEN@*/) - ) - - }) + }, image: "flip_camera", size: 30).rotationEffect(.degrees(rotation)) } - @State private var zoomScale: CGFloat = 1.0 - var zoomSlider: some View { - - Button(action: { + CameraSettingButton(action: { cameraSettingMode = .ZOOM isListVisible.toggle() zoomClicked = !zoomClicked - }, label: { - Circle() - .foregroundColor(Color.gray.opacity(0.0)) - .frame(width: 45, height: 45, alignment: .center) - .overlay( - Image("ic_zoom") - .resizable() - .scaledToFit() - .frame(width: 30, height: 30) - .clipShape(/*@START_MENU_TOKEN@*/Circle()/*@END_MENU_TOKEN@*/)) - - }) + }, image: "ic_zoom", size: 45) } var autoExposure: some View { - Button(action: { - cameraSettingMode = .AUTO_EXPOSURE - autoExposureOff = !autoExposureOff - isListVisible = false - zoomClicked = false - cameraSettingModel.autoExposure(auto: !autoExposureOff) - }, label: { - Circle() - .foregroundColor(Color.gray.opacity(0.0)) - .frame(width: 45, height: 45, alignment: .center) - .overlay( - Image("ic_auto_exposure") - .resizable() - .scaledToFit() - .frame(width: 25, height: 25)) - - }) + CameraSettingButton(action: { + cameraSettingMode = .AUTO_EXPOSURE + autoExposureOff = !autoExposureOff + isListVisible = false + zoomClicked = false + cameraSettingModel.autoExposure(auto: !autoExposureOff) + }, image: "ic_auto_exposure", size: 25) } + var exposureSetting: some View{ + CameraSettingButton(action: { + cameraSettingMode = .EXPOSURE + isListVisible.toggle() + zoomClicked = false + }, image: "ic_exposure", size: 30) + } var isoSetting: some View { - Button(action: { + CameraSettingButton(action: { cameraSettingMode = .ISO isListVisible.toggle() zoomClicked = false - }, label: { - Circle() - .foregroundColor(Color.gray.opacity(0.0)) - .frame(width: 45, height: 45, alignment: .center) - .overlay( - Image("ic_camera_iso") - .resizable() - .scaledToFit() - .frame(width: 30, height: 30) - .clipShape(/*@START_MENU_TOKEN@*/Circle()/*@END_MENU_TOKEN@*/)) - - }) + }, image: "ic_camera_iso", size: 30) } var shutterSpeedSetting: some View { - Button(action: { + CameraSettingButton(action: { cameraSettingMode = .SHUTTER_SPEED isListVisible.toggle() zoomClicked = false - }, label: { - Circle() - .foregroundColor(Color.gray.opacity(0.0)) - .frame(width: 45, height: 45, alignment: .center) - .overlay( - Image("ic_shutter_speed") - .resizable() - .scaledToFit() - .frame(width: 30, height: 30) - .clipShape(/*@START_MENU_TOKEN@*/Circle()/*@END_MENU_TOKEN@*/)) - }) + }, image: "ic_shutter_speed", size: 30) } var apertureSetting: some View { - Button(action: { + CameraSettingButton(action: { cameraSettingMode = .NONE isListVisible = false zoomClicked = false - }, label: { - Circle() - .foregroundColor(Color.gray.opacity(0.0)) - .frame(width: 45, height: 45, alignment: .center) - .overlay( - Image(systemName: "camera.aperture") - .resizable() - .scaledToFit() - .frame(width: 25, height: 25) - .clipShape(/*@START_MENU_TOKEN@*/Circle()/*@END_MENU_TOKEN@*/) - .foregroundColor(.white)) - }) + }, image: "camera.aperture", size: 25) } var body: some View { - HStack { - - VStack(spacing: 0) { - flipCameraButton - .frame(maxWidth: .infinity) - - Text(isFlipped ? "Front" : "Back") - .font(.caption2) - } - - - VStack(spacing: 0) { - autoExposure - .frame(maxWidth: .infinity) - - Text(autoExposureOff ? "Off" : "On") - .font(.caption2) - - } - - - - VStack(spacing: 0) { - - isoSetting - .opacity(autoExposureOff ? 1.0 : 0.4 ) - .frame(maxWidth: .infinity) - .disabled(autoExposureOff ? false : true) - - Text(String(Int(cameraSettingModel.currentIso))).font(.caption2) - .opacity(autoExposureOff ? 1.0 : 0.4 ) - } - - - VStack(spacing: 0) { - - shutterSpeedSetting - .opacity(autoExposureOff ? 1.0 : 0.4 ) - .frame(maxWidth: .infinity) - .disabled(autoExposureOff ? false : true) - let exposureDuration = CMTimeGetSeconds(cameraSettingModel.currentShutterSpeed ?? CMTime(seconds: 30.0, preferredTimescale: 1000)) - Text("1/" + String(Int((1 / exposureDuration)))) - .font(.caption2) - .opacity(autoExposureOff ? 1.0 : 0.4 ) - } + + if(exposureSettingLevel == 0){ + HStack{} + } else { - + HStack { - VStack(spacing: 0) { + VStack { + flipCameraButton.frame(maxWidth: .infinity) + Text(isFlipped ? "Front" : "Back").font(.caption2) + } - apertureSetting - .opacity(autoExposureOff ? 1.0 : 0.4 ) - .frame(maxWidth: .infinity) - .disabled(autoExposureOff ? false : true) + if(exposureSettingLevel == 2){ + VStack { + exposureSetting + .opacity(autoExposureOff ? 1.0 : 0.4 ) + .disabled(autoExposureOff ? false : true).frame(maxWidth: .infinity) + + Text("0.0") + .font(.caption2) + .opacity(autoExposureOff ? 1.0 : 0.4 ) + } + + } - Text("f/" + String(cameraSettingModel.currentApertureValue)) - .font(.caption2) - .opacity(autoExposureOff ? 1.0 : 0.4 ) + if(exposureSettingLevel == 3){ + VStack { + autoExposure.frame(maxWidth: .infinity) + Text(autoExposureOff ? "Off" : "On").font(.caption2) + } + + VStack { + isoSetting + .opacity(autoExposureOff ? 1.0 : 0.4 ) + .disabled(autoExposureOff ? false : true) + .frame(maxWidth: .infinity) + + Text(String(Int(cameraSettingModel.currentIso))) + .font(.caption2) + .opacity(autoExposureOff ? 1.0 : 0.4 ) + } + + + VStack { + + shutterSpeedSetting + .opacity(autoExposureOff ? 1.0 : 0.4 ) + .disabled(autoExposureOff ? false : true) + .frame(maxWidth: .infinity) + + let exposureDuration = CMTimeGetSeconds(cameraSettingModel.currentShutterSpeed ?? CMTime(seconds: 30.0, preferredTimescale: 1000)) + + Text("1/" + String(Int((1 / exposureDuration)))) + .font(.caption2) + .opacity(autoExposureOff ? 1.0 : 0.4 ) + } + + VStack { + + apertureSetting + .opacity(autoExposureOff ? 1.0 : 0.4 ) + .frame(maxWidth: .infinity) + .disabled(autoExposureOff ? false : true) + + Text("f/" + String(cameraSettingModel.currentApertureValue)) + .font(.caption2) + .opacity(autoExposureOff ? 1.0 : 0.4 ) + } + } + + VStack { + zoomSlider.frame(maxWidth: .infinity) + Text("Zoom").font(.caption2) + } } - + .frame(maxWidth: .infinity) + .padding(.horizontal, 1) + .preferredColorScheme(.dark) - VStack(spacing: 0) { - - zoomSlider - .frame(maxWidth: .infinity) - - Text("Zoom").font(.caption2) - } - } - .frame(maxWidth: .infinity) - .padding(.horizontal, 1) - .preferredColorScheme(.dark) VStack { diff --git a/phyphox-iOS/phyphox/Camera/ExperimentCameraInput.swift b/phyphox-iOS/phyphox/Camera/ExperimentCameraInput.swift index 578837ef..3d755c5d 100644 --- a/phyphox-iOS/phyphox/Camera/ExperimentCameraInput.swift +++ b/phyphox-iOS/phyphox/Camera/ExperimentCameraInput.swift @@ -19,10 +19,16 @@ final class ExperimentCameraInput { let zBuffer: DataBuffer? let tBuffer: DataBuffer? + let autoExposure: Bool + let exposureAdjustmentLevel: Int + let locked: String + let feature: String + let analysis: String + lazy var session: Any? = nil - init(timeReference: ExperimentTimeReference, zBuffer: DataBuffer?, tBuffer: DataBuffer?, x1: Float, x2: Float, y1: Float, y2: Float, smooth: Bool) { + init(timeReference: ExperimentTimeReference, zBuffer: DataBuffer?, tBuffer: DataBuffer?, x1: Float, x2: Float, y1: Float, y2: Float, smooth: Bool, autoExposure: Bool, exposureAdjustmentLevel: Int, locked: String, feature: String, analysis: String) { self.initx1 = x1 self.initx2 = x2 self.inity1 = y1 @@ -30,6 +36,11 @@ final class ExperimentCameraInput { self.zBuffer = zBuffer self.tBuffer = tBuffer self.timeReference = timeReference + self.autoExposure = autoExposure + self.exposureAdjustmentLevel = exposureAdjustmentLevel + self.locked = locked + self.feature = feature + self.analysis = analysis session = ExperimentCameraInputSession() guard let session = session as? ExperimentCameraInputSession else { @@ -44,6 +55,12 @@ final class ExperimentCameraInput { session.tBuffer = tBuffer session.timeReference = timeReference + session.autoExposure = autoExposure + session.exposureAdjustmentLevel = exposureAdjustmentLevel + session.locked = locked + session.feature = feature + session.analysis = analysis + } diff --git a/phyphox-iOS/phyphox/Camera/ExperimentCameraInputSession.swift b/phyphox-iOS/phyphox/Camera/ExperimentCameraInputSession.swift index 61ed6968..552e067a 100644 --- a/phyphox-iOS/phyphox/Camera/ExperimentCameraInputSession.swift +++ b/phyphox-iOS/phyphox/Camera/ExperimentCameraInputSession.swift @@ -22,6 +22,12 @@ class ExperimentCameraInputSession: NSObject { lazy var cameraModel: Any? = nil + var autoExposure: Bool = true + var exposureAdjustmentLevel: Int = 0 + var locked: String = "" + var feature: String = "" + var analysis: String = "" + func initializeCameraModel(){ @@ -40,6 +46,8 @@ class ExperimentCameraInputSession: NSObject { cameraModel.metalRenderer.timeReference = timeReference cameraModel.metalRenderer.zBuffer = zBuffer cameraModel.metalRenderer.tBuffer = tBuffer + + cameraModel.exposureSettingLevel = exposureAdjustmentLevel } else { // Fallback on earlier versions } diff --git a/phyphox-iOS/phyphox/Experiments/Serialization/Handlers/InputElementHandler.swift b/phyphox-iOS/phyphox/Experiments/Serialization/Handlers/InputElementHandler.swift index 04c966fe..bb709d4e 100644 --- a/phyphox-iOS/phyphox/Experiments/Serialization/Handlers/InputElementHandler.swift +++ b/phyphox-iOS/phyphox/Experiments/Serialization/Handlers/InputElementHandler.swift @@ -124,6 +124,11 @@ struct CameraInputDescriptor: SensorDescriptor { let y1: Float let y2: Float let smooth: Bool + let autoExposure: Bool + let exposureAdjustmentLevel: Int + let locked: String + let feature: String + let analysis: String let outputs: [SensorOutputDescriptor] } @@ -140,6 +145,7 @@ private final class CameraElementHandler: ResultElementHandler, LookupElementHan func startElement(attributes: AttributeContainer) throws {} + // spelling in the xml should match with it private enum Attribute: String, AttributeKey { case mode case x1 @@ -147,6 +153,11 @@ private final class CameraElementHandler: ResultElementHandler, LookupElementHan case y1 case y2 case smooth + case auto_exposure + case exposure_adjustment_level + case locked + case feature + case analysis } func endElement(text: String, attributes: AttributeContainer) throws { @@ -165,7 +176,17 @@ private final class CameraElementHandler: ResultElementHandler, LookupElementHan let smooth: Bool = try attributes.optionalValue(for: .smooth) ?? true - results.append(CameraInputDescriptor( x1: x1, x2: x2, y1: y1, y2: y2, smooth: smooth, outputs: outputHandler.results)) + let autoExposure: Bool = try attributes.optionalValue(for: .auto_exposure) ?? true + + let exposureAdjustmentLevel: Int = try attributes.optionalValue(for: .exposure_adjustment_level) ?? 3 + + let locked: String = try attributes.optionalValue(for: .locked) ?? "" + + let feature: String = try attributes.optionalString(for: .feature) ?? "" + + let analysis: String = try attributes.optionalString(for: .analysis) ?? "" + + results.append(CameraInputDescriptor(x1: x1, x2: x2, y1: y1, y2: y2, smooth: smooth, autoExposure: autoExposure, exposureAdjustmentLevel: exposureAdjustmentLevel, locked: locked, feature: feature, analysis: analysis, outputs: outputHandler.results)) } } diff --git a/phyphox-iOS/phyphox/Experiments/Serialization/Handlers/PhyphoxElementHandler.swift b/phyphox-iOS/phyphox/Experiments/Serialization/Handlers/PhyphoxElementHandler.swift index 0b483a82..a868e03a 100644 --- a/phyphox-iOS/phyphox/Experiments/Serialization/Handlers/PhyphoxElementHandler.swift +++ b/phyphox-iOS/phyphox/Experiments/Serialization/Handlers/PhyphoxElementHandler.swift @@ -43,7 +43,7 @@ private extension ExperimentCameraInput { let zBuffer = descriptor.buffer(for: "z", from: buffers) let tBuffer = descriptor.buffer(for: "t", from: buffers) - self.init(timeReference: timeReference, zBuffer: zBuffer, tBuffer: tBuffer, x1: descriptor.x1, x2: descriptor.x2, y1: descriptor.y1, y2: descriptor.y2, smooth: descriptor.smooth) + self.init(timeReference: timeReference, zBuffer: zBuffer, tBuffer: tBuffer, x1: descriptor.x1, x2: descriptor.x2, y1: descriptor.y1, y2: descriptor.y2, smooth: descriptor.smooth, autoExposure: descriptor.autoExposure, exposureAdjustmentLevel: descriptor.exposureAdjustmentLevel, locked: descriptor.locked, feature: descriptor.feature, analysis: descriptor.analysis) } } diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 26327fe3..46761f13 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 13216 + 13331 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From e9ab2a65b5c9a61d0cfc8653f8916d49c71cf1d9 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Wed, 13 Mar 2024 12:18:07 +0100 Subject: [PATCH 74/81] fix: disable the dragging gesture when the preview is minimized --- phyphox-iOS/phyphox/Camera/CameraModel.swift | 5 +++- .../phyphox/Camera/CameraService.swift | 2 +- phyphox-iOS/phyphox/Camera/CameraView.swift | 28 +++++++++++-------- phyphox-iOS/phyphox/Info.plist | 2 +- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/phyphox-iOS/phyphox/Camera/CameraModel.swift b/phyphox-iOS/phyphox/Camera/CameraModel.swift index 32cb03b4..4bc4fb5a 100644 --- a/phyphox-iOS/phyphox/Camera/CameraModel.swift +++ b/phyphox-iOS/phyphox/Camera/CameraModel.swift @@ -23,10 +23,11 @@ protocol CameraSelectionDelegate { } @available(iOS 14.0, *) -protocol CameraViewDelegate { +protocol CameraViewDelegate: AnyObject { var metalView: CameraMetalView { get set } var metalRenderer: MetalRenderer { get set } var cameraSettingsModel : CameraSettingsModel { get set } + var isOverlayEditable: Bool { get set } } @@ -183,6 +184,8 @@ final class CameraModel: ObservableObject, CameraViewDelegate, CameraSelectionDe var zBuffer: DataBuffer? var tBuffer: DataBuffer? + var isOverlayEditable: Bool = false + /// The app's default camera. var defaultCamera: AVCaptureDevice? { diff --git a/phyphox-iOS/phyphox/Camera/CameraService.swift b/phyphox-iOS/phyphox/Camera/CameraService.swift index 74492102..a402540f 100644 --- a/phyphox-iOS/phyphox/Camera/CameraService.swift +++ b/phyphox-iOS/phyphox/Camera/CameraService.swift @@ -580,7 +580,7 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega } self.metalRender?.updateFrame(imageBuffer: imageBuffer, selectionState: MetalRenderer.SelectionStruct( - x1: cameraModel?.x1 ?? 0, x2: cameraModel?.x2 ?? 0, y1: cameraModel?.y1 ?? 0, y2: cameraModel?.y2 ?? 0, editable: true), time: seconds) + x1: cameraModel?.x1 ?? 0, x2: cameraModel?.x2 ?? 0, y1: cameraModel?.y1 ?? 0, y2: cameraModel?.y2 ?? 0, editable: cameraModel?.isOverlayEditable ?? true), time: seconds) } diff --git a/phyphox-iOS/phyphox/Camera/CameraView.swift b/phyphox-iOS/phyphox/Camera/CameraView.swift index 525738be..e3ec0fb4 100644 --- a/phyphox-iOS/phyphox/Camera/CameraView.swift +++ b/phyphox-iOS/phyphox/Camera/CameraView.swift @@ -23,7 +23,7 @@ struct PhyphoxCameraView: View { @State private var modelGesture: CameraGestureState = .none - @State private var isMinimized = true + @State private var isMaximized = false @State private var overlayWidth: CGFloat = 50 @State private var overlayHeight: CGFloat = 50 @@ -43,9 +43,11 @@ struct PhyphoxCameraView: View { var mimimizeCameraButton: some View { Button(action: { - self.isMinimized.toggle() + self.isMaximized.toggle() + + self.cameraViewDelegete?.isOverlayEditable.toggle() }, label: { - Image(systemName: isMinimized ? "arrow.down.right.and.arrow.up.left" : "arrow.up.left.and.arrow.down.right") + Image(systemName: isMaximized ? "arrow.down.right.and.arrow.up.left" : "arrow.up.left.and.arrow.down.right") .font(.title2) .padding() .background(Color.black) @@ -58,13 +60,17 @@ struct PhyphoxCameraView: View { let dragGesture = DragGesture() .onChanged{ value in - viewState = value.location - pannned(locationY: viewState.y, locationX: viewState.x , state: CameraGestureState.begin) - + if(isMaximized){ + viewState = value.location + pannned(locationY: viewState.y, locationX: viewState.x , state: CameraGestureState.begin) + } + } .onEnded{ value in - pannned(locationY: viewState.y, locationX: viewState.x , state: CameraGestureState.end) - self.viewState = .zero + if(isMaximized){ + pannned(locationY: viewState.y, locationX: viewState.x , state: CameraGestureState.end) + self.viewState = .zero + } } @@ -93,8 +99,8 @@ struct PhyphoxCameraView: View { .foregroundColor(.gray) .gesture(dragGesture) .foregroundColor(/*@START_MENU_TOKEN@*/.blue/*@END_MENU_TOKEN@*/) - .frame(width: !isMinimized ? reader.size.width / 2 : reader.size.width , - height: !isMinimized ? reader.size.height / 3 : reader.size.height / 1.5, + .frame(width: !isMaximized ? reader.size.width / 2 : reader.size.width , + height: !isMaximized ? reader.size.height / 3 : reader.size.height / 1.5, alignment: .topTrailing) @@ -104,7 +110,7 @@ struct PhyphoxCameraView: View { CameraSettingView( cameraSettingModel: cameraViewDelegete?.cameraSettingsModel ?? CameraSettingsModel(), exposureSettingLevel: cameraSelectionDelegate?.exposureSettingLevel ?? 0 - ).opacity(isMinimized ? 1.0 : 0.0) + ).opacity(isMaximized ? 1.0 : 0.0) } diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 46761f13..a216158d 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 13331 + 13350 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From bead6653063d00a7625a49a7877b886484fe1fd3 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Thu, 4 Apr 2024 15:02:26 +0200 Subject: [PATCH 75/81] fix: remove the ios16 dependency by using uiview as the main parent for cameraview and removing swiftui as parent view --- phyphox-iOS/phyphox/Camera/CameraModel.swift | 11 +- .../phyphox/Camera/CameraService.swift | 48 +++-- phyphox-iOS/phyphox/Camera/CameraUIView.swift | 191 +++++++++++------- phyphox-iOS/phyphox/Camera/CameraView.swift | 120 +++++------ .../Camera/ExperimentCameraInputSession.swift | 8 +- .../phyphox/Camera/MetalRenderer.swift | 16 +- phyphox-iOS/phyphox/Camera/MetalView.swift | 5 +- phyphox-iOS/phyphox/Info.plist | 2 +- .../ExperimentPageViewController.swift | 22 +- .../ExperimentViewModuleFactory.swift | 9 +- ...ExtensionExperimentViewModuleFactory.swift | 42 ++++ .../Static/ExperimentCameraGUIView.swift | 2 +- .../Static/ExperimentDepthGUIView.swift | 4 + 13 files changed, 289 insertions(+), 191 deletions(-) create mode 100644 phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/ExtensionExperimentViewModuleFactory.swift diff --git a/phyphox-iOS/phyphox/Camera/CameraModel.swift b/phyphox-iOS/phyphox/Camera/CameraModel.swift index 4bc4fb5a..40dd0cc2 100644 --- a/phyphox-iOS/phyphox/Camera/CameraModel.swift +++ b/phyphox-iOS/phyphox/Camera/CameraModel.swift @@ -203,14 +203,10 @@ final class CameraModel: ObservableObject, CameraViewDelegate, CameraSelectionDe return device } - // Find the built-in Wide-Angle Camera, if it exists. - if let device = AVCaptureDevice.default(.builtInWideAngleCamera, - for: .video, - position: .back) { - return device - } - return nil + return AVCaptureDevice.default(.builtInWideAngleCamera, + for: .video, + position: .back) } @@ -224,6 +220,7 @@ final class CameraModel: ObservableObject, CameraViewDelegate, CameraSelectionDe initModel(model: self) } + func configure(){ diff --git a/phyphox-iOS/phyphox/Camera/CameraService.swift b/phyphox-iOS/phyphox/Camera/CameraService.swift index a402540f..dd3524fc 100644 --- a/phyphox-iOS/phyphox/Camera/CameraService.swift +++ b/phyphox-iOS/phyphox/Camera/CameraService.swift @@ -56,6 +56,8 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega @Published var zoomScale: CGFloat = 1.0 + let defaultMinExposureCMTime = CMTime(value: 14, timescale: 1000000, flags: CMTimeFlags(rawValue: 1), epoch: 0) + let defaultMaxExposureCMTime = CMTime(value: 1, timescale: 1, flags: CMTimeFlags(rawValue: 1), epoch: 0) // MARK: Device Configuration Properties private let videoDeviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera, .builtInDualCamera, .builtInTrueDepthCamera], mediaType: .video, position: .unspecified) @@ -82,7 +84,7 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega setupResult = .notAuthorized DispatchQueue.main.async { - self.alertError = AlertError(title: "Camera Access", message: "SwiftCamera doesn't have access to use your camera, please update your privacy settings.", primaryButtonTitle: "Settings", secondaryButtonTitle: nil, primaryAction: { + self.alertError = AlertError(title: "Camera Access", message: "Phyphox doesn't have access to use your camera, please update your privacy settings.", primaryButtonTitle: "Settings", secondaryButtonTitle: nil, primaryAction: { UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil) @@ -121,7 +123,7 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega func updateZoom(scale: CGFloat){ lockConfig { () -> () in - defaultVideoDevice?.videoZoomFactor = max(1.0, min(zoomScale, (defaultVideoDevice?.activeFormat.videoMaxZoomFactor)!)) + defaultVideoDevice?.videoZoomFactor = max(1.0, min(zoomScale, (defaultVideoDevice?.activeFormat.videoMaxZoomFactor) ?? 1.0)) zoomScale = scale } } @@ -181,8 +183,8 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega let shutters: [Float] = [1, 2, 4, 8, 15, 30, 60, 125, 250, 500, 1000, 2000, 4000, 8000] var shutters_available: [Float] = [] - let min_seconds = CMTimeGetSeconds((cameraModel?.defaultCamera?.activeFormat.minExposureDuration)!) - let max_seconds = CMTimeGetSeconds((cameraModel?.defaultCamera?.activeFormat.maxExposureDuration)!) + let min_seconds = CMTimeGetSeconds((cameraModel?.defaultCamera?.activeFormat.minExposureDuration) ?? defaultMinExposureCMTime) + let max_seconds = CMTimeGetSeconds((cameraModel?.defaultCamera?.activeFormat.maxExposureDuration) ?? defaultMinExposureCMTime) for one_shutter in shutters { let seconds = 1.0 / Float64(one_shutter) @@ -243,7 +245,7 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega case .SWITCH_LENS: return [] case .ISO: - return isoRange(min: Int((cameraModel?.cameraSettingsModel.minIso)!), max: Int((cameraModel?.cameraSettingsModel.maxIso)!)) + return isoRange(min: Int((cameraModel?.cameraSettingsModel.minIso) ?? 30.0), max: Int((cameraModel?.cameraSettingsModel.maxIso) ?? 100.0)) case .SHUTTER_SPEED: return getShutterSpeedRange() case .WHITE_BAlANCE: @@ -259,15 +261,16 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega cameraModel?.cameraSettingsModel.minIso = cameraModel?.defaultCamera?.activeFormat.minISO ?? 30.0 cameraModel?.cameraSettingsModel.maxIso = cameraModel?.defaultCamera?.activeFormat.maxISO ?? 100.0 - cameraModel?.cameraSettingsModel.minShutterSpeed = CMTimeGetSeconds((cameraModel?.defaultCamera?.activeFormat.minExposureDuration)!) - cameraModel?.cameraSettingsModel.maxShutterSpeed = CMTimeGetSeconds((cameraModel?.defaultCamera?.activeFormat.maxExposureDuration)!) + + cameraModel?.cameraSettingsModel.minShutterSpeed = CMTimeGetSeconds((cameraModel?.defaultCamera?.activeFormat.minExposureDuration) ?? defaultMinExposureCMTime) + cameraModel?.cameraSettingsModel.maxShutterSpeed = CMTimeGetSeconds((cameraModel?.defaultCamera?.activeFormat.maxExposureDuration) ?? defaultMaxExposureCMTime) - cameraModel?.cameraSettingsModel.apertureValue = (cameraModel?.defaultCamera!.lensAperture)! + cameraModel?.cameraSettingsModel.apertureValue = (cameraModel?.defaultCamera?.lensAperture) ?? 1.0 //cameraModel?.cameraSettingsModel.minZoom = (cameraModel?.defaultCamera?.minAvailableVideoZoomFactor.rounded().hashValue)! //cameraModel?.cameraSettingsModel.maxZoom = (cameraModel?.defaultCamera?.maxAvailableVideoZoomFactor.rounded().hashValue)! - cameraModel?.cameraSettingsModel.maxOpticalZoom = cameraModel?.defaultCamera!.virtualDeviceSwitchOverVideoZoomFactors.last?.intValue ?? 1 + cameraModel?.cameraSettingsModel.maxOpticalZoom = cameraModel?.defaultCamera?.virtualDeviceSwitchOverVideoZoomFactors.last?.intValue ?? 1 if(cameraModel?.defaultCamera?.deviceType == AVCaptureDevice.DeviceType.builtInDualWideCamera || cameraModel?.defaultCamera?.deviceType == AVCaptureDevice.DeviceType.builtInTripleCamera){ @@ -277,7 +280,7 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega func changeISO(_ iso: Float) { - let duration_seconds = (cameraModel?.cameraSettingsModel.currentShutterSpeed)! + let duration_seconds = (cameraModel?.cameraSettingsModel.currentShutterSpeed) ?? defaultMinExposureCMTime if (defaultVideoDevice?.isExposureModeSupported(.locked) == true){ lockConfig { () -> () in @@ -361,18 +364,15 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega cameraModel?.cameraSettingsModel.currentApertureValue = defaultVideoDevice?.lensAperture ?? 1.0 cameraModel?.cameraSettingsModel.currentIso = defaultVideoDevice?.iso ?? 30.0 - print("setting deviceType ", defaultCameraType) print("setting iso ", defaultVideoDevice?.iso ?? 30.0) - cameraModel?.cameraSettingsModel.currentShutterSpeed = (defaultVideoDevice?.exposureDuration)! - print("setting shutter", Double((defaultVideoDevice?.exposureDuration.timescale)!)) - - - cameraModel?.cameraSettingsModel.minZoom = Int((defaultVideoDevice?.minAvailableVideoZoomFactor)!) + cameraModel?.cameraSettingsModel.currentShutterSpeed = (defaultVideoDevice?.exposureDuration) + + cameraModel?.cameraSettingsModel.minZoom = Int((defaultVideoDevice?.minAvailableVideoZoomFactor ?? 1.0)) if(defaultVideoDevice?.virtualDeviceSwitchOverVideoZoomFactors.isEmpty == true){ - cameraModel?.cameraSettingsModel.maxZoom = Int((defaultVideoDevice?.maxAvailableVideoZoomFactor)!) / 10 + cameraModel?.cameraSettingsModel.maxZoom = Int((defaultVideoDevice?.maxAvailableVideoZoomFactor ?? 1.0) ) / 10 } else { cameraModel?.cameraSettingsModel.maxZoom = (defaultVideoDevice?.virtualDeviceSwitchOverVideoZoomFactors.last?.intValue ?? 1) * 3 } @@ -389,7 +389,7 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega do { try defaultVideoDevice?.lockForConfiguration() - defaultVideoDevice?.videoZoomFactor = max(1.0, min(zoomScale, (defaultVideoDevice?.activeFormat.videoMaxZoomFactor)!)) + defaultVideoDevice?.videoZoomFactor = max(1.0, min(zoomScale, (defaultVideoDevice?.activeFormat.videoMaxZoomFactor ?? 1.0))) defaultVideoDevice?.unlockForConfiguration() } catch { print("Error setting camera zoom: \(error.localizedDescription)") @@ -415,8 +415,6 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega captureOutput.alwaysDiscardsLateVideoFrames = true - - //captureOutput.videoSettings = let captureSessionQueue = DispatchQueue(label: "CameraSessionQueue", attributes: []) captureOutput.setSampleBufferDelegate(self, queue: captureSessionQueue) @@ -572,14 +570,20 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { - var presentationTimestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer) + let presentationTimestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer) let seconds = CMTimeGetSeconds(presentationTimestamp) guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } - self.metalRender?.updateFrame(imageBuffer: imageBuffer, selectionState: MetalRenderer.SelectionStruct( + let width = CVPixelBufferGetWidth(imageBuffer) + let height = CVPixelBufferGetHeight(imageBuffer) + + print("Image Resolution: \(width)x\(height)") + + + self.metalRender?.updateFrame(imageBuffer: imageBuffer, selectionState: MetalRenderer.SelectionStruct( x1: cameraModel?.x1 ?? 0, x2: cameraModel?.x2 ?? 0, y1: cameraModel?.y1 ?? 0, y2: cameraModel?.y2 ?? 0, editable: cameraModel?.isOverlayEditable ?? true), time: seconds) } diff --git a/phyphox-iOS/phyphox/Camera/CameraUIView.swift b/phyphox-iOS/phyphox/Camera/CameraUIView.swift index 6224abb3..32a2565e 100644 --- a/phyphox-iOS/phyphox/Camera/CameraUIView.swift +++ b/phyphox-iOS/phyphox/Camera/CameraUIView.swift @@ -9,103 +9,140 @@ import Foundation import AVFoundation import SwiftUI +import MetalKit +import Combine + +private enum CameraGestureState { + case begin + case end + case none +} + +@available(iOS 13.0, *) +class CameraUIDataModel: ObservableObject { + @Published var isToggled: Bool = false +} + @available(iOS 14.0, *) -final class ExperimentCameraUIView: UIView { +final class ExperimentCameraUIView: UIView, CameraGUIDelegate { - private let cameraModel = CameraModel() - private var isMinimized = true - - private var overlayWidth: CGFloat = 50 - private var overlayHeight: CGFloat = 50 - private var scale: CGFloat = 1.0 - private var currentPosition: CGSize = .zero - private var newPosition: CGSize = .zero - private var height: CGFloat = 200.0 - private var width: CGFloat = 200.0 - private var startPosition: CGFloat = 0.0 - private var speed = 50.0 - private var viewState = CGPoint.zero - - required init?(coder: NSCoder) { - super.init(coder: coder) - - addSubview(minimizeCameraButton) + func updateFrame(captureSession: AVCaptureSession) { + print("update frame") } - - private var minimizeCameraButton: UIButton { - let minimizedButton = UIButton(type: .system) - minimizedButton.addTarget(self, action: #selector(minimizeButtonTapped), for: .touchUpInside) - - let imageName = isMinimized ? "arrow.down.right.and.arrow.up.left" : "arrow.up.left.and.arrow.down.right" - let image = UIImage(systemName: imageName)?.withRenderingMode(.alwaysOriginal) - - minimizedButton.setImage(image, for: .normal) - minimizedButton.titleLabel?.font = UIFont.systemFont(ofSize: 20) - minimizedButton.tintColor = UIColor(named: "textColor") - minimizedButton.backgroundColor = .black - minimizedButton.contentEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) - - return minimizedButton + func updateResolution(resolution: CGSize) { + setNeedsLayout() } - @objc private func minimizeButtonTapped(){ - isMinimized.toggle() - } -} - + var cameraSelectionDelegate: CameraSelectionDelegate? + var cameraViewDelegete: CameraViewDelegate? -@available(iOS 14.0, *) -final class CameraSettingUIView: UIView { - private var cameraSettingModel = CameraSettingsModel() + let descriptor: CameraViewDescriptor - private var cameraSettingMode: CameraSettingMode = .NONE + let screenWidth = UIScreen.main.bounds.width + let screenHeight = UIScreen.main.bounds.height / 2 - private var isEditing = false - private var isZooming = false + var resizableState: ResizableViewModuleState = .normal - private var rotation: Double = 0.0 - private var isFlipped: Bool = false - private var autoExposureOff : Bool = true + let dataModel = CameraUIDataModel() - private var zoomClicked: Bool = false + private var cancellables = Set() - private var isListVisible: Bool = false + required init?(descriptor: CameraViewDescriptor) { + self.descriptor = descriptor + + super.init(frame: .zero) + + + } + @available(*, unavailable) + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } - private var flipCameraBUtton: UIButton { - let flipCameraButton = UIButton(type: .system) - flipCameraButton.addTarget(self, action: #selector(flipCameraButtonTapped), for: .touchUpInside) + override func sizeThatFits(_ size: CGSize) -> CGSize { + + switch resizableState { + case .exclusive: + print("exclusive") + return size + case .hidden: + print("hidden") + return CGSize.init(width: 0, height: 0) + default: + print("default") + return size + //return size + } + } + + override func layoutSubviews() { + super.layoutSubviews() + + let cameraViewModel = CameraViewModel(cameraUIDataModel: dataModel) - let image = UIImage(named: "flip_camera")?.withRenderingMode(.alwaysOriginal) + let cameraViewHostingController = UIHostingController(rootView: PhyphoxCameraView( + viewModel: cameraViewModel, cameraSelectionDelegate: cameraSelectionDelegate, cameraViewDelegete: cameraViewDelegete + )) + + let hostingController = UIHostingController(rootView: CameraSettingView( + cameraSettingModel: cameraViewDelegete?.cameraSettingsModel ?? CameraSettingsModel(), + exposureSettingLevel: cameraSelectionDelegate?.exposureSettingLevel ?? 0 + )) - flipCameraButton.setImage(image, for: .normal) - flipCameraButton.imageView?.contentMode = .scaleAspectFit - flipCameraButton.backgroundColor = UIColor.gray.withAlphaComponent(0.0) + hostingController.view.translatesAutoresizingMaskIntoConstraints = false + cameraViewHostingController.view.translatesAutoresizingMaskIntoConstraints = false - return flipCameraButton - } - - - @objc private func flipCameraButtonTapped() { - cameraSettingMode = .SWITCH_LENS - isListVisible = false - if isFlipped { - withAnimation(Animation.linear(duration: 0.3)) { - self.rotation = -90.0 - } - isFlipped = false + addSubview(cameraViewHostingController.view) + addSubview(hostingController.view) + + dataModel.objectWillChange.sink{ + [weak self] _ in + print("viewmodel ", self?.dataModel.isToggled) + if(self?.dataModel.isToggled == true){ + hostingController.view.isHidden = true + self?.resizableState = .normal + cameraViewHostingController.view.sizeThatFits(CGSize.init(width: self?.screenWidth ?? 600 , height: 250 )) + self?.setNeedsDisplay() + } else { - withAnimation(Animation.linear(duration: 0.3)) { - self.rotation = 90.0 - } - isFlipped = true + hostingController.view.isHidden = false + self?.resizableState = .exclusive + cameraViewHostingController.view.sizeThatFits(CGSize.init(width: self?.screenWidth ?? 600, height: 860)) + self?.setNeedsDisplay() + + } - - // Call your camera setting model method here - cameraSettingModel.switchCamera() - } + }.store(in: &cancellables) + + var constraints = [NSLayoutConstraint]() + + constraints.append(cameraViewHostingController.view.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor)) + constraints.append(cameraViewHostingController.view.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor)) + constraints.append(cameraViewHostingController.view.bottomAnchor.constraint(equalTo: hostingController.view.topAnchor)) + constraints.append(cameraViewHostingController.view.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor)) + + + constraints.append(hostingController.view.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor)) + constraints.append(hostingController.view.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor)) + constraints.append(hostingController.view.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor)) + constraints.append(hostingController.view.topAnchor.constraint(equalTo: cameraViewHostingController.view.bottomAnchor)) + + constraints.append(hostingController.view.widthAnchor.constraint(equalTo: cameraViewHostingController.view.widthAnchor, multiplier: 1)) + + constraints.append(cameraViewHostingController.view.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.75)) + + constraints.append(cameraViewHostingController.view.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 1)) + + constraints.append(hostingController.view.heightAnchor.constraint(equalTo: cameraViewHostingController.view.heightAnchor, multiplier: 0.30)) + + + NSLayoutConstraint.activate(constraints) + + } + } diff --git a/phyphox-iOS/phyphox/Camera/CameraView.swift b/phyphox-iOS/phyphox/Camera/CameraView.swift index e3ec0fb4..79cdeeee 100644 --- a/phyphox-iOS/phyphox/Camera/CameraView.swift +++ b/phyphox-iOS/phyphox/Camera/CameraView.swift @@ -11,10 +11,21 @@ import SwiftUI import Combine import CoreMedia +@available(iOS 13.0, *) +class CameraViewModel: ObservableObject { + @Published var cameraUIDataModel: CameraUIDataModel + + init(cameraUIDataModel: CameraUIDataModel) { + self.cameraUIDataModel = cameraUIDataModel + } +} + @available(iOS 14.0, *) struct PhyphoxCameraView: View { - + + @ObservedObject var viewModel: CameraViewModel + var cameraSelectionDelegate: CameraSelectionDelegate? var cameraViewDelegete: CameraViewDelegate? @@ -23,7 +34,7 @@ struct PhyphoxCameraView: View { @State private var modelGesture: CameraGestureState = .none - @State private var isMaximized = false + @State var isMaximized = false @State private var overlayWidth: CGFloat = 50 @State private var overlayHeight: CGFloat = 50 @@ -40,11 +51,12 @@ struct PhyphoxCameraView: View { @State private var viewState = CGPoint.zero + var resizableState: ResizableViewModuleState = .normal var mimimizeCameraButton: some View { Button(action: { self.isMaximized.toggle() - + viewModel.cameraUIDataModel.isToggled.toggle() self.cameraViewDelegete?.isOverlayEditable.toggle() }, label: { Image(systemName: isMaximized ? "arrow.down.right.and.arrow.up.left" : "arrow.up.left.and.arrow.down.right") @@ -76,7 +88,7 @@ struct PhyphoxCameraView: View { GeometryReader { reader in ZStack { - + VStack(alignment: .center, spacing: 5) { HStack(spacing: 0){ @@ -89,33 +101,32 @@ struct PhyphoxCameraView: View { Circle().frame(width: 50, height: 50).opacity(0.0) - } - ZStack{ + + /* + Rectangle() + .foregroundColor(.red) + .frame(width: !isMaximized ? reader.size.width / 2.5 : reader.size.width, + height: !isMaximized ? reader.size.height / 2.3 : reader.size.height, + alignment: .center) + */ + cameraViewDelegete?.metalView - .foregroundColor(.gray) .gesture(dragGesture) - .foregroundColor(/*@START_MENU_TOKEN@*/.blue/*@END_MENU_TOKEN@*/) - .frame(width: !isMaximized ? reader.size.width / 2 : reader.size.width , - height: !isMaximized ? reader.size.height / 3 : reader.size.height / 1.5, + .frame(width: !isMaximized ? reader.size.width / 2 : reader.size.width / 1.2 , + height: !isMaximized ? reader.size.height / 2.5 : reader.size.height / 1.2, alignment: .topTrailing) } - - CameraSettingView( - cameraSettingModel: cameraViewDelegete?.cameraSettingsModel ?? CameraSettingsModel(), - exposureSettingLevel: cameraSelectionDelegate?.exposureSettingLevel ?? 0 - ).opacity(isMaximized ? 1.0 : 0.0) - } - } - }.background(Color.black) + }//.background(Color.black) + } } @@ -209,9 +220,11 @@ struct CameraSettingButton: View { let image: String let size: CGFloat + var body: some View { Button(action: { + print("CameraSettingButton") action() }){ Circle() @@ -222,7 +235,6 @@ struct CameraSettingButton: View { .resizable() .scaledToFit() .frame(width: size, height: size) - .clipShape(Circle()) ) } @@ -275,7 +287,7 @@ struct CameraSettingView: View { cameraSettingMode = .ZOOM isListVisible.toggle() zoomClicked = !zoomClicked - }, image: "ic_zoom", size: 45) + }, image: "ic_zoom", size: 30) } var autoExposure: some View { @@ -313,19 +325,42 @@ struct CameraSettingView: View { } var apertureSetting: some View { - CameraSettingButton(action: { + + Button(action: { cameraSettingMode = .NONE isListVisible = false zoomClicked = false - }, image: "camera.aperture", size: 25) + }){ + Circle() + .foregroundColor(Color.gray.opacity(0.0)) + .frame(width: 45, height: 45, alignment: .center) + .overlay( + Image(systemName: "camera.aperture") + .resizable() + .scaledToFit() + .frame(width: 25, height: 25) + .clipShape(Circle()) + ) + } + } var body: some View { + GeometryReader { reader in + let _ = print("setting height", reader.size.width) + let _ = print("setting width", reader.size.width) + } if(exposureSettingLevel == 0){ HStack{} } else { + if(cameraSettingMode == .ISO || cameraSettingMode == .SHUTTER_SPEED){ + Spacer().frame(width: 10.0, height: 40.0) + } else { + Spacer().frame(width: 0.0, height: 0.0) + } + HStack { @@ -397,7 +432,7 @@ struct CameraSettingView: View { Text("Zoom").font(.caption2) } } - .frame(maxWidth: .infinity) + .frame(maxWidth: .infinity, maxHeight: .infinity) .padding(.horizontal, 1) .preferredColorScheme(.dark) @@ -409,6 +444,11 @@ struct CameraSettingView: View { ScrollView(.horizontal, showsIndicators: false) { HStack { + if(cameraSettingMode == .ISO || cameraSettingMode == .SHUTTER_SPEED){ + Spacer().frame(width: 10.0, height: 40.0) + } else { + Spacer().frame(width: 0.0, height: 0.0) + } ForEach(cameraSettingsValues , id: \.self) { title in if(cameraSettingMode == .SHUTTER_SPEED){ TextButton(text: "1/" + String(title)) { @@ -417,7 +457,6 @@ struct CameraSettingView: View { } } else if(cameraSettingMode == .ISO){ TextButton(text: String(title)) { - //cameraSettingModel.exposure(value: Double(title)) cameraSettingModel.iso(value: Float(title)) } @@ -452,18 +491,6 @@ struct CameraSettingView: View { } } - -struct PhyphoxCameraView_Previews: PreviewProvider { - @available(iOS 13.0, *) - static var previews: some View { - if #available(iOS 14.0, *) { - PhyphoxCameraView() - } else { - // Fallback on earlier versions - } - } -} - public struct AlertError { public var title: String = "" public var message: String = "" @@ -498,26 +525,5 @@ struct TextButton: View { } } -@available(iOS 14.0, *) -class PhyphoxCameraHostingController: UIHostingController{ - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented. Use init(rootView:) instead.") - } - - init(){ - super.init(rootView: PhyphoxCameraView()) - } - - override func viewDidLoad() { - super.viewDidLoad() - // Additional setup if needed - } - - func getUIView() -> UIView { - return self.view - } -} - - diff --git a/phyphox-iOS/phyphox/Camera/ExperimentCameraInputSession.swift b/phyphox-iOS/phyphox/Camera/ExperimentCameraInputSession.swift index 552e067a..95ce9a27 100644 --- a/phyphox-iOS/phyphox/Camera/ExperimentCameraInputSession.swift +++ b/phyphox-iOS/phyphox/Camera/ExperimentCameraInputSession.swift @@ -28,6 +28,8 @@ class ExperimentCameraInputSession: NSObject { var feature: String = "" var analysis: String = "" + var delegate : CameraGUIDelegate? + func initializeCameraModel(){ @@ -97,6 +99,10 @@ class ExperimentCameraInputSession: NSObject { } - + + public func attachDelegate(delegate: CameraGUIDelegate) { + self.delegate = delegate + delegate.updateResolution(resolution: CGSize(width: 300, height: 300)) + } } diff --git a/phyphox-iOS/phyphox/Camera/MetalRenderer.swift b/phyphox-iOS/phyphox/Camera/MetalRenderer.swift index 3f504c4a..a51807c2 100644 --- a/phyphox-iOS/phyphox/Camera/MetalRenderer.swift +++ b/phyphox-iOS/phyphox/Camera/MetalRenderer.swift @@ -15,7 +15,7 @@ import Accelerate @available(iOS 13.0, *) class MetalRenderer: NSObject, MTKViewDelegate{ - var metalDevice: MTLDevice! + var metalDevice: MTLDevice? var metalCommandQueue: MTLCommandQueue! var imagePlaneVertexBuffer: MTLBuffer! var renderDestination: MTKView @@ -205,11 +205,11 @@ class MetalRenderer: NSObject, MTKViewDelegate{ // Create a vertex buffer with our image plane vertex data. let imagePlaneVertexDataCount = kImagePlaneVertexData.count * MemoryLayout.size - imagePlaneVertexBuffer = metalDevice.makeBuffer(bytes: kImagePlaneVertexData, length: imagePlaneVertexDataCount, options: []) + imagePlaneVertexBuffer = metalDevice?.makeBuffer(bytes: kImagePlaneVertexData, length: imagePlaneVertexDataCount, options: []) imagePlaneVertexBuffer.label = "ImagePlaneVertexBuffer" // Load all the shader files with a metal file extension in the project. - let defaultLibrary = metalDevice.makeDefaultLibrary()! + let defaultLibrary = metalDevice?.makeDefaultLibrary()! // Create a vertex descriptor for our image plane vertex buffer. let imagePlaneVertexDescriptor = MTLVertexDescriptor() @@ -231,12 +231,12 @@ class MetalRenderer: NSObject, MTKViewDelegate{ // Create camera image texture cache. var textureCache: CVMetalTextureCache? - CVMetalTextureCacheCreate(nil, nil, metalDevice, nil, &textureCache) + CVMetalTextureCacheCreate(nil, nil, metalDevice!, nil, &textureCache) cameraImageTextureCache = textureCache // Define the shaders that will render the camera image on the GPU. - let vertexFunction = defaultLibrary.makeFunction(name: "vertexTransform")! - let fragmentFunction = defaultLibrary.makeFunction(name: "fragmentShader")! + let vertexFunction = defaultLibrary?.makeFunction(name: "vertexTransform")! + let fragmentFunction = defaultLibrary?.makeFunction(name: "fragmentShader")! let pipelineStateDescriptor = MTLRenderPipelineDescriptor() pipelineStateDescriptor.label = "MyPipeline" pipelineStateDescriptor.sampleCount = renderDestination.sampleCount @@ -247,13 +247,13 @@ class MetalRenderer: NSObject, MTKViewDelegate{ // Initialize the pipeline. do { - try pipelineState = metalDevice.makeRenderPipelineState(descriptor: pipelineStateDescriptor) + try pipelineState = metalDevice?.makeRenderPipelineState(descriptor: pipelineStateDescriptor) } catch let error { print("Failed to create pipeline state, error \(error)") } // Create the command queue for one frame of rendering work. - metalCommandQueue = metalDevice.makeCommandQueue() + metalCommandQueue = metalDevice?.makeCommandQueue() diff --git a/phyphox-iOS/phyphox/Camera/MetalView.swift b/phyphox-iOS/phyphox/Camera/MetalView.swift index 60f516b7..f1d087ac 100644 --- a/phyphox-iOS/phyphox/Camera/MetalView.swift +++ b/phyphox-iOS/phyphox/Camera/MetalView.swift @@ -32,7 +32,10 @@ struct CameraMetalView: UIViewRepresentable { metalView.clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0) //Fix: As the imagebuffer width * height is 480 and 180, we need to set the drawable size of MTKView same. Else, the drawable size will be 1080 * 1256 (for example) and due to the large render buffer, the frame starts dropping. TODO: Need to test it and remove the hard dependency. - metalView.drawableSize = CGSize(width: 480, height: 180) + + let h = metalView.drawableSize.height + let w = metalView.drawableSize.width + metalView.drawableSize = CGSize(width: w / 2, height: h / 2) return metalView } diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index a216158d..b9b14965 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 13350 + 13787 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift index f2efb555..e98185ff 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ExperimentPageViewController.swift @@ -202,6 +202,8 @@ final class ExperimentPageViewController: UIViewController, UIPageViewController override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) + + print("view will apprear") if isMovingToParent { experiment.willBecomeActive { DispatchQueue.main.async { @@ -313,6 +315,8 @@ final class ExperimentPageViewController: UIViewController, UIPageViewController override func viewDidLoad() { super.viewDidLoad() + print("View did load") + self.automaticallyAdjustsScrollViewInsets = false self.edgesForExtendedLayout = UIRectEdge() @@ -510,20 +514,16 @@ final class ExperimentPageViewController: UIViewController, UIPageViewController depthGUI.depthGUISelectionDelegate = session } - - if let cameraUI = view as? _UIHostingView{ + if let cameraGUI = view as? ExperimentCameraUIView { guard let session = experiment.cameraInput?.session as? ExperimentCameraInputSession else { continue } session.initializeCameraModel() - // TODO remove the dependability with iOS 16 - if #available(iOS 16.0, *) { - print("race : viewDidApprear") - cameraUI.rootView.cameraSelectionDelegate = session.cameraModel as? any CameraSelectionDelegate - cameraUI.rootView.cameraViewDelegete = session.cameraModel as? any CameraViewDelegate - } else { - // Fallback on earlier versions - } + print("viewDidAppear") + session.attachDelegate(delegate: cameraGUI as! CameraGUIDelegate) + cameraGUI.cameraSelectionDelegate = session.cameraModel as? any CameraSelectionDelegate + cameraGUI.cameraViewDelegete = session.cameraModel as? any CameraViewDelegate + print("experimentViewController") } } @@ -557,7 +557,7 @@ final class ExperimentPageViewController: UIViewController, UIPageViewController } disconnectFromBluetoothDevices() disconnectFromNetworkDevices() - + print("viewDidDisappear") if isMovingFromParent { tearDownWebServer() diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/ExperimentViewModuleFactory.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/ExperimentViewModuleFactory.swift index 227c28c4..b0a9f9d0 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/ExperimentViewModuleFactory.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/ExperimentViewModuleFactory.swift @@ -7,7 +7,6 @@ // import UIKit -import SwiftUI final class ExperimentViewModuleFactory { @@ -50,16 +49,16 @@ final class ExperimentViewModuleFactory { print("DepthGUI not supported below iOS 14") //Should not happen as the depth input is marked as unavailable below iOS 14 } - } - else if let descriptor = descriptor as? CameraViewDescriptor { + } else if let descriptor = descriptor as? CameraViewDescriptor { if #available(iOS 14.0, *) { // TODO need to pass descriptor in view argument. - let hostingController = PhyphoxCameraHostingController() - views.append(hostingController.getUIView()) + + views.append(ExperimentCameraUIView(descriptor: descriptor)) //views.append(UIHostingController(rootView: PhyphoxCameraView()).view) } else { // Fallback on earlier versions } + } else { print("Error! Invalid view descriptor: \(descriptor)") diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/ExtensionExperimentViewModuleFactory.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/ExtensionExperimentViewModuleFactory.swift new file mode 100644 index 00000000..2bdee6b4 --- /dev/null +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/ExtensionExperimentViewModuleFactory.swift @@ -0,0 +1,42 @@ +// +// ExtensionExperimentViewModuleFactory.swift +// phyphox +// +// Created by Gaurav Tripathee on 18.03.24. +// Copyright © 2024 RWTH Aachen. All rights reserved. +// + +import Foundation + +import SwiftUI + +extension ExperimentViewModuleFactory { + + @available(iOS 13.0, *) + class func createSwiftUiViews(_ viewDescriptor: ExperimentViewCollectionDescriptor) -> [UIView] { + + var views: [UIView] = [] + + /** + for descriptor in viewDescriptor.views { + if let descriptor = descriptor as? CameraViewDescriptor { + if #available(iOS 14.0, *) { + // TODO need to pass descriptor in view argument. + let hostingController = UIHostingController(rootView: PhyphoxCameraView()) + views.append(hostingController.view) + //views.append(UIHostingController(rootView: PhyphoxCameraView()).view) + } else { + // Fallback on earlier versions + } + + } else { + print("Error! Invalid view descriptor: \(descriptor)") + } + } + */ + + return views.compactMap { $0 } + + } + +} diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentCameraGUIView.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentCameraGUIView.swift index f8ffe1e7..4f0ef426 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentCameraGUIView.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentCameraGUIView.swift @@ -26,7 +26,7 @@ protocol CameraGUISelectionDelegate { @available(iOS 13.0, *) -final class ExperimentCameraGUIView: UIView, CameraGUIDelegate { +final class ExperimentCameraGUIView: UIView { var videoPreviewLayer: AVCaptureVideoPreviewLayer? diff --git a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentDepthGUIView.swift b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentDepthGUIView.swift index f0f807e0..9f4ea391 100644 --- a/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentDepthGUIView.swift +++ b/phyphox-iOS/phyphox/UI/MainView/ExperimentView/ViewModules/Static/ExperimentDepthGUIView.swift @@ -39,6 +39,7 @@ final class ExperimentDepthGUIView: UIView, DescriptorBoundViewModule, Resizable var depthGUISelectionDelegate: DepthGUISelectionDelegate? func updateResolution(resolution: CGSize) { + print("updateResolution resolution ", resolution) self.resolution = resolution setNeedsLayout() } @@ -191,6 +192,9 @@ final class ExperimentDepthGUIView: UIView, DescriptorBoundViewModule, Resizable w = frame.width - 2*sideMargins h = frame.height - 2*spacing - s.height - buttonH - button2H } + + print("updateResolution w ", w) + print("updateResolution h ", h) arView.frame = CGRect(x: (frame.width - w)/2, y: 2*spacing + s.height, width: w, height: h) if resizableState == .exclusive { aggregationBtn.frame = CGRect(x: (frame.width - buttonS.width)/2, y: 2*spacing + s.height + h + 2*spacing, width: buttonS.width, height: buttonS.height) From 2091b4648553b6a5e89a5b833e1080722ea2cf4a Mon Sep 17 00:00:00 2001 From: GTripathee Date: Sun, 7 Apr 2024 13:40:53 +0200 Subject: [PATCH 76/81] fix: resolve camera setting hiding logic --- phyphox-iOS/phyphox/Camera/CameraUIView.swift | 13 ++++++++++--- phyphox-iOS/phyphox/Camera/CameraView.swift | 10 +++++----- phyphox-iOS/phyphox/Info.plist | 2 +- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/phyphox-iOS/phyphox/Camera/CameraUIView.swift b/phyphox-iOS/phyphox/Camera/CameraUIView.swift index 32a2565e..d0ebda94 100644 --- a/phyphox-iOS/phyphox/Camera/CameraUIView.swift +++ b/phyphox-iOS/phyphox/Camera/CameraUIView.swift @@ -20,7 +20,7 @@ private enum CameraGestureState { @available(iOS 13.0, *) class CameraUIDataModel: ObservableObject { - @Published var isToggled: Bool = false + @Published var cameraIsMaximized: Bool = false } @@ -55,6 +55,7 @@ final class ExperimentCameraUIView: UIView, CameraGUIDelegate { super.init(frame: .zero) + } @@ -99,11 +100,17 @@ final class ExperimentCameraUIView: UIView, CameraGUIDelegate { addSubview(cameraViewHostingController.view) addSubview(hostingController.view) + + if(dataModel.cameraIsMaximized){ + hostingController.view.isHidden = false + } else { + hostingController.view.isHidden = true + } dataModel.objectWillChange.sink{ [weak self] _ in - print("viewmodel ", self?.dataModel.isToggled) - if(self?.dataModel.isToggled == true){ + print("viewmodel ", self?.dataModel.cameraIsMaximized) + if(self?.dataModel.cameraIsMaximized == true){ hostingController.view.isHidden = true self?.resizableState = .normal cameraViewHostingController.view.sizeThatFits(CGSize.init(width: self?.screenWidth ?? 600 , height: 250 )) diff --git a/phyphox-iOS/phyphox/Camera/CameraView.swift b/phyphox-iOS/phyphox/Camera/CameraView.swift index 79cdeeee..196d6c32 100644 --- a/phyphox-iOS/phyphox/Camera/CameraView.swift +++ b/phyphox-iOS/phyphox/Camera/CameraView.swift @@ -56,7 +56,7 @@ struct PhyphoxCameraView: View { var mimimizeCameraButton: some View { Button(action: { self.isMaximized.toggle() - viewModel.cameraUIDataModel.isToggled.toggle() + viewModel.cameraUIDataModel.cameraIsMaximized.toggle() self.cameraViewDelegete?.isOverlayEditable.toggle() }, label: { Image(systemName: isMaximized ? "arrow.down.right.and.arrow.up.left" : "arrow.up.left.and.arrow.down.right") @@ -105,22 +105,22 @@ struct PhyphoxCameraView: View { ZStack{ - /* + Rectangle() .foregroundColor(.red) .frame(width: !isMaximized ? reader.size.width / 2.5 : reader.size.width, height: !isMaximized ? reader.size.height / 2.3 : reader.size.height, alignment: .center) - */ - + + /* cameraViewDelegete?.metalView .gesture(dragGesture) .frame(width: !isMaximized ? reader.size.width / 2 : reader.size.width / 1.2 , height: !isMaximized ? reader.size.height / 2.5 : reader.size.height / 1.2, alignment: .topTrailing) - + */ } } diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index b9b14965..65f0afb1 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 13787 + 13792 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 20ed1982d61f7860248903fa2bb71e39c25ce872 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Mon, 8 Apr 2024 15:21:37 +0200 Subject: [PATCH 77/81] feat: show and change the exposure value when the exposure setting has level two --- phyphox-iOS/phyphox/Camera/CameraModel.swift | 11 ++-- .../phyphox/Camera/CameraService.swift | 55 ++++++++++++++++--- phyphox-iOS/phyphox/Camera/CameraUIView.swift | 19 ++----- phyphox-iOS/phyphox/Camera/CameraView.swift | 20 +++---- phyphox-iOS/phyphox/Info.plist | 2 +- 5 files changed, 68 insertions(+), 39 deletions(-) diff --git a/phyphox-iOS/phyphox/Camera/CameraModel.swift b/phyphox-iOS/phyphox/Camera/CameraModel.swift index 40dd0cc2..b9724c41 100644 --- a/phyphox-iOS/phyphox/Camera/CameraModel.swift +++ b/phyphox-iOS/phyphox/Camera/CameraModel.swift @@ -76,6 +76,8 @@ class CameraSettingsModel: ObservableObject, CameraSettingDelegate { private var zoomScale: Float = 1.0 + var exposureCompensationRange: ClosedRange? + var service: CameraService? @available(iOS 14.0, *) @@ -108,7 +110,7 @@ class CameraSettingsModel: ObservableObject, CameraSettingDelegate { service?.changeCamera() } - func getLisOfCameraSettingsValue(cameraSettingMode: CameraSettingMode) -> [Int] { + func getLisOfCameraSettingsValue(cameraSettingMode: CameraSettingMode) -> [Float] { service?.getSelectableValuesForCameraSettings(cameraSettingMode: cameraSettingMode) ?? [] } @@ -117,7 +119,9 @@ class CameraSettingsModel: ObservableObject, CameraSettingDelegate { service?.setExposureTo(auto: auto) } - func shutterSpeed() {} + func exposure(value: Float) { + service?.changeExposure(value: value) + } func aperture() {} @@ -125,8 +129,7 @@ class CameraSettingsModel: ObservableObject, CameraSettingDelegate { service?.changeISO(value) } - func exposure(value: Double) { - //service?.changeExpoDuration() + func shutterSpeed(value: Double) { service?.changeExposureDuration(value) } diff --git a/phyphox-iOS/phyphox/Camera/CameraService.swift b/phyphox-iOS/phyphox/Camera/CameraService.swift index dd3524fc..68fd7c69 100644 --- a/phyphox-iOS/phyphox/Camera/CameraService.swift +++ b/phyphox-iOS/phyphox/Camera/CameraService.swift @@ -221,7 +221,7 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega return nearestNumber } - func getSelectableValuesForCameraSettings(cameraSettingMode: CameraSettingMode) -> [Int] { + func getSelectableValuesForCameraSettings(cameraSettingMode: CameraSettingMode) -> [Float] { switch cameraSettingMode { case .ZOOM: var zoomList : [Float] = [] @@ -236,18 +236,18 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega } //zoomList.append(Float((cameraModel?.cameraSettingsModel.maxOpticalZoom)! * 3 )) } - return zoomList.map{Int($0)} + return zoomList.map{Float($0)} case .EXPOSURE: - return [] + return getExposureValues() case .AUTO_EXPOSURE: return [] case .SWITCH_LENS: return [] case .ISO: - return isoRange(min: Int((cameraModel?.cameraSettingsModel.minIso) ?? 30.0), max: Int((cameraModel?.cameraSettingsModel.maxIso) ?? 100.0)) + return isoRange(min: Int((cameraModel?.cameraSettingsModel.minIso) ?? 30.0), max: Int((cameraModel?.cameraSettingsModel.maxIso) ?? 100.0)).map{Float($0)} case .SHUTTER_SPEED: - return getShutterSpeedRange() + return getShutterSpeedRange().map{Float($0)} case .WHITE_BAlANCE: return [] case .NONE: @@ -317,7 +317,7 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega defaultVideoDevice?.exposureMode = .custom defaultVideoDevice?.unlockForConfiguration() } catch { - print("Error setting camera zoom: \(error.localizedDescription)") + print("Error setting camera shutter speed: \(error.localizedDescription)") } } else { @@ -331,10 +331,47 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega defaultVideoDevice?.exposureMode = auto ? .autoExpose : .custom defaultVideoDevice?.unlockForConfiguration() } catch { - print("Error setting camera zoom: \(error.localizedDescription)") + print("Error setting camera exposure: \(error.localizedDescription)") } print("is already in autoexposure") } + + func changeExposure(value: Float) { + + if (defaultVideoDevice?.isExposureModeSupported(.locked) == true && defaultVideoDevice?.isExposureModeSupported(.continuousAutoExposure) == true){ + do { + try defaultVideoDevice?.lockForConfiguration() + defaultVideoDevice?.exposureMode = .continuousAutoExposure + defaultVideoDevice!.setExposureTargetBias(value, completionHandler: nil) + defaultVideoDevice?.unlockForConfiguration() + } catch { + print("Error setting camera expsoure: \(error.localizedDescription)") + } + } + else { + print("custom exposure setting not supported") + } + } + + func getExposureValues() -> [Float] { + return getExposureValuesFromRange(min: Int(cameraModel?.cameraSettingsModel.exposureCompensationRange?.lowerBound ?? -8.0), + max: Int(cameraModel?.cameraSettingsModel.exposureCompensationRange?.upperBound ?? 8.0), + step: 1) + } + + func getExposureValuesFromRange(min: Int, max: Int, step: Float) -> [Float] { + var exposureValues = [Float]() + + for value in min...max { + let exposureCompensation = Float(value) * step + let decimalPlaces = 1 + let powerOf10 = pow(10.0, Float(decimalPlaces)) + let roundedNumber = round(exposureCompensation * powerOf10) / powerOf10 + exposureValues.append(roundedNumber) + } + + return exposureValues.filter { (Int($0 * 10) % 5) == 0 } + } private func configureSession(){ @@ -415,6 +452,10 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega captureOutput.alwaysDiscardsLateVideoFrames = true + let minExposure = defaultVideoDevice?.minExposureTargetBias + let maxExposure = defaultVideoDevice?.maxExposureTargetBias + + cameraModel?.cameraSettingsModel.exposureCompensationRange = (minExposure ?? -8.0)...(maxExposure ?? 8.0) let captureSessionQueue = DispatchQueue(label: "CameraSessionQueue", attributes: []) captureOutput.setSampleBufferDelegate(self, queue: captureSessionQueue) diff --git a/phyphox-iOS/phyphox/Camera/CameraUIView.swift b/phyphox-iOS/phyphox/Camera/CameraUIView.swift index d0ebda94..315b55e8 100644 --- a/phyphox-iOS/phyphox/Camera/CameraUIView.swift +++ b/phyphox-iOS/phyphox/Camera/CameraUIView.swift @@ -66,18 +66,7 @@ final class ExperimentCameraUIView: UIView, CameraGUIDelegate { override func sizeThatFits(_ size: CGSize) -> CGSize { - switch resizableState { - case .exclusive: - print("exclusive") - return size - case .hidden: - print("hidden") - return CGSize.init(width: 0, height: 0) - default: - print("default") - return size - //return size - } + return size } @@ -107,19 +96,19 @@ final class ExperimentCameraUIView: UIView, CameraGUIDelegate { hostingController.view.isHidden = true } + // UI Showing the dataModel.objectWillChange.sink{ [weak self] _ in - print("viewmodel ", self?.dataModel.cameraIsMaximized) if(self?.dataModel.cameraIsMaximized == true){ hostingController.view.isHidden = true self?.resizableState = .normal - cameraViewHostingController.view.sizeThatFits(CGSize.init(width: self?.screenWidth ?? 600 , height: 250 )) + cameraViewHostingController.view.sizeThatFits(CGSize.init(width: self?.screenWidth ?? 400 , height: 250 )) self?.setNeedsDisplay() } else { hostingController.view.isHidden = false self?.resizableState = .exclusive - cameraViewHostingController.view.sizeThatFits(CGSize.init(width: self?.screenWidth ?? 600, height: 860)) + cameraViewHostingController.view.sizeThatFits(CGSize.init(width: self?.screenWidth ?? 400, height: 860)) self?.setNeedsDisplay() diff --git a/phyphox-iOS/phyphox/Camera/CameraView.swift b/phyphox-iOS/phyphox/Camera/CameraView.swift index 196d6c32..eb62fc3f 100644 --- a/phyphox-iOS/phyphox/Camera/CameraView.swift +++ b/phyphox-iOS/phyphox/Camera/CameraView.swift @@ -106,21 +106,12 @@ struct PhyphoxCameraView: View { ZStack{ - Rectangle() - .foregroundColor(.red) - .frame(width: !isMaximized ? reader.size.width / 2.5 : reader.size.width, - height: !isMaximized ? reader.size.height / 2.3 : reader.size.height, - alignment: .center) - - - /* cameraViewDelegete?.metalView .gesture(dragGesture) .frame(width: !isMaximized ? reader.size.width / 2 : reader.size.width / 1.2 , height: !isMaximized ? reader.size.height / 2.5 : reader.size.height / 1.2, alignment: .topTrailing) - */ } } @@ -451,15 +442,20 @@ struct CameraSettingView: View { } ForEach(cameraSettingsValues , id: \.self) { title in if(cameraSettingMode == .SHUTTER_SPEED){ - TextButton(text: "1/" + String(title)) { - cameraSettingModel.exposure(value: Double(title)) + TextButton(text: "1/" + String(Int(title))) { + cameraSettingModel.shutterSpeed(value: Double(title)) } } else if(cameraSettingMode == .ISO){ - TextButton(text: String(title)) { + TextButton(text: String(Int(title))) { cameraSettingModel.iso(value: Float(title)) } + } else if(cameraSettingMode == .EXPOSURE){ + TextButton(text: String(title)) { + cameraSettingModel.exposure(value: Float(title)) + + } } } } diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 65f0afb1..1e93af0f 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 13792 + 13804 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From f2456cae6031243adea0c4169f8c79fa7af4e1ba Mon Sep 17 00:00:00 2001 From: GTripathee Date: Mon, 8 Apr 2024 15:41:17 +0200 Subject: [PATCH 78/81] fix: resolve the front camera not changing the camera setting --- phyphox-iOS/phyphox/Camera/CameraService.swift | 3 +-- phyphox-iOS/phyphox/Info.plist | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/phyphox-iOS/phyphox/Camera/CameraService.swift b/phyphox-iOS/phyphox/Camera/CameraService.swift index 68fd7c69..e956ea3f 100644 --- a/phyphox-iOS/phyphox/Camera/CameraService.swift +++ b/phyphox-iOS/phyphox/Camera/CameraService.swift @@ -397,8 +397,6 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega defaultVideoDevice = frontCameraDevice } - - cameraModel?.cameraSettingsModel.currentApertureValue = defaultVideoDevice?.lensAperture ?? 1.0 cameraModel?.cameraSettingsModel.currentIso = defaultVideoDevice?.iso ?? 30.0 @@ -598,6 +596,7 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega } catch { print("Error occurred while creating video device input: \(error)") } + self.defaultVideoDevice = videoDevice } diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 1e93af0f..0facad89 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 13804 + 13805 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From c20b63480f16e9016bd1e5ad423a630fdc624b80 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Tue, 9 Apr 2024 21:13:24 +0200 Subject: [PATCH 79/81] fix: show only the predefined value of the iso in current iso --- phyphox-iOS/phyphox/Camera/CameraModel.swift | 4 +- .../phyphox/Camera/CameraService.swift | 51 ++++++++++++------- phyphox-iOS/phyphox/Camera/CameraView.swift | 4 +- phyphox-iOS/phyphox/Constants.swift | 4 ++ phyphox-iOS/phyphox/Info.plist | 2 +- 5 files changed, 41 insertions(+), 24 deletions(-) diff --git a/phyphox-iOS/phyphox/Camera/CameraModel.swift b/phyphox-iOS/phyphox/Camera/CameraModel.swift index b9724c41..a11eae1d 100644 --- a/phyphox-iOS/phyphox/Camera/CameraModel.swift +++ b/phyphox-iOS/phyphox/Camera/CameraModel.swift @@ -59,7 +59,7 @@ class CameraSettingsModel: ObservableObject, CameraSettingDelegate { var minIso: Float = 30.0 var maxIso: Float = 100.0 - @Published var currentIso: Float = 30.0 + @Published var currentIso: Int = 30 var apertureValue: Float = 1.0 @Published var currentApertureValue: Float = 1.0 @@ -125,7 +125,7 @@ class CameraSettingsModel: ObservableObject, CameraSettingDelegate { func aperture() {} - func iso(value: Float) { + func iso(value: Int) { service?.changeISO(value) } diff --git a/phyphox-iOS/phyphox/Camera/CameraService.swift b/phyphox-iOS/phyphox/Camera/CameraService.swift index e956ea3f..00284ec6 100644 --- a/phyphox-iOS/phyphox/Camera/CameraService.swift +++ b/phyphox-iOS/phyphox/Camera/CameraService.swift @@ -172,15 +172,12 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega } func shutterSpeedRange(min: Int, max: Int) -> [Int] { - let shutterSpeedRange = [1, 2, 4, 8, 15, 30, 60, 125, 250, 500, 1000, 2000, 4000, 8000] - - let filteredShutterSpeedRange = shutterSpeedRange.filter{$0 >= min && $0 <= max} + let filteredShutterSpeedRange = shutters.filter{$0 >= min && $0 <= max} return filteredShutterSpeedRange } func getShutterSpeedRange() -> [Int] { - let shutters: [Float] = [1, 2, 4, 8, 15, 30, 60, 125, 250, 500, 1000, 2000, 4000, 8000] var shutters_available: [Float] = [] let min_seconds = CMTimeGetSeconds((cameraModel?.defaultCamera?.activeFormat.minExposureDuration) ?? defaultMinExposureCMTime) @@ -189,7 +186,7 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega for one_shutter in shutters { let seconds = 1.0 / Float64(one_shutter) if seconds >= min_seconds && seconds <= max_seconds { - shutters_available.append(one_shutter) + shutters_available.append(Float(one_shutter)) } } @@ -197,11 +194,28 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega } + func getNearestValue(value: Int, numbers: [Int]) -> Int{ + + if(numbers.contains(value)){ + return value + } + + for (index, number) in numbers.enumerated() { + if(number > value){ + let average = (number + numbers[index - 1])/2 + if(value > average){ + return number + } else { + return numbers[index - 1] + } + } + } + return value + } + func isoRange(min: Int, max: Int) -> [Int] { - let isoRange = [25, 50, 100, 200, 400, 800, 1600, 3200, 6400, 12800, 25600, 51200] - - let filteredIsoRange = isoRange.filter { $0 >= min && $0 <= max } + let filteredIsoRange = iso.filter { $0 >= min && $0 <= max } return filteredIsoRange } @@ -245,7 +259,8 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega case .SWITCH_LENS: return [] case .ISO: - return isoRange(min: Int((cameraModel?.cameraSettingsModel.minIso) ?? 30.0), max: Int((cameraModel?.cameraSettingsModel.maxIso) ?? 100.0)).map{Float($0)} + return isoRange(min: Int((cameraModel?.cameraSettingsModel.minIso) ?? 30.0), + max: Int((cameraModel?.cameraSettingsModel.maxIso) ?? 100.0)).map{Float($0)} case .SHUTTER_SPEED: return getShutterSpeedRange().map{Float($0)} case .WHITE_BAlANCE: @@ -278,14 +293,14 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega } } - func changeISO(_ iso: Float) { + func changeISO(_ iso: Int) { let duration_seconds = (cameraModel?.cameraSettingsModel.currentShutterSpeed) ?? defaultMinExposureCMTime if (defaultVideoDevice?.isExposureModeSupported(.locked) == true){ lockConfig { () -> () in defaultVideoDevice?.exposureMode = .custom - defaultVideoDevice?.setExposureModeCustom(duration: duration_seconds , iso: iso, completionHandler: nil) + defaultVideoDevice?.setExposureModeCustom(duration: duration_seconds , iso: Float(iso), completionHandler: nil) cameraModel?.cameraSettingsModel.currentIso = iso } @@ -398,9 +413,7 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega } cameraModel?.cameraSettingsModel.currentApertureValue = defaultVideoDevice?.lensAperture ?? 1.0 - cameraModel?.cameraSettingsModel.currentIso = defaultVideoDevice?.iso ?? 30.0 - - print("setting iso ", defaultVideoDevice?.iso ?? 30.0) + cameraModel?.cameraSettingsModel.currentIso = getNearestValue(value: Int(defaultVideoDevice?.iso ?? 30.0), numbers: iso) cameraModel?.cameraSettingsModel.currentShutterSpeed = (defaultVideoDevice?.exposureDuration) @@ -414,6 +427,11 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega cameraModel?.cameraSettingsModel.maxOpticalZoom = defaultVideoDevice?.virtualDeviceSwitchOverVideoZoomFactors.last?.intValue ?? 1 + let minExposure = defaultVideoDevice?.minExposureTargetBias + let maxExposure = defaultVideoDevice?.maxExposureTargetBias + + cameraModel?.cameraSettingsModel.exposureCompensationRange = (minExposure ?? -8.0)...(maxExposure ?? 8.0) + guard let videoDevice = defaultVideoDevice else { print("Default video device is unavailable.") @@ -449,11 +467,6 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega captureOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)] captureOutput.alwaysDiscardsLateVideoFrames = true - - let minExposure = defaultVideoDevice?.minExposureTargetBias - let maxExposure = defaultVideoDevice?.maxExposureTargetBias - - cameraModel?.cameraSettingsModel.exposureCompensationRange = (minExposure ?? -8.0)...(maxExposure ?? 8.0) let captureSessionQueue = DispatchQueue(label: "CameraSessionQueue", attributes: []) captureOutput.setSampleBufferDelegate(self, queue: captureSessionQueue) diff --git a/phyphox-iOS/phyphox/Camera/CameraView.swift b/phyphox-iOS/phyphox/Camera/CameraView.swift index eb62fc3f..b3295f1a 100644 --- a/phyphox-iOS/phyphox/Camera/CameraView.swift +++ b/phyphox-iOS/phyphox/Camera/CameraView.swift @@ -448,12 +448,12 @@ struct CameraSettingView: View { } } else if(cameraSettingMode == .ISO){ TextButton(text: String(Int(title))) { - cameraSettingModel.iso(value: Float(title)) + cameraSettingModel.iso(value: Int(title)) } } else if(cameraSettingMode == .EXPOSURE){ TextButton(text: String(title)) { - cameraSettingModel.exposure(value: Float(title)) + cameraSettingModel.exposure(value: title) } } diff --git a/phyphox-iOS/phyphox/Constants.swift b/phyphox-iOS/phyphox/Constants.swift index 43728d96..4a0fb32d 100644 --- a/phyphox-iOS/phyphox/Constants.swift +++ b/phyphox-iOS/phyphox/Constants.swift @@ -55,6 +55,10 @@ let namedColors = [ "weakwhite": UIColor(red: (196.0/255.0), green: (196.0/255.0), blue: (196.0/255.0), alpha: 1.0) ] +let shutters = [1, 2, 4, 8, 15, 30, 60, 125, 250, 500, 1000, 2000, 4000, 8000] + +let iso = [25, 50, 100, 200, 400, 800, 1600, 3200, 6400, 12800, 25600, 51200] + func mapColorString(_ string: String?) -> UIColor? { guard let colorString = string else { return nil diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 0facad89..6d746b21 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 13805 + 13808 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From ba0b863a050af4fdb2377ddf3b6fcf98eff971cf Mon Sep 17 00:00:00 2001 From: GTripathee Date: Wed, 10 Apr 2024 09:02:32 +0200 Subject: [PATCH 80/81] fix: update the value of camera setting in ui when camera is switched --- phyphox-iOS/phyphox/Camera/CameraModel.swift | 11 - .../phyphox/Camera/CameraService.swift | 202 +++--------------- phyphox-iOS/phyphox/Info.plist | 2 +- 3 files changed, 36 insertions(+), 179 deletions(-) diff --git a/phyphox-iOS/phyphox/Camera/CameraModel.swift b/phyphox-iOS/phyphox/Camera/CameraModel.swift index a11eae1d..ac88d5c5 100644 --- a/phyphox-iOS/phyphox/Camera/CameraModel.swift +++ b/phyphox-iOS/phyphox/Camera/CameraModel.swift @@ -251,17 +251,6 @@ final class CameraModel: ObservableObject, CameraViewDelegate, CameraSelectionDe func endSession(){ service.session.stopRunning() } - - - func getMetalView() -> MTKView { - return service.metalView - } - - func getCIImage() -> CGImage { - return service.image! - } - - } diff --git a/phyphox-iOS/phyphox/Camera/CameraService.swift b/phyphox-iOS/phyphox/Camera/CameraService.swift index 00284ec6..0e813c13 100644 --- a/phyphox-iOS/phyphox/Camera/CameraService.swift +++ b/phyphox-iOS/phyphox/Camera/CameraService.swift @@ -19,8 +19,6 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega private let sessionQueue = DispatchQueue(label: "session queue", attributes: [], autoreleaseFrequency: .workItem) - private let metadataObjectsQueue = DispatchQueue(label: "sample buffer", attributes: []) - @objc dynamic var videoDeviceInput: AVCaptureDeviceInput! var setupResult: SessionSetupResult = .success @@ -41,12 +39,6 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega var metalRender : MetalRenderer? - let metalView: MTKView = MTKView() - - var image: CGImage? - - var flag: Bool = true - var defaultVideoDevice: AVCaptureDevice? var cameraModel: CameraModel? @@ -59,7 +51,6 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega let defaultMinExposureCMTime = CMTime(value: 14, timescale: 1000000, flags: CMTimeFlags(rawValue: 1), epoch: 0) let defaultMaxExposureCMTime = CMTime(value: 1, timescale: 1, flags: CMTimeFlags(rawValue: 1), epoch: 0) - // MARK: Device Configuration Properties private let videoDeviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera, .builtInDualCamera, .builtInTrueDepthCamera], mediaType: .video, position: .unspecified) @@ -121,31 +112,28 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega // optical zoom range, normal zoom range. // iphone 12 mini: Dual 12MP, wide and ultra wide, wide : f/1,6, ultra wide: f/2.4 120 degree field of view, 2x optical zoom out , digital zoom upto 5x, - func updateZoom(scale: CGFloat){ + func updateZoom(scale: CGFloat){ lockConfig { () -> () in defaultVideoDevice?.videoZoomFactor = max(1.0, min(zoomScale, (defaultVideoDevice?.activeFormat.videoMaxZoomFactor) ?? 1.0)) zoomScale = scale } - } + } - func lockConfig(complete: () -> ()) { + func lockConfig(complete: () -> ()) { if isConfigured { configLocked = true do{ try defaultVideoDevice?.lockForConfiguration() complete() defaultVideoDevice?.unlockForConfiguration() - self.postChangeCameraSetting() configLocked = false } catch { configLocked = false } } - } - - func postChangeCameraSetting(){} + } func getAvailableOpticalZoomList(maxOpticalZoom_: Int?) -> [Int] { guard let maxOpticalZoom = maxOpticalZoom_ else { @@ -293,6 +281,30 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega } } + func setCameraSettinginfo(){ + self.cameraModel?.cameraSettingsModel.currentApertureValue = self.defaultVideoDevice?.lensAperture ?? 1.0 + self.cameraModel?.cameraSettingsModel.currentIso = self.getNearestValue(value: Int(self.defaultVideoDevice?.iso ?? 30.0), numbers: iso) + + self.cameraModel?.cameraSettingsModel.currentShutterSpeed = (self.defaultVideoDevice?.exposureDuration) + + self.cameraModel?.cameraSettingsModel.minZoom = Int((self.defaultVideoDevice?.minAvailableVideoZoomFactor ?? 1.0)) + + if(self.defaultVideoDevice?.virtualDeviceSwitchOverVideoZoomFactors.isEmpty == true){ + self.cameraModel?.cameraSettingsModel.maxZoom = Int((self.defaultVideoDevice?.maxAvailableVideoZoomFactor ?? 1.0) ) / 10 + } else { + self.cameraModel?.cameraSettingsModel.maxZoom = (self.defaultVideoDevice?.virtualDeviceSwitchOverVideoZoomFactors.last?.intValue ?? 1) * 3 + } + + self.cameraModel?.cameraSettingsModel.maxOpticalZoom = self.defaultVideoDevice?.virtualDeviceSwitchOverVideoZoomFactors.last?.intValue ?? 1 + + let minExposure = self.defaultVideoDevice?.minExposureTargetBias + let maxExposure = self.defaultVideoDevice?.maxExposureTargetBias + + self.cameraModel?.cameraSettingsModel.exposureCompensationRange = (minExposure ?? -8.0)...(maxExposure ?? 8.0) + + + } + func changeISO(_ iso: Int) { let duration_seconds = (cameraModel?.cameraSettingsModel.currentShutterSpeed) ?? defaultMinExposureCMTime @@ -412,26 +424,7 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega defaultVideoDevice = frontCameraDevice } - cameraModel?.cameraSettingsModel.currentApertureValue = defaultVideoDevice?.lensAperture ?? 1.0 - cameraModel?.cameraSettingsModel.currentIso = getNearestValue(value: Int(defaultVideoDevice?.iso ?? 30.0), numbers: iso) - - cameraModel?.cameraSettingsModel.currentShutterSpeed = (defaultVideoDevice?.exposureDuration) - - cameraModel?.cameraSettingsModel.minZoom = Int((defaultVideoDevice?.minAvailableVideoZoomFactor ?? 1.0)) - - if(defaultVideoDevice?.virtualDeviceSwitchOverVideoZoomFactors.isEmpty == true){ - cameraModel?.cameraSettingsModel.maxZoom = Int((defaultVideoDevice?.maxAvailableVideoZoomFactor ?? 1.0) ) / 10 - } else { - cameraModel?.cameraSettingsModel.maxZoom = (defaultVideoDevice?.virtualDeviceSwitchOverVideoZoomFactors.last?.intValue ?? 1) * 3 - } - - cameraModel?.cameraSettingsModel.maxOpticalZoom = defaultVideoDevice?.virtualDeviceSwitchOverVideoZoomFactors.last?.intValue ?? 1 - - let minExposure = defaultVideoDevice?.minExposureTargetBias - let maxExposure = defaultVideoDevice?.maxExposureTargetBias - - cameraModel?.cameraSettingsModel.exposureCompensationRange = (minExposure ?? -8.0)...(maxExposure ?? 8.0) - + setCameraSettinginfo() guard let videoDevice = defaultVideoDevice else { print("Default video device is unavailable.") @@ -470,7 +463,7 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega let captureSessionQueue = DispatchQueue(label: "CameraSessionQueue", attributes: []) captureOutput.setSampleBufferDelegate(self, queue: captureSessionQueue) - //captureOutput.alwaysDiscardsLateVideoFrames = true + if session.canAddOutput(captureOutput) { session.addOutput(captureOutput) } else { @@ -494,36 +487,6 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega } - private func texture(sampleBuffer: CMSampleBuffer?, textureCache: CVMetalTextureCache?, planeIndex: Int = 0, pixelFormat: MTLPixelFormat = .bgra8Unorm) throws -> MTLTexture { - guard let sampleBuffer = sampleBuffer else { - throw MetalCameraSessionError.missingSampleBuffer - } - guard let textureCache = textureCache else { - throw MetalCameraSessionError.failedToCreateTextureCache - } - guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { - throw MetalCameraSessionError.failedToGetImageBuffer - } - - let isPlanar = CVPixelBufferIsPlanar(imageBuffer) - let width = isPlanar ? CVPixelBufferGetWidthOfPlane(imageBuffer, planeIndex) : CVPixelBufferGetWidth(imageBuffer) - let height = isPlanar ? CVPixelBufferGetHeightOfPlane(imageBuffer, planeIndex) : CVPixelBufferGetHeight(imageBuffer) - - var imageTexture: CVMetalTexture? - - let result = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, textureCache, imageBuffer, nil, pixelFormat, width, height, planeIndex, &imageTexture) - - guard - let unwrappedImageTexture = imageTexture, - let texture = CVMetalTextureGetTexture(unwrappedImageTexture), - result == kCVReturnSuccess - else { - throw MetalCameraSessionError.failedToCreateTextureFromImage - } - - return texture - } - public func start() { // We use our capture session queue to ensure our UI runs smoothly on the main thread. sessionQueue.async { @@ -555,7 +518,6 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega /// - Tag: ChangeCamera public func changeCamera() { - sessionQueue.async { let currentVideoDevice = self.videoDeviceInput.device let currentPosition = currentVideoDevice.position @@ -603,21 +565,21 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega } else { self.session.addInput(self.videoDeviceInput) } - + self.defaultVideoDevice = videoDevice + + self.setCameraSettinginfo() self.session.commitConfiguration() } catch { print("Error occurred while creating video device input: \(error)") } - self.defaultVideoDevice = videoDevice + } - - } } public func captureOutput(_ output: AVCaptureOutput, didDrop sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { - let totalSampleSize = CMSampleBufferGetTotalSampleSize(sampleBuffer) + print("captureOutput didDrop") } @@ -634,104 +596,10 @@ public class CameraService: NSObject, AVCaptureVideoDataOutputSampleBufferDelega let height = CVPixelBufferGetHeight(imageBuffer) print("Image Resolution: \(width)x\(height)") - self.metalRender?.updateFrame(imageBuffer: imageBuffer, selectionState: MetalRenderer.SelectionStruct( x1: cameraModel?.x1 ?? 0, x2: cameraModel?.x2 ?? 0, y1: cameraModel?.y1 ?? 0, y2: cameraModel?.y2 ?? 0, editable: cameraModel?.isOverlayEditable ?? true), time: seconds) } - } - - - - - -public enum MetalCameraSessionState { - case ready - case streaming - case stopped - case waiting - case error -} - -public enum MetalCameraPixelFormat { - case rgb - case yCbCr - - var coreVideoType: OSType { - switch self { - case .rgb: - return kCVPixelFormatType_32BGRA - case .yCbCr: - return kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange - } - } -} - -/** - Streaming error - */ -public enum MetalCameraSessionError: Error { - /** - * Streaming errors - */// - case noHardwareAccess - case failedToAddCaptureInputDevice - case failedToAddCaptureOutput - case requestedHardwareNotFound - case inputDeviceNotAvailable - case captureSessionRuntimeError - - /** - * Conversion errors - */// - case failedToCreateTextureCache - case missingSampleBuffer - case failedToGetImageBuffer - case failedToCreateTextureFromImage - case failedToRetrieveTimestamp - - /** - Indicates if the error is related to streaming the media. - - - returns: True if the error is related to streaming, false otherwise - */ - public func isStreamingError() -> Bool { - switch self { - case .noHardwareAccess, .failedToAddCaptureInputDevice, .failedToAddCaptureOutput, .requestedHardwareNotFound, .inputDeviceNotAvailable, .captureSessionRuntimeError: - return true - default: - return false - } - } - - public var localizedDescription: String { - switch self { - case .noHardwareAccess: - return "Failed to get access to the hardware for a given media type" - case .failedToAddCaptureInputDevice: - return "Failed to add a capture input device to the capture session" - case .failedToAddCaptureOutput: - return "Failed to add a capture output data channel to the capture session" - case .requestedHardwareNotFound: - return "Specified hardware is not available on this device" - case .inputDeviceNotAvailable: - return "Capture input device cannot be opened, probably because it is no longer available or because it is in use" - case .captureSessionRuntimeError: - return "AVCaptureSession runtime error" - case .failedToCreateTextureCache: - return "Failed to initialize texture cache" - case .missingSampleBuffer: - return "No sample buffer to convert the image from" - case .failedToGetImageBuffer: - return "Failed to retrieve an image buffer from camera's output sample buffer" - case .failedToCreateTextureFromImage: - return "Failed to convert the frame to a Metal texture" - case .failedToRetrieveTimestamp: - return "Failed to retrieve timestamp from the sample buffer" - } - } -} - diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index 6d746b21..f589aee6 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 13808 + 13812 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace From 9cb31d63bd25a565c66a828c26eeb824dda238d9 Mon Sep 17 00:00:00 2001 From: GTripathee Date: Thu, 11 Apr 2024 13:06:30 +0200 Subject: [PATCH 81/81] fix: resolve the color for dark and light theme --- phyphox-iOS/phyphox/Camera/CameraView.swift | 9 ++--- phyphox-iOS/phyphox/Info.plist | 2 +- .../Color.colorset/Contents.json | 38 +++++++++++++++++++ .../Image.imageset/Contents.json | 20 ---------- .../buttonBackground.colorset/Contents.json | 34 +++++++++++++++++ 5 files changed, 77 insertions(+), 26 deletions(-) create mode 100644 phyphox-iOS/phyphox/color.xcassets/Color.colorset/Contents.json delete mode 100644 phyphox-iOS/phyphox/color.xcassets/Image.imageset/Contents.json create mode 100644 phyphox-iOS/phyphox/color.xcassets/buttonBackground.colorset/Contents.json diff --git a/phyphox-iOS/phyphox/Camera/CameraView.swift b/phyphox-iOS/phyphox/Camera/CameraView.swift index b3295f1a..f0285fcc 100644 --- a/phyphox-iOS/phyphox/Camera/CameraView.swift +++ b/phyphox-iOS/phyphox/Camera/CameraView.swift @@ -62,8 +62,6 @@ struct PhyphoxCameraView: View { Image(systemName: isMaximized ? "arrow.down.right.and.arrow.up.left" : "arrow.up.left.and.arrow.down.right") .font(.title2) .padding() - .background(Color.black) - .foregroundColor(.white) }) } @@ -96,7 +94,7 @@ struct PhyphoxCameraView: View { .frame(maxWidth: .infinity, alignment: .leading) Text("Preview") - .foregroundColor(.white) + .foregroundColor(Color("textColor")) .frame(maxWidth: .infinity, alignment: .leading) Circle().frame(width: 50, height: 50).opacity(0.0) @@ -215,7 +213,6 @@ struct CameraSettingButton: View { var body: some View { Button(action: { - print("CameraSettingButton") action() }){ Circle() @@ -506,6 +503,8 @@ public struct AlertError { @available(iOS 13.0.0, *) struct TextButton: View { + + @Environment(\.colorScheme) var colorScheme var text: String var action: () -> Void @@ -515,7 +514,7 @@ struct TextButton: View { Text(text) .padding(EdgeInsets(top: 2, leading: 8, bottom: 2, trailing: 8)) .foregroundColor(.black) - .background(Color.white) + .background(Color("buttonBackground")) .cornerRadius(10) } } diff --git a/phyphox-iOS/phyphox/Info.plist b/phyphox-iOS/phyphox/Info.plist index f589aee6..b1910958 100644 --- a/phyphox-iOS/phyphox/Info.plist +++ b/phyphox-iOS/phyphox/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 13812 + 13822 LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace diff --git a/phyphox-iOS/phyphox/color.xcassets/Color.colorset/Contents.json b/phyphox-iOS/phyphox/color.xcassets/Color.colorset/Contents.json new file mode 100644 index 00000000..22c4bb0a --- /dev/null +++ b/phyphox-iOS/phyphox/color.xcassets/Color.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/phyphox-iOS/phyphox/color.xcassets/Image.imageset/Contents.json b/phyphox-iOS/phyphox/color.xcassets/Image.imageset/Contents.json deleted file mode 100644 index a19a5492..00000000 --- a/phyphox-iOS/phyphox/color.xcassets/Image.imageset/Contents.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/phyphox-iOS/phyphox/color.xcassets/buttonBackground.colorset/Contents.json b/phyphox-iOS/phyphox/color.xcassets/buttonBackground.colorset/Contents.json new file mode 100644 index 00000000..12c62758 --- /dev/null +++ b/phyphox-iOS/phyphox/color.xcassets/buttonBackground.colorset/Contents.json @@ -0,0 +1,34 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "gray-gamma-22", + "components" : { + "alpha" : "1.000", + "white" : "0.665" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "gray-gamma-22", + "components" : { + "alpha" : "1.000", + "white" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +}