Browse Source

Added palette formats

Krzysztof Krysiński 2 years ago
parent
commit
bcd85b8189

+ 3 - 2
src/PixiEditor/Helpers/Extensions/ServiceCollectionHelpers.cs

@@ -3,8 +3,8 @@ using PixiEditor.Models.Commands;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.DataProviders;
 using PixiEditor.Models.DataProviders;
 using PixiEditor.Models.IO;
 using PixiEditor.Models.IO;
-using PixiEditor.Models.IO.ClsFile;
-using PixiEditor.Models.IO.JascPalFile;
+using PixiEditor.Models.IO.PaletteParsers;
+using PixiEditor.Models.IO.PaletteParsers.JascPalFile;
 using PixiEditor.Models.UserPreferences;
 using PixiEditor.Models.UserPreferences;
 using PixiEditor.ViewModels;
 using PixiEditor.ViewModels;
 using PixiEditor.ViewModels.SubViewModels.Document;
 using PixiEditor.ViewModels.SubViewModels.Document;
@@ -62,6 +62,7 @@ internal static class ServiceCollectionHelpers
         // Palette Parsers
         // Palette Parsers
         .AddSingleton<PaletteFileParser, JascFileParser>()
         .AddSingleton<PaletteFileParser, JascFileParser>()
         .AddSingleton<PaletteFileParser, ClsFileParser>()
         .AddSingleton<PaletteFileParser, ClsFileParser>()
+        .AddSingleton<PaletteFileParser, PngPaletteParser>()
         // Palette data sources
         // Palette data sources
         .AddSingleton<PaletteListDataSource, LocalPalettesFetcher>();
         .AddSingleton<PaletteListDataSource, LocalPalettesFetcher>();
 }
 }

+ 1 - 1
src/PixiEditor/Models/DataProviders/LocalPalettesFetcher.cs

@@ -3,7 +3,7 @@ using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.DataHolders.Palettes;
 using PixiEditor.Models.DataHolders.Palettes;
 using PixiEditor.Models.IO;
 using PixiEditor.Models.IO;
-using PixiEditor.Models.IO.JascPalFile;
+using PixiEditor.Models.IO.PaletteParsers.JascPalFile;
 using PixiEditor.Models.UserPreferences;
 using PixiEditor.Models.UserPreferences;
 
 
 namespace PixiEditor.Models.DataProviders;
 namespace PixiEditor.Models.DataProviders;

+ 1 - 0
src/PixiEditor/Models/IO/PaletteFileData.cs

@@ -7,6 +7,7 @@ internal class PaletteFileData
     public string Title { get; set; }
     public string Title { get; set; }
     public Color[] Colors { get; set; }
     public Color[] Colors { get; set; }
     public bool IsCorrupted { get; set; } = false;
     public bool IsCorrupted { get; set; } = false;
