Did a few things

This commit is contained in:
2021-07-23 09:45:12 +02:00
parent 3da75e4cc6
commit db537bd7b7
122 changed files with 15101 additions and 619 deletions

View File

@@ -0,0 +1,125 @@
using System;
namespace Unity.Screenshots
{
public static class Downsampler
{
#region Static Methods
public static byte[] Downsample(byte[] dataRgba, int stride, int maximumWidth, int maximumHeight, out int downsampledStride)
{
// Preconditions
if (stride == 0)
{
throw new ArgumentException("The stride must be greater than 0.");
}
if (stride % 4 != 0)
{
throw new ArgumentException("The stride must be evenly divisible by 4.");
}
if (dataRgba == null)
{
throw new ArgumentNullException("dataRgba");
}
if (dataRgba.Length == 0)
{
throw new ArgumentException("The data length must be greater than 0.");
}
if (dataRgba.Length % 4 != 0)
{
throw new ArgumentException("The data must be evenly divisible by 4.");
}
if (dataRgba.Length % stride != 0)
{
throw new ArgumentException("The data must be evenly divisible by the stride.");
}
// Implementation
int width = stride / 4;
int height = dataRgba.Length / stride;
float ratioX = maximumWidth / (float) width;
float ratioY = maximumHeight / (float) height;
float ratio = Math.Min(ratioX, ratioY);
if (ratio < 1)
{
int downsampledWidth = (int) Math.Round(width * ratio);
int downsampledHeight = (int) Math.Round(height * ratio);
float[] downsampledData = new float[downsampledWidth * downsampledHeight * 4];
float sampleWidth = width / (float) downsampledWidth;
float sampleHeight = height / (float) downsampledHeight;
int kernelWidth = (int) Math.Floor(sampleWidth);
int kernelHeight = (int) Math.Floor(sampleHeight);
int kernelSize = kernelWidth * kernelHeight;
for (int y = 0; y < downsampledHeight; y++)
{
for (int x = 0; x < downsampledWidth; x++)
{
int destinationIndex = y * downsampledWidth * 4 + x * 4;
int sampleLowerX = (int) Math.Floor(x * sampleWidth);
int sampleLowerY = (int) Math.Floor(y * sampleHeight);
int sampleUpperX = sampleLowerX + kernelWidth;
int sampleUpperY = sampleLowerY + kernelHeight;
for (int sampleY = sampleLowerY; sampleY < sampleUpperY; sampleY++)
{
if (sampleY >= height)
{
continue;
}
for (int sampleX = sampleLowerX; sampleX < sampleUpperX; sampleX++)
{
if (sampleX >= width)
{
continue;
}
int sourceIndex = sampleY * width * 4 + sampleX * 4;
downsampledData[destinationIndex] += dataRgba[sourceIndex];
downsampledData[destinationIndex + 1] += dataRgba[sourceIndex + 1];
downsampledData[destinationIndex + 2] += dataRgba[sourceIndex + 2];
downsampledData[destinationIndex + 3] += dataRgba[sourceIndex + 3];
}
}
downsampledData[destinationIndex] /= kernelSize;
downsampledData[destinationIndex + 1] /= kernelSize;
downsampledData[destinationIndex + 2] /= kernelSize;
downsampledData[destinationIndex + 3] /= kernelSize;
}
}
byte[] flippedData = new byte[downsampledWidth * downsampledHeight * 4];
for (int y = 0; y < downsampledHeight; y++)
{
for (int x = 0; x < downsampledWidth; x++)
{
int sourceIndex = (downsampledHeight - 1 - y) * downsampledWidth * 4 + x * 4;
int destinationIndex = y * downsampledWidth * 4 + x * 4;
flippedData[destinationIndex] += (byte) downsampledData[sourceIndex];
flippedData[destinationIndex + 1] += (byte) downsampledData[sourceIndex + 1];
flippedData[destinationIndex + 2] += (byte) downsampledData[sourceIndex + 2];
flippedData[destinationIndex + 3] += (byte) downsampledData[sourceIndex + 3];
}
}
downsampledStride = downsampledWidth * 4;
return flippedData;
}
else
{
byte[] flippedData = new byte[dataRgba.Length];
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int sourceIndex = (height - 1 - y) * width * 4 + x * 4;
int destinationIndex = y * width * 4 + x * 4;
flippedData[destinationIndex] += (byte) dataRgba[sourceIndex];
flippedData[destinationIndex + 1] += (byte) dataRgba[sourceIndex + 1];
flippedData[destinationIndex + 2] += (byte) dataRgba[sourceIndex + 2];
flippedData[destinationIndex + 3] += (byte) dataRgba[sourceIndex + 3];
}
}
downsampledStride = width * 4;
return flippedData;
}
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 40babb20770d5ac45a84d207281b725d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,303 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading;
namespace Unity.Screenshots
{
/// <summary>
/// Provides PNG encoding.
/// </summary>
/// <remarks>This is a minimal implementation of a PNG encoder with no scanline filtering or additional features.</remarks>
public static class PngEncoder
{
#region Nested Types
public class Crc32
{
#region Static Fields
private static UInt32 generator = 0xEDB88320;
#endregion
#region Constructors
public Crc32()
{
this.checksumTable = Enumerable.Range(0, 256)
.Select(i =>
{
uint tableEntry = (uint)i;
for (int j = 0; j < 8; j++)
{
tableEntry = (tableEntry & 1) != 0 ? Crc32.generator ^ (tableEntry >> 1) : tableEntry >> 1;
}
return tableEntry;
})
.ToArray();
}
#endregion
#region Fields
private readonly UInt32[] checksumTable;
#endregion
#region Methods
public UInt32 Calculate<T>(IEnumerable<T> byteStream)
{
return ~byteStream.Aggregate(0xFFFFFFFF, (checksumRegister, currentByte) => this.checksumTable[(checksumRegister & 0xFF) ^ Convert.ToByte(currentByte)] ^ (checksumRegister >> 8));
}
#endregion
}
#endregion
#region Static Constructors
static PngEncoder()
{
PngEncoder.crc32 = new Crc32();
}
#endregion
#region Static Fields
private static Crc32 crc32;
#endregion
#region Static Methods
private static uint Adler32(byte[] bytes)
{
const int mod = 65521;
uint a = 1, b = 0;
foreach (byte byteValue in bytes)
{
a = (a + byteValue) % mod;
b = (b + a) % mod;
}
return (b << 16) | a;
}
private static void AppendByte(this byte[] data, ref int position, byte value)
{
data[position] = value;
position++;
}
private static void AppendBytes(this byte[] data, ref int position, byte[] value)
{
foreach (byte valueByte in value)
{
data.AppendByte(ref position, valueByte);
}
}
private static void AppendChunk(this byte[] data, ref int position, string chunkType, byte[] chunkData)
{
byte[] chunkTypeBytes = PngEncoder.GetChunkTypeBytes(chunkType);
if (chunkTypeBytes != null)
{
data.AppendInt(ref position, chunkData.Length);
data.AppendBytes(ref position, chunkTypeBytes);
data.AppendBytes(ref position, chunkData);
data.AppendUInt(ref position, PngEncoder.GetChunkCrc(chunkTypeBytes, chunkData));
}
}
private static void AppendInt(this byte[] data, ref int position, int value)
{
byte[] valueBytes = BitConverter.GetBytes(value);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(valueBytes);
}
data.AppendBytes(ref position, valueBytes);
}
private static void AppendUInt(this byte[] data, ref int position, uint value)
{
byte[] valueBytes = BitConverter.GetBytes(value);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(valueBytes);
}
data.AppendBytes(ref position, valueBytes);
}
private static byte[] Compress(byte[] bytes)
{
using (MemoryStream outStream = new MemoryStream())
{
using (DeflateStream gZipStream = new DeflateStream(outStream, CompressionMode.Compress))
using (MemoryStream mStream = new MemoryStream(bytes))
{
mStream.WriteTo(gZipStream);
}
byte[] compressedBytes = outStream.ToArray();
return compressedBytes;
}
}
public static byte[] Encode(byte[] dataRgba, int stride)
{
// Preconditions
if (dataRgba == null)
{
throw new ArgumentNullException("dataRgba");
}
if (dataRgba.Length == 0)
{
throw new ArgumentException("The data length must be greater than 0.");
}
if (stride == 0)
{
throw new ArgumentException("The stride must be greater than 0.");
}
if (stride % 4 != 0)
{
throw new ArgumentException("The stride must be evenly divisible by 4.");
}
if (dataRgba.Length % 4 != 0)
{
throw new ArgumentException("The data must be evenly divisible by 4.");
}
if (dataRgba.Length % stride != 0)
{
throw new ArgumentException("The data must be evenly divisible by the stride.");
}
// Dimensions
int pixels = dataRgba.Length / 4;
int width = stride / 4;
int height = pixels / width;
// IHDR Chunk
byte[] ihdrData = new byte[13];
int ihdrPosition = 0;
ihdrData.AppendInt(ref ihdrPosition, width);
ihdrData.AppendInt(ref ihdrPosition, height);
ihdrData.AppendByte(ref ihdrPosition, 8); // Bit depth
ihdrData.AppendByte(ref ihdrPosition, 6); // Color type (color + alpha)
ihdrData.AppendByte(ref ihdrPosition, 0); // Compression method (always 0)
ihdrData.AppendByte(ref ihdrPosition, 0); // Filter method (always 0)
ihdrData.AppendByte(ref ihdrPosition, 0); // Interlace method (no interlacing)
// IDAT Chunk
byte[] scanlineData = new byte[dataRgba.Length + height];
int scanlineDataPosition = 0;
int scanlinePosition = 0;
for (int i = 0; i < dataRgba.Length; i++)
{
if (scanlinePosition >= stride)
{
scanlinePosition = 0;
}
if (scanlinePosition == 0)
{
scanlineData.AppendByte(ref scanlineDataPosition, 0);
}
scanlineData.AppendByte(ref scanlineDataPosition, dataRgba[i]);
scanlinePosition++;
}
byte[] compressedScanlineData = PngEncoder.Compress(scanlineData);
byte[] idatData = new byte[1 + 1 + compressedScanlineData.Length + 4];
int idatPosition = 0;
idatData.AppendByte(ref idatPosition, 0x78); // Zlib header
idatData.AppendByte(ref idatPosition, 0x9C); // Zlib header
idatData.AppendBytes(ref idatPosition, compressedScanlineData); // Data
idatData.AppendUInt(ref idatPosition, PngEncoder.Adler32(scanlineData)); // Adler32 checksum
// Png
byte[] png = new byte[8 + ihdrData.Length + 12 + idatData.Length + 12 + 12];
// Position
int position = 0;
// Signature
png.AppendByte(ref position, 0x89); // High bit set
png.AppendByte(ref position, 0x50); // P
png.AppendByte(ref position, 0x4E); // N
png.AppendByte(ref position, 0x47); // G
png.AppendByte(ref position, 0x0D); // DOS line ending
png.AppendByte(ref position, 0x0A); // DOS line ending
png.AppendByte(ref position, 0x1A); // DOS end of file
png.AppendByte(ref position, 0x0A); // Unix line ending
// Assemble
png.AppendChunk(ref position, "IHDR", ihdrData);
png.AppendChunk(ref position, "IDAT", idatData);
png.AppendChunk(ref position, "IEND", new byte[0]);
// Return
return png;
}
public static void EncodeAsync(byte[] dataRgba, int stride, Action<Exception, byte[]> callback)
{
ThreadPool.QueueUserWorkItem((state) =>
{
try
{
byte[] png = PngEncoder.Encode(dataRgba, stride);
callback(null, png);
}
catch (Exception ex)
{
callback(ex, null);
throw;
}
}, null);
}
private static uint GetChunkCrc(byte[] chunkTypeBytes, byte[] chunkData)
{
byte[] combinedBytes = new byte[chunkTypeBytes.Length + chunkData.Length];
Array.Copy(chunkTypeBytes, 0, combinedBytes, 0, chunkTypeBytes.Length);
Array.Copy(chunkData, 0, combinedBytes, 4, chunkData.Length);
return PngEncoder.crc32.Calculate(combinedBytes);
}
private static byte[] GetChunkTypeBytes(string value)
{
char[] characters = value.ToCharArray();
if (characters.Length < 4)
{
return null;
}
byte[] type = new byte[4];
for (int i = 0; i < type.Length; i++)
{
type[i] = (byte)characters[i];
}
return type;
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b5febb852ffdece4ca30c97b63d617de
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,165 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Unity.Screenshots
{
public class ScreenshotManager
{
#region Nested Types
private class ScreenshotOperation
{
#region Properties
public Action<int, byte[]> Callback { get; set; }
public byte[] Data { get; set; }
public int FrameNumber { get; set; }
public bool IsAwaiting { get; set; }
public bool IsComplete { get; set; }
public bool IsInUse { get; set; }
public int MaximumHeight { get; set; }
public int MaximumWidth { get; set; }
public object Source { get; set; }
#endregion
#region Methods
public void Use()
{
this.Callback = null;
this.Data = null;
this.FrameNumber = 0;
this.IsAwaiting = true;
this.IsComplete = false;
this.IsInUse = true;
this.MaximumHeight = 0;
this.MaximumWidth = 0;
this.Source = null;
}
#endregion
}
#endregion
#region Constructors
public ScreenshotManager()
{
this.screenshotRecorder = new ScreenshotRecorder();
this.screenshotCallbackDelegate = this.ScreenshotCallback;
this.screenshotOperations = new List<ScreenshotOperation>();
}
#endregion
#region Fields
private Action<byte[], object> screenshotCallbackDelegate;
private List<ScreenshotOperation> screenshotOperations;
private ScreenshotRecorder screenshotRecorder;
#endregion
#region Methods
private ScreenshotOperation GetScreenshotOperation()
{
foreach (ScreenshotOperation screenshotOperation in this.screenshotOperations)
{
if (!screenshotOperation.IsInUse)
{
screenshotOperation.Use();
return screenshotOperation;
}
}
ScreenshotOperation newScreenshotOperation = new ScreenshotOperation();
newScreenshotOperation.Use();
this.screenshotOperations.Add(newScreenshotOperation);
return newScreenshotOperation;
}
public void OnEndOfFrame()
{
foreach (ScreenshotOperation screenshotOperation in this.screenshotOperations)
{
if (screenshotOperation.IsInUse)
{
if (screenshotOperation.IsAwaiting)
{
screenshotOperation.IsAwaiting = false;
if (screenshotOperation.Source == null)
{
this.screenshotRecorder.Screenshot(screenshotOperation.MaximumWidth, screenshotOperation.MaximumHeight, ScreenshotType.Png, this.screenshotCallbackDelegate, screenshotOperation);
}
else if (screenshotOperation.Source is Camera)
{
this.screenshotRecorder.Screenshot(screenshotOperation.Source as Camera, screenshotOperation.MaximumWidth, screenshotOperation.MaximumHeight, ScreenshotType.Png, this.screenshotCallbackDelegate, screenshotOperation);
}
else if (screenshotOperation.Source is RenderTexture)
{
this.screenshotRecorder.Screenshot(screenshotOperation.Source as RenderTexture, screenshotOperation.MaximumWidth, screenshotOperation.MaximumHeight, ScreenshotType.Png, this.screenshotCallbackDelegate, screenshotOperation);
}
else if (screenshotOperation.Source is Texture2D)
{
this.screenshotRecorder.Screenshot(screenshotOperation.Source as Texture2D, screenshotOperation.MaximumWidth, screenshotOperation.MaximumHeight, ScreenshotType.Png, this.screenshotCallbackDelegate, screenshotOperation);
}
else
{
this.ScreenshotCallback(null, screenshotOperation);
}
}
else if (screenshotOperation.IsComplete)
{
screenshotOperation.IsInUse = false;
try
{
if (screenshotOperation != null && screenshotOperation.Callback != null)
{
screenshotOperation.Callback(screenshotOperation.FrameNumber, screenshotOperation.Data);
}
}
catch
{
// Do Nothing
}
}
}
}
}
private void ScreenshotCallback(byte[] data, object state)
{
ScreenshotOperation screenshotOperation = state as ScreenshotOperation;
if (screenshotOperation != null)
{
screenshotOperation.Data = data;
screenshotOperation.IsComplete = true;
}
}
public void TakeScreenshot(object source, int frameNumber, int maximumWidth, int maximumHeight, Action<int, byte[]> callback)
{
ScreenshotOperation screenshotOperation = this.GetScreenshotOperation();
screenshotOperation.FrameNumber = frameNumber;
screenshotOperation.MaximumWidth = maximumWidth;
screenshotOperation.MaximumHeight = maximumHeight;
screenshotOperation.Source = source;
screenshotOperation.Callback = callback;
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c56568a2c9bad0245b35d93ba6b0e046
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,199 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Rendering;
namespace Unity.Screenshots
{
public class ScreenshotRecorder
{
#region Nested Types
private class ScreenshotOperation
{
#region Constructors
public ScreenshotOperation()
{
this.ScreenshotCallbackDelegate = this.ScreenshotCallback;
this.EncodeCallbackDelegate = this.EncodeCallback;
}
#endregion
#region Fields
public WaitCallback EncodeCallbackDelegate;
public Action<AsyncGPUReadbackRequest> ScreenshotCallbackDelegate;
#endregion
#region Properties
public Action<byte[], object> Callback { get; set; }
public int Height { get; set; }
public int Identifier { get; set; }
public bool IsInUse { get; set; }
public int MaximumHeight { get; set; }
public int MaximumWidth { get; set; }
public NativeArray<byte> NativeData { get; set; }
public Texture Source { get; set; }
public object State { get; set; }
public ScreenshotType Type { get; set; }
public int Width { get; set; }
#endregion
#region Methods
private void EncodeCallback(object state)
{
byte[] byteData = this.NativeData.ToArray();
int downsampledStride;
byteData = Downsampler.Downsample(byteData, this.Width * 4, this.MaximumWidth, this.MaximumHeight, out downsampledStride);
if (this.Type == ScreenshotType.Png)
{
byteData = PngEncoder.Encode(byteData, downsampledStride);
}
if (this.Callback != null)
{
this.Callback(byteData, this.State);
}
this.NativeData.Dispose();
this.IsInUse = false;
}
private void SavePngToDisk(byte[] byteData)
{
if (!Directory.Exists("Screenshots"))
{
Directory.CreateDirectory("Screenshots");
}
File.WriteAllBytes(string.Format("Screenshots/{0}.png", this.Identifier % 60), byteData);
}
private void ScreenshotCallback(AsyncGPUReadbackRequest request)
{
if (!request.hasError)
{
NativeLeakDetection.Mode = NativeLeakDetectionMode.Disabled;
NativeArray<byte> data = request.GetData<byte>();
NativeArray<byte> persistentData = new NativeArray<byte>(data, Allocator.Persistent);
this.Width = request.width;
this.Height = request.height;
this.NativeData = persistentData;
ThreadPool.QueueUserWorkItem(this.EncodeCallbackDelegate, null);
}
else
{
if (this.Callback != null)
{
this.Callback(null, this.State);
}
}
if (this.Source != null)
{
UnityEngine.Object.Destroy(this.Source);
}
}
#endregion
}
#endregion
#region Static Fields
private static int nextIdentifier;
#endregion
#region Constructors
public ScreenshotRecorder()
{
this.operationPool = new List<ScreenshotOperation>();
}
#endregion
#region Fields
private List<ScreenshotOperation> operationPool;
#endregion
#region Methods
private ScreenshotOperation GetOperation()
{
foreach (ScreenshotOperation operation in this.operationPool)
{
if (!operation.IsInUse)
{
operation.IsInUse = true;
return operation;
}
}
ScreenshotOperation newOperation = new ScreenshotOperation();
newOperation.IsInUse = true;
this.operationPool.Add(newOperation);
return newOperation;
}
public void Screenshot(int maximumWidth, int maximumHeight, ScreenshotType type, Action<byte[], object> callback, object state)
{
Texture2D texture = ScreenCapture.CaptureScreenshotAsTexture();
this.Screenshot(texture, maximumWidth, maximumHeight, type, callback, state);
}
public void Screenshot(Camera source, int maximumWidth, int maximumHeight, ScreenshotType type, Action<byte[], object> callback, object state)
{
RenderTexture renderTexture = new RenderTexture(maximumWidth, maximumHeight, 24);
RenderTexture originalTargetTexture = source.targetTexture;
source.targetTexture = renderTexture;
source.Render();
source.targetTexture = originalTargetTexture;
this.Screenshot(renderTexture, maximumWidth, maximumHeight, type, callback, state);
}
public void Screenshot(RenderTexture source, int maximumWidth, int maximumHeight, ScreenshotType type, Action<byte[], object> callback, object state)
{
this.ScreenshotInternal(source, maximumWidth, maximumHeight, type, callback, state);
}
public void Screenshot(Texture2D source, int maximumWidth, int maximumHeight, ScreenshotType type, Action<byte[], object> callback, object state)
{
this.ScreenshotInternal(source, maximumWidth, maximumHeight, type, callback, state);
}
private void ScreenshotInternal(Texture source, int maximumWidth, int maximumHeight, ScreenshotType type, Action<byte[], object> callback, object state)
{
ScreenshotOperation operation = this.GetOperation();
operation.Identifier = ScreenshotRecorder.nextIdentifier++;
operation.Source = source;
operation.MaximumWidth = maximumWidth;
operation.MaximumHeight = maximumHeight;
operation.Type = type;
operation.Callback = callback;
operation.State = state;
AsyncGPUReadback.Request(source, 0, TextureFormat.RGBA32, operation.ScreenshotCallbackDelegate);
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c9737edefe34777499da210b11503b2a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,9 @@
namespace Unity.Screenshots
{
public enum ScreenshotType
{
Raw = 0,
Png = 1
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 244a33b73709b0e4da95c36348a0bfc6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: