@@ -19,12 +19,12 @@ class CameraViewController: UIViewController, UINavigationControllerDelegate {
1919
2020 // Image processing
2121 private let context = CIContext ( )
22-
22+
2323 // Privacy Filtering
2424 private var privacyMode : String = " segmentation "
2525 private var personSegmentationRequest : VNGeneratePersonSegmentationRequest !
2626 private var personMaskBuffer : CVPixelBuffer ?
27-
27+
2828 // Body Pose Detection
2929 private var bodyPoseRequest : VNDetectHumanBodyPoseRequest !
3030 private var detectedBodyPoses : [ VNHumanBodyPoseObservation ] = [ ]
@@ -130,30 +130,30 @@ class CameraViewController: UIViewController, UINavigationControllerDelegate {
130130 let overlay = UIView ( frame: view. bounds)
131131 overlay. backgroundColor = AppConstants . Colors. backgroundBaseUI. withAlphaComponent ( AppConstants . UI. overlayOpacityDark)
132132 overlay. autoresizingMask = [ . flexibleWidth, . flexibleHeight]
133-
133+
134134 let indicator = UIActivityIndicatorView ( style: . large)
135135 indicator. color = . white
136136 indicator. translatesAutoresizingMaskIntoConstraints = false
137137 indicator. startAnimating ( )
138-
138+
139139 let label = UILabel ( )
140140 label. text = " Initializing Privacy Models... "
141141 label. textColor = . white
142142 label. font = . systemFont( ofSize: AppConstants . UI. fontSizeStandard, weight: . medium)
143143 label. translatesAutoresizingMaskIntoConstraints = false
144-
144+
145145 let stack = UIStackView ( arrangedSubviews: [ indicator, label] )
146146 stack. axis = . vertical
147147 stack. spacing = AppConstants . UI. spacingMedium
148148 stack. alignment = . center
149149 stack. translatesAutoresizingMaskIntoConstraints = false
150-
150+
151151 overlay. addSubview ( stack)
152152 NSLayoutConstraint . activate ( [
153153 stack. centerXAnchor. constraint ( equalTo: overlay. centerXAnchor) ,
154154 stack. centerYAnchor. constraint ( equalTo: overlay. centerYAnchor)
155155 ] )
156-
156+
157157 view. addSubview ( overlay)
158158 self . loadingOverlay = overlay
159159 self . activityIndicator = indicator
@@ -265,11 +265,11 @@ class CameraViewController: UIViewController, UINavigationControllerDelegate {
265265 connectionHostingController = UIHostingController ( rootView: buttonView)
266266 connectionHostingController. view. translatesAutoresizingMaskIntoConstraints = false
267267 connectionHostingController. view. backgroundColor = . clear
268-
268+
269269 addChild ( connectionHostingController)
270270 view. addSubview ( connectionHostingController. view)
271271 connectionHostingController. didMove ( toParent: self )
272-
272+
273273 NSLayoutConstraint . activate ( [
274274 connectionHostingController. view. topAnchor. constraint ( equalTo: view. safeAreaLayoutGuide. topAnchor, constant: AppConstants . UI. paddingStandard) ,
275275 connectionHostingController. view. trailingAnchor. constraint ( equalTo: view. safeAreaLayoutGuide. trailingAnchor, constant: AppConstants . UI. offsetTrailingStatus)
@@ -470,10 +470,20 @@ class CameraViewController: UIViewController, UINavigationControllerDelegate {
470470 do {
471471 assetWriter = try AVAssetWriter ( outputURL: videoURL, fileType: fileType)
472472
473+ var outputWidth = videoRotationAngle == 0 || videoRotationAngle == 180 ? AppConstants . Video. dimensionHigh : AppConstants . Video. dimensionLow
474+ var outputHeight = videoRotationAngle == 0 || videoRotationAngle == 180 ? AppConstants . Video. dimensionLow : AppConstants . Video. dimensionHigh
475+
476+ #if targetEnvironment(simulator)
477+ if let presentationSize = simulatorPlayer? . currentItem? . presentationSize, presentationSize. width > 0 {
478+ outputWidth = Int ( presentationSize. width)
479+ outputHeight = Int ( presentationSize. height)
480+ }
481+ #endif
482+
473483 let outputSettings : [ String : Any ] = [
474484 AVVideoCodecKey: AVVideoCodecType . h264,
475- AVVideoWidthKey: videoRotationAngle == 0 || videoRotationAngle == 180 ? AppConstants . Video . dimensionHigh : AppConstants . Video . dimensionLow ,
476- AVVideoHeightKey: videoRotationAngle == 0 || videoRotationAngle == 180 ? AppConstants . Video . dimensionLow : AppConstants . Video . dimensionHigh
485+ AVVideoWidthKey: outputWidth ,
486+ AVVideoHeightKey: outputHeight
477487 ]
478488
479489 assetWriterInput = AVAssetWriterInput ( mediaType: . video, outputSettings: outputSettings)
@@ -484,8 +494,8 @@ class CameraViewController: UIViewController, UINavigationControllerDelegate {
484494
485495 let sourcePixelBufferAttributes : [ String : Any ] = [
486496 kCVPixelBufferPixelFormatTypeKey as String : kCVPixelFormatType_32BGRA,
487- kCVPixelBufferWidthKey as String : videoRotationAngle == 0 || videoRotationAngle == 180 ? AppConstants . Video . dimensionHigh : AppConstants . Video . dimensionLow ,
488- kCVPixelBufferHeightKey as String : videoRotationAngle == 0 || videoRotationAngle == 180 ? AppConstants . Video . dimensionLow : AppConstants . Video . dimensionHigh
497+ kCVPixelBufferWidthKey as String : outputWidth ,
498+ kCVPixelBufferHeightKey as String : outputHeight
489499 ]
490500
491501 adapter = AVAssetWriterInputPixelBufferAdaptor ( assetWriterInput: input, sourcePixelBufferAttributes: sourcePixelBufferAttributes)
@@ -928,23 +938,23 @@ class CameraViewController: UIViewController, UINavigationControllerDelegate {
928938
929939 private func setupPrivacyFiltering( ) {
930940 self . privacyMode = UserDefaults . standard. string ( forKey: " privacyMode " ) ?? " segmentation "
931-
941+
932942 let segRequest = VNGeneratePersonSegmentationRequest ( )
933943 segRequest. qualityLevel = . balanced // Balanced is much faster for real-time video than .accurate
934944 self . personSegmentationRequest = segRequest
935-
945+
936946 self . bodyPoseRequest = VNDetectHumanBodyPoseRequest ( )
937947 }
938948
939949 private func applyPrivacyBlur( to image: CIImage ) -> CIImage {
940950 if privacyMode == " segmentation " {
941951 guard let maskBuffer = personMaskBuffer else { return image }
942952 let maskImage = CIImage ( cvPixelBuffer: maskBuffer)
943-
953+
944954 let scaleX = image. extent. width / maskImage. extent. width
945955 let scaleY = image. extent. height / maskImage. extent. height
946956 let scaledMask = maskImage. transformed ( by: CGAffineTransform ( scaleX: scaleX, y: scaleY) )
947-
957+
948958 if let pixelateFilter = CIFilter ( name: " CIPixellate " ) {
949959 pixelateFilter. setValue ( image, forKey: kCIInputImageKey)
950960 pixelateFilter. setValue ( NSNumber ( value: Float ( AppConstants . Privacy. pixelationScale) ) , forKey: kCIInputScaleKey)
@@ -1082,6 +1092,15 @@ class CameraViewController: UIViewController, UINavigationControllerDelegate {
10821092 if let cgImage = context. createCGImage ( processedImage, from: processedImage. extent) {
10831093 let uiImage = UIImage ( cgImage: cgImage)
10841094 self . imageView. image = uiImage
1095+
1096+ if let overlay = self . loadingOverlay {
1097+ self . loadingOverlay = nil
1098+ UIView . animate ( withDuration: 0.3 , animations: { [ weak overlay] in
1099+ overlay? . alpha = 0
1100+ } ) { [ weak overlay] _ in
1101+ overlay? . removeFromSuperview ( )
1102+ }
1103+ }
10851104 }
10861105 }
10871106#endif
@@ -1237,7 +1256,7 @@ extension CameraViewController: AVCaptureVideoDataOutputSampleBufferDelegate, AV
12371256 DispatchQueue . main. async { [ weak self] in
12381257 guard let self = self else { return }
12391258 self . imageView. image = uiImage
1240-
1259+
12411260 if let overlay = self . loadingOverlay {
12421261 self . loadingOverlay = nil // Nullify immediately to prevent duplicate animations
12431262 UIView . animate ( withDuration: 0.3 , animations: { [ weak overlay] in
0 commit comments