안녕하세요. 너만바라보면 입니다.

저번시간에는 윈도우폰7 망고에서 새롭게 추가된 기능인 멀티태스킹 기능을 Tidy앱을 통해 구현해 보았습니다. 조금은 어려웠죠?

이번시간에는 윈도우폰7카메라를 더 효과적으로 활용할 수 잇는 방법을 알아보겠습니다.

기존의 윈도우폰7 7.0 버전에서는 프로그램과 카메라가 서로 연결될 수 있는 방법은 CameraCaptureTask Selector 를 사용하는 것이었습니다. , 내장카메라 및 스냅샷 촬영을 할 때 CameraCaptureTask Selector를 사용하였는데요. 망고버전으로 바뀌면서 카메라 제어기능을 제공하는 클래스가 들어있습니다. 바로 Microsoft.Device.PhtoCamera 라는 클래스이죠.

이 클래스로 인해 이제 Selector 따위는 사용을 안해도 되고 또한 아주 정밀하고 로우레벨 단계에 까지 직접 접근이 가능하게 되었습니다.

이번 시간에는 카메라로 투영된 원본 데이터를 응용 프로그램에서 캡쳐하여 보여주고 카메라를 사용해 사진 촬영하는 기능을 구현해 보도록 하겠습니다.

 

우선 첨부되어 있는 파일을 받아서 시작해 봅시다.

첨부된 파일은 CameraApp 프로젝트 명을 가진 애플리케이션으로 우리는 이 애플리케이션에서 Microsoft.Devices.PhotoCamera 클래스를 사용하는 방법에 대해서 알아볼 것입니다.

우리가 예를 들어 메신저를 만드는데 이 메신저에서 상대방에게 나의 사진을 찍어 보낸다고 생각해 봅시다. 그렇다면 응용프로그램이 폰에 있는 카메라를 찾아서 이 카메라를 연결시켜 주고 카메라가 찍은 데이터를 상대방에게 보내주면 되는것이겠죠?

윈도우폰에서는 응용프로그램이 PhoneApplicationPage OnNavigatedTo OnNavigatedFrom 메서드를 호출합니다. 우리가 카메라로 촬영한 사진이나 동영상등을 이러한 두 메서드를 사용하여 카메라에 접근해 어플리케이션에서 사진, 동영상등을 미리보기 할 수 있습니다. 결국 이 두 메서드를 사용하는 것이 미리보기를 활용하는데 가장 적합한 메서드라고 말씀 드릴 수 있겠습니다.

OnNavigatedTo 메서드에서 PhotoCamera 클래스의 인스턴스를 만들어서 이를  OnNavigatedFrom 오버라이드로 보내는 구조가 됩니다.

 

참고로 본 실습은 윈도우폰7 에뮬레이터에서는 정상적으로 동작이 불가하니 이점 양해바랍니다. (에뮬레이터에는 카메라가 없기 때문입니다. )

 

public partial class MainPage : PhoneApplicationPage

    {

        #region Members

 

        private PhotoCamera camera;

        private PhotofunDataContext PhotofunDataContext

        {

            get { return this.DataContext as PhotofunDataContext; }

        }

public MainPage()

        {

            InitializeComponent();

        }

}

CameraApp MainPage.xaml.cs 파일을 열고나서 필드 선언을 해줍니다.

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)

        {

            if(null == camera)

            {

                camera = new PhotoCamera();

camera.Initialized += camera_Initialized;

 

            }

            base.OnNavigatedTo(e);

        }

 

 

그다음에는 미리보기 페이지가 활성화 될 때 PhotoCamera 클래스의 인스턴스를 만들도록

OnNavigatedTo, 메서드를 재정의 시킵니다. 그런데 중요한건 카메라가 초기화 될때가지는

시간이 조금 걸리겠죠? 초기화를 시키고 나서 PhotoCamera 클래스가 Initialized 이벤트가

실행될 수 있도록 이벤트 핸들러를 연결합시다. 더 자세한 설정은 뒤에서 할것이니까 우선

지금은 연결만 해놓자구요.

 

 

 

 

protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)

        {

 

            camera.Dispose();

            camera = null;

 

            base.OnNavigatedFrom(e);

        }

 

미리보기 페이지를 볼 필요가 없을때는 카메라를 굳이 켤 필요가 없겠죠? 그래서 OnNavigatedFrom에서는 카메라를 사라지게 만듭니다.

 

private void camera_Initialized(object sender, CameraOperationCompletedEventArgs e)

        {

            if(e.Succeeded)

            {

                var res = from resolution in camera.AvailableResolutions

                          where resolution.Width == 640

                          select resolution;

 

                camera.Resolution = res.First();

                this.Dispatcher.BeginInvoke(delegate()

                {

                    EffectSelected();

                });

            }

        }

 

