Did a few things
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 40babb20770d5ac45a84d207281b725d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5febb852ffdece4ca30c97b63d617de
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c56568a2c9bad0245b35d93ba6b0e046
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c9737edefe34777499da210b11503b2a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,9 @@
|
||||
namespace Unity.Screenshots
|
||||
{
|
||||
public enum ScreenshotType
|
||||
{
|
||||
Raw = 0,
|
||||
|
||||
Png = 1
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 244a33b73709b0e4da95c36348a0bfc6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user