SW 중심대학 OSS GIT 서버 박건태, 이승준, 고기완, 이준호 새로운 배포
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

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);
}
}
}