Example #1
0
        // 섬 데이터와 외곽선 데이터를 이용해 색칠을 자동으로 해 본다.
        // 색칠 후 이미지에 문제가 없는지 확인하기 위한 테스트 과정이다.
        static void ExecuteDetermineIslandTest(string sourceFileName, string bytesFileName)
        {
            Console.Out.WriteLine($"Running {nameof(ExecuteDetermineIslandTest)}");

            var       targetFileName = AppendToFileName(sourceFileName, "-DIT");
            StageData stageData;

            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     = UInt32ToVector2Int(island.Key);
                    var targetColor  = UInt32ToRgba32(island.Value.rgba);
                    var fillMinPoint = FloodFill.ExecuteFillIf(image, minPoint, Rgba32.White, targetColor, out var pixelArea, out _, out _);
                    if (fillMinPoint != new Vector2Int(image.Width, image.Height) && pixelArea == island.Value.pixelArea)
                    {
                    }
                    else
                    {
                        Console.WriteLine("Logic error in ExecuteDetermineIslandTest()!");
                    }
                }

                using (var stream = new FileStream(targetFileName, FileMode.Create)) {
                    image.SaveAsPng(stream);
                    stream.Close();
                }
            }
        }
Example #2
0
        // 아주 작은 비검은색을 검은색으로 메운다.
        static string ExecuteFillSmallNotBlack(string sourceFileName, int threshold = 4 * 4 * 4)
        {
            Logger.WriteLine($"Running {nameof(ExecuteFillSmallNotBlack)}");

            var targetFileName = AppendToFileName(sourceFileName, "-FSNB");
            // Min Point 별 (섬 별) 섬 픽셀 수(면적)
            var islandPixelAreaByMinPoint = new Dictionary <Vector2Int, int>();

            using (var image = Image.Load <Rgba32>(sourceFileName))
            {
                // 각 픽셀에 대해서 반복한다.
                for (var h = 0; h < image.Height; h++)
                {
                    for (var w = 0; w < image.Width; w++)
                    {
                        var pixelColor = image[w, h];
                        if (pixelColor == Black)
                        {
                            // 경계선 색상(검은색)이면 할 일이 없다.
                        }
                        else
                        {
                            // (w, h) 좌표부터 검은색이 아닌 색을 검은색으로 채우면서 픽셀 수집한다.
                            // 수집한 모든 픽셀은 points에, points의 min point는 반환값으로 받는다.
                            var coord        = new Vector2Int(w, h);
                            var fillMinPoint = FloodFill.ExecuteFillIfNotBlack(image, coord, Black, out var pixelArea,
                                                                               out _, out _);
                            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, Black, out var pixelArea,
                                                                           out _, out _);
                        if (fillMinPoint != new Vector2Int(image.Width, image.Height) && pixelArea == island.Value)
                        {
                        }
                        else
                        {
                            Logger.WriteErrorLine("Logic error in ExecuteFillSmallNotBlack()!");
                        }
                    }
                }

                // 그리고 저장!
                var targetDirName = Path.GetDirectoryName(targetFileName);
                if (string.IsNullOrEmpty(targetDirName))
                {
                    return(string.Empty);
                }

                Directory.CreateDirectory(targetDirName);
                using (var stream = new FileStream(targetFileName, FileMode.Create))
                {
                    image.SaveAsPng(stream);
                    stream.Close();
                }
            }

            return(targetFileName);
        }
