1//*********************************************************
2//
3// Copyright (c) Microsoft. All rights reserved.
4// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
5// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
6// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
7// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
8//
9//*********************************************************
10
11//
12// AdvancedCapture.xaml.cpp
13// Implementation of the AdvancedCapture class
14//
15
16#include "pch.h"
17#include "AdvancedCapture.xaml.h"
18
19using namespace SDKSample::MediaCapture;
20
21using namespace Windows::UI::Xaml;
22using namespace Windows::UI::Xaml::Navigation;
23using namespace Windows::UI::Xaml::Data;
24using namespace Windows::System;
25using namespace Windows::Foundation;
26using namespace Windows::Foundation::Collections;
27using namespace Platform;
28using namespace Windows::UI;
29using namespace Windows::UI::Core;
30using namespace Windows::UI::Xaml;
31using namespace Windows::UI::Xaml::Controls;
32using namespace Windows::UI::Xaml::Data;
33using namespace Windows::UI::Xaml::Media;
34using namespace Windows::Storage;
35using namespace Windows::Media::MediaProperties;
36using namespace Windows::Storage::Streams;
37using namespace Windows::System;
38using namespace Windows::UI::Xaml::Media::Imaging;
39using namespace Windows::Devices::Enumeration;
40
41ref class ReencodeState sealed
42{
43public:
44    ReencodeState()
45    {
46    }
47
48    virtual ~ReencodeState()
49    {
50        if (InputStream != nullptr)
51        {
52            delete InputStream;
53        }
54        if (OutputStream != nullptr)
55        {
56            delete OutputStream;
57        }
58    }
59
60internal:
61    Windows::Storage::Streams::IRandomAccessStream ^InputStream;
62    Windows::Storage::Streams::IRandomAccessStream ^OutputStream;
63    Windows::Storage::StorageFile ^PhotoStorage;
64    Windows::Graphics::Imaging::BitmapDecoder ^Decoder;
65    Windows::Graphics::Imaging::BitmapEncoder ^Encoder;
66};
67
68AdvancedCapture::AdvancedCapture()
69{
70    InitializeComponent();
71    ScenarioInit();
72}
73
74/// <summary>
75/// Invoked when this page is about to be displayed in a Frame.
76/// </summary>
77/// <param name="e">Event data that describes how this page was reached.  The Parameter
78/// property is typically used to configure the page.</param>
79void AdvancedCapture::OnNavigatedTo(NavigationEventArgs^ e)
80{
81    // A pointer back to the main page.  This is needed if you want to call methods in MainPage such
82    // as NotifyUser()
83    rootPage = MainPage::Current;
84
85    m_orientationChangedEventToken = Windows::Graphics::Display::DisplayProperties::OrientationChanged += ref new Windows::Graphics::Display::DisplayPropertiesEventHandler(this, &AdvancedCapture::DisplayProperties_OrientationChanged);
86}
87
88void AdvancedCapture::OnNavigatedFrom(NavigationEventArgs^ e)
89{
90    Windows::Media::MediaControl::SoundLevelChanged -= m_eventRegistrationToken;
91    Windows::Graphics::Display::DisplayProperties::OrientationChanged  -= m_orientationChangedEventToken;
92}
93
94void  AdvancedCapture::ScenarioInit()
95{
96    rootPage = MainPage::Current;
97    btnStartDevice2->IsEnabled = true;
98    btnStartPreview2->IsEnabled = false;
99    m_bRecording = false;
100    m_bPreviewing = false;
101    m_bEffectAdded = false;
102    previewElement2->Source = nullptr;
103    ShowStatusMessage("");
104    EffectTypeCombo->IsEnabled = false;
105    previewCanvas2->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
106    EnumerateWebcamsAsync();
107    m_bSuspended = false;
108}
109
110void AdvancedCapture::ScenarioReset()
111{
112    previewCanvas2->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
113    ScenarioInit();
114}
115
116void AdvancedCapture::Failed(Windows::Media::Capture::MediaCapture ^currentCaptureObject, Windows::Media::Capture::MediaCaptureFailedEventArgs^ currentFailure)
117{
118    String ^message = "Fatal error" + currentFailure->Message;
119    create_task(Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::High,
120        ref new Windows::UI::Core::DispatchedHandler([this, message]()
121    {
122        ShowStatusMessage(message);
123    })));
124}
125
126void AdvancedCapture::btnStartDevice_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
127{
128    try
129    {
130        EnableButton(false, "StartDevice");
131        ShowStatusMessage("Starting device");
132        auto mediaCapture = ref new Windows::Media::Capture::MediaCapture();
133        m_mediaCaptureMgr = mediaCapture;
134        auto settings = ref new Windows::Media::Capture::MediaCaptureInitializationSettings();
135        auto chosenDevInfo = m_devInfoCollection->GetAt(EnumedDeviceList2->SelectedIndex);
136        settings->VideoDeviceId = chosenDevInfo->Id;
137        if (chosenDevInfo->EnclosureLocation != nullptr && chosenDevInfo->EnclosureLocation->Panel == Windows::Devices::Enumeration::Panel::Back)
138        {
139            m_bRotateVideoOnOrientationChange = true;
140            m_bReversePreviewRotation = false;
141        }
142        else if (chosenDevInfo->EnclosureLocation != nullptr && chosenDevInfo->EnclosureLocation->Panel == Windows::Devices::Enumeration::Panel::Front)
143        {
144            m_bRotateVideoOnOrientationChange = true;
145            m_bReversePreviewRotation = true;
146        }
147        else
148        {
149            m_bRotateVideoOnOrientationChange = false;
150        }
151
152        create_task(mediaCapture->InitializeAsync(settings)).then([this](task<void> initTask)
153        {
154            try
155            {
156                initTask.get();
157
158                auto mediaCapture =  m_mediaCaptureMgr.Get();
159
160                DisplayProperties_OrientationChanged(nullptr);
161
162                EnableButton(true, "StartPreview");
163                EnableButton(true, "StartStopRecord");
164                EnableButton(true, "TakePhoto");
165                ShowStatusMessage("Device initialized successful");
166                EffectTypeCombo->IsEnabled = true;
167                mediaCapture->Failed += ref new Windows::Media::Capture::MediaCaptureFailedEventHandler(this, &AdvancedCapture::Failed);
168            }
169            catch (Exception ^ e)
170            {
171                ShowExceptionMessage(e);
172            }
173        });
174    }
175    catch (Platform::Exception^ e)
176    {
177        ShowExceptionMessage(e);
178    }
179}
180
181void AdvancedCapture::btnStartPreview_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
182{
183    m_bPreviewing = false;
184    try
185    {
186        ShowStatusMessage("Starting preview");
187        EnableButton(false, "StartPreview");
188
189        auto mediaCapture = m_mediaCaptureMgr.Get();
190        previewCanvas2->Visibility = Windows::UI::Xaml::Visibility::Visible;
191        previewElement2->Source = mediaCapture;
192        create_task(mediaCapture->StartPreviewAsync()).then([this](task<void> previewTask)
193        {
194            try
195            {
196                previewTask.get();
197                m_bPreviewing = true;
198                ShowStatusMessage("Start preview successful");
199            }
200            catch (Exception ^e)
201            {
202                ShowExceptionMessage(e);
203            }
204        });
205    }
206    catch (Platform::Exception^ e)
207    {
208        m_bPreviewing = false;
209        previewElement2->Source = nullptr;
210        EnableButton(true, "StartPreview");
211        ShowExceptionMessage(e);
212    }
213}
214
215void AdvancedCapture::lstEnumedDevices_SelectionChanged(Platform::Object^ sender, Windows::UI::Xaml::Controls::SelectionChangedEventArgs^ e)
216{
217     if ( m_bPreviewing )
218     {
219         create_task(m_mediaCaptureMgr->StopPreviewAsync()).then([this](task<void> previewTask)
220         {
221             try
222             {
223                 previewTask.get();
224                 m_bPreviewing = false;
225             }
226             catch (Exception ^e)
227             {
228                ShowExceptionMessage(e);
229             }
230         });
231    }
232
233    btnStartDevice2->IsEnabled = true;
234    btnStartPreview2->IsEnabled = false;
235    m_bRecording = false;
236    previewElement2->Source = nullptr;
237    EffectTypeCombo->IsEnabled = false;
238    m_bEffectAdded = false;
239    m_bEffectAddedToRecord = false;
240    m_bEffectAddedToPhoto = false;
241    ShowStatusMessage("");
242}
243
244void AdvancedCapture::EnumerateWebcamsAsync()
245{
246    try
247    {
248        ShowStatusMessage("Enumerating Webcams...");
249        m_devInfoCollection = nullptr;
250
251        EnumedDeviceList2->Items->Clear();
252
253        task<DeviceInformationCollection^>(DeviceInformation::FindAllAsync(DeviceClass::VideoCapture)).then([this](task<DeviceInformationCollection^> findTask)
254        {
255            try
256            {
257                m_devInfoCollection = findTask.get();
258                if (m_devInfoCollection == nullptr || m_devInfoCollection->Size == 0)
259                {
260                    ShowStatusMessage("No WebCams found.");
261                }
262                else
263                {
264                    for(unsigned int i = 0; i < m_devInfoCollection->Size; i++)
265                    {
266                        auto devInfo = m_devInfoCollection->GetAt(i);
267                        EnumedDeviceList2->Items->Append(devInfo->Name);
268                    }
269                    EnumedDeviceList2->SelectedIndex = 0;
270                    ShowStatusMessage("Enumerating Webcams completed successfully.");
271                    btnStartDevice2->IsEnabled = true;
272                }
273            }
274            catch (Exception ^e)
275            {
276                ShowExceptionMessage(e);
277            }
278        });
279    }
280    catch (Platform::Exception^ e)
281    {
282        ShowExceptionMessage(e);
283    }
284}
285
286void AdvancedCapture::AddEffectToImageStream()
287{
288    auto mediaCapture = m_mediaCaptureMgr.Get();
289    Windows::Media::Capture::VideoDeviceCharacteristic charecteristic = mediaCapture->MediaCaptureSettings->VideoDeviceCharacteristic;
290
291    if((charecteristic != Windows::Media::Capture::VideoDeviceCharacteristic::AllStreamsIdentical) &&
292        (charecteristic != Windows::Media::Capture::VideoDeviceCharacteristic::PreviewPhotoStreamsIdentical) &&
293        (charecteristic != Windows::Media::Capture::VideoDeviceCharacteristic::RecordPhotoStreamsIdentical))
294    {
295        Windows::Media::MediaProperties::IMediaEncodingProperties ^props = mediaCapture->VideoDeviceController->GetMediaStreamProperties(Windows::Media::Capture::MediaStreamType::Photo);
296        if(props->Type->Equals("Image"))
297        {
298            //Switch to a video media type instead since we cant add an effect to a image media type
299            Windows::Foundation::Collections::IVectorView<Windows::Media::MediaProperties::IMediaEncodingProperties^>^ supportedPropsList = mediaCapture->VideoDeviceController->GetAvailableMediaStreamProperties(Windows::Media::Capture::MediaStreamType::Photo);
300            {
301                unsigned int i = 0;
302                while (i < supportedPropsList->Size)
303                {
304                    Windows::Media::MediaProperties::IMediaEncodingProperties^ props = supportedPropsList->GetAt(i);
305
306                    String^ s = props->Type;
307                    if(props->Type->Equals("Video"))
308                    {
309                        task<void>(mediaCapture->VideoDeviceController->SetMediaStreamPropertiesAsync(Windows::Media::Capture::MediaStreamType::Photo,props)).then([this](task<void> changeTypeTask)
310                        {
311                            try
312                            {
313                                changeTypeTask.get();
314                                ShowStatusMessage("Change type on photo stream successful");
315                                //Now add the effect on the image pin
316                                task<void>(m_mediaCaptureMgr->AddEffectAsync(Windows::Media::Capture::MediaStreamType::Photo,"OcvTransform.OcvImageManipulations", nullptr)).then([this](task<void> effectTask3)
317                                {
318                                    try
319                                    {
320                                        effectTask3.get();
321                                        m_bEffectAddedToPhoto = true;
322                                        ShowStatusMessage("Adding effect to photo stream successful");
323                                        EffectTypeCombo->IsEnabled = true;
324
325                                    }
326                                    catch(Exception ^e)
327                                    {
328                                        ShowExceptionMessage(e);
329                                        EffectTypeCombo->IsEnabled = true;
330                                    }
331                                });
332
333                            }
334                            catch(Exception ^e)
335                            {
336                                ShowExceptionMessage(e);
337                                EffectTypeCombo->IsEnabled = true;
338                            }
339
340                        });
341                        break;
342
343                    }
344                    i++;
345                }
346            }
347        }
348        else
349        {
350            //Add the effect to the image pin if the type is already "Video"
351            task<void>(mediaCapture->AddEffectAsync(Windows::Media::Capture::MediaStreamType::Photo,"OcvTransform.OcvImageManipulations", nullptr)).then([this](task<void> effectTask3)
352            {
353                try
354                {
355                    effectTask3.get();
356                    m_bEffectAddedToPhoto = true;
357                    ShowStatusMessage("Adding effect to photo stream successful");
358                    EffectTypeCombo->IsEnabled = true;
359
360                }
361                catch(Exception ^e)
362                {
363                    ShowExceptionMessage(e);
364                    EffectTypeCombo->IsEnabled = true;
365                }
366            });
367        }
368    }
369}
370
371void AdvancedCapture::ShowStatusMessage(Platform::String^ text)
372{
373    rootPage->NotifyUser(text, NotifyType::StatusMessage);
374}
375
376void AdvancedCapture::ShowExceptionMessage(Platform::Exception^ ex)
377{
378    rootPage->NotifyUser(ex->Message, NotifyType::ErrorMessage);
379}
380
381void AdvancedCapture::EnableButton(bool enabled, String^ name)
382{
383    if (name->Equals("StartDevice"))
384    {
385        btnStartDevice2->IsEnabled = enabled;
386    }
387    else if (name->Equals("StartPreview"))
388    {
389        btnStartPreview2->IsEnabled = enabled;
390    }
391}
392
393task<Windows::Storage::StorageFile^> AdvancedCapture::ReencodePhotoAsync(
394    Windows::Storage::StorageFile ^tempStorageFile,
395    Windows::Storage::FileProperties::PhotoOrientation photoRotation)
396{
397    ReencodeState ^state = ref new ReencodeState();
398
399    return create_task(tempStorageFile->OpenAsync(Windows::Storage::FileAccessMode::Read)).then([state](Windows::Storage::Streams::IRandomAccessStream ^stream)
400    {
401        state->InputStream = stream;
402        return Windows::Graphics::Imaging::BitmapDecoder::CreateAsync(state->InputStream);
403    }).then([state](Windows::Graphics::Imaging::BitmapDecoder ^decoder)
404    {
405        state->Decoder = decoder;
406        return Windows::Storage::KnownFolders::PicturesLibrary->CreateFileAsync(PHOTO_FILE_NAME, Windows::Storage::CreationCollisionOption::GenerateUniqueName);
407    }).then([state](Windows::Storage::StorageFile ^storageFile)
408    {
409        state->PhotoStorage = storageFile;
410        return state->PhotoStorage->OpenAsync(Windows::Storage::FileAccessMode::ReadWrite);
411    }).then([state](Windows::Storage::Streams::IRandomAccessStream ^stream)
412    {
413        state->OutputStream = stream;
414        state->OutputStream->Size = 0;
415        return Windows::Graphics::Imaging::BitmapEncoder::CreateForTranscodingAsync(state->OutputStream, state->Decoder);
416    }).then([state, photoRotation](Windows::Graphics::Imaging::BitmapEncoder ^encoder)
417    {
418        state->Encoder = encoder;
419        auto properties = ref new Windows::Graphics::Imaging::BitmapPropertySet();
420        properties->Insert("System.Photo.Orientation",
421            ref new Windows::Graphics::Imaging::BitmapTypedValue((unsigned short)photoRotation, Windows::Foundation::PropertyType::UInt16));
422        return create_task(state->Encoder->BitmapProperties->SetPropertiesAsync(properties));
423    }).then([state]()
424    {
425        return state->Encoder->FlushAsync();
426    }).then([tempStorageFile, state](task<void> previousTask)
427    {
428        auto result = state->PhotoStorage;
429        delete state;
430
431        tempStorageFile->DeleteAsync(Windows::Storage::StorageDeleteOption::PermanentDelete);
432
433        previousTask.get();
434
435        return result;
436    });
437}
438
439Windows::Storage::FileProperties::PhotoOrientation AdvancedCapture::GetCurrentPhotoRotation()
440{
441    bool counterclockwiseRotation = m_bReversePreviewRotation;
442
443    if (m_bRotateVideoOnOrientationChange)
444    {
445        return PhotoRotationLookup(Windows::Graphics::Display::DisplayProperties::CurrentOrientation, counterclockwiseRotation);
446    }
447    else
448    {
449        return Windows::Storage::FileProperties::PhotoOrientation::Normal;
450    }
451}
452
453void AdvancedCapture::PrepareForVideoRecording()
454{
455    Windows::Media::Capture::MediaCapture ^mediaCapture = m_mediaCaptureMgr.Get();
456    if (mediaCapture == nullptr)
457    {
458        return;
459    }
460
461    bool counterclockwiseRotation = m_bReversePreviewRotation;
462
463    if (m_bRotateVideoOnOrientationChange)
464    {
465        mediaCapture->SetRecordRotation(VideoRotationLookup(Windows::Graphics::Display::DisplayProperties::CurrentOrientation, counterclockwiseRotation));
466    }
467    else
468    {
469        mediaCapture->SetRecordRotation(Windows::Media::Capture::VideoRotation::None);
470    }
471}
472
473void AdvancedCapture::DisplayProperties_OrientationChanged(Platform::Object^ sender)
474{
475    Windows::Media::Capture::MediaCapture ^mediaCapture = m_mediaCaptureMgr.Get();
476    if (mediaCapture == nullptr)
477    {
478        return;
479    }
480
481    bool previewMirroring = mediaCapture->GetPreviewMirroring();
482    bool counterclockwiseRotation = (previewMirroring && !m_bReversePreviewRotation) ||
483        (!previewMirroring && m_bReversePreviewRotation);
484
485    if (m_bRotateVideoOnOrientationChange)
486    {
487        mediaCapture->SetPreviewRotation(VideoRotationLookup(Windows::Graphics::Display::DisplayProperties::CurrentOrientation, counterclockwiseRotation));
488    }
489    else
490    {
491        mediaCapture->SetPreviewRotation(Windows::Media::Capture::VideoRotation::None);
492    }
493}
494
495Windows::Storage::FileProperties::PhotoOrientation AdvancedCapture::PhotoRotationLookup(
496    Windows::Graphics::Display::DisplayOrientations displayOrientation, bool counterclockwise)
497{
498    switch (displayOrientation)
499    {
500    case Windows::Graphics::Display::DisplayOrientations::Landscape:
501        return Windows::Storage::FileProperties::PhotoOrientation::Normal;
502
503    case Windows::Graphics::Display::DisplayOrientations::Portrait:
504        return (counterclockwise) ? Windows::Storage::FileProperties::PhotoOrientation::Rotate270:
505            Windows::Storage::FileProperties::PhotoOrientation::Rotate90;
506
507    case Windows::Graphics::Display::DisplayOrientations::LandscapeFlipped:
508        return Windows::Storage::FileProperties::PhotoOrientation::Rotate180;
509
510    case Windows::Graphics::Display::DisplayOrientations::PortraitFlipped:
511        return (counterclockwise) ? Windows::Storage::FileProperties::PhotoOrientation::Rotate90 :
512            Windows::Storage::FileProperties::PhotoOrientation::Rotate270;
513
514    default:
515        return Windows::Storage::FileProperties::PhotoOrientation::Unspecified;
516    }
517}
518
519Windows::Media::Capture::VideoRotation AdvancedCapture::VideoRotationLookup(
520    Windows::Graphics::Display::DisplayOrientations displayOrientation, bool counterclockwise)
521{
522    switch (displayOrientation)
523    {
524    case Windows::Graphics::Display::DisplayOrientations::Landscape:
525        return Windows::Media::Capture::VideoRotation::None;
526
527    case Windows::Graphics::Display::DisplayOrientations::Portrait:
528        return (counterclockwise) ? Windows::Media::Capture::VideoRotation::Clockwise270Degrees :
529            Windows::Media::Capture::VideoRotation::Clockwise90Degrees;
530
531    case Windows::Graphics::Display::DisplayOrientations::LandscapeFlipped:
532        return Windows::Media::Capture::VideoRotation::Clockwise180Degrees;
533
534    case Windows::Graphics::Display::DisplayOrientations::PortraitFlipped:
535        return (counterclockwise) ? Windows::Media::Capture::VideoRotation::Clockwise90Degrees:
536            Windows::Media::Capture::VideoRotation::Clockwise270Degrees ;
537
538    default:
539        return Windows::Media::Capture::VideoRotation::None;
540    }
541}
542
543void SDKSample::MediaCapture::AdvancedCapture::Button_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
544{
545    try
546    {
547        create_task(m_mediaCaptureMgr->ClearEffectsAsync(Windows::Media::Capture::MediaStreamType::VideoPreview)).then([this](task<void> cleanTask)
548        {
549            m_bEffectAdded = true;
550            int index = EffectTypeCombo->SelectedIndex;
551            PropertySet^ props = ref new PropertySet();
552            props->Insert(L"{698649BE-8EAE-4551-A4CB-3EC98FBD3D86}", index);
553            create_task(m_mediaCaptureMgr->AddEffectAsync(Windows::Media::Capture::MediaStreamType::VideoPreview,"OcvTransform.OcvImageManipulations", props)).then([this](task<void> effectTask)
554            {
555                try
556                {
557                    effectTask.get();
558
559                    auto mediaCapture = m_mediaCaptureMgr.Get();
560                    Windows::Media::Capture::VideoDeviceCharacteristic charecteristic = mediaCapture->MediaCaptureSettings->VideoDeviceCharacteristic;
561
562                    ShowStatusMessage("Add effect successful to preview stream successful");
563                    if((charecteristic != Windows::Media::Capture::VideoDeviceCharacteristic::AllStreamsIdentical) &&
564                        (charecteristic != Windows::Media::Capture::VideoDeviceCharacteristic::PreviewRecordStreamsIdentical))
565                    {
566                        Windows::Media::MediaProperties::IMediaEncodingProperties ^props = mediaCapture->VideoDeviceController->GetMediaStreamProperties(Windows::Media::Capture::MediaStreamType::VideoRecord);
567                        Windows::Media::MediaProperties::VideoEncodingProperties ^videoEncodingProperties  = static_cast<Windows::Media::MediaProperties::VideoEncodingProperties ^>(props);
568                        if(!videoEncodingProperties->Subtype->Equals("H264")) //Cant add an effect to an H264 stream
569                        {
570                            task<void>(mediaCapture->AddEffectAsync(Windows::Media::Capture::MediaStreamType::VideoRecord,"OcvTransform.OcvImageManipulations", nullptr)).then([this](task<void> effectTask2)
571                            {
572                                try
573                                {
574                                    effectTask2.get();
575                                    ShowStatusMessage("Add effect successful to record stream successful");
576                                    m_bEffectAddedToRecord = true;
577                                    AddEffectToImageStream();
578                                    EffectTypeCombo->IsEnabled = true;
579                                }
580                                catch(Exception ^e)
581                                {
582                                    ShowExceptionMessage(e);
583                                    EffectTypeCombo->IsEnabled = true;
584                                }
585                            });
586                        }
587                        else
588                        {
589                            AddEffectToImageStream();
590                            EffectTypeCombo->IsEnabled = true;
591                        }
592
593                    }
594                    else
595                    {
596                        AddEffectToImageStream();
597                        EffectTypeCombo->IsEnabled = true;
598                    }
599                }
600                catch (Exception ^e)
601                {
602                    ShowExceptionMessage(e);
603                    EffectTypeCombo->IsEnabled = true;
604                }
605            });
606        });
607    }
608    catch (Platform::Exception ^e)
609    {
610        ShowExceptionMessage(e);
611        EffectTypeCombo->IsEnabled = true;
612    }
613}
614