Beispiel #1
0
        /// <summary>
        /// Produces a mapping to a sprite that has given maximum dimensions.
        /// If the mapping can not be done inside those dimensions, returns null.
        /// </summary>
        /// <param name="images">
        /// List of image infos.
        ///
        /// This method will not sort this list.
        /// All images in this collection will be used, regardless of size.
        /// </param>
        /// <param name="maxWidth">
        /// The sprite won't be wider than this.
        /// </param>
        /// <param name="maxHeight">
        /// The generated sprite won't be higher than this.
        /// </param>
        /// <param name="canvasStats">
        /// The statistics produced by the canvas. These numbers are since the last call to its SetCanvasDimensions method.
        /// </param>
        /// <param name="lowestFreeHeightDeficitTallestRightFlushedImage">
        /// The lowest free height deficit for the images up to and including the tallest rectangle whose right hand border sits furthest to the right
        /// of all images.
        ///
        /// This is the minimum amount by which the height of the canvas needs to be increased to accommodate that rectangle.
        /// if the width of the canvas is decreased to one less than the width now taken by images.
        ///
        /// Note that providing the additional height might get some other (not right flushed) image to be placed higher, thereby
        /// making room for the flushed right image.
        ///
        /// This will be set to Int32.MaxValue if there was never any free height deficit.
        /// </param>
        /// <returns>
        /// The generated sprite.
        ///
        /// null if not all the images could be placed within the size limitations.
        /// </returns>
        protected virtual Atlas MappingRestrictedBox(
            IOrderedEnumerable <ImageInfo> images,
            int maxWidth, int maxHeight, CanvasStats canvasStats,
            out int lowestFreeHeightDeficitTallestRightFlushedImage)
        {
            lowestFreeHeightDeficitTallestRightFlushedImage = 0;
            _canvas.SetCanvasDimensions(maxWidth, maxHeight);

            Atlas spriteInfo = new Atlas();
            int   heightHighestRightFlushedImage = 0;
            int   furthestRightEdge = 0;

            foreach (ImageInfo image in images)
            {
                int xOffset;
                int yOffset;
                int lowestFreeHeightDeficit;
                if (!_canvas.AddRectangle(
                        image.Width, image.Height,
                        out xOffset, out yOffset,
                        out lowestFreeHeightDeficit))
                {
                    // Not enough room on the canvas to place the rectangle
                    spriteInfo = null;
                    break;
                }

                MappedImageInfo imageLocation = new MappedImageInfo(xOffset, yOffset, image);
                spriteInfo.AddMappedImage(imageLocation);

                // Update the lowestFreeHeightDeficitTallestRightFlushedImage
                int rightEdge = image.Width + xOffset;
                if ((rightEdge > furthestRightEdge) ||
                    ((rightEdge == furthestRightEdge) && (image.Height > heightHighestRightFlushedImage)))
                {
                    // The image is flushed the furthest right of all images, or it is flushed equally far to the right
                    // as the furthest flushed image but it is taller.

                    lowestFreeHeightDeficitTallestRightFlushedImage = lowestFreeHeightDeficit;
                    heightHighestRightFlushedImage = image.Height;
                    furthestRightEdge = rightEdge;
                }
            }

            _canvas.GetStatistics(canvasStats);

            return(spriteInfo);
        }
