// 섬 데이터와 외곽선 데이터를 이용해 색칠을 자동으로 해 본다. // 색칠 후 이미지에 문제가 없는지 확인하기 위한 테스트 과정이다. private string ExecuteDetermineIslandTest(string sourceFileName, string bytesFileName) { var targetFileName = AppendToFileName(sourceFileName, "-DIT"); StageData stageData = null; using (var bytesFileStream = new FileStream(bytesFileName, FileMode.Open)) { var formatter = new BinaryFormatter(); try { // Deserialize the hashtable from the file and // assign the reference to the local variable. stageData = (StageData)formatter.Deserialize(bytesFileStream); } catch (SerializationException e) { Console.WriteLine("Failed to deserialize. Reason: " + e.Message); throw; } } using (var image = Image.Load <Rgba32>(sourceFileName)) { foreach (var island in stageData.islandDataByMinPoint) { var minPoint = Utils.UInt32ToVector2Int(island.Key); var targetColor = Utils.UInt32ToRgba32(island.Value.rgba); var fillMinPoint = FloodFill.ExecuteFillIf(image, minPoint, Rgba32.White, targetColor, out var pixelArea, out var points, out var originalColors); if (fillMinPoint != new Vector2Int(image.Width, image.Height) && pixelArea == island.Value.pixelArea) { } else { Console.WriteLine("Logic Error!"); } } using (var stream = new FileStream(targetFileName, FileMode.Create)) { image.SaveAsPng(stream); stream.Close(); } } return(targetFileName); }
// 입력 이미지로 섬 데이터를 만든다. // 섬 데이터는 유니티에서 사용하게 된다. private string ExecuteDetermineIsland(string sourceFileName, string startFileName) { // 이미지 파일을 열어봅시다~ using (var image = Image.Load <Rgba32>(sourceFileName)) { // 색상 별 픽셀 수 Dictionary <Rgba32, int> pixelCountByColor = new Dictionary <Rgba32, int>(); // Min Point 별 (섬 별) 섬 색상 Dictionary <Vector2Int, Rgba32> islandColorByMinPoint = new Dictionary <Vector2Int, Rgba32>(); // Min Point 별 (섬 별) 섬 픽셀 수(면적) Dictionary <Vector2Int, int> islandPixelAreaByMinPoint = new Dictionary <Vector2Int, int>(); // 색상 별 섬 수 Dictionary <Rgba32, int> islandCountByColor = new Dictionary <Rgba32, int>(); // 픽셀 수(면적) 별 섬 수 Dictionary <int, int> islandCountByPixelArea = new Dictionary <int, int>(); // Min Point 별 (섬 별) Max Rect Dictionary <uint, ulong> maxRectByMinPoint = new Dictionary <uint, ulong>(); // 각 픽셀에 대해서 반복한다. for (int h = 0; h < image.Height; h++) { for (int w = 0; w < image.Width; w++) { var pixelColor = image[w, h]; if (pixelColor == Rgba32.Black) { // 경계선 색상(검은색)이면 할 일이 없다. } else { // (w, h) 좌표부터 검은색이 아닌 색을 검은색으로 채우면서 픽셀 수집한다. // 수집한 모든 픽셀은 points에, points의 min point는 반환값으로 받는다. var coord = new Vector2Int(w, h); var fillMinPoint = FloodFill.ExecuteFillIfNotBlack(image, coord, Rgba32.Black, out var pixelArea, out var points, out var originalColors); if (fillMinPoint != new Vector2Int(image.Width, image.Height)) { if (originalColors.Count > 1) { // 한 섬에 색상이 여러 가지라면 가장 많은 색상이 최종 색깔이 되도록 하자. // 주로 경계선 주변에서 경계선과 섬 색깔이 블렌딩되면서 다른 색깔이 되는 패턴이다. //var prominentColor = originalColors.Aggregate((l, r) => l.Value > r.Value ? l : r).Key; var prominentColor = originalColors.OrderByDescending(e => e.Value).Where(e => e.Key != Rgba32.White).First().Key; pixelColor = prominentColor; // foreach (var originalColor in originalColors) { // Console.WriteLine($"{originalColor.Key} = {originalColor.Value}"); // } // throw new Exception($"Island color is not uniform! It has {originalColors.Count} colors in it! coord={coord}"); } if (originalColors.Count == 0) { throw new Exception($"Island color is empty. Is this possible?"); } if (pixelColor == Rgba32.White) { throw new Exception($"Island color is WHITE?! Fix it!"); } Utils.IncreaseCountOfDictionaryValue(pixelCountByColor, pixelColor); islandColorByMinPoint[fillMinPoint] = pixelColor; islandPixelAreaByMinPoint[fillMinPoint] = pixelArea; Utils.IncreaseCountOfDictionaryValue(islandCountByPixelArea, pixelArea); Utils.IncreaseCountOfDictionaryValue(islandCountByColor, pixelColor); var xMax = points.Max(e => e.x); var xMin = points.Min(e => e.x); var yMax = points.Max(e => e.y); var yMin = points.Min(e => e.y); var subRectW = xMax - xMin + 1; var subRectH = yMax - yMin + 1; var A = Enumerable.Range(0, subRectH).Select(e => new int[subRectW]).ToArray(); foreach (var point in points) { A[point.y - yMin][point.x - xMin] = 1; } var area = MaxSubRect.MaxRectangle(subRectH, subRectW, A, out var beginIndexR, out var endIndexR, out var beginIndexC, out var endIndexC); Console.WriteLine($"Sub Rect: area:{area} [({yMin + beginIndexR},{xMin + beginIndexC})-({yMin + endIndexR},{xMin + endIndexC})]"); maxRectByMinPoint[Utils.Vector2IntToUInt32(fillMinPoint)] = Utils.GetRectRange( xMin + beginIndexC, yMin + beginIndexR, xMin + endIndexC, yMin + endIndexR); } else { throw new Exception("Invalid fill min point!"); } } } } Console.WriteLine($"Total Pixel Count: {image.Width * image.Height}"); pixelCountByColor.TryGetValue(Rgba32.White, out var whiteCount); Console.WriteLine($"White Count: {whiteCount}"); var islandIndex = 0; foreach (var kv in islandColorByMinPoint) { Console.WriteLine($"Island #{islandIndex} fillMinPoint={kv.Key}, color={kv.Value}, area={islandPixelAreaByMinPoint[kv.Key]}"); islandIndex++; } var colorCountIndex = 0; foreach (var kv in pixelCountByColor) { islandCountByColor.TryGetValue(kv.Key, out var islandCount); Console.WriteLine($"Color #{colorCountIndex} {kv.Key}: pixelCount={kv.Value}, islandCount={islandCount}"); colorCountIndex++; if (kv.Key == Rgba32.White) { throw new Exception("Palette color should not be white!"); } } var pixelAreaCountIndex = 0; foreach (var kv in islandCountByPixelArea.OrderByDescending(kv => kv.Key)) { Console.WriteLine($"Pixel Area #{pixelAreaCountIndex} {kv.Key}: islandCount={kv.Value}"); pixelAreaCountIndex++; } Dictionary <uint, uint> islandColorByMinPointPrimitive = new Dictionary <uint, uint>(); foreach (var kv in islandColorByMinPoint) { var p = Utils.Vector2IntToUInt32(kv.Key); var c = Utils.Rgba32ToUInt32(kv.Value); islandColorByMinPointPrimitive[p] = c; } var stageData = new StageData(); foreach (var kv in islandPixelAreaByMinPoint) { var p = Utils.Vector2IntToUInt32(kv.Key); stageData.islandDataByMinPoint[p] = new IslandData { pixelArea = islandPixelAreaByMinPoint[kv.Key], rgba = Utils.Rgba32ToUInt32(islandColorByMinPoint[kv.Key]), maxRect = maxRectByMinPoint[p], }; } var outputPath = Path.ChangeExtension(startFileName, "bytes").Replace(OutputPathReplaceFrom, OutputPathReplaceTo); using (var stream = File.Create(outputPath)) { var formatter = new BinaryFormatter(); formatter.Serialize(stream, stageData); stream.Close(); } Console.WriteLine($"{stageData.islandDataByMinPoint.Count} islands loaded."); Console.WriteLine($"Written to {outputPath}"); return(outputPath); } }
// 아주 작은 비검은색을 검은색으로 메운다. private string ExecuteFillSmallNotBlack(string sourceFileName, int threshold = 4 * 4 * 4) { var targetFileName = AppendToFileName(sourceFileName, "-FSNB"); // Min Point 별 (섬 별) 섬 픽셀 수(면적) Dictionary <Vector2Int, int> islandPixelAreaByMinPoint = new Dictionary <Vector2Int, int>(); using (var image = Image.Load <Rgba32>(sourceFileName)) { // 각 픽셀에 대해서 반복한다. for (int h = 0; h < image.Height; h++) { for (int w = 0; w < image.Width; w++) { var pixelColor = image[w, h]; if (pixelColor == Rgba32.Black) { // 경계선 색상(검은색)이면 할 일이 없다. } else { // (w, h) 좌표부터 검은색이 아닌 색을 검은색으로 채우면서 픽셀 수집한다. // 수집한 모든 픽셀은 points에, points의 min point는 반환값으로 받는다. var coord = new Vector2Int(w, h); var fillMinPoint = FloodFill.ExecuteFillIfNotBlack(image, coord, Rgba32.Black, out var pixelArea, out var points, out var originalColors); if (fillMinPoint != new Vector2Int(image.Width, image.Height)) { islandPixelAreaByMinPoint[fillMinPoint] = pixelArea; } else { throw new Exception("Invalid fill min point!"); } } } } } // 메모리상 image 변수는 직전 과정에서 변경되었으므로, 다시 읽어들이자. // 여기서부터 본격적으로 작은 비검정색칸을 검정색칸으로 채운다. using (var image = Image.Load <Rgba32>(sourceFileName)) { foreach (var island in islandPixelAreaByMinPoint) { if (island.Value < threshold) { var fillMinPoint = FloodFill.ExecuteFillIfNotBlack(image, island.Key, Rgba32.Black, out var pixelArea, out var points, out var originalColors); if (fillMinPoint != new Vector2Int(image.Width, image.Height) && pixelArea == island.Value) { } else { Console.WriteLine("Logic Error!"); } } } // 그리고 저장! Directory.CreateDirectory(Path.GetDirectoryName(targetFileName)); using (var stream = new FileStream(targetFileName, FileMode.Create)) { image.SaveAsPng(stream); stream.Close(); } } return(targetFileName); }