• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

samsmithnz / PuzzleSolver / 3959451537

pending completion
3959451537

Pull #20

github

GitHub
Merge 2f1ec4480 into 9fd794745
Pull Request #20: Adding more features to group colors and multiple color palettes

297 of 342 branches covered (86.84%)

Branch coverage included in aggregate %.

154 of 154 new or added lines in 2 files covered. (100.0%)

389 of 519 relevant lines covered (74.95%)

5197396.66 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.34
/src/PuzzleSolver/ImageProcessing.cs
1
using SixLabors.ImageSharp;
2
using SixLabors.ImageSharp.PixelFormats;
3
using System.Text;
4

5
namespace PuzzleSolver;
6

7
public class ImageProcessing
8
{
9
    public List<Rgb24> ColorPalette { get; set; }
4,997,633✔
10

11
    public ImageProcessing(List<Rgb24> colorPalette)
10✔
12
    {
10✔
13
        //Add the primary and secondary colors, with black and white for initial buckets
14
        ColorPalette = colorPalette;
10✔
15
    }
10✔
16

17
    public Dictionary<Rgb24, List<Rgb24>> ProcessImageIntoColorGroups(string? sourceFilename = null, Image<Rgb24>? image = null)
18
    {
10✔
19
        Dictionary<Rgb24, List<Rgb24>> groupedColors = new();
10✔
20
        //string destFilename = Path.GetFileNameWithoutExtension(srcFile.Name) + "_sorted.jpg";
21

22
        Image<Rgb24>? sourceImage = null;
10✔
23
        if (sourceFilename != null)
10!
24
        {
10✔
25
            FileInfo srcFile = new(sourceFilename);
10✔
26
            sourceImage = Image.Load<Rgb24>(srcFile.FullName);
10✔
27
        }
10✔
28
        else if (image != null)
×
29
        {
×
30
            sourceImage = image;
×
31
        }
×
32

33
        //using var destImg = new Image<Rgb24>(srcWidth, srcHeight);
34
        //Dictionary<Rgb24, int> pixels = new();
35
        sourceImage?.ProcessPixelRows(accessor =>
10!
36
        {
10✔
37
            //int srcWidth = sourceImg.Size().Width;
10✔
38
            int srcHeight = sourceImage.Size().Height;
10✔
39

10✔
40
            for (var row = 0; row < srcHeight; row++)
7,196✔
41
            {
3,588✔
42
                Span<Rgb24> pixelSpan = accessor.GetRowSpan(row);
3,588✔
43
                for (var col = 0; col < pixelSpan.Length; col++)
10,002,422✔
44
                {
4,997,623✔
45
                    Rgb24? colorGroup = FindClosestColorGroup(pixelSpan[col]);
4,997,623✔
46
                    if (colorGroup != null)
4,997,623✔
47
                    {
4,997,623✔
48
                        if (!groupedColors.ContainsKey((Rgb24)colorGroup))
4,997,623✔
49
                        {
281✔
50
                            List<Rgb24> colorList = new()
281✔
51
                                {
281✔
52
                                pixelSpan[col]
281✔
53
                                };
281✔
54
                            groupedColors[(Rgb24)colorGroup] = colorList;
281✔
55
                        }
281✔
56
                        else
10✔
57
                        {
4,997,342✔
58
                            List<Rgb24> colorList = groupedColors[(Rgb24)colorGroup];
4,997,342✔
59
                            colorList.Add(pixelSpan[col]);
4,997,342✔
60
                            groupedColors[(Rgb24)colorGroup] = colorList;
4,997,342✔
61
                        }
4,997,342✔
62
                    }
4,997,623✔
63
                }
4,997,623✔
64
            }
3,588✔
65
        });
20✔
66

67
        return groupedColors;
10✔
68
    }
10✔
69

70
    //Group RGB colors into multiple groups
71
    private Rgb24? FindClosestColorGroup(Rgb24 colorToTest)
72
    {
4,997,623✔
73
        Rgb24? closestColorGroup = null;
4,997,623✔
74
        List<KeyValuePair<Rgb24, int>> results = new();
4,997,623✔
75
        foreach (Rgb24 color in ColorPalette)
526,479,687✔
76
        {
255,743,409✔
77
            int colorDifference = GetColorDifference(color, colorToTest);
255,743,409✔
78
            results.Add(new KeyValuePair<Rgb24, int>(color, colorDifference));
255,743,409✔
79
        }
255,743,409✔
80
        //Order the results and save over itself
81
        results = results.OrderBy(t => t.Value).ToList();
260,741,032✔
82

83
        //Check if the largest postive or negative value is closer
84
        if (results.Count > 0)
4,997,623✔
85
        {
4,997,623✔
86
            closestColorGroup = results[0].Key;
4,997,623✔
87
        }
4,997,623✔
88
        return closestColorGroup;
4,997,623✔
89
    }
4,997,623✔
90

91
    //Since it uses Sqrt, it always returns a postive number
92
    private static int GetColorDifference(Rgb24 color1, Rgb24 color2)
93
    {
255,743,409✔
94
        return (int)Math.Sqrt(Math.Pow(color1.R - color2.R, 2) + Math.Pow(color1.G - color2.G, 2) + Math.Pow(color1.B - color2.B, 2));
255,743,409✔
95
    }
255,743,409✔
96

97
    public static List<KeyValuePair<string, double>> BuildNamedColorsAndPercentList(Dictionary<Rgb24, List<Rgb24>> colorGroups)
98
    {
6✔
99
        List<KeyValuePair<string, double>> namePercents = new();
6✔
100
        //Calculate the name and percent and add it into a list
101
        foreach (KeyValuePair<Rgb24, List<Rgb24>> colorGroup in colorGroups)
544✔
102
        {
263✔
103
            double percent = (double)colorGroup.Value.Count / (double)colorGroups.Sum(t => t.Value.Count);
30,854✔
104
            namePercents.Add(new KeyValuePair<string, double>(ColorPalettes.ToName(colorGroup.Key), percent));
263✔
105
        }
263✔
106
        //Order the percents
107
        namePercents = namePercents.OrderByDescending(t => t.Value).ThenBy(x => x.Key).ToList();
532✔
108
        return namePercents;
6✔
109
    }
6✔
110
    
111
    public static string BuildNamedColorsAndPercentsString(Dictionary<Rgb24, List<Rgb24>> colorGroups)
112
    {
6✔
113
        //loop through dictionary and calculate percents for each key
114
        List<KeyValuePair<string, double>> namePercents = BuildNamedColorsAndPercentList(colorGroups);
6✔
115

116
        //Return the string ordered by percent
117
        StringBuilder sb = new();
6✔
118
        foreach (KeyValuePair<string, double> item in namePercents)
544✔
119
        {
263✔
120
            sb.AppendLine($"{item.Key}: {item.Value:0.00%}");
263✔
121
        }
263✔
122
        return sb.ToString();
6✔
123
    }
6✔
124

125
    //from https://docs.sixlabors.com/articles/imagesharp/pixelbuffers.html#efficient-pixel-manipulation
126
    public static Image<Rgb24> CropImage(Image<Rgb24> sourceImage, Rectangle areaToExtract)
127
    {
×
128
        Image<Rgb24> targetImage = new(areaToExtract.Width, areaToExtract.Height);
×
129
        int height = areaToExtract.Height;
×
130
        sourceImage.ProcessPixelRows(targetImage, (sourceAccessor, targetAccessor) =>
×
131
        {
×
132
            for (int row = 0; row < height; row++)
×
133
            {
×
134
                //get the row from the Y + row index
×
135
                Span<Rgb24> sourceRow = sourceAccessor.GetRowSpan(areaToExtract.Y + row);
×
136
                //Setup the target row
×
137
                Span<Rgb24> targetRow = targetAccessor.GetRowSpan(row);
×
138
                //Copy the source to the target
×
139
                //Copy the source to the target
×
140
                sourceRow.Slice(areaToExtract.X, areaToExtract.Width).CopyTo(targetRow);
×
141
            }
×
142
        });
×
143

144
        return targetImage;
×
145
    }
×
146

147
    //public static byte[] ToArray(SixLabors.ImageSharp.Image imageIn)
148
    //{
149
    //    using (MemoryStream ms = new MemoryStream())
150
    //    {
151
    //        imageIn.Save(ms, JpegFormat.Instance);
152
    //        return ms.ToArray();
153
    //    }
154
    //}
155

156
    //public static byte[] ToArray(this SixLabors.ImageSharp.Image imageIn, IImageFormat fmt)
157
    //{
158
    //    using (MemoryStream ms = new MemoryStream())
159
    //    {
160
    //        imageIn.Save(ms, fmt);
161
    //        return ms.ToArray();
162
    //    }
163
    //}
164

165

166
    //public static System.Drawing.Bitmap ToBitmap<TPixel>(this Image<TPixel> image) where TPixel : unmanaged, IPixel<TPixel>
167
    //{
168
    //    using (var memoryStream = new MemoryStream())
169
    //    {
170
    //        var imageEncoder = image.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance);
171
    //        image.Save(memoryStream, imageEncoder);
172

173
    //        memoryStream.Seek(0, SeekOrigin.Begin);
174

175
    //        return new System.Drawing.Bitmap(memoryStream);
176
    //    }
177
    //}
178

179
    public static List<Image<Rgb24>> SplitImageIntoPieces(Image<Rgb24> sourceImage, int width, int height)
180
    {
×
181
        List<Image<Rgb24>> images = new();
×
182
        for (int y = 0; y < (sourceImage.Height / height); y++)
×
183
        {
×
184
            for (int x = 0; x < (sourceImage.Width / width); x++)
×
185
            {
×
186
                Rectangle rectangle = new(x * width, y * height, width, height);
×
187
                images.Add(CropImage(sourceImage, rectangle));
×
188
            }
×
189
        }
×
190
        return images;
×
191
    }
×
192

193
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc