diff --git a/.gitignore b/.gitignore index 7244ea4..fe39e03 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ /.vs/LegoArt /LegoArtTool/bin/Debug/netcoreapp3.1 /LegoArtTool/obj +/.idea diff --git a/LegoArtTool/LegoArtColorOffset.cs b/LegoArtTool/LegoArtColorOffset.cs new file mode 100644 index 0000000..161cf6c --- /dev/null +++ b/LegoArtTool/LegoArtColorOffset.cs @@ -0,0 +1,18 @@ +namespace LegoArtTool +{ + public class LegoArtColorOffset + { + public LegoArtColorInfo LegoArtColorInfo { get; private set; } + public decimal ColorOffset { get; private set; } + public bool IsAvailable { get; set; } + + public LegoArtColorOffset( decimal colorOffset, LegoArtColorInfo legoArtColorInfo) + { + ColorOffset = colorOffset; + LegoArtColorInfo = legoArtColorInfo; + IsAvailable = true; + } + + + } +} \ No newline at end of file diff --git a/LegoArtTool/LegoArtColorService.cs b/LegoArtTool/LegoArtColorService.cs index 6b05cd6..36b422b 100644 --- a/LegoArtTool/LegoArtColorService.cs +++ b/LegoArtTool/LegoArtColorService.cs @@ -29,11 +29,10 @@ namespace LegoArtTool LegoColors.Add(new LegoArtColorInfo(Color.FromArgb(66, 23, 3), 15, 554)); } - public List ParseImage(string imagePath) + public static List ParseImage(Bitmap bitmap) { var parsedLegoColors = new List(LegoColors); - var bitmap = new Bitmap(imagePath); var width = bitmap.Width; var height = bitmap.Height; for (int h = 0; h < height; h++) diff --git a/LegoArtTool/LegoArtImageGenerationService.cs b/LegoArtTool/LegoArtImageGenerationService.cs new file mode 100644 index 0000000..ea7bdf0 --- /dev/null +++ b/LegoArtTool/LegoArtImageGenerationService.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.Linq; + +namespace LegoArtTool +{ + public class LegoArtImageGenerationService + { + [SuppressMessage("ReSharper.DPA", "DPA0002: Excessive memory allocations in SOH", MessageId = "type: WhereListIterator`1[LegoArtTool.LegoArtColorOffset]")] + public Bitmap GenerateLegoArtImageFromFullColorImage(Bitmap bitmap) + { + var width = bitmap.Width; + var height = bitmap.Height; + + var pixels = new List(); + + for (int h = 0; h < height; h++) + { + for (int w = 0; w < width; w++) + { + var color = bitmap.GetPixel(h, w); + var pixel = MapPixel(color, h ,w); + pixels.Add(pixel); + } + } + + var reducedBitmap = new Bitmap(bitmap.Width, bitmap.Height); + + var legoArtColorInfos = LegoArtColorService.LegoColors; + var remainingColors = legoArtColorInfos.ToDictionary(k => k.LegoNumber, v => v.HaveCount); + + + while (pixels.Count(p => !p.IsAssigned) > 0) + { + var minOffset = pixels.Where(p => !p.IsAssigned).SelectMany(p => p.LegoArtColorOffsets.Where(o => o.IsAvailable) + .Select(o => o.ColorOffset)) + .Min(x => x); + + var pixel = pixels.Where(p => !p.IsAssigned).First(p => p.LegoArtColorOffsets.Any(o => o.ColorOffset == minOffset)); + var legoArtColorInfo = pixel.LegoArtColorOffsets.First(o => o.ColorOffset == minOffset).LegoArtColorInfo; + reducedBitmap.SetPixel(pixel.X, pixel.Y, legoArtColorInfo.Color); + + + remainingColors[legoArtColorInfo.LegoNumber]--; + pixel.IsAssigned = true; + + if (remainingColors[legoArtColorInfo.LegoNumber] < 1) + { + var legoArtColorOffsets = pixels.SelectMany(p => p.LegoArtColorOffsets + .Where(o => o.LegoArtColorInfo.LegoNumber == legoArtColorInfo.LegoNumber)).ToList(); + legoArtColorOffsets.ForEach(o => o.IsAvailable = false); + } + } + + return reducedBitmap; + } + + private Pixel MapPixel( + Color inputColor, + int xPosition, + int yPosition) + { + var legoArtColorInfos = LegoArtColorService.LegoColors; + + var legoArtColorOffsets = legoArtColorInfos.Select(i => ColorOffset(inputColor, i)).ToList(); + + return new Pixel(xPosition, yPosition, legoArtColorOffsets); + } + + private static LegoArtColorOffset ColourDistance(Color inputColor, LegoArtColorInfo colorInfo) + { + var targetColor = colorInfo.Color; + var rmean = ((long)inputColor.R + (long)targetColor.R) / 2; + var r = (long)inputColor.R - (long)targetColor.R; + var g = (long)inputColor.G - (long)targetColor.G; + var b = (long)inputColor.B - (long)targetColor.B; + var offset = (decimal) Math.Sqrt((((512 + rmean) * r * r) >> 8) + 4 * g * g + (((767 - rmean) * b * b) >> 8)); + return new LegoArtColorOffset((decimal) offset, colorInfo); + } + + private LegoArtColorOffset ColorOffset(Color inputColor, LegoArtColorInfo colorInfo) + { + var offset = Math.Abs( + ColorNum(inputColor) - ColorNum(colorInfo.Color)) + + GetHueDistance(inputColor.GetHue(), colorInfo.Color.GetHue()); + + return new LegoArtColorOffset((decimal) offset, colorInfo); + } + + private static float GetBrightness(Color c) + { + return (c.R * 0.299f + c.G * 0.587f + c.B *0.114f) / 256f; + } + + private static float GetHueDistance(float hue1, float hue2) + { + var d = Math.Abs(hue1 - hue2); + return d > 180 ? 360 - d : d; + + } + + private static float ColorNum(Color c) + { + return c.GetSaturation() + GetBrightness(c); + } + } +} \ No newline at end of file diff --git a/LegoArtTool/Pixel.cs b/LegoArtTool/Pixel.cs new file mode 100644 index 0000000..8798af2 --- /dev/null +++ b/LegoArtTool/Pixel.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +namespace LegoArtTool +{ + public class Pixel + { + public int X { get; private set; } + public int Y { get; private set; } + public List LegoArtColorOffsets { get; private set; } + + public bool IsAssigned { get; set; } + + public Pixel(int x, int y, List legoArtColorOffsets) + { + X = x; + Y = y; + LegoArtColorOffsets = legoArtColorOffsets; + IsAssigned = false; + } + + } +} \ No newline at end of file