/// <summary>
        ///   Optimizes the rectangle's placement by moving it either left or up to fill
        ///   any gaps resulting from rectangles blocking the anchors of the most optimal
        ///   placements.
        /// </summary>
        /// <param name="placement">Placement to be optimized</param>
        /// <param name="rectangleWidth">Width of the rectangle to be optimized</param>
        /// <param name="rectangleHeight">Height of the rectangle to be optimized</param>
        private void OptimizePlacement(ref Pointi placement, int rectangleWidth, int rectangleHeight)
        {
            var rectangle = Recti.FromSize(placement.X, placement.Y, rectangleWidth, rectangleHeight);

            // Try to move the rectangle to the left as far as possible
            int leftMost = placement.X;

            while (IsFree(ref rectangle, PackingAreaWidth, PackingAreaHeight))
            {
                leftMost = rectangle.X;
                --rectangle.X;
            }

            // Reset rectangle to original position
            rectangle.X = placement.X;

            // Try to move the rectangle upwards as far as possible
            int topMost = placement.Y;

            while (IsFree(ref rectangle, PackingAreaWidth, PackingAreaHeight))
            {
                topMost = rectangle.Y;
                --rectangle.Y;
            }

            // Use the dimension in which the rectangle could be moved farther
            if ((placement.X - leftMost) > (placement.Y - topMost))
            {
                placement.X = leftMost;
            }
            else
            {
                placement.Y = topMost;
            }
        }
        /// <summary>Tries to allocate space for a rectangle in the packing area</summary>
        /// <param name="rectangleWidth">Width of the rectangle to allocate</param>
        /// <param name="rectangleHeight">Height of the rectangle to allocate</param>
        /// <param name="placement">Output parameter receiving the rectangle's placement</param>
        /// <returns>True if space for the rectangle could be allocated</returns>
        public override bool TryPack(int rectangleWidth, int rectangleHeight, out Pointi placement)
        {
            // Try to find an anchor where the rectangle fits in, enlarging the packing
            // area and repeating the search recursively until it fits or the
            // maximum allowed size is exceeded.
            int anchorIndex = SelectAnchorRecursive(rectangleWidth, rectangleHeight, actualPackingAreaWidth, actualPackingAreaHeight);

            // No anchor could be found at which the rectangle did fit in
            if (anchorIndex == -1)
            {
                placement = new Pointi();
                return(false);
            }

            placement = anchors[anchorIndex];

            // Move the rectangle either to the left or to the top until it collides with
            // a neightbouring rectangle. This is done to combat the effect of lining up
            // rectangles with gaps to the left or top of them because the anchor that
            // would allow placement there has been blocked by another rectangle
            OptimizePlacement(ref placement, rectangleWidth, rectangleHeight);

            // Remove the used anchor and add new anchors at the upper right and lower left
            // positions of the new rectangle
            // The anchor is only removed if the placement optimization didn't
            // move the rectangle so far that the anchor isn't blocked anymore
            bool blocksAnchor =
                ((placement.X + rectangleWidth) > anchors[anchorIndex].X) &&
                ((placement.Y + rectangleHeight) > anchors[anchorIndex].Y);

            if (blocksAnchor)
            {
                anchors.RemoveAt(anchorIndex);
            }

            // Add new anchors at the upper right and lower left coordinates of the rectangle
            InsertAnchor(new Pointi(placement.X + rectangleWidth, placement.Y));
            InsertAnchor(new Pointi(placement.X, placement.Y + rectangleHeight));

            // Finally, we can add the rectangle to our packed rectangles list
            packedRectangles.Add(Recti.FromSize(placement.X, placement.Y, rectangleWidth, rectangleHeight));

            return(true);
        }
        /// <summary>Locates the first free anchor at which the rectangle fits</summary>
        /// <param name="rectangleWidth">Width of the rectangle to be placed</param>
        /// <param name="rectangleHeight">Height of the rectangle to be placed</param>
        /// <param name="testedPackingAreaWidth">Total width of the packing area</param>
        /// <param name="testedPackingAreaHeight">Total height of the packing area</param>
        /// <returns>The index of the first free anchor or -1 if none is found</returns>
        private int FindFirstFreeAnchor(int rectangleWidth, int rectangleHeight, int testedPackingAreaWidth, int testedPackingAreaHeight)
        {
            var potentialLocation = Recti.FromSize(0, 0, rectangleWidth, rectangleHeight);

            // Walk over all anchors (which are ordered by their distance to the
            // upper left corner of the packing area) until one is discovered that
            // can house the new rectangle.
            for (int index = 0; index < anchors.Count; ++index)
            {
                potentialLocation.X = anchors[index].X;
                potentialLocation.Y = anchors[index].Y;

                // See if the rectangle would fit in at this anchor point
                if (IsFree(ref potentialLocation, testedPackingAreaWidth, testedPackingAreaHeight))
                {
                    return(index);
                }
            }

            // No anchor points were found where the rectangle would fit in
            return(-1);
        }
