Editor/SeamlessSuiteImageUtility.cs
using System;
using System.Collections.Generic;
using System.IO;
using Editor;
using Sandbox;
public static class SeamlessSuiteImageUtility
{
private static readonly HashSet<string> SupportedExtensions = new( StringComparer.OrdinalIgnoreCase )
{
".png",
".jpg",
".jpeg",
".webp",
".bmp",
".tga",
".tif",
".tiff",
".psd",
".svg"
};
public static bool IsSupportedImageFile( string path )
{
if ( string.IsNullOrWhiteSpace( path ) )
return false;
return SupportedExtensions.Contains( Path.GetExtension( path ) );
}
public static string ResolveFilePath( string path )
{
if ( string.IsNullOrWhiteSpace( path ) )
return path;
if ( File.Exists( path ) )
return path;
var asset = AssetSystem.FindByPath( path );
if ( asset != null && File.Exists( asset.AbsolutePath ) )
return asset.AbsolutePath;
return path;
}
public static Bitmap LoadBitmapFromFile( string path )
{
var extension = Path.GetExtension( path ).ToLowerInvariant();
var bytes = File.ReadAllBytes( path );
return extension switch
{
".tga" => Bitmap.CreateFromTgaBytes( bytes ),
".tif" => Bitmap.CreateFromTifBytes( bytes ),
".tiff" => Bitmap.CreateFromTifBytes( bytes ),
".psd" => Bitmap.CreateFromPsdBytes( bytes ),
".svg" => Bitmap.CreateFromSvgString( File.ReadAllText( path ), null, null, null, null, null ),
_ => Bitmap.CreateFromBytes( bytes )
};
}
public static List<string> GetFilesFromDragData( DragData data )
{
var files = new List<string>();
if ( data?.Files != null )
{
foreach ( var file in data.Files )
{
files.Add( file );
}
}
if ( data?.Assets != null )
{
foreach ( var dragAsset in data.Assets )
{
if ( !string.IsNullOrWhiteSpace( dragAsset.AssetPath ) )
files.Add( dragAsset.AssetPath );
}
}
if ( data != null )
{
foreach ( var asset in data.OfType<Editor.Asset>() )
{
AddAssetPathCandidates( files, asset );
}
}
return files;
}
public static string FindFirstSupportedFile( IEnumerable<string> files )
{
foreach ( var file in files )
{
var resolved = ResolveFilePath( file );
if ( IsSupportedImageFile( resolved ) )
return resolved;
}
return null;
}
public static string BuildUniquePath( string outputDirectory, string sourceName, string suffix, string extension, bool overwriteExisting )
{
var cleanSourceName = CleanFileNamePart( string.IsNullOrWhiteSpace( sourceName ) ? "texture" : sourceName );
var cleanSuffix = CleanFileNamePart( suffix );
var basePath = Path.Combine( outputDirectory, $"{cleanSourceName}{cleanSuffix}{extension}" );
if ( overwriteExisting || !File.Exists( basePath ) )
return basePath;
for ( var i = 1; i < 1000; i++ )
{
var numberedPath = Path.Combine( outputDirectory, $"{cleanSourceName}{cleanSuffix}_{i:000}{extension}" );
if ( !File.Exists( numberedPath ) )
return numberedPath;
}
return basePath;
}
public static void RegisterFile( string path, Action<string> logMessage )
{
try
{
AssetSystem.RegisterFile( path );
}
catch ( Exception ex )
{
logMessage?.Invoke( $"Export wrote the file, but S&box asset registration skipped it: {ex.Message}" );
}
}
public static string GetTextureReferencePath( string texturePath, Action<string> addWarning )
{
var fullTexturePath = Path.GetFullPath( texturePath );
var normalizedTexturePath = NormalizePath( fullTexturePath );
var projectAssetsPath = Sandbox.Project.Current?.GetAssetsPath();
if ( !string.IsNullOrWhiteSpace( projectAssetsPath ) )
{
var normalizedAssetsPath = NormalizePath( Path.GetFullPath( projectAssetsPath ) );
if ( !normalizedAssetsPath.EndsWith( "/" ) )
normalizedAssetsPath += "/";
if ( normalizedTexturePath.StartsWith( normalizedAssetsPath, StringComparison.OrdinalIgnoreCase ) )
return normalizedTexturePath.Substring( normalizedAssetsPath.Length );
addWarning?.Invoke( "The exported texture stack is outside the active project's Assets folder. S&box may not treat it as normal project assets; choose a folder under Assets for best editor use." );
}
else
{
addWarning?.Invoke( "Could not find the active project's Assets folder. The .vmat will reference textures by full path." );
}
return normalizedTexturePath;
}
public static string CleanFileNamePart( string value )
{
if ( string.IsNullOrEmpty( value ) )
return "";
var invalid = Path.GetInvalidFileNameChars();
var cleaned = value;
foreach ( var character in invalid )
{
cleaned = cleaned.Replace( character, '_' );
}
return cleaned;
}
public static string NormalizePath( string path )
{
return path?.Replace( '\\', '/' ) ?? "";
}
public static float GetLuminance( Color color )
{
return Clamp01( color.r * 0.2126f + color.g * 0.7152f + color.b * 0.0722f );
}
public static float Clamp01( float value )
{
if ( value < 0f )
return 0f;
if ( value > 1f )
return 1f;
return value;
}
public static float Lerp( float a, float b, float amount )
{
return a + (b - a) * Clamp01( amount );
}
public static int WrapIndex( int value, int size )
{
if ( size <= 0 )
return 0;
var wrapped = value % size;
return wrapped < 0 ? wrapped + size : wrapped;
}
private static void AddAssetPathCandidates( List<string> files, Editor.Asset asset )
{
if ( asset == null || asset.IsDeleted )
return;
if ( !string.IsNullOrWhiteSpace( asset.AbsolutePath ) )
files.Add( asset.AbsolutePath );
if ( !string.IsNullOrWhiteSpace( asset.RelativePath ) )
files.Add( asset.RelativePath );
if ( !string.IsNullOrWhiteSpace( asset.Path ) )
files.Add( asset.Path );
}
}