Utilities/Decals.cs
using System;
using System.IO;
using System.Text;
using Clover.Ui;
using Sandbox.Diagnostics;
using Sandbox.Utility;
namespace Clover.Utilities;
public class Decals
{
public struct DecalData
{
public int Width;
public int Height;
public PaintUi.PaintType PaintType;
public string Name;
public ulong Author;
public string AuthorName;
public string Palette;
public byte[] Image;
public DateTime Created;
public DateTime Modified;
// not stored in file
public Texture Texture;
public string GetHash()
{
var data = Crc64.FromBytes( Image ).ToString();
return Crc64.FromString( $"{Name}:{Author}:{Palette}:{Modified}:{data}" ).ToString();
}
public DecalDataRpc ToRpc()
{
return new DecalDataRpc
{
Width = Width,
Height = Height,
PaintType = PaintType,
Name = Name,
Author = Author,
AuthorName = AuthorName,
Palette = Palette,
Image = Image
};
}
}
public struct DecalDataRpc
{
public int Width { get; set; }
public int Height { get; set; }
public PaintUi.PaintType PaintType { get; set; }
public string Name { get; set; }
public ulong Author { get; set; }
public string AuthorName { get; set; }
public string Palette { get; set; }
public byte[] Image { get; set; }
public bool Dummy()
{
return true;
}
public Texture GetTexture()
{
Assert.NotNull( Palette, "Palette is null" );
var palette = GetPalette( Palette );
var texture = Texture.Create( Width, Height ).Finish();
var allPixels = ByteArrayToColor32( Image, palette );
texture.Update( allPixels, 0, 0, Width, Height );
return texture;
}
public DecalData ToDecalData()
{
return new DecalData
{
Width = Width,
Height = Height,
PaintType = PaintType,
Name = Name,
Author = Author,
AuthorName = AuthorName,
Palette = Palette,
Image = Image,
Texture = GetTexture()
};
}
}
public static Texture GetDecalTexture( DecalDataRpc decal )
{
Assert.NotNull( decal.Palette, "Palette is null" );
var palette = GetPalette( decal.Palette );
var texture = Texture.Create( decal.Width, decal.Height ).Finish();
var allPixels = ByteArrayToColor32( decal.Image, palette );
texture.Update( allPixels, 0, 0, decal.Width, decal.Height );
return texture;
}
public static DecalData ToDecalData( DecalDataRpc decal )
{
return new DecalData
{
Width = decal.Width,
Height = decal.Height,
Name = decal.Name,
Author = decal.Author,
AuthorName = decal.AuthorName,
Palette = decal.Palette,
Image = decal.Image,
Texture = GetDecalTexture( decal )
};
}
public static List<string> GetPalettes()
{
var palettes = new List<string>();
var files = FileSystem.Mounted.FindFile( "materials/palettes", "*.png" );
foreach ( var file in files )
{
var name = Path.GetFileNameWithoutExtension( file );
palettes.Add( name );
}
return palettes;
}
public static Color32[] GetPalette( string name )
{
var paletteTexture = Texture.LoadFromFileSystem( $"materials/palettes/{name}.png", FileSystem.Mounted );
if ( !paletteTexture.IsValid() )
{
Log.Error( $"Failed to load palette {name}" );
return null;
}
var palette = paletteTexture.GetPixels();
// swap out last color for transparent
// TODO: do this in palettes
if ( palette.Length == 256 )
palette[255] = new Color32( 0, 0, 0, 0 );
return palette;
}
public static int GetClosestPaletteColor( Color32[] palette, Color32 texturePixel )
{
var minDistance = float.MaxValue;
var closestColor = -1;
for ( var i = 0; i < palette.Length; i++ )
{
var paletteColor = palette[i];
var distance =
new Vector3( texturePixel.r, texturePixel.g, texturePixel.b ).Distance( new Vector3( paletteColor.r,
paletteColor.g, paletteColor.b ) );
if ( distance < minDistance )
{
minDistance = distance;
closestColor = i;
}
}
return closestColor;
}
public static List<string> GetAllDecals()
{
FileSystem.Data.CreateDirectory( "decals" );
return FileSystem.Data.FindFile( "decals", "*.decal" ).Select( Path.GetFileNameWithoutExtension ).ToList();
}
public static void WriteDecal( Stream stream, DecalData decalData )
{
var writer = new BinaryWriter( stream, Encoding.UTF8 );
writer.Write( 'C' );
writer.Write( 'L' );
writer.Write( 'P' );
writer.Write( 'T' );
writer.Write( 6 ); // version
writer.Write( decalData.Width ); // width
writer.Write( decalData.Height ); // height
writer.Write( decalData.PaintType.AsInt() ); // paint type
writer.Write( decalData.Name ); // name, 16 chars
writer.Write( decalData.Author ); // author
writer.Write( decalData.AuthorName ); // author name
writer.Write( decalData.Palette ); // palette name
writer.Write( decalData.Created.ToBinary() );
writer.Write( decalData.Modified.ToBinary() );
// writer.Write( decalData.Image );
var cbytes = CompressBytes( decalData.Image );
writer.Write( cbytes.Length );
writer.Write( cbytes );
writer.Flush();
}
public static DecalData ReadDecal( string filePath )
{
Log.Info( "Loading decal" );
if ( !filePath.EndsWith( ".decal" ) )
{
throw new Exception( $"Invalid file extension: {filePath}" );
}
var stream = FileSystem.Data.OpenRead( filePath );
var reader = new BinaryReader( stream, Encoding.UTF8 );
var magic = new string( reader.ReadChars( 4 ) );
// Log.Info( $"Magic: {magic}" );
var version = reader.ReadUInt32();
// Log.Info( $"Version: {version}" );
if ( version < 6 )
{
stream.Close();
reader.Close();
throw new System.Exception( "Decal version is too old" );
}
var width = reader.ReadInt32();
var height = reader.ReadInt32();
var paintType = (PaintUi.PaintType)reader.ReadInt32();
var name = reader.ReadString();
// Log.Info( $"Name: {name}" );
var author = reader.ReadUInt64();
// Log.Info( $"Author: {author}" );
var authorName = reader.ReadString();
// Log.Info( $"Author Name: {authorName}" );
var paletteName = reader.ReadString();
// Log.Info( $"Palette: {paletteName}" );
var created = DateTime.FromBinary( reader.ReadInt64() );
var modified = DateTime.FromBinary( reader.ReadInt64() );
var cbyteLength = reader.ReadInt32();
var imageBytes = reader.ReadBytes( cbyteLength );
imageBytes = DecompressBytes( imageBytes );
// Log.Info( $"Image bytes: {imageBytes.Length}" );
var decalData = new DecalData
{
Width = width,
Height = height,
PaintType = paintType,
Name = name,
Author = author,
AuthorName = authorName,
Palette = paletteName,
Created = created,
Modified = modified,
Image = imageBytes
};
reader.Close();
stream.Close();
var palette = GetPalette( paletteName );
if ( palette == null )
{
Log.Error( "Failed to load palette" );
return default;
}
decalData.Texture = GetDecalTexture( decalData.ToRpc() );
return decalData;
}
public static Color32[] ByteArrayToColor32( byte[] byteArray, Color32[] palette )
{
Assert.NotNull( palette, "Palette is null" );
Assert.NotNull( byteArray, "Byte array is null" );
Assert.True( palette.Length > 0, "Palette is empty" );
Assert.True( byteArray.Length > 0, "Byte array is empty" );
var result = new Color32[byteArray.Length];
for ( var i = 0; i < byteArray.Length; i++ )
{
var color = palette.ElementAtOrDefault( byteArray[i] );
result[i] = color;
}
return result;
}
public static byte[] CompressBytes( byte[] bytes )
{
using var memoryStream = new MemoryStream();
using var gZipStream =
new System.IO.Compression.GZipStream( memoryStream, System.IO.Compression.CompressionMode.Compress );
gZipStream.Write( bytes, 0, bytes.Length );
gZipStream.Close();
return memoryStream.ToArray();
}
public static byte[] DecompressBytes( byte[] bytes )
{
using var memoryStream = new MemoryStream();
using var gZipStream =
new System.IO.Compression.GZipStream( new MemoryStream( bytes ),
System.IO.Compression.CompressionMode.Decompress );
gZipStream.CopyTo( memoryStream );
return memoryStream.ToArray();
}
}