diff --git a/plugin.xml b/plugin.xml index 745e8122..fda49b1c 100755 --- a/plugin.xml +++ b/plugin.xml @@ -65,9 +65,6 @@ - - - diff --git a/src/windows/QRReader/CameraRotationHelper.cs b/src/windows/QRReader/CameraRotationHelper.cs new file mode 100644 index 00000000..ff1ce1f3 --- /dev/null +++ b/src/windows/QRReader/CameraRotationHelper.cs @@ -0,0 +1,263 @@ +using System; +using Windows.Devices.Enumeration; +using Windows.Devices.Sensors; +using Windows.Graphics.Display; +using Windows.Media.Capture; +using Windows.Storage.FileProperties; + +namespace QRReader +{ + 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); + } + + public 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; + } + } +} \ No newline at end of file diff --git a/src/windows/QRReader/QRReader.csproj b/src/windows/QRReader/QRReader.csproj index b4db9bd7..c2b225b2 100644 --- a/src/windows/QRReader/QRReader.csproj +++ b/src/windows/QRReader/QRReader.csproj @@ -4,7 +4,7 @@ Debug AnyCPU - {2928CF9F-4255-4D8A-A7C3-70324CC137A4} + {DF132792-0542-4936-914D-4C25D779CAC8} winmdobj Properties QRReader @@ -12,7 +12,7 @@ en-US UAP 10.0.14393.0 - 10.0.10586.0 + 10.0.14393.0 14 512 {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} @@ -27,7 +27,6 @@ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP prompt 4 - true AnyCPU @@ -109,8 +108,10 @@ - + + + @@ -120,9 +121,6 @@ 14.0 - - -