위의 화면이 camera_Initialized에 관련된 설정입니다. 이벤트 핸들러에서 카메라 해상도를 지정해

줘야 합니다. 가장 낮은 해상도를 사용하는 것이 성능을 높이고 속도가 빠르겠죠?

정리를 하자면 미리보기가 실행되면 OnNavigatedTo가 실행되면서 카메라를 불러오게 되고 이때 바로 camera_Initialized 이벤트 핸들러를 열면서 카메라 해상도 지정을 하게 됩니다.

 

 

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)

        {

            if(null == camera)

            {

                camera = new PhotoCamera();

camera.Initialized += camera_Initialized;

viewfinderBrush.SetSource(camera);

 

 

            }

            base.OnNavigatedTo(e);

        }

 

 

 

우리가 카메라로 투영된 리소스(사진,동영상등)를 보여주기 위해서는 ViewFinderBrush 라고 하는 VideoBrush를 사용해야 합니다. 카메라를 조금 다루시는 분들은 아시겠지만 우리가 사진촬영을 할 때 뷰파인더(ViewFinder)를 통해 보이는 사물을 촬영하게 되죠? 핸드폰 카메라에서는 이 뷰파인더 라는것이 따로 없기 때문에 애플리케이션에서 취급해주어야 합니다. VideoBrush는 이미 Xaml에 선언되어 있습니다.  위 형광펜으로 칠해져 있는 부분을 작성해 줍니다.

<Capabilities>

<Capability Name="ID_CAP_ISV_CAMERA" />

    </Capabilities>

 

 

Properties에 가서 WMAppManifest.xaml 파일을 여세요. 우리는 이 파일을 수정함으로써 응용프로그램이 카메라를 사용할 있도록 권한을 부여를 할 것입니다. 만약 여기서 위와같이 추가하지 않으면 응용프로그램에서 카메라로 연결하려 할 때 에러메시지를 띄웁니다. 권한이 없기 때문이죠. 쉽게 생각하시면 제한구역에 출입할 수 있도록 출입허가증을 부여받는 것이라 생각하시면 됩니다. 출입허가증이 있으면 들어갈 수 있지만 출입허가증이 없으면 들어갈 수 없는것이죠. 들어가기 원하던 안 원하던 말입니다.

 

 

여기까지 우리가 응용 프로그램 차원에서 카메라를 불러와 원래 카메라로 투영된 리소스를 미리보기로 보여줄 수 있게 되는 것 까지 구현하였습니다.즉 우리가 윈도우폰을 이용해 촬영할 이미지를 폰에서 미리 볼 수 있게 하게끔 구현한 셈이죠.