Example #3
0
        // 입력 이미지로 섬 데이터를 만든다.
        // 섬 데이터는 유니티에서 사용하게 된다.
        static string ExecuteDetermineIsland(string sourceFileName, string startFileName)
        {
            Logger.WriteLine($"Running {nameof(ExecuteDetermineIsland)}");

            // 이미지 파일을 열어봅시다~
            using (var image = Image.Load <Rgba32>(sourceFileName))
            {
                // 색상 별 픽셀 수
                var pixelCountByColor = new Dictionary <Rgba32, int>();
                // Min Point 별 (섬 별) 섬 색상
                var islandColorByMinPoint = new Dictionary <Vector2Int, Rgba32>();
                // Min Point 별 (섬 별) 섬 픽셀 수(면적)
                var islandPixelAreaByMinPoint = new Dictionary <Vector2Int, int>();
                // 색상 별 섬 수
                var islandCountByColor = new Dictionary <Rgba32, int>();
                // 픽셀 수(면적) 별 섬 수
                var islandCountByPixelArea = new Dictionary <int, int>();
                // Min Point 별 (섬 별) Max Rect
                var maxRectByMinPoint = new Dictionary <uint, ulong>();

                // 각 픽셀에 대해서 반복한다.
                for (var h = 0; h < image.Height; h++)
                {
                    for (var w = 0; w < image.Width; w++)
                    {
                        var pixelColor = image[w, h];

                        if (pixelColor == Black)
                        {
                            // 경계선 색상(검은색)이면 할 일이 없다.
                        }
                        else
                        {
                            // (w, h) 좌표부터 검은색이 아닌 색을 검은색으로 채우면서 픽셀 수집한다.
                            // 수집한 모든 픽셀은 points에, points의 min point는 반환값으로 받는다.
                            var coord        = new Vector2Int(w, h);
                            var fillMinPoint = FloodFill.ExecuteFillIfNotBlack(image, coord, 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)
                                                         .First(e => e.Key != White).Key;

                                    pixelColor = prominentColor;

                                    // foreach (var originalColor in originalColors) {
                                    //     Logger.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 == White)
                                {
                                    throw new Exception("Island color is WHITE?! Fix it!");
                                }

                                IncreaseCountOfDictionaryValue(pixelCountByColor, pixelColor);

                                islandColorByMinPoint[fillMinPoint]     = pixelColor;
                                islandPixelAreaByMinPoint[fillMinPoint] = pixelArea;
                                IncreaseCountOfDictionaryValue(islandCountByPixelArea, pixelArea);
                                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);
                                Logger.WriteLine(
                                    $"Sub Rect: area:{area} [({yMin + beginIndexR},{xMin + beginIndexC})-({yMin + endIndexR},{xMin + endIndexC})]");
                                maxRectByMinPoint[Vector2IntToUInt32(fillMinPoint)] = GetRectRange(
                                    xMin + beginIndexC,
                                    yMin + beginIndexR,
                                    xMin + endIndexC,
                                    yMin + endIndexR);
                            }
                            else
                            {
                                throw new Exception("Invalid fill min point!");
                            }
                        }
                    }
                }

                Logger.WriteLine($"Total Pixel Count: {image.Width * image.Height}");
                pixelCountByColor.TryGetValue(White, out var whiteCount);
                Logger.WriteLine($"White Count: {whiteCount}");

                if (islandColorByMinPoint.Count < 1)
                {
                    throw new IslandCountException();
                }

                var islandIndex = 1; // 0번째 island는 외곽선을 위해 예비한 값이다.
                foreach (var kv in islandColorByMinPoint.OrderBy(kv => Vector2IntToUInt32(kv.Key)))
                {
                    Logger.WriteLine(
                        $"Island #{islandIndex} fillMinPoint={kv.Key}, color={kv.Value}, area={islandPixelAreaByMinPoint[kv.Key]}");
                    islandIndex++;
                }

                var colorCountIndex = 1; // 0 번째 컬러는 외곽선을 위해 예비한 블랙 값이다.
                foreach (var kv in pixelCountByColor)
                {
                    islandCountByColor.TryGetValue(kv.Key, out var islandCount);
                    Logger.WriteLine(
                        $"Color #{colorCountIndex} {kv.Key}: pixelCount={kv.Value}, islandCount={islandCount}");
                    colorCountIndex++;

                    if (kv.Key == White)
                    {
                        throw new Exception("Palette color should not be white!");
                    }
                }

                var pixelAreaCountIndex = 0;
                foreach (var kv in islandCountByPixelArea.OrderByDescending(kv => kv.Key))
                {
                    Logger.WriteLine($"Pixel Area #{pixelAreaCountIndex} {kv.Key}: islandCount={kv.Value}");
                    pixelAreaCountIndex++;
                }

                var stageData    = new StageData();
                var islandIndex2 = 1;
                foreach (var kv in islandPixelAreaByMinPoint.OrderBy(kv => Vector2IntToUInt32(kv.Key)))
                {
                    var p = Vector2IntToUInt32(kv.Key);
                    stageData.islandDataByMinPoint[p] = new IslandData
                    {
                        index     = islandIndex2,
                        pixelArea = islandPixelAreaByMinPoint[kv.Key],
                        rgba      = Rgba32ToUInt32(islandColorByMinPoint[kv.Key]),
                        maxRect   = maxRectByMinPoint[p]
                    };
                    islandIndex2++;
                }

                var outputPath = Path.ChangeExtension(startFileName, "bytes");
                if (string.IsNullOrEmpty(outputPathReplaceFrom) == false &&
                    string.IsNullOrEmpty(outputPathReplaceTo) == false)
                {
                    outputPath = outputPath.Replace(outputPathReplaceFrom, outputPathReplaceTo);
                }

                using (var stream = File.Create(outputPath))
                {
                    var formatter = new BinaryFormatter();
                    formatter.Serialize(stream, stageData);
                    stream.Close();
                }

                Logger.WriteLine($"{stageData.islandDataByMinPoint.Count} islands loaded.");
                Logger.WriteLine($"Written to {outputPath}");
                return(outputPath);
            }
        }
