You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
488 lines
21 KiB
488 lines
21 KiB
using AOT;
|
|
using System;
|
|
using System.Runtime.InteropServices;
|
|
using Unity.Collections;
|
|
using Unity.Collections.LowLevel.Unsafe;
|
|
|
|
namespace UnityEngine.XR.ARSubsystems
|
|
{
|
|
/// <summary>
|
|
/// Represents a single, raw image from a device camera. Provides access to the raw image plane data, as well as
|
|
/// conversion methods to convert to color and grayscale formats. See <see cref="Convert"/> and
|
|
/// <see cref="ConvertAsync(XRCameraImageConversionParams)"/>. Use
|
|
/// <see cref="XRCameraSubsystem.TryGetLatestImage"/> to get a <c>XRCameraImage</c>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Each <c>XRCameraImage</c> must be explicitly disposed. Failing to do so will leak resources and could prevent
|
|
/// future camera image access.
|
|
/// </remarks>
|
|
public struct XRCameraImage : IDisposable, IEquatable<XRCameraImage>
|
|
{
|
|
int m_NativeHandle;
|
|
|
|
XRCameraSubsystem m_CameraSubsystem;
|
|
|
|
static XRCameraSubsystem.OnImageRequestCompleteDelegate s_OnAsyncConversionComplete;
|
|
|
|
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
|
AtomicSafetyHandle m_SafetyHandle;
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// The dimensions (width and height) of the image.
|
|
/// </summary>
|
|
/// <value>
|
|
/// The dimensions (width and height) of the image.
|
|
/// </value>
|
|
public Vector2Int dimensions { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The image width.
|
|
/// </summary>
|
|
/// <value>
|
|
/// The image width.
|
|
/// </value>
|
|
public int width { get { return dimensions.x; } }
|
|
|
|
/// <summary>
|
|
/// The image height.
|
|
/// </summary>
|
|
/// <value>
|
|
/// The image height.
|
|
/// </value>
|
|
public int height { get { return dimensions.y; } }
|
|
|
|
/// <summary>
|
|
/// The number of image planes. A "plane" in this context refers to a channel in the raw video format,
|
|
/// not a physical surface.
|
|
/// </summary>
|
|
/// <value>
|
|
/// The number of image planes.
|
|
/// </value>
|
|
public int planeCount { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The format used by the image planes. You will only need this if you plan to interpret the raw plane data.
|
|
/// </summary>
|
|
/// <value>
|
|
/// The format used by the image planes.
|
|
/// </value>
|
|
public CameraImageFormat format { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The timestamp, in seconds, associated with this camera image
|
|
/// </summary>
|
|
/// <value>
|
|
/// The timestamp, in seconds, associated with this camera image
|
|
/// </value>
|
|
public double timestamp { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Whether this <c>XRCameraImage</c> represents a valid image (i.e., not Disposed).
|
|
/// </summary>
|
|
/// <value>
|
|
/// <c>true</c> if this <c>XRCameraImage</c> represents a valid image. Otherwise, <c>false</c>.
|
|
/// </value>
|
|
public bool valid
|
|
{
|
|
get { return (m_CameraSubsystem != null) && m_CameraSubsystem.NativeHandleValid(m_NativeHandle); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialize the static callback for when the image request completes.
|
|
/// </summary>
|
|
static XRCameraImage()
|
|
{
|
|
s_OnAsyncConversionComplete = new XRCameraSubsystem.OnImageRequestCompleteDelegate(OnAsyncConversionComplete);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Construct the <c>XRCameraImage</c> with the given native image information.
|
|
/// </summary>
|
|
/// <param name="cameraSubsystem">The camera subsystem to use for interacting with the native image.</param>
|
|
/// <param name="nativeHandle">The native image handle.</param>
|
|
/// <param name="dimensions">The dimensions of the native image.</param>
|
|
/// <param name="planeCount">The number of video planes in the native image.</param>
|
|
/// <param name="timestamp">The timestamp for when the native image was captured.</param>
|
|
/// <param name="format">The camera image format of the native image.</param>
|
|
internal XRCameraImage(
|
|
XRCameraSubsystem cameraSubsystem,
|
|
int nativeHandle,
|
|
Vector2Int dimensions,
|
|
int planeCount,
|
|
double timestamp,
|
|
CameraImageFormat format)
|
|
{
|
|
m_CameraSubsystem = cameraSubsystem;
|
|
m_NativeHandle = nativeHandle;
|
|
this.dimensions = dimensions;
|
|
this.planeCount = planeCount;
|
|
this.timestamp = timestamp;
|
|
this.format = format;
|
|
|
|
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
|
m_SafetyHandle = AtomicSafetyHandle.Create();
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether the given <c>TextureFormat</c> is supported for conversion.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// These texture formats are supported:
|
|
/// <list type="bullet">
|
|
/// <item><description><c>TextureFormat.R8</c></description></item>
|
|
/// <item><description><c>TextureFormat.Alpha8</c></description></item>
|
|
/// <item><description><c>TextureFormat.RGB24</c></description></item>
|
|
/// <item><description><c>TextureFormat.RGBA32</c></description></item>
|
|
/// <item><description><c>TextureFormat.ARGBA32</c></description></item>
|
|
/// <item><description><c>TextureFormat.BGRA32</c></description></item>
|
|
/// </list>
|
|
/// </remarks>
|
|
/// <param name="format">A <c>TextureFormat</c> to test.</param>
|
|
/// <returns><c>true</c> if the format is supported by the various conversion methods.</returns>
|
|
public static bool FormatSupported(TextureFormat format)
|
|
{
|
|
switch (format)
|
|
{
|
|
case TextureFormat.Alpha8:
|
|
case TextureFormat.R8:
|
|
case TextureFormat.RGB24:
|
|
case TextureFormat.RGBA32:
|
|
case TextureFormat.ARGB32:
|
|
case TextureFormat.BGRA32:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get an image "plane". A "plane" in this context refers to a channel in the raw video format, not a physical
|
|
/// surface.
|
|
/// </summary>
|
|
/// <param name="planeIndex">The index of the plane to get.</param>
|
|
/// <returns>A <see cref="XRCameraImagePlane"/> describing the plane.</returns>
|
|
/// <exception cref="System.ArgumentOutOfRangeException">Thrown if <paramref name="planeIndex"/> is not within
|
|
/// the range [0, <see cref="planeCount"/>).</exception>
|
|
/// <exception cref="System.InvalidOperationException">Thrown if the requested plane is not valid.</exception>
|
|
public unsafe XRCameraImagePlane GetPlane(int planeIndex)
|
|
{
|
|
ValidateNativeHandleAndThrow();
|
|
if (planeIndex < 0 || planeIndex >= planeCount)
|
|
{
|
|
throw new ArgumentOutOfRangeException("planeIndex",
|
|
string.Format("planeIndex must be in the range 0 to {0}", planeCount - 1));
|
|
}
|
|
|
|
XRCameraSubsystem.CameraImagePlaneCinfo imagePlaneCinfo;
|
|
if (!m_CameraSubsystem.TryGetPlane(m_NativeHandle, planeIndex, out imagePlaneCinfo))
|
|
{
|
|
throw new InvalidOperationException("The requested plane is not valid for this XRCameraImage.");
|
|
}
|
|
|
|
var data = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<byte>(
|
|
(void*)imagePlaneCinfo.dataPtr, imagePlaneCinfo.dataLength, Allocator.None);
|
|
|
|
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
|
NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref data, m_SafetyHandle);
|
|
#endif
|
|
|
|
return new XRCameraImagePlane
|
|
{
|
|
rowStride = imagePlaneCinfo.rowStride,
|
|
pixelStride = imagePlaneCinfo.pixelStride,
|
|
data = data
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the number of bytes required to store a converted image with the given parameters.
|
|
/// </summary>
|
|
/// <param name="dimensions">The desired dimensions of the converted image.</param>
|
|
/// <param name="format">The desired <c>TextureFormat</c> for the converted image.</param>
|
|
/// <returns>The number of bytes required to store the converted image.</returns>
|
|
/// <exception cref="System.ArgumentException">Thrown if the desired <paramref name="format"/> is not
|
|
/// supported.</exception>
|
|
/// <exception cref="System.ArgumentOutOfRangeException">Thrown if the desired <paramref name="dimensions"/>
|
|
/// exceed the native image dimensions.</exception>
|
|
/// <exception cref="System.InvalidOperationException">Thrown if the image is invalid.</exception>
|
|
/// <seealso cref="FormatSupported"/>
|
|
public int GetConvertedDataSize(Vector2Int dimensions, TextureFormat format)
|
|
{
|
|
ValidateNativeHandleAndThrow();
|
|
|
|
if (dimensions.x > this.dimensions.x)
|
|
{
|
|
throw new ArgumentOutOfRangeException("width",
|
|
string.Format("Converted image width must be less than or equal to native image width. {0} > {1}",
|
|
dimensions.x, this.dimensions.x));
|
|
}
|
|
|
|
if (dimensions.y > this.dimensions.y)
|
|
{
|
|
throw new ArgumentOutOfRangeException("height",
|
|
string.Format("Converted image height must be less than or equal to native image height. {0} > {1}",
|
|
dimensions.y, this.dimensions.y));
|
|
}
|
|
|
|
if (!FormatSupported(format))
|
|
{
|
|
throw new ArgumentException("Invalid texture format.", "format");
|
|
}
|
|
|
|
int size;
|
|
if (!m_CameraSubsystem.TryGetConvertedDataSize(m_NativeHandle, dimensions, format, out size))
|
|
{
|
|
throw new InvalidOperationException("XRCameraImage is not valid.");
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the number of bytes required to store a converted image with the given parameters.
|
|
/// </summary>
|
|
/// <param name="conversionParams">The desired conversion parameters.</param>
|
|
/// <returns>The number of bytes required to store the converted image.</returns>
|
|
/// <exception cref="System.ArgumentException">Thrown if the desired format is not supported.</exception>
|
|
/// <exception cref="System.ArgumentOutOfRangeException">Thrown if the desired dimensions exceed the native
|
|
/// image dimensions.</exception>
|
|
/// <exception cref="System.InvalidOperationException">Thrown if the image is invalid.</exception>
|
|
/// <seealso cref="FormatSupported"/>
|
|
public int GetConvertedDataSize(XRCameraImageConversionParams conversionParams)
|
|
{
|
|
return GetConvertedDataSize(
|
|
conversionParams.outputDimensions,
|
|
conversionParams.outputFormat);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert the <c>XRCameraImage</c> to one of the supported formats using the specified
|
|
/// <paramref name="conversionParams"/>.
|
|
/// </summary>
|
|
/// <param name="conversionParams">The parameters for the image conversion.</param>
|
|
/// <param name="destinationBuffer">A pointer to memory to which to write the converted image.</param>
|
|
/// <param name="bufferLength">The number of bytes pointed to by <paramref name="destinationBuffer"/>. Must be
|
|
/// greater than or equal to the value returned by
|
|
/// <see cref="GetConvertedDataSize(XRCameraImageConversionParams)"/>.</param>
|
|
/// <exception cref="System.ArgumentException">Thrown if the <paramref name="bufferLength"/> is smaller than
|
|
/// the data size required by the conversion.</exception>
|
|
/// <exception cref="System.InvalidOperationException">Thrown if the conversion failed.</exception>
|
|
/// <seealso cref="FormatSupported"/>
|
|
public void Convert(XRCameraImageConversionParams conversionParams, IntPtr destinationBuffer, int bufferLength)
|
|
{
|
|
ValidateNativeHandleAndThrow();
|
|
ValidateConversionParamsAndThrow(conversionParams);
|
|
int requiredDataSize = GetConvertedDataSize(conversionParams);
|
|
|
|
if (bufferLength < requiredDataSize)
|
|
{
|
|
throw new ArgumentException(string.Format(
|
|
"Conversion requires {0} bytes but only provided {1} bytes.", requiredDataSize, bufferLength),
|
|
"bufferLength");
|
|
}
|
|
|
|
if (!m_CameraSubsystem.TryConvert(m_NativeHandle, conversionParams, destinationBuffer, bufferLength))
|
|
{
|
|
throw new InvalidOperationException("Conversion failed.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert the <c>XRCameraImage</c> to one of the supported formats using the specified
|
|
/// <paramref name="conversionParams"/>. The conversion is performed asynchronously. Use the returned
|
|
/// <see cref="XRAsyncCameraImageConversion"/> to check for the status of the conversion, and retrieve the data
|
|
/// when complete.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// It is safe to <c>Dispose</c> the <c>XRCameraImage</c> before the asynchronous operation has completed.
|
|
/// </remarks>
|
|
/// <param name="conversionParams">The parameters to use for the conversion.</param>
|
|
/// <returns>A <see cref="XRAsyncCameraImageConversion"/> which can be used to check the status of the
|
|
/// conversion operation and get the resulting data.</returns>
|
|
/// <seealso cref="FormatSupported"/>
|
|
public XRAsyncCameraImageConversion ConvertAsync(XRCameraImageConversionParams conversionParams)
|
|
{
|
|
ValidateNativeHandleAndThrow();
|
|
ValidateConversionParamsAndThrow(conversionParams);
|
|
|
|
return new XRAsyncCameraImageConversion(m_CameraSubsystem, m_NativeHandle, conversionParams);
|
|
}
|
|
|
|
/// <summary>
|
|
/// <para>Convert the <c>XRCameraImage</c> to one of the supported formats using the specified
|
|
/// <paramref name="conversionParams"/>. The conversion is performed asynchronously, and
|
|
/// <paramref name="onComplete"/> is invoked when the conversion is complete, whether successful or not.</para>
|
|
/// <para>The <c>NativeArray</c> provided in the <paramref name="onComplete"/> delegate is only valid during
|
|
/// the invocation and is disposed immediately upon return.</para>
|
|
/// </summary>
|
|
/// <param name="conversionParams">The parameters to use for the conversion.</param>
|
|
/// <param name="onComplete">A delegate to invoke when the conversion operation completes. The delegate is
|
|
/// always invoked.</param>
|
|
/// <seealso cref="FormatSupported"/>
|
|
public void ConvertAsync(
|
|
XRCameraImageConversionParams conversionParams,
|
|
Action<AsyncCameraImageConversionStatus, XRCameraImageConversionParams, NativeArray<byte>> onComplete)
|
|
{
|
|
ValidateNativeHandleAndThrow();
|
|
ValidateConversionParamsAndThrow(conversionParams);
|
|
|
|
var handle = GCHandle.Alloc(onComplete);
|
|
var context = GCHandle.ToIntPtr(handle);
|
|
m_CameraSubsystem.ConvertAsync(m_NativeHandle, conversionParams, s_OnAsyncConversionComplete, context);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Callback from native code for when the asychronous conversion is complete.
|
|
/// </summary>
|
|
/// <param name="status">The status of the conversion operation.</param>
|
|
/// <param name="conversionParams">The parameters for the conversion.</param>
|
|
/// <param name="dataPtr">The native pointer to the converted data.</param>
|
|
/// <param name="dataLength">The memory size of the converted data.</param>
|
|
/// <param name="context">The native context for the conversion operation.</param>
|
|
[MonoPInvokeCallback(typeof(XRCameraSubsystem.OnImageRequestCompleteDelegate))]
|
|
static unsafe void OnAsyncConversionComplete(
|
|
AsyncCameraImageConversionStatus status, XRCameraImageConversionParams conversionParams, IntPtr dataPtr,
|
|
int dataLength, IntPtr context)
|
|
{
|
|
var handle = GCHandle.FromIntPtr(context);
|
|
var onComplete = (Action<AsyncCameraImageConversionStatus, XRCameraImageConversionParams, NativeArray<byte>>)handle.Target;
|
|
|
|
if (onComplete != null)
|
|
{
|
|
var data = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<byte>(
|
|
(void*)dataPtr, dataLength, Allocator.None);
|
|
|
|
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
|
var safetyHandle = AtomicSafetyHandle.Create();
|
|
NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref data, safetyHandle);
|
|
#endif
|
|
|
|
onComplete(status, conversionParams, data);
|
|
|
|
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
|
AtomicSafetyHandle.Release(safetyHandle);
|
|
#endif
|
|
}
|
|
|
|
handle.Free();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ensure the image is valid.
|
|
/// </summary>
|
|
/// <exception cref="System.InvalidOperationException">Thrown if the image is invalid.</exception>
|
|
void ValidateNativeHandleAndThrow()
|
|
{
|
|
if (!valid)
|
|
{
|
|
throw new InvalidOperationException("XRCameraImage is not valid.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ensure the conversion parameters are valid.
|
|
/// </summary>
|
|
/// <param name="conversionParams">The conversion parameters to validate.</param>
|
|
/// <exception cref="System.ArgumentOutOfRangeException">Thrown if the input image rect exceeds the actual
|
|
/// image dimensions or if the output dimensions exceed the input dimensions.</exception>
|
|
/// <exception cref="System.ArgumentException">Thrown if the texture format is not suppported</exception>
|
|
/// <seealso cref="FormatSupported"/>
|
|
void ValidateConversionParamsAndThrow(XRCameraImageConversionParams conversionParams)
|
|
{
|
|
if ((conversionParams.inputRect.x + conversionParams.inputRect.width > width) ||
|
|
(conversionParams.inputRect.y + conversionParams.inputRect.height > height))
|
|
{
|
|
throw new ArgumentOutOfRangeException(
|
|
"conversionParams.inputRect",
|
|
"Input rect must be completely within the original image.");
|
|
}
|
|
|
|
if ((conversionParams.outputDimensions.x > conversionParams.inputRect.width) ||
|
|
(conversionParams.outputDimensions.y > conversionParams.inputRect.height))
|
|
{
|
|
throw new ArgumentOutOfRangeException(string.Format(
|
|
"Output dimensions must be less than or equal to the inputRect's dimensions: ({0}x{1} > {2}x{3}).",
|
|
conversionParams.outputDimensions.x, conversionParams.outputDimensions.y,
|
|
conversionParams.inputRect.width, conversionParams.inputRect.height));
|
|
}
|
|
|
|
if (!FormatSupported(conversionParams.outputFormat))
|
|
{
|
|
throw new ArgumentException("TextureFormat not supported.", "conversionParams.format");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dispose native resources associated with this request, including the raw image data. Any
|
|
/// <see cref="XRCameraImagePlane"/>s returned by <see cref="GetPlane"/> are invalidated immediately after
|
|
/// calling <c>Dispose</c>.
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
if (m_CameraSubsystem == null || m_NativeHandle == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_CameraSubsystem.DisposeImage(m_NativeHandle);
|
|
m_NativeHandle = 0;
|
|
m_CameraSubsystem = null;
|
|
|
|
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
|
AtomicSafetyHandle.Release(m_SafetyHandle);
|
|
#endif
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
unchecked
|
|
{
|
|
var hash = width.GetHashCode();
|
|
hash = hash * 486187739 + height.GetHashCode();
|
|
hash = hash * 486187739 + planeCount.GetHashCode();
|
|
hash = hash * 486187739 + m_NativeHandle.GetHashCode();
|
|
hash = hash * 486187739 + ((int)format).GetHashCode();
|
|
if (m_CameraSubsystem != null)
|
|
{
|
|
hash = hash * 486187739 + m_CameraSubsystem.GetHashCode();
|
|
}
|
|
return hash;
|
|
}
|
|
}
|
|
|
|
public override bool Equals(object obj)
|
|
{
|
|
return ((obj is XRCameraImage) && Equals((XRCameraImage)obj));
|
|
}
|
|
|
|
public bool Equals(XRCameraImage other)
|
|
{
|
|
return
|
|
(width == other.width) &&
|
|
(height == other.height) &&
|
|
(planeCount == other.planeCount) &&
|
|
(format == other.format) &&
|
|
(m_NativeHandle == other.m_NativeHandle) &&
|
|
(m_CameraSubsystem == other.m_CameraSubsystem);
|
|
}
|
|
|
|
public static bool operator ==(XRCameraImage lhs, XRCameraImage rhs)
|
|
{
|
|
return lhs.Equals(rhs);
|
|
}
|
|
|
|
public static bool operator !=(XRCameraImage lhs, XRCameraImage rhs)
|
|
{
|
|
return !lhs.Equals(rhs);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format(
|
|
"(Width: {0}, Height: {1}, PlaneCount: {2}, Format: {3})",
|
|
width, height, planeCount, format);
|
|
}
|
|
}
|
|
}
|