mirror of
https://github.com/AssetRipper/AssetRipper.TextureDecoder.git
synced 2026-11-13 03:57:41 -10:00
OpenEXR writing
This commit is contained in:
1 parent
1957784de4
commit
8e82e702a7
21 files changed
+645
-12
No files matched your search
@@ -1,3 +1,4 @@
|
||||
using AssetRipper.TextureDecoder.Exr;
|
||||
using AssetRipper.TextureDecoder.Rgb;
|
||||
using AssetRipper.TextureDecoder.Rgb.Channels;
|
||||
using AssetRipper.TextureDecoder.Rgb.Formats;
|
||||
@@ -55,6 +56,12 @@ public readonly struct DirectBitmap<TColor, TColorArg>
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveAsExr(string path)
|
||||
{
|
||||
using Stream stream = File.OpenWrite(path);
|
||||
ExrWriter.Write<TColor, TColorArg>(stream, Width, Height, Pixels);
|
||||
}
|
||||
|
||||
public void SaveAsHdr(string path)
|
||||
{
|
||||
GetDataAndComponentsForSaving(out byte[] data, out ColorComponents components);
|
||||
|
||||
@@ -63,18 +63,32 @@ internal static partial class Program
|
||||
|
||||
switch (outputType)
|
||||
{
|
||||
case "png":
|
||||
{
|
||||
string newPath = Path.Combine(dirPath, name + ".png");
|
||||
bitmap.FlipY();
|
||||
bitmap.SaveAsPng(newPath);
|
||||
}
|
||||
break;
|
||||
case "bgra":
|
||||
{
|
||||
string newPath = Path.Combine(dirPath, name + ".bgra");
|
||||
WriteAllBytes(newPath, bitmap.Bits);
|
||||
}
|
||||
WriteAllBytes(Path.Combine(dirPath, name + ".bgra"), bitmap.Bits);
|
||||
break;
|
||||
case "bmp":
|
||||
bitmap.FlipY();
|
||||
bitmap.SaveAsBmp(Path.Combine(dirPath, name + ".bmp"));
|
||||
break;
|
||||
case "exr":
|
||||
bitmap.FlipY();
|
||||
bitmap.SaveAsExr(Path.Combine(dirPath, name + ".exr"));
|
||||
break;
|
||||
case "hdr":
|
||||
bitmap.FlipY();
|
||||
bitmap.SaveAsHdr(Path.Combine(dirPath, name + ".hdr"));
|
||||
break;
|
||||
case "jpg":
|
||||
bitmap.FlipY();
|
||||
bitmap.SaveAsJpg(Path.Combine(dirPath, name + ".jpg"));
|
||||
break;
|
||||
case "png":
|
||||
bitmap.FlipY();
|
||||
bitmap.SaveAsPng(Path.Combine(dirPath, name + ".png"));
|
||||
break;
|
||||
case "tga":
|
||||
bitmap.FlipY();
|
||||
bitmap.SaveAsTga(Path.Combine(dirPath, name + ".tga"));
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException(outputType);
|
||||
@@ -239,4 +253,4 @@ internal static partial class Program
|
||||
using Microsoft.Win32.SafeHandles.SafeFileHandle sfh = File.OpenHandle(path, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
RandomAccess.Write(sfh, bytes, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
using System.Buffers;
|
||||
using System.Text;
|
||||
|
||||
namespace AssetRipper.TextureDecoder.Exr;
|
||||
|
||||
internal static class BinaryWriterExtensions
|
||||
{
|
||||
public static void WriteLengthPrefixedString(this BinaryWriter writer, string value)
|
||||
{
|
||||
int length = Encoding.UTF8.GetByteCount(value);
|
||||
writer.Write(length);
|
||||
|
||||
byte[]? rentedArray;
|
||||
scoped Span<byte> buffer;
|
||||
if (length <= 1024)
|
||||
{
|
||||
rentedArray = null;
|
||||
buffer = stackalloc byte[length];
|
||||
}
|
||||
else
|
||||
{
|
||||
rentedArray = ArrayPool<byte>.Shared.Rent(length);
|
||||
buffer = rentedArray.AsSpan(0, length);
|
||||
}
|
||||
|
||||
Encoding.UTF8.GetBytes(value, buffer);
|
||||
writer.Write(buffer);
|
||||
|
||||
if (rentedArray is not null)
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(rentedArray);
|
||||
}
|
||||
}
|
||||
|
||||
public static void WriteNullTerminatedString(this BinaryWriter writer, string value)
|
||||
{
|
||||
int length = Encoding.UTF8.GetByteCount(value);
|
||||
|
||||
byte[]? rentedArray;
|
||||
scoped Span<byte> buffer;
|
||||
if (length <= 1024)
|
||||
{
|
||||
rentedArray = null;
|
||||
buffer = stackalloc byte[length];
|
||||
}
|
||||
else
|
||||
{
|
||||
rentedArray = ArrayPool<byte>.Shared.Rent(length);
|
||||
buffer = rentedArray.AsSpan(0, length);
|
||||
}
|
||||
|
||||
Encoding.UTF8.GetBytes(value, buffer);
|
||||
writer.Write(buffer);
|
||||
|
||||
if (rentedArray is not null)
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(rentedArray);
|
||||
}
|
||||
|
||||
writer.WriteNullByte();
|
||||
}
|
||||
|
||||
public static void WriteNullTerminatedString(this BinaryWriter writer, ReadOnlySpan<byte> utf8String)
|
||||
{
|
||||
writer.Write(utf8String);
|
||||
writer.WriteNullByte();
|
||||
}
|
||||
|
||||
public static void WriteNullByte(this BinaryWriter writer)
|
||||
{
|
||||
writer.Write(default(byte));
|
||||
}
|
||||
|
||||
public static void Write<T>(this BinaryWriter writer, T value) where T : unmanaged, Enum
|
||||
{
|
||||
switch (Unsafe.SizeOf<T>())
|
||||
{
|
||||
case sizeof(byte):
|
||||
writer.Write(Unsafe.As<T, byte>(ref value));
|
||||
break;
|
||||
case sizeof(ushort):
|
||||
writer.Write(Unsafe.As<T, ushort>(ref value));
|
||||
break;
|
||||
case sizeof(uint):
|
||||
writer.Write(Unsafe.As<T, uint>(ref value));
|
||||
break;
|
||||
case sizeof(ulong):
|
||||
writer.Write(Unsafe.As<T, ulong>(ref value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void WriteAttribute<T>(this BinaryWriter writer, ReadOnlySpan<byte> attributeName, T attributeValue) where T : IExrDataType
|
||||
{
|
||||
writer.Write(attributeName);
|
||||
writer.WriteNullByte();
|
||||
writer.Write(T.TypeName);
|
||||
writer.WriteNullByte();
|
||||
writer.Write(attributeValue.Size);
|
||||
attributeValue.Write(writer);
|
||||
}
|
||||
|
||||
public static void WriteAttribute<T>(this BinaryWriter writer, ReadOnlySpan<byte> attributeName, ReadOnlySpan<byte> attributeType, T attributeValue) where T : unmanaged, Enum
|
||||
{
|
||||
writer.Write(attributeName);
|
||||
writer.WriteNullByte();
|
||||
writer.Write(attributeType);
|
||||
writer.WriteNullByte();
|
||||
writer.Write(Unsafe.SizeOf<T>());
|
||||
writer.Write(attributeValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace AssetRipper.TextureDecoder.Exr;
|
||||
|
||||
public static class ExrAttributes
|
||||
{
|
||||
public static ReadOnlySpan<byte> Channels => "channels"u8;
|
||||
|
||||
public static ReadOnlySpan<byte> Compression => "compression"u8;
|
||||
|
||||
public static ReadOnlySpan<byte> DataWindow => "dataWindow"u8;
|
||||
|
||||
public static ReadOnlySpan<byte> DisplayWindow => "displayWindow"u8;
|
||||
|
||||
public static ReadOnlySpan<byte> LineOrder => "lineOrder"u8;
|
||||
|
||||
public static ReadOnlySpan<byte> PixelAspectRatio => "pixelAspectRatio"u8;
|
||||
|
||||
public static ReadOnlySpan<byte> ScreenWindowCenter => "screenWindowCenter"u8;
|
||||
|
||||
public static ReadOnlySpan<byte> ScreenWindowWidth => "screenWindowWidth"u8;
|
||||
|
||||
public static ReadOnlySpan<byte> Tiles => "tiles"u8;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace AssetRipper.TextureDecoder.Exr;
|
||||
|
||||
public readonly record struct ExrBox2F(float XMin, float YMin, float XMax, float YMax) : IExrDataType
|
||||
{
|
||||
public static ReadOnlySpan<byte> TypeName => "box2f"u8;
|
||||
|
||||
public int Size => sizeof(float) + sizeof(float) + sizeof(float) + sizeof(float);
|
||||
|
||||
public void Write(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(XMin);
|
||||
writer.Write(YMin);
|
||||
writer.Write(XMax);
|
||||
writer.Write(YMax);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace AssetRipper.TextureDecoder.Exr;
|
||||
|
||||
public readonly record struct ExrBox2I(int XMin, int YMin, int XMax, int YMax) : IExrDataType
|
||||
{
|
||||
public static ReadOnlySpan<byte> TypeName => "box2i"u8;
|
||||
|
||||
public int Size => sizeof(int) + sizeof(int) + sizeof(int) + sizeof(int);
|
||||
|
||||
public void Write(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(XMin);
|
||||
writer.Write(YMin);
|
||||
writer.Write(XMax);
|
||||
writer.Write(YMax);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.Text;
|
||||
|
||||
namespace AssetRipper.TextureDecoder.Exr;
|
||||
|
||||
public readonly record struct ExrChannel(string Name, ExrPixelType PixelType, bool PLinear, int XSampling, int YSampling)
|
||||
{
|
||||
public int Size
|
||||
{
|
||||
get
|
||||
{
|
||||
return Encoding.UTF8.GetByteCount(Name)
|
||||
+ sizeof(byte)//Null terminator
|
||||
+ sizeof(ExrPixelType)
|
||||
+ sizeof(bool)
|
||||
+ 3 * sizeof(byte)//Reserved bytes
|
||||
+ sizeof(int)
|
||||
+ sizeof(int);
|
||||
}
|
||||
}
|
||||
|
||||
public void Write(BinaryWriter writer)
|
||||
{
|
||||
writer.WriteNullTerminatedString(Name);
|
||||
writer.Write(PixelType);
|
||||
writer.Write(PLinear);
|
||||
writer.WriteNullByte();
|
||||
writer.WriteNullByte();
|
||||
writer.WriteNullByte();
|
||||
writer.Write(XSampling);
|
||||
writer.Write(YSampling);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace AssetRipper.TextureDecoder.Exr;
|
||||
|
||||
public readonly record struct ExrChannelList(ExrChannel[] Channels) : IExrDataType
|
||||
{
|
||||
public static ReadOnlySpan<byte> TypeName => "chlist"u8;
|
||||
|
||||
public int Size => Channels.Sum(c => c.Size);
|
||||
|
||||
public void Write(BinaryWriter writer)
|
||||
{
|
||||
foreach (ExrChannel channel in Channels)
|
||||
{
|
||||
channel.Write(writer);
|
||||
}
|
||||
writer.WriteNullByte();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace AssetRipper.TextureDecoder.Exr;
|
||||
|
||||
public enum ExrCompression : byte
|
||||
{
|
||||
None = 0,
|
||||
RLE = 1,
|
||||
ZIPS = 2,
|
||||
ZIP = 3,
|
||||
PIZ = 4,
|
||||
PXR24 = 5,
|
||||
B44 = 6,
|
||||
B44A = 7,
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace AssetRipper.TextureDecoder.Exr;
|
||||
|
||||
public readonly record struct ExrDouble(double Value) : IExrDataType
|
||||
{
|
||||
public static ReadOnlySpan<byte> TypeName => "double"u8;
|
||||
|
||||
public int Size => sizeof(double);
|
||||
|
||||
public void Write(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(Value);
|
||||
}
|
||||
|
||||
public static implicit operator double(ExrDouble value) => value.Value;
|
||||
|
||||
public static implicit operator ExrDouble(double value) => new(value);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace AssetRipper.TextureDecoder.Exr;
|
||||
public enum ExrImageFormat
|
||||
{
|
||||
ScanLines,
|
||||
Tiles,
|
||||
}
|
||||
public static class ExrImageFormatExtensions
|
||||
{
|
||||
public static byte ToVersionByte(this ExrImageFormat format) => format switch
|
||||
{
|
||||
ExrImageFormat.Tiles => 0x02,
|
||||
_ => 0x00,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace AssetRipper.TextureDecoder.Exr;
|
||||
|
||||
public readonly record struct ExrInt32(int Value) : IExrDataType
|
||||
{
|
||||
public static ReadOnlySpan<byte> TypeName => "int"u8;
|
||||
|
||||
public int Size => sizeof(int);
|
||||
|
||||
public void Write(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(Value);
|
||||
}
|
||||
|
||||
public static implicit operator int(ExrInt32 value) => value.Value;
|
||||
|
||||
public static implicit operator ExrInt32(int value) => new(value);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace AssetRipper.TextureDecoder.Exr;
|
||||
|
||||
[Flags]
|
||||
public enum ExrLevelMode : byte
|
||||
{
|
||||
OneLevel = 0,
|
||||
MipMapLevels = 1,
|
||||
RipMapLevels = 2,
|
||||
RoundDown = 0,
|
||||
RoundUp = 16,
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace AssetRipper.TextureDecoder.Exr;
|
||||
|
||||
public enum ExrLineOrder : byte
|
||||
{
|
||||
IncreasingY = 0,
|
||||
DecreasingY = 1,
|
||||
RandomY = 2,
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace AssetRipper.TextureDecoder.Exr;
|
||||
|
||||
public enum ExrPixelType : int
|
||||
{
|
||||
UInt = 0,
|
||||
Half = 1,
|
||||
Single = 2,
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace AssetRipper.TextureDecoder.Exr;
|
||||
|
||||
public readonly record struct ExrSingle(float Value) : IExrDataType
|
||||
{
|
||||
public static ReadOnlySpan<byte> TypeName => "float"u8;
|
||||
|
||||
public int Size => sizeof(float);
|
||||
|
||||
public void Write(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(Value);
|
||||
}
|
||||
|
||||
public static implicit operator float(ExrSingle value) => value.Value;
|
||||
|
||||
public static implicit operator ExrSingle(float value) => new(value);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.Text;
|
||||
|
||||
namespace AssetRipper.TextureDecoder.Exr;
|
||||
|
||||
public readonly record struct ExrString(string Value) : IExrDataType
|
||||
{
|
||||
public static ReadOnlySpan<byte> TypeName => "string"u8;
|
||||
|
||||
public int Size => sizeof(int) + Encoding.UTF8.GetByteCount(Value);
|
||||
|
||||
public void Write(BinaryWriter writer) => writer.WriteLengthPrefixedString(Value);
|
||||
|
||||
public static implicit operator string(ExrString value) => value.Value;
|
||||
|
||||
public static implicit operator ExrString(string value) => new(value);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
namespace AssetRipper.TextureDecoder.Exr;
|
||||
|
||||
public readonly record struct ExrVector2(Vector2 Value) : IExrDataType
|
||||
{
|
||||
public ExrVector2(float x, float y) : this(new Vector2(x, y))
|
||||
{
|
||||
}
|
||||
|
||||
public int Size => sizeof(float) + sizeof(float);
|
||||
|
||||
public float X => Value.X;
|
||||
|
||||
public float Y => Value.Y;
|
||||
|
||||
public static ReadOnlySpan<byte> TypeName => "v2f"u8;
|
||||
|
||||
public void Write(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(X);
|
||||
writer.Write(Y);
|
||||
}
|
||||
|
||||
public static implicit operator Vector2(ExrVector2 value) => value.Value;
|
||||
|
||||
public static implicit operator ExrVector2(Vector2 value) => new(value);
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
using AssetRipper.TextureDecoder.Rgb;
|
||||
|
||||
namespace AssetRipper.TextureDecoder.Exr;
|
||||
|
||||
public static class ExrWriter
|
||||
{
|
||||
public static ReadOnlySpan<byte> Magic => [0x76, 0x2f, 0x31, 0x01];
|
||||
|
||||
public static void Write<TColor, TChannelValue>(Stream stream, int width, int height, ReadOnlySpan<TColor> pixels)
|
||||
where TChannelValue : unmanaged
|
||||
where TColor : unmanaged, IColor<TChannelValue>
|
||||
{
|
||||
int ChannelCount = Color.GetChannelCount<TColor>();
|
||||
ExrPixelType PixelType = Unsafe.SizeOf<TChannelValue>() <= sizeof(ushort) ? ExrPixelType.Half : ExrPixelType.Single;
|
||||
int ChannelSize = PixelType is ExrPixelType.Half ? Unsafe.SizeOf<Half>() : sizeof(float);
|
||||
|
||||
using BinaryWriter writer = new BinaryWriter(stream);
|
||||
|
||||
//Magic bytes
|
||||
writer.Write(Magic);
|
||||
|
||||
//Version bytes
|
||||
writer.Write((byte)2);//OpenEXR version 2
|
||||
writer.Write(ExrImageFormat.ScanLines.ToVersionByte());
|
||||
writer.WriteNullByte();
|
||||
writer.WriteNullByte();
|
||||
|
||||
ExrChannel[] channels;
|
||||
{
|
||||
Span<ExrChannel> buffer = [default, default, default, default];
|
||||
//Stackalloc doesn't work for managed types.
|
||||
//This can be reworked when ref structs can be used in generics.
|
||||
|
||||
int i = 0;
|
||||
|
||||
//The order doesn't really matter here because the format sorts channels alphabetically
|
||||
//when reading reading scan lines, but this makes that more clear.
|
||||
if (TColor.HasAlphaChannel)
|
||||
{
|
||||
buffer[i] = new ExrChannel("A", PixelType, false, 1, 1);
|
||||
i++;
|
||||
}
|
||||
if (TColor.HasBlueChannel)
|
||||
{
|
||||
buffer[i] = new ExrChannel("B", PixelType, false, 1, 1);
|
||||
i++;
|
||||
}
|
||||
if (TColor.HasGreenChannel)
|
||||
{
|
||||
buffer[i] = new ExrChannel("G", PixelType, false, 1, 1);
|
||||
i++;
|
||||
}
|
||||
if (TColor.HasRedChannel)
|
||||
{
|
||||
buffer[i] = new ExrChannel("R", PixelType, false, 1, 1);
|
||||
i++;
|
||||
}
|
||||
|
||||
channels = buffer[..i].ToArray();
|
||||
}
|
||||
|
||||
//Attributes
|
||||
writer.WriteAttribute(ExrAttributes.Channels, new ExrChannelList(channels));
|
||||
writer.WriteAttribute(ExrAttributes.Compression, "compression"u8, ExrCompression.None);
|
||||
writer.WriteAttribute(ExrAttributes.DataWindow, new ExrBox2I(0, 0, width - 1, height - 1));
|
||||
writer.WriteAttribute(ExrAttributes.DisplayWindow, new ExrBox2I(0, 0, width - 1, height - 1));
|
||||
writer.WriteAttribute(ExrAttributes.LineOrder, "lineOrder"u8, ExrLineOrder.IncreasingY);
|
||||
writer.WriteAttribute(ExrAttributes.PixelAspectRatio, new ExrSingle(1f));
|
||||
writer.WriteAttribute(ExrAttributes.ScreenWindowCenter, new ExrVector2(0f, 0f));
|
||||
writer.WriteAttribute(ExrAttributes.ScreenWindowWidth, new ExrSingle(1f));
|
||||
writer.WriteNullByte();//End of header
|
||||
|
||||
//Scan line offset table
|
||||
long offset = writer.BaseStream.Position + height * sizeof(ulong);
|
||||
for (int i = 0; i < height; i++)
|
||||
{
|
||||
writer.Write(offset);
|
||||
offset += sizeof(int) + ChannelCount * ChannelSize * width;
|
||||
}
|
||||
|
||||
//Scan lines
|
||||
if (PixelType is ExrPixelType.Half)
|
||||
{
|
||||
for (int i = 0; i < height; i++)
|
||||
{
|
||||
writer.Write(i);
|
||||
writer.Write(ChannelCount * ChannelSize * width);
|
||||
if (TColor.HasAlphaChannel)
|
||||
{
|
||||
for (int j = 0; j < width; j++)
|
||||
{
|
||||
Half alpha = NumericConversion.Convert<TChannelValue, Half>(pixels[i * width + j].A);
|
||||
writer.Write(alpha);
|
||||
}
|
||||
}
|
||||
if (TColor.HasBlueChannel)
|
||||
{
|
||||
for (int j = 0; j < width; j++)
|
||||
{
|
||||
float blue = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].B);
|
||||
if (TColor.HasAlphaChannel)
|
||||
{
|
||||
float alpha = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].A);
|
||||
writer.Write((Half)ConvertSRGB(blue * alpha));
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.Write((Half)ConvertSRGB(blue));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (TColor.HasGreenChannel)
|
||||
{
|
||||
for (int j = 0; j < width; j++)
|
||||
{
|
||||
float green = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].G);
|
||||
if (TColor.HasAlphaChannel)
|
||||
{
|
||||
float alpha = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].A);
|
||||
writer.Write((Half)ConvertSRGB(green * alpha));
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.Write((Half)ConvertSRGB(green));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (TColor.HasRedChannel)
|
||||
{
|
||||
for (int j = 0; j < width; j++)
|
||||
{
|
||||
float red = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].R);
|
||||
if (TColor.HasAlphaChannel)
|
||||
{
|
||||
float alpha = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].A);
|
||||
writer.Write((Half)ConvertSRGB(red * alpha));
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.Write((Half)ConvertSRGB(red));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < height; i++)
|
||||
{
|
||||
writer.Write(i);
|
||||
writer.Write(ChannelCount * ChannelSize * width);
|
||||
if (TColor.HasAlphaChannel)
|
||||
{
|
||||
for (int j = 0; j < width; j++)
|
||||
{
|
||||
float alpha = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].A);
|
||||
writer.Write(alpha);
|
||||
}
|
||||
}
|
||||
if (TColor.HasBlueChannel)
|
||||
{
|
||||
for (int j = 0; j < width; j++)
|
||||
{
|
||||
float blue = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].B);
|
||||
if (TColor.HasAlphaChannel)
|
||||
{
|
||||
float alpha = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].A);
|
||||
writer.Write(ConvertSRGB(blue * alpha));
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.Write(ConvertSRGB(blue));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (TColor.HasGreenChannel)
|
||||
{
|
||||
for (int j = 0; j < width; j++)
|
||||
{
|
||||
float green = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].G);
|
||||
if (TColor.HasAlphaChannel)
|
||||
{
|
||||
float alpha = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].A);
|
||||
writer.Write(ConvertSRGB(green * alpha));
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.Write(ConvertSRGB(green));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (TColor.HasRedChannel)
|
||||
{
|
||||
for (int j = 0; j < width; j++)
|
||||
{
|
||||
float red = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].R);
|
||||
if (TColor.HasAlphaChannel)
|
||||
{
|
||||
float alpha = NumericConversion.Convert<TChannelValue, float>(pixels[i * width + j].A);
|
||||
writer.Write(ConvertSRGB(red * alpha));
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.Write(ConvertSRGB(red));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static float ConvertSRGB(float value)
|
||||
{
|
||||
//https://en.wikipedia.org/wiki/SRGB#From_sRGB_to_CIE_XYZ
|
||||
//https://en.wikipedia.org/wiki/Gamma_correction#Computer_displays
|
||||
if (value < 0.04045)
|
||||
{
|
||||
return value / 12.92f;
|
||||
}
|
||||
else
|
||||
{
|
||||
return float.Pow((value + 0.055f) / 1.055f, 2.4f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace AssetRipper.TextureDecoder.Exr;
|
||||
|
||||
public interface IExrDataType
|
||||
{
|
||||
static abstract ReadOnlySpan<byte> TypeName { get; }
|
||||
|
||||
int Size { get; }
|
||||
|
||||
void Write(BinaryWriter writer);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace AssetRipper.TextureDecoder.Exr;
|
||||
|
||||
public readonly record struct TileDescription(int XSize, int YSize, ExrLevelMode Mode) : IExrDataType
|
||||
{
|
||||
public static ReadOnlySpan<byte> TypeName => "tileDesc"u8;
|
||||
|
||||
public int Size => sizeof(int) + sizeof(int) + sizeof(ExrLevelMode);
|
||||
|
||||
public void Write(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(XSize);
|
||||
writer.Write(YSize);
|
||||
writer.Write(Mode);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user