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; } }