Beispiel #2
0
        /// <summary>
        /// Works out how to map a series of images into a sprite.
        /// </summary>
        /// <param name="images">
        /// The list of images to place into the sprite.
        /// </param>
        /// <returns>
        /// A SpriteInfo object. This describes the locations of the images within the sprite,
        /// and the dimensions of the sprite.
        /// </returns>
        public Atlas Mapping(IEnumerable <ImageInfo> images)
        {
            int candidateSpritesGenerated  = 0;
            int canvasRectangleAddAttempts = 0;
            int canvasNbrCellsGenerated    = 0;

            // Sort the images by height descending
            IOrderedEnumerable <ImageInfo> imageInfosHighestFirst =
                images.OrderByDescending(p => p.Height);

            int totalAreaAllImages =
                (from a in imageInfosHighestFirst select a.Width * a.Height).Sum();

            int widthWidestImage =
                (from a in imageInfosHighestFirst select a.Width).Max();

            int heightHighestImage = imageInfosHighestFirst.First().Height;

            Atlas bestSprite = null;

            int canvasMaxWidth  = 4096;
            int canvasMaxHeight = Math.Max(heightHighestImage, (int)Math.Ceiling((float)totalAreaAllImages / canvasMaxWidth));

            while (canvasMaxWidth >= widthWidestImage)
            {
                CanvasStats canvasStats = new CanvasStats();
                int         lowestFreeHeightDeficitTallestRightFlushedImage;
                Atlas       spriteInfo =
                    MappingRestrictedBox(imageInfosHighestFirst, canvasMaxWidth, canvasMaxHeight, canvasStats, out lowestFreeHeightDeficitTallestRightFlushedImage);

                canvasRectangleAddAttempts += canvasStats.RectangleAddAttempts;
                canvasNbrCellsGenerated    += canvasStats.NbrCellsGenerated;

                if (spriteInfo == null)
                {
                    // Failure - Couldn't generate a SpriteInfo with the given maximum canvas dimensions

                    // Try again with a greater max height. Add enough height so that
                    // you don't get the same rectangle placement as this time.

                    if (canvasStats.LowestFreeHeightDeficit == Int32.MaxValue)
                    {
                        canvasMaxHeight++;
                    }
                    else
                    {
                        canvasMaxHeight += canvasStats.LowestFreeHeightDeficit;
                    }
                }
                else
                {
                    // Success - Managed to generate a SpriteInfo with the given maximum canvas dimensions

                    candidateSpritesGenerated++;

                    // Find out if the new SpriteInfo is better than the current best one
                    if ((bestSprite == null) || (bestSprite.Area > spriteInfo.Area))
                    {
                        bestSprite = spriteInfo;

                        float bestEfficiency = (float)totalAreaAllImages / spriteInfo.Area;
                        if (bestEfficiency >= CutoffEfficiency)
                        {
                            break;
                        }
                    }

                    if (candidateSpritesGenerated >= MaxNbrCandidateSprites)
                    {
                        break;
                    }

                    // Try again with a reduce maximum canvas width, to see if we can squeeze out a smaller sprite
                    // Note that in this algorithm, the maximum canvas width is never increased, so a new sprite
                    // always has the same or a lower width than an older sprite.
                    canvasMaxWidth = bestSprite.Width - 1;

                    // Now that we've decreased the width of the canvas to 1 pixel less than the width
                    // taken by the images on the canvas, we know for sure that the images whose
                    // right borders are most to the right will have to move up.
                    //
                    // To make sure that the next try is not automatically a failure, increase the height of the
                    // canvas sufficiently for the tallest right flushed image to be placed. Note that when
                    // images are placed sorted by highest first, it will be the tallest right flushed image
                    // that will fail to be placed if we don't increase the height of the canvas sufficiently.

                    if (lowestFreeHeightDeficitTallestRightFlushedImage == Int32.MaxValue)
                    {
                        canvasMaxHeight++;
                    }
                    else
                    {
                        canvasMaxHeight += lowestFreeHeightDeficitTallestRightFlushedImage;
                    }
                }

                // ---------------------
                // Adjust max canvas width and height to cut out sprites that we'll never accept
                if (bestSprite != null)
                {
                    int  bestSpriteArea = bestSprite.Area;
                    bool candidateBiggerThanBestSprite;
                    bool candidateSmallerThanCombinedImages;

                    while (
                        (canvasMaxWidth >= widthWidestImage) &&
                        (!CandidateCanvasFeasable(
                             canvasMaxWidth, canvasMaxHeight, bestSpriteArea, totalAreaAllImages,
                             out candidateBiggerThanBestSprite, out candidateSmallerThanCombinedImages)))
                    {
                        if (candidateBiggerThanBestSprite)
                        {
                            canvasMaxWidth--;
                        }
                        if (candidateSmallerThanCombinedImages)
                        {
                            canvasMaxHeight++;
                        }
                    }
                }
            }

            return(bestSprite);
        }
Beispiel #3
0
 /// <summary>
 /// The canvas keeps statistics, on for example the number of times a FreeAreas is generated.
 /// Use this method to fill an object that implements ICanvasStats with these statistics.
 ///
 /// Note that calling SetCanvasDimensions resets all counters.
 /// </summary>
 /// <param name="canvasStats">
 /// Reference to object to be filled.
 ///
 /// If this is null, nothing happens (so there is no exception).
 /// </param>
 public void GetStatistics(CanvasStats canvasStats)
 {
     canvasStats.NbrCellsGenerated       = _nbrCellsGenerated;
     canvasStats.RectangleAddAttempts    = _nbrRectangleAddAttempts;
     canvasStats.LowestFreeHeightDeficit = _lowestFreeHeightDeficitSinceLastRedim;
 }