using System.IO; using System.Linq; using System.Text; using Godot; namespace KeepersCompound.TMV; public partial class TextureLoader { // TODO: Support more types of PCX just in case // References: // - https://www.fileformat.info/format/pcx/egff.htm // - http://www.fysnet.net/pcxfile.htm private static Image LoadPcx(string path) { static Color[] LoadFamilyPalette(string famPath) { var numColors = 256; var bytesPerColor = 3; var paletteSize = numColors * bytesPerColor; var palette = new Color[numColors]; var options = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive }; var paths = Directory.GetFiles(famPath, "full.pcx", options); if (paths.Length != 0) { var palettePath = paths[0]; using MemoryStream stream = new(File.ReadAllBytes(palettePath)); using BinaryReader reader = new(stream, Encoding.UTF8, false); stream.Seek(-paletteSize, SeekOrigin.End); 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; var a = (i == 0) ? 0.0f : 1.0f; palette[i] = new Color(r, g, b, a); } } return palette; } static System.Numerics.Vector2 ReadVec2(BinaryReader reader) { return new(reader.ReadUInt16(), reader.ReadUInt16()); } 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 = ReadVec2(reader); var max = ReadVec2(reader); var dpi = ReadVec2(reader); var headerPalette = reader.ReadBytes(48); reader.ReadByte(); var numPlanes = reader.ReadByte(); var bytesPerRow = reader.ReadUInt16(); var paletteMode = reader.ReadUInt16(); var dpiSrc = ReadVec2(reader); reader.ReadBytes(54); // 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 = LoadFamilyPalette(path.GetBaseDir()); // Read run length encoded pixel Data var width = (int)(max.X - min.X + 1); var height = (int)(max.Y - min.Y + 1); var image = Image.CreateEmpty(width, height, false, 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 image; } }