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

samsmithnz / PuzzleSolver / 3962091840

pending completion
3962091840

Pull #20

github

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

300 of 348 branches covered (86.21%)

Branch coverage included in aggregate %.

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

435 of 532 relevant lines covered (81.77%)

5070400.95 hits per line

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

68.29
/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, bool onlyShowTop3 = false)
98
    {
6✔
99
        List<KeyValuePair<string, double>> namePercents = new();
6✔
100
        //Calculate the name and percent and add it into a list
101
        int count = 0;
6✔
102
        double totalOtherPercent = 0;
6✔
103
        foreach (KeyValuePair<Rgb24, List<Rgb24>> colorGroup in colorGroups)
544✔
104
        {
263✔
105
            count++;
263✔
106
            double percent = (double)colorGroup.Value.Count / (double)colorGroups.Sum(t => t.Value.Count);
30,854✔
107
            if (onlyShowTop3 == true && count > 2)
263!
108
            {
×
109
                totalOtherPercent += percent;
×
110
            }
×
111
            else
112
            {
263✔
113
                namePercents.Add(new KeyValuePair<string, double>(ColorPalettes.ToName(colorGroup.Key), percent));
263✔
114
            }
263✔
115
        }
263✔
116
        //Order the percents
117
        namePercents = namePercents.OrderByDescending(t => t.Value).ThenBy(x => x.Key).ToList();
532✔
118
        if (onlyShowTop3 == true)
6!
119
        {
×
120
            namePercents.Add(new KeyValuePair<string, double>("Other", totalOtherPercent));
×
121
        }
×
122
        return namePercents;
6✔
123
    }
6✔
124

125
    public static string BuildNamedColorsAndPercentsString(Dictionary<Rgb24, List<Rgb24>> colorGroups)
126
    {
6✔
127
        //loop through dictionary and calculate percents for each key
128
        List<KeyValuePair<string, double>> namePercents = BuildNamedColorsAndPercentList(colorGroups);
6✔
129

130
        //Return the string ordered by percent
131
        StringBuilder sb = new();
6✔
132
        foreach (KeyValuePair<string, double> item in namePercents)
544✔
133
        {
263✔
134
            sb.AppendLine($"{item.Key}: {item.Value:0.00%}");
263✔
135
        }
263✔
136
        return sb.ToString();
6✔
137
    }
6✔
138

139
    //from https://docs.sixlabors.com/articles/imagesharp/pixelbuffers.html#efficient-pixel-manipulation
140
    public static Image<Rgb24> CropImage(Image<Rgb24> sourceImage, Rectangle areaToExtract)
141
    {
×
142
        Image<Rgb24> targetImage = new(areaToExtract.Width, areaToExtract.Height);
×
143
        int height = areaToExtract.Height;
×
144
        sourceImage.ProcessPixelRows(targetImage, (sourceAccessor, targetAccessor) =>
×
145
        {
×
146
            for (int row = 0; row < height; row++)
×
147
            {
×
148
                //get the row from the Y + row index
×
149
                Span<Rgb24> sourceRow = sourceAccessor.GetRowSpan(areaToExtract.Y + row);
×
150
                //Setup the target row
×
151
                Span<Rgb24> targetRow = targetAccessor.GetRowSpan(row);
×
152
                //Copy the source to the target
×
153
                //Copy the source to the target
×
154
                sourceRow.Slice(areaToExtract.X, areaToExtract.Width).CopyTo(targetRow);
×
155
            }
×
156
        });
×
157

158
        return targetImage;
×
159
    }
×
160

161
    //public static byte[] ToArray(SixLabors.ImageSharp.Image imageIn)
162
    //{
163
    //    using (MemoryStream ms = new MemoryStream())
164
    //    {
165
    //        imageIn.Save(ms, JpegFormat.Instance);
166
    //        return ms.ToArray();
167
    //    }
168
    //}
169

170
    //public static byte[] ToArray(this SixLabors.ImageSharp.Image imageIn, IImageFormat fmt)
171
    //{
172
    //    using (MemoryStream ms = new MemoryStream())
173
    //    {
174
    //        imageIn.Save(ms, fmt);
175
    //        return ms.ToArray();
176
    //    }
177
    //}
178

179

180
    //public static System.Drawing.Bitmap ToBitmap<TPixel>(this Image<TPixel> image) where TPixel : unmanaged, IPixel<TPixel>
181
    //{
182
    //    using (var memoryStream = new MemoryStream())
183
    //    {
184
    //        var imageEncoder = image.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance);
185
    //        image.Save(memoryStream, imageEncoder);
186

187
    //        memoryStream.Seek(0, SeekOrigin.Begin);
188

189
    //        return new System.Drawing.Bitmap(memoryStream);
190
    //    }
191
    //}
192

193
    public static List<Image<Rgb24>> SplitImageIntoPieces(Image<Rgb24> sourceImage, int width, int height)
194
    {
×
195
        List<Image<Rgb24>> images = new();
×
196
        for (int y = 0; y < (sourceImage.Height / height); y++)
×
197
        {
×
198
            for (int x = 0; x < (sourceImage.Width / width); x++)
×
199
            {
×
200
                Rectangle rectangle = new(x * width, y * height, width, height);
×
201
                images.Add(CropImage(sourceImage, rectangle));
×
202
            }
×
203
        }
×
204
        return images;
×
205
    }
×
206

207
}
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