Skip to main content
Version: 21 Aug 2024

YUVUtility.cs

Source code

namespace MagicLeap.Android
{
using System.Runtime.CompilerServices;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
using UnityEngine.XR.MagicLeap.Unsafe;

public static class YUVUtility
{
private const float kRedCoefficient = 1.370705f;
private const float kGreenCoefficientV = 0.698001f;
private const float kGreenCoefficientU = 0.337633f;
private const float kBlueCoefficient = 1.732446f;

private struct YuvToRgbJob : IJobFor
{
public NativePlane Y;
public NativePlane U;
public NativePlane V;

public ImageDimensions Dimensions;

public NativeArray<Color32> RgbData;

public void Execute(int index)
{
(int x, int y) = Dimensions.GetCoordinatesFromIndex(index);

int yIndex = CalculateYIndex(x, y, Y.PixelStride, Y.RowStride);
int uvIndex = CalculateUVIndex(x, y, U.PixelStride, U.RowStride);

byte rawY = Y.GetDataAtOffset<byte>(yIndex);
byte rawU = U.GetDataAtOffset<byte>(uvIndex);
byte rawV = V.GetDataAtOffset<byte>(uvIndex);

RgbData[index] = ProcessPixel(rawY, rawU, rawV);
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateYIndex(int x, int y, int pixelStride, int rowStride)
=> (y * rowStride) + (x * pixelStride);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateUVIndex(int x, int y, int pixelStride, int rowStride)
{
int uvx = x / 2;
int uvy = y / 2;

return (uvy * rowStride) + (uvx * pixelStride);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte ClampToByte(int value) => (byte)Mathf.Clamp(value, 0, 255);

public static JobHandle ConvertPlanesToRGBAsync(Allocator allocator, NativeYUVPlanes yuvPlanes, out NativeArray<Color32> outRgb)
{
outRgb = yuvPlanes.Dimensions.CreateNativeArray<Color32>(allocator);

var job = new YuvToRgbJob
{
Y = yuvPlanes.YPlane,
U = yuvPlanes.UPlane,
V = yuvPlanes.VPlane,
Dimensions = yuvPlanes.Dimensions,
RgbData = outRgb,
};

return job.ScheduleParallel(yuvPlanes.Dimensions.Size, yuvPlanes.Dimensions.Width, default);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Color32 ProcessPixel(byte rawY, byte rawU, byte rawV)
{
int y = rawY & 0xFF;
int u = (rawU & 0xFF) - 128;
int v = (rawV & 0xFF) - 128;

int r = (int)(y + kRedCoefficient * v);
int g = (int)(y - (kGreenCoefficientV * v) - (kGreenCoefficientU * u));
int b = (int)(y + kBlueCoefficient * u);

return new Color32
{
a = 255,
r = ClampToByte(r),
g = ClampToByte(g),
b = ClampToByte(b)
};
}
}
}