첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.
728x90
반응형
728x170

TestProject.zip
0.03MB

▶ MainPage.xaml

<Page x:Class="TestProject.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    FontFamily="나눔고딕코딩"
    FontSize="16">
    <Page.Resources>
        <SolidColorBrush x:Key="TranslucentBlackBrushKey"
            Color="Black"
            Opacity="0.3" />
    </Page.Resources>
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.Resources>
            <Style TargetType="Button">
                <Setter Property="Margin"                Value="10 40" />
                <Setter Property="MinWidth"              Value="80"    />
                <Setter Property="MinHeight"             Value="80"    />
                <Setter Property="Foreground"            Value="White" />
                <Setter Property="BorderBrush"           Value="White" />
                <Setter Property="Background"            Value="{StaticResource TranslucentBlackBrushKey}" />
                <Setter Property="RenderTransformOrigin" Value="0.5 0.5" />
            </Style>
            <Style TargetType="Viewbox">
                <Setter Property="MaxWidth"  Value="40" />
                <Setter Property="MaxHeight" Value="40" />
            </Style>
        </Grid.Resources>
        <CaptureElement Name="previewCaptureElement"
            Stretch="Uniform" />
        <Canvas>
            <Canvas Name="faceCanvas"
                RenderTransformOrigin="0.5 0.5" />
        </Canvas>
        <StackPanel
            HorizontalAlignment="Right"
            VerticalAlignment="Center">
            <Button Name="photoButton"
                IsEnabled="False"
                Click="photoButton_Click">
                <Viewbox>
                    <SymbolIcon Symbol="Camera" />
                </Viewbox>
            </Button>
            <Button Name="videoButton"
                IsEnabled="False"
                Click="videoButton_Click">
                <Grid>
                    <Ellipse Name="startVideoEllipse"
                        Width="20"
                        Height="20"
                        Fill="Red" />
                    <Rectangle Name="stopVideoRectangle"
                        Width="20"
                        Height="20"
                        Fill="White"
                        Visibility="Collapsed" />
                </Grid>
            </Button>
        </StackPanel>
        <Button Name="faceDetectionButton"
            IsEnabled="False"
            Click="faceDetectionButton_Click">
            <Viewbox>
                <Grid>
                    <SymbolIcon Name="faceDetectionSymbolIcon1"
                        Symbol="Contact2"
                        Visibility="Collapsed" />
                    <SymbolIcon Name="faceDetectionSymbolIcon2"
                        Symbol="Contact"
                        Visibility="Visible" />
                </Grid>
            </Viewbox>
        </Button>
    </Grid>
</Page>

 

728x90

 

▶ MainPage.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.Devices.Enumeration;
using Windows.Devices.Sensors;
using Windows.Foundation;
using Windows.Foundation.Metadata;
using Windows.Graphics.Display;
using Windows.Graphics.Imaging;
using Windows.Media;
using Windows.Media.Core;
using Windows.Media.Capture;
using Windows.Media.FaceAnalysis;
using Windows.Media.MediaProperties;
using Windows.Phone.UI.Input;
using Windows.Storage;
using Windows.Storage.FileProperties;
using Windows.Storage.Streams;
using Windows.System.Display;
using Windows.UI;
using Windows.UI.Core;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Windows.UI.Xaml.Shapes;

namespace TestProject
{
    /// <summary>
    /// 메인 페이지
    /// </summary>
    public sealed partial class MainPage : Page
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 회전 키 GUID
        /// </summary>
        private static readonly Guid _rotationKeyGUID = new Guid("C380465D-2271-428C-9B83-ECEA3B4A85C1");

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 디스플레이 정보
        /// </summary>
        private readonly DisplayInformation displayInformation = DisplayInformation.GetForCurrentView();

        /// <summary>
        /// 단순 방향 센서
        /// </summary>
        private readonly SimpleOrientationSensor orientationSensor = SimpleOrientationSensor.GetDefault();

        /// <summary>
        /// 장치 단순 방향
        /// </summary>
        private SimpleOrientation deviceOrientation = SimpleOrientation.NotRotated;

        /// <summary>
        /// 디스플레이 방향
        /// </summary>
        private DisplayOrientations displayOrientations = DisplayOrientations.Portrait;

        /// <summary>
        /// 캡처 저장소 폴더
        /// </summary>
        private StorageFolder captureStorageFolder = null;

        /// <summary>
        /// 디스플레이 요청
        /// </summary>
        private readonly DisplayRequest displayRequest = new DisplayRequest();

        /// <summary>
        /// 시스템 미디어 전송 컨트롤
        /// </summary>
        private readonly SystemMediaTransportControls systemMediaTransportControls = SystemMediaTransportControls.GetForCurrentView();

        /// <summary>
        /// 미디어 캡처
        /// </summary>
        private MediaCapture mediaCapture;

        /// <summary>
        /// 미디어 인코딩 속성
        /// </summary>
        private IMediaEncodingProperties mediaEncodingProperties;

        /// <summary>
        /// 초기화 여부
        /// </summary>
        private bool isInitialized;

        /// <summary>
        /// 레코딩 여부
        /// </summary>
        private bool isRecording;

        /// <summary>
        /// 미리보기 미러링 여부
        /// </summary>
        private bool mirroringPreview;

        /// <summary>
        /// 외부 카메라 여부
        /// </summary>
        private bool externalCamera;

        /// <summary>
        /// 얼굴 탐지 효과
        /// </summary>
        private FaceDetectionEffect faceDetectionEffect;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - MainPage()

