diff --git a/Models/CelModel.cs b/Models/CelModel.cs index d611a24..ad557d3 100644 --- a/Models/CelModel.cs +++ b/Models/CelModel.cs @@ -1,57 +1,56 @@ using System; using System.Collections.Generic; using System.Text.RegularExpressions; +using fxl.codes.kisekae.Services; +using Microsoft.Extensions.Logging; namespace fxl.codes.kisekae.Models { public class CelModel { - public const string Regex = @"#(\d*)\.?(\d*)\s*([\w\d\-]*\.[cCeElL]*)\s*\*?(\d*)?\s*\:?([\d\s]*)?;?([\w\d\-\s\%]*)"; + private const string Regex = @"#(?\d*)\.?(?\d*)\s*(?[\w\d\-]*\.[cCeElL]*)\s*\*?(?\d*)?\s*\:?(?[\d\s]*)?;?(?[\w\d\-\s\%]*)"; public Dictionary ImageByPalette = new(); - public CelModel(string line) + internal CelModel(ILogger logger, string line) { + logger.LogTrace($"Parsing cel line {line}"); var matcher = new Regex(Regex); var match = matcher.Match(line); foreach (var groupName in matcher.GetGroupNames()) { - if (groupName == "0") continue; - + var property = typeof(CelModel).GetProperty(groupName); + if (property == null) continue; match.Groups.TryGetValue(groupName, out var group); - if (string.IsNullOrEmpty(group?.Value)) continue; - var value = group.Value.Trim(); - - switch (groupName) + if (string.IsNullOrEmpty(group?.Value)) { - case "1": - Id = int.Parse(value); - break; - case "2": - Fix = int.Parse(value); - break; - case "3": - FileName = value; - break; - case "4": - PaletteId = int.Parse(value); - break; - case "5": - foreach (var id in value.Split(' ', StringSplitOptions.RemoveEmptyEntries)) Sets[int.Parse(id)] = true; - break; - case "6": - Comment = value; - break; + logger.LogWarning($"Unable to find regex group {groupName} in {line}"); + continue; + } + + var value = group.Value.Trim(); + if (property.PropertyType == typeof(string)) property.SetValue(this, value); + if (property.PropertyType == typeof(int)) property.SetValue(this, int.Parse(value)); + + if (property.PropertyType != Sets.GetType()) continue; + + foreach (var id in value.Split(' ', StringSplitOptions.RemoveEmptyEntries)) + { + var success = int.TryParse(id, out var result); + if (success) Sets[result] = true; + else logger.LogWarning($"Unable to parse {id} as cel sets"); } } } - public int Id { get; } - public int Fix { get; } - public string FileName { get; } - public int PaletteId { get; } + public int Id { get; init; } + public int Fix { get; init; } + public string FileName { get; init; } + public int PaletteId { get; init; } public bool[] Sets { get; } = new bool[10]; - public string Comment { get; } - public Point[] InitialPositions { get; } = new Point[10]; + public string Comment { get; init; } + public Coordinate[] InitialPositions { get; } = new Coordinate[10]; + public string DefaultImage => ImageByPalette.ContainsKey(PaletteId) ? ImageByPalette[PaletteId] : null; + public Coordinate Offset { get; set; } } } \ No newline at end of file diff --git a/Models/Coordinate.cs b/Models/Coordinate.cs new file mode 100644 index 0000000..6e6c94d --- /dev/null +++ b/Models/Coordinate.cs @@ -0,0 +1,14 @@ +namespace fxl.codes.kisekae.Models +{ + public class Coordinate + { + public Coordinate(int x, int y) + { + X = x; + Y = y; + } + + public int X { get; } + public int Y { get; } + } +} \ No newline at end of file diff --git a/Models/PaletteModel.cs b/Models/PaletteModel.cs index 26e9b4e..47947e3 100644 --- a/Models/PaletteModel.cs +++ b/Models/PaletteModel.cs @@ -1,12 +1,16 @@ using System; +using fxl.codes.kisekae.Services; +using Microsoft.Extensions.Logging; using SixLabors.ImageSharp; namespace fxl.codes.kisekae.Models { public class PaletteModel { - public PaletteModel(string line) + internal PaletteModel(ILogger logger, string line) { + logger.LogTrace($"Parsing palette line {line}"); + var parts = line.Split(';'); FileName = parts[0].Replace("%", "").Trim(); diff --git a/Models/PlaysetModel.cs b/Models/PlaysetModel.cs index f5cdb0a..e54e17a 100644 --- a/Models/PlaysetModel.cs +++ b/Models/PlaysetModel.cs @@ -1,15 +1,16 @@ using System.Collections.Generic; +using SixLabors.ImageSharp; namespace fxl.codes.kisekae.Models { public class PlaysetModel { + public int[] CurrentPalettes = new int[10]; public int Height { get; set; } public int Width { get; set; } - public int BorderColorIndex { get; set; } + public Color BorderColor { get; set; } public List Palettes { get; } = new(); public List Cels { get; } = new(); - public int[] CurrentPalettes = new int[10]; } } \ No newline at end of file diff --git a/Models/Point.cs b/Models/Point.cs deleted file mode 100644 index a16eb95..0000000 --- a/Models/Point.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace fxl.codes.kisekae.Models -{ - public class Point - { - public Point(int x, int y) - { - X = x; - Y = y; - } - - public int X { get; set; } - public int Y { get; set; } - } -} \ No newline at end of file diff --git a/Services/ConfigurationReaderService.cs b/Services/ConfigurationReaderService.cs index e52ca98..a0870de 100644 --- a/Services/ConfigurationReaderService.cs +++ b/Services/ConfigurationReaderService.cs @@ -30,6 +30,7 @@ namespace fxl.codes.kisekae.Services { var model = new PlaysetModel(); var initialPositions = new StringBuilder(); + var borderColorIndex = 0; using var reader = new StreamReader(fileStream); while (!reader.EndOfStream) @@ -48,13 +49,13 @@ namespace fxl.codes.kisekae.Services case '[': var borderValue = line.Replace("[", ""); if (borderValue.Contains(';')) borderValue = borderValue.Split(';')[0].Trim(); - model.BorderColorIndex = int.Parse(borderValue); + borderColorIndex = int.Parse(borderValue); break; case '%': - model.Palettes.Add(new PaletteModel(line)); + model.Palettes.Add(new PaletteModel(_logger, line)); break; case '#': - model.Cels.Add(new CelModel(line)); + model.Cels.Add(new CelModel(_logger, line)); break; case '$': case ' ': @@ -64,6 +65,7 @@ namespace fxl.codes.kisekae.Services } SetInitialPositions(model, initialPositions.ToString()); + if (model.Palettes.Any() && model.Palettes[0].Colors.Any()) model.BorderColor = model.Palettes[0].Colors[borderColorIndex]; if (string.IsNullOrEmpty(directory)) return model; @@ -95,7 +97,7 @@ namespace fxl.codes.kisekae.Services foreach (var cel in model.Cels.Where(x => x.Id == innerIndex - 1)) { - cel.InitialPositions[index] = new Point(int.Parse(point[0]), int.Parse(point[1])); + cel.InitialPositions[index] = new Coordinate(int.Parse(point[0]), int.Parse(point[1])); } } } diff --git a/Services/FileParserService.cs b/Services/FileParserService.cs index a05adf7..191f3d9 100644 --- a/Services/FileParserService.cs +++ b/Services/FileParserService.cs @@ -14,6 +14,7 @@ namespace fxl.codes.kisekae.Services public class FileParserService { private static readonly byte[] KissHeader = Encoding.ASCII.GetBytes("KiSS"); + private static readonly Color Transparent = Color.Black.WithAlpha(0); private readonly ILogger _logger; private readonly IsolatedStorageFile _storage; @@ -64,7 +65,8 @@ namespace fxl.codes.kisekae.Services var xOffset = BitConverter.ToInt16(buffer, 12); var yOffset = BitConverter.ToInt16(buffer, 14); - cel.ImageByPalette = GetCelImages(buffer[32..], palettes.ToArray(), width, height, pixelBits, xOffset, yOffset); + cel.ImageByPalette = GetCelImages(buffer[32..], palettes.ToArray(), width, height, pixelBits); + cel.Offset = new Coordinate(xOffset, yOffset); } else { @@ -86,51 +88,37 @@ namespace fxl.codes.kisekae.Services return buffer; } - private Color[] GetColors(byte[] bytes, int depth = 12, int colorsPerGroup = 16, int groups = 10) + private Color[] GetColors(IReadOnlyCollection bytes, int depth = 12, int colorsPerGroup = 16, int groups = 10) { _logger.LogTrace("Converting byte array to colors"); - var colorCounter = 0; + var colorValues = GetValues(bytes, depth / 3); var colors = new Color[colorsPerGroup * groups]; - var chunkSize = depth == 12 ? 2 : 3; + var length = depth == 12 ? 4 : 3; + var multiplier = depth == 12 ? 16 : 1; + // Nibbles order for 12 bits: R, B, 0, G - for (var index = 0; index < bytes.Length; index += chunkSize) + for (var groupIndex = 0; groupIndex < groups; groupIndex++) + for (var colorIndex = 0; colorIndex < colorsPerGroup; colorIndex++) { - colors[colorCounter] = GetColor(bytes[index..(index + chunkSize)], depth); - colorCounter++; + var start = (groupIndex + colorIndex) * length; + var red = colorValues[start] * multiplier; + var green = colorValues[start + (depth == 12 ? 3 : 1)] * multiplier; + var blue = colorValues[start + (depth == 12 ? 1 : 2)] * multiplier; + colors[groupIndex + colorIndex] = Color.FromRgb(Convert.ToByte(red), Convert.ToByte(green), Convert.ToByte(blue)); } return colors; } - private static Color GetColor(IReadOnlyList bytes, int depth) - { - if (depth != 12) return Color.FromRgb(bytes[0], bytes[1], bytes[2]); - - var redBlue = bytes[0]; - var red = (redBlue >> 4) & 0x0F; - red += red * 16; - - var blue = redBlue & 0x0F; - blue += blue * 16; - - var zeroGreen = bytes[1]; - var green = zeroGreen & 0x0F; - green += green * 16; - - return Color.FromRgb(Convert.ToByte(red), Convert.ToByte(green), Convert.ToByte(blue)); - } - - private Dictionary GetCelImages(IReadOnlyList bytes, + private Dictionary GetCelImages(IReadOnlyCollection bytes, IReadOnlyList palettes, int width, int height, - int pixelBits = 4, - int xOffset = 0, - int yOffset = 0) + int pixelBits = 4) { _logger.LogTrace("Converting byte array to base64 encoded gifs per palette"); var dictionary = new Dictionary(); - var step = (int)Math.Ceiling((double)8 / pixelBits); + var values = GetValues(bytes, pixelBits); for (var paletteIndex = 0; paletteIndex < palettes.Count; paletteIndex++) { @@ -141,20 +129,14 @@ namespace fxl.codes.kisekae.Services for (var row = 0; row < height; row++) { var span = bitmap.GetPixelRowSpan(row); - for (var column = 0; column < width; column += step) + for (var column = 0; column < width; column++) { - var pixelByte = bytes[index]; - var nib1 = (pixelByte >> 4) & 0x0F; - span[column] = nib1 == 0 ? Color.Black.WithAlpha(0) : palette.Colors[nib1]; - - if (column + 1 < width) - { - var nib2 = pixelByte & 0x0F; - span[column + 1] = nib2 == 0 ? Color.Black.WithAlpha(0) : palette.Colors[nib2]; - } - + var value = values[index]; + span[column] = value == 0 ? Transparent : palette.Colors[value]; index++; } + + if (width % 2 != 0 && pixelBits == 4) index++; } using var memory = new MemoryStream(); @@ -164,5 +146,23 @@ namespace fxl.codes.kisekae.Services return dictionary; } + + private static int[] GetValues(IReadOnlyCollection bytes, int bits = 4) + { + if (bits != 4) return bytes.Select(Convert.ToInt32).ToArray(); + + var values = new int[bytes.Count * 2]; + var index = 0; + foreach (var slice in bytes) + { + var nib1 = (slice >> 4) & 0x0F; + var nib2 = slice & 0x0F; + values[index] = nib1; + values[index + 1] = nib2; + index += 2; + } + + return values; + } } } \ No newline at end of file diff --git a/Views/Home/Play.cshtml b/Views/Home/Play.cshtml index e1254e4..3be2eb2 100644 --- a/Views/Home/Play.cshtml +++ b/Views/Home/Play.cshtml @@ -9,11 +9,14 @@ @foreach (var palette in Model.Palettes) {

+ Start Palette @palette.Comment
@foreach (var color in palette.Colors) { @color } +
+ End Palette

} @@ -21,9 +24,6 @@
@foreach (var cel in Model.Cels) { - foreach (var (key, value) in cel.ImageByPalette) - { - @key - } + @cel.FileName }
\ No newline at end of file diff --git a/appsettings.Development.json b/appsettings.Development.json index 8983e0f..cc5fe2b 100644 --- a/appsettings.Development.json +++ b/appsettings.Development.json @@ -1,7 +1,7 @@ { "Logging": { "LogLevel": { - "Default": "Information", + "Default": "Trace", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" }