+    public static PaletteFileData Corrupted => new ("Corrupted", Array.Empty<Color>()) { IsCorrupted = true };
 
 
     public PaletteFileData(Color[] colors)
     public PaletteFileData(Color[] colors)
     {
     {

+ 1 - 1
src/PixiEditor/Models/IO/PaletteFileParser.cs

@@ -3,7 +3,7 @@
 internal abstract class PaletteFileParser
 internal abstract class PaletteFileParser
 {
 {
     public abstract Task<PaletteFileData> Parse(string path);
     public abstract Task<PaletteFileData> Parse(string path);
-    public abstract Task Save(string path, PaletteFileData data);
+    public abstract Task<bool> Save(string path, PaletteFileData data);
     public abstract string FileName { get; }
     public abstract string FileName { get; }
     public abstract string[] SupportedFileExtensions { get; }
     public abstract string[] SupportedFileExtensions { get; }
 }
 }

+ 1 - 1
src/PixiEditor/Models/IO/ClsFile/ClsFileParser.cs → src/PixiEditor/Models/IO/PaletteParsers/ClsFileParser.cs

@@ -2,7 +2,7 @@
 using CLSEncoderDecoder;
 using CLSEncoderDecoder;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 
 
-namespace PixiEditor.Models.IO.ClsFile;
+namespace PixiEditor.Models.IO.PaletteParsers;
 
 
 internal class ClsFileParser : PaletteFileParser
 internal class ClsFileParser : PaletteFileParser
 {
 {

+ 1 - 1
src/PixiEditor/Models/IO/JascPalFile/JascFileException.cs → src/PixiEditor/Models/IO/PaletteParsers/JascPalFile/JascFileException.cs

@@ -1,4 +1,4 @@
-namespace PixiEditor.Models.IO.JascPalFile;
+namespace PixiEditor.Models.IO.PaletteParsers.JascPalFile;
 
 
 internal class JascFileException : Exception
 internal class JascFileException : Exception
 {
 {

+ 3 - 3
src/PixiEditor/Models/IO/JascPalFile/JascFileParser.cs → src/PixiEditor/Models/IO/PaletteParsers/JascPalFile/JascFileParser.cs

@@ -1,7 +1,7 @@
 using System.IO;
 using System.IO;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 
 
-namespace PixiEditor.Models.IO.JascPalFile;
+namespace PixiEditor.Models.IO.PaletteParsers.JascPalFile;
 
 
 /// <summary>
 /// <summary>
 ///     This class is responsible for parsing JASC-PAL files. Which holds the color palette data.
 ///     This class is responsible for parsing JASC-PAL files. Which holds the color palette data.
@@ -60,11 +60,11 @@ internal class JascFileParser : PaletteFileParser
         }
         }
         catch
         catch
         {
         {
-            return new PaletteFileData("Corrupted", Array.Empty<Color>()) { IsCorrupted = true };
+            return PaletteFileData.Corrupted;
         }
         }
     }
     }
 
 
-    public override async Task Save(string path, PaletteFileData data) => await SaveFile(path, data);
+    public override async Task<bool> Save(string path, PaletteFileData data) => await SaveFile(path, data);
 
 
     private static bool ValidateFile(string fileType, string magicBytes)
     private static bool ValidateFile(string fileType, string magicBytes)
     {
     {

+ 123 - 0
src/PixiEditor/Models/IO/PaletteParsers/PngPaletteParser.cs

@@ -0,0 +1,123 @@
+using System.IO;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using Color = PixiEditor.DrawingApi.Core.ColorsImpl.Color;
+
+namespace PixiEditor.Models.IO.PaletteParsers;
+
+internal class PngPaletteParser : PaletteFileParser
+{
+    public override string FileName { get; } = "PNG Palette";
+    public override string[] SupportedFileExtensions { get; } = { ".png" };
+
+    public override async Task<PaletteFileData> Parse(string path)
+    {
+           try
+           {
+               return await ParseFile(path);
+           }
+           catch
+           {
+               return PaletteFileData.Corrupted;
+           }
+    }
+
+    private async Task<PaletteFileData> ParseFile(string path)
+    {
+        return await Task.Run(() =>
+        {
+            PngBitmapDecoder decoder = new PngBitmapDecoder(new Uri(path), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
+
+            BitmapFrame frame = decoder.Frames[0];
+
+            Color[] colors = ExtractFromBitmap(frame);
+
+            PaletteFileData data = new(
+                Path.GetFileNameWithoutExtension(path), colors);
+
+            return data;
+        });
+    }
+
+    private Color[] ExtractFromBitmap(BitmapFrame frame)
+    {
+        if (frame.Palette is not null && frame.Palette.Colors.Count > 0)
+        {
+            return ExtractFromBitmapPalette(frame.Palette);
+        }
+
+        return ExtractFromBitmapSource(frame);
+    }
+
+    private Color[] ExtractFromBitmapSource(BitmapFrame frame)
+    {
+        if (frame.PixelWidth == 0 || frame.PixelHeight == 0)
+        {
+            return Array.Empty<Color>();
+        }
+
+        List<Color> colors = new();
+
+        byte[] pixels = new byte[frame.PixelWidth * frame.PixelHeight * 4];
+        frame.CopyPixels(pixels, frame.PixelWidth * 4, 0);
+        int pixelCount = pixels.Length / 4;
+        for (int i = 0; i < pixelCount; i++)
+        {
+            var color = GetColorFromBytes(pixels, i);
+            if (!colors.Contains(color))
+            {
+                colors.Add(color);
+            }
+        }
+
+        return colors.ToArray();
+    }
+
+    private Color GetColorFromBytes(byte[] pixels, int i)
+    {
+        return new Color(pixels[i * 4 + 2], pixels[i * 4 + 1], pixels[i * 4]);
+    }
+
+    private Color[] ExtractFromBitmapPalette(BitmapPalette palette)
+    {
+        if (palette.Colors == null || palette.Colors.Count == 0)
+        {
+            return Array.Empty<Color>();
+        }
+
+        return palette.Colors.Select(color => color.ToColor()).ToArray();
+    }
+
+    public override async Task<bool> Save(string path, PaletteFileData data)
+    {
+        try
+        {
+            await SaveFile(path, data);
+            return true;
+        }
+        catch
+        {
+            return false;
+        }
+    }
+
+    private async Task SaveFile(string path, PaletteFileData data)
+    {
+        await Task.Run(() =>
+        {
+            WriteableBitmap bitmap = new(data.Colors.Length, 1, 96, 96, PixelFormats.Bgra32, null);
+            bitmap.Lock();
+            for (int i = 0; i < data.Colors.Length; i++)
+            {
+                Color color = data.Colors[i];
+                bitmap.SetPixel(i, 0, color.ToOpaqueMediaColor());
+            }
+
+            bitmap.Unlock();
+            PngBitmapEncoder encoder = new();
+            encoder.Frames.Add(BitmapFrame.Create(bitmap));
+            using FileStream stream = new(path, FileMode.Create);
+            encoder.Save(stream);
+        });
+    }
+}

+ 6 - 1
src/PixiEditor/Views/UserControls/Palettes/PaletteViewer.xaml.cs

@@ -7,6 +7,7 @@ using Microsoft.Win32;
 using PixiEditor.Helpers;
 using PixiEditor.Helpers;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.DataProviders;
 using PixiEditor.Models.DataProviders;
+using PixiEditor.Models.Dialogs;
 using PixiEditor.Models.IO;
 using PixiEditor.Models.IO;
 using PixiEditor.Views.Dialogs;
 using PixiEditor.Views.Dialogs;
 using BackendColor = PixiEditor.DrawingApi.Core.ColorsImpl.Color;
 using BackendColor = PixiEditor.DrawingApi.Core.ColorsImpl.Color;
@@ -138,7 +139,11 @@ internal partial class PaletteViewer : UserControl
         {
         {
             string fileName = saveFileDialog.FileName;
             string fileName = saveFileDialog.FileName;
             var foundParser = FileParsers.First(x => x.SupportedFileExtensions.Contains(Path.GetExtension(fileName)));
             var foundParser = FileParsers.First(x => x.SupportedFileExtensions.Contains(Path.GetExtension(fileName)));
-            await foundParser.Save(fileName, new PaletteFileData(Colors.ToArray()));
+            bool saved = await foundParser.Save(fileName, new PaletteFileData(Colors.ToArray()));
+            if (!saved)
+            {
+                NoticeDialog.Show("COULD_NOT_SAVE_PALETTE", "ERROR");
+            }
         }
         }
     }
     }