Example #1
0
    /// <returns>(Overall areas, Unique areas)</returns>
    private (int total, int unique) getUniqueAreas(MyVector2 areaSizing, int spriteIndex, ConcurrentDictionary <string, MyArea> areas, IAreaEnumerator areaEnumerator, ProgressReport progressReport)
    {
        var areasTotal  = 0;
        var areasUnique = 0;

        areaEnumerator.EnumerateThroughSprite(areaSizing, spriteIndex, (sprite, index, x, y) =>
        {
            if (!_mapOfEmptiness.Contains(areaSizing, index, x, y))
            {
                var area = MyArea.CreateFromSprite(sprite, spriteIndex, x, y, areaSizing);
                var hash = area.UniqueString;
                if (areas.TryAdd(hash, area))
                {
                    areasUnique++;
                }
                area = areas[hash];

                area.Correlations.TryAdd(area.Correlations.Count, new MyAreaCoordinates(index, x, y, areaSizing.X, areaSizing.Y));

                areasTotal++;
            }
        });
        progressReport.OperationsDone++;
        return(areasTotal, areasUnique);
    }
    private static void getUniqueAreas(MyVector2 areaResolution, MyColor[][] sprite, int spriteIndex, ConcurrentDictionary <int, MyArea> areas, ConcurrentDictionary <int, long> areaDirtyScores, bool[][] mapOfEmptiness)
    {
        var areaSquare = areaResolution.X * areaResolution.Y;

        for (int x = 0; x < sprite.Length - areaResolution.X; x++)
        {
            for (int y = 0; y < sprite[x].Length - areaResolution.Y; y++)
            {
                ProcessedAreas++;
                if (mapOfEmptiness[x][y])
                {
                    continue;
                }
                var area = MyArea.CreateFromSprite(sprite, spriteIndex, x, y, areaResolution);
                var hash = area.GetHashCode();
                if (areas.TryAdd(hash, area))
                {
                    UniqueAreas++;
                }

                var dirtyScore = (int)(Mathf.Pow(area.OpaquePixelsCount, 3f) / areaSquare);
                areaDirtyScores.AddOrUpdate(hash, dirtyScore, (key, existingValue) => existingValue + dirtyScore);
            }
        }
        CurrentOp++;
    }
Example #3
0
 internal async Task Initialize(IEnumerable <MyVector2> areaSizings, MyColor[][][] sprites, IAreaEnumerator enumerator)
 {
     var sizingsList = areaSizings.ToList();
     await Task.Run(() =>
     {
         Parallel.For(0, sizingsList.Count, i =>
         {
             var sizing        = sizingsList[i];
             var newSizingDict = new ConcurrentDictionary <int, List <int> >();
             _mapOfEmptiness.AddOrUpdate(sizing, newSizingDict, (_, __) => newSizingDict);
             Parallel.For(0, sprites.Length, j =>
             {
                 var newSpriteDict = new List <int>();
                 newSizingDict.AddOrUpdate(j, newSpriteDict, (_, __) => newSpriteDict);
                 enumerator.EnumerateThroughSprite(sizing, j, (sprite, spriteIndex, x, y) =>
                 {
                     var area = MyArea.CreateFromSprite(sprite, spriteIndex, x, y, sizing);
                     if (area.OpaquePixelsCount == 0)
                     {
                         newSpriteDict.Add(x + y *_yMultiplier);
                     }
                 });
             });
         });
     });
 }