Exemple #4
0
        public void ImportFrames(IEnumerable <string> fileNames, int padding)
        {
            var sprites = fileNames.Select(x =>
            {
                var item = new SpriteItem
                {
                    Name   = Path.GetFileNameWithoutExtension(x),
                    Sprite = new BitmapImage(new Uri(x))
                };
                item.AreaSize = item.Sprite.PixelWidth * item.Sprite.PixelHeight;
                return(item);
            })
                          .OrderByDescending(x => x.AreaSize)
                          .ThenByDescending(x => x.Sprite.PixelWidth)
                          .ToArray();

            int  width = 64, height = 64;
            bool increaser = false;

            bool failed = true;

            do
            {
                failed = false;
                var packer = new ArevaloRectanglePacker(width, height);
                foreach (var item in sprites)
                {
                    int spriteWidth  = item.Sprite.PixelWidth;
                    int spriteHeight = item.Sprite.PixelHeight;

                    Sizei size = new Sizei
                    {
                        Width  = spriteWidth + padding,
                        Height = spriteHeight + padding
                    };

                    if (!packer.TryPack(size.Width, size.Height, out var origin))
                    {
                        failed = true;
                        break;
                    }

                    item.Rectangle = Recti.FromSize(origin.X, origin.Y, spriteWidth, spriteHeight);
                }

                if (failed)
                {
                    if (increaser)
                    {
                        height *= 2;
                    }
                    else
                    {
                        width *= 2;
                    }
                    increaser = !increaser;
                }
            } while (failed);

            var dicFrames     = Frames.ToDictionary(x => x.Name, x => x);
            var newFramesList = new List <Frame>(Frames.Capacity);

            foreach (var sprite in sprites)
            {
                if (dicFrames.TryGetValue(sprite.Name, out var frame))
                {
                    frame.Left   = sprite.Rectangle.Left;
                    frame.Top    = sprite.Rectangle.Top;
                    frame.Right  = sprite.Rectangle.Right;
                    frame.Bottom = sprite.Rectangle.Bottom;
                }
                else
                {
                    frame = new Frame()
                    {
                        Name    = sprite.Name,
                        Left    = sprite.Rectangle.Left,
                        Top     = sprite.Rectangle.Top,
                        Right   = sprite.Rectangle.Right,
                        Bottom  = sprite.Rectangle.Bottom,
                        CenterX = sprite.Rectangle.Width / 2,
                        CenterY = sprite.Rectangle.Height / 2
                    };
                }
                newFramesList.Add(frame);
            }

            var drawingVisual = new DrawingVisual();

            using (var drawingContext = drawingVisual.RenderOpen())
            {
                foreach (var sprite in sprites)
                {
                    drawingContext.DrawImage(sprite.Sprite, new Rect()
                    {
                        X      = sprite.Rectangle.X,
                        Y      = sprite.Rectangle.Y,
                        Width  = sprite.Rectangle.Width,
                        Height = sprite.Rectangle.Height
                    });
                }
            }

            var renderTargetBitmap = new RenderTargetBitmap(width, height, 96.0f, 96.0f, PixelFormats.Pbgra32);

            renderTargetBitmap.Render(drawingVisual);

            Texture = renderTargetBitmap;
            Frames.Clear();
            Frames.AddRange(newFramesList.OrderBy(x => x.Name));
        }