        /// <summary>
        /// 생성자
        /// </summary>
        public MainPage()
        {
            InitializeComponent();

            #region 윈도우 크기를 설정한다.

            double width  = 800d;
            double height = 600d;

            double dpi = (double)DisplayInformation.GetForCurrentView().LogicalDpi;

            ApplicationView.PreferredLaunchWindowingMode = ApplicationViewWindowingMode.PreferredLaunchViewSize;

            Size windowSize = new Size(width * 96d / dpi, height * 96d / dpi);

            ApplicationView.PreferredLaunchViewSize = windowSize;

            Window.Current.Activate();

            ApplicationView.GetForCurrentView().TryResizeView(windowSize);

            #endregion
            #region 윈도우 제목을 설정한다.

            ApplicationView.GetForCurrentView().Title = "카메라를 사용해 얼굴 탐지하기";

            #endregion
            
            NavigationCacheMode = NavigationCacheMode.Disabled;

            Application.Current.Suspending += Application_Suspending;
            Application.Current.Resuming   += Application_Resuming;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Protected

        #region 탐색되는 경우 처리하기 - OnNavigatedTo(e)

        /// <summary>
        /// 탐색되는 경우 처리하기
        /// </summary>
        /// <param name="e">이벤트 인자</param>
        protected override async void OnNavigatedTo(NavigationEventArgs e)
        {
            await SetupUIAsync();

            await InitializeCameraAsync();
        }

        #endregion
        #region 탐색하는 경우 처리하기 - OnNavigatingFrom(e)

        /// <summary>
        /// 탐색하는 경우 처리하기
        /// </summary>
        /// <param name="e">이벤트 인자</param>
        protected override async void OnNavigatingFrom(NavigatingCancelEventArgs e)
        {
            await CleanupCameraAsync();

            await CleanupUIAsync();
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Private
        //////////////////////////////////////////////////////////////////////////////// Event

        #region 애플리케이션 일시 중단시 처리하기 - Application_Suspending(sender, e)

        /// <summary>
        /// 애플리케이션 일시 중단시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private async void Application_Suspending(object sender, SuspendingEventArgs e)
        {
            if(Frame.CurrentSourcePageType == typeof(MainPage))
            {
                SuspendingDeferral deferral = e.SuspendingOperation.GetDeferral();

                await CleanupCameraAsync();

                await CleanupUIAsync();

                deferral.Complete();
            }
        }

        #endregion
        #region 애플리케이션 재개시 처리하기 - Application_Resuming(sender, e)

        /// <summary>
        /// 애플리케이션 재개시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private async void Application_Resuming(object sender, object e)
        {
            if(Frame.CurrentSourcePageType == typeof(MainPage))
            {
                await SetupUIAsync();

                await InitializeCameraAsync();
            }
        }

        #endregion

        #region 단순 방향 센서 방향 변경시 처리하기 - orientationSensor_OrientationChanged(sender, e)

        /// <summary>
        /// 단순 방향 센서 방향 변경시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private async void orientationSensor_OrientationChanged
        (
            SimpleOrientationSensor                            sender,
            SimpleOrientationSensorOrientationChangedEventArgs e
        )
        {
            if(e.Orientation != SimpleOrientation.Faceup && e.Orientation != SimpleOrientation.Facedown)
            {
                this.deviceOrientation = e.Orientation;

                await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => UpdateButtonOrientation());
            }
        }

        #endregion
        #region 디스플레이 정보 방향 변경시 처리하기 - displayInformation_OrientationChanged(sender, e)

        /// <summary>
        /// 디스플레이 정보 방향 변경시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        /// <remarks>
        /// 이 이벤트는 SetupUIAsync 함수에 설정된 DisplayInformation.AutoRotationPreferences 값이 적용되지 않을 때
        /// 페이지가 회전될 때 발생한다.
        /// </remarks>
        private async void displayInformation_OrientationChanged(DisplayInformation sender, object e)
        {
            this.displayOrientations = sender.CurrentOrientation;

            if(this.mediaEncodingProperties != null)
            {
                await SetPreviewRotationAsync();
            }

            await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => UpdateButtonOrientation());
        }

        #endregion

        #region 미디어 캡처 레코드 제한 초과시 처리하기 - mediaCapture_RecordLimitationExceeded(sender)

