Add PCX image loading

This commit is contained in:
Jarrod Doyle 2024-08-12 18:46:30 +01:00
parent 3aa6024103
commit c14f74b108
Signed by: Jayrude
GPG Key ID: 38B57B16E7C0ADF7
2 changed files with 123 additions and 4 deletions

View File

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

View File

@ -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))