|
@@ -0,0 +1,122 @@
|
|
|
+using System.IO;
|
|
|
+using System.Text;
|
|
|
+using PixiEditor.Extensions.Palettes;
|
|
|
+using PixiEditor.Extensions.Palettes.Parsers;
|
|
|
+
|
|
|
+namespace PixiEditor.Models.IO.PaletteParsers;
|
|
|
+
|
|
|
+// Reads 8-bit color palette data from Interleaved Bitmap format (LBM/BBM)
|
|
|
+// most commonly used by DeluxePaint on Amiga and MS DOS.
|
|
|
+//
|
|
|
+// https://en.wikipedia.org/wiki/ILBM
|
|
|
+
|
|
|
+// Note: A BBM file is essentially a LBM without a full image.
|
|
|
+
|
|
|
+internal class DeluxePaintParser : PaletteFileParser
|
|
|
+{
|
|
|
+ public override string FileName { get; } = "DeluxePaint Interleaved Bitmap Palette";
|
|
|
+ public override string[] SupportedFileExtensions { get; } = new string[] { ".bbm", ".lbm" };
|
|
|
+ public override async Task<PaletteFileData> Parse(string path)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ return await ParseFile(path);
|
|
|
+ }
|
|
|
+ catch
|
|
|
+ {
|
|
|
+ return PaletteFileData.Corrupted;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static async Task<PaletteFileData> ParseFile(string path)
|
|
|
+ {
|
|
|
+ List<PaletteColor> colorPalette = new();
|
|
|
+ string name = Path.GetFileNameWithoutExtension(path);
|
|
|
+
|
|
|
+ await using (Stream stream = File.OpenRead(path))
|
|
|
+ {
|
|
|
+ byte[] buffer;
|
|
|
+ string header;
|
|
|
+
|
|
|
+ // read the FORM header that identifies the document as an IFF file
|
|
|
+ buffer = new byte[4];
|
|
|
+ stream.Read(buffer, 0, buffer.Length);
|
|
|
+ if (Encoding.ASCII.GetString(buffer) != "FORM")
|
|
|
+ return PaletteFileData.Corrupted; // Form header not found
|
|
|
+
|
|
|
+ // the next value is the size of all the data in the FORM chunk
|
|
|
+ // We don't actually need this value, but we have to read it
|
|
|
+ // regardless to advance the stream
|
|
|
+ ReadInt(stream);
|
|
|
+
|
|
|
+ stream.Read(buffer, 0, buffer.Length);
|
|
|
+ header = Encoding.ASCII.GetString(buffer);
|
|
|
+ if (header != "PBM " && header != "ILBM")
|
|
|
+ return PaletteFileData.Corrupted; // Bitmap header not found
|
|
|
+
|
|
|
+ while (stream.Read(buffer, 0, buffer.Length) == buffer.Length)
|
|
|
+ {
|
|
|
+ int chunkLength;
|
|
|
+
|
|
|
+ chunkLength = ReadInt(stream);
|
|
|
+
|
|
|
+ if (Encoding.ASCII.GetString(buffer) != "CMAP")
|
|
|
+ {
|
|
|
+ // some other LBM chunk, skip it
|
|
|
+ if (stream.CanSeek)
|
|
|
+ {
|
|
|
+ stream.Seek(chunkLength, SeekOrigin.Current);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ for (int i = 0; i < chunkLength; i++)
|
|
|
+ stream.ReadByte();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // color map chunk
|
|
|
+ for (int i = 0; i < chunkLength / 3; i++)
|
|
|
+ {
|
|
|
+ int[] rgb = new int[3];
|
|
|
+
|
|
|
+ rgb[0] = stream.ReadByte();
|
|
|
+ rgb[1] = stream.ReadByte();
|
|
|
+ rgb[2] = stream.ReadByte();
|
|
|
+
|
|
|
+ colorPalette.Add(new PaletteColor((byte)rgb[0], (byte)rgb[1], (byte)rgb[2]));
|
|
|
+ }
|
|
|
+
|
|
|
+ // all done so stop reading the rest of the file
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // chunks always contain an even number of bytes even if the recorded length is odd
|
|
|
+ // if the length is odd, then there's a padding byte in the file - just read and discard
|
|
|
+ if (chunkLength % 2 != 0)
|
|
|
+ stream.ReadByte();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return new PaletteFileData(name, colorPalette.ToArray());
|
|
|
+ }
|
|
|
+
|
|
|
+ public override bool CanSave => false;
|
|
|
+
|
|
|
+ public override async Task<bool> Save(string path, PaletteFileData data)
|
|
|
+ {
|
|
|
+ throw new SavingNotSupportedException("Saving palette as .bbm or .lbm is not supported.");
|
|
|
+ }
|
|
|
+
|
|
|
+ private static int ReadInt(Stream stream)
|
|
|
+ {
|
|
|
+ byte[] buffer;
|
|
|
+
|
|
|
+ // big endian conversion: http://stackoverflow.com/a/14401341/148962
|
|
|
+
|
|
|
+ buffer = new byte[4];
|
|
|
+ stream.Read(buffer, 0, buffer.Length);
|
|
|
+
|
|
|
+ return (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3];
|
|
|
+ }
|
|
|
+}
|