react-native-camera_edit/windows/RNCamera/CameraRotationHelper.cs
2025-07-09 11:33:00 +09:00

229 lines
10 KiB
C#

using System;
using Windows.Devices.Enumeration;
using Windows.Devices.Sensors;
using Windows.Graphics.Display;
using Windows.Storage.FileProperties;
namespace RNCamera
{
class CameraRotationHelper
{
private EnclosureLocation _cameraEnclosureLocation;
private DisplayInformation _displayInformation = DisplayInformation.GetForCurrentView();
private SimpleOrientationSensor _orientationSensor = SimpleOrientationSensor.GetDefault();
/// <summary>
/// Occurs each time the simple orientation sensor reports a new sensor reading or when the display's current or native orientation changes
/// </summary>
public event EventHandler<bool> OrientationChanged;
public CameraRotationHelper(EnclosureLocation cameraEnclosureLocation)
{
_cameraEnclosureLocation = cameraEnclosureLocation;
if (!IsEnclosureLocationExternal(_cameraEnclosureLocation) && _orientationSensor != null)
{
_orientationSensor.OrientationChanged += SimpleOrientationSensor_OrientationChanged;
}
_displayInformation.OrientationChanged += DisplayInformation_OrientationChanged;
}
/// <summary>
/// Detects whether or not the camera is external to the device
/// </summary>
public static bool IsEnclosureLocationExternal(EnclosureLocation enclosureLocation)
{
return (enclosureLocation == null || enclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Unknown);
}
/// <summary>
/// Gets the rotation of the camera to rotate pictures/videos when saving to file
/// </summary>
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;
}
/// <summary>
/// Gets the rotation of the camera to display the camera preview
/// </summary>
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 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;
}
}
}