/// <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); }
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)); }