        /// <summary>
        /// 미디어 캡처 레코드 제한 초과시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        private async void mediaCapture_RecordLimitationExceeded(MediaCapture sender)
        {
            // 녹음을 중지해야 하며 앱에서 녹음을 완료할 것으로 예상되는 알림이다.

            await StopRecordingAsync();

            await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => UpdateCaptureControls());
        }

        #endregion
        #region 미디어 캡처 실패시 처리하기 - mediaCapture_Failed(sender, e)

        /// <summary>
        /// 미디어 캡처 실패시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private async void mediaCapture_Failed(MediaCapture sender, MediaCaptureFailedEventArgs e)
        {
            await CleanupCameraAsync();

            await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => UpdateCaptureControls());
        }

        #endregion
        #region 시스템 미디어 전송 컨트롤 속성 변경시 처리하기 - systemMediaTransportControls_PropertyChanged(sender, e)

        /// <summary>
        /// 시스템 미디어 전송 컨트롤 속성 변경시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        /// <remarks>
        /// 앱이 최소화되는 경우 이 메소드는 미디어 속성 변경 이벤트를 처리한다.
        /// 앱이 음소거 알림을 수신하면 더 이상 포그라운드에 있지 않는다.
        /// </remarks>
        private async void systemMediaTransportControls_PropertyChanged
        (
            SystemMediaTransportControls                         sender,
            SystemMediaTransportControlsPropertyChangedEventArgs e
        )
        {
            await Dispatcher.RunAsync
            (
                CoreDispatcherPriority.Normal,
                async () =>
                {
                    // 이 페이지가 현재 표시되고 있는 경우에만 이 이벤트를 처리한다.
                    if
                    (
                        e.Property == SystemMediaTransportControlsProperty.SoundLevel &&
                        Frame.CurrentSourcePageType == typeof(MainPage)
                    )
                    {
                        // 앱이 음소거되고 있는지 확인한다. 그렇다면 최소화하고 있다.
                        // 그렇지 않으면 초기화되지 않은 경우 초점이 맞춰진다.
                        if(sender.SoundLevel == SoundLevel.Muted)
                        {
                            await CleanupCameraAsync();
                        }
                        else if (!this.isInitialized)
                        {
                            await InitializeCameraAsync();
                        }
                    }
                }
            );
        }

        #endregion

        #region 얼굴 탐지 효과 얼굴 탐지시 처리하기 - faceDetectionEffect_FaceDetected(sender, e)

        /// <summary>
        /// 얼굴 탐지 효과 얼굴 탐지시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private async void faceDetectionEffect_FaceDetected(FaceDetectionEffect sender, FaceDetectedEventArgs e)
        {
            await Dispatcher.RunAsync
            (
                CoreDispatcherPriority.Normal,
                () => HighlightDetectedFaces(e.ResultFrame.DetectedFaces)
            );
        }

        #endregion

        #region 하드웨어 버튼 카메라 PRESS 처리하기 - HardwareButtons_CameraPressed(sender, e)

        /// <summary>
        /// 하드웨어 버튼 카메라 PRESS 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private async void HardwareButtons_CameraPressed(object sender, CameraEventArgs e)
        {
            await TakePhotoAsync();
        }

        #endregion

        #region 사진 버튼 클릭시 처리하기 - photoButton_Click(sender, e)

        /// <summary>
        /// 사진 버튼 클릭시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private async void photoButton_Click(object sender, RoutedEventArgs e)
        {
            await TakePhotoAsync();
        }

        #endregion
        #region 비디오 버튼 클릭시 처리하기 - videoButton_Click(sender, e)

        /// <summary>
        /// 비디오 버튼 클릭시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private async void videoButton_Click(object sender, RoutedEventArgs e)
        {
            if(!this.isRecording)
            {
                await StartRecordingAsync();
            }
            else
            {
                await StopRecordingAsync();
            }

            UpdateCaptureControls();
        }

        #endregion
        #region 얼굴 탐지 버튼 클릭시 처리하기 - faceDetectionButton_Click(sender, e)

        /// <summary>
        /// 얼굴 탐지 버튼 클릭시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private async void faceDetectionButton_Click(object sender, RoutedEventArgs e)
        {
            if(this.faceDetectionEffect == null || !this.faceDetectionEffect.Enabled)
            {
                this.faceCanvas.Children.Clear();

                await CreateFaceDetectionEffectAsync();
            }
            else
            {
                await CleanUpFaceDetectionEffectAsync();
            }

            UpdateCaptureControls();
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////// Function

        #region 카메라 방향 구하기 - GetCameraOrientation()

        /// <summary>
        /// 카메라 방향 구하기
        /// </summary>
        /// <returns>카메라 방향</returns>
        /// <remarks>
        /// 카메라가 외부에 있는지 또는 사용자를 향하고 있는지를 고려하여 장치 방향에서 현재 카메라 방향을 계산한다.
        /// </remarks>
        private SimpleOrientation GetCameraOrientation()
        {
            if(this.externalCamera)
            {
                return SimpleOrientation.NotRotated;
            }

            SimpleOrientation targetOrientation = this.deviceOrientation;

            // 세로 우선 장치에서 카메라 센서가 기본 방향에 대해 90도 오프셋으로 장착된다는 사실을 고려한다.
            if(this.displayInformation.NativeOrientation == DisplayOrientations.Portrait)
            {
                switch(targetOrientation)
                {
                    case SimpleOrientation.Rotated90DegreesCounterclockwise :
                    
                        targetOrientation = SimpleOrientation.NotRotated;
                        
                        break;

                    case SimpleOrientation.Rotated180DegreesCounterclockwise :
                    
                        targetOrientation = SimpleOrientation.Rotated90DegreesCounterclockwise;
                        
                        break;

                    case SimpleOrientation.Rotated270DegreesCounterclockwise :
                    
                        targetOrientation = SimpleOrientation.Rotated180DegreesCounterclockwise;
                        
                        break;

                    case SimpleOrientation.NotRotated :
                    
                        targetOrientation = SimpleOrientation.Rotated270DegreesCounterclockwise;
                        
                        break;
                }
            }

            // 전면 카메라에 대해 미리보기가 미러링되는 경우 회전이 반전되어야 한다.
            if(this.mirroringPreview)
            {
                // 이것은 90도와 270도 경우에만 영향을 미친다.
                // 0도와 180도 회전은 시계 방향과 반시계 방향이 동일하기 때문이다.
                switch(targetOrientation)
                {
                    case SimpleOrientation.Rotated90DegreesCounterclockwise  : return SimpleOrientation.Rotated270DegreesCounterclockwise; 
                    case SimpleOrientation.Rotated270DegreesCounterclockwise : return SimpleOrientation.Rotated90DegreesCounterclockwise;
                }
            }

            return targetOrientation;
        }

        #endregion
        #region 사진 방향 구하기 - GetPhotoOrientation(cameraOrientation)

        /// <summary>
        /// 사진 방향 구하기
        /// </summary>
        /// <param name="cameraOrientation">카메라 방향</param>
        /// <returns>사진 방향</returns>
        private static PhotoOrientation GetPhotoOrientation(SimpleOrientation cameraOrientation)
        {
            switch(cameraOrientation)
            {
                case SimpleOrientation.Rotated90DegreesCounterclockwise  : return PhotoOrientation.Rotate90;
                case SimpleOrientation.Rotated180DegreesCounterclockwise : return PhotoOrientation.Rotate180;
                case SimpleOrientation.Rotated270DegreesCounterclockwise : return PhotoOrientation.Rotate270;
                case SimpleOrientation.NotRotated                        :
                default                                                  : return PhotoOrientation.Normal;
            }
        }

        #endregion

        #region 장치 방향 각도 구하기 - GetDeviceOrientationDegrees(deviceOrientation)

        /// <summary>
        /// 장치 방향 각도 구하기
        /// </summary>
        /// <param name="deviceOrientation">장치 방향</param>
        /// <returns>장치 방향 각도</returns>
        private static int GetDeviceOrientationDegrees(SimpleOrientation deviceOrientation)
        {
            switch(deviceOrientation)
            {
                case SimpleOrientation.Rotated90DegreesCounterclockwise  : return 90;
                case SimpleOrientation.Rotated180DegreesCounterclockwise : return 180;
                case SimpleOrientation.Rotated270DegreesCounterclockwise : return 270;
                case SimpleOrientation.NotRotated                        :
                default                                                  : return 0;
            }
        }

        #endregion
        #region 디스플레이 방향 각도 구하기 - GetDisplayOrientationDegrees(displayOrientations)

        /// <summary>
        /// 디스플레이 방향 각도 구하기
        /// </summary>
        /// <param name="displayOrientations">디스플레이 방향</param>
        /// <returns>디스플레이 방향 각도 구하기</returns>
        private static int GetDisplayOrientationDegrees(DisplayOrientations displayOrientations)
        {
            switch(displayOrientations)
            {
                case DisplayOrientations.Portrait         : return 90;
                case DisplayOrientations.LandscapeFlipped : return 180;
                case DisplayOrientations.PortraitFlipped  : return 270;
                case DisplayOrientations.Landscape        :
                default                                   : return 0;
            }
        }

        #endregion

        #region 카메라 장치 정보 구하기 - GetCameraDeviceInformation(targetPanel)

        /// <summary>
        /// 카메라 장치 정보 구하기
        /// </summary>
        /// <param name="targetPanel">타겟 패널</param>
        /// <returns>카메라 장치 정보</returns>
        private static async Task<DeviceInformation> GetCameraDeviceInformation
        (
            Windows.Devices.Enumeration.Panel targetPanel
        )
        {
            // 사진 캡처에 사용할 수 있는 장치를 가져온다.
            DeviceInformationCollection collection = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture);

            // 패널별로 원하는 카메라를 가져온다.
            DeviceInformation deviceInformation = collection.FirstOrDefault
            (
                x => x.EnclosureLocation != null &&
                x.EnclosureLocation.Panel == targetPanel
            );

            // 원하는 패널에 장착된 장치가 없으면 처음 발견된 장치를 반환한다.
            return deviceInformation ?? collection.FirstOrDefault();
        }

        #endregion

        #region 버튼 방향 업데이트하기 - UpdateButtonOrientation()

        /// <summary>
        /// 버튼 방향 업데이트하기
        /// </summary>
        /// <remarks>
        /// 공간의 현재 장치 방향과 화면의 페이지 방향을 사용하여 컨트롤에 적용할 회전 변환을 계산합니다.
        /// </remarks>
        private void UpdateButtonOrientation()
        {
            int deviceOrientationDegrees   = GetDeviceOrientationDegrees (this.deviceOrientation  );
            int displayOritentationDegrees = GetDisplayOrientationDegrees(this.displayOrientations);

            if(this.displayInformation.NativeOrientation == DisplayOrientations.Portrait)
            {
                deviceOrientationDegrees -= 90;
            }

            int angle = (360 + displayOritentationDegrees + deviceOrientationDegrees) % 360;

            RotateTransform transform = new RotateTransform { Angle = angle };

            this.photoButton.RenderTransform         = transform;
            this.videoButton.RenderTransform         = transform;
            this.faceDetectionButton.RenderTransform = transform;
        }

        #endregion
        #region 캡처 컨트롤 업데이트히기 - UpdateCaptureControls()

        /// <summary>
        /// 캡처 컨트롤 업데이트히기
        /// </summary>
        /// <remarks>
        /// 이 방법은 앱의 현재 상태와 장치의 기능에 따라 아이콘을 업데이트하고
        /// 사진/비디오 버튼을 활성화/비활성화하고 표시하거나/숨긴다.
        /// </remarks>
        private void UpdateCaptureControls()
        {
            // 버튼은 미리보기가 성공적으로 시작된 경우에만 활성화되어야 한다.
            this.photoButton.IsEnabled         = this.mediaEncodingProperties != null;
            this.videoButton.IsEnabled         = this.mediaEncodingProperties != null;
            this.faceDetectionButton.IsEnabled = this.mediaEncodingProperties != null;

            // 효과 유무에 따라 얼굴 탐지 아이콘을 업데이트한다.
            this.faceDetectionSymbolIcon1.Visibility =
                (this.faceDetectionEffect != null &&  this.faceDetectionEffect.Enabled) ? Visibility.Visible : Visibility.Collapsed;

            this.faceDetectionSymbolIcon2.Visibility =
                (this.faceDetectionEffect == null || !this.faceDetectionEffect.Enabled) ? Visibility.Visible : Visibility.Collapsed;

            // 얼굴 탐지 캔버스를 숨기고 지운다.
            this.faceCanvas.Visibility =
                (this.faceDetectionEffect != null && this.faceDetectionEffect.Enabled) ? Visibility.Visible : Visibility.Collapsed;

            // 레코딩시 적색 "레코딩" 아이콘 대신 "중지" 아이콘을 표시하도록 레코딩 버튼을 업데이트한다.
            this.startVideoEllipse.Visibility  = this.isRecording ? Visibility.Collapsed : Visibility.Visible;
            this.stopVideoRectangle.Visibility = this.isRecording ? Visibility.Visible   : Visibility.Collapsed;

            // 카메라가 사진 촬영과 비디오 녹화를 동시에 지원하지 않는 경우 녹화에서 사진 버튼을 비활성화한다.
            if(this.isInitialized && !this.mediaCapture.MediaCaptureSettings.ConcurrentRecordAndPhotoSupported)
            {
                this.photoButton.IsEnabled = !this.isRecording;

                // 비활성화된 경우 버튼을 보이지 않게 하여 상호 작용할 수 없음을 분명히 한다.
                this.photoButton.Opacity = this.photoButton.IsEnabled ? 1 : 0;
            }
        }

        #endregion

        #region 이벤트 핸들러 등록하기 - RegisterEventHandlers()

        /// <summary>
        /// 이벤트 핸들러 등록하기
        /// </summary>
        private void RegisterEventHandlers()
        {
            if(ApiInformation.IsTypePresent("Windows.Phone.UI.Input.HardwareButtons"))
            {
                HardwareButtons.CameraPressed += HardwareButtons_CameraPressed;
            }

            if(this.orientationSensor != null)
            {
                this.orientationSensor.OrientationChanged += orientationSensor_OrientationChanged;

                UpdateButtonOrientation();
            }

            this.displayInformation.OrientationChanged += displayInformation_OrientationChanged;

            this.systemMediaTransportControls.PropertyChanged += systemMediaTransportControls_PropertyChanged;
        }

        #endregion
        #region 이벤트 핸들러 등록취소하기 - UnregisterEventHandlers()

        /// <summary>
        /// 이벤트 핸들러 등록취소하기
        /// </summary>
        private void UnregisterEventHandlers()
        {
            if(ApiInformation.IsTypePresent("Windows.Phone.UI.Input.HardwareButtons"))
            {
                HardwareButtons.CameraPressed -= HardwareButtons_CameraPressed;
            }

            if(this.orientationSensor != null)
            {
                this.orientationSensor.OrientationChanged -= orientationSensor_OrientationChanged;
            }

            this.displayInformation.OrientationChanged -= displayInformation_OrientationChanged;

            this.systemMediaTransportControls.PropertyChanged -= systemMediaTransportControls_PropertyChanged;
        }

        #endregion

        #region UI 셋업하기 (비동기) - SetupUIAsync()

        /// <summary>
        /// UI 셋업하기 (비동기)
        /// </summary>
        /// <returns>태스크</returns>
        /// <remarks>
        /// 페이지 방향을 잠그고 상태 표시줄(전화에서)을 숨기고
        /// 하드웨어 버튼 및 방향 센서에 대해 등록된 이벤트 핸들러를 숨기려고 시도합니다.
        /// </remarks>
        private async Task SetupUIAsync()
        {
            // 더 나은 경험을 제공하므로 CaptureElement가 회전하지 않도록 페이지를 가로 방향으로 잠그도록 시도한다.
            DisplayInformation.AutoRotationPreferences = DisplayOrientations.Landscape;

            // 상태 표시줄 숨긴다.
            if(ApiInformation.IsTypePresent("Windows.UI.ViewManagement.StatusBar"))
            {
                await Windows.UI.ViewManagement.StatusBar.GetForCurrentView().HideAsync();
            }

            // 현재 상태로 방향 변수를 채운다.
            this.displayOrientations = this.displayInformation.CurrentOrientation;

            if(this.orientationSensor != null)
            {
                this.deviceOrientation = this.orientationSensor.GetCurrentOrientation();
            }
            
            RegisterEventHandlers();

            StorageLibrary picturesStorageLibrary = await StorageLibrary.GetLibraryAsync(KnownLibraryId.Pictures);

            this.captureStorageFolder = picturesStorageLibrary.SaveFolder ?? ApplicationData.Current.LocalFolder;
        }

        #endregion
        #region UI 청소하기 (비동기) - CleanupUIAsync()

        /// <summary>
        /// UI 청소하기 (비동기)
        /// </summary>
        /// <returns></returns>
        /// <remarks>
        /// 하드웨어 버튼 및 방향 센서에 대한 이벤트 핸들러를 등록취소하고,
        /// 상태 표시줄(전화기에 대한) 표시를 허용하고,
        /// 페이지 방향 잠금을 제거한다.
        /// </remarks>
        private async Task CleanupUIAsync()
        {
            UnregisterEventHandlers();

            if(ApiInformation.IsTypePresent("Windows.UI.ViewManagement.StatusBar"))
            {
                await Windows.UI.ViewManagement.StatusBar.GetForCurrentView().ShowAsync();
            }

            DisplayInformation.AutoRotationPreferences = DisplayOrientations.None;
        }

        #endregion

        #region 카메라 초기화하기 (비동기) - InitializeCameraAsync()

        /// <summary>
        /// 카메라 초기화하기 (비동기)
        /// </summary>
        /// <returns></returns>
        private async Task InitializeCameraAsync()
        {
            if(this.mediaCapture == null)
            {
                // 가능한 경우 전면 카메라를 가져오는 것을 시도하고, 그렇지 않은 경우 다른 카메라 장치를 사용한다.
                DeviceInformation cameraDeviceInformation = await GetCameraDeviceInformation
                (
                    Windows.Devices.Enumeration.Panel.Front
                );

                if(cameraDeviceInformation == null)
                {
                    return;
                }

                this.mediaCapture = new MediaCapture();

                // 동영상 녹화가 최대 시간에 도달했을 때와 문제가 발생했을 때 이벤트를 등록한다.
                this.mediaCapture.RecordLimitationExceeded += mediaCapture_RecordLimitationExceeded;
                this.mediaCapture.Failed                   += mediaCapture_Failed;

                MediaCaptureInitializationSettings settings = new MediaCaptureInitializationSettings
                {
                    VideoDeviceId = cameraDeviceInformation.Id
                };

                try
                {
                    await this.mediaCapture.InitializeAsync(settings);

                    this.isInitialized = true;
                }
                catch(UnauthorizedAccessException)
                {
                }

                if(this.isInitialized)
                {
                    if
                    (
                        cameraDeviceInformation.EnclosureLocation       == null ||
                        cameraDeviceInformation.EnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Unknown
                    )
                    {
                        this.externalCamera = true;
                    }
                    else
                    {
                        this.externalCamera = false;

                        this.mirroringPreview =
                            (cameraDeviceInformation.EnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Front);
                    }

                    await StartPreviewAsync();

                    UpdateCaptureControls();
                }
            }
        }

        #endregion
        #region 카메라 청소하기 (비동기) - CleanupCameraAsync()

        /// <summary>
        /// 카메라 청소하기 (비동기)
        /// </summary>
        /// <returns>태스크</returns>
        /// <remarks>
        /// 카메라 리소스를 정리하고(비디오 녹화 및/또는 필요한 경우 미리 보기를 중지한 후)
        /// MediaCapture 이벤트에서 등록을 취소한다.
        /// </remarks>
        private async Task CleanupCameraAsync()
        {
            if(this.isInitialized)
            {
                // 정리 중 녹음이 진행 중인 경우 중지하여 녹음을 저장한다.
                if(this.isRecording)
                {
                    await StopRecordingAsync();
                }

                if(this.faceDetectionEffect != null)
                {
                    await CleanUpFaceDetectionEffectAsync();
                }

                if(this.mediaEncodingProperties != null)
                {
                    // 미리 보기를 중지하는 호출은 완성을 위해 여기에 포함되지만
                    // 나중에 MediaCapture.Dispose()에 대한 호출이 수행되는 경우
                    // 미리 보기가 해당 지점에서 자동으로 중지되므로 안전하게 제거할 수 있다.
                    await StopPreviewAsync();
                }

                this.isInitialized = false;
            }

            if(this.mediaCapture != null)
            {
                this.mediaCapture.RecordLimitationExceeded -= mediaCapture_RecordLimitationExceeded;
                this.mediaCapture.Failed                   -= mediaCapture_Failed;

                this.mediaCapture.Dispose();

                this.mediaCapture = null;
            }
        }

        #endregion

        #region 미리보기 사각형 구하기 - GetPreviewRectangle(previewResolution, previewControl)

        /// <summary>
        /// 미리보기 사각형 구하기
        /// </summary>
        /// <param name="previewResolution">미리보기 비디오 인코딩 속성</param>
        /// <param name="previewControl">미리보기 컨트롤</param>
        /// <returns>미리보기 사각형</returns>
        /// <remarks>
        /// 크기 조정 모드가 균일일 때 미리 보기 컨트롤 내에서 미리 보기 스트림을 포함하는 사각형의 크기와 위치를 계산한다.
        /// </remarks>
        public Rect GetPreviewRectangle(VideoEncodingProperties previewResolution, CaptureElement previewControl)
        {
            Rect previewRectangle = new Rect();

            // 모든 것이 올바르게 초기화되기 전에 이 함수가 호출된 경우 빈 결과를 반환한다.
            if
            (
                previewControl == null          ||
                previewControl.ActualHeight < 1 ||
                previewControl.ActualWidth  < 1 ||
                previewResolution == null       ||
                previewResolution.Height == 0   ||
                previewResolution.Width  == 0
            )
            {
                return previewRectangle;
            }

            uint streamWidth  = previewResolution.Width;
            uint streamHeight = previewResolution.Height;

            // 세로 방향의 경우 너비와 높이를 바꿔야 한다.
            if
            (
                this.displayOrientations == DisplayOrientations.Portrait ||
                this.displayOrientations == DisplayOrientations.PortraitFlipped
            )
            {
                streamWidth  = previewResolution.Height;
                streamHeight = previewResolution.Width;
            }

            // 컨트롤의 미리보기 표시 영역이 전체 너비와 높이에 걸쳐 있다고
            // 가정하여 시작한다(필요한 치수의 경우 다음에서 수정된다).
            previewRectangle.Width  = previewControl.ActualWidth;
            previewRectangle.Height = previewControl.ActualHeight;

            // UI가 미리보기보다 "넓은" 경우 레터박스가 측면에 표시된다.
            if((previewControl.ActualWidth / previewControl.ActualHeight > streamWidth / (double)streamHeight))
            {
                double scale       = previewControl.ActualHeight / streamHeight;
                double scaledWidth = streamWidth * scale;

                previewRectangle.X     = (previewControl.ActualWidth - scaledWidth) / 2.0;
                previewRectangle.Width = scaledWidth;
            }
            else // 미리보기 스트림은 UI보다 "넓음"이므로 레터박스가 상단+하단에 표시된다.
            {
                double scale        = previewControl.ActualWidth / streamWidth;
                double scaledHeight = streamHeight * scale;

                previewRectangle.Y      = (previewControl.ActualHeight - scaledHeight) / 2.0;
                previewRectangle.Height = scaledHeight;
            }

            return previewRectangle;
        }

        #endregion
        #region 미리보기 회전 설정하기 (비동기) - SetPreviewRotationAsync()

        /// <summary>
        /// 미리보기 회전 설정하기 (비동기)
        /// </summary>
        /// <returns>태스크</returns>
        /// <remarks>
        /// 장치와 관련된 UI의 현재 방향을 가져오고(AutoRotationPreferences를 준수할 수 없는 경우)
        /// 미리 보기에 수정 회전을 적용한다.
        /// </remarks>
        private async Task SetPreviewRotationAsync()
        {
            // 카메라가 장치에 장착된 경우에만 방향을 업데이트해야 한다.
            if(this.externalCamera)
            {
                return;
            }

            // 미리보기를 회전할 방향과 거리를 계산한다.
            int rotationDegrees = GetDisplayOrientationDegrees(this.displayOrientations);

            // 미리보기가 미러링되는 경우 회전 방향을 반전해야 한다.
            if(this.mirroringPreview)
            {
                rotationDegrees = (360 - rotationDegrees) % 360;
            }

            // 미리보기 스트림에 회전 메타 데이터를 추가하여 미리보기 프레임을 렌더링하고 가져올 때
            // 종횡비/치수가 일치하도록 한다.
            IMediaEncodingProperties properties = this.mediaCapture.VideoDeviceController.GetMediaStreamProperties
            (
                MediaStreamType.VideoPreview
            );

            properties.Properties.Add(_rotationKeyGUID, rotationDegrees);

            await this.mediaCapture.SetEncodingPropertiesAsync(MediaStreamType.VideoPreview, properties, null);
        }

        #endregion
        #region 미리보기 시작하기 (비동기) - StartPreviewAsync()

        /// <summary>
        /// 미리보기 시작하기 (비동기)
        /// </summary>
        /// <returns>태스크</returns>
        /// <remarks>
        /// 미리보기를 시작하고 화면을 켜두도록 요청한 후 회전 및 미러링을 위해 조정한다.
        /// </remarks>
        private async Task StartPreviewAsync()
        {
            // 미리보기가 실행되는 동안 장치가 절전 모드로 전환되지 않도록 방지한다.
            this.displayRequest.RequestActive();

            // UI에서 미리보기 소스를 설정하고 필요한 경우 미러링을 처리한다.
            this.previewCaptureElement.Source        = this.mediaCapture;
            this.previewCaptureElement.FlowDirection = this.mirroringPreview ? FlowDirection.RightToLeft : FlowDirection.LeftToRight;

            // 미리보기를 시작한다.
            await this.mediaCapture.StartPreviewAsync();

            this.mediaEncodingProperties = this.mediaCapture.VideoDeviceController.GetMediaStreamProperties
            (
                MediaStreamType.VideoPreview
            );

            // 현재 방향으로 미리보기를 초기화한다.
            if(this.mediaEncodingProperties != null)
            {
                this.displayOrientations = this.displayInformation.CurrentOrientation;

                await SetPreviewRotationAsync();
            }
        }

        #endregion
        #region 미리보기 중단하기 (비동기) - StopPreviewAsync()

        /// <summary>
        /// 미리보기 중단하기 (비동기)
        /// </summary>
        /// <returns>태스크</returns>
        /// <remarks>
        /// 미리보기를 중지하고 화면을 절전 모드로 전환할 수 있도록 표시 요청을 비활성화한다.
        /// </remarks>
        private async Task StopPreviewAsync()
        {
            // 미리보기를 중단한다.
            this.mediaEncodingProperties = null;

            await this.mediaCapture.StopPreviewAsync();

            // 이 메소드는 UI가 아닌 스레드에서 호출되는 경우가 있으므로 디스패처를 사용합니다.
            await Dispatcher.RunAsync
            (
                CoreDispatcherPriority.Normal,
                () =>
                {
                    // UI를 청소한다.
                    this.previewCaptureElement.Source = null;

                    // 미리보기가 중지되었으므로 이제 장치 화면을 절전 모드로 전환한다.
                    this.displayRequest.RequestRelease();
                }
            );
        }

        #endregion

        #region 레코딩 시작하기 (비동기) - StartRecordingAsync()

        /// <summary>
        /// 레코딩 시작하기 (비동기)
        /// </summary>
        /// <returns></returns>
        /// <remarks>
        /// MP4 비디오를 StorageFile에 녹화하고 회전 메타데이터를 추가한다.
        /// </remarks>
        private async Task StartRecordingAsync()
        {
            try
            {
                StorageFile videoStorageFile = await this.captureStorageFolder.CreateFileAsync
                (
                    "SimpleVideo.mp4",
                    CreationCollisionOption.GenerateUniqueName
                );

                MediaEncodingProfile mediaEncodingProfile = MediaEncodingProfile.CreateMp4(VideoEncodingQuality.Auto);

                // 필요한 경우 미러링을 고려하여 회전 각도를 계산한다.
                int rotationAngle = 360 - GetDeviceOrientationDegrees(GetCameraOrientation());

                mediaEncodingProfile.Video.Properties.Add(_rotationKeyGUID, PropertyValue.CreateInt32(rotationAngle));

                await this.mediaCapture.StartRecordToStorageFileAsync(mediaEncodingProfile, videoStorageFile);

                this.isRecording = true;
            }
            catch
            {
            }
        }

        #endregion
        #region 레코딩 중단하기 (비동기) - StopRecordingAsync()

        /// <summary>
        /// 레코딩 중단하기 (비동기)
        /// </summary>
        /// <returns>태스크</returns>
        private async Task StopRecordingAsync()
        {
            this.isRecording = false;

            await this.mediaCapture.StopRecordAsync();
        }

        #endregion

        #region 사진 저장하기 (비동기) - SavePhotoAsync(stream, targetFile, photoOrientation)

        /// <summary>
        /// 사진 저장하기 (비동기)
        /// </summary>
        /// <param name="stream">랜덤 액세스 스트림</param>
        /// <param name="targetFile">타겟 저장소 파일</param>
        /// <param name="photoOrientation">사진 방향</param>
        /// <returns>태스크</returns>
        private static async Task SavePhotoAsync
        (
            IRandomAccessStream stream,
            StorageFile         targetFile,
            PhotoOrientation    photoOrientation
        )
        {
            using(IRandomAccessStream sourceStream = stream)
            {
                BitmapDecoder decoder = await BitmapDecoder.CreateAsync(sourceStream);

                using(IRandomAccessStream targetStream = await targetFile.OpenAsync(FileAccessMode.ReadWrite))
                {
                    BitmapEncoder encoder = await BitmapEncoder.CreateForTranscodingAsync(targetStream, decoder);

                    BitmapPropertySet bitmapPropertySet = new BitmapPropertySet
                    {
                        {
                            "System.Photo.Orientation",
                            new BitmapTypedValue(photoOrientation, PropertyType.UInt16)
                        }
                    };

                    await encoder.BitmapProperties.SetPropertiesAsync(bitmapPropertySet);

                    await encoder.FlushAsync();
                }
            }
        }

        #endregion
        #region 사진 촬영하기 (비동기) - TakePhotoAsync()

        /// <summary>
        /// 사진 촬영하기 (비동기)
        /// </summary>
        /// <returns>태스크</returns>
        private async Task TakePhotoAsync()
        {
            this.videoButton.IsEnabled = this.mediaCapture.MediaCaptureSettings.ConcurrentRecordAndPhotoSupported;

            this.videoButton.Opacity = this.videoButton.IsEnabled ? 1 : 0;

            InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream();

            await this.mediaCapture.CapturePhotoToStreamAsync(ImageEncodingProperties.CreateJpeg(), stream);

            try
            {
                StorageFile storageFile = await this.captureStorageFolder.CreateFileAsync
                (
                    "SimplePhoto.jpg",
                    CreationCollisionOption.GenerateUniqueName
                );

                PhotoOrientation photoOrientation = GetPhotoOrientation(GetCameraOrientation());

                await SavePhotoAsync(stream, storageFile, photoOrientation);
            }
            catch
            {
            }

            this.videoButton.IsEnabled = true;
            this.videoButton.Opacity   = 1;
        }

        #endregion

        #region 얼굴 탐지 효과 생성하기 (비동기) - CreateFaceDetectionEffectAsync()

        /// <summary>
        /// 얼굴 탐지 효과 생성하기 (비동기)
        /// </summary>
        /// <returns>태스크</returns>
        private async Task CreateFaceDetectionEffectAsync()
        {
            FaceDetectionEffectDefinition definition = new FaceDetectionEffectDefinition();

            definition.SynchronousDetectionEnabled = false;
            definition.DetectionMode               = FaceDetectionMode.HighPerformance;

            this.faceDetectionEffect = await this.mediaCapture.AddVideoEffectAsync
            (
                definition,
                MediaStreamType.VideoPreview
            ) as FaceDetectionEffect;

            this.faceDetectionEffect.FaceDetected += faceDetectionEffect_FaceDetected;

            this.faceDetectionEffect.DesiredDetectionInterval = TimeSpan.FromMilliseconds(33);

            this.faceDetectionEffect.Enabled = true;
        }

        #endregion
        #region 얼굴 탐지 효과 청소하기 (비동기) - CleanUpFaceDetectionEffectAsync()

        /// <summary>
        /// 얼굴 탐지 효과 청소하기 (비동기)
        /// </summary>
        /// <returns>태스크</returns>
        /// <remarks>
        /// 얼굴 탐지 효과를 비활성화 및 제거하고 얼굴 탐지를 위한 이벤트 처리기를 등록 취소한다.
        /// </remarks>
        private async Task CleanUpFaceDetectionEffectAsync()
        {
            this.faceDetectionEffect.Enabled = false;

            this.faceDetectionEffect.FaceDetected -= faceDetectionEffect_FaceDetected;

            await this.mediaCapture.RemoveEffectAsync(this.faceDetectionEffect);

            this.faceDetectionEffect = null;
        }

        #endregion
        #region 얼굴 캔버스 회전 설정하기 - SetFaceCanvasRotation()

        /// <summary>
        /// 얼굴 캔버스 회전 설정하기
        /// </summary>
        /// <remarks>
        /// 현재 표시 방향을 사용하여 얼굴 탐지 캔버스에 적용할 회전 변환을 계산하고
        /// 미리보기가 미러링되는 경우 미러링한다.
        /// </remarks>
        private void SetFaceCanvasRotation()
        {
            // 캔버스를 얼마나 회전시킬지 계산한다.
            int rotationDegrees = GetDisplayOrientationDegrees(this.displayOrientations);

            // SetPreviewRotationAsync와 마찬가지로 미리보기가 미러링되는 경우 회전 방향을 반전해야 한다.
            if(this.mirroringPreview)
            {
                rotationDegrees = (360 - rotationDegrees) % 360;
            }

            // 회전을 적용한다.
            RotateTransform transform = new RotateTransform { Angle = rotationDegrees };

            this.faceCanvas.RenderTransform = transform;

            Rect previewRect = GetPreviewRectangle
            (
                this.mediaEncodingProperties as VideoEncodingProperties,
                this.previewCaptureElement
            );

            // 세로 모드 방향의 경우 회전 후 캔버스의 너비와 높이를 바꿔 컨트롤이 계속 미리보기와 겹치도록 한다.
            if
            (
                this.displayOrientations == DisplayOrientations.Portrait ||
                this.displayOrientations == DisplayOrientations.PortraitFlipped
            )
            {
                this.faceCanvas.Width  = previewRect.Height;
                this.faceCanvas.Height = previewRect.Width;

                // 크기 조정이 컨트롤의 중앙에 영향을 미치므로 캔버스의 위치도 조정해야 한다.
                Canvas.SetLeft(this.faceCanvas, previewRect.X - (previewRect.Height - previewRect.Width ) / 2);
                Canvas.SetTop (this.faceCanvas, previewRect.Y - (previewRect.Width  - previewRect.Height) / 2);
            }
            else
            {
                this.faceCanvas.Width  = previewRect.Width;
                this.faceCanvas.Height = previewRect.Height;

                Canvas.SetLeft(this.faceCanvas, previewRect.X);
                Canvas.SetTop (this.faceCanvas, previewRect.Y);
            }

            // 미리보기가 미러링되는 경우 캔버스도 미러링한다.
            this.faceCanvas.FlowDirection = this.mirroringPreview ? FlowDirection.RightToLeft : FlowDirection.LeftToRight;
        }

        #endregion
        #region 얼굴 사각형 구하기 - GetFaceRectangle(faceBitmapBounds)

        /// <summary>
        /// 얼굴 사각형 구하기
        /// </summary>
        /// <param name="faceBitmapBounds">얼굴 비트맵 테두리</param>
        /// <returns>얼굴 사각형</returns>
        /// <remarks>
        /// 미리보기 좌표에 정의된 얼굴 정보를 가져와 미리보기 컨트롤의 위치와 크기를 고려하여 UI 좌표로 반환한다.
        /// </remarks>
        private Rectangle GetFaceRectangle(BitmapBounds faceBitmapBounds)
        {
            Rectangle rectangle = new Rectangle();

            VideoEncodingProperties previewProperties = this.mediaEncodingProperties as VideoEncodingProperties;

            // 미리보기에 대한 사용 가능한 정보가 없으면 화면 좌표로 다시 크기 조정이 불가능하므로 빈 사각형을 반환한다.
            if(previewProperties == null)
            {
                return rectangle;
            }

            // 유사하게, 차원 중 하나라도 0이면(오류 경우에만 발생) 빈 사각형을 반환한다.
            if(previewProperties.Width == 0 || previewProperties.Height == 0)
            {
                return rectangle;
            }

            double streamWidth  = previewProperties.Width;
            double streamHeight = previewProperties.Height;

            // 세로 방향의 경우 너비와 높이를 바꿔야 한다.
            if
            (
                this.displayOrientations == DisplayOrientations.Portrait ||
                this.displayOrientations == DisplayOrientations.PortraitFlipped
            )
            {
                streamHeight = previewProperties.Width;
                streamWidth  = previewProperties.Height;
            }

            // 실제 비디오 피드가 차지하는 사각형을 가져온다.
            Rect previewInUI = GetPreviewRectangle(previewProperties, previewCaptureElement);

            // 미리보기 스트림 좌표에서 창 좌표로 너비 및 높이를 크기 조정한다.
            rectangle.Width  = (faceBitmapBounds.Width  / streamWidth ) * previewInUI.Width;
            rectangle.Height = (faceBitmapBounds.Height / streamHeight) * previewInUI.Height;

            // 미리보기 스트림 좌표에서 창 좌표로 X 및 Y 좌표를 크기 조정한다.
            double x = (faceBitmapBounds.X / streamWidth ) * previewInUI.Width;
            double y = (faceBitmapBounds.Y / streamHeight) * previewInUI.Height;

            Canvas.SetLeft(rectangle, x);
            Canvas.SetTop (rectangle, y);

            return rectangle;
        }

        #endregion
        #region 탐지 얼굴 하이라이트 처리하기 - HighlightDetectedFaces(detectedFaceList)

        /// <summary>
        /// 탐지 얼굴 하이라이트 처리하기
        /// </summary>
        /// <param name="detectedFaceList">탐지 얼굴 리스트</param>
        /// <remarks>
        /// 탐지된 모든 얼굴에 대해 반복하여 Rectangle을 생성하고 FaceCanvas에 얼굴 탐지 상자로 추가한다.
        /// </remarks>
        private void HighlightDetectedFaces(IReadOnlyList<DetectedFace> detectedFaceList)
        {
            // 이전 이벤트에서 기존 사각형을 제거한다.
            this.faceCanvas.Children.Clear();

            for(int i = 0; i < detectedFaceList.Count; i++)
            {
                // 얼굴 좌표 단위는 미리보기 해상도 픽셀로 디스플레이 해상도와 스케일이 다를 수 있으므로 변환이 필요할 수 있다.
                Rectangle faceRectangle = GetFaceRectangle(detectedFaceList[i].FaceBox);

                faceRectangle.StrokeThickness = 2;
                faceRectangle.Stroke          = (i == 0 ? new SolidColorBrush(Colors.Blue) : new SolidColorBrush(Colors.DeepSkyBlue));

                this.faceCanvas.Children.Add(faceRectangle);
            }

            // 얼굴 캔버스 회전을 설정한다.
            SetFaceCanvasRotation();
        }

        #endregion
    }
}

 

