public void DidOutputMetadataObjects(AVCaptureMetadataOutput captureOutput, AVMetadataObject[] metadataObjects, AVCaptureConnection connection) { var lostFaces = _faceLayers.Keys.ToList(); foreach (var metadata in metadataObjects.OfType <AVMetadataFaceObject>()) { var transformed = VideoPreviewLayer.GetTransformedMetadataObject(metadata); var face = transformed as AVMetadataFaceObject; var bounds = transformed.Bounds; if (lostFaces.Contains(face.FaceID)) { lostFaces.Remove(face.FaceID); } CALayer faceLayer; if (!_faceLayers.TryGetValue(face.FaceID, out faceLayer)) { faceLayer = CreateFaceLayer(); _overlayLayer.AddSublayer(faceLayer); _faceLayers.Add(face.FaceID, faceLayer); } faceLayer.Transform = CATransform3D.Identity; faceLayer.Frame = bounds; if (face.HasRollAngle) { var transform = RollTransform(face.RollAngle); faceLayer.Transform = faceLayer.Transform.Concat(transform); } if (face.HasYawAngle) { var transform = YawTransform(face.YawAngle); faceLayer.Transform = faceLayer.Transform.Concat(transform); } } RemoveLostFaces(lostFaces); }
// Updates the region of interest with a proposed region of interest ensuring // the new region of interest is within the bounds of the video preview. When // a new region of interest is set, the region of interest is redrawn. public void SetRegionOfInterestWithProposedRegionOfInterest(CGRect proposedRegionOfInterest) { // We standardize to ensure we have positive widths and heights with an origin at the top left. var videoPreviewRect = VideoPreviewLayer.MapToLayerCoordinates(new CGRect(0, 0, 1, 1)).Standardize(); // Intersect the video preview view with the view's frame to only get // the visible portions of the video preview view. var visibleVideoPreviewRect = CGRect.Intersect(videoPreviewRect, Frame); var oldRegion = RegionOfInterest; var newRegion = proposedRegionOfInterest.Standardize(); // Move the region of interest in bounds. if (currentControlCorner == ControlCorner.None) { nfloat xOffset = 0; nfloat yOffset = 0; if (!visibleVideoPreviewRect.Contains(newRegion.CornerTopLeft())) { xOffset = NMath.Max(visibleVideoPreviewRect.GetMinX() - newRegion.GetMinX(), 0); yOffset = NMath.Max(visibleVideoPreviewRect.GetMinY() - newRegion.GetMinY(), 0); } if (!visibleVideoPreviewRect.Contains(visibleVideoPreviewRect.CornerBottomRight())) { xOffset = NMath.Min(visibleVideoPreviewRect.GetMaxX() - newRegion.GetMaxX(), xOffset); yOffset = NMath.Min(visibleVideoPreviewRect.GetMaxY() - newRegion.GetMaxY(), yOffset); } newRegion.Offset(xOffset, yOffset); } // Clamp the size when the region of interest is being resized. visibleVideoPreviewRect.Intersect(newRegion); newRegion = visibleVideoPreviewRect; // Fix a minimum width of the region of interest. if (proposedRegionOfInterest.Size.Width < MinimumRegionOfInterestSize) { switch (currentControlCorner) { case ControlCorner.TopLeft: case ControlCorner.BottomLeft: var x = oldRegion.Location.X + oldRegion.Size.Width - MinimumRegionOfInterestSize; newRegion = newRegion.WithX(x).WithWidth(MinimumRegionOfInterestSize); break; case ControlCorner.TopRight: newRegion = newRegion.WithX(oldRegion.Location.X) .WithWidth(MinimumRegionOfInterestSize); break; default: newRegion = newRegion.WithLocation(oldRegion.Location) .WithWidth(MinimumRegionOfInterestSize); break; } } // Fix a minimum height of the region of interest. if (proposedRegionOfInterest.Height < MinimumRegionOfInterestSize) { switch (currentControlCorner) { case ControlCorner.TopLeft: case ControlCorner.TopRight: newRegion = newRegion.WithY(oldRegion.Y + oldRegion.Height - MinimumRegionOfInterestSize) .WithHeight(MinimumRegionOfInterestSize); break; case ControlCorner.BottomLeft: newRegion = newRegion.WithY(oldRegion.Y) .WithHeight(MinimumRegionOfInterestSize); break; default: newRegion = newRegion.WithLocation(oldRegion.Location) .WithHeight(MinimumRegionOfInterestSize); break; } } RegionOfInterest = newRegion; SetNeedsLayout(); }
void ResizeRegionOfInterestWithGestureRecognizer(UIPanGestureRecognizer pan) { var touchLocation = pan.LocationInView(pan.View); var oldRegionOfInterest = RegionOfInterest; switch (pan.State) { case UIGestureRecognizerState.Began: // When the gesture begins, save the corner that is closes to // the resize region of interest gesture recognizer's touch location. currentControlCorner = CornerOfRect(oldRegionOfInterest, touchLocation); break; case UIGestureRecognizerState.Changed: var newRegionOfInterest = oldRegionOfInterest; switch (currentControlCorner) { case ControlCorner.None: // Update the new region of interest with the gesture recognizer's translation. var translation = pan.TranslationInView(pan.View); // Move the region of interest with the gesture recognizer's translation. if (RegionOfInterest.Contains(touchLocation)) { newRegionOfInterest.X += translation.X; newRegionOfInterest.Y += translation.Y; } // If the touch location goes outside the preview layer, // we will only translate the region of interest in the // plane that is not out of bounds. var normalizedRect = new CGRect(0, 0, 1, 1); if (!normalizedRect.Contains(VideoPreviewLayer.PointForCaptureDevicePointOfInterest(touchLocation))) { if (touchLocation.X < RegionOfInterest.GetMinX() || touchLocation.X > RegionOfInterest.GetMaxX()) { newRegionOfInterest.Y += translation.Y; } else if (touchLocation.Y < RegionOfInterest.GetMinY() || touchLocation.Y > RegionOfInterest.GetMaxY()) { newRegionOfInterest.X += translation.X; } } // Set the translation to be zero so that the new gesture // recognizer's translation is in respect to the region of // interest's new position. pan.SetTranslation(CGPoint.Empty, pan.View); break; case ControlCorner.TopLeft: newRegionOfInterest = new CGRect(touchLocation.X, touchLocation.Y, oldRegionOfInterest.Width + oldRegionOfInterest.X - touchLocation.X, oldRegionOfInterest.Height + oldRegionOfInterest.Y - touchLocation.Y); break; case ControlCorner.TopRight: newRegionOfInterest = new CGRect(newRegionOfInterest.X, touchLocation.Y, touchLocation.X - newRegionOfInterest.X, oldRegionOfInterest.Height + newRegionOfInterest.Y - touchLocation.Y); break; case ControlCorner.BottomLeft: newRegionOfInterest = new CGRect(touchLocation.X, oldRegionOfInterest.Y, oldRegionOfInterest.Width + oldRegionOfInterest.X - touchLocation.X, touchLocation.Y - oldRegionOfInterest.Y); break; case ControlCorner.BottomRight: newRegionOfInterest = new CGRect(oldRegionOfInterest.X, oldRegionOfInterest.Y, touchLocation.X - oldRegionOfInterest.X, touchLocation.Y - oldRegionOfInterest.Y); break; } // Update the region of intresest with a valid CGRect. SetRegionOfInterestWithProposedRegionOfInterest(newRegionOfInterest); break; case UIGestureRecognizerState.Ended: RegionOfInterestDidChange?.Invoke(this, EventArgs.Empty); // Reset the current corner reference to none now that the resize. // gesture recognizer has ended. currentControlCorner = ControlCorner.None; break; default: return; } }