Skip to content

Commit

Permalink
Added a "Capture element / camera preview" page in the Media section. (
Browse files Browse the repository at this point in the history
…microsoft#1357)

## Description
`CaptureElement` from UWP XAML does not exist in WinAppSDK, but
`MediaPlayerElement` can be used in its place. The change adds a new
sample page to demonstrate how this can be done.

Note: Currently this page just reuses the same icon as the
MediaPlayerElement page. Ideally at some point we'll get a camera icon
which could be used here instead.

## Motivation and Context
Demonstrate the couple of calls to get from a `MediaCapture` to an
`IMediaPlaybackSource` necessary to display the capture in a
MediaPlayerElement. This also demonstrates basic creation of a
`MediaCapture` to provide an end-to-end sample. This sample should help
address questions in microsoft/microsoft-ui-xaml#4710 and
microsoft/microsoft-ui-xaml#8214.

## How Has This Been Tested?
Ad-hoc testing and testing with Accessibility Insights for Windows tool.

## Types of changes
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
  • Loading branch information
codendone committed Sep 19, 2023
1 parent 258d8c8 commit 9c80909
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 1 deletion.
5 changes: 4 additions & 1 deletion WinUIGallery/ContentIncludes.props
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
<Content Include="Assets\ControlImages\Button.png" />
<Content Include="Assets\ControlImages\CalendarDatePicker.png" />
<Content Include="Assets\ControlImages\CalendarView.png" />
<!-- TODO: Need a camera capture icon. -->
<Content Include="Assets\ControlImages\Canvas.png" />
<Content Include="Assets\ControlImages\Checkbox.png" />
<Content Include="Assets\ControlImages\Clipboard.png" />
Expand Down Expand Up @@ -303,6 +304,8 @@
<Content Include="ControlPagesSampleCode\ListView\ListViewSample4_cs.txt" />
<Content Include="ControlPagesSampleCode\ListView\ListViewSample4_xaml.txt" />
<Content Include="ControlPagesSampleCode\ListView\ListViewStickyHeaderSample_xaml.txt" />
<Content Include="ControlPagesSampleCode\Media\CaptureElementPreviewSample_cs.txt" />
<Content Include="ControlPagesSampleCode\Media\CaptureElementPreviewSample_xaml.txt" />
<Content Include="ControlPagesSampleCode\MenuBar\MenuBarSample1.txt" />
<Content Include="ControlPagesSampleCode\MenuBar\MenuBarSample2.txt" />
<Content Include="ControlPagesSampleCode\MenuBar\MenuBarSample3.txt" />
Expand Down Expand Up @@ -372,4 +375,4 @@
<Content Include="Common\ReadMe.txt" />
<Content Include="DataModel\ControlInfoData.json" />
</ItemGroup>
</Project>
</Project>
31 changes: 31 additions & 0 deletions WinUIGallery/ControlPages/CaptureElementPreviewPage.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Page x:Class="AppUIBasics.ControlPages.CaptureElementPreviewPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:AppUIBasics"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d">
<StackPanel>
<local:ControlExample x:Name="Example1" HeaderText="A MediaCapture preview displayed via a MediaPlayerElement." XamlSource="Media/CaptureElementPreviewSample_xaml.txt" CSharpSource="Media/CaptureElementPreviewSample_cs.txt">
<local:ControlExample.Example>
<Grid RowDefinitions="Auto,*" ColumnDefinitions="*,100" MinWidth="400" MinHeight="300" RowSpacing="10" ColumnSpacing="4">
<TextBlock x:Name="frameSourceName" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center"/>
<MediaPlayerElement x:Name="captureElement" Grid.Row="1" Grid.Column="0" Stretch="Uniform" AutoPlay="True" />
<TextBlock x:Name="capturedText" Visibility="Collapsed" Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" Text="Captured:" />
<Grid x:Name="captureContainer" Grid.Row="1" Grid.Column="1">
<ScrollViewer VerticalScrollMode="Enabled">
<StackPanel x:Name="snapshots" Spacing="2"/>
</ScrollViewer>
</Grid>
</Grid>
</local:ControlExample.Example>
<local:ControlExample.Options>
<StackPanel>
<ToggleSwitch x:Name="mirrorSwitch" Header="Mirror preview" IsOn="False" Toggled="MirrorToggleSwitch_Toggled" ToolTipService.ToolTip="Mirrors only the preview, not captured photos"/>
<Button x:Name="captureButton" Content="Capture Photo" Click="CapturePhoto_Click" />
</StackPanel>
</local:ControlExample.Options>
<local:ControlExample.Substitutions>
<local:ControlExampleSubstitution Key="MirrorPreview" Value="{x:Bind MirrorTextReplacement, Mode=OneWay}"/>
</local:ControlExample.Substitutions>
</local:ControlExample>
</StackPanel>
</Page>
130 changes: 130 additions & 0 deletions WinUIGallery/ControlPages/CaptureElementPreviewPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using Windows.Media.Capture.Frames;
using Windows.Media.Capture;
using Microsoft.UI.Xaml.Media.Imaging;
using Windows.Media.MediaProperties;
using Windows.Storage.Streams;
using System.ComponentModel;
using AppUIBasics.Helper;

namespace AppUIBasics.ControlPages
{
public sealed partial class CaptureElementPreviewPage : Page, INotifyPropertyChanged
{
public CaptureElementPreviewPage()
{
this.InitializeComponent();

StartCaptureElement();

// Move the ScrollViewer from the captureContainer under an ExpandToFillContainer.
// This will allow the snapshots column to use all available height without
// influencing the height.
var expandToFillContainer = new ExpandToFillContainer();
var sv = captureContainer.Children[0];
captureContainer.Children.Remove(sv);
captureContainer.Children.Add(expandToFillContainer);
expandToFillContainer.Children.Add(sv);
}

private MediaFrameSourceGroup mediaFrameSourceGroup;
private MediaCapture mediaCapture;

async private void StartCaptureElement()
{
var groups = await MediaFrameSourceGroup.FindAllAsync();
if (groups.Count == 0)
{
frameSourceName.Text = "No camera devices found.";
return;
}
mediaFrameSourceGroup = groups.First();

frameSourceName.Text = "Viewing: " + mediaFrameSourceGroup.DisplayName;
mediaCapture = new MediaCapture();
var mediaCaptureInitializationSettings = new MediaCaptureInitializationSettings()
{
SourceGroup = this.mediaFrameSourceGroup,
SharingMode = MediaCaptureSharingMode.SharedReadOnly,
StreamingCaptureMode = StreamingCaptureMode.Video,
MemoryPreference = MediaCaptureMemoryPreference.Cpu
};
await mediaCapture.InitializeAsync(mediaCaptureInitializationSettings);

// Set the MediaPlayerElement's Source property to the MediaSource for the mediaCapture.
var frameSource = mediaCapture.FrameSources[this.mediaFrameSourceGroup.SourceInfos[0].Id];
captureElement.Source = Windows.Media.Core.MediaSource.CreateFromMediaFrameSource(frameSource);
}

public string MirrorTextReplacement = ""; // starts not mirrored, so no text in that case

public event PropertyChangedEventHandler PropertyChanged;

public void OnPropertyChanged(string PropertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}

private void MirrorToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
if (mirrorSwitch.IsOn)
{
captureElement.RenderTransform = new ScaleTransform() { ScaleX = -1 };
captureElement.RenderTransformOrigin = new Point(0.5, 0.5);
MirrorTextReplacement =
"\n" +
" // Mirror the preview\n" +
" captureElement.RenderTransform = new ScaleTransform() { ScaleX = -1 };\n" +
" captureElement.RenderTransformOrigin = new Point(0.5, 0.5);\n";
}
else
{
captureElement.RenderTransform = null;
MirrorTextReplacement = "";
}
OnPropertyChanged("MirrorTextReplacement");
}

async private void CapturePhoto_Click(object sender, RoutedEventArgs e)
{
// Capture a photo to a stream
var imgFormat = ImageEncodingProperties.CreateJpeg();
var stream = new InMemoryRandomAccessStream();
await mediaCapture.CapturePhotoToStreamAsync(imgFormat, stream);
stream.Seek(0);

// Show the photo in an Image element
BitmapImage bmpImage = new BitmapImage();
await bmpImage.SetSourceAsync(stream);
var image = new Image() { Source = bmpImage };
snapshots.Children.Insert(0, image);

capturedText.Visibility = Visibility.Visible;

UIHelper.AnnounceActionForAccessibility(captureButton, "Photo successfully captured.", "CameraPreviewSampleCaptureNotificationId");
}
}

class ExpandToFillContainer : Grid
{
protected override Size MeasureOverride(Size availableSize)
{
// Measure with the minimum height so it will just expand to whatever space is available.
var desiredSize = base.MeasureOverride(new Size(availableSize.Width, 100));
return desiredSize;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using Windows.Media.Capture.Frames;
using Windows.Media.Capture;

private MediaFrameSourceGroup mediaFrameSourceGroup;
private MediaCapture mediaCapture;

async private void StartCaptureElement()
{
var groups = await MediaFrameSourceGroup.FindAllAsync();
if (groups.Count == 0)
{
frameSourceName.Text = "No camera devices found.";
return;
}
mediaFrameSourceGroup = groups.First();

frameSourceName.Text = "Viewing: " + mediaFrameSourceGroup.DisplayName;
mediaCapture = new MediaCapture();
var mediaCaptureInitializationSettings = new MediaCaptureInitializationSettings()
{
SourceGroup = this.mediaFrameSourceGroup,
SharingMode = MediaCaptureSharingMode.SharedReadOnly,
StreamingCaptureMode = StreamingCaptureMode.Video,
MemoryPreference = MediaCaptureMemoryPreference.Cpu
};
await mediaCapture.InitializeAsync(mediaCaptureInitializationSettings);

// Set the MediaPlayerElement's Source property to the MediaSource for the mediaCapture.
var frameSource = mediaCapture.FrameSources[this.mediaFrameSourceGroup.SourceInfos[0].Id];
captureElement.Source = Windows.Media.Core.MediaSource.CreateFromMediaFrameSource(frameSource);
$(MirrorPreview) }

async private void CapturePhoto_Click(object sender, RoutedEventArgs e)
{
// Capture a photo to a stream
var imgFormat = ImageEncodingProperties.CreateJpeg();
var stream = new InMemoryRandomAccessStream();
await mediaCapture.CapturePhotoToStreamAsync(imgFormat, stream);
stream.Seek(0);

// Show the photo in an Image element
BitmapImage bmpImage = new BitmapImage();
await bmpImage.SetSourceAsync(stream);
var image = new Image() { Source = bmpImage };
snapshots.Children.Insert(0, image);

capturedText.Visibility = Visibility.Visible;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Grid RowDefinitions="Auto,*" ColumnDefinitions="*,100" MinWidth="400" MinHeight="300" RowSpacing="10" ColumnSpacing="4">
<TextBlock x:Name="frameSourceName" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center"/>
<MediaPlayerElement x:Name="captureElement" Grid.Row="1" Grid.Column="0" Stretch="Uniform" AutoPlay="True" />
<TextBlock x:Name="capturedText" Visibility="Collapsed" Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" Text="Captured:" />
<Grid x:Name="captureContainer" Grid.Row="1" Grid.Column="1">
<ScrollViewer VerticalScrollMode="Enabled">
<StackPanel x:Name="snapshots" Spacing="2"/>
</ScrollViewer>
</Grid>
</Grid>
18 changes: 18 additions & 0 deletions WinUIGallery/DataModel/ControlInfoData.json
Original file line number Diff line number Diff line change
Expand Up @@ -2009,6 +2009,24 @@
}
]
},
{
"UniqueId": "CaptureElementPreview",
"Title": "Capture Element / Camera Preview",
"Subtitle": "A sample for doing a camera preview.",
"ImagePath": "ms-appx:///Assets/ControlImages/MediaPlayerElement.png",
"ImageIconPath": "ms-appx:///Assets/ControlIcons/MediaElementIcon.png",
"Description": "You can use a MediaPlayerElement control to show a camera preview with a MediaCapture object.",
"IsNew": true,
"Docs": [
{
"Title": "MediaCapture - API",
"Uri": "https://learn.microsoft.com/uwp/api/windows.media.capture.mediacapture"
}
],
"RelatedControls": [
"MediaPlayerElement"
]
},
{
"UniqueId": "Image",
"Title": "Image",
Expand Down

0 comments on commit 9c80909

Please sign in to comment.