* Add overlay to ScanningView.
* Apply fixes after review.
--- /dev/null
+using System.Collections.Generic;
+using System.Linq;
+using Tizen.Multimedia.Vision;
+using Tizen.NUI;
+using Tizen.NUI.BaseComponents.VectorGraphics;
+
+namespace Fitness.Controls
+{
+ /// <summary>
+ /// Body parts for display.
+ /// </summary>
+ internal enum BodyPart
+ {
+ /// <summary>
+ /// Head.
+ /// </summary>
+ Head,
+
+ /// <summary>
+ /// Neck.
+ /// </summary>
+ Neck,
+
+ /// <summary>
+ /// Upper part of the right arm.
+ /// </summary>
+ RightUpArm,
+
+ /// <summary>
+ /// Lower part of the right arm.
+ /// </summary>
+ RightLowArm,
+
+ /// <summary>
+ /// Right hand.
+ /// </summary>
+ RightHand,
+
+ /// <summary>
+ /// Upper part of the left arm.
+ /// </summary>
+ LeftUpArm,
+
+ /// <summary>
+ /// Lower part of the left arm.
+ /// </summary>
+ LeftLowArm,
+
+ /// <summary>
+ /// Left hand.
+ /// </summary>
+ LeftHand,
+
+ /// <summary>
+ /// Upper part of the right leg.
+ /// </summary>
+ RightUpLeg,
+
+ /// <summary>
+ /// Lower part of the right leg.
+ /// </summary>
+ RightLowLeg,
+
+ /// <summary>
+ /// Right foot.
+ /// </summary>
+ RightFoot,
+
+ /// <summary>
+ /// Upper part of the left leg.
+ /// </summary>
+ LeftUpLeg,
+
+ /// <summary>
+ /// Lower part of the left leg.
+ /// </summary>
+ LeftLowLeg,
+
+ /// <summary>
+ /// Left foot.
+ /// </summary>
+ LeftFoot,
+ }
+
+ /// <summary>
+ /// Class that determines the position of the body.
+ /// </summary>
+ public class BodyMarker
+ {
+ private static List<(BodyPart From, BodyPart To)> bodyPartConnections;
+ private List<PositionMarker> markers;
+ private List<MarkersConnection> markersConnections;
+ private int numberOfBodyParts;
+ private Color color;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BodyMarker"/> class.
+ /// </summary>
+ /// <param name="numberOfBodyParts">The number of body parts.</param>
+ /// <param name="color">The color of marker.</param>
+ public BodyMarker(int numberOfBodyParts, Color color)
+ {
+ this.numberOfBodyParts = numberOfBodyParts;
+ this.color = color;
+
+ Initialize();
+ }
+
+ /// <summary>
+ /// Gets all shapes in the marker.
+ /// </summary>
+ public IEnumerable<Shape> AllShapes { get; private set; }
+
+ /// <summary>
+ /// Hides the marker.
+ /// </summary>
+ internal void Hide()
+ {
+ foreach (var marker in markers)
+ {
+ if (marker != null)
+ {
+ marker.Opacity = 0.0f;
+ }
+ }
+
+ foreach (var connection in markersConnections)
+ {
+ if (connection != null)
+ {
+ connection.Opacity = 0.0f;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Updates the marker's position.
+ /// </summary>
+ /// <param name="landmark">Position of body elements.</param>
+ /// <param name="overlaySize">Size of the view.</param>
+ internal void Update(Landmark[] landmark, Size overlaySize)
+ {
+ UpdateMarkersPosition(landmark, overlaySize);
+ UpdateConnections();
+ }
+
+ private static void DefineBodyPartConnections()
+ {
+ if (bodyPartConnections == null)
+ {
+ bodyPartConnections = new List<(BodyPart From, BodyPart To)>()
+ {
+ (BodyPart.Head, BodyPart.Neck),
+ (BodyPart.Neck, BodyPart.RightUpArm),
+ (BodyPart.RightUpArm, BodyPart.RightLowArm),
+ (BodyPart.RightLowArm, BodyPart.RightHand),
+ (BodyPart.Neck, BodyPart.LeftUpArm),
+ (BodyPart.LeftUpArm, BodyPart.LeftLowArm),
+ (BodyPart.LeftLowArm, BodyPart.LeftHand),
+ (BodyPart.Neck, BodyPart.RightUpLeg),
+ (BodyPart.RightUpLeg, BodyPart.RightLowLeg),
+ (BodyPart.RightLowLeg, BodyPart.RightFoot),
+ (BodyPart.Neck, BodyPart.LeftUpLeg),
+ (BodyPart.LeftUpLeg, BodyPart.LeftLowLeg),
+ (BodyPart.LeftLowLeg, BodyPart.LeftFoot),
+ };
+ }
+ }
+
+ private void UpdateMarkersPosition(Landmark[] landmark, Size overlaySize)
+ {
+ for (int j = 0; j < numberOfBodyParts; j++)
+ {
+ int x = landmark[j].Location.X * (int)overlaySize.Width / 640;
+ int y = landmark[j].Location.Y * (int)overlaySize.Height / 480;
+
+ markers[j].Position = new Tizen.Multimedia.Point(x, y);
+ markers[j].Opacity = 1.0f;
+ }
+ }
+
+ private void UpdateConnections()
+ {
+ for (int i = 0; i < bodyPartConnections.Count; i++)
+ {
+ int from = (int)bodyPartConnections[i].From;
+ int to = (int)bodyPartConnections[i].To;
+ markersConnections[i].Update(markers[from].Position, markers[to].Position);
+ markersConnections[i].Opacity = 1.0f;
+ }
+ }
+
+ private void Initialize()
+ {
+ InitializeMarkers();
+ InitializeConnections();
+
+ var allShapes = new List<Shape>(markers.Count + markersConnections.Count);
+ allShapes.AddRange(markers);
+ allShapes.AddRange(markersConnections);
+ AllShapes = allShapes;
+ }
+
+ private void InitializeMarkers()
+ {
+ var fillColor = new Color(color.R, color.G, color.B, 0.5f);
+
+ markers = new List<PositionMarker>();
+ for (int j = 0; j < numberOfBodyParts; j++)
+ {
+ var marker = new PositionMarker(color, fillColor);
+ markers.Add(marker);
+ }
+ }
+
+ private void InitializeConnections()
+ {
+ DefineBodyPartConnections();
+ markersConnections = new List<MarkersConnection>();
+ for (int i = 0; i < bodyPartConnections.Count; i++)
+ {
+ var markersConnection = new MarkersConnection(color);
+ markersConnections.Add(markersConnection);
+ }
+ }
+ }
+}
--- /dev/null
+using System;
+using Tizen.Multimedia;
+using Tizen.NUI;
+using Tizen.NUI.BaseComponents.VectorGraphics;
+
+namespace Fitness.Controls
+{
+ /// <summary>
+ /// The body part position markers connection.
+ /// </summary>
+ public class MarkersConnection : Shape
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MarkersConnection"/> class.
+ /// </summary>
+ /// <param name="color">The color to use for stroking the connection.</param>
+ public MarkersConnection(Color color)
+ {
+ Opacity = 1.0f;
+ StrokeColor = color;
+ StrokeWidth = 3.0f;
+ }
+
+ /// <summary>
+ /// Updates the connection position.
+ /// </summary>
+ /// <param name="position1">Start of connection.</param>
+ /// <param name="position2">End of connection.</param>
+ internal void Update(Point position1, Point position2)
+ {
+ var dX = position2.X - position1.X;
+ var dY = position2.Y - position1.Y;
+ var distance = (float)System.Math.Sqrt((dX * dX) + (dY * dY));
+ var shiftX = dX * PositionMarker.Radius / distance;
+ var shiftY = dY * PositionMarker.Radius / distance;
+ ResetPath();
+ Translate(position1.X, position1.Y);
+ AddMoveTo(shiftX, shiftY);
+ AddLineTo(position2.X - position1.X - shiftX, position2.Y - position1.Y - shiftY);
+ }
+ }
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Tizen.Multimedia;
+using Tizen.Multimedia.Vision;
+using Tizen.NUI;
+using Tizen.NUI.BaseComponents.VectorGraphics;
+using Tizen.NUI.Binding;
+
+namespace Fitness.Controls
+{
+ /// <summary>
+ /// Class that displays body position markers.
+ /// </summary>
+ public class Overlay : CanvasView
+ {
+ /// <summary>
+ /// PoseLandmark property.
+ /// </summary>
+ public static readonly BindableProperty PoseLandmarksProperty = BindableProperty.Create(
+ nameof(PoseLandmarks),
+ typeof(Landmark[,]),
+ typeof(Overlay),
+ null,
+ propertyChanged: OnPoseLandmarksChanged);
+
+ private const int MaxNumberOfPeople = 3;
+
+ private static readonly Color[] MarkersColors = new Color[]
+ {
+ new Color(1.0f, 0.0f, 0.0f, 1.0f),
+ new Color(0.145f, 0.93f, 0.898f, 1.0f),
+ Color.Blue,
+ Color.Green,
+ Color.Yellow,
+ };
+
+ private List<BodyMarker> bodyMarkers;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Overlay"/> class.
+ /// </summary>
+ public Overlay()
+ : this(NUIApplication.GetDefaultWindow().Size)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Overlay"/> class.
+ /// </summary>
+ /// <param name="viewBox">The size of viewbox.</param>
+ public Overlay(Size2D viewBox)
+ : base(viewBox)
+ {
+ bodyMarkers = new List<BodyMarker>();
+ }
+
+ /// <summary>
+ /// Gets or sets the landmarks of body pose.
+ /// </summary>
+ public Landmark[,] PoseLandmarks { get; set; }
+
+ private static void OnPoseLandmarksChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is Overlay overlay && newValue is Landmark[,] landmark)
+ {
+ int numberOfPeople = landmark.GetLength(1);
+ int numberOfBodyParts = landmark.GetLength(0);
+ int numberOfPeopleDisplayed = System.Math.Min(numberOfPeople, MaxNumberOfPeople);
+
+ for (int i = 0; i < numberOfPeopleDisplayed; i++)
+ {
+ if (overlay.bodyMarkers.Count <= i)
+ {
+ var bodyMarker = new BodyMarker(numberOfBodyParts, MarkersColors[i]);
+ overlay.bodyMarkers.Add(bodyMarker);
+ overlay.AddDrawables(bodyMarker);
+ }
+
+ var landmarksRow = Enumerable.Range(0, landmark.GetLength(0))
+ .Select(x => landmark[x, i])
+ .ToArray();
+ overlay.bodyMarkers[i].Update(landmarksRow, overlay.Size);
+ }
+
+ for (int i = numberOfPeopleDisplayed; i < overlay.bodyMarkers.Count; ++i)
+ {
+ overlay.bodyMarkers[i].Hide();
+ }
+ }
+ }
+
+ private void AddDrawables(BodyMarker bodyMarker)
+ {
+ foreach (var drawableShape in bodyMarker.AllShapes)
+ {
+ AddDrawable(drawableShape);
+ }
+ }
+ }
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Tizen.NUI;
+using Tizen.NUI.BaseComponents.VectorGraphics;
+
+namespace Fitness.Controls
+{
+ /// <summary>
+ /// The body part position marker.
+ /// </summary>
+ public class PositionMarker : Shape
+ {
+ private Tizen.Multimedia.Point position;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PositionMarker"/> class.
+ /// </summary>
+ /// <param name="color">The color to use for stroking the marker.</param>
+ /// <param name="fillcolor">The color to use for filling the marker.</param>
+ public PositionMarker(Color color, Color fillcolor)
+ {
+ Opacity = 1.0f;
+ FillColor = fillcolor;
+ StrokeColor = color;
+ StrokeWidth = 2.0f;
+ AddCircle(0.0f, 0.0f, Radius, Radius);
+ }
+
+ /// <summary>
+ /// Gets the radius of the marker.
+ /// </summary>
+ public static float Radius => 8.0f;
+
+ /// <summary>
+ /// Gets or sets the position of the marker.
+ /// </summary>
+ public Tizen.Multimedia.Point Position
+ {
+ get
+ {
+ return position;
+ }
+
+ set
+ {
+ position = value;
+ Translate(value.X, value.Y);
+ }
+ }
+ }
+}
using System.Windows.Input;
using Fitness.Models;
using Fitness.Services;
+using Tizen.Multimedia.Vision;
+using Tizen.NUI;
using Tizen.NUI.Binding;
namespace Fitness.ViewModels
{
public class ScanningViewModel : BaseViewModel
{
+ private Landmark[,] poseLandmarks;
+ private Timer timer;
+
public ScanningViewModel()
{
- CloseScanningView = new Command(() => { NavigationService.Instance.Pop(); });
+ CloseScanningView = new Command(() =>
+ {
+ NavigationService.Instance.Pop();
+ timer.Stop();
+ });
+ AddTimer();
}
/// <summary>
/// </summary>
public string Title { get; private set; } = "ScanningView";
+ public Landmark[,] PoseLandmarks
+ {
+ get => poseLandmarks;
+ set
+ {
+ if (value != poseLandmarks)
+ {
+ poseLandmarks = value;
+ RaisePropertyChanged();
+ }
+ }
+ }
+
/// <summary>
/// Gets EndWorkout Command.
/// </summary>
public ICommand CloseScanningView { get; private set; }
+
+ private void AddTimer()
+ {
+ bool OnTick(object o, Timer.TickEventArgs e)
+ {
+ Random rnd = new Random();
+ int numberOfBodyParts = 14;
+ int numberOfPeople = rnd.Next(5);
+ Landmark[,] array = new Landmark[numberOfBodyParts, numberOfPeople];
+
+ for (int i = 0; i < numberOfBodyParts; i++)
+ {
+ for (int j = 0; j < numberOfPeople; j++)
+ {
+ Tizen.Multimedia.Point point1 = new Tizen.Multimedia.Point(rnd.Next(640), rnd.Next(480));
+ Landmark landmark = new Landmark
+ {
+ Location = point1,
+ };
+ array[i, j] = landmark;
+ }
+ }
+
+ PoseLandmarks = array;
+ return true;
+ }
+
+ timer = new Timer(1000 / 2);
+ timer.Tick += OnTick;
+ timer.Start();
+ }
}
}
<vm:ScanningViewModel/>
</View.BindingContext>
- <ctrl:Camera x:Name="cameraView"
- PreviewFps="Fps30"
- PositionX="640" PositionY="300" SizeWidth="640" SizeHeight="480"/>
+ <View Position="320, 100"
+ Size="1280, 960">
+ <ctrl:Camera x:Name="cameraView"
+ WidthResizePolicy="FillToParent"
+ HeightResizePolicy="FillToParent"
+ PreviewFps="Fps30" />
+ <ctrl:Overlay WidthResizePolicy="FillToParent"
+ HeightResizePolicy="FillToParent"
+ BindingContext="{Binding Source={x:Reference Root}, Path=BindingContext}"
+ PoseLandmarks="{Binding PoseLandmarks}" >
+ <x:Arguments>
+ <Size2D>1280, 960</Size2D>
+ </x:Arguments>
+ </ctrl:Overlay>
+ </View>
<View Size="{views:SizeInUnits Height=20}"
WidthResizePolicy="FillToParent">