이제 사용자가 카메라 버튼을 눌렀을 때 카메라의 ButtonFullPress 이벤트를 통해 이미지 캡쳐작업을 시작해보도록 합시다.

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)

        {

            if(null == camera)

            {

                camera = new PhotoCamera();

 

                camera.Initialized += camera_Initialized;

 

               

                CameraButtons.ShutterKeyPressed += camera_ButtonFullPress;

 

               

                viewfinderBrush.SetSource(camera);

            }

 

위의 코드처럼 카메라의 버튼을 누르면 ButtonFullPress를 실행하도록 하죠.

private void camera_ButtonFullPress(object sender, EventArgs e)

        {

            if(capturingImage)

                return;

 

            capturingImage = true;

 

            camera.CaptureImage();

        }

 

그다음에는 이미지 캡쳐를 시작하도록 카메라에 알려줘야 합니다. 카메라 버튼을 누르면 이미지가 캡쳐 되도록 말이죠.

 

 

 

 

 

 

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)

        {

            if(null == camera)

            {

                camera = new PhotoCamera();

 

                camera.Initialized += camera_Initialized;

 

               

                CameraButtons.ShutterKeyHalfPressed += camera_ButtonHalfPress;

 

               

                CameraButtons.ShutterKeyPressed += camera_ButtonFullPress;

           

                camera.CaptureImageAvailable += camera_CaptureImageAvailable;

 

 

                viewfinderBrush.SetSource(camera);

            }

 

카메라가 이미지를 촬영한 뒤에는 이 이미지가 촬영되었다고 알려줘야 겠죠? CaptureimageAvailable 이벤트를 만들어 줘야 합니다. 촬영된 이미지를 응용프로그램에 전달하기 위해서는 스트림 형식으로 전달이 됩니다.

private void camera_CaptureImageAvailable(object sender, ContentReadyEventArgs e)

        {

            Dispatcher.BeginInvoke(delegate()

            {

                SavePicture(e.ImageStream);

            });

        }

CaptureImageAvailalbe 이벤트 핸들러는 캡쳐된 이미지를 스트림 형식으로 가져와서 SavePicture로 보냅니다. 캡쳐된 이미지를 저장해야 되기 때문입니다.

 

 

 

 

 

 

 

 

private void SavePicture(Stream imageStream)

        {

            WriteableBitmap bitmap = CreateWriteableBitmap(imageStream);

 

            WriteableBitmap processedBitmap = ProcessCapturedImage(bitmap);

 

            PhotofunDataContext.Previews.Add(processedBitmap);

 

            SaveCapturedImage(processedBitmap);

        }

 

        private WriteableBitmap CreateWriteableBitmap(Stream imageStream)

        {

            WriteableBitmap bitmap = new WriteableBitmap((int)camera.Resolution.Width, (int)camera.Resolution.Height);

 

            imageStream.Position = 0;

            

            bitmap.LoadJpeg(imageStream);

            return bitmap;

        }

 

 

SavePicture 메서드의 역할은 이미지를 Stream 형식으로 가져와서 WriteableBitmap 으로 변환을 합니다. 이렇게 변환된 이미지는 미디어라이브러리에 저장이 되는데요. WriteableBitmap DependencyObject 에서 파생되 나온것입니다. 또한 이 메서드는 UI를 업데이트 하는데요. UI DependencyObjects Dispathcer 스레드를 통해서만 접근이 가능하기 때문에 CaptureImageAvailalbe 이벤트 핸들러는 디스패처(Dispatcher)를 사용해 SavePicture 메서드를 불러오게 됩니다.

 

private void SaveCapturedImage(WriteableBitmap imageToSave)

        {

            MemoryStream stream = new MemoryStream();

            imageToSave.SaveJpeg(stream, imageToSave.PixelWidth, imageToSave.PixelHeight, 0, 100);

 

                        stream.Position = 0;

 

           

            MediaLibrary library = new MediaLibrary();

            string fileName = string.Format("{0:yyyy-MM-dd-HH-mm-ss}.jpg", DateTime.Now);

            library.SavePicture(fileName, stream);

        }

 

SaveCaptureImage  메서드는 임시로 MemoryStream을 만듭니다. 그러면 이 캡쳐된 이미지를 WriteableBitmap에서 Stream으로 변환하여 이미지 데이터를 로드하고 미디어 라이브러리에 이 스트림을 보내게 되죠.

<Capabilities>

      <Capability Name="ID_CAP_MEDIALIB" />

      <Capability Name="ID_CAP_ISV_CAMERA" />

    </Capabilities>

 

여기서 의문점이 하나 드실 수 있습니다. 그렇다면 미디어라이브러리에 접근하는 것은 맘대로 가능한가요? 허가가 있어야 되지 않나요? 라는 의문이 말이죠. 이런 의문이 드셨다면 여러분은 천재(?)입니다. 당연히  WMAppMenifest.xml 파일에서 허가를 받아야 합니다. 위와 같이 기능을 추가합니다.

 

           private void SavePicture(Stream imageStream)

        {

            WriteableBitmap bitmap = CreateWriteableBitmap(imageStream);

 

            WriteableBitmap processedBitmap = ProcessCapturedImage(bitmap);

 

            PhotofunDataContext.Previews.Add(processedBitmap);

 

            SaveCapturedImage(processedBitmap);

        }

사용자가 스냅샷을 찍을때마다 우리는 UI에 미리보기를 보여줘야 합니다. 보여주지 않으면 어떤 것을 찍었는지 알 수가 없죠? 그래서 이를 보여주기 위해 PhotofunDataContext 클래스의 Preview 속성에 데이터 바인딩을 추가하였습니다.

이번 시간에는 카메라로 투영된 원본 데이터를 응용 프로그램에서 캡쳐하여 보여주고 카메라를 사용해 사진 촬영하는 기능을 구현해 보았습니다. 사실 에뮬레이터에서는 카메라 기능이 없으므로 이를 적용하여 실습하기가 어렵습니다. 하지만 스마트폰의 가장 중요하고 기본적인 기능인 카메라를 로우레벨 단계까지 접근이 가능하고 이를 이용할 줄 안다면 여러분들의 무한한 상상력이 보태져서 더욱 멋진 애플리케이션이 나올것이라 믿습니다.

이번시간에도 열독해주셔서 감사합니다. 다음시간에 뵙겠습니다.





안녕하세요.윈도우폰에 관심이 많은 너만바라보면 입니다.