using System; using System.Collections.Generic; using Unity.Collections; namespace UnityEngine.XR.ARSubsystems { /// /// Provides access to a device's camera. /// /// /// The XRCameraSubsystem links a Unity Camera to a device camera for video overlay (pass-thru /// rendering). It also allows developers to query for environmental light estimation, when available. /// public abstract class XRCameraSubsystem : XRSubsystem { /// /// The provider created by the implementation that contains the required camera functionality. /// /// /// The provider created by the implementation that contains the required camera functionality. /// Provider m_Provider; /// /// Construct the XRCameraSubsystem. /// public XRCameraSubsystem() { m_Provider = CreateProvider(); Debug.Assert(m_Provider != null, "camera functionality provider cannot be null"); } /// /// Interface for providing camera functionality for the implementation. /// protected class Provider { /// /// Property to be implemented by the provder to get the material used by XRCameraSubsystem to /// render the camera texture. /// /// /// The material to render the camera texture. /// public virtual Material cameraMaterial => null; /// /// Property to be implemented by the provider to determine whether camera permission has been granted. /// /// /// true if camera permission has been granted. Otherwise, false. /// public virtual bool permissionGranted => false; /// /// Whether or not culling should be inverted during rendering. Some front-facing /// camera modes may require this. /// public virtual bool invertCulling => false; /// /// Method to be implemented by provider to start the camera for the subsystem. /// public virtual void Start() { } /// /// Method to be implemented by provider to stop the camera for the subsystem. /// public virtual void Stop() { } /// /// Method to be implemented by provider to destroy the camera for the subsystem. /// public virtual void Destroy() { } /// /// Method to be implemented by provider to get the camera frame for the subsystem. /// /// The current Unity Camera parameters. /// The current camera frame returned by the method. /// /// true if the method successfully got a frame. Otherwise, false. /// public virtual bool TryGetFrame( XRCameraParams cameraParams, out XRCameraFrame cameraFrame) { cameraFrame = default(XRCameraFrame); return false; } /// /// Property to be implemented by the provider to get or set the focus mode for the camera. /// public virtual CameraFocusMode cameraFocusMode { get => CameraFocusMode.Fixed; set { } } /// /// Method to be implemented by the provider to set the light estimation mode. /// /// The light estimation mode to set. /// /// true if the method successfully set the light estimation mode. Otherwise, false. /// public virtual bool TrySetLightEstimationMode(LightEstimationMode lightEstimationMode) => false; /// /// Method to be implemented by the provider to get the camera intrinisics information. /// /// The camera intrinsics information returned from the method. /// /// true if the method successfully gets the camera intrinsics information. Otherwise, false. /// public virtual bool TryGetIntrinsics( out XRCameraIntrinsics cameraIntrinsics) { cameraIntrinsics = default(XRCameraIntrinsics); return false; } /// /// Method to be implemented by the provider to query the supported camera configurations. /// /// A default value used to fill the returned array before copying /// in real values. This ensures future additions to this struct are backwards compatible. /// The allocation strategy to use for the returned data. /// /// The supported camera configurations. /// public virtual NativeArray GetConfigurations(XRCameraConfiguration defaultCameraConfiguration, Allocator allocator) { return new NativeArray(0, allocator); } /// /// Property to be implemented by the provider to query/set the current camera configuration. /// /// /// The current camera configuration if it exists. Otherise, null. /// /// Thrown when setting the current configuration if the /// implementation does not support camera configurations. /// Thrown when setting the current configuration if the given /// configuration is not a valid, supported camera configuration. /// Thrown when setting the current configuration if the /// implementation is unable to set the current camera configuration. public virtual XRCameraConfiguration? currentConfiguration { get => null; set => throw new NotSupportedException("setting current camera configuration is not supported by this implementation"); } /// /// Get the s associated with the current /// . /// /// The current texture descriptors. /// A default value which should /// be used to fill the returned array before copying in the /// real values. This ensures future additions to this struct /// are backwards compatible. /// The allocator to use when creating /// the returned NativeArray. public virtual NativeArray GetTextureDescriptors( XRTextureDescriptor defaultDescriptor, Allocator allocator) { return new NativeArray(0, allocator); } /// /// Method to be implemented by the provider to get the enabled and disabled shader keywords for the /// material. /// /// The keywords to enable for the material. /// The keywords to disable for the material. public virtual void GetMaterialKeywords(out List enabledKeywords, out List disabledKeywords) { enabledKeywords = null; disabledKeywords = null; } /// /// Method to be implemented by the provider to query for the latest native camera image. /// /// The metadata required to construct a /// /// true if the camera image is acquired. Otherwise, false. /// /// Thrown if the implementation does not support camera /// image. public virtual bool TryAcquireLatestImage(out CameraImageCinfo cameraImageCinfo) { throw new NotSupportedException("getting camera image is not supported by this implementation"); } /// /// Method to be implemented by the provider to get the status of an existing asynchronous conversion /// request. /// /// The unique identifier associated with a request. /// The state of the request. /// Thrown if the implementation does not support camera /// image. /// public virtual AsyncCameraImageConversionStatus GetAsyncRequestStatus(int requestId) { throw new NotSupportedException("camera image conversion is not supported by this implementation"); } /// /// Method to be implemented by the provider to dispose an existing native image identified by /// . /// /// A unique identifier for this camera image. /// Thrown if the implementation does not support camera /// image. /// public virtual void DisposeImage(int nativeHandle) { throw new NotSupportedException("camera image conversion is not supported by this implementation"); } /// /// Method to be implemented by the provider to dispose an existing async conversion request. /// /// A unique identifier for the request. /// Thrown if the implementation does not support camera /// image. /// public virtual void DisposeAsyncRequest(int requestId) { throw new NotSupportedException("camera image conversion is not supported by this implementation"); } /// /// Method to be implemented by the provider to get information about an image plane from a native image /// handle by index. /// /// A unique identifier for this camera image. /// The index of the plane to get. /// The returned camera plane information if successful. /// /// true if the image plane was acquired. Otherwise, false. /// /// Thrown if the implementation does not support camera /// image. /// public virtual bool TryGetPlane( int nativeHandle, int planeIndex, out CameraImagePlaneCinfo planeCinfo) { throw new NotSupportedException("camera image conversion is not supported by this implementation"); } /// /// Method to be implemented by the provider to determine whether a native image handle returned by /// is currently valid. An image may become invalid if it has been /// disposed. /// /// /// If a handle is valid, and should not fail. /// /// A unique identifier for the camera image in question. /// true, if it is a valid handle. Otherwise, false. /// Thrown if the implementation does not support camera /// image. /// public virtual bool NativeHandleValid( int nativeHandle) { throw new NotSupportedException("camera image conversion is not supported by this implementation"); } /// /// Method to be implemented by the provider to get the number of bytes required to store an image with the /// given dimensions and TextureFormat. /// /// A unique identifier for the camera image to convert. /// The dimensions of the output image. /// The TextureFormat for the image. /// The number of bytes required to store the converted image. /// true if the output was set. /// Thrown if the implementation does not support camera /// image. public virtual bool TryGetConvertedDataSize( int nativeHandle, Vector2Int dimensions, TextureFormat format, out int size) { throw new NotSupportedException("camera image conversion is not supported by this implementation"); } /// /// Method to be implemented by the provider to convert the image with handle /// using the provided . /// /// A unique identifier for the camera image to convert. /// The parameters to use during the conversion. /// A buffer to write the converted image to. /// The number of bytes available in the buffer. /// /// true if the image was converted and stored in . /// /// Thrown if the implementation does not support camera /// image. public virtual bool TryConvert( int nativeHandle, XRCameraImageConversionParams conversionParams, IntPtr destinationBuffer, int bufferLength) { throw new NotSupportedException("camera image conversion is not supported by this implementation"); } /// /// Method to be implemented by the provider to create an asynchronous request to convert a camera image, /// similar to except the conversion should happen on a thread other than the /// calling (main) thread. /// /// A unique identifier for the camera image to convert. /// The parameters to use during the conversion. /// A unique identifier for this request. /// Thrown if the implementation does not support camera /// image. public virtual int ConvertAsync( int nativeHandle, XRCameraImageConversionParams conversionParams) { throw new NotSupportedException("camera image conversion is not supported by this implementation"); } /// /// Method to be implemented by the provider to get a pointer to the image data from a completed /// asynchronous request. This method should only succeed if returns /// . /// /// The unique identifier associated with a request. /// A pointer to the native buffer containing the data. /// The number of bytes in . /// true if and were set and point /// to the image data. /// Thrown if the implementation does not support camera /// image. public virtual bool TryGetAsyncRequestData(int requestId, out IntPtr dataPtr, out int dataLength) { throw new NotSupportedException("camera image conversion is not supported by this implementation"); } /// /// Method to be implemented by the provider to similar to /// but takes a delegate to invoke when the /// request is complete, rather than returning a request id. /// /// /// If the first parameter to is /// then the dataPtr parameter must be valid /// for the duration of the invocation. The data may be destroyed immediately upon return. The /// parameter must be passed back to the . /// /// A unique identifier for the camera image to convert. /// The parameters to use during the conversion. /// A delegate which must be invoked when the request is complete, whether the /// conversion was successfully or not. /// A native pointer which must be passed back unaltered to /// . /// Thrown if the implementation does not support camera /// image. public virtual void ConvertAsync( int nativeHandle, XRCameraImageConversionParams conversionParams, OnImageRequestCompleteDelegate callback, IntPtr context) { throw new NotSupportedException("camera image conversion is not supported by this implementation"); } /// /// Create the camera material from the given camera shader name. /// /// The name of the camera shader. /// /// The created camera material shader. /// /// Thrown if the shader cannot be found or if a /// material cannot be created for the shader. protected Material CreateCameraMaterial(string cameraShaderName) { var shader = Shader.Find(cameraShaderName); if (shader == null) { throw new InvalidOperationException($"Could not find shader named '{cameraShaderName}' required " + $"for video overlay on camera subsystem."); } Material material = new Material(shader); if (material == null) { throw new InvalidOperationException($"Could not create a material for shader named " + $"'{cameraShaderName}' required for video overlay on camera " + $"subsystem."); } return material; } } /// /// Get or set the focus mode for the camera. /// /// /// The focus mode for the camera. /// public CameraFocusMode focusMode { get => m_Provider.cameraFocusMode; set => m_Provider.cameraFocusMode = value; } /// /// Specifies the light estimation mode. /// /// /// The light estimation mode. /// public LightEstimationMode lightEstimationMode { get => m_LightEstimationMode; set { if ((m_LightEstimationMode != value) && m_Provider.TrySetLightEstimationMode(value)) { m_LightEstimationMode = value; } } } LightEstimationMode m_LightEstimationMode = LightEstimationMode.Disabled; /// /// Start the camera subsystem. /// protected sealed override void OnStart() => m_Provider.Start(); /// /// Stop the camera subsystem. /// protected sealed override void OnStop() => m_Provider.Stop(); /// /// Destroy the camera subsystem. /// protected sealed override void OnDestroyed() => m_Provider.Destroy(); /// /// Gets the s associated with the /// current frame. The caller owns the returned NativeArray /// and is responsible for calling Dispose on it. /// /// An array of texture descriptors. /// The allocator to use when creating /// the returned NativeArray. public NativeArray GetTextureDescriptors( Allocator allocator) { return m_Provider.GetTextureDescriptors( default(XRTextureDescriptor), allocator); } /// /// Get the material used by XRCameraSubsystem to render the camera texture. /// /// /// The material to render the camera texture. /// public Material cameraMaterial => m_Provider.cameraMaterial; /// /// Returns the camera intrinsics information. /// /// The camera intrinsics information returned from the method. /// /// true if the method successfully gets the camera intrinsics information. Otherwise, false. /// public bool TryGetIntrinsics(out XRCameraIntrinsics cameraIntrinsics) { return m_Provider.TryGetIntrinsics(out cameraIntrinsics); } /// /// Queries for the supported camera configurations. /// /// The allocation strategy to use for the returned data. /// /// The supported camera configurations. /// public NativeArray GetConfigurations(Allocator allocator) { return m_Provider.GetConfigurations(default(XRCameraConfiguration), allocator); } /// /// The current camera configuration. /// /// /// The current camera configuration if it exists. Otherise, null. /// /// Thrown when setting the current configuration if the /// implementation does not support camera configurations. /// Thrown when setting the current configuration if the given /// configuration is null. /// Thrown when setting the current configuration if the given /// configuration is not a supported camera configuration. /// Thrown when setting the current configuration if the /// implementation is unable to set the current camera configuration. public virtual XRCameraConfiguration? currentConfiguration { get => m_Provider.currentConfiguration; set { if (value == null) { throw new ArgumentNullException("value", "cannot set the camera configuration to null"); } m_Provider.currentConfiguration = value; } } /// /// Whether to invert the culling mode during rendering. Some front-facing /// camera modes may require this. /// public bool invertCulling => m_Provider.invertCulling; /// /// Method for the implementation to create the camera functionality provider. /// /// /// The camera functionality provider. /// protected abstract Provider CreateProvider(); /// /// Get the latest frame from the provider. /// /// The Unity Camera parameters. /// The camera frame to be populated if the subsystem is running and successfully provides /// the latest camera frame. /// /// true if the camera frame is successfully returned. Otherwise, false. /// public bool TryGetLatestFrame( XRCameraParams cameraParams, out XRCameraFrame frame) { if (running && m_Provider.TryGetFrame(cameraParams, out frame)) { return true; } frame = default(XRCameraFrame); return false; } /// /// Determines whether camera permission has been granted. /// /// /// true if camera permission has been granted. Otherwise, false. /// public bool permissionGranted => m_Provider.permissionGranted; /// /// Get the enabled and disabled shader keywords for the material. /// /// The keywords to enable for the material. /// The keywords to disable for the material. public void GetMaterialKeywords(out List enabledKeywords, out List disabledKeywords) => m_Provider.GetMaterialKeywords(out enabledKeywords, out disabledKeywords); /// /// Attempt to get the latest camera image. This provides directly access to the raw pixel data, as well as /// utilities to convert to RGB and Grayscale formats. /// /// /// The returned must be disposed to avoid resource leaks. /// /// A valid if this method returns true. /// /// true if the image was acquired. Otherwise, false. /// /// Thrown if the implementation does not support camera image. /// public bool TryGetLatestImage(out XRCameraImage cameraImage) { CameraImageCinfo cameraImageCinfo; if (m_Provider.TryAcquireLatestImage(out cameraImageCinfo)) { cameraImage = new XRCameraImage(this, cameraImageCinfo.nativeHandle, cameraImageCinfo.dimensions, cameraImageCinfo.planeCount, cameraImageCinfo.timestamp, cameraImageCinfo.format); return true; } else { cameraImage = default(XRCameraImage); return false; } } /// /// Registers a camera subsystem implementation based on the given subsystem parameters. /// /// The parameters defining the camera subsystem functionality implemented /// by the subsystem provider. /// /// true if the subsystem implementation is registered. Otherwise, false. /// /// Thrown when the values specified in the /// parameter are invalid. Typically, this will occur /// /// /// if is null or empty /// /// /// if is null /// /// /// if does not derive from the /// class /// /// /// /// public static bool Register(XRCameraSubsystemCinfo cameraSubsystemParams) { XRCameraSubsystemDescriptor cameraSubsystemDescriptor = XRCameraSubsystemDescriptor.Create(cameraSubsystemParams); return SubsystemRegistration.CreateDescriptor(cameraSubsystemDescriptor); } /// /// Get the status of an existing asynchronous conversion request. /// /// The unique identifier associated with a request. /// The state of the request. /// Thrown if the implementation does not support camera image. /// /// internal AsyncCameraImageConversionStatus GetAsyncRequestStatus(int requestId) { return m_Provider.GetAsyncRequestStatus(requestId); } /// /// Dispose an existing native image identified by . /// /// A unique identifier for this camera image. /// Thrown if the implementation does not support camera image. /// /// internal void DisposeImage(int nativeHandle) => m_Provider.DisposeImage(nativeHandle); /// /// Dispose an existing async conversion request. /// /// A unique identifier for the request. /// Thrown if the implementation does not support camera image. /// /// internal void DisposeAsyncRequest(int requestId) => m_Provider.DisposeAsyncRequest(requestId); /// /// Attempt to get information about an image plane from a native image by index. /// /// A unique identifier for this camera image. /// The index of the plane to get. /// The returned camera plane information if successful. /// /// true if the image plane is successfully acquired. Otherwise, false. /// /// Thrown if the implementation does not support camera image. /// /// internal bool TryGetPlane( int nativeHandle, int planeIndex, out CameraImagePlaneCinfo planeCinfo) { return m_Provider.TryGetPlane(nativeHandle, planeIndex, out planeCinfo); } /// /// Determine whether a native image handle returned by is /// currently valid. An image may become invalid if it has been disposed. /// /// /// If a handle is valid, and should not fail. /// /// A unique identifier for the camera image in question. /// true if it is a valid handle, false otherwise. /// Thrown if the implementation does not support camera image. /// /// internal bool NativeHandleValid(int nativeHandle) => m_Provider.NativeHandleValid(nativeHandle); /// /// Get the number of bytes required to store an image with the given dimensions and TextureFormat. /// /// A unique identifier for the camera image to convert. /// The dimensions of the output image. /// The TextureFormat for the image. /// The number of bytes required to store the converted image. /// true if the output was set. /// Thrown if the implementation does not support camera image. /// internal bool TryGetConvertedDataSize( int nativeHandle, Vector2Int dimensions, TextureFormat format, out int size) { return m_Provider.TryGetConvertedDataSize(nativeHandle, dimensions, format, out size); } /// /// Convert the image with handle using the provided /// . /// /// A unique identifier for the camera image to convert. /// The parameters to use during the conversion. /// A buffer to write the converted image to. /// The number of bytes available in the buffer. /// /// true if the image was converted and stored in . /// /// Thrown if the implementation does not support camera image. /// internal bool TryConvert( int nativeHandle, XRCameraImageConversionParams conversionParams, IntPtr destinationBuffer, int bufferLength) { return m_Provider.TryConvert(nativeHandle, conversionParams, destinationBuffer, bufferLength); } /// /// Create an asynchronous request to convert a camera image, similar to except the /// conversion should happen on a thread other than the calling (main) thread. /// /// A unique identifier for the camera image to convert. /// The parameters to use during the conversion. /// A unique identifier for this request. /// Thrown if the implementation does not support camera image. /// internal int ConvertAsync( int nativeHandle, XRCameraImageConversionParams conversionParams) { return m_Provider.ConvertAsync(nativeHandle, conversionParams); } /// /// Get a pointer to the image data from a completed asynchronous request. This method should only succeed if /// returns . /// /// The unique identifier associated with a request. /// A pointer to the native buffer containing the data. /// The number of bytes in . /// true if and were set and point to /// the image data. /// Thrown if the implementation does not support camera image. /// internal bool TryGetAsyncRequestData(int requestId, out IntPtr dataPtr, out int dataLength) { return m_Provider.TryGetAsyncRequestData(requestId, out dataPtr, out dataLength); } /// /// Callback from native code for when the asychronous conversion is complete. /// /// The status of the conversion operation. /// The parameters for the conversion. /// The native pointer to the converted data. /// The memory size of the converted data. /// The native context for the conversion operation. protected internal delegate void OnImageRequestCompleteDelegate( AsyncCameraImageConversionStatus status, XRCameraImageConversionParams conversionParams, IntPtr dataPtr, int dataLength, IntPtr context); /// /// Similar to but takes a delegate to invoke /// when the request is complete, rather than returning a request id. /// /// /// If the first parameter to is /// then the dataPtr parameter must be valid for /// the duration of the invocation. The data may be destroyed immediately upon return. The /// parameter must be passed back to the . /// /// A unique identifier for the camera image to convert. /// The parameters to use during the conversion. /// A delegate which must be invoked when the request is complete, whether the /// conversion was successfully or not. /// A native pointer which must be passed back unaltered to . /// /// Thrown if the implementation does not support camera image. /// internal void ConvertAsync( int nativeHandle, XRCameraImageConversionParams conversionParams, OnImageRequestCompleteDelegate callback, IntPtr context) { m_Provider.ConvertAsync(nativeHandle, conversionParams, callback, context); } /// /// Container for native camera image construction metadata. /// protected struct CameraImageCinfo : IEquatable { /// /// The handle representing the camera image on the native level. /// /// /// The handle representing the camera image on the native level. /// public int nativeHandle => m_NativeHandle; int m_NativeHandle; /// /// The dimensions of the camera image. /// /// /// The dimensions of the camera image. /// public Vector2Int dimensions => m_Dimensions; Vector2Int m_Dimensions; /// /// The number of video planes in the camera image. /// /// /// The number of video planes in the camera image. /// public int planeCount => m_PlaneCount; int m_PlaneCount; /// /// The timestamp for when the camera image was captured. /// /// /// The timestamp for when the camera image was captured. /// public double timestamp => m_Timestamp; double m_Timestamp; /// /// The format of the camera image. /// /// /// The format of the camera image. /// public CameraImageFormat format => m_Format; CameraImageFormat m_Format; /// /// Constructs the camera image cinfo. /// /// The handle representing the camera image on the native level. /// The dimensions of the camera image. /// The number of video planes in the camera image. /// The timestamp for when the camera image was captured. /// The format of the camera image. public CameraImageCinfo(int nativeHandle, Vector2Int dimensions, int planeCount, double timestamp, CameraImageFormat format) { this.m_NativeHandle = nativeHandle; this.m_Dimensions = dimensions; this.m_PlaneCount = planeCount; this.m_Timestamp = timestamp; this.m_Format = format; } public bool Equals(CameraImageCinfo other) { return (nativeHandle.Equals(other.nativeHandle) && dimensions.Equals(other.dimensions) && planeCount.Equals(other.planeCount) && timestamp.Equals(other.timestamp) && format.Equals(other.format)); } public override bool Equals(System.Object obj) { return ReferenceEquals(this, obj) || ((obj is CameraImageCinfo) && Equals((CameraImageCinfo)obj)); } public static bool operator ==(CameraImageCinfo lhs, CameraImageCinfo rhs) => lhs.Equals(rhs); public static bool operator !=(CameraImageCinfo lhs, CameraImageCinfo rhs) => !(lhs == rhs); public override int GetHashCode() { int hashCode = 486187739; unchecked { hashCode = (hashCode * 486187739) + nativeHandle.GetHashCode(); hashCode = (hashCode * 486187739) + dimensions.GetHashCode(); hashCode = (hashCode * 486187739) + planeCount.GetHashCode(); hashCode = (hashCode * 486187739) + timestamp.GetHashCode(); hashCode = (hashCode * 486187739) + ((int)format).GetHashCode(); } return hashCode; } public override string ToString() { return string.Format("nativeHandle: {0} dimensions:{1} planes:{2} timestamp:{3} format:{4}", nativeHandle.ToString(), dimensions.ToString(), planeCount.ToString(), timestamp.ToString(), format.ToString()); } } /// /// Container for the metadata describing access to the raw camera image plane data. /// protected internal struct CameraImagePlaneCinfo : IEquatable { /// /// The pointer to the raw native image data. /// /// /// The pointer to the raw native image data. /// public IntPtr dataPtr => m_DataPtr; IntPtr m_DataPtr; /// /// The length of the native image data. /// /// /// The length of the native image data. /// public int dataLength => m_DataLength; int m_DataLength; /// /// The stride for iterating through the rows of the native image data. /// /// /// The stride for iterating through the rows of the native image data. /// public int rowStride => m_RowStride; int m_RowStride; /// /// The stride for iterating through the pixels of the native image data. /// /// /// The stride for iterating through the pixels of the native image data. /// public int pixelStride => m_PixelStride; int m_PixelStride; /// /// Constructs the camera image plane cinfo. /// /// The pointer to the raw native image data. /// The length of the native image data. /// The stride for iterating through the rows of the native image data. /// The stride for iterating through the pixels of the native image data. public CameraImagePlaneCinfo(IntPtr dataPtr, int dataLength, int rowStride, int pixelStride) { this.m_DataPtr = dataPtr; this.m_DataLength = dataLength; this.m_RowStride = rowStride; this.m_PixelStride = pixelStride; } public bool Equals(CameraImagePlaneCinfo other) { return (dataPtr.Equals(other.dataPtr) && dataLength.Equals(other.dataLength) && rowStride.Equals(other.rowStride) && pixelStride.Equals(other.pixelStride)); } public override bool Equals(System.Object obj) { return ReferenceEquals(this, obj) || ((obj is CameraImagePlaneCinfo) && Equals((CameraImagePlaneCinfo)obj)); } public static bool operator ==(CameraImagePlaneCinfo lhs, CameraImagePlaneCinfo rhs) => lhs.Equals(rhs); public static bool operator !=(CameraImagePlaneCinfo lhs, CameraImagePlaneCinfo rhs) => !(lhs == rhs); public override int GetHashCode() { int hashCode = 486187739; unchecked { hashCode = (hashCode * 486187739) + dataPtr.GetHashCode(); hashCode = (hashCode * 486187739) + dataLength.GetHashCode(); hashCode = (hashCode * 486187739) + rowStride.GetHashCode(); hashCode = (hashCode * 486187739) + pixelStride.GetHashCode(); } return hashCode; } public override string ToString() { return string.Format("dataPtr: {0} length:{1} rowStride:{2} pixelStride:{3}", dataPtr.ToString(), dataLength.ToString(), rowStride.ToString(), pixelStride.ToString()); } } } }