-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Camera helper WIP #1980
Merged
Merged
Camera helper WIP #1980
Changes from 64 commits
Commits
Show all changes
71 commits
Select commit
Hold shift + click to select a range
2a6b3d6
Initial checkin- adding camera helper class for capturing frames, Add…
0c75302
Cleaning up resources, adding logic to update source and capture video
b32d3c5
- Added MediaPlayer and hooking it to the frame source
eb73681
- Added CameraResult class to return status of Camera Helper Capture …
7acdac2
Updating error message for debugging
add873f
- Updated CameraPreviewCode.bind class to check for result from camer…
e4db028
Merge pull request #1 from Microsoft/master
skommireddi f60fbf4
Merge branch 'master' into CameraHelper
d3468a3
Added copyright header for source files
skommireddi 803dcd2
Renaming Async methods with Async keyword
skommireddi d5a0300
Added new custom CameraVideoPreview xaml control
skommireddi 97e00b0
Addressing code review comment: changing to auto property initializers
skommireddi ef8a6cf
Removing Debug statements per code review comment
skommireddi a472edf
Added sample page, code bind file and documentation for Camera Previe…
skommireddi 929233b
Merge pull request #2 from Microsoft/master
skommireddi 93aad10
Merge branch 'master' into CameraHelper
skommireddi bee2fb1
Changing icons for CameraPreview and CameraHelper sample pages
skommireddi 09fcc25
Addressing code review comments from PR.
skommireddi aa357e7
Addressing more code review comments -Disposing software bitmap after…
skommireddi 5110904
Addressing PR review comments - Updated docs, added PreviewFailed eve…
skommireddi 1777b7b
Merge pull request #3 from Microsoft/master
skommireddi 741c414
Merge branch 'master' into CameraHelper
skommireddi 815321d
Adding headers to code files, fixing out of range exception when clic…
skommireddi 9d0b9b8
Renamed filename to match type name
skommireddi 7c60361
Changing camera icon to show on top left corner inside MediaPlayerEle…
skommireddi 7fa67e0
PR review changes - Updated docs per template
skommireddi 5fa587a
Adding code to handle exception when user denies camera access, fixin…
skommireddi ecf7e42
Added FrameSourceGroupButton Visibility dependency property
skommireddi ea3b77b
Removed comment
skommireddi 89271cc
Addressing PR review comments
skommireddi 6410612
Adding IsFrameSourceGroupButtonAvailable dependency property for Came…
skommireddi 781f73c
Removed Software Bitmap from FrameEventArgs, Fixed a bug causing Vide…
skommireddi 85ecafb
Merge branch 'master' into CameraHelper
nmetulev 20a8f7f
Removing duplicate frame sources
skommireddi 99b66c0
exposing filtered available format on the source and selecting a defa…
LPBourret 3fcae45
correcting lower fps threshold filtered on for frame format and corre…
LPBourret 73f9039
Making CleanUp method async, so that Camera controls are disposed bef…
skommireddi 4a9990a
Exposing FrameSourceGroup property setter so user can set a camera so…
skommireddi 72276f3
Removing app suspend and resume event handles from camera control and…
skommireddi 22a7bd3
addressing comments
LPBourret 131e2d1
Merge branch 'CameraHelper' into lobourre/CameraHelper_update
LPBourret 4612c55
adding example of SetFormatAsync on the CameraHelper.FrameSource
LPBourret 033a77b
Merge pull request #4 from skommireddi/lobourre/CameraHelper_update
LPBourret e6da67f
Changing namespace from Microsoft.Toolkit.Uwp.Helpers.CameraHelper to…
skommireddi aef4b6b
Renaming FrameSource to PreviewFrameSource as you could have a differ…
skommireddi 8a04de9
Per review comments :Renamed SetCameraHelperAsync() to StartAsync() f…
skommireddi 67e8d98
fixed merge conflicts
nmetulev 8dc39dd
CameraHelper FrameArrived event returns a copy of the VideoFrame
nmetulev b9da02d
Added missing docs
nmetulev 3cd8a8c
Fixing suspend and adding app resume event handlers on Camera Helper …
skommireddi 6653e7f
updated EventArgs to lazy copy the VideoFrame
nmetulev 19d3e5a
Merge pull request #5 from Microsoft/nmetulev/camera
nmetulev f965d6d
Fixing multiple issues: 1. In Camera Preview control toggle button wa…
skommireddi 0547ab6
Merge branch 'CameraHelper' of https://github.com/skommireddi/UWPComm…
skommireddi 884d075
Adding thread safety to Camera Helper logic. Modified logic in Camera…
skommireddi 4d37ee1
Fixing an issue with toggle button not finding the correct group
skommireddi c904aa3
Merge branch 'master' into CameraHelper
nmetulev d3652a7
unregistered lifecycle events to prevent memory leak
nmetulev f11291b
updated docs
nmetulev 9ef5c71
cleanup
nmetulev 718064c
Updating docs, adding thread safe code for cleaning up CameraHelper r…
skommireddi da47ee0
Added webcam capability link in docs.
skommireddi 1a81de7
Addressing Michael's comments: Changed logic to find previe control b…
skommireddi 6ca4963
Merge branch 'master' into CameraHelper
nmetulev 294f05a
Merge branch 'master' into CameraHelper
nmetulev a764de3
Addressing comments: adding constants to refer to Template Part Names
skommireddi 2cd0448
Addressing comments
skommireddi d06a3b0
Merge branch 'CameraHelper' of https://github.com/skommireddi/UWPComm…
skommireddi 97487ee
Removing unnecessary usings
skommireddi 12e387e
Updating docs with Dispose() method
skommireddi e2f556f
Merge branch 'master' into CameraHelper
nmetulev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file added
BIN
+2.49 KB
Microsoft.Toolkit.Uwp.SampleApp/SamplePages/CameraHelper/CameraHelper.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 50 additions & 0 deletions
50
Microsoft.Toolkit.Uwp.SampleApp/SamplePages/CameraHelper/CameraHelperCode.bind
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
// Creates a Camera Helper and subscribes to frames from an available frame source. | ||
// Make sure you have the capability webcam enabled for your app to access the device's camera. | ||
|
||
using Microsoft.Toolkit.Uwp.Helpers; | ||
|
||
CameraHelper cameraHelper = new CameraHelper(); | ||
var result = await _cameraHelper.InitializeAndStartCaptureAsync(); | ||
|
||
if(result == CameraHelperResult.Success) | ||
{ | ||
// Subscribe to get frames as they arrive | ||
cameraHelper.FrameArrived += CameraHelper_FrameArrived; | ||
} | ||
else | ||
{ | ||
// Get error information | ||
var errorMessage = result.ToString(); | ||
} | ||
|
||
private void CameraHelper_FrameArrived(object sender, FrameEventArgs e) | ||
{ | ||
// Gets the current video frame | ||
VideoFrame currentVideoFrame = e.VideoFrame; | ||
|
||
// Gets the software bitmap image | ||
SoftwareBitmap softwareBitmap = currentVideoFrame.SoftwareBitmap; | ||
} | ||
|
||
private async void Application_Suspending(object sender, SuspendingEventArgs e) | ||
{ | ||
if (Frame.CurrentSourcePageType == typeof(CameraHelperPage)) | ||
{ | ||
var deferral = e.SuspendingOperation.GetDeferral(); | ||
await CleanUpAsync(); | ||
deferral.Complete(); | ||
} | ||
} | ||
|
||
private async Task CleanUpAsync() | ||
{ | ||
// You may want to unsubscribe from any events and call CameraHelper CleanUpAsync() method | ||
// to free up camera helper resources on App suspending, Page OnNavigatedFrom events when appropriate. | ||
// Note: You would need to re-initialize the CameraHelper and rehook events on App resuming, OnNavigatedTo events. | ||
if (_cameraHelper != null) | ||
{ | ||
_cameraHelper.FrameArrived -= CameraHelper_FrameArrived; | ||
await _cameraHelper.CleanUpAsync(); | ||
_cameraHelper = null; | ||
} | ||
} |
28 changes: 28 additions & 0 deletions
28
Microsoft.Toolkit.Uwp.SampleApp/SamplePages/CameraHelper/CameraHelperPage.xaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<Page | ||
x:Class="Microsoft.Toolkit.Uwp.SampleApp.SamplePages.CameraHelperPage" | ||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | ||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||
xmlns:local="using:Microsoft.Toolkit.Uwp.SampleApp.SamplePages" | ||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" | ||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | ||
mc:Ignorable="d"> | ||
<Page.Resources> | ||
<Style x:Name="ErrorMessageStyle" TargetType="TextBlock"> | ||
<Setter Property="FontSize" Value="14"/> | ||
<Setter Property="FontWeight" Value="Bold"/> | ||
<Setter Property="Foreground" Value="Red"/> | ||
</Style> | ||
</Page.Resources> | ||
<StackPanel Orientation="Vertical" Margin="20"> | ||
<ComboBox x:Name="FrameSourceGroupCombo" Header="Frame Source Group" HorizontalAlignment="Left" Width="Auto"> | ||
<ComboBox.ItemTemplate> | ||
<DataTemplate> | ||
<TextBlock Text="{Binding DisplayName}"></TextBlock> | ||
</DataTemplate> | ||
</ComboBox.ItemTemplate> | ||
</ComboBox> | ||
<TextBlock x:Name="CameraErrorTextBlock" Style="{StaticResource ErrorMessageStyle}" Margin="0,0,0,10" Visibility="Collapsed"></TextBlock> | ||
<Button x:Name="CaptureButton" Content="Capture Video Frame" Margin="0,10" Click="CaptureButton_Click"></Button> | ||
<Image x:Name="CurrentFrameImage" MinWidth="300" Width="400" HorizontalAlignment="Left"></Image> | ||
</StackPanel> | ||
</Page> |
156 changes: 156 additions & 0 deletions
156
Microsoft.Toolkit.Uwp.SampleApp/SamplePages/CameraHelper/CameraHelperPage.xaml.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
// ****************************************************************** | ||
// Copyright (c) Microsoft. All rights reserved. | ||
// This code is licensed under the MIT License (MIT). | ||
// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, | ||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | ||
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | ||
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH | ||
// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE. | ||
// ****************************************************************** | ||
|
||
using System; | ||
using System.Threading.Tasks; | ||
using Microsoft.Toolkit.Uwp.Helpers; | ||
using Windows.ApplicationModel; | ||
using Windows.Graphics.Imaging; | ||
using Windows.Media; | ||
using Windows.Media.Capture.Frames; | ||
using Windows.UI.Xaml; | ||
using Windows.UI.Xaml.Controls; | ||
using Windows.UI.Xaml.Media.Imaging; | ||
using Windows.UI.Xaml.Navigation; | ||
|
||
namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages | ||
{ | ||
/// <summary> | ||
/// Sample page for Camera Helper | ||
/// </summary> | ||
public sealed partial class CameraHelperPage : Page | ||
{ | ||
private CameraHelper _cameraHelper; | ||
private VideoFrame _currentVideoFrame; | ||
private SoftwareBitmapSource _softwareBitmapSource; | ||
|
||
public CameraHelperPage() | ||
{ | ||
this.InitializeComponent(); | ||
} | ||
|
||
protected override async void OnNavigatedTo(NavigationEventArgs e) | ||
{ | ||
base.OnNavigatedTo(e); | ||
_softwareBitmapSource = new SoftwareBitmapSource(); | ||
CurrentFrameImage.Source = _softwareBitmapSource; | ||
|
||
Application.Current.Suspending += Application_Suspending; | ||
Application.Current.Resuming += Application_Resuming; | ||
|
||
await InitializeAsync(); | ||
} | ||
|
||
protected async override void OnNavigatedFrom(NavigationEventArgs e) | ||
{ | ||
Application.Current.Suspending -= Application_Suspending; | ||
Application.Current.Resuming -= Application_Resuming; | ||
await CleanUpAsync(); | ||
} | ||
|
||
private async void Application_Suspending(object sender, SuspendingEventArgs e) | ||
{ | ||
if (Frame.CurrentSourcePageType == typeof(CameraHelperPage)) | ||
{ | ||
var deferral = e.SuspendingOperation.GetDeferral(); | ||
await CleanUpAsync(); | ||
deferral.Complete(); | ||
} | ||
} | ||
|
||
private async void Application_Resuming(object sender, object e) | ||
{ | ||
await InitializeAsync(); | ||
} | ||
|
||
private void CameraHelper_FrameArrived(object sender, FrameEventArgs e) | ||
{ | ||
_currentVideoFrame = e.VideoFrame; | ||
} | ||
|
||
private async Task InitializeAsync() | ||
{ | ||
var frameSourceGroups = await CameraHelper.GetFrameSourceGroupsAsync(); | ||
if (_cameraHelper == null) | ||
{ | ||
_cameraHelper = new CameraHelper(); | ||
} | ||
|
||
var result = await _cameraHelper.InitializeAndStartCaptureAsync(); | ||
if (result == CameraHelperResult.Success) | ||
{ | ||
// Subscribe to the video frame as they arrive | ||
_cameraHelper.FrameArrived += CameraHelper_FrameArrived; | ||
FrameSourceGroupCombo.ItemsSource = frameSourceGroups; | ||
FrameSourceGroupCombo.SelectionChanged += FrameSourceGroupCombo_SelectionChanged; | ||
FrameSourceGroupCombo.SelectedIndex = 0; | ||
} | ||
|
||
SetUIControls(result); | ||
} | ||
|
||
private async void FrameSourceGroupCombo_SelectionChanged(object sender, SelectionChangedEventArgs e) | ||
{ | ||
if (FrameSourceGroupCombo.SelectedItem is MediaFrameSourceGroup selectedGroup) | ||
{ | ||
_cameraHelper.FrameSourceGroup = selectedGroup; | ||
var result = await _cameraHelper.InitializeAndStartCaptureAsync(); | ||
SetUIControls(result); | ||
} | ||
} | ||
|
||
private void SetUIControls(CameraHelperResult result) | ||
{ | ||
var success = result == CameraHelperResult.Success; | ||
if (!success) | ||
{ | ||
_currentVideoFrame = null; | ||
} | ||
|
||
CameraErrorTextBlock.Text = result.ToString(); | ||
CameraErrorTextBlock.Visibility = success ? Visibility.Collapsed : Visibility.Visible; | ||
|
||
CaptureButton.IsEnabled = success; | ||
CurrentFrameImage.Opacity = success ? 1 : 0.5; | ||
} | ||
|
||
private async void CaptureButton_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e) | ||
{ | ||
var softwareBitmap = _currentVideoFrame.SoftwareBitmap; | ||
|
||
if (softwareBitmap != null) | ||
{ | ||
if (softwareBitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8 || softwareBitmap.BitmapAlphaMode == BitmapAlphaMode.Straight) | ||
{ | ||
softwareBitmap = SoftwareBitmap.Convert(softwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied); | ||
} | ||
|
||
await _softwareBitmapSource.SetBitmapAsync(softwareBitmap); | ||
} | ||
} | ||
|
||
private async Task CleanUpAsync() | ||
{ | ||
if (FrameSourceGroupCombo != null) | ||
{ | ||
FrameSourceGroupCombo.SelectionChanged -= FrameSourceGroupCombo_SelectionChanged; | ||
} | ||
|
||
if (_cameraHelper != null) | ||
{ | ||
_cameraHelper.FrameArrived -= CameraHelper_FrameArrived; | ||
await _cameraHelper.CleanUpAsync(); | ||
_cameraHelper = null; | ||
} | ||
} | ||
} | ||
} |
Binary file added
BIN
+2.59 KB
Microsoft.Toolkit.Uwp.SampleApp/SamplePages/CameraPreview/CameraPreview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 50 additions & 0 deletions
50
Microsoft.Toolkit.Uwp.SampleApp/SamplePages/CameraPreview/CameraPreviewCode.bind
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
// Make sure you have the capability webcam enabled for your app to access the device's camera. | ||
|
||
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | ||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" | ||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | ||
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls" | ||
mc:Ignorable="d"> | ||
<StackPanel Orientation="Vertical" Margin="20"> | ||
<controls:CameraPreview x:Name="CameraPreviewControl"> | ||
</controls:CameraPreview> | ||
<TextBlock x:Name="ErrorMessage"></TextBlock> | ||
<Image x:Name="CurrentFrameImage" MinWidth="300" Width="400" HorizontalAlignment="Left"></Image> | ||
</StackPanel> | ||
</Page> | ||
|
||
// Create and associate the camera helper instance with the camera preview control | ||
var cameraHelper = new CameraHelper(); | ||
await _cameraPreviewControl.StartAsync(cameraHelper); | ||
_cameraPreviewControl.CameraHelper.FrameArrived += CameraPreviewControl_FrameArrived; | ||
_cameraPreviewControl.PreviewFailed += CameraPreviewControl_PreviewFailed; | ||
|
||
|
||
// Create a software bitmap source and set it to the Xaml Image control source. | ||
var softwareBitmapSource = new SoftwareBitmapSource(); | ||
CurrentFrameImage.Source = softwareBitmapSource; | ||
|
||
// Register for FrameArrived to get real time video frames, software bitmaps. | ||
private void CameraPreviewControl_FrameArrived(object sender, FrameEventArgs e) | ||
{ | ||
var videoFrame = e.VideoFrame; | ||
var softwareBitmap = e.VideoFrame.SoftwareBitmap; | ||
var targetSoftwareBitmap = softwareBitmap; | ||
|
||
if (softwareBitmap != null) | ||
{ | ||
if (softwareBitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8 || softwareBitmap.BitmapAlphaMode == BitmapAlphaMode.Straight) | ||
{ | ||
targetSoftwareBitmap = SoftwareBitmap.Convert(softwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied); | ||
} | ||
|
||
await softwareBitmapSource.SetBitmapAsync(targetSoftwareBitmap); | ||
} | ||
} | ||
|
||
// Register for PreviewFailed to get failure error information. | ||
private void CameraPreviewControl_PreviewFailed(object sender, PreviewFailedEventArgs e) | ||
{ | ||
ErrorMessage.Text = e.Error; | ||
} |
10 changes: 10 additions & 0 deletions
10
Microsoft.Toolkit.Uwp.SampleApp/SamplePages/CameraPreview/CameraPreviewPage.xaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<Page | ||
x:Class="Microsoft.Toolkit.Uwp.SampleApp.SamplePages.CameraPreviewPage" | ||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | ||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||
xmlns:local="using:Microsoft.Toolkit.Uwp.SampleApp.SamplePages" | ||
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls" | ||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" | ||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | ||
mc:Ignorable="d"> | ||
</Page> |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of using an Image, you can use a MediaPlayerElement XAML control and hook it to your source directly (like in my email) so you don't have to orchestrate rendering of the frames as they come in
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this would save you most of the code in your page below
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just a different example of using the Video Frames and displaying in an image using the Camera Helper directly. The CameraPreview sample page demonstrates what you mentioned and it uses the xaml control encapsulating the MediaPlayerElement. Does that make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep ok