/****************************************************************************
* Copyright 2019 Nreal Techonology Limited. All rights reserved.
*
* This file is part of NRSDK.
*
* https://www.nreal.ai/
*
*****************************************************************************/
namespace NRKernal.Record
{
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
/// An image encoder.
public class ImageEncoder : IEncoder
{
/// The current frame.
RenderTexture m_CurrentFrame;
/// The requests.
Queue m_Requests;
/// The tasks.
Queue m_Tasks;
/// Options for controlling the camera.
CameraParameters m_CameraParameters;
/// A temp texture for capture.
Texture2D m_EncodeTempTex = null;
#region Interface
/// Commits.
/// The right.
/// The timestamp.
public void Commit(RenderTexture rt, ulong timestamp)
{
m_CurrentFrame = rt;
}
/// Configurations the given parameter.
/// The parameter.
public void Config(CameraParameters param)
{
this.m_CameraParameters = param;
m_Requests = new Queue();
m_Tasks = new Queue();
}
/// Starts this object.
public void Start()
{
NRKernalUpdater.OnUpdate -= Update;
NRKernalUpdater.OnUpdate += Update;
}
/// Stops this object.
public void Stop()
{
NRKernalUpdater.OnUpdate -= Update;
}
/// Releases this object.
public void Release()
{
if (m_EncodeTempTex != null)
{
GameObject.Destroy(m_EncodeTempTex);
m_EncodeTempTex = null;
}
}
#endregion
/// Commits the given task.
/// The task.
public void Commit(CaptureTask task)
{
if (m_CurrentFrame != null)
{
m_Requests.Enqueue(AsyncGPUReadback.Request(m_CurrentFrame));
m_Tasks.Enqueue(task);
}
else
{
NRDebugger.Warning("[ImageEncoder] Lost frame data.");
}
}
/// Updates this object.
private void Update()
{
while (m_Requests.Count > 0)
{
var req = m_Requests.Peek();
var task = m_Tasks.Peek();
if (req.hasError)
{
NRDebugger.Info("GPU readback error detected");
m_Requests.Dequeue();
CommitResult(null, task);
m_Tasks.Dequeue();
}
else if (req.done)
{
var buffer = req.GetData();
if (m_EncodeTempTex != null &&
m_EncodeTempTex.width != m_CameraParameters.cameraResolutionWidth &&
m_EncodeTempTex.height != m_CameraParameters.cameraResolutionHeight)
{
GameObject.Destroy(m_EncodeTempTex);
m_EncodeTempTex = null;
}
if (m_EncodeTempTex == null)
{
m_EncodeTempTex = new Texture2D(
m_CameraParameters.cameraResolutionWidth,
m_CameraParameters.cameraResolutionHeight,
TextureFormat.RGB24,
false
);
}
m_EncodeTempTex.SetPixels32(buffer.ToArray());
m_EncodeTempTex.Apply();
if (task.OnReceive != null)
{
if (m_EncodeTempTex.width != task.Width || m_EncodeTempTex.height != task.Height)
{
Texture2D scaledtexture;
NRDebugger.Info("[BlendCamera] need to scale the texture which origin width:{0} and out put width:{1}",
m_EncodeTempTex.width, task.Width);
scaledtexture = ImageEncoder.ScaleTexture(m_EncodeTempTex, task.Width, task.Height);
CommitResult(scaledtexture, task);
//Destroy the scale temp texture.
GameObject.Destroy(scaledtexture);
}
else
{
CommitResult(m_EncodeTempTex, task);
}
}
m_Requests.Dequeue();
m_Tasks.Dequeue();
}
else
{
break;
}
}
}
/// Commits a result.
/// The texture.
/// The task.
private void CommitResult(Texture2D texture, CaptureTask task)
{
if (task.OnReceive == null)
{
return;
}
if (texture == null)
{
task.OnReceive(task, null);
return;
}
byte[] result = null;
switch (task.CaptureFormat)
{
case PhotoCaptureFileOutputFormat.JPG:
result = texture.EncodeToJPG();
break;
case PhotoCaptureFileOutputFormat.PNG:
result = texture.EncodeToPNG();
break;
default:
break;
}
task.OnReceive(task, result);
}
/// Encodes.
/// The width.
/// The height.
/// Describes the format to use.
/// A byte[].
public byte[] Encode(int width, int height, PhotoCaptureFileOutputFormat format)
{
if (m_CurrentFrame == null)
{
NRDebugger.Warning("Current frame is empty!");
return null;
}
byte[] data = null;
RenderTexture pre = RenderTexture.active;
RenderTexture targetRT = m_CurrentFrame;
RenderTexture.active = targetRT;
Texture2D texture2D = new Texture2D(targetRT.width, targetRT.height, TextureFormat.ARGB32, false);
texture2D.ReadPixels(new Rect(0, 0, targetRT.width, targetRT.height), 0, 0);
texture2D.Apply();
RenderTexture.active = pre;
Texture2D outPutTex = texture2D;
Texture2D scaleTexture = null;
// Scale the texture while the output width or height not equal to the targetRT.
if (width != targetRT.width || height != targetRT.height)
{
scaleTexture = ImageEncoder.ScaleTexture(texture2D, width, height);
outPutTex = scaleTexture;
}
switch (format)
{
case PhotoCaptureFileOutputFormat.JPG:
data = outPutTex.EncodeToJPG();
break;
case PhotoCaptureFileOutputFormat.PNG:
data = outPutTex.EncodeToPNG();
break;
default:
break;
}
// Clear the temp texture.
GameObject.Destroy(texture2D);
if (scaleTexture != null)
{
GameObject.Destroy(scaleTexture);
}
return data;
}
/// Scale texture.
/// Source for the.
/// Width of the target.
/// Height of the target.
/// A Texture2D.
public static Texture2D ScaleTexture(Texture2D source, int targetWidth, int targetHeight)
{
NRDebugger.Info("[ImageEncoder] ScaleTexture..");
Texture2D result = new Texture2D(targetWidth, targetHeight, source.format, false);
for (int i = 0; i < result.height; ++i)
{
for (int j = 0; j < result.width; ++j)
{
Color newColor = source.GetPixelBilinear((float)j / (float)result.width, (float)i / (float)result.height);
result.SetPixel(j, i, newColor);
}
}
result.Apply();
return result;
}
}
}