300x250

 

▶ App.xaml

<Application x:Class="TestProject.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
</Application>

 

▶ App.xaml.cs

using System;
using System.Diagnostics;
using Windows.ApplicationModel.Activation;
using Windows.Globalization;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

namespace TestProject
{
    /// <summary>
    /// 앱
    /// </summary>
    sealed partial class App : Application
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - App()

        /// <summary>
        /// 생성자
        /// </summary>
        public App()
        {
            InitializeComponent();
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Protected

        #region 시작시 처리하기 - OnLaunched(e)

        /// <summary>
        /// 시작시 처리하기
        /// </summary>
        /// <param name="e">이벤트 인자</param>
        protected override void OnLaunched(LaunchActivatedEventArgs e)
        {
#if DEBUG
            if(Debugger.IsAttached)
            {
                DebugSettings.EnableFrameRateCounter = true;
            }
#endif

            Frame rootFrame = Window.Current.Content as Frame;

            if(rootFrame == null)
            {
                rootFrame = new Frame();

                rootFrame.Language = ApplicationLanguages.Languages[0];

                rootFrame.NavigationFailed += rootFrame_NavigationFailed;

                Window.Current.Content = rootFrame;
            }

            if(rootFrame.Content == null)
            {
                rootFrame.Navigate(typeof(MainPage), e.Arguments);
            }

            Window.Current.Activate();
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 루트 프레임 네비게이션 실패시 처리하기 - rootFrame_NavigationFailed(sender, e)

        /// <summary>
        /// 루트 프레임 네비게이션 실패시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        void rootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
        {
            throw new Exception($"페이지 로드를 실패했습니다 : {e.SourcePageType.FullName}");
        }

        #endregion
    }
}
728x90
반응형
그리드형(광고전용)
Posted by icodebroker

댓글을 달아 주세요