Example #4
0
    private static void getUniqueAreas(MyVector2 areaResolution, int spriteIndex, MyColor[][] sprite, ConcurrentDictionary <string, MyArea> areas, bool[][] mapOfEmptiness, ProgressReport progressReport)
    {
        var areaSquare = areaResolution.X * areaResolution.Y;

        for (int x = 0; x < sprite.Length - areaResolution.X; x++)
        {
            for (int y = 0; y < sprite[x].Length - areaResolution.Y; y++)
            {
                if (mapOfEmptiness[x][y])
                {
                    continue;
                }
                var area = MyArea.CreateFromSprite(sprite, spriteIndex, x, y, areaResolution);
                if (area.Score == 0)
                {
                    continue;
                }
                //var hash = area.GetHashCode();
                var hash = area.UniqueString;
                areas.TryAdd(hash, area);
            }
        }
        progressReport.OperationsDone++;
    }
    private static Chunk[][] goGetEm(MyVector2 area, MyColor[][][] sprites /*, out Texture optimizedSpritesheet*/)
    {
        //optimizedSpritesheet = new Texture2D(1, 1);
        //Debug.Log("goGetEm . ......");
        int pixelsTotal;
        int opaquePixelsTotal;

        countOpaquePixels(sprites, out pixelsTotal, out opaquePixelsTotal);

        UnoptimizedPixelsCount = opaquePixelsTotal;
        //Debug.Log($"pixelsTotal = {pixelsTotal}, opaquePixelsTotal = {opaquePixelsTotal}");

        var areaVariants = getAreaVariants(area);
        //Debug.Log("Area variants:");
        //for (int i = 0; i < areaVariants.Length; i++)
        //    Debug.Log($"    {areaVariants[i].ToString()}");

        //var areas = new Dictionary<int, MyArea>();
        //for (int i = 0; i < areaVariants.Length; i++)
        //{
        //    CurrentVariant++;
        //    CurrentSprite = 0;
        //    for (int j = 0; j < sprites.Length; j++)
        //    {
        //        CurrentSprite++;
        //        getUniqueAreas(areaVariants[i], sprites[j], areas);
        //    }
        //}

        var mapsOfEmptiness = new Dictionary <MyVector2, bool[][][]>();

        for (int i = 0; i < areaVariants.Length; i++)
        {
            var currentAreaVariant    = areaVariants[i];
            var currentMapOfEmptiness = new bool[sprites.Length][][];
            for (int j = 0; j < sprites.Length; j++)
            {
                var sprite = sprites[j];
                var spriteMapOfEmptiness = new bool[sprite.Length - currentAreaVariant.X][];
                for (int x = 0; x < sprite.Length - currentAreaVariant.X; x++)
                {
                    spriteMapOfEmptiness[x] = new bool[sprite[x].Length - currentAreaVariant.Y];
                    for (int y = 0; y < sprite[x].Length - currentAreaVariant.Y; y++)
                    {
                        spriteMapOfEmptiness[x][y] = !MyArea.ContainsOpaquePixels(sprite, x, y, currentAreaVariant);
                    }
                }
                currentMapOfEmptiness[j] = spriteMapOfEmptiness;
            }
            mapsOfEmptiness.Add(currentAreaVariant, currentMapOfEmptiness);
        }

        var map = new Dictionary <MyArea, List <(int, int, int)> >();

        var sw = new Stopwatch();

        while (UnoptimizedPixelsCount > 0)
        {
            sw.Reset();
            sw.Start();
            ProcessedAreas = 0;
            UniqueAreas    = 0;

            var areas           = new ConcurrentDictionary <int, MyArea>(); //Тут мы храним все уникальные области по их хешам
            var areaDirtyScores = new ConcurrentDictionary <int, long>();   //А тут - их счет по их хешам
            //Dirty - потому что мы не удаляем пиксели по-настоящему, так что счет может быть выше чем на самом деле из-за повторов.
            //Так что это рассчет грубый и неточный, но пойдет для первичного отсева.

            //Сейчас мы идем заполнять эти два словаря, и это можно делать параллельно - слава TPL
            var overallOpsCount = areaVariants.Length * sprites.Length;

            CurrentOpsTotal = overallOpsCount;
            CurrentOp       = 0;

            try
            {
                Parallel.For(0, overallOpsCount, (int index, ParallelLoopState state) =>
                {
                    if (state.IsExceptional)
                    {
                        Debug.Log("Exception!");
                    }
                    var areaVariantIndex = Mathf.FloorToInt(index / sprites.Length);
                    var spriteIndex      = index - areaVariantIndex * sprites.Length;
                    var targetArea       = areaVariants[areaVariantIndex];
                    var mapOfEmptinessForAreaAndSprite = mapsOfEmptiness[targetArea][spriteIndex];
                    getUniqueAreas(targetArea, sprites[spriteIndex], spriteIndex, areas, areaDirtyScores, mapOfEmptinessForAreaAndSprite);
                });
            }
            catch (AggregateException ae)
            {
                Debug.Log("catch");
                ae.Handle((inner) =>
                {
                    Debug.Log(inner.Message);
                    return(true);
                });
            }

            Debug.Log($"unique areas count = {areas.Count}");

            /*
             * Итак, что мы тут делаем. Мы имеем грязный словарь, с примерными значениями полезности. Что мы должны сделать в идеальном мире -
             * - пройтись с каждым из нескольких миллионов областей по потенциально многомиллионопиксельному спрайту и проанализировать каждый в плане
             * полезности. И сделать это надо для каждого шага. Ясно, что сложность тут нереальная. Поэтому, я думаю, надо иметь некий буфер, выборку
             * возможных кандитатов, например, 100. Берем 100 самых достойных областей (первые 100 из грязного словаря), проходимся с ними по спрайтам,
             * и смотрим какой из них удаляет больше всего пикселей. Его и забираем и действительно удаляем все пиксели с ним. У нас осталось на 1 область
             * грязного списка меньше. Повторять покуда остались пиксели.
             */

            var orderedDirtyKvpArray = areaDirtyScores.ToArray().OrderByDescending(kvp => kvp.Value).ToArray();
            Debug.Log($"The winner is {orderedDirtyKvpArray[0].Key} with highest score of {orderedDirtyKvpArray[0].Value}!");
            for (int i = 0; i < _bestOfTheDirtyBufferSize; i++)
            {
                Debug.Log($"    {i + 1}. {orderedDirtyKvpArray[i].Key}. Score: {orderedDirtyKvpArray[i].Value}");
            }


            //var imageCopy = CopyArrayOfColors(sprites);
            //var same = true;
            //for (int i = 0; i < imageCopy.Length; i++)
            //{
            //    for (int j = 0; j < imageCopy[i].Length; j++)
            //    {
            //        for (int m = 0; m < imageCopy[i][j].Length; m++)
            //        {
            //            if (imageCopy[i][j][m].GetHashCode() != sprites[i][j][m].GetHashCode())
            //                same = false;
            //        }
            //    }
            //}

            //if (same)
            //    Debug.Log($"Clone of image is same"); //Same!
            //else
            //    Debug.Log($"Clone of image isn't the same");

            /*
             * Ок, дальше мы идем делать чистую проверку самых вероятных победителей - с пробным удалением пикселей.
             */

            CurrentOpsTotal = _bestOfTheDirtyBufferSize;
            CurrentOp       = 0;

            var partialCleanScore = new ConcurrentDictionary <int, long>(); //Это и есть словарь с чистым счетом - при пробном удалении пикселей

            Parallel.For(0, _bestOfTheDirtyBufferSize, index =>
            {
                var imageCopy      = CopyArrayOf(sprites);
                var candidateHash  = orderedDirtyKvpArray[index].Key;
                var candidate      = areas[candidateHash];
                var areaDimensions = candidate.Dimensions;

                var emptinessMapCopy = CopyArrayOf(mapsOfEmptiness[areaDimensions]);

                var deletedOpaquePixels = 0;
                for (int i = 0; i < imageCopy.Length; i++)
                {
                    var spriteCopy = imageCopy[i];
                    var spritesMapOfEmptinessCopy = emptinessMapCopy[i];
                    for (int x = 0; x < spriteCopy.Length - areaDimensions.X; x++)
                    {
                        for (int y = 0; y < spriteCopy[x].Length - areaDimensions.Y; y++)
                        {
                            if (spritesMapOfEmptinessCopy[x][y])
                            {
                                continue;
                            }
                            var comparedArea = MyArea.CreateFromSprite(spriteCopy, i, x, y, areaDimensions);
                            if (comparedArea.GetHashCode() == candidate.GetHashCode())
                            {
                                MyArea.EraseAreaFromSprite(spriteCopy, x, y, areaDimensions);
                                deletedOpaquePixels += comparedArea.OpaquePixelsCount;
                                MyArea.EraseUpdateEmptinessMap(spriteCopy, spritesMapOfEmptinessCopy, x, y, areaDimensions, areaDimensions); //т.к. мы сейчас смотрим только на эффективность текущей области в плане удаления пикселей, нам не нужно оптимизировать другие области
                            }
                        }
                    }
                }

                var cleanScore = ((long)(Mathf.Pow(candidate.OpaquePixelsCount, 2f) / candidate.Dimensions.Square)) * deletedOpaquePixels;
                partialCleanScore.AddOrUpdate(candidateHash, cleanScore, (key, _) => cleanScore);
                CurrentOp++;
            });

            var orderedCleanKvpArray = partialCleanScore.ToArray().OrderByDescending(kvp => kvp.Value).ToArray();

            Debug.Log($"");
            Debug.Log($"It's time for clean results everybody!");
            Debug.Log($"The winner is {orderedCleanKvpArray[0].Key} with highest score of {orderedCleanKvpArray[0].Value}!");
            for (int i = 0; i < orderedCleanKvpArray.Length; i++)
            {
                Debug.Log($"    {i + 1}. {orderedCleanKvpArray[i].Key}. Score: {orderedCleanKvpArray[i].Value}");
            }

            /*
             * Ок, теперь мы имеем чистый список и победителя - забираем его из areas, удаляем его пиксели с картинки, наносим на карту его id,
             * и после этого мы должны пойти по новой - пересчитать areaDirtyScores, взять buffer лучших, посчитать Clean, взять лучшего, и т.д..
             * Но вообще я могу сделать по-другому. Взять старый areaDirtyScores и пересчитать только те области, которые были затронуты предыдущим
             * удалением. Для этого мне надо иметь карту размером с картинку, где каждый пиксель будет содержать инфу о том, частью какого хеша он является.
             * Поэтому при удалении пикселей победителя чистого списка с картинки, мы сохрянем все уникальные хеши удаленных пикселей, и потом пересчитываем
             * области с соответствующими хешами - может оказаться, что эти области вообще больше не существуют и надо их тогда удалить из грязного списка.
             * Если же они существуют - обновляем их рейтинг в грязном списке. А затем уже можно пойти по новой итерации цикла.
             *
             * С другой стороны, карта размером с картинку - это потенциально много миллионов List'ов, каждый из которых будет содержать потенциально сотни
             * значений. В ххудшем случае, если у нас 4к текстура и какая-нибудь большая в пределах разумного область, допустим, 8х8, то у нас 16 миллионов
             * листов, и, несколько сотен хешей, размером, допустим 40 байт. В общем, не знаю, может я неправильно рассчитал, но у меня получилось, что мне
             * понадобятся несколько сотен гигабайт оперативки для всего это счатья. Так что наверное, лучше все же смещать баланс в сторону вычислительной
             * сложности.
             *
             * Нет, все-таки мне нужно как-то это дело оптимизировать. Так оставлять нельзя, очень долго будет обрабатываться. Я думаю, нужно сделать карту,
             * содержащую информацию о пустых областях, чтобы можно было скипнуть проходы цикла.
             */

            var winnerAreaHash         = orderedCleanKvpArray[0].Key;
            var winnerArea             = areas[winnerAreaHash];
            var winnerAreaDimensions   = winnerArea.Dimensions;
            var winnerAreaEmptinessMap = mapsOfEmptiness[winnerAreaDimensions];

            var mappedAreas = new List <(int, int, int)>();

            var opaquePixelsDeletedByWinner = 0;
            for (int i = 0; i < sprites.Length; i++)
            {
                var sprite = sprites[i];
                var spritesMapOfEmptiness = winnerAreaEmptinessMap[i];
                for (int x = 0; x < sprite.Length - winnerAreaDimensions.X; x++)
                {
                    for (int y = 0; y < sprite[x].Length - winnerAreaDimensions.Y; y++)
                    {
                        if (spritesMapOfEmptiness[x][y])
                        {
                            continue;
                        }
                        var comparedArea = MyArea.CreateFromSprite(sprite, i, x, y, winnerAreaDimensions);
                        if (comparedArea.GetHashCode() == winnerArea.GetHashCode())
                        {
                            MyArea.EraseAreaFromSprite(sprite, x, y, winnerAreaDimensions);
                            opaquePixelsDeletedByWinner += comparedArea.OpaquePixelsCount;

                            mappedAreas.Add((i, x, y));

                            /*
                             * Сообщаем всем мапам пустот, что в данной конкретной области на данном конкретном спрайте прибавилось пустоты, поэтому их
                             * надо обновить. Это действует на все варианты областей и только на 1 конкретный спрайт.
                             */

                            for (int v = 0; v < areaVariants.Length; v++)
                            {
                                var currentAreaVariant = areaVariants[v];
                                MyArea.EraseUpdateEmptinessMap(sprite, spritesMapOfEmptiness, x, y, winnerAreaDimensions, currentAreaVariant);
                            }
                        }
                    }
                }
            }

            map.Add(winnerArea, mappedAreas);

            UnoptimizedPixelsCount -= opaquePixelsDeletedByWinner;
            sw.Stop();
            LastPassTime = sw.ElapsedMilliseconds;
        }

        /*
         * Ок, все равно занимает кучу времени. И еще почему-то кол-во уникальных областей неуклонно растет, хотя по логике, удаляя пиксели, мы должны
         * получать меньше уникальных областей. Хотя нет. Удаляя пиксели мы получаем много вырезанных пространств, много пробелов в картинке, и эти
         * пробелы по идее должны добавлять уникальных вариантов составов пикселей.
         *
         * Ок, так как мне уменьшить время выполнения? Я думаю, надо добавлять эвристик. Например, мы можем предположить, что скорее всего удаление
         * какой-либо области из карты после одного прохода цикла не изменило ситуацию настолько кардинально, чтобы перелопатить все сверху донизу.
         * Т.е. скорее всего все изменилось не настолько чтобы, например, каждый из первых 10_000 областей из лидеров грязного списка перестал быть в
         * первых 10_000 после удаления. На самом деле, удаление потенциально затрагивает не так уж много областей. В области 4х4 может быть 16 1х1
         * уникальных областей, 9 2х2 уникальных областей, и 4 3х3 уникальных областей, т.е. всего, кроме собственно самой 4х4 области победителя, могут
         * быть потенциально затронуты лишь 29 областей. Нет, это те, что могут быть полностью удалены, а затронуты -  15+16*(кол-во вариантов областей - 1),
         * то есть, для 4х4 - это ((16 * 10) - 1) областей, т.е. 159 затронутых областей. Нет, забыл учесть те, что слева и сверху, их должно быть еще порядка
         * 16 * 3 * 10. Т.е., если считать приблизительно, для 4х4 и 10 вариантов областей кол-во затронутых областей не должно быть больше 4х4х4х10. Скорее
         * всего их будет где-то в районе 3х4х4х10 * 0.5 + 4х4х10, или 2.5х4х4х10 или 400. Т.е. в принципе, если предположить худший сценарий, что все эти
         * 400 областей были в 400 первых в грязном списке, а после удаления они все ушли из этих 400 первых, то нам надо пересчитать рейтинги только для
         * первых 400 областей и переупорядочить список. Конечно, мы скорее всего не пересчитаем все реально затронутые области, но если их нету в первых
         * 400 областях, значит эти затронутые области были довольно незначитальными по своему вкладу в картинку и не стали значительнее после удаления.
         * Т.е. да, они останутся с неправильным рейтингом за пределами 400 первых, но если вдруг на каком-то этапе они попадут в эти 400 первых мы
         * пересчитаем их рейтинг. Т.е. по сути мы как бы говорим "да, после удаления рейтинг многих областей поменялся, но скорее всего после удаления
         * новый чистый лидер будет среди первых (400 + buffer) грязных областей по версии до удаления".
         *
         * Но, тут еще есть один момент - новые пустоты могут насоздавать новых областей, которые в принципе могут посоперничать с областями из лидеров. Т.е.
         * в принципе после удаления мы можем дополнительно обновлять список уникальных областей теми, что были обнаружены в прилежащих к удаленным областям,
         * считать для них рейтинги и потом включить их в общий список после пересчета первых 400 областей и перед переупорядочиванием.
         *
         * Так, отмена, пиксели за пределами удаленных областей могут быть любыми, так что тут не 2.5х4х4х10 а гораздо больше областей может быть затронуто.
         * Тогда можно такую эвристику придумать. Если мы удалили процент A картинки, то затронутых пикселей областей потенциально будет процент B (где-то
         * в районе Aх9). Т.е. в худшем случае верхний процент B областей грязного рейтинга ушел куда-то на дно. Т.е. достаточно перепроверить эти B
         * процентов областей сверху грязного рейтинга и тогда мы сможем безопасно взять оттуда буффер. Конечно, это не меняет тот факт, что надо добавить
         * новообразованных областей из окресностей удаленных.
         *
         * Чтобы понять где окресности удаленных областей, мне нужна карта. Карта будет словарем с ключами в виде областей и значениями в виде координат
         * вида (индекс спрайта, X, Y). Имея этот словарь и зная последнюю добавленную туда область мы сможем узнать все затронутые ей части картинки и
         * пройтись по всем окресностям, создавая новые области по необходимости. Также эта карта будет полезна вдальнейшем при составлении структур
         * областей для каждого спрайта.
         */

        Working = false;

        throw new NotImplementedException();
        //return new Chunk[0][];
    }