Example #4
0
        // 섬 데이터와 외곽선 데이터를 이용해 색칠을 자동으로 해 본다.
        // 색칠 후 이미지에 문제가 없는지 확인하기 위한 테스트 과정이다.
        static string ExecuteDetermineIslandTest(string sourceFileName, string bytesFileName, bool errorAsWarning, bool writeA1A2Tex)
        {
            Logger.WriteLine($"Running {nameof(ExecuteDetermineIslandTest)}");

            var       targetFileName = AppendToFileName(sourceFileName, "-DIT");
            var       a1TexFileName  = AppendToFileName(targetFileName, "-A1");
            var       a2TexFileName  = AppendToFileName(targetFileName, "-A2");
            StageData stageData;

            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)
                {
                    Logger.WriteErrorLine("Failed to deserialize. Reason: " + e.Message);
                    throw;
                }
            }

            var colorUintArray = stageData.CreateColorUintArray();
            var colorUintDict  = new Dictionary <uint, int>();

            for (var i = 0; i < colorUintArray.Length; i++)
            {
                colorUintDict[colorUintArray[i]] = i + 1; // Palette Index 0은 외곽선 용으로 예비한다.
            }

            using (var image = Image.Load <Rgba32>(sourceFileName))
            {
                var a1Tex = new Image <Rgba32>(image.Width, image.Height, AllZeros);
                var a2Tex = new Image <Rgba32>(image.Width, image.Height, AllZeros);

                var islandIndex = 1; // Island Index 0은 외곽선 용으로 예비한다.

                // dictionary의 이터레이션 순서에 의존하면 안되고, island data에서 지정한 index 순서로 하자.
                foreach (var island in stageData.islandDataByMinPoint.OrderBy(e => e.Value.index))
                {
                    var minPoint     = UInt32ToVector2Int(island.Key);
                    var targetColor  = UInt32ToRgba32(island.Value.rgba);
                    var paletteIndex = colorUintDict[island.Value.rgba];
                    var fillMinPoint = FloodFill.ExecuteFillIf(image, minPoint, White, targetColor, out var pixelArea,
                                                               out _, out _, islandIndex, (islandIndexCallback, fx, fy) =>
                    {
                        GetAlpha8Pair(islandIndexCallback, paletteIndex, out var a1, out var a2);
                        a1Tex[fx, fy] = new Rgba32 {
                            A = a1
                        };
                        a2Tex[fx, fy] = new Rgba32 {
                            A = a2
                        };
                    });

                    if (fillMinPoint == new Vector2Int(image.Width, image.Height))
                    {
                        if (errorAsWarning)
                        {
                            // 이번엔 오류로 안친다.
                            Logger.WriteLine("Logic error in ExecuteDetermineIslandTest()! Invalid fillMinPoint");
                        }
                        else
                        {
                            Logger.WriteErrorLine("Logic error in ExecuteDetermineIslandTest()! Invalid fillMinPoint");
                        }
                    }

                    if (pixelArea != island.Value.pixelArea)
                    {
                        if (errorAsWarning)
                        {
                            // 이번엔 오류로 안친다.
                            Logger.WriteLine(
                                $"Logic error in ExecuteDetermineIslandTest()! Pixel area {pixelArea} expected to be {island.Value.pixelArea}");
                        }
                        else
                        {
                            Logger.WriteErrorLine(
                                $"Logic error in ExecuteDetermineIslandTest()! Pixel area {pixelArea} expected to be {island.Value.pixelArea}");
                        }
                    }

                    islandIndex++;
                }

                using (var stream = new FileStream(targetFileName, FileMode.Create))
                {
                    image.SaveAsPng(stream);
                    stream.Close();
                }

                if (writeA1A2Tex)
                {
                    using (var stream = new FileStream(a1TexFileName, FileMode.Create))
                    {
                        a1Tex.SaveAsPng(stream);
                        stream.Close();
                    }

                    using (var stream = new FileStream(a2TexFileName, FileMode.Create))
                    {
                        a2Tex.SaveAsPng(stream);
                        stream.Close();
                    }
                }
            }

            return(targetFileName);
        }