diff --git a/Assets/Mediapipe/Samples/Scenes/Hair Segmentation/Hair Segmentation.unity b/Assets/Mediapipe/Samples/Scenes/Hair Segmentation/Hair Segmentation.unity
index 07344ef0e..af967f8d2 100644
--- a/Assets/Mediapipe/Samples/Scenes/Hair Segmentation/Hair Segmentation.unity
+++ b/Assets/Mediapipe/Samples/Scenes/Hair Segmentation/Hair Segmentation.unity
@@ -242,12 +242,18 @@ PrefabInstance:
objectReference: {fileID: 0}
m_RemovedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: 5713b959e3c77a58fb258133fc8e4aef, type: 3}
---- !u!224 &107400409 stripped
-RectTransform:
- m_CorrespondingSourceObject: {fileID: 3259285889726014650, guid: 5713b959e3c77a58fb258133fc8e4aef,
+--- !u!114 &107400409 stripped
+MonoBehaviour:
+ m_CorrespondingSourceObject: {fileID: 3259285889726014649, guid: 5713b959e3c77a58fb258133fc8e4aef,
type: 3}
m_PrefabInstance: {fileID: 107400408}
m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 1344c3c82d62a2a41a3576d8abb8e3ea, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
--- !u!4 &107400410 stripped
Transform:
m_CorrespondingSourceObject: {fileID: 7074087083388479136, guid: 5713b959e3c77a58fb258133fc8e4aef,
@@ -275,8 +281,6 @@ MonoBehaviour:
annotation: {fileID: 1452360049}
_maskWidth: 512
_maskHeight: 512
- _minAlpha: 0.9
- _maxAlpha: 1
--- !u!114 &107400413 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 2020366215714292840, guid: 5713b959e3c77a58fb258133fc8e4aef,
@@ -508,12 +512,27 @@ PrefabInstance:
type: 3}
propertyPath: screen
value:
- objectReference: {fileID: 1625703806}
+ objectReference: {fileID: 0}
- target: {fileID: 5850198979010014858, guid: 24d349d9c78db0f3d94d4657029f4c5e,
type: 3}
propertyPath: _screen
value:
- objectReference: {fileID: 1625703806}
+ objectReference: {fileID: 107400409}
+ - target: {fileID: 5850198979010014858, guid: 24d349d9c78db0f3d94d4657029f4c5e,
+ type: 3}
+ propertyPath: _maskShader
+ value:
+ objectReference: {fileID: 4800000, guid: 7adfa9ebe754989caa35dbb2c5526d1c, type: 3}
+ - target: {fileID: 5850198979010014858, guid: 24d349d9c78db0f3d94d4657029f4c5e,
+ type: 3}
+ propertyPath: _maskTexture
+ value:
+ objectReference: {fileID: 0}
+ - target: {fileID: 5850198979010014858, guid: 24d349d9c78db0f3d94d4657029f4c5e,
+ type: 3}
+ propertyPath: _minConfidence
+ value: 0.275
+ objectReference: {fileID: 0}
m_RemovedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: 24d349d9c78db0f3d94d4657029f4c5e, type: 3}
--- !u!114 &1452360049 stripped
@@ -528,78 +547,6 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: f993d16dcccfcecb6892ad3cc2ea76c8, type: 3}
m_Name:
m_EditorClassIdentifier:
---- !u!1 &1625703804
-GameObject:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- serializedVersion: 6
- m_Component:
- - component: {fileID: 1625703805}
- - component: {fileID: 1625703807}
- - component: {fileID: 1625703806}
- m_Layer: 5
- m_Name: HairMask Screen
- m_TagString: Untagged
- m_Icon: {fileID: 0}
- m_NavMeshLayer: 0
- m_StaticEditorFlags: 0
- m_IsActive: 1
---- !u!224 &1625703805
-RectTransform:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 1625703804}
- m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
- m_LocalPosition: {x: 0, y: 0, z: 0}
- m_LocalScale: {x: 1, y: 1, z: 1}
- m_Children: []
- m_Father: {fileID: 107400409}
- m_RootOrder: 1
- m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
- m_AnchorMin: {x: 0, y: 0}
- m_AnchorMax: {x: 1, y: 1}
- m_AnchoredPosition: {x: 0, y: 0}
- m_SizeDelta: {x: 0, y: 0}
- m_Pivot: {x: 0.5, y: 0.5}
---- !u!114 &1625703806
-MonoBehaviour:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 1625703804}
- m_Enabled: 1
- m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 1344c3c82d62a2a41a3576d8abb8e3ea, type: 3}
- m_Name:
- m_EditorClassIdentifier:
- m_Material: {fileID: 0}
- m_Color: {r: 1, g: 1, b: 1, a: 0}
- m_RaycastTarget: 1
- m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
- m_Maskable: 1
- m_OnCullStateChanged:
- m_PersistentCalls:
- m_Calls: []
- m_Texture: {fileID: 0}
- m_UVRect:
- serializedVersion: 2
- x: 0
- y: 0
- width: 1
- height: 1
---- !u!222 &1625703807
-CanvasRenderer:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 1625703804}
- m_CullTransparentMesh: 1
--- !u!1 &1806680259
GameObject:
m_ObjectHideFlags: 0
diff --git a/Assets/Mediapipe/Samples/Scenes/Hair Segmentation/HairSegmentationGraph.cs b/Assets/Mediapipe/Samples/Scenes/Hair Segmentation/HairSegmentationGraph.cs
index 4d2cfb04b..76d34d346 100644
--- a/Assets/Mediapipe/Samples/Scenes/Hair Segmentation/HairSegmentationGraph.cs
+++ b/Assets/Mediapipe/Samples/Scenes/Hair Segmentation/HairSegmentationGraph.cs
@@ -100,9 +100,26 @@ private SidePacket BuildSidePacket(ImageSource imageSource)
var sidePacket = new SidePacket();
SetImageTransformationOptions(sidePacket, imageSource);
- var outputRotation = imageSource.isHorizontallyFlipped ? imageSource.rotation.Reverse() : imageSource.rotation;
+
+ // TODO: refactoring
+ // The orientation of the output image must match that of the input image.
+ var isInverted = CoordinateSystem.ImageCoordinate.IsInverted(imageSource.rotation);
+ var outputRotation = imageSource.rotation;
+ var outputHorizontallyFlipped = !isInverted && imageSource.isHorizontallyFlipped;
+ var outputVerticallyFlipped = imageSource.isVerticallyFlipped ^ (isInverted && imageSource.isHorizontallyFlipped);
+
+ if ((outputHorizontallyFlipped && outputVerticallyFlipped) || outputRotation == RotationAngle.Rotation180)
+ {
+ outputRotation = outputRotation.Add(RotationAngle.Rotation180);
+ outputHorizontallyFlipped = !outputHorizontallyFlipped;
+ outputVerticallyFlipped = !outputVerticallyFlipped;
+ }
+
sidePacket.Emplace("output_rotation", new IntPacket((int)outputRotation));
+ sidePacket.Emplace("output_horizontally_flipped", new BoolPacket(outputHorizontallyFlipped));
+ sidePacket.Emplace("output_vertically_flipped", new BoolPacket(outputVerticallyFlipped));
+ Logger.LogDebug($"output_rotation = {outputRotation}, output_horizontally_flipped = {outputHorizontallyFlipped}, output_vertically_flipped = {outputVerticallyFlipped}");
return sidePacket;
}
}
diff --git a/Assets/Mediapipe/Samples/Scenes/Hair Segmentation/hair_segmentation_cpu.txt b/Assets/Mediapipe/Samples/Scenes/Hair Segmentation/hair_segmentation_cpu.txt
index 23a17748a..9ff92b230 100644
--- a/Assets/Mediapipe/Samples/Scenes/Hair Segmentation/hair_segmentation_cpu.txt
+++ b/Assets/Mediapipe/Samples/Scenes/Hair Segmentation/hair_segmentation_cpu.txt
@@ -165,5 +165,7 @@ node: {
calculator: "ImageTransformationCalculator"
input_stream: "IMAGE:hair_mask_rotated"
input_side_packet: "ROTATION_DEGREES:output_rotation"
+ input_side_packet: "FLIP_HORIZONTALLY:output_horizontally_flipped"
+ input_side_packet: "FLIP_VERTICALLY:output_vertically_flipped"
output_stream: "IMAGE:hair_mask"
-}
\ No newline at end of file
+}
diff --git a/Assets/Mediapipe/Samples/Scenes/Hair Segmentation/hair_segmentation_gpu.txt b/Assets/Mediapipe/Samples/Scenes/Hair Segmentation/hair_segmentation_gpu.txt
index 17b103a28..18bcaf1c3 100644
--- a/Assets/Mediapipe/Samples/Scenes/Hair Segmentation/hair_segmentation_gpu.txt
+++ b/Assets/Mediapipe/Samples/Scenes/Hair Segmentation/hair_segmentation_gpu.txt
@@ -171,6 +171,8 @@ node: {
calculator: "ImageTransformationCalculator"
input_stream: "IMAGE_GPU:hair_mask_gpu"
input_side_packet: "ROTATION_DEGREES:output_rotation"
+ input_side_packet: "FLIP_HORIZONTALLY:output_horizontally_flipped"
+ input_side_packet: "FLIP_VERTICALLY:output_vertically_flipped"
output_stream: "IMAGE_GPU:hair_mask_unrotated_gpu"
}
diff --git a/Assets/Mediapipe/Samples/Scenes/Hair Segmentation/hair_segmentation_opengles.txt b/Assets/Mediapipe/Samples/Scenes/Hair Segmentation/hair_segmentation_opengles.txt
index 34553df0a..ca6a849ce 100644
--- a/Assets/Mediapipe/Samples/Scenes/Hair Segmentation/hair_segmentation_opengles.txt
+++ b/Assets/Mediapipe/Samples/Scenes/Hair Segmentation/hair_segmentation_opengles.txt
@@ -164,6 +164,8 @@ node: {
calculator: "ImageTransformationCalculator"
input_stream: "IMAGE_GPU:hair_mask_gpu"
input_side_packet: "ROTATION_DEGREES:output_rotation"
+ input_side_packet: "FLIP_HORIZONTALLY:output_horizontally_flipped"
+ input_side_packet: "FLIP_VERTICALLY:output_vertically_flipped"
output_stream: "IMAGE_GPU:hair_mask_unrotated_gpu"
}
diff --git a/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Framework/Formats/ImageFrame.cs b/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Framework/Formats/ImageFrame.cs
index 1ce5d5ebf..043e157be 100644
--- a/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Framework/Formats/ImageFrame.cs
+++ b/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Framework/Formats/ImageFrame.cs
@@ -59,6 +59,112 @@ private static void ReleasePixelData(IntPtr ptr)
// Do nothing (pixelData is moved)
}
+ ///
+ /// The number of channels for a .
+ /// If channels don't make sense in the , returns 0.
+ ///
+ ///
+ /// Unlike the original implementation, this API won't signal SIGABRT.
+ ///
+ public static int NumberOfChannelsForFormat(ImageFormat.Types.Format format)
+ {
+ switch (format)
+ {
+ case ImageFormat.Types.Format.Srgb:
+ case ImageFormat.Types.Format.Srgb48:
+ return 3;
+ case ImageFormat.Types.Format.Srgba:
+ case ImageFormat.Types.Format.Srgba64:
+ case ImageFormat.Types.Format.Sbgra:
+ return 4;
+ case ImageFormat.Types.Format.Gray8:
+ case ImageFormat.Types.Format.Gray16:
+ return 1;
+ case ImageFormat.Types.Format.Vec32F1:
+ return 1;
+ case ImageFormat.Types.Format.Vec32F2:
+ return 2;
+ case ImageFormat.Types.Format.Lab8:
+ return 3;
+ case ImageFormat.Types.Format.Ycbcr420P:
+ case ImageFormat.Types.Format.Ycbcr420P10:
+ case ImageFormat.Types.Format.Unknown:
+ default:
+ return 0;
+ }
+ }
+
+ ///
+ /// The channel size for a .
+ /// If channels don't make sense in the , returns 0.
+ ///
+ ///
+ /// Unlike the original implementation, this API won't signal SIGABRT.
+ ///
+ public static int ChannelSizeForFormat(ImageFormat.Types.Format format)
+ {
+ switch (format)
+ {
+ case ImageFormat.Types.Format.Srgb:
+ case ImageFormat.Types.Format.Srgba:
+ case ImageFormat.Types.Format.Sbgra:
+ return sizeof(byte);
+ case ImageFormat.Types.Format.Srgb48:
+ case ImageFormat.Types.Format.Srgba64:
+ return sizeof(ushort);
+ case ImageFormat.Types.Format.Gray8:
+ return sizeof(byte);
+ case ImageFormat.Types.Format.Gray16:
+ return sizeof(ushort);
+ case ImageFormat.Types.Format.Vec32F1:
+ case ImageFormat.Types.Format.Vec32F2:
+ // sizeof float may be wrong since it's platform-dependent, but we assume that it's constant across all supported platforms.
+ return sizeof(float);
+ case ImageFormat.Types.Format.Lab8:
+ return sizeof(byte);
+ case ImageFormat.Types.Format.Ycbcr420P:
+ case ImageFormat.Types.Format.Ycbcr420P10:
+ case ImageFormat.Types.Format.Unknown:
+ default:
+ return 0;
+ }
+ }
+
+ ///
+ /// The depth of each channel in bytes for a .
+ /// If channels don't make sense in the , returns 0.
+ ///
+ ///
+ /// Unlike the original implementation, this API won't signal SIGABRT.
+ ///
+ public static int ByteDepthForFormat(ImageFormat.Types.Format format)
+ {
+ switch (format)
+ {
+ case ImageFormat.Types.Format.Srgb:
+ case ImageFormat.Types.Format.Srgba:
+ case ImageFormat.Types.Format.Sbgra:
+ return 1;
+ case ImageFormat.Types.Format.Srgb48:
+ case ImageFormat.Types.Format.Srgba64:
+ return 2;
+ case ImageFormat.Types.Format.Gray8:
+ return 1;
+ case ImageFormat.Types.Format.Gray16:
+ return 2;
+ case ImageFormat.Types.Format.Vec32F1:
+ case ImageFormat.Types.Format.Vec32F2:
+ return 4;
+ case ImageFormat.Types.Format.Lab8:
+ return 1;
+ case ImageFormat.Types.Format.Ycbcr420P:
+ case ImageFormat.Types.Format.Ycbcr420P10:
+ case ImageFormat.Types.Format.Unknown:
+ default:
+ return 0;
+ }
+ }
+
public bool IsEmpty()
{
return SafeNativeMethods.mp_ImageFrame__IsEmpty(mpPtr);
@@ -92,28 +198,40 @@ public int Height()
return SafeNativeMethods.mp_ImageFrame__Height(mpPtr);
}
+ ///
+ /// The channel size.
+ /// If channels don't make sense, returns 0.
+ ///
+ ///
+ /// Unlike the original implementation, this API won't signal SIGABRT.
+ ///
public int ChannelSize()
{
- var code = SafeNativeMethods.mp_ImageFrame__ChannelSize(mpPtr, out var value);
-
- GC.KeepAlive(this);
- return ValueOrFormatException(code, value);
+ return ChannelSizeForFormat(Format());
}
+ ///
+ /// The Number of channels.
+ /// If channels don't make sense, returns 0.
+ ///
+ ///
+ /// Unlike the original implementation, this API won't signal SIGABRT.
+ ///
public int NumberOfChannels()
{
- var code = SafeNativeMethods.mp_ImageFrame__NumberOfChannels(mpPtr, out var value);
-
- GC.KeepAlive(this);
- return ValueOrFormatException(code, value);
+ return NumberOfChannelsForFormat(Format());
}
+ ///
+ /// The depth of each image channel in bytes.
+ /// If channels don't make sense, returns 0.
+ ///
+ ///
+ /// Unlike the original implementation, this API won't signal SIGABRT.
+ ///
public int ByteDepth()
{
- var code = SafeNativeMethods.mp_ImageFrame__ByteDepth(mpPtr, out var value);
-
- GC.KeepAlive(this);
- return ValueOrFormatException(code, value);
+ return ByteDepthForFormat(Format());
}
public int WidthStep()
@@ -128,15 +246,19 @@ public IntPtr MutablePixelData()
public int PixelDataSize()
{
- return SafeNativeMethods.mp_ImageFrame__PixelDataSize(mpPtr);
+ return Height() * WidthStep();
}
+ ///
+ /// The total size the pixel data would take if it was stored contiguously (which may not be the case).
+ /// If channels don't make sense, returns 0.
+ ///
+ ///
+ /// Unlike the original implementation, this API won't signal SIGABRT.
+ ///
public int PixelDataSizeStoredContiguously()
{
- var code = SafeNativeMethods.mp_ImageFrame__PixelDataSizeStoredContiguously(mpPtr, out var value);
-
- GC.KeepAlive(this);
- return ValueOrFormatException(code, value);
+ return Width() * Height() * ByteDepth() * NumberOfChannels();
}
public void SetToZero()
@@ -196,63 +318,6 @@ public Color32[] GetPixels32(bool flipVertically = false)
return GetPixels32(flipVertically, new Color32[Width() * Height()]);
}
- ///
- /// Get the value of a specific channel only.
- /// It's useful when only one channel is used (e.g. Hair Segmentation mask).
- ///
- ///
- /// Specify from which channel the data will be retrieved.
- /// For example, if the format is RGB, 0 means R channel, 1 means G channel, and 2 means B channel.
- ///
- ///
- /// The array to which the output data will be written.
- ///
- public byte[] GetChannel(int channelNumber, bool flipVertically, byte[] colors)
- {
- var format = Format();
-
-#pragma warning disable IDE0010
- switch (format)
- {
- case ImageFormat.Types.Format.Srgb:
- {
- if (channelNumber < 0 || channelNumber > 3)
- {
- throw new ArgumentException($"There are only 3 channels, but No. {channelNumber} is specified");
- }
- ReadChannel(MutablePixelData(), channelNumber, 3, Width(), Height(), WidthStep(), flipVertically, colors);
- return colors;
- }
- case ImageFormat.Types.Format.Srgba:
- {
- if (channelNumber < 0 || channelNumber > 4)
- {
- throw new ArgumentException($"There are only 4 channels, but No. {channelNumber} is specified");
- }
- ReadChannel(MutablePixelData(), channelNumber, 4, Width(), Height(), WidthStep(), flipVertically, colors);
- return colors;
- }
- default:
- {
- throw new NotImplementedException($"Currently only SRGB and SRGBA format are supported: {format}");
- }
- }
-#pragma warning restore IDE0010
- }
-
- ///
- /// Get the value of a specific channel only.
- /// It's useful when only one channel is used (e.g. Hair Segmentation mask).
- ///
- ///
- /// Specify from which channel the data will be retrieved.
- /// For example, if the format is RGB, 0 means R channel, 1 means G channel, and 2 means B channel.
- ///
- public byte[] GetChannel(int channelNumber, bool flipVertically)
- {
- return GetChannel(channelNumber, flipVertically, new byte[Width() * Height()]);
- }
-
private delegate MpReturnCode CopyToBufferHandler(IntPtr ptr, IntPtr buffer, int bufferSize);
private T[] CopyToBuffer(CopyToBufferHandler handler, int bufferSize) where T : unmanaged
@@ -271,19 +336,6 @@ private T[] CopyToBuffer(CopyToBufferHandler handler, int bufferSize) where T
return buffer;
}
- private T ValueOrFormatException(MpReturnCode code, T value)
- {
- try
- {
- code.Assert();
- return value;
- }
- catch (MediaPipeException)
- {
- throw new FormatException($"Invalid image format: {Format()}");
- }
- }
-
///
/// In the source array, pixels are laid out left to right, top to bottom,
/// but in the returned array, left to right, top to bottom.
@@ -395,57 +447,5 @@ private static void ReadSRGBAByteArray(IntPtr ptr, int width, int height, int wi
}
}
}
-
- ///
- /// In the source array, pixels are laid out left to right, top to bottom,
- /// but in the returned array, left to right, top to bottom.
- ///
- private static void ReadChannel(IntPtr ptr, int channelNumber, int channelCount, int width, int height, int widthStep, bool flipVertically, byte[] colors)
- {
- if (colors.Length != width * height)
- {
- throw new ArgumentException("colors length is invalid");
- }
- var padding = widthStep - (channelCount * width);
-
- unsafe
- {
- fixed (byte* dest = colors)
- {
- var pSrc = (byte*)ptr.ToPointer();
- pSrc += channelNumber;
-
- if (flipVertically)
- {
- var pDest = dest + colors.Length - 1;
-
- for (var i = 0; i < height; i++)
- {
- for (var j = 0; j < width; j++)
- {
- *pDest-- = *pSrc;
- pSrc += channelCount;
- }
- pSrc += padding;
- }
- }
- else
- {
- var pDest = dest + (width * (height - 1));
-
- for (var i = 0; i < height; i++)
- {
- for (var j = 0; j < width; j++)
- {
- *pDest++ = *pSrc;
- pSrc += channelCount;
- }
- pSrc += padding;
- pDest -= 2 * width;
- }
- }
- }
- }
- }
}
}
diff --git a/Packages/com.github.homuler.mediapipe/Runtime/Scripts/PInvoke/NativeMethods/Framework/Format/ImageFrame_Safe.cs b/Packages/com.github.homuler.mediapipe/Runtime/Scripts/PInvoke/NativeMethods/Framework/Format/ImageFrame_Safe.cs
index 202454f1e..ff051301f 100644
--- a/Packages/com.github.homuler.mediapipe/Runtime/Scripts/PInvoke/NativeMethods/Framework/Format/ImageFrame_Safe.cs
+++ b/Packages/com.github.homuler.mediapipe/Runtime/Scripts/PInvoke/NativeMethods/Framework/Format/ImageFrame_Safe.cs
@@ -33,27 +33,12 @@ public static extern MpReturnCode mp_ImageFrame__IsAligned__ui(
[Pure, DllImport(MediaPipeLibrary, ExactSpelling = true)]
public static extern int mp_ImageFrame__Height(IntPtr imageFrame);
- [Pure, DllImport(MediaPipeLibrary, ExactSpelling = true)]
- public static extern MpReturnCode mp_ImageFrame__ChannelSize(IntPtr imageFrame, out int value);
-
- [Pure, DllImport(MediaPipeLibrary, ExactSpelling = true)]
- public static extern MpReturnCode mp_ImageFrame__NumberOfChannels(IntPtr imageFrame, out int value);
-
- [Pure, DllImport(MediaPipeLibrary, ExactSpelling = true)]
- public static extern MpReturnCode mp_ImageFrame__ByteDepth(IntPtr imageFrame, out int value);
-
[Pure, DllImport(MediaPipeLibrary, ExactSpelling = true)]
public static extern int mp_ImageFrame__WidthStep(IntPtr imageFrame);
[Pure, DllImport(MediaPipeLibrary, ExactSpelling = true)]
public static extern IntPtr mp_ImageFrame__MutablePixelData(IntPtr imageFrame);
- [Pure, DllImport(MediaPipeLibrary, ExactSpelling = true)]
- public static extern int mp_ImageFrame__PixelDataSize(IntPtr imageFrame);
-
- [Pure, DllImport(MediaPipeLibrary, ExactSpelling = true)]
- public static extern MpReturnCode mp_ImageFrame__PixelDataSizeStoredContiguously(IntPtr imageFrame, out int value);
-
#region StatusOr
[Pure, DllImport(MediaPipeLibrary, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.I1)]
diff --git a/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Unity/Annotation/MaskAnnotation.cs b/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Unity/Annotation/MaskAnnotation.cs
index 7baa15b32..cececef97 100644
--- a/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Unity/Annotation/MaskAnnotation.cs
+++ b/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Unity/Annotation/MaskAnnotation.cs
@@ -5,8 +5,9 @@
// https://opensource.org/licenses/MIT.
using System;
-using System.Linq;
+using System.Runtime.InteropServices;
using UnityEngine;
+using UnityEngine.Rendering;
using UnityEngine.UI;
namespace Mediapipe.Unity
@@ -18,121 +19,102 @@ namespace Mediapipe.Unity
public class MaskAnnotation : HierarchicalAnnotation
{
[SerializeField] private RawImage _screen;
+ [SerializeField] private Shader _maskShader;
+ [SerializeField] private Texture2D _maskTexture;
[SerializeField] private Color _color = Color.blue;
+ [SerializeField, Range(0, 1)] private float _minConfidence = 0.9f;
- private Texture2D _texture;
- private Color32[] _colors;
+ private Material _material;
+ private GraphicsBuffer _maskBuffer;
- public void InitScreen()
+ private void OnEnable()
{
- var rect = GetAnnotationLayer().rect;
- var width = (int)rect.width;
- var height = (int)rect.height;
-
- var transparentColor = new Color32((byte)(255 * _color.r), (byte)(255 * _color.g), (byte)(255 * _color.b), 0);
- _colors = Enumerable.Repeat(transparentColor, width * height).ToArray();
+ ApplyMaskTexture(_maskTexture, _color);
+ ApplyMinConfidence(_minConfidence);
+ }
- _texture = new Texture2D(width, height, TextureFormat.RGBA32, false);
- _texture.SetPixels32(_colors);
- _screen.texture = _texture;
- _screen.color = new Color(1, 1, 1, 1);
+ private void OnDisable()
+ {
+ ApplyMinConfidence(1.1f); // larger than the maximum value (1.0).
}
- public void SetColor(Color color)
+ private void OnValidate()
{
- _color = color;
- ApplyColor(_color);
+ ApplyMaskTexture(_maskTexture, _color);
+ ApplyMinConfidence(_minConfidence);
}
- public void Draw(byte[] mask, int width, int height, float minAlpha = 0.9f, float maxAlpha = 1.0f)
+ private void OnDestroy()
{
- if (mask.Length != width * height)
+ if (_maskBuffer != null)
{
- throw new ArgumentException("mask size must equal width * height");
+ _maskBuffer.Release();
}
+ }
- ResetAlpha();
- var alphaCoeff = Mathf.Clamp(maxAlpha, 0.0f, 1.0f);
- var threshold = (byte)(255 * minAlpha);
- var wInterval = (float)_texture.width / width;
- var hInterval = (float)_texture.height / height;
+ public void InitScreen(int width, int height)
+ {
+ _screen.color = new Color(1, 1, 1, 1);
- unsafe
+ _material = new Material(_maskShader)
{
- fixed (byte* ptr = mask)
- {
- var maskPtr = ptr;
-
- for (var i = 0; i < height; i++)
- {
- for (var j = 0; j < width; j++)
- {
- if (*maskPtr >= threshold)
- {
- var alpha = (byte)((*maskPtr) * alphaCoeff);
- SetColorAlpha(GetNearestRange(j, wInterval, _texture.width), GetNearestRange(i, hInterval, _texture.height), alpha);
- }
- maskPtr++;
- }
- }
- }
- }
- _texture.SetPixels32(_colors);
- _texture.Apply();
+ renderQueue = (int)RenderQueue.Transparent
+ };
+
+ _material.SetTexture("_MainTex", _screen.texture);
+ ApplyMaskTexture(_maskTexture, _color);
+ _material.SetInt("_Width", width);
+ _material.SetInt("_Height", height);
+ ApplyMinConfidence(_minConfidence);
+ InitMaskBuffer(width, height);
+
+ _screen.material = _material;
}
- private void SetColorAlpha((int, int) xRange, (int, int) yRange, byte alpha)
+ public void Draw(float[] mask, int width, int height)
{
- unsafe
+ if (mask.Length != width * height)
{
- fixed (Color32* ptr = _colors)
- {
- var rowPtr = ptr + (yRange.Item1 * _texture.width);
-
- for (var i = yRange.Item1; i <= yRange.Item2; i++)
- {
- var colorPtr = rowPtr + xRange.Item1;
- for (var j = xRange.Item1; j <= xRange.Item2; j++)
- {
- colorPtr++->a = alpha;
- }
- rowPtr += _texture.width;
- }
- }
+ throw new ArgumentException("mask size must equal width * height");
}
+
+ _maskBuffer.SetData(mask);
}
- private (int, int) GetNearestRange(int p, float interval, int max)
+ private Texture2D CreateMonoColorTexture(Color color)
{
- var start = (int)Math.Ceiling((p - 0.5) * interval);
- var end = (int)Math.Floor((p + 0.5f) * interval);
+ var texture = new Texture2D(1, 1, TextureFormat.RGBA32, false);
+ var textureColor = new Color32((byte)(255 * color.r), (byte)(255 * color.g), (byte)(255 * color.b), 255);
+ texture.SetPixels32(new Color32[] { textureColor });
+ texture.Apply();
- return (Mathf.Max(0, start), Mathf.Min(end, max - 1));
+ return texture;
}
- private void ApplyColor(Color color)
+ private void InitMaskBuffer(int width, int height)
{
- if (_colors == null) { return; }
-
- var r = (byte)(255 * color.r);
- var g = (byte)(255 * color.g);
- var b = (byte)(255 * color.b);
-
- for (var i = 0; i < _colors.Length; i++)
+ if (_maskBuffer != null)
{
- _colors[i].r = r;
- _colors[i].g = g;
- _colors[i].b = b;
+ _maskBuffer.Release();
}
+ var stride = Marshal.SizeOf(typeof(float));
+ _maskBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, width * height, stride);
+ _material.SetBuffer("_MaskBuffer", _maskBuffer);
}
- private void ResetAlpha()
+ private void ApplyMaskTexture(Texture maskTexture, Color color)
{
- if (_colors == null) { return; }
+ if (_material != null)
+ {
+ _material.SetTexture("_MaskTex", maskTexture == null ? CreateMonoColorTexture(color) : maskTexture);
+ }
+ }
- for (var i = 0; i < _colors.Length; i++)
+ private void ApplyMinConfidence(float minConfidence)
+ {
+ if (_material != null)
{
- _colors[i].a = 0;
+ _material.SetFloat("_MinConfidence", minConfidence);
}
}
}
diff --git a/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Unity/Annotation/MaskAnnotationController.cs b/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Unity/Annotation/MaskAnnotationController.cs
index 61239c23e..941b00cc3 100644
--- a/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Unity/Annotation/MaskAnnotationController.cs
+++ b/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Unity/Annotation/MaskAnnotationController.cs
@@ -12,16 +12,14 @@ public class MaskAnnotationController : AnnotationController
{
[SerializeField] private int _maskWidth = 512;
[SerializeField] private int _maskHeight = 512;
- [SerializeField, Range(0, 1)] private float _minAlpha = 0.9f;
- [SerializeField, Range(0, 1)] private float _maxAlpha = 1.0f;
private ImageFrame _currentTarget;
- private byte[] _maskArray;
+ private float[] _maskArray;
public void InitScreen()
{
- _maskArray = new byte[_maskWidth * _maskHeight];
- annotation.InitScreen();
+ _maskArray = new float[_maskWidth * _maskHeight];
+ annotation.InitScreen(_maskWidth, _maskHeight);
}
public void DrawNow(ImageFrame target)
@@ -41,14 +39,15 @@ private void UpdateMaskArray(ImageFrame imageFrame)
{
if (imageFrame != null)
{
- var _ = imageFrame.GetChannel(0, isMirrored, _maskArray);
+ // NOTE: assume that the image is transformed properly by calculators.
+ var _ = imageFrame.TryReadChannelNormalized(0, _maskArray);
}
}
protected override void SyncNow()
{
isStale = false;
- annotation.Draw(_maskArray, _maskWidth, _maskHeight, _minAlpha, _maxAlpha);
+ annotation.Draw(_maskArray, _maskWidth, _maskHeight);
}
}
}
diff --git a/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Unity/Extension/ImageFrameExtension.cs b/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Unity/Extension/ImageFrameExtension.cs
new file mode 100644
index 000000000..bf2e9f58a
--- /dev/null
+++ b/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Unity/Extension/ImageFrameExtension.cs
@@ -0,0 +1,420 @@
+// Copyright (c) 2021 homuler
+//
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT.
+
+using System;
+
+namespace Mediapipe.Unity
+{
+ public static class ImageFrameExtension
+ {
+ ///
+ /// Read the specific channel data only.
+ /// It's useful when only one channel is used (e.g. Hair Segmentation mask).
+ ///
+ ///
+ /// true if the channel data is read successfully; otherwise false.
+ ///
+ ///
+ /// Specify from which channel (0-indexed) the data will be retrieved.
+ /// For example, if the format is RGB, 0 means R channel, 1 means G channel, and 2 means B channel.
+ ///
+ ///
+ /// The array to which the output data will be written.
+ ///
+ ///
+ /// Set true if the is flipped horizontally.
+ ///
+ ///
+ /// Set true if the is flipped vertically.
+ ///
+ public static bool TryReadChannel(this ImageFrame imageFrame, int channelNumber, byte[] channelData, bool isHorizontallyFlipped = false, bool isVerticallyFlipped = false)
+ {
+ var format = imageFrame.Format();
+ var channelCount = ImageFrame.NumberOfChannelsForFormat(format);
+ if (!IsChannelNumberValid(channelCount, channelNumber))
+ {
+ return false;
+ }
+
+#pragma warning disable IDE0010
+ switch (format)
+ {
+ case ImageFormat.Types.Format.Srgb:
+ case ImageFormat.Types.Format.Srgba:
+ case ImageFormat.Types.Format.Sbgra:
+ case ImageFormat.Types.Format.Gray8:
+ case ImageFormat.Types.Format.Lab8:
+ return TryReadChannel(imageFrame, channelCount, channelNumber, channelData, isHorizontallyFlipped, isVerticallyFlipped);
+ default:
+ Logger.LogWarning("The channel data is not stored in bytes");
+ return false;
+ }
+#pragma warning restore IDE0010
+ }
+
+ ///
+ /// Read the specific channel data only.
+ /// It's useful when only one channel is used (e.g. Hair Segmentation mask).
+ ///
+ ///
+ /// true if the channel data is read successfully; otherwise false.
+ ///
+ ///
+ /// Specify from which channel (0-indexed) the data will be retrieved.
+ /// For example, if the format is RGB, 0 means R channel, 1 means G channel, and 2 means B channel.
+ ///
+ ///
+ /// The array to which the output data will be written.
+ ///
+ ///
+ /// Set true if the is flipped horizontally.
+ ///
+ ///
+ /// Set true if the is flipped vertically.
+ ///
+ public static bool TryReadChannel(this ImageFrame imageFrame, int channelNumber, ushort[] channelData, bool isHorizontallyFlipped = false, bool isVerticallyFlipped = false)
+ {
+ var format = imageFrame.Format();
+ var channelCount = ImageFrame.NumberOfChannelsForFormat(format);
+ if (!IsChannelNumberValid(channelCount, channelNumber))
+ {
+ return false;
+ }
+
+#pragma warning disable IDE0010
+ switch (format)
+ {
+ case ImageFormat.Types.Format.Srgb48:
+ case ImageFormat.Types.Format.Srgba64:
+ case ImageFormat.Types.Format.Gray16:
+ return TryReadChannel(imageFrame, channelCount, channelNumber, channelData, isHorizontallyFlipped, isVerticallyFlipped);
+ default:
+ Logger.LogWarning("The channel data is not stored in ushorts");
+ return false;
+ }
+#pragma warning restore IDE0010
+ }
+
+ ///
+ /// Read the specific channel data only.
+ /// It's useful when only one channel is used (e.g. Selfie Segmentation mask).
+ ///
+ ///
+ /// true if the channel data is read successfully; otherwise false.
+ ///
+ ///
+ /// Specify from which channel (0-indexed) the data will be retrieved.
+ ///
+ ///
+ /// The array to which the output data will be written.
+ ///
+ ///
+ /// Set true if the is flipped horizontally.
+ ///
+ ///
+ /// Set true if the is flipped vertically.
+ ///
+ public static bool TryReadChannel(this ImageFrame imageFrame, int channelNumber, float[] channelData, bool isHorizontallyFlipped = false, bool isVerticallyFlipped = false)
+ {
+ var format = imageFrame.Format();
+ var channelCount = ImageFrame.NumberOfChannelsForFormat(format);
+ if (!IsChannelNumberValid(channelCount, channelNumber))
+ {
+ return false;
+ }
+
+#pragma warning disable IDE0010
+ switch (format)
+ {
+ case ImageFormat.Types.Format.Vec32F1:
+ case ImageFormat.Types.Format.Vec32F2:
+ return TryReadChannel(imageFrame, channelCount, channelNumber, channelData, isHorizontallyFlipped, isVerticallyFlipped);
+ default:
+ Logger.LogWarning("The channel data is not stored in floats");
+ return false;
+ }
+#pragma warning restore IDE0010
+ }
+
+ ///
+ /// Read the specific channel data only.
+ /// Each value in will be normalized to [0.0, 1.0].
+ ///
+ ///
+ /// true if the channel data is read successfully; otherwise false.
+ ///
+ ///
+ /// Specify from which channel (0-indexed) the data will be retrieved.
+ ///
+ ///
+ /// The array to which the output data will be written.
+ ///
+ ///
+ /// Set true if the is flipped horizontally.
+ ///
+ ///
+ /// Set true if the is flipped vertically.
+ ///
+ public static bool TryReadChannelNormalized(this ImageFrame imageFrame, int channelNumber, float[] normalizedChannelData, bool isHorizontallyFlipped = false, bool isVerticallyFlipped = false)
+ {
+ var format = imageFrame.Format();
+ var channelCount = ImageFrame.NumberOfChannelsForFormat(format);
+ if (!IsChannelNumberValid(channelCount, channelNumber))
+ {
+ return false;
+ }
+
+#pragma warning disable IDE0010
+ switch (format)
+ {
+ case ImageFormat.Types.Format.Srgb:
+ case ImageFormat.Types.Format.Srgba:
+ case ImageFormat.Types.Format.Sbgra:
+ case ImageFormat.Types.Format.Gray8:
+ case ImageFormat.Types.Format.Lab8:
+ return TryReadChannel(imageFrame, channelCount, channelNumber, v => (float)v / ((1 << 8) - 1), normalizedChannelData, isHorizontallyFlipped, isVerticallyFlipped);
+ case ImageFormat.Types.Format.Srgb48:
+ case ImageFormat.Types.Format.Srgba64:
+ case ImageFormat.Types.Format.Gray16:
+ return TryReadChannel(imageFrame, channelCount, channelNumber, v => (float)v / ((1 << 16) - 1), normalizedChannelData, isHorizontallyFlipped, isVerticallyFlipped);
+ case ImageFormat.Types.Format.Vec32F1:
+ case ImageFormat.Types.Format.Vec32F2:
+ return TryReadChannel(imageFrame, channelCount, channelNumber, normalizedChannelData, isHorizontallyFlipped, isVerticallyFlipped);
+ default:
+ Logger.LogWarning("Channels don't make sense in the current context");
+ return false;
+ }
+#pragma warning restore IDE0010
+ }
+
+ private static bool TryReadChannel(ImageFrame imageFrame, int channelCount, int channelNumber, T[] channelData, bool isHorizontallyFlipped, bool isVerticallyFlipped) where T : unmanaged
+ {
+ var width = imageFrame.Width();
+ var height = imageFrame.Height();
+ var length = width * height;
+
+ if (channelData.Length != length)
+ {
+ Logger.LogWarning($"The length of channelData () does not equal {length}");
+ return false;
+ }
+
+ var widthStep = imageFrame.WidthStep();
+ var byteDepth = imageFrame.ByteDepth();
+
+ unsafe
+ {
+ fixed (T* dest = channelData)
+ {
+ // NOTE: We cannot assume that the pixel data is aligned properly.
+ var pLine = (byte*)imageFrame.MutablePixelData();
+ pLine += byteDepth * channelNumber;
+
+ if (isVerticallyFlipped)
+ {
+ if (isHorizontallyFlipped)
+ {
+ // The first element is at bottom-right.
+ var pDest = dest + width - 1;
+
+ for (var i = 0; i < height; i++)
+ {
+ var pSrc = (T*)pLine;
+ for (var j = 0; j < width; j++)
+ {
+ *pDest-- = *pSrc;
+ pSrc += channelCount;
+ }
+ pLine += widthStep;
+ pDest += 2 * width;
+ }
+ }
+ else
+ {
+ // The first element is at bottom-left.
+ // NOTE: In the Unity coordinate system, the image can be considered as not flipped.
+ var pDest = dest;
+
+ for (var i = 0; i < height; i++)
+ {
+ var pSrc = (T*)pLine;
+ for (var j = 0; j < width; j++)
+ {
+ *pDest++ = *pSrc;
+ pSrc += channelCount;
+ }
+ pLine += widthStep;
+ }
+ }
+ }
+ else
+ {
+ if (isHorizontallyFlipped)
+ {
+ // The first element is at top-right.
+ var pDest = dest + length - 1;
+
+ for (var i = 0; i < height; i++)
+ {
+ var pSrc = (T*)pLine;
+ for (var j = 0; j < width; j++)
+ {
+ *pDest-- = *pSrc;
+ pSrc += channelCount;
+ }
+ pLine += widthStep;
+ }
+ }
+ else
+ {
+ // The first element is at top-left (the image is not flipped at all).
+ var pDest = dest + (width * (height - 1));
+
+ for (var i = 0; i < height; i++)
+ {
+ var pSrc = (T*)pLine;
+ for (var j = 0; j < width; j++)
+ {
+ *pDest++ = *pSrc;
+ pSrc += channelCount;
+ }
+ pLine += widthStep;
+ pDest -= 2 * width;
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private static bool TryReadChannel(ImageFrame imageFrame, int channelCount, int channelNumber, Func transformer,
+ TDst[] channelData, bool isHorizontallyFlipped, bool isVerticallyFlipped) where TSrc : unmanaged where TDst : unmanaged
+ {
+ var width = imageFrame.Width();
+ var height = imageFrame.Height();
+ var length = width * height;
+
+ if (channelData.Length != length)
+ {
+ Logger.LogWarning($"The length of channelData () does not equal {length}");
+ return false;
+ }
+
+ var widthStep = imageFrame.WidthStep();
+ var byteDepth = imageFrame.ByteDepth();
+
+ unsafe
+ {
+ fixed (TDst* dest = channelData)
+ {
+ // NOTE: We cannot assume that the pixel data is aligned properly.
+ var pLine = (byte*)imageFrame.MutablePixelData();
+ pLine += byteDepth * channelNumber;
+
+ if (isVerticallyFlipped)
+ {
+ if (isHorizontallyFlipped)
+ {
+ // The first element is at bottom-right.
+ var pDest = dest + width - 1;
+
+ for (var i = 0; i < height; i++)
+ {
+ var pSrc = (TSrc*)pLine;
+ for (var j = 0; j < width; j++)
+ {
+ *pDest-- = transformer(*pSrc);
+ pSrc += channelCount;
+ }
+ pLine += widthStep;
+ pDest += 2 * width;
+ }
+ }
+ else
+ {
+ // The first element is at bottom-left.
+ // NOTE: In the Unity coordinate system, the image can be considered as not flipped.
+ var pDest = dest;
+
+ for (var i = 0; i < height; i++)
+ {
+ var pSrc = (TSrc*)pLine;
+ for (var j = 0; j < width; j++)
+ {
+ *pDest++ = transformer(*pSrc);
+ pSrc += channelCount;
+ }
+ pLine += widthStep;
+ }
+ }
+ }
+ else
+ {
+ if (isHorizontallyFlipped)
+ {
+ // The first element is at top-right.
+ var pDest = dest + length - 1;
+
+ for (var i = 0; i < height; i++)
+ {
+ var pSrc = (TSrc*)pLine;
+ for (var j = 0; j < width; j++)
+ {
+ *pDest-- = transformer(*pSrc);
+ pSrc += channelCount;
+ }
+ pLine += widthStep;
+ }
+ }
+ else
+ {
+ // The first element is at top-left (the image is not flipped at all).
+ var pDest = dest + (width * (height - 1));
+
+ for (var i = 0; i < height; i++)
+ {
+ var pSrc = (TSrc*)pLine;
+ for (var j = 0; j < width; j++)
+ {
+ *pDest++ = transformer(*pSrc);
+ pSrc += channelCount;
+ }
+ pLine += widthStep;
+ pDest -= 2 * width;
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private static bool IsChannelNumberValid(int channelCount, int channelNumber)
+ {
+ if (channelNumber < 0)
+ {
+ Logger.LogWarning($"{channelNumber} must be >= 0");
+ return false;
+ }
+
+ if (channelCount == 0)
+ {
+ Logger.LogWarning("Channels don't make sense in the current context");
+ return false;
+ }
+
+ if (channelNumber >= channelCount)
+ {
+ Logger.LogWarning($"channelNumber must be <= {channelCount - 1} since there are only {channelCount} channels, but {channelNumber} is given");
+ return false;
+ }
+ return true;
+ }
+ }
+}
diff --git a/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Unity/Extension/ImageFrameExtension.cs.meta b/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Unity/Extension/ImageFrameExtension.cs.meta
new file mode 100644
index 000000000..524953389
--- /dev/null
+++ b/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Unity/Extension/ImageFrameExtension.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3d2ed92677011f5249a32983f023f216
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Packages/com.github.homuler.mediapipe/Runtime/Shaders/Convergence Shader.shader b/Packages/com.github.homuler.mediapipe/Runtime/Shaders/Convergence Shader.shader
index 1171f0423..d9b0fc585 100644
--- a/Packages/com.github.homuler.mediapipe/Runtime/Shaders/Convergence Shader.shader
+++ b/Packages/com.github.homuler.mediapipe/Runtime/Shaders/Convergence Shader.shader
@@ -1,4 +1,4 @@
-Shader "Unlit/Convergence Shader"
+Shader "Unlit/MediaPipe/Convergence Shader"
{
Properties
{
diff --git a/Packages/com.github.homuler.mediapipe/Runtime/Shaders/MaskShader.shader b/Packages/com.github.homuler.mediapipe/Runtime/Shaders/MaskShader.shader
new file mode 100644
index 000000000..ad46ab16e
--- /dev/null
+++ b/Packages/com.github.homuler.mediapipe/Runtime/Shaders/MaskShader.shader
@@ -0,0 +1,75 @@
+Shader "Unlit/MediaPipe/Mask Shader"
+{
+ Properties
+ {
+ _MainTex ("Main Texture", 2D) = "" {}
+ _MaskTex ("Mask Texture", 2D) = "blue" {}
+ _Width ("Mask Width", Int) = 0
+ _Height ("Mask Height", Int) = 0
+ _MinConfidence ("Min Confidence", Range(0.0, 1.0)) = 0.9
+ }
+
+ SubShader
+ {
+ Tags { "RenderType"="Transparent" }
+ LOD 100
+
+ Pass
+ {
+ CGPROGRAM
+ #pragma vertex vert
+ #pragma fragment frag
+ // make fog work
+ #pragma multi_compile_fog
+
+ #include "UnityCG.cginc"
+
+ struct appdata
+ {
+ float4 vertex : POSITION;
+ float2 uv : TEXCOORD0;
+ };
+
+ struct v2f
+ {
+ float2 uv : TEXCOORD0;
+ UNITY_FOG_COORDS(1)
+ float4 vertex : SV_POSITION;
+ };
+
+ sampler2D _MainTex;
+ float4 _MainTex_ST;
+
+ v2f vert (appdata v)
+ {
+ v2f o;
+ o.vertex = UnityObjectToClipPos(v.vertex);
+ o.uv = TRANSFORM_TEX(v.uv, _MainTex);
+ UNITY_TRANSFER_FOG(o,o.vertex);
+ return o;
+ }
+
+ sampler2D _MaskTex;
+
+ int _Width;
+ int _Height;
+ float _MinConfidence;
+ uniform StructuredBuffer _MaskBuffer;
+
+ fixed4 frag (v2f i) : SV_Target
+ {
+ // sample the texture
+ fixed4 mainCol = tex2D(_MainTex, i.uv);
+ fixed4 maskCol = tex2D(_MaskTex, i.uv);
+ int idx = int(i.uv.y * _Height) * _Width + int(i.uv.x * _Width);
+ float mask = _MaskBuffer[idx];
+ fixed4 col = lerp(mainCol, lerp(mainCol, maskCol, mask), step(_MinConfidence, mask));
+
+ // apply fog
+ UNITY_APPLY_FOG(i.fogCoord, col);
+ return col;
+ }
+ ENDCG
+ }
+ }
+}
diff --git a/Packages/com.github.homuler.mediapipe/Runtime/Shaders/MaskShader.shader.meta b/Packages/com.github.homuler.mediapipe/Runtime/Shaders/MaskShader.shader.meta
new file mode 100644
index 000000000..c9e3a9dad
--- /dev/null
+++ b/Packages/com.github.homuler.mediapipe/Runtime/Shaders/MaskShader.shader.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 7adfa9ebe754989caa35dbb2c5526d1c
+ShaderImporter:
+ externalObjects: {}
+ defaultTextures: []
+ nonModifiableTextures: []
+ preprocessorOverride: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Packages/com.github.homuler.mediapipe/Tests/EditMode/Framework/Format/ImageFrameTest.cs b/Packages/com.github.homuler.mediapipe/Tests/EditMode/Framework/Format/ImageFrameTest.cs
index 156da7a06..bc45b22a0 100644
--- a/Packages/com.github.homuler.mediapipe/Tests/EditMode/Framework/Format/ImageFrameTest.cs
+++ b/Packages/com.github.homuler.mediapipe/Tests/EditMode/Framework/Format/ImageFrameTest.cs
@@ -15,26 +15,24 @@ namespace Tests
public class ImageFrameTest
{
#region Constructor
- [Test, SignalAbort]
+ [Test]
public void Ctor_ShouldInstantiateImageFrame_When_CalledWithNoArguments()
{
using (var imageFrame = new ImageFrame())
{
-#pragma warning disable IDE0058
Assert.AreEqual(imageFrame.Format(), ImageFormat.Types.Format.Unknown);
Assert.AreEqual(imageFrame.Width(), 0);
Assert.AreEqual(imageFrame.Height(), 0);
- Assert.Throws(() => { imageFrame.ChannelSize(); });
- Assert.Throws(() => { imageFrame.NumberOfChannels(); });
- Assert.Throws(() => { imageFrame.ByteDepth(); });
+ Assert.AreEqual(imageFrame.ChannelSize(), 0);
+ Assert.AreEqual(imageFrame.NumberOfChannels(), 0);
+ Assert.AreEqual(imageFrame.ByteDepth(), 0);
Assert.AreEqual(imageFrame.WidthStep(), 0);
Assert.AreEqual(imageFrame.PixelDataSize(), 0);
- Assert.Throws(() => { imageFrame.PixelDataSizeStoredContiguously(); });
+ Assert.AreEqual(imageFrame.PixelDataSizeStoredContiguously(), 0);
Assert.True(imageFrame.IsEmpty());
Assert.False(imageFrame.IsContiguous());
Assert.False(imageFrame.IsAligned(16));
Assert.AreEqual(imageFrame.MutablePixelData(), IntPtr.Zero);
-#pragma warning restore IDE0058
}
}
diff --git a/Packages/com.github.homuler.mediapipe/Tests/EditMode/Unity/Extension.meta b/Packages/com.github.homuler.mediapipe/Tests/EditMode/Unity/Extension.meta
new file mode 100644
index 000000000..e69c0776c
--- /dev/null
+++ b/Packages/com.github.homuler.mediapipe/Tests/EditMode/Unity/Extension.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 65e6f6718f2bed55c97d7a509412a25f
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Packages/com.github.homuler.mediapipe/Tests/EditMode/Unity/Extension/ImageFrameExtensionTest.cs b/Packages/com.github.homuler.mediapipe/Tests/EditMode/Unity/Extension/ImageFrameExtensionTest.cs
new file mode 100644
index 000000000..9bd21b5ef
--- /dev/null
+++ b/Packages/com.github.homuler.mediapipe/Tests/EditMode/Unity/Extension/ImageFrameExtensionTest.cs
@@ -0,0 +1,1091 @@
+// Copyright (c) 2021 homuler
+//
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT.
+
+using Mediapipe;
+using Mediapipe.Unity;
+
+using NUnit.Framework;
+using Unity.Collections;
+
+using System.Linq;
+
+namespace Tests
+{
+ public class ImageFrameExtensionTest
+ {
+ #region TryReadChannel(byte)
+ [Test]
+ public void TryReadChannelByte_ShouldReturnFalse_When_ChannelNumberIsNegative()
+ {
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgba, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(-1, new byte[] { }));
+ }
+ }
+
+ [Test]
+ public void TryReadChannelByte_ShouldReturnFalse_When_TheChannelDataIsNotStoredInBytes()
+ {
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgb48, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(0, new byte[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgba64, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(0, new byte[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Gray16, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(0, new byte[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Vec32F1, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(0, new byte[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Vec32F2, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(0, new byte[] { }));
+ }
+ }
+
+ [Test]
+ public void TryReadChannelByte_ShouldReturnFalse_When_ChannelNumberEqualsTheNumberOfChannels()
+ {
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgb, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(3, new byte[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgba, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(4, new byte[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Sbgra, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(4, new byte[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Gray8, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(1, new byte[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Lab8, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(3, new byte[] { }));
+ }
+ }
+
+ [Test]
+ public void TryReadChannelByte_ShouldReturnTrue_When_ChannelNumberIsLessThanTheNumberOfChannels()
+ {
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgb, 0, 0, 0, new byte[] { }))
+ {
+ Assert.True(imageFrame.TryReadChannel(2, new byte[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgba, 0, 0, 0, new byte[] { }))
+ {
+ Assert.True(imageFrame.TryReadChannel(3, new byte[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Sbgra, 0, 0, 0, new byte[] { }))
+ {
+ Assert.True(imageFrame.TryReadChannel(3, new byte[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Gray8, 0, 0, 0, new byte[] { }))
+ {
+ Assert.True(imageFrame.TryReadChannel(0, new byte[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Lab8, 0, 0, 0, new byte[] { }))
+ {
+ Assert.True(imageFrame.TryReadChannel(2, new byte[] { }));
+ }
+ }
+
+ [Test]
+ public void TryReadChannelByte_ShouldReadTheSpecifiedChannelData_When_TheFormatIsSrgb()
+ {
+ // (w, h, c) -> w << 5 + h << 3 + (c + 1)
+ var bytes = new byte[] {
+ // padding is 16 - 3 * 3 = 7
+ 1, 2, 3, 33, 34, 35, 65, 66, 67, 0, 0, 0, 0, 0, 0, 0,
+ 9, 10, 11, 41, 42, 43, 73, 74, 75, 0, 0, 0, 0, 0, 0, 0,
+ };
+ var result = new byte[6];
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgb, 3, 2, 16, bytes))
+ {
+ Assert.True(imageFrame.TryReadChannel(0, result, false, false));
+ Assert.AreEqual(result, new byte[] { 9, 41, 73, 1, 33, 65 });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, false, true));
+ Assert.AreEqual(result, new byte[] { 1, 33, 65, 9, 41, 73 });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, true, false));
+ Assert.AreEqual(result, new byte[] { 73, 41, 9, 65, 33, 1 });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, true, true));
+ Assert.AreEqual(result, new byte[] { 65, 33, 1, 73, 41, 9 });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, false, false));
+ Assert.AreEqual(result, new byte[] { 10, 42, 74, 2, 34, 66 });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, false, true));
+ Assert.AreEqual(result, new byte[] { 2, 34, 66, 10, 42, 74 });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, true, false));
+ Assert.AreEqual(result, new byte[] { 74, 42, 10, 66, 34, 2 });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, true, true));
+ Assert.AreEqual(result, new byte[] { 66, 34, 2, 74, 42, 10 });
+
+ Assert.True(imageFrame.TryReadChannel(2, result, false, false));
+ Assert.AreEqual(result, new byte[] { 11, 43, 75, 3, 35, 67 });
+
+ Assert.True(imageFrame.TryReadChannel(2, result, false, true));
+ Assert.AreEqual(result, new byte[] { 3, 35, 67, 11, 43, 75 });
+
+ Assert.True(imageFrame.TryReadChannel(2, result, true, false));
+ Assert.AreEqual(result, new byte[] { 75, 43, 11, 67, 35, 3 });
+
+ Assert.True(imageFrame.TryReadChannel(2, result, true, true));
+ Assert.AreEqual(result, new byte[] { 67, 35, 3, 75, 43, 11 });
+
+ }
+ }
+
+ [Test]
+ public void TryReadChannelByte_ShouldReadTheSpecifiedChannelData_When_TheFormatIsSrgba()
+ {
+ // (w, h, c) -> w << 5 + h << 3 + (c + 1)
+ var bytes = new byte[] {
+ // padding is 16 - 3 * 4 = 4
+ 1, 2, 3, 4, 33, 34, 35, 36, 65, 66, 67, 68, 0, 0, 0, 0,
+ 9, 10, 11, 12, 41, 42, 43, 44, 73, 74, 75, 76, 0, 0, 0, 0,
+ };
+ var result = new byte[6];
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgba, 3, 2, 16, bytes))
+ {
+ Assert.True(imageFrame.TryReadChannel(0, result, false, false));
+ Assert.AreEqual(result, new byte[] { 9, 41, 73, 1, 33, 65 });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, false, true));
+ Assert.AreEqual(result, new byte[] { 1, 33, 65, 9, 41, 73 });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, true, false));
+ Assert.AreEqual(result, new byte[] { 73, 41, 9, 65, 33, 1 });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, true, true));
+ Assert.AreEqual(result, new byte[] { 65, 33, 1, 73, 41, 9 });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, false, false));
+ Assert.AreEqual(result, new byte[] { 10, 42, 74, 2, 34, 66 });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, false, true));
+ Assert.AreEqual(result, new byte[] { 2, 34, 66, 10, 42, 74 });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, true, false));
+ Assert.AreEqual(result, new byte[] { 74, 42, 10, 66, 34, 2 });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, true, true));
+ Assert.AreEqual(result, new byte[] { 66, 34, 2, 74, 42, 10 });
+
+ Assert.True(imageFrame.TryReadChannel(2, result, false, false));
+ Assert.AreEqual(result, new byte[] { 11, 43, 75, 3, 35, 67 });
+
+ Assert.True(imageFrame.TryReadChannel(2, result, false, true));
+ Assert.AreEqual(result, new byte[] { 3, 35, 67, 11, 43, 75 });
+
+ Assert.True(imageFrame.TryReadChannel(2, result, true, false));
+ Assert.AreEqual(result, new byte[] { 75, 43, 11, 67, 35, 3 });
+
+ Assert.True(imageFrame.TryReadChannel(2, result, true, true));
+ Assert.AreEqual(result, new byte[] { 67, 35, 3, 75, 43, 11 });
+
+ Assert.True(imageFrame.TryReadChannel(3, result, false, false));
+ Assert.AreEqual(result, new byte[] { 12, 44, 76, 4, 36, 68 });
+
+ Assert.True(imageFrame.TryReadChannel(3, result, false, true));
+ Assert.AreEqual(result, new byte[] { 4, 36, 68, 12, 44, 76 });
+
+ Assert.True(imageFrame.TryReadChannel(3, result, true, false));
+ Assert.AreEqual(result, new byte[] { 76, 44, 12, 68, 36, 4 });
+
+ Assert.True(imageFrame.TryReadChannel(3, result, true, true));
+ Assert.AreEqual(result, new byte[] { 68, 36, 4, 76, 44, 12 });
+ }
+ }
+
+ [Test]
+ public void TryReadChannelByte_ShouldReadTheSpecifiedChannelData_When_TheFormatIsSbgra()
+ {
+ // (w, h, c) -> w << 5 + h << 3 + (c + 1)
+ var bytes = new byte[] {
+ // padding is 16 - 4 * 3 = 4
+ 1, 2, 3, 4, 33, 34, 35, 36, 65, 66, 67, 68, 0, 0, 0, 0,
+ 9, 10, 11, 12, 41, 42, 43, 44, 73, 74, 75, 76, 0, 0, 0, 0,
+ };
+ var result = new byte[6];
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Sbgra, 3, 2, 16, bytes))
+ {
+ Assert.True(imageFrame.TryReadChannel(0, result, false, false));
+ Assert.AreEqual(result, new byte[] { 9, 41, 73, 1, 33, 65 });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, false, true));
+ Assert.AreEqual(result, new byte[] { 1, 33, 65, 9, 41, 73 });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, true, false));
+ Assert.AreEqual(result, new byte[] { 73, 41, 9, 65, 33, 1 });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, true, true));
+ Assert.AreEqual(result, new byte[] { 65, 33, 1, 73, 41, 9 });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, false, false));
+ Assert.AreEqual(result, new byte[] { 10, 42, 74, 2, 34, 66 });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, false, true));
+ Assert.AreEqual(result, new byte[] { 2, 34, 66, 10, 42, 74 });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, true, false));
+ Assert.AreEqual(result, new byte[] { 74, 42, 10, 66, 34, 2 });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, true, true));
+ Assert.AreEqual(result, new byte[] { 66, 34, 2, 74, 42, 10 });
+
+ Assert.True(imageFrame.TryReadChannel(2, result, false, false));
+ Assert.AreEqual(result, new byte[] { 11, 43, 75, 3, 35, 67 });
+
+ Assert.True(imageFrame.TryReadChannel(2, result, false, true));
+ Assert.AreEqual(result, new byte[] { 3, 35, 67, 11, 43, 75 });
+
+ Assert.True(imageFrame.TryReadChannel(2, result, true, false));
+ Assert.AreEqual(result, new byte[] { 75, 43, 11, 67, 35, 3 });
+
+ Assert.True(imageFrame.TryReadChannel(2, result, true, true));
+ Assert.AreEqual(result, new byte[] { 67, 35, 3, 75, 43, 11 });
+
+ Assert.True(imageFrame.TryReadChannel(3, result, false, false));
+ Assert.AreEqual(result, new byte[] { 12, 44, 76, 4, 36, 68 });
+
+ Assert.True(imageFrame.TryReadChannel(3, result, false, true));
+ Assert.AreEqual(result, new byte[] { 4, 36, 68, 12, 44, 76 });
+
+ Assert.True(imageFrame.TryReadChannel(3, result, true, false));
+ Assert.AreEqual(result, new byte[] { 76, 44, 12, 68, 36, 4 });
+
+ Assert.True(imageFrame.TryReadChannel(3, result, true, true));
+ Assert.AreEqual(result, new byte[] { 68, 36, 4, 76, 44, 12 });
+ }
+ }
+
+ [Test]
+ public void TryReadChannelByte_ShouldReadTheSpecifiedChannelData_When_TheFormatIsGray8()
+ {
+ var bytes = new byte[] {
+ // padding is 16 - 3 * 1 = 13
+ 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+ var result = new byte[6];
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Gray8, 3, 2, 16, bytes))
+ {
+ Assert.True(imageFrame.TryReadChannel(0, result, false, false));
+ Assert.AreEqual(result, new byte[] { 4, 5, 6, 1, 2, 3 });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, false, true));
+ Assert.AreEqual(result, new byte[] { 1, 2, 3, 4, 5, 6 });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, true, false));
+ Assert.AreEqual(result, new byte[] { 6, 5, 4, 3, 2, 1 });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, true, true));
+ Assert.AreEqual(result, new byte[] { 3, 2, 1, 6, 5, 4 });
+ }
+ }
+
+ [Test]
+ public void TryReadChannelByte_ShouldReadTheSpecifiedChannelData_When_TheFormatIsLab8()
+ {
+ // (w, h, c) -> w << 5 + h << 3 + (c + 1)
+ var bytes = new byte[] {
+ // padding is 16 - 3 * 3 = 7
+ 1, 2, 3, 33, 34, 35, 65, 66, 67, 0, 0, 0, 0, 0, 0, 0,
+ 9, 10, 11, 41, 42, 43, 73, 74, 75, 0, 0, 0, 0, 0, 0, 0,
+ };
+ var result = new byte[6];
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Lab8, 3, 2, 16, bytes))
+ {
+ Assert.True(imageFrame.TryReadChannel(0, result, false, false));
+ Assert.AreEqual(result, new byte[] { 9, 41, 73, 1, 33, 65 });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, false, true));
+ Assert.AreEqual(result, new byte[] { 1, 33, 65, 9, 41, 73 });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, true, false));
+ Assert.AreEqual(result, new byte[] { 73, 41, 9, 65, 33, 1 });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, true, true));
+ Assert.AreEqual(result, new byte[] { 65, 33, 1, 73, 41, 9 });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, false, false));
+ Assert.AreEqual(result, new byte[] { 10, 42, 74, 2, 34, 66 });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, false, true));
+ Assert.AreEqual(result, new byte[] { 2, 34, 66, 10, 42, 74 });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, true, false));
+ Assert.AreEqual(result, new byte[] { 74, 42, 10, 66, 34, 2 });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, true, true));
+ Assert.AreEqual(result, new byte[] { 66, 34, 2, 74, 42, 10 });
+
+ Assert.True(imageFrame.TryReadChannel(2, result, false, false));
+ Assert.AreEqual(result, new byte[] { 11, 43, 75, 3, 35, 67 });
+
+ Assert.True(imageFrame.TryReadChannel(2, result, false, true));
+ Assert.AreEqual(result, new byte[] { 3, 35, 67, 11, 43, 75 });
+
+ Assert.True(imageFrame.TryReadChannel(2, result, true, false));
+ Assert.AreEqual(result, new byte[] { 75, 43, 11, 67, 35, 3 });
+
+ Assert.True(imageFrame.TryReadChannel(2, result, true, true));
+ Assert.AreEqual(result, new byte[] { 67, 35, 3, 75, 43, 11 });
+ }
+ }
+ #endregion
+
+ #region TryReadChannel(ushort)
+ [Test]
+ public void TryReadChannelUshort_ShouldReturnFalse_When_ChannelNumberIsNegative()
+ {
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgba64, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(-1, new ushort[] { }));
+ }
+ }
+
+ [Test]
+ public void TryReadChannelUshort_ShouldReturnFalse_When_TheChannelDataIsNotStoredInUshorts()
+ {
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgb, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(0, new ushort[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgba, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(0, new ushort[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Sbgra, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(0, new ushort[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Gray8, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(0, new ushort[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Vec32F1, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(0, new ushort[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Vec32F2, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(0, new ushort[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Lab8, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(0, new ushort[] { }));
+ }
+ }
+
+ [Test]
+ public void TryReadChannelUshort_ShouldReturnFalse_When_ChannelNumberEqualsTheNumberOfChannels()
+ {
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgb48, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(3, new ushort[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgba64, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(4, new ushort[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Gray16, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(1, new ushort[] { }));
+ }
+ }
+
+ [Test]
+ public void TryReadChannelUshort_ShouldReturnTrue_When_ChannelNumberIsLessThanTheNumberOfChannels()
+ {
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgb48, 0, 0, 0, new byte[] { }))
+ {
+ Assert.True(imageFrame.TryReadChannel(2, new ushort[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgba64, 0, 0, 0, new byte[] { }))
+ {
+ Assert.True(imageFrame.TryReadChannel(3, new ushort[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Gray16, 0, 0, 0, new byte[] { }))
+ {
+ Assert.True(imageFrame.TryReadChannel(0, new ushort[] { }));
+ }
+ }
+
+ [Test]
+ public void TryReadChannelUshort_ShouldReadTheSpecifiedChannelData_When_TheFormatIsSrgb48()
+ {
+ // (w, h, c) -> w << 5 + h << 3 + (c + 1)
+ var bytes = new byte[] {
+ // padding is 24 - 2 * 3 * 3 = 6
+ 1, 0, 2, 0, 3, 0, 33, 0, 34, 0, 35, 0, 65, 0, 66, 0, 67, 0, 0, 0, 0, 0, 0, 0,
+ 9, 0, 10, 0, 11, 0, 41, 0, 42, 0, 43, 0, 73, 0, 74, 0, 75, 0, 0, 0, 0, 0, 0, 0,
+ };
+ var result = new ushort[6];
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgb48, 3, 2, 24, bytes))
+ {
+ Assert.True(imageFrame.TryReadChannel(0, result, false, false));
+ Assert.AreEqual(result, new ushort[] { 9, 41, 73, 1, 33, 65 });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, false, true));
+ Assert.AreEqual(result, new ushort[] { 1, 33, 65, 9, 41, 73 });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, true, false));
+ Assert.AreEqual(result, new ushort[] { 73, 41, 9, 65, 33, 1 });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, true, true));
+ Assert.AreEqual(result, new ushort[] { 65, 33, 1, 73, 41, 9 });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, false, false));
+ Assert.AreEqual(result, new ushort[] { 10, 42, 74, 2, 34, 66 });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, false, true));
+ Assert.AreEqual(result, new ushort[] { 2, 34, 66, 10, 42, 74 });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, true, false));
+ Assert.AreEqual(result, new ushort[] { 74, 42, 10, 66, 34, 2 });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, true, true));
+ Assert.AreEqual(result, new ushort[] { 66, 34, 2, 74, 42, 10 });
+
+ Assert.True(imageFrame.TryReadChannel(2, result, false, false));
+ Assert.AreEqual(result, new ushort[] { 11, 43, 75, 3, 35, 67 });
+
+ Assert.True(imageFrame.TryReadChannel(2, result, false, true));
+ Assert.AreEqual(result, new ushort[] { 3, 35, 67, 11, 43, 75 });
+
+ Assert.True(imageFrame.TryReadChannel(2, result, true, false));
+ Assert.AreEqual(result, new ushort[] { 75, 43, 11, 67, 35, 3 });
+
+ Assert.True(imageFrame.TryReadChannel(2, result, true, true));
+ Assert.AreEqual(result, new ushort[] { 67, 35, 3, 75, 43, 11 });
+ }
+ }
+
+ [Test]
+ public void TryReadChannelUshort_ShouldReadTheSpecifiedChannelData_When_TheFormatIsSrgba64()
+ {
+ // (w, h, c) -> w << 5 + h << 3 + (c + 1)
+ var bytes = new byte[] {
+ // padding is 24 - 2 * 3 * 4 = 0
+ 1, 0, 2, 0, 3, 0, 4, 0, 33, 0, 34, 0, 35, 0, 36, 0, 65, 0, 66, 0, 67, 0, 68, 0,
+ 9, 0, 10, 0, 11, 0, 12, 0, 41, 0, 42, 0, 43, 0, 44, 0, 73, 0, 74, 0, 75, 0, 76, 0,
+ };
+ var result = new ushort[6];
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgba64, 3, 2, 24, bytes))
+ {
+ Assert.True(imageFrame.TryReadChannel(0, result, false, false));
+ Assert.AreEqual(result, new ushort[] { 9, 41, 73, 1, 33, 65 });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, false, true));
+ Assert.AreEqual(result, new ushort[] { 1, 33, 65, 9, 41, 73 });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, true, false));
+ Assert.AreEqual(result, new ushort[] { 73, 41, 9, 65, 33, 1 });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, true, true));
+ Assert.AreEqual(result, new ushort[] { 65, 33, 1, 73, 41, 9 });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, false, false));
+ Assert.AreEqual(result, new ushort[] { 10, 42, 74, 2, 34, 66 });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, false, true));
+ Assert.AreEqual(result, new ushort[] { 2, 34, 66, 10, 42, 74 });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, true, false));
+ Assert.AreEqual(result, new ushort[] { 74, 42, 10, 66, 34, 2 });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, true, true));
+ Assert.AreEqual(result, new ushort[] { 66, 34, 2, 74, 42, 10 });
+
+ Assert.True(imageFrame.TryReadChannel(2, result, false, false));
+ Assert.AreEqual(result, new ushort[] { 11, 43, 75, 3, 35, 67 });
+
+ Assert.True(imageFrame.TryReadChannel(2, result, false, true));
+ Assert.AreEqual(result, new ushort[] { 3, 35, 67, 11, 43, 75 });
+
+ Assert.True(imageFrame.TryReadChannel(2, result, true, false));
+ Assert.AreEqual(result, new ushort[] { 75, 43, 11, 67, 35, 3 });
+
+ Assert.True(imageFrame.TryReadChannel(2, result, true, true));
+ Assert.AreEqual(result, new ushort[] { 67, 35, 3, 75, 43, 11 });
+
+ Assert.True(imageFrame.TryReadChannel(3, result, false, false));
+ Assert.AreEqual(result, new ushort[] { 12, 44, 76, 4, 36, 68 });
+
+ Assert.True(imageFrame.TryReadChannel(3, result, false, true));
+ Assert.AreEqual(result, new ushort[] { 4, 36, 68, 12, 44, 76 });
+
+ Assert.True(imageFrame.TryReadChannel(3, result, true, false));
+ Assert.AreEqual(result, new ushort[] { 76, 44, 12, 68, 36, 4 });
+
+ Assert.True(imageFrame.TryReadChannel(3, result, true, true));
+ Assert.AreEqual(result, new ushort[] { 68, 36, 4, 76, 44, 12 });
+ }
+ }
+
+ [Test]
+ public void TryReadChannelUshort_ShouldReadTheSpecifiedChannelData_When_TheFormatIsGray16()
+ {
+ var bytes = new byte[] {
+ // padding is 16 - 2 * 3 = 10
+ 1, 0, 2, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 4, 0, 5, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+ var result = new ushort[6];
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Gray16, 3, 2, 16, bytes))
+ {
+ Assert.True(imageFrame.TryReadChannel(0, result, false, false));
+ Assert.AreEqual(result, new ushort[] { 4, 5, 6, 1, 2, 3 });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, false, true));
+ Assert.AreEqual(result, new ushort[] { 1, 2, 3, 4, 5, 6 });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, true, false));
+ Assert.AreEqual(result, new ushort[] { 6, 5, 4, 3, 2, 1 });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, true, true));
+ Assert.AreEqual(result, new ushort[] { 3, 2, 1, 6, 5, 4 });
+ }
+ }
+ #endregion
+
+ #region TryReadChannel(float)
+ [Test]
+ public void TryReadChannelFloat_ShouldReturnFalse_When_ChannelNumberIsNegative()
+ {
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Vec32F1, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(-1, new float[] { }));
+ }
+ }
+
+ [Test]
+ public void TryReadChannelFloat_ShouldReturnFalse_When_TheChannelDataIsNotStoredInFloats()
+ {
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgb, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(0, new float[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgba, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(0, new float[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Sbgra, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(0, new float[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgb48, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(3, new float[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgba64, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(4, new float[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Gray8, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(0, new float[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Gray16, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(0, new float[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Lab8, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(0, new float[] { }));
+ }
+ }
+
+ [Test]
+ public void TryReadChannelFloat_ShouldReturnFalse_When_ChannelNumberEqualsTheNumberOfChannels()
+ {
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Vec32F1, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(1, new float[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Vec32F2, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannel(2, new float[] { }));
+ }
+ }
+
+ [Test]
+ public void TryReadChannelFloat_ShouldReturnTrue_When_ChannelNumberIsLessThanTheNumberOfChannels()
+ {
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Vec32F1, 0, 0, 0, new byte[] { }))
+ {
+ Assert.True(imageFrame.TryReadChannel(0, new float[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Vec32F2, 0, 0, 0, new byte[] { }))
+ {
+ Assert.True(imageFrame.TryReadChannel(1, new float[] { }));
+ }
+ }
+
+ [Test]
+ public void TryReadChannelFloat_ShouldReadTheSpecifiedChannelData_When_TheFormatIsVec32F1()
+ {
+ var floats = new float[] {
+ // padding is 16 - 3 * 4 = 4
+ 1.0f, 2.0f, 3.0f, 0,
+ 4.0f, 5.0f, 6.0f, 0,
+ };
+ var bytes = FloatsToBytes(floats);
+ var result = new float[6];
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Vec32F1, 3, 2, 16, bytes))
+ {
+ Assert.True(imageFrame.TryReadChannel(0, result, false, false));
+ Assert.AreEqual(result, new float[] { 4.0f, 5.0f, 6.0f, 1.0f, 2.0f, 3.0f });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, false, true));
+ Assert.AreEqual(result, new float[] { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, true, false));
+ Assert.AreEqual(result, new float[] { 6.0f, 5.0f, 4.0f, 3.0f, 2.0f, 1.0f });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, true, true));
+ Assert.AreEqual(result, new float[] { 3.0f, 2.0f, 1.0f, 6.0f, 5.0f, 4.0f });
+ }
+ }
+
+ [Test]
+ public void TryReadChannelFloat_ShouldReadTheSpecifiedChannelData_When_TheFormatIsVec32F2()
+ {
+ // (w, h, c) -> w << 5 + h << 3 + (c + 1)
+ var floats = new float[] {
+ // padding is 32 - 2 * 3 * 4 = 8
+ 1.0f, 2.0f, 33.0f, 34.0f, 65.0f, 66.0f, 0, 0,
+ 9.0f, 10.0f, 41.0f, 42.0f, 73.0f, 74.0f, 0, 0,
+ };
+ var bytes = FloatsToBytes(floats);
+ var result = new float[6];
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Vec32F2, 3, 2, 32, bytes))
+ {
+ Assert.True(imageFrame.TryReadChannel(0, result, false, false));
+ Assert.AreEqual(result, new float[] { 9.0f, 41.0f, 73.0f, 1.0f, 33.0f, 65.0f });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, false, true));
+ Assert.AreEqual(result, new float[] { 1.0f, 33.0f, 65.0f, 9.0f, 41.0f, 73.0f });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, true, false));
+ Assert.AreEqual(result, new float[] { 73.0f, 41.0f, 9.0f, 65.0f, 33.0f, 1.0f });
+
+ Assert.True(imageFrame.TryReadChannel(0, result, true, true));
+ Assert.AreEqual(result, new float[] { 65.0f, 33.0f, 1.0f, 73.0f, 41.0f, 9.0f });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, false, false));
+ Assert.AreEqual(result, new float[] { 10.0f, 42.0f, 74.0f, 2.0f, 34.0f, 66.0f });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, false, true));
+ Assert.AreEqual(result, new float[] { 2.0f, 34.0f, 66.0f, 10.0f, 42.0f, 74.0f });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, true, false));
+ Assert.AreEqual(result, new float[] { 74.0f, 42.0f, 10.0f, 66.0f, 34.0f, 2.0f });
+
+ Assert.True(imageFrame.TryReadChannel(1, result, true, true));
+ Assert.AreEqual(result, new float[] { 66.0f, 34.0f, 2.0f, 74.0f, 42.0f, 10.0f });
+ }
+ }
+ #endregion
+
+ #region TryReadChannelNormalized
+ [Test]
+ public void TryReadChannelNormalized_ShouldReturnFalse_When_ChannelNumberIsNegative()
+ {
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgba, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannelNormalized(-1, new float[] { }));
+ }
+ }
+
+ [Test]
+ public void TryReadChannelNormalized_ShouldReturnFalse_When_ChannelNumberEqualsTheNumberOfChannels()
+ {
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgb, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannelNormalized(3, new float[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgba, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannelNormalized(4, new float[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Sbgra, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannelNormalized(4, new float[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgb48, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannelNormalized(3, new float[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgba64, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannelNormalized(4, new float[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Gray8, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannelNormalized(1, new float[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Gray16, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannelNormalized(1, new float[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Vec32F1, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannelNormalized(1, new float[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Vec32F2, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannelNormalized(2, new float[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Lab8, 0, 0, 0, new byte[] { }))
+ {
+ Assert.False(imageFrame.TryReadChannelNormalized(3, new float[] { }));
+ }
+ }
+
+ [Test]
+ public void TryReadChannelNormalized_ShouldReturnTrue_When_ChannelNumberIsLessThanTheNumberOfChannels()
+ {
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgb, 0, 0, 0, new byte[] { }))
+ {
+ Assert.True(imageFrame.TryReadChannelNormalized(2, new float[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgba, 0, 0, 0, new byte[] { }))
+ {
+ Assert.True(imageFrame.TryReadChannelNormalized(3, new float[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Sbgra, 0, 0, 0, new byte[] { }))
+ {
+ Assert.True(imageFrame.TryReadChannelNormalized(3, new float[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgb48, 0, 0, 0, new byte[] { }))
+ {
+ Assert.True(imageFrame.TryReadChannelNormalized(2, new float[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgba64, 0, 0, 0, new byte[] { }))
+ {
+ Assert.True(imageFrame.TryReadChannelNormalized(3, new float[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Gray8, 0, 0, 0, new byte[] { }))
+ {
+ Assert.True(imageFrame.TryReadChannelNormalized(0, new float[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Gray16, 0, 0, 0, new byte[] { }))
+ {
+ Assert.True(imageFrame.TryReadChannelNormalized(0, new float[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Vec32F1, 0, 0, 0, new byte[] { }))
+ {
+ Assert.True(imageFrame.TryReadChannelNormalized(0, new float[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Vec32F2, 0, 0, 0, new byte[] { }))
+ {
+ Assert.True(imageFrame.TryReadChannelNormalized(1, new float[] { }));
+ }
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Lab8, 0, 0, 0, new byte[] { }))
+ {
+ Assert.True(imageFrame.TryReadChannelNormalized(2, new float[] { }));
+ }
+ }
+
+ [Test]
+ public void TryReadChannelNormalized_ShouldReadTheSpecifiedChannelData_When_TheFormatIsSrgba()
+ {
+ // (w, h, c) -> w << 5 + h << 3 + (c + 1)
+ var bytes = new byte[] {
+ // padding is 16 - 3 * 4 = 4
+ 1, 2, 3, 4, 33, 34, 35, 36, 65, 66, 67, 68, 0, 0, 0, 0,
+ 9, 10, 11, 12, 41, 42, 43, 44, 73, 74, 75, 76, 0, 0, 0, 0,
+ };
+ var result = new float[6];
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgba, 3, 2, 16, bytes))
+ {
+ Assert.True(imageFrame.TryReadChannelNormalized(0, result, false, false));
+ AssertNormalized(result, new byte[] { 9, 41, 73, 1, 33, 65 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(0, result, false, true));
+ AssertNormalized(result, new byte[] { 1, 33, 65, 9, 41, 73 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(0, result, true, false));
+ AssertNormalized(result, new byte[] { 73, 41, 9, 65, 33, 1 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(0, result, true, true));
+ AssertNormalized(result, new byte[] { 65, 33, 1, 73, 41, 9 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(1, result, false, false));
+ AssertNormalized(result, new byte[] { 10, 42, 74, 2, 34, 66 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(1, result, false, true));
+ AssertNormalized(result, new byte[] { 2, 34, 66, 10, 42, 74 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(1, result, true, false));
+ AssertNormalized(result, new byte[] { 74, 42, 10, 66, 34, 2 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(1, result, true, true));
+ AssertNormalized(result, new byte[] { 66, 34, 2, 74, 42, 10 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(2, result, false, false));
+ AssertNormalized(result, new byte[] { 11, 43, 75, 3, 35, 67 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(2, result, false, true));
+ AssertNormalized(result, new byte[] { 3, 35, 67, 11, 43, 75 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(2, result, true, false));
+ AssertNormalized(result, new byte[] { 75, 43, 11, 67, 35, 3 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(2, result, true, true));
+ AssertNormalized(result, new byte[] { 67, 35, 3, 75, 43, 11 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(3, result, false, false));
+ AssertNormalized(result, new byte[] { 12, 44, 76, 4, 36, 68 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(3, result, false, true));
+ AssertNormalized(result, new byte[] { 4, 36, 68, 12, 44, 76 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(3, result, true, false));
+ AssertNormalized(result, new byte[] { 76, 44, 12, 68, 36, 4 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(3, result, true, true));
+ AssertNormalized(result, new byte[] { 68, 36, 4, 76, 44, 12 });
+ }
+ }
+
+ [Test]
+ public void TryReadChannelNormalized_ShouldReadTheSpecifiedChannelData_When_TheFormatIsGray8()
+ {
+ var bytes = new byte[] {
+ // padding is 16 - 3 * 1 = 13
+ 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+ var result = new float[6];
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Gray8, 3, 2, 16, bytes))
+ {
+ Assert.True(imageFrame.TryReadChannelNormalized(0, result, false, false));
+ AssertNormalized(result, new byte[] { 4, 5, 6, 1, 2, 3 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(0, result, false, true));
+ AssertNormalized(result, new byte[] { 1, 2, 3, 4, 5, 6 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(0, result, true, false));
+ AssertNormalized(result, new byte[] { 6, 5, 4, 3, 2, 1 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(0, result, true, true));
+ AssertNormalized(result, new byte[] { 3, 2, 1, 6, 5, 4 });
+ }
+ }
+
+ [Test]
+ public void TryReadChannelNormalized_ShouldReadTheSpecifiedChannelData_When_TheFormatIsSrgba64()
+ {
+ // (w, h, c) -> w << 5 + h << 3 + (c + 1)
+ var bytes = new byte[] {
+ // padding is 24 - 2 * 3 * 4 = 0
+ 1, 0, 2, 0, 3, 0, 4, 0, 33, 0, 34, 0, 35, 0, 36, 0, 65, 0, 66, 0, 67, 0, 68, 0,
+ 9, 0, 10, 0, 11, 0, 12, 0, 41, 0, 42, 0, 43, 0, 44, 0, 73, 0, 74, 0, 75, 0, 76, 0,
+ };
+ var result = new float[6];
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Srgba64, 3, 2, 24, bytes))
+ {
+ Assert.True(imageFrame.TryReadChannelNormalized(0, result, false, false));
+ AssertNormalized(result, new ushort[] { 9, 41, 73, 1, 33, 65 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(0, result, false, true));
+ AssertNormalized(result, new ushort[] { 1, 33, 65, 9, 41, 73 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(0, result, true, false));
+ AssertNormalized(result, new ushort[] { 73, 41, 9, 65, 33, 1 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(0, result, true, true));
+ AssertNormalized(result, new ushort[] { 65, 33, 1, 73, 41, 9 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(1, result, false, false));
+ AssertNormalized(result, new ushort[] { 10, 42, 74, 2, 34, 66 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(1, result, false, true));
+ AssertNormalized(result, new ushort[] { 2, 34, 66, 10, 42, 74 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(1, result, true, false));
+ AssertNormalized(result, new ushort[] { 74, 42, 10, 66, 34, 2 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(1, result, true, true));
+ AssertNormalized(result, new ushort[] { 66, 34, 2, 74, 42, 10 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(2, result, false, false));
+ AssertNormalized(result, new ushort[] { 11, 43, 75, 3, 35, 67 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(2, result, false, true));
+ AssertNormalized(result, new ushort[] { 3, 35, 67, 11, 43, 75 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(2, result, true, false));
+ AssertNormalized(result, new ushort[] { 75, 43, 11, 67, 35, 3 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(2, result, true, true));
+ AssertNormalized(result, new ushort[] { 67, 35, 3, 75, 43, 11 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(3, result, false, false));
+ AssertNormalized(result, new ushort[] { 12, 44, 76, 4, 36, 68 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(3, result, false, true));
+ AssertNormalized(result, new ushort[] { 4, 36, 68, 12, 44, 76 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(3, result, true, false));
+ AssertNormalized(result, new ushort[] { 76, 44, 12, 68, 36, 4 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(3, result, true, true));
+ AssertNormalized(result, new ushort[] { 68, 36, 4, 76, 44, 12 });
+ }
+ }
+
+ [Test]
+ public void TryReadChannelNormalized_ShouldReadTheSpecifiedChannelData_When_TheFormatIsGray16()
+ {
+ var bytes = new byte[] {
+ // padding is 16 - 2 * 3 = 10
+ 1, 0, 2, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 4, 0, 5, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+ var result = new float[6];
+
+ using (var imageFrame = BuildImageFrame(ImageFormat.Types.Format.Gray16, 3, 2, 16, bytes))
+ {
+ Assert.True(imageFrame.TryReadChannelNormalized(0, result, false, false));
+ AssertNormalized(result, new ushort[] { 4, 5, 6, 1, 2, 3 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(0, result, false, true));
+ AssertNormalized(result, new ushort[] { 1, 2, 3, 4, 5, 6 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(0, result, true, false));
+ AssertNormalized(result, new ushort[] { 6, 5, 4, 3, 2, 1 });
+
+ Assert.True(imageFrame.TryReadChannelNormalized(0, result, true, true));
+ AssertNormalized(result, new ushort[] { 3, 2, 1, 6, 5, 4 });
+ }
+ }
+ #endregion
+
+ private ImageFrame BuildImageFrame(ImageFormat.Types.Format format, int width, int height, int widthStep, byte[] pixelData)
+ {
+ var array = new NativeArray(pixelData.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
+ array.CopyFrom(pixelData);
+
+ return new ImageFrame(format, width, height, widthStep, array);
+ }
+
+ private byte[] FloatsToBytes(float[] array)
+ {
+ var bytes = new byte[array.Length * 4];
+
+ unsafe
+ {
+ fixed (float* pArray = array)
+ {
+ var pByte = (byte*)pArray;
+ for (var i = 0; i < 4 * array.Length; i++)
+ {
+ bytes[i] = *pByte++;
+ }
+ }
+ }
+ return bytes;
+ }
+
+ private void AssertNormalized(float[] result, byte[] expectedUnnormalized)
+ {
+ Assert.True(result.All((v) => v >= 0.0f && v <= 1.0f));
+ AreAlmostEqual(result, expectedUnnormalized.Select((v) => (float)v / 255).ToArray(), 1e-6);
+ }
+
+ private void AssertNormalized(float[] result, ushort[] expectedUnnormalized)
+ {
+ Assert.True(result.All((v) => v >= 0.0f && v <= 1.0f));
+ AreAlmostEqual(result, expectedUnnormalized.Select((v) => (float)v / 65525).ToArray(), 1e-6);
+ }
+
+ private void AreAlmostEqual(float[] xs, float[] ys, double threshold)
+ {
+ Assert.AreEqual(xs.Length, ys.Length);
+ Assert.True(xs.Zip(ys, (x, y) => x - y).All((diff) => UnityEngine.Mathf.Abs(diff) < threshold));
+ }
+ }
+}
diff --git a/Packages/com.github.homuler.mediapipe/Tests/EditMode/Unity/Extension/ImageFrameExtensionTest.cs.meta b/Packages/com.github.homuler.mediapipe/Tests/EditMode/Unity/Extension/ImageFrameExtensionTest.cs.meta
new file mode 100644
index 000000000..b8a26e32b
--- /dev/null
+++ b/Packages/com.github.homuler.mediapipe/Tests/EditMode/Unity/Extension/ImageFrameExtensionTest.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 46cfb5995c4d2b93899f4920ae0b016a
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/mediapipe_api/framework/formats/image_frame.cc b/mediapipe_api/framework/formats/image_frame.cc
index cbd42f7dc..07f54c0dd 100644
--- a/mediapipe_api/framework/formats/image_frame.cc
+++ b/mediapipe_api/framework/formats/image_frame.cc
@@ -62,40 +62,10 @@ int mp_ImageFrame__Width(mediapipe::ImageFrame* image_frame) { return image_fram
int mp_ImageFrame__Height(mediapipe::ImageFrame* image_frame) { return image_frame->Height(); }
-MpReturnCode mp_ImageFrame__ChannelSize(mediapipe::ImageFrame* image_frame, int* value_out) {
- TRY_ALL
- *value_out = image_frame->ChannelSize();
- RETURN_CODE(MpReturnCode::Success);
- CATCH_ALL
-}
-
-MpReturnCode mp_ImageFrame__NumberOfChannels(mediapipe::ImageFrame* image_frame, int* value_out) {
- TRY_ALL
- *value_out = image_frame->NumberOfChannels();
- RETURN_CODE(MpReturnCode::Success);
- CATCH_ALL
-}
-
-MpReturnCode mp_ImageFrame__ByteDepth(mediapipe::ImageFrame* image_frame, int* value_out) {
- TRY_ALL
- *value_out = image_frame->ByteDepth();
- RETURN_CODE(MpReturnCode::Success);
- CATCH_ALL
-}
-
int mp_ImageFrame__WidthStep(mediapipe::ImageFrame* image_frame) { return image_frame->WidthStep(); }
uint8* mp_ImageFrame__MutablePixelData(mediapipe::ImageFrame* image_frame) { return image_frame->MutablePixelData(); }
-int mp_ImageFrame__PixelDataSize(mediapipe::ImageFrame* image_frame) { return image_frame->PixelDataSize(); }
-
-MpReturnCode mp_ImageFrame__PixelDataSizeStoredContiguously(mediapipe::ImageFrame* image_frame, int* value_out) {
- TRY_ALL
- *value_out = image_frame->PixelDataSizeStoredContiguously();
- RETURN_CODE(MpReturnCode::Success);
- CATCH_ALL
-}
-
MpReturnCode mp_ImageFrame__CopyToBuffer__Pui8_i(mediapipe::ImageFrame* image_frame, uint8* buffer, int buffer_size) {
TRY_ALL
image_frame->CopyToBuffer(buffer, buffer_size);
diff --git a/mediapipe_api/framework/formats/image_frame.h b/mediapipe_api/framework/formats/image_frame.h
index 4d0b1ed62..26fa62520 100644
--- a/mediapipe_api/framework/formats/image_frame.h
+++ b/mediapipe_api/framework/formats/image_frame.h
@@ -35,13 +35,8 @@ MP_CAPI(MpReturnCode) mp_ImageFrame__IsAligned__ui(mediapipe::ImageFrame* image_
MP_CAPI(mediapipe::ImageFormat::Format) mp_ImageFrame__Format(mediapipe::ImageFrame* image_frame);
MP_CAPI(int) mp_ImageFrame__Width(mediapipe::ImageFrame* image_frame);
MP_CAPI(int) mp_ImageFrame__Height(mediapipe::ImageFrame* image_frame);
-MP_CAPI(MpReturnCode) mp_ImageFrame__ChannelSize(mediapipe::ImageFrame* image_frame, int* value_out);
-MP_CAPI(MpReturnCode) mp_ImageFrame__NumberOfChannels(mediapipe::ImageFrame* image_frame, int* value_out);
-MP_CAPI(MpReturnCode) mp_ImageFrame__ByteDepth(mediapipe::ImageFrame* image_frame, int* value_out);
MP_CAPI(int) mp_ImageFrame__WidthStep(mediapipe::ImageFrame* image_frame);
MP_CAPI(uint8*) mp_ImageFrame__MutablePixelData(mediapipe::ImageFrame* image_frame);
-MP_CAPI(int) mp_ImageFrame__PixelDataSize(mediapipe::ImageFrame* image_frame);
-MP_CAPI(MpReturnCode) mp_ImageFrame__PixelDataSizeStoredContiguously(mediapipe::ImageFrame* image_frame, int* value_out);
MP_CAPI(MpReturnCode) mp_ImageFrame__CopyToBuffer__Pui8_i(mediapipe::ImageFrame* image_frame, uint8* buffer, int buffer_size);
MP_CAPI(MpReturnCode) mp_ImageFrame__CopyToBuffer__Pui16_i(mediapipe::ImageFrame* image_frame, uint16* buffer, int buffer_size);
MP_CAPI(MpReturnCode) mp_ImageFrame__CopyToBuffer__Pf_i(mediapipe::ImageFrame* image_frame, float* buffer, int buffer_size);