Quellcode durchsuchen

Merge pull request #588 from warrengalyen/feat-deluxepaint-format

Add support to read DeluxePaint palettes
Krzysztof Krysiński vor 1 Jahr
Ursprung
Commit
9ef7c5a1b3

+ 1 - 4
src/PixiEditor/Helpers/Extensions/ServiceCollectionHelpers.cs

@@ -1,5 +1,4 @@
 using Microsoft.Extensions.DependencyInjection;
-using PixiEditor.Extensions;
 using PixiEditor.Extensions.Common.Localization;
 using PixiEditor.Extensions.Common.UserPreferences;
 using PixiEditor.Extensions.Palettes;
@@ -10,15 +9,12 @@ using PixiEditor.Models.AppExtensions.Services;
 using PixiEditor.Models.Commands;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.DataProviders;
-using PixiEditor.Models.IO;
 using PixiEditor.Models.IO.PaletteParsers;
 using PixiEditor.Models.IO.PaletteParsers.JascPalFile;
 using PixiEditor.Models.Localization;
 using PixiEditor.Models.Preferences;
-using PixiEditor.ViewModels;
 using PixiEditor.ViewModels.SubViewModels.AdditionalContent;
 using PixiEditor.ViewModels.SubViewModels.Document;
-using PixiEditor.ViewModels.SubViewModels.Main;
 using PixiEditor.ViewModels.SubViewModels.Tools;
 using PixiEditor.ViewModels.SubViewModels.Tools.Tools;
 
@@ -75,6 +71,7 @@ internal static class ServiceCollectionHelpers
         // Palette Parsers
         .AddSingleton<PaletteFileParser, JascFileParser>()
         .AddSingleton<PaletteFileParser, ClsFileParser>()
+        .AddSingleton<PaletteFileParser, DeluxePaintParser>()
         .AddSingleton<PaletteFileParser, PngPaletteParser>()
         .AddSingleton<PaletteFileParser, PaintNetTxtParser>()
         .AddSingleton<PaletteFileParser, HexPaletteParser>()

+ 122 - 0
src/PixiEditor/Models/IO/PaletteParsers/DeluxePaintParser.cs

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