// Analyze iOS XCode PNG errors // Chris Lomont 2023 // todo - link blog using SixLabors.ImageSharp; // get from nuget var srcPath = @"C:\iOSTestPNGs\origImages"; // where original images are var errPath = @"C:\iOSTestPNGs\errorImages"; // error images pulled from iOS var delPath = @"C:\iOSTestPNGs\diffImages"; // save diff images here var show = true; var makeDiffs = false; var makeTiny = false; // make a set of tiny images var tinySourcename = "Glow_A.png"; // samples to grab int sampleSize = 500_000; TextWriter output = Console.Out; //TextWriter output = File.CreateText("output.csv"); var srcNames = Directory.GetFiles(srcPath,"*.png").Order().ToList(); var errNames = Directory.GetFiles(errPath, "*.png").Order().ToList(); var n = srcNames.Count; if (errNames.Count != n) throw new Exception($"Mismatched file names count {n} != {errNames.Count}"); Stats globalStats = new(); Random rand = new(); List samples = new(); int samplesInspected = 0; output.Write("Filename, width, height, pixels, error pixels, error%, red, red%, green, green%, blue, blue%,"); output.Write("largeRed,largeRed%,largeGreen,largeGreen%,largeBlue,largeBlue%,"); foreach (var key in Stats.keys) { output.Write($"{key},{key}%,"); } output.WriteLine(); int minTiny = 1000000; for (var k = 0; k < n; ++k) { // color zero or not Func C = delta => (byte)(delta == 0 ? 0 : 255); using var src = Image.Load(srcNames[k]); using var err = Image.Load(errNames[k]); var (w1, h1) = src.Frames[0].Size(); var (w2, h2) = err.Frames[0].Size(); if (w1 != w2 || h1 != h2) { throw new Exception($"Size mismatch on {srcNames[k]}"); } using var diff = makeDiffs ? new Image(w1, h1) : null; var fn = Path.GetFileName(srcNames[k]); if (makeTiny && fn.StartsWith(tinySourcename)) MakeSmallCopies(src); if (show) output.Write($"{fn},{w1} ,{h1},"); Stats fileStats = new(); for (var j = 0; j < h1; ++j) for (var i = 0; i < w1; ++i) { var srcPixel = src[i, j]; var errPixel = err[i, j]; var (r1, g1, b1) = (srcPixel.R, srcPixel.G, srcPixel.B); var (r2, g2, b2) = (errPixel.R, errPixel.G, errPixel.B); var dr = r2 - r1; // +1 if green moved up var dg = g2 - g1; // +1 if green moved up var db = b2 - b1; // +1 if blue moved up fileStats.Add(dr, dg, db); Sample(dr, dg, db, srcPixel, errPixel); if (makeDiffs) diff[i, j] = new Rgb24(C(dr), C(dg), C(db)); } if (makeDiffs) { var diffName = Path.Combine(delPath, Path.GetFileNameWithoutExtension(fn) + "_diff.bmp"); //output.WriteLine(diffName); diff.SaveAsBmp(diffName); } // find smllest with error //if (fileStats.totalErrors != 0) //{ // var area = w1 * h1; // if (area < minTiny) // { // Console.WriteLine($"Nonzero {fn}"); // minTiny = area; // } //} // weird behavior if (fileStats.totalErrors == 0) Console.WriteLine($"Zero errors: {fn}"); if (fileStats.redErrors != 0) Console.WriteLine($"Red errors: {fn} {fileStats.redErrors}"); if (show) Dump(output,fileStats); globalStats.Add(fileStats); } if (output != Console.Out) output.Close(); Console.WriteLine(); Console.WriteLine($"{n} files processed"); Dump(Console.Out,globalStats); Console.WriteLine(); // some samples - looks like only small numbers? for (int i = 0; i < 100; ++i) { var s = samples[i]; var g1 = s.src.B; var g2 = s.dst.B; //if (g1 != g2) { // Console.WriteLine($"{g1}->{g2}"); Console.WriteLine($"{s.src}->{s.dst}"); } } // save transitions using var f = File.CreateText("transitions.csv"); foreach (var s in samples) { var (r1, g1, b1) = (s.src.R, s.src.G, s.src.B); var (r2, g2, b2) = (s.dst.R, s.dst.G, s.dst.B); f.Write($"{r1},{g1},{b1},{r2},{g2},{b2},"); } f.Close(); Console.WriteLine("Done!"); // save small images, trying to find tiny error example void MakeSmallCopies(Image img) { var (w1, h1) = img.Frames[0].Size(); int sz = Math.Min(50, Math.Min(w1, h1)); for (int h2 = 1; h2 <= sz; ++h2) for (int w2 = 1; w2 <= sz; ++w2) { var d = new Image(w2,h2); for (int j = 0; j < h2; ++j) for (int i = 0; i < w2; ++i) { d[i, j] = img.Frames[0][i, j]; } var fn2 = @$"C:\iOSTestPNGs\tiny\tiny_{w2}x{h2}.png"; d.SaveAsPng(fn2); } } void Sample(int dr, int dg, int db, Rgb24 src, Rgb24 dst) { if (sampleSize == 0) return; if (dr == 0 && dg == 0 && db == 0) return; samplesInspected++; if (samples.Count errTypes = new (); // any red errors public int redErrors = 0, greenErrors = 0, blueErrors = 0; // large errors +- > 1 public int largeRedErrors = 0, largeGreenErrors = 0, largeBlueErrors = 0; // for deltas of dr,dg,db in +-1, get key string public static string Key(int dr, int dg, int db) { // -1 is lowercase, +1 is uppercase var rs = "r R"[dr + 1]; var gs = "g B"[dg + 1]; var bs = "b B"[db + 1]; string a = "" + rs + gs + bs; return a.Replace(" ", ""); } public void Add(Stats s) { totalPixels += s.totalPixels; totalErrors += s.totalErrors; redErrors += s.redErrors; greenErrors += s.greenErrors; blueErrors += s.blueErrors; largeRedErrors += s.largeRedErrors; largeGreenErrors += s.largeGreenErrors; largeBlueErrors += s.largeBlueErrors; foreach (var k in s.errTypes.Keys) { var c = s.errTypes[k]; if (!errTypes.ContainsKey(k)) errTypes.Add(k, 0); errTypes[k] += c; } } // each -1,0,+1 public void Add(int deltaR, int deltaG, int deltaB) { totalPixels++; if (deltaR == 0 && deltaG == 0 && deltaB == 0) return; // nothing more to do var lr = Large(deltaR, ref redErrors, ref largeRedErrors); var lg = Large(deltaG, ref greenErrors, ref largeGreenErrors); var lb = Large(deltaB, ref blueErrors, ref largeBlueErrors); if (lr || lg || lb) { if (largeBlueErrors + largeGreenErrors + largeRedErrors == 1) // show first only Console.WriteLine($"WARNING: Large error {deltaR} {deltaG} {deltaB}"); } if (deltaR != 0 || deltaG != 0 || deltaB != 0) totalErrors++; if (!(lr || lg || lb)) { var key = Key(deltaR, deltaG, deltaB); if (!errTypes.ContainsKey(key)) errTypes.Add(key, 0); errTypes[key]++; } static bool Large(int delta, ref int channelErrors, ref int largeChannelErrors) { if (delta != 0) channelErrors++; var largeError = Math.Abs(delta) > 1; if (largeError) largeChannelErrors++; return largeError; } } static Stats() { for (var dr = -1; dr <= 1; ++dr) for (var db = -1; db <= 1; ++db) for (var dg = -1; dg <= 1; ++dg) { var k = Key(dr, dg, db); if (k.Length > 0) { // Console.WriteLine($"Key {k}"); keys.Add(k); } } } public static List keys = new(); } class Sample { public Rgb24 src, dst; }