diff --git a/Unigram/Unigram/Common/CameraRotationHelper.cs b/Unigram/Unigram/Common/CameraRotationHelper.cs new file mode 100644 index 0000000000..54e55f38ab --- /dev/null +++ b/Unigram/Unigram/Common/CameraRotationHelper.cs @@ -0,0 +1,266 @@ +using System; +using Windows.Devices.Enumeration; +using Windows.Devices.Sensors; +using Windows.Graphics.Display; +using Windows.Media.Capture; +using Windows.Storage.FileProperties; + +namespace Unigram.Common +{ + /// + /// https://github.com/Microsoft/Windows-universal-samples/blob/master/Samples/CameraStarterKit/cs/CameraRotationHelper.cs + /// + class CameraRotationHelper + { + private EnclosureLocation _cameraEnclosureLocation; + private DisplayInformation _displayInformation = DisplayInformation.GetForCurrentView(); + private SimpleOrientationSensor _orientationSensor = SimpleOrientationSensor.GetDefault(); + + /// + /// Occurs each time the simple orientation sensor reports a new sensor reading or when the display's current or native orientation changes + /// + public event EventHandler OrientationChanged; + + public CameraRotationHelper(EnclosureLocation cameraEnclosureLocation) + { + _cameraEnclosureLocation = cameraEnclosureLocation; + if (!IsEnclosureLocationExternal(_cameraEnclosureLocation) && _orientationSensor != null) + { + _orientationSensor.OrientationChanged += SimpleOrientationSensor_OrientationChanged; + } + _displayInformation.OrientationChanged += DisplayInformation_OrientationChanged; + } + + /// + /// Detects whether or not the camera is external to the device + /// + public static bool IsEnclosureLocationExternal(EnclosureLocation enclosureLocation) + { + return (enclosureLocation == null || enclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Unknown); + } + + /// + /// Gets the rotation to rotate ui elements + /// + public SimpleOrientation GetUIOrientation() + { + if (IsEnclosureLocationExternal(_cameraEnclosureLocation)) + { + // Cameras that are not attached to the device do not rotate along with it, so apply no rotation + return SimpleOrientation.NotRotated; + } + + // Return the difference between the orientation of the device and the orientation of the app display + var deviceOrientation = _orientationSensor?.GetCurrentOrientation() ?? SimpleOrientation.NotRotated; + var displayOrientation = ConvertDisplayOrientationToSimpleOrientation(_displayInformation.CurrentOrientation); + return SubtractOrientations(displayOrientation, deviceOrientation); + } + + /// + /// Gets the rotation of the camera to rotate pictures/videos when saving to file + /// + public SimpleOrientation GetCameraCaptureOrientation() + { + if (IsEnclosureLocationExternal(_cameraEnclosureLocation)) + { + // Cameras that are not attached to the device do not rotate along with it, so apply no rotation + return SimpleOrientation.NotRotated; + } + + // Get the device orientation offset by the camera hardware offset + var deviceOrientation = _orientationSensor?.GetCurrentOrientation() ?? SimpleOrientation.NotRotated; + var result = SubtractOrientations(deviceOrientation, GetCameraOrientationRelativeToNativeOrientation()); + + // If the preview is being mirrored for a front-facing camera, then the rotation should be inverted + if (ShouldMirrorPreview()) + { + result = MirrorOrientation(result); + } + return result; + } + + /// + /// Gets the rotation of the camera to display the camera preview + /// + public SimpleOrientation GetCameraPreviewOrientation() + { + if (IsEnclosureLocationExternal(_cameraEnclosureLocation)) + { + // Cameras that are not attached to the device do not rotate along with it, so apply no rotation + return SimpleOrientation.NotRotated; + } + + // Get the app display rotation offset by the camera hardware offset + var result = ConvertDisplayOrientationToSimpleOrientation(_displayInformation.CurrentOrientation); + result = SubtractOrientations(result, GetCameraOrientationRelativeToNativeOrientation()); + + // If the preview is being mirrored for a front-facing camera, then the rotation should be inverted + if (ShouldMirrorPreview()) + { + result = MirrorOrientation(result); + } + return result; + } + + public static PhotoOrientation ConvertSimpleOrientationToPhotoOrientation(SimpleOrientation orientation) + { + switch (orientation) + { + 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; + } + } + + public static int ConvertSimpleOrientationToClockwiseDegrees(SimpleOrientation orientation) + { + switch (orientation) + { + case SimpleOrientation.Rotated90DegreesCounterclockwise: + return 270; + case SimpleOrientation.Rotated180DegreesCounterclockwise: + return 180; + case SimpleOrientation.Rotated270DegreesCounterclockwise: + return 90; + case SimpleOrientation.NotRotated: + default: + return 0; + } + } + + private SimpleOrientation ConvertDisplayOrientationToSimpleOrientation(DisplayOrientations orientation) + { + SimpleOrientation result; + switch (orientation) + { + case DisplayOrientations.Landscape: + result = SimpleOrientation.NotRotated; + break; + case DisplayOrientations.PortraitFlipped: + result = SimpleOrientation.Rotated90DegreesCounterclockwise; + break; + case DisplayOrientations.LandscapeFlipped: + result = SimpleOrientation.Rotated180DegreesCounterclockwise; + break; + case DisplayOrientations.Portrait: + default: + result = SimpleOrientation.Rotated270DegreesCounterclockwise; + break; + } + + // Above assumes landscape; offset is needed if native orientation is portrait + if (_displayInformation.NativeOrientation == DisplayOrientations.Portrait) + { + result = AddOrientations(result, SimpleOrientation.Rotated90DegreesCounterclockwise); + } + + return result; + } + + private static SimpleOrientation MirrorOrientation(SimpleOrientation orientation) + { + // This only affects the 90 and 270 degree cases, because rotating 0 and 180 degrees is the same clockwise and counter-clockwise + switch (orientation) + { + case SimpleOrientation.Rotated90DegreesCounterclockwise: + return SimpleOrientation.Rotated270DegreesCounterclockwise; + case SimpleOrientation.Rotated270DegreesCounterclockwise: + return SimpleOrientation.Rotated90DegreesCounterclockwise; + } + return orientation; + } + + private static SimpleOrientation AddOrientations(SimpleOrientation a, SimpleOrientation b) + { + var aRot = ConvertSimpleOrientationToClockwiseDegrees(a); + var bRot = ConvertSimpleOrientationToClockwiseDegrees(b); + var result = (aRot + bRot) % 360; + return ConvertClockwiseDegreesToSimpleOrientation(result); + } + + private static SimpleOrientation SubtractOrientations(SimpleOrientation a, SimpleOrientation b) + { + var aRot = ConvertSimpleOrientationToClockwiseDegrees(a); + var bRot = ConvertSimpleOrientationToClockwiseDegrees(b); + // Add 360 to ensure the modulus operator does not operate on a negative + var result = (360 + (aRot - bRot)) % 360; + return ConvertClockwiseDegreesToSimpleOrientation(result); + } + + private static VideoRotation ConvertSimpleOrientationToVideoRotation(SimpleOrientation orientation) + { + switch (orientation) + { + case SimpleOrientation.Rotated90DegreesCounterclockwise: + return VideoRotation.Clockwise270Degrees; + case SimpleOrientation.Rotated180DegreesCounterclockwise: + return VideoRotation.Clockwise180Degrees; + case SimpleOrientation.Rotated270DegreesCounterclockwise: + return VideoRotation.Clockwise90Degrees; + case SimpleOrientation.NotRotated: + default: + return VideoRotation.None; + } + } + + private static SimpleOrientation ConvertClockwiseDegreesToSimpleOrientation(int orientation) + { + switch (orientation) + { + case 270: + return SimpleOrientation.Rotated90DegreesCounterclockwise; + case 180: + return SimpleOrientation.Rotated180DegreesCounterclockwise; + case 90: + return SimpleOrientation.Rotated270DegreesCounterclockwise; + case 0: + default: + return SimpleOrientation.NotRotated; + } + } + + private void SimpleOrientationSensor_OrientationChanged(SimpleOrientationSensor sender, SimpleOrientationSensorOrientationChangedEventArgs args) + { + if (args.Orientation != SimpleOrientation.Faceup && args.Orientation != SimpleOrientation.Facedown) + { + // Only raise the OrientationChanged event if the device is not parallel to the ground. This allows users to take pictures of documents (FaceUp) + // or the ceiling (FaceDown) in portrait or landscape, by first holding the device in the desired orientation, and then pointing the camera + // either up or down, at the desired subject. + //Note: This assumes that the camera is either facing the same way as the screen, or the opposite way. For devices with cameras mounted + // on other panels, this logic should be adjusted. + OrientationChanged?.Invoke(this, false); + } + } + + private void DisplayInformation_OrientationChanged(DisplayInformation sender, object args) + { + OrientationChanged?.Invoke(this, true); + } + + private bool ShouldMirrorPreview() + { + // It is recommended that applications mirror the preview for front-facing cameras, as it gives users a more natural experience, since it behaves more like a mirror + return (_cameraEnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Front); + } + + private SimpleOrientation GetCameraOrientationRelativeToNativeOrientation() + { + // Get the rotation angle of the camera enclosure as it is mounted in the device hardware + var enclosureAngle = ConvertClockwiseDegreesToSimpleOrientation((int)_cameraEnclosureLocation.RotationAngleInDegreesClockwise); + + // Account for the fact that, on portrait-first devices, the built in camera sensor is read at a 90 degree offset to the native orientation + if (_displayInformation.NativeOrientation == DisplayOrientations.Portrait && !IsEnclosureLocationExternal(_cameraEnclosureLocation)) + { + enclosureAngle = AddOrientations(SimpleOrientation.Rotated90DegreesCounterclockwise, enclosureAngle); + } + + return enclosureAngle; + } + } +} diff --git a/Unigram/Unigram/Controls/DialogBackgroundPresenter.cs b/Unigram/Unigram/Controls/DialogBackgroundPresenter.cs index 31b92edb13..c662ccdbde 100644 --- a/Unigram/Unigram/Controls/DialogBackgroundPresenter.cs +++ b/Unigram/Unigram/Controls/DialogBackgroundPresenter.cs @@ -18,7 +18,7 @@ namespace Unigram.Controls public class DialogBackgroundPresenter : ContentControl, IHandle { private DialogBackground _defaultBackground; - private Image _imageBackground; + private Rectangle _imageBackground; private Rectangle _colorBackground; public DialogBackgroundPresenter() @@ -48,13 +48,13 @@ private async void Reload() if (item is StorageFile file) { if (_imageBackground == null) - _imageBackground = new Image { Stretch = Stretch.UniformToFill }; + _imageBackground = new Rectangle(); using (var stream = await file.OpenReadAsync()) { var bitmap = new BitmapImage(); await bitmap.SetSourceAsync(stream); - _imageBackground.Source = bitmap; + _imageBackground.Fill = new ImageBrush { ImageSource = bitmap, AlignmentX = AlignmentX.Center, AlignmentY = AlignmentY.Center, Stretch = Stretch.UniformToFill }; } Content = _imageBackground; diff --git a/Unigram/Unigram/Controls/Views/RoundVideoView.xaml b/Unigram/Unigram/Controls/Views/RoundVideoView.xaml index 4d387f213b..875cd1d920 100644 --- a/Unigram/Unigram/Controls/Views/RoundVideoView.xaml +++ b/Unigram/Unigram/Controls/Views/RoundVideoView.xaml @@ -65,8 +65,8 @@ - - + + diff --git a/Unigram/Unigram/Controls/Views/RoundVideoView.xaml.cs b/Unigram/Unigram/Controls/Views/RoundVideoView.xaml.cs index c88bbf22d6..ce041015d3 100644 --- a/Unigram/Unigram/Controls/Views/RoundVideoView.xaml.cs +++ b/Unigram/Unigram/Controls/Views/RoundVideoView.xaml.cs @@ -70,42 +70,42 @@ public IAsyncAction SetAsync(MediaCapture media) { return Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () => { - var profile = MediaEncodingProfile.CreateMp4(VideoEncodingQuality.Vga); - profile.Audio = null; - profile.Container = null; + //var profile = MediaEncodingProfile.CreateMp4(VideoEncodingQuality.Vga); + //profile.Audio = null; + //profile.Container = null; - _preview = MediaCapturePreviewSource.CreateFromVideoEncodingProperties(profile.Video); - await media.StartPreviewToCustomSinkAsync(profile, _preview.MediaSink); + //_preview = MediaCapturePreviewSource.CreateFromVideoEncodingProperties(profile.Video); + //await media.StartPreviewToCustomSinkAsync(profile, _preview.MediaSink); - _player = new MediaPlayer(); - _player.RealTimePlayback = true; - _player.AutoPlay = true; - _player.Source = _preview.MediaSource as IMediaPlaybackSource; + //_player = new MediaPlayer(); + //_player.RealTimePlayback = true; + //_player.AutoPlay = true; + //_player.Source = _preview.MediaSource as IMediaPlaybackSource; - _surface = _player.GetSurface(_compositor); + //_surface = _player.GetSurface(_compositor); - var brush = _compositor.CreateSurfaceBrush(_surface.CompositionSurface); - brush.Stretch = CompositionStretch.UniformToFill; + //var brush = _compositor.CreateSurfaceBrush(_surface.CompositionSurface); + //brush.Stretch = CompositionStretch.UniformToFill; - var mask = ImageLoader.Instance.LoadCircle(200, Colors.White).Brush; - var graphicsEffect = new AlphaMaskEffect - { - Source = new CompositionEffectSourceParameter("image"), - AlphaMask = new CompositionEffectSourceParameter("mask") - }; + //var mask = ImageLoader.Instance.LoadCircle(200, Colors.White).Brush; + //var graphicsEffect = new AlphaMaskEffect + //{ + // Source = new CompositionEffectSourceParameter("image"), + // AlphaMask = new CompositionEffectSourceParameter("mask") + //}; - var effectFactory = _compositor.CreateEffectFactory(graphicsEffect); - var effectBrush = effectFactory.CreateBrush(); - effectBrush.SetSourceParameter("image", brush); - effectBrush.SetSourceParameter("mask", mask); + //var effectFactory = _compositor.CreateEffectFactory(graphicsEffect); + //var effectBrush = effectFactory.CreateBrush(); + //effectBrush.SetSourceParameter("image", brush); + //effectBrush.SetSourceParameter("mask", mask); - _capture.Brush = effectBrush; + //_capture.Brush = effectBrush; - //Capture.Source = media; - //await media.StartPreviewAsync(); + Capture.Source = media; + await media.StartPreviewAsync(); }); } } diff --git a/Unigram/Unigram/Controls/VoiceButton.cs b/Unigram/Unigram/Controls/VoiceButton.cs index 9b184a9baf..08b0d5111f 100644 --- a/Unigram/Unigram/Controls/VoiceButton.cs +++ b/Unigram/Unigram/Controls/VoiceButton.cs @@ -181,7 +181,9 @@ private void Start() { _recorder.m_mediaCapture = new MediaCapture(); - _recorder.settings.VideoDeviceId = await _recorder.GetVideoProfileSupportedDeviceIdAsync(Windows.Devices.Enumeration.Panel.Front); + var cameraDevice = await _recorder.FindCameraDeviceByPanelAsync(Windows.Devices.Enumeration.Panel.Front); + + _recorder.settings.VideoDeviceId = cameraDevice.Id; await _recorder.m_mediaCapture.InitializeAsync(_recorder.settings); if (_video) @@ -355,21 +357,16 @@ private void InitializeSettings() settings.StreamingCaptureMode = m_isVideo ? StreamingCaptureMode.AudioAndVideo : StreamingCaptureMode.Audio; } - public async Task GetVideoProfileSupportedDeviceIdAsync(Windows.Devices.Enumeration.Panel panel) + public async Task FindCameraDeviceByPanelAsync(Windows.Devices.Enumeration.Panel desiredPanel) { - var deviceId = string.Empty; - var devices = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture); + // Get available devices for capturing pictures + var allVideoDevices = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture); - foreach (var device in devices) - { - if (MediaCapture.IsVideoProfileSupported(device.Id) && device.EnclosureLocation.Panel == panel) - { - deviceId = device.Id; - break; - } - } + // Get the desired camera by panel + DeviceInformation desiredDevice = allVideoDevices.FirstOrDefault(x => x.EnclosureLocation != null && x.EnclosureLocation.Panel == desiredPanel); - return deviceId; + // If there is no device mounted on the desired panel, return the first device found + return desiredDevice ?? allVideoDevices.FirstOrDefault(); } public async Task StartAsync() diff --git a/Unigram/Unigram/Unigram.csproj b/Unigram/Unigram/Unigram.csproj index 75e0f677d5..0fe0620a4a 100644 --- a/Unigram/Unigram/Unigram.csproj +++ b/Unigram/Unigram/Unigram.csproj @@ -97,6 +97,7 @@ + diff --git a/Unigram/Unigram/ViewModels/Settings/SettingsWallpaperViewModel.cs b/Unigram/Unigram/ViewModels/Settings/SettingsWallpaperViewModel.cs index e42809155e..ab9d88df7f 100644 --- a/Unigram/Unigram/ViewModels/Settings/SettingsWallpaperViewModel.cs +++ b/Unigram/Unigram/ViewModels/Settings/SettingsWallpaperViewModel.cs @@ -13,6 +13,7 @@ using Unigram.Core.Common; using Windows.Storage; using Windows.Storage.Pickers; +using Windows.UI.Xaml.Media.Imaging; using Windows.UI.Xaml.Navigation; namespace Unigram.ViewModels.Settings @@ -43,7 +44,7 @@ public override Task OnNavigatedToAsync(object parameter, NavigationMode mode, I return base.OnNavigatedToAsync(parameter, mode, state); } - private void UpdateView() + private async void UpdateView() { if (Items == null) { @@ -55,6 +56,17 @@ private void UpdateView() { IsLocal = true; SelectedItem = null; + + var item = await ApplicationData.Current.LocalFolder.TryGetItemAsync(FileUtils.GetTempFilePath("wallpaper.jpg")); + if (item is StorageFile file) + { + using (var stream = await file.OpenReadAsync()) + { + var bitmap = new BitmapImage(); + await bitmap.SetSourceAsync(stream); + Local = bitmap; + } + } } else { @@ -75,6 +87,19 @@ public bool IsLocal } } + private BitmapImage _local; + public BitmapImage Local + { + get + { + return _local; + } + set + { + Set(ref _local, value); + } + } + private TLWallPaperBase _selectedItem; public TLWallPaperBase SelectedItem { @@ -88,6 +113,7 @@ public TLWallPaperBase SelectedItem if (value != null) { + Local = null; IsLocal = false; } } @@ -111,6 +137,13 @@ private async void LocalExecute() IsLocal = true; SelectedItem = null; + + using (var stream = await result.OpenReadAsync()) + { + var bitmap = new BitmapImage(); + await bitmap.SetSourceAsync(stream); + Local = bitmap; + } } } diff --git a/Unigram/Unigram/Views/Settings/SettingsWallpaperPage.xaml b/Unigram/Unigram/Views/Settings/SettingsWallpaperPage.xaml index 7bf66d535a..c28dcc18f6 100644 --- a/Unigram/Unigram/Views/Settings/SettingsWallpaperPage.xaml +++ b/Unigram/Unigram/Views/Settings/SettingsWallpaperPage.xaml @@ -30,6 +30,12 @@ Command="{x:Bind ViewModel.DoneCommand}" Canvas.ZIndex="1"/> + + + + + +