mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-23 13:50:13 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
263 lines
7.4 KiB
C#
263 lines
7.4 KiB
C#
using Sandbox.UI;
|
|
using System.Text.Json.Serialization;
|
|
using System.Threading;
|
|
|
|
namespace Sandbox.Resources;
|
|
|
|
/// <summary>
|
|
/// Load images from disk and convert them to textures
|
|
/// </summary>
|
|
[Order( -100 )]
|
|
[Title( "Image File" )]
|
|
[Icon( "image" )]
|
|
[ClassName( "imagefile" )]
|
|
public class ImageFileGenerator : TextureGenerator
|
|
{
|
|
/// <summary>
|
|
/// The path to the image file, relative to any other assets in the project.
|
|
/// </summary>
|
|
[Header( "Image" )]
|
|
[KeyProperty, TextureImagePath]
|
|
public string FilePath { get; set; }
|
|
|
|
/// <summary>
|
|
/// The maximum size of the image in pixels. If the imported image is larger than this (after cropping), it will be downscaled to fit.
|
|
/// </summary>
|
|
public int MaxSize { get; set; } = 4096;
|
|
|
|
/// <summary>
|
|
/// When enabled, the output texture will be a normal map generated from the heightmap of the image.
|
|
/// </summary>
|
|
[Header( "Normal Map" ), Title( "Height to Normal" )]
|
|
public bool ConvertHeightToNormals { get; set; }
|
|
|
|
/// <summary>
|
|
/// The scale of the normal map when using <see cref="ConvertHeightToNormals"/>. If negative, the normal map will be inverted.
|
|
/// </summary>
|
|
[ShowIf( "ConvertHeightToNormals", true )]
|
|
public float NormalScale { get; set; } = 1;
|
|
|
|
/// <summary>
|
|
/// How much to rotate the image by, in degrees. This is applied after cropping and padding.
|
|
/// </summary>
|
|
[Header( "Adjust" ), Range( 0, 360 )]
|
|
public float Rotate { get; set; } = 0.0f;
|
|
|
|
/// <summary>
|
|
/// Whether or not to flip the image vertically. This is done after everything else has been applied.
|
|
/// </summary>
|
|
public bool FlipVertical { get; set; } = false;
|
|
|
|
/// <summary>
|
|
/// Whether or not to flip the image horizontally. This is done after everything else has been applied.
|
|
/// </summary>
|
|
public bool FlipHorizontal { get; set; } = false;
|
|
|
|
/// <summary>
|
|
/// How many pixels from each edge to crop from the image. If negative values are used, the image will be expanded instead of cropped.
|
|
/// </summary>
|
|
public Margin Cropping { get; set; }
|
|
|
|
/// <summary>
|
|
/// How many pixels of padding from each edge. After the image has been cropped,
|
|
/// padding is added without affecting the size of the image (scaling the original image down to fit padded margins).
|
|
/// </summary>
|
|
public Margin Padding { get; set; }
|
|
|
|
/// <summary>
|
|
/// Whether or not to invert the colors of the image.
|
|
/// </summary>
|
|
[Header( "Effects" )]
|
|
public bool InvertColor { get; set; } = false;
|
|
|
|
/// <summary>
|
|
/// The color the image should be tinted. This effectively multiplies the color of each pixel by this color (including alpha).
|
|
/// </summary>
|
|
public Color Tint { get; set; } = Color.White;
|
|
|
|
/// <summary>
|
|
/// The intensity of the blur effect. If 0, no blur is applied.
|
|
/// </summary>
|
|
[Range( 0, 32 )]
|
|
public float Blur { get; set; } = 0.0f;
|
|
|
|
/// <summary>
|
|
/// The intensity of the sharpen effect. If 0, no sharpening is applied.
|
|
/// </summary>
|
|
[Range( 0, 10 )]
|
|
public float Sharpen { get; set; } = 0.0f;
|
|
|
|
/// <summary>
|
|
/// The brightness of the image.
|
|
/// </summary>
|
|
[Range( 0, 2 )]
|
|
public float Brightness { get; set; } = 1.0f;
|
|
|
|
/// <summary>
|
|
/// The contrast of the image.
|
|
/// </summary>
|
|
[Range( 0, 2 )]
|
|
public float Contrast { get; set; } = 1.0f;
|
|
|
|
/// <summary>
|
|
/// The saturation of the image.
|
|
/// </summary>
|
|
[Range( 0, 2 )]
|
|
public float Saturation { get; set; } = 1.0f;
|
|
|
|
/// <summary>
|
|
/// How much to adjust the hue of the image, in degrees. If 0, no hue adjustment is applied.
|
|
/// </summary>
|
|
[Range( 0, 360 )]
|
|
public float Hue { get; set; } = 0.0f;
|
|
|
|
/// <summary>
|
|
/// When enabled, every pixel in the image will be re-colored to the <see cref="TargetColor"/> (interpolated by the alpha).
|
|
/// </summary>
|
|
[ToggleGroup( "Colorize" )]
|
|
public bool Colorize { get; set; }
|
|
|
|
/// <summary>
|
|
/// When <see cref="Colorize"/> is enabled, this is the target color that every pixel in the image will be re-colored to.
|
|
/// </summary>
|
|
[Group( "Colorize" )]
|
|
public Color TargetColor { get; set; } = Color.White;
|
|
|
|
[Hide]
|
|
public override bool CacheToDisk => true;
|
|
|
|
// Don't continually load from disk, cache that shit
|
|
[JsonIgnore, Hide]
|
|
int _loadedCacheHash;
|
|
|
|
[JsonIgnore, Hide]
|
|
Bitmap _cacheLoaded;
|
|
|
|
[JsonIgnore, Hide]
|
|
int loadCount = 0;
|
|
|
|
async Task<Bitmap> LoadCached()
|
|
{
|
|
if ( string.IsNullOrWhiteSpace( FilePath ) ) return default;
|
|
var path = FilePath.NormalizeFilename();
|
|
|
|
if ( string.IsNullOrEmpty( FilePath ) ) return default;
|
|
if ( !EngineFileSystem.Mounted.FileExists( path ) )
|
|
{
|
|
Log.Warning( $"ImageFileGenerator could not find file: {path}" );
|
|
return default;
|
|
}
|
|
|
|
var size = EngineFileSystem.Mounted.FileSize( path );
|
|
|
|
var hash = HashCode.Combine( size, FilePath, Cropping );
|
|
if ( hash == _loadedCacheHash )
|
|
return _cacheLoaded?.Clone();
|
|
|
|
var bytes = await EngineFileSystem.Mounted.ReadAllBytesAsync( path );
|
|
|
|
// Create the bitmap and crop it if needed
|
|
var bitmap = Bitmap.CreateFromBytes( bytes );
|
|
var newRect = new Rect( Cropping.Left, Cropping.Top, bitmap.Width - Cropping.Right - Cropping.Left, bitmap.Height - Cropping.Bottom - Cropping.Top );
|
|
if ( newRect.Width > 0 && newRect.Height > 0 )
|
|
{
|
|
bitmap = bitmap.Crop( newRect.SnapToGrid() );
|
|
}
|
|
|
|
if ( loadCount > 3 )
|
|
{
|
|
_loadedCacheHash = hash;
|
|
_cacheLoaded = bitmap?.Clone();
|
|
}
|
|
|
|
loadCount++;
|
|
|
|
return bitmap;
|
|
}
|
|
|
|
protected override async ValueTask<Texture> CreateTexture( Options options, CancellationToken ct )
|
|
{
|
|
if ( string.IsNullOrWhiteSpace( FilePath ) )
|
|
return null;
|
|
|
|
//
|
|
// A regular texture!
|
|
//
|
|
|
|
// Trim compiled names!
|
|
if ( FilePath.EndsWith( "_c", StringComparison.OrdinalIgnoreCase ) )
|
|
FilePath = FilePath[..^2];
|
|
|
|
if ( FilePath.EndsWith( ".vtex", StringComparison.OrdinalIgnoreCase ) )
|
|
{
|
|
await MainThread.Wait();
|
|
return Texture.Load( FilePath );
|
|
}
|
|
|
|
var bitmap = await LoadCached();
|
|
if ( bitmap is null ) return Texture.Invalid;
|
|
|
|
//
|
|
// Tell the compiler we're using this file, so to add it as a compile reference
|
|
//
|
|
if ( options.Compiler is not null )
|
|
{
|
|
options.Compiler.Context.AddCompileReference( FilePath );
|
|
}
|
|
|
|
if ( !Padding.IsNearlyZero() )
|
|
{
|
|
bitmap.InsertPadding( Padding );
|
|
}
|
|
|
|
if ( Rotate != 0 ) bitmap = bitmap.Rotate( Rotate );
|
|
|
|
if ( bitmap.Width > MaxSize || bitmap.Height > MaxSize )
|
|
{
|
|
float scale = Math.Min( (float)MaxSize / bitmap.Width, (float)MaxSize / bitmap.Height );
|
|
int newWidth = (int)(bitmap.Width * scale);
|
|
int newHeight = (int)(bitmap.Height * scale);
|
|
|
|
newWidth = newWidth.Clamp( 1, 1024 * 8 );
|
|
newHeight = newHeight.Clamp( 1, 1024 * 8 );
|
|
|
|
bitmap = bitmap.Resize( newWidth, newHeight );
|
|
}
|
|
|
|
if ( Tint != Color.White || Tint.a != 1 )
|
|
{
|
|
bitmap.Tint( Tint );
|
|
}
|
|
if ( Sharpen > 0.0f ) bitmap.Sharpen( Sharpen, true );
|
|
if ( Blur > 0.0f ) bitmap.Blur( Blur, true );
|
|
if ( Brightness != 1 || Contrast != 1 || Saturation != 1 || Hue != 0 ) bitmap.Adjust( Brightness, Contrast, Saturation, Hue );
|
|
if ( InvertColor ) bitmap.InvertColor();
|
|
|
|
if ( Colorize )
|
|
{
|
|
bitmap.Colorize( TargetColor );
|
|
}
|
|
|
|
// if ( Posterize != 0 ) bitmap.Posterize( Posterize );
|
|
|
|
if ( FlipHorizontal ) bitmap = bitmap.FlipHorizontal();
|
|
if ( FlipVertical ) bitmap = bitmap.FlipVertical();
|
|
|
|
if ( ConvertHeightToNormals )
|
|
{
|
|
bitmap = bitmap.HeightmapToNormalMap( NormalScale );
|
|
}
|
|
|
|
return bitmap.ToTexture();
|
|
}
|
|
|
|
public override EmbeddedResource? CreateEmbeddedResource()
|
|
{
|
|
// if we're a vtex, don't create an embedded resource
|
|
if ( FilePath.EndsWith( ".vtex", StringComparison.OrdinalIgnoreCase ) )
|
|
return null;
|
|
|
|
return base.CreateEmbeddedResource();
|
|
}
|
|
}
|