using System; using System.IO; using JetBrains.Annotations; using Robust.Shared.IoC; using Robust.Shared.Maths; using Robust.Shared.Utility; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using YamlDotNet.RepresentationModel; using Color = Robust.Shared.Maths.Color; namespace Robust.Client.Graphics { /// /// Contains a texture used for drawing things. /// [PublicAPI] public abstract class Texture : IRsiStateLike { /// /// The width of the texture, in pixels. /// public int Width => Size.X; /// /// The height of the texture, in pixels. /// public int Height => Size.Y; /// /// The size of the texture, in pixels. /// public Vector2i Size { get; /*protected set;*/ } public Color this[int x, int y] => this.GetPixel(x, y); protected Texture(Vector2i size) { Size = size; } public static Texture Transparent => IoCManager.Resolve().GetStockTexture(ClydeStockTexture.Transparent); public static Texture White => IoCManager.Resolve().GetStockTexture(ClydeStockTexture.White); public static Texture Black => IoCManager.Resolve().GetStockTexture(ClydeStockTexture.Black); /// /// Loads a new texture an existing image. /// /// The image to load. /// The "name" of this texture. This can be referred to later to aid debugging. /// /// Parameters that influence the loading of textures. /// Defaults to if null. /// /// The type of pixels of the image. At the moment, images must be . public static Texture LoadFromImage(Image image, string? name = null, TextureLoadParameters? loadParameters = null) where T : unmanaged, IPixel { var manager = IoCManager.Resolve(); return manager.LoadTextureFromImage(image, name, loadParameters); } /// /// Loads an image from a stream containing PNG data. /// /// The stream to load the image from. /// The "name" of this texture. This can be referred to later to aid debugging. /// /// Parameters that influence the loading of textures. /// Defaults to if null. /// public static Texture LoadFromPNGStream(Stream stream, string? name = null, TextureLoadParameters? loadParameters = null) { var manager = IoCManager.Resolve(); return manager.LoadTextureFromPNGStream(stream, name, loadParameters); } Texture IDirectionalTextureProvider.Default => this; Texture IDirectionalTextureProvider.TextureFor(Direction dir) { return this; } RSI.State.DirectionType IRsiStateLike.Directions => RSI.State.DirectionType.Dir1; bool IRsiStateLike.IsAnimated => false; int IRsiStateLike.AnimationFrameCount => 0; float IRsiStateLike.GetDelay(int frame) { if (frame != 0) throw new IndexOutOfRangeException(); return 0; } Texture IRsiStateLike.GetFrame(RSI.State.Direction dir, int frame) { if (frame != 0) throw new IndexOutOfRangeException(); return this; } public abstract Color GetPixel(int x, int y); } /// /// Flags for loading of textures. /// [PublicAPI] public struct TextureLoadParameters { /// /// The default sampling parameters for the texture. /// public TextureSampleParameters SampleParameters { get; set; } /// /// If true, the image data will be treated as sRGB. /// public bool Srgb { get; set; } public static TextureLoadParameters FromYaml(YamlMappingNode yaml) { var loadParams = Default; if (yaml.TryGetNode("sample", out YamlMappingNode? sampleNode)) { loadParams.SampleParameters = TextureSampleParameters.FromYaml(sampleNode); } if (yaml.TryGetNode("srgb", out var srgb)) { loadParams.Srgb = srgb.AsBool(); } return loadParams; } public static readonly TextureLoadParameters Default = new() { SampleParameters = TextureSampleParameters.Default, Srgb = true }; } /// /// Sample flags for textures. /// These are separate from , /// because it is possible to create "proxies" to existing textures /// with different sampling parameters than the base texture. /// [PublicAPI] public struct TextureSampleParameters { // NOTE: If somebody is gonna add support for 3D/1D textures, change this doc comment. // See the note on this page for why: https://www.khronos.org/opengl/wiki/Sampler_Object#Filtering /// /// If true, use bi-linear texture filtering if the texture cannot be rendered 1:1 /// public bool Filter { get; set; } /// /// Controls how to wrap the texture if texture coordinates outside 0-1 are accessed. /// public TextureWrapMode WrapMode { get; set; } public static TextureSampleParameters FromYaml(YamlMappingNode node) { var wrap = TextureWrapMode.None; var filter = false; if (node.TryGetNode("filter", out var filterNode)) { filter = filterNode.AsBool(); } if (node.TryGetNode("wrap", out var wrapNode)) { switch (wrapNode.AsString()) { case "none": wrap = TextureWrapMode.None; break; case "repeat": wrap = TextureWrapMode.Repeat; break; case "mirrored_repeat": wrap = TextureWrapMode.MirroredRepeat; break; default: throw new ArgumentException("Not a valid wrap mode."); } } return new TextureSampleParameters {Filter = filter, WrapMode = wrap}; } public static readonly TextureSampleParameters Default = new() { Filter = false, WrapMode = TextureWrapMode.None }; } /// /// Controls behavior when reading texture coordinates outside 0-1, which usually wraps the texture somehow. /// [PublicAPI] public enum TextureWrapMode : byte { /// /// Do not wrap, instead clamp to edge. /// None = 0, /// /// Repeat the texture. /// Repeat, /// /// Repeat the texture mirrored. /// MirroredRepeat, } }