/**************************************************************************** * 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; } } }