From c14f74b108c043554bd4aa3de2b2c301c3556fc3 Mon Sep 17 00:00:00 2001 From: Jarrod Doyle Date: Mon, 12 Aug 2024 18:46:30 +0100 Subject: [PATCH] Add PCX image loading --- project/code/TMV/TextureLoader.Pcx.cs | 108 ++++++++++++++++++++++++++ project/code/TMV/TextureLoader.cs | 19 ++++- 2 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 project/code/TMV/TextureLoader.Pcx.cs diff --git a/project/code/TMV/TextureLoader.Pcx.cs b/project/code/TMV/TextureLoader.Pcx.cs new file mode 100644 index 0000000..34f490a --- /dev/null +++ b/project/code/TMV/TextureLoader.Pcx.cs @@ -0,0 +1,108 @@ +using System.IO; +using System.Linq; +using System.Numerics; +using System.Text; + +namespace KeepersCompound.TMV; + +public partial class TextureLoader +{ + // TODO: Support more types of PCX just in case + // TODO: We need to load the palette from an external file!!! + + // References: + // - https://www.fileformat.info/format/pcx/egff.htm + // - http://www.fysnet.net/pcxfile.htm + private static Godot.ImageTexture LoadPcx(string path) + { + using MemoryStream stream = new(File.ReadAllBytes(path)); + using BinaryReader reader = new(stream, Encoding.UTF8, false); + + // Header + var tag = reader.ReadByte(); + var version = reader.ReadByte(); + var compression = reader.ReadByte(); + var bytesPerPixel = reader.ReadByte(); + var min = new Vector2(reader.ReadUInt16(), reader.ReadUInt16()); + var max = new Vector2(reader.ReadUInt16(), reader.ReadUInt16()); + var dpi = new Vector2(reader.ReadUInt16(), reader.ReadUInt16()); + var headerPalette = reader.ReadBytes(48); + reader.ReadByte(); + var numPlanes = reader.ReadByte(); + var bytesPerRow = reader.ReadUInt16(); + var paletteMode = reader.ReadUInt16(); + var dpiSrc = new Vector2(reader.ReadUInt16(), reader.ReadUInt16()); + reader.ReadBytes(54); + var dataOffset = stream.Position; + + // Validation + if (tag != 0x0A) + { + throw new System.Exception("Invalid tag"); + } + var validVersions = new int[] { 0, 2, 3, 4, 5 }; + if (!validVersions.Contains(version)) + { + throw new System.Exception("Unsupported version"); + } + if (compression != 1) + { + throw new System.Exception("Unsupported compression mode"); + } + if (bytesPerPixel != 8 || numPlanes != 1) + { + throw new System.Exception("Unsupported bpp/plane format"); + } + + var palette = LoadPcxPalette(reader); + + // Read pixels + stream.Seek(dataOffset, SeekOrigin.Begin); + var width = (int)(max.X - min.X + 1); + var height = (int)(max.Y - min.Y + 1); + var image = Godot.Image.Create(width, height, false, Godot.Image.Format.Rgba8); + for (var y = 0; y < height; y++) + { + var x = 0; + while (x < bytesPerRow) + { + var b = reader.ReadByte(); + if ((b & 0xc0) == 0xc0) + { + var runLength = b & 0x3f; + b = reader.ReadByte(); + for (var i = 0; i < runLength; i++) + { + image.SetPixel(x, y, palette[b]); + x++; + } + } + else + { + image.SetPixel(x, y, palette[b]); + x++; + } + } + } + + return Godot.ImageTexture.CreateFromImage(image); + } + + private static Godot.Color[] LoadPcxPalette(BinaryReader reader) + { + var numColors = 256; + var bytesPerColor = 3; + var paletteSize = numColors * bytesPerColor; + reader.BaseStream.Seek(-paletteSize, SeekOrigin.End); + + var palette = new Godot.Color[numColors]; + for (var i = 0; i < numColors; i++) + { + var r = reader.ReadByte() / 255.0f; + var g = reader.ReadByte() / 255.0f; + var b = reader.ReadByte() / 255.0f; + palette[i] = new Godot.Color(r, g, b); + } + return palette; + } +} \ No newline at end of file diff --git a/project/code/TMV/TextureLoader.cs b/project/code/TMV/TextureLoader.cs index 978bcc9..20d5364 100644 --- a/project/code/TMV/TextureLoader.cs +++ b/project/code/TMV/TextureLoader.cs @@ -5,7 +5,7 @@ using Godot; namespace KeepersCompound.TMV; -public class TextureLoader +public partial class TextureLoader { private readonly string _rootPath; // TODO: Load from installation resources private readonly string _fmPath; @@ -60,17 +60,17 @@ public class TextureLoader public bool Load(int id, string path) { - var userTexturesPath = ProjectSettings.GlobalizePath($"user://textures{path}.png"); + var userTexturesPath = ProjectSettings.GlobalizePath($"user://textures{path}.PCX"); var loaded = false; if (_fmTexturePaths.TryGetValue(path.ToLower(), out var filePath)) { - _textureCache.Add(ImageTexture.CreateFromImage(Image.LoadFromFile(filePath))); + _textureCache.Add(LoadTexture(filePath)); loaded = true; } else if (File.Exists(userTexturesPath)) { - _textureCache.Add(ImageTexture.CreateFromImage(Image.LoadFromFile(userTexturesPath))); + _textureCache.Add(LoadTexture(userTexturesPath)); loaded = true; } @@ -80,6 +80,17 @@ public class TextureLoader return loaded; } + private static ImageTexture LoadTexture(string path) + { + var ext = path.GetExtension().ToLower(); + var texture = ext switch + { + "pcx" => LoadPcx(path), + _ => ImageTexture.CreateFromImage(Image.LoadFromFile(path)), + }; + return texture; + } + public ImageTexture Get(int id) { if (!_idMap.ContainsKey(id))