public static int CompareImageFrameWidthDescending(ImageFrame f1, ImageFrame f2)
 {
     if (f1.Width < f2.Width)
         return 1;
     if (f1.Width == f2.Width)
         return 0;
     return -1;
 }
 public static int CompareImageFrameHeightDescending(ImageFrame f1, ImageFrame f2)
 {
     if (f1.Height < f2.Height)
         return 1;
     if (f1.Height == f2.Height)
         return 0;
     return -1;
 }
 public static int CompareImageFrameByPerimeter(ImageFrame f1, ImageFrame f2)
 {
     //Original...doesn't work propertly when there are really tall/wide items
     //along with square items with 1 larger dimension. It results in images
     //being packed in over other images. Do a 10 item test with the following sizes:
     //      (7) 502w by 36h
     //      (4) 31w  by 31h
     //      (4) 62w  by 62h
     if (f1.SemiPerimeter < f2.SemiPerimeter)
         return -1;
     if (f1.SemiPerimeter == f2.SemiPerimeter)
         return f1.Area - f2.Area;
     return 1;
 }
 public ImageFrame(ImageFrame f1, ImageFrame f2, CombineMode mode)
 {
     switch (mode)
     {
         case CombineMode.SideBySide:
             f1.OffsetX = 0;
             f2.OffsetX = f1.Width;
             Width = f1.Width + f2.Width;
             Height = Math.Max(f1.Height, f2.Height);
             break;
         case CombineMode.OneBelowOther:
             f1.OffsetY = 0;
             f2.OffsetY = f1.Height;
             Width = Math.Max(f1.Width, f2.Width);
             Height = f1.Height + f2.Height;
             break;
         default:
             throw new ArgumentException("invalid combine mode");
     }
     //Console.WriteLine(string.Format("New Combo: {0}x{1}, from f1 {2}x{3}, f2 {4}x{5}", Width, Height, f1.Width, f1.Height, f2.Width, f2.Height));
     SubFrames = new ImageFrame[2];
     SubFrames[0] = f1;
     SubFrames[1] = f2;
 }
        /// <summary>
        /// Turn a list of images into a packed sprite image and matching css file
        /// </summary>
        /// <param name="pOptions"></param>
        /// <param name="oResults">result messages</param>
        /// <returns>true/false for success</returns>
        public static bool CombineImages(PackOptions pOptions, out List<string> oResults)
        {
            oResults = new List<string>();

            long inputFileTotalSize = 0;
            long outputFileSize = 0;
            long areaTotalImages = 0;
            long areaTotalFrames = 0;
            long areaImage = 0;
            long areaFrame = 0;
            int widestImage = 0;
            int tallestImage = 0;

            var cssBuffer = new StringBuilder();
            var testPage = new StringBuilder();

            if (!string.IsNullOrWhiteSpace(pOptions.CssHeaderText))
                cssBuffer.AppendLine(pOptions.CssHeaderText);

            pOptions.ImageFilePaths.Sort(); //doing this as a debugging test

            var frameArray = new List<ImageFrame>();
            foreach (string imageFilePath in pOptions.ImageFilePaths)
            {
                if (File.Exists(imageFilePath))
                {
                    inputFileTotalSize += new FileInfo(imageFilePath).Length;

                    using (var image = Image.FromFile(imageFilePath))
                    {
                        var frame = new ImageFrame(imageFilePath, image.Width, image.Height);
                        frameArray.Add(frame);
                        #region Debugging and Error Checking
                        if (image.Width > widestImage)
                            widestImage = image.Width;
                        if (image.Height > tallestImage)
                            tallestImage = image.Height;

                        areaImage = (image.Width * image.Height);
                        areaFrame = (frame.Width * frame.Height);

                        areaTotalImages += areaImage;
                        areaTotalFrames += areaFrame;

                        if (image.Width != frame.Width
                            || image.Height != frame.Height
                            || areaImage != frame.Area)
                        {
                            oResults.Add(string.Format("Error: Image>Frame conversion size is incorrect: {0} ", imageFilePath));
                            oResults.Add(string.Format("H/W/A: Image:{0}/{1}/{2}, Frame:{3}/{4}/{5}"
                                , image.Height, image.Width, areaImage
                                , frame.Height, frame.Width, frame.Area
                                ));
                        }
                        #endregion
                    }
                }
                else
                {
                    oResults.Add(string.Format("Could not find file: {0} ", imageFilePath));
                    if (pOptions.TreatInputFileNotFoundAsError)
                        return false;
                }
            }

            long minArea = tallestImage * widestImage;
            if (areaTotalFrames > minArea)
                minArea = areaTotalFrames;
            var pref = ImageLayoutHelper.PackPreference.ByWidth;
            if (widestImage > tallestImage)
                pref = ImageLayoutHelper.PackPreference.ByHeight;

            int optimalTotalWidth = (int)Math.Ceiling(Math.Sqrt(minArea));
            if (optimalTotalWidth < widestImage)
                optimalTotalWidth = widestImage;
            optimalTotalWidth = GetNextPowerOf2(optimalTotalWidth, false);

            oResults.Add(string.Format("Tallest Image: {0:0,0} ", tallestImage));
            oResults.Add(string.Format("Widest Image: {0:0,0} ", widestImage));
            oResults.Add(string.Format("Total Image Area: {0:0,0} ", areaTotalFrames));
            oResults.Add(string.Format("Smallest Possible Area: {0:0,0} ", minArea));
            oResults.Add(string.Format("Optimal Width: {0:0,0} ", optimalTotalWidth));
            oResults.Add(string.Format("PackPreference: {0} ", pref));

            ImageFrame layoutFrame = ImageLayoutHelper.LayImageFrames(frameArray, optimalTotalWidth);
            List<ImageFrame> framesList = layoutFrame.GetFlatList();

            using (var bitmap = new Bitmap(layoutFrame.Width, layoutFrame.Height))
            {
                using (var g = Graphics.FromImage(bitmap))
                {
                    g.InterpolationMode = InterpolationMode.HighQualityBicubic; //http://msdn.microsoft.com/en-us/library/system.drawing.drawing2d.interpolationmode.aspx
                    g.SmoothingMode = SmoothingMode.None;                       //http://msdn.microsoft.com/en-us/library/z714w2y9.aspx
                    g.PixelOffsetMode = PixelOffsetMode.None;                   //http://msdn.microsoft.com/en-us/library/system.drawing.drawing2d.pixeloffsetmode.aspx
                    g.CompositingQuality = CompositingQuality.HighQuality;      //http://msdn.microsoft.com/en-us/library/system.drawing.drawing2d.compositingquality.aspx

                    if (pOptions.GenerateTestHtmlPage)
                        testPage.AppendFormat("<h1>{0} Images</h1>", framesList.Count);
                    var testPageAltClass = "a";

                    foreach (var f in framesList)
                    {
                        using (var image = Image.FromFile(f.Id))
                        {
                            g.DrawImage(image, f.OffsetX, f.OffsetY, f.Width, f.Height);
                            pOptions.CssPlaceholderValues[pOptions.CSS_PLACEHOLDER_CSS_CLASS_NAME_BASE] = GetOutputCssClassNameFromFilename(
                                    Path.GetFileName(f.Id).Substring(0, Path.GetFileName(f.Id).LastIndexOf("."))
                                    , pOptions.CssClassNameInvalidCharReplacement
                                    , pOptions.ValidSpriteClassNameCharacters);
                            pOptions.CssPlaceholderValues[pOptions.CSS_PLACEHOLDER_WIDTH] = f.Width.ToString();
                            pOptions.CssPlaceholderValues[pOptions.CSS_PLACEHOLDER_HEIGHT] = f.Height.ToString();
                            pOptions.CssPlaceholderValues[pOptions.CSS_PLACEHOLDER_IMAGE_FILE_NAME] = Path.GetFileName(pOptions.ImageOutputFilePath);
                            pOptions.CssPlaceholderValues[pOptions.CSS_PLACEHOLDER_OFFSET_X] = (-f.OffsetX).ToString();
                            pOptions.CssPlaceholderValues[pOptions.CSS_PLACEHOLDER_OFFSET_Y] = (-f.OffsetY).ToString();

                            var currentStyle = new StringBuilder(pOptions.CssFormatStringForSpriteDefinition);
                            foreach (KeyValuePair<string, string> entry in pOptions.CssPlaceholderValues)
                                currentStyle.Replace(entry.Key, entry.Value);

                            cssBuffer.AppendLine(currentStyle.ToString());
                            if (pOptions.GenerateTestHtmlPage)
                            {
                                testPageAltClass = testPageAltClass == "a" ? "" : "a";
                                testPage.AppendFormat("<tr class='{4}'><td>{0}{1}</td><td>{2}</td><td>{3}</td><td><img src='{5}' class='{0}{1}' title='{0}{1}' /></td></tr>"
                                    , pOptions.CssPlaceholderValues[pOptions.CSS_PLACEHOLDER_CSS_CLASS_NAME_PREFIX] + pOptions.CssPlaceholderValues[pOptions.CSS_PLACEHOLDER_CSS_CLASS_NAME_BASE]
                                    , pOptions.CssPlaceholderValues[pOptions.CSS_PLACEHOLDER_CSS_CLASS_NAME_SUFFIX]
                                    , f.Height
                                    , f.Width
                                    , testPageAltClass
                                    , pOptions.TestHtmlClearGifUrl
                                );
                            }
                        }
                    }
                }

                string imageOutputFilePath = pOptions.ImageOutputFilePath;

                File.WriteAllText(pOptions.CssOutputFilePath, cssBuffer.ToString());
                if (pOptions.GenerateTestHtmlPage)
                {
                    var css = cssBuffer.ToString();
                    if (!string.IsNullOrWhiteSpace(pOptions.TestHtmlImageDeployUrlBase))
                        css = css.Replace(pOptions.CssPlaceholderValues[pOptions.CSS_PLACEHOLDER_IMAGE_DEPLOY_URL_BASE], pOptions.TestHtmlImageDeployUrlBase);

                    var htmlForRows = testPage.ToString();
                    var html = string.Format("<html><head><title>CSS Sprite Preview for: {0}</title>{3}<style>body {1}background-color:#eeeeff;{2} img {1}margin:1px;border:solid 1px red;{2} TH {1}background-color:#bbbbbb;{2} TD {1}background-color:#cccccc;{2} TR.a TD {1}background-color:#DDDDDD;{2}  .clickable {1}cursor:pointer;cursor:hand;{2} {4}</style></head><body><table class='sortable' cellspacing=0 cellpadding=5><tr><td class=clickable>Css Class</td><td class=clickable>Height</td><td class=clickable>Width</td><td>Preview</td></tr>",
                        Path.GetFileName(pOptions.CssOutputFilePath), "{", "}", pOptions.TestHtmlHeadIncludes, css)
                        + htmlForRows
                        + "</table></body></html>";

                    var testFilePath = pOptions.CssOutputFilePath + pOptions.TestHtmlFilenameSuffix;
                    if (!string.IsNullOrWhiteSpace(pOptions.TestHtmlPath))
                        testFilePath = Path.Combine(pOptions.TestHtmlPath, Path.GetFileName(pOptions.CssOutputFilePath) + pOptions.TestHtmlFilenameSuffix);

                    File.WriteAllText(testFilePath, html);
                    oResults.Add("Successfully generated the CSS test html file to: " + testFilePath);
                }

                oResults.Add("Successfully generated the corresponding CSS file to: " + pOptions.CssOutputFilePath);

                //option for reducing the output bit depth using OctreeQuantizer
                if (pOptions.LimitOutputImageTo8Bits)
                {
                    var octreeQuantizer = new OctreeQuantizer(OctreeQuantizer.BitDepth.Bits8);
                    using (var quantizedBitmap = octreeQuantizer.Quantize(bitmap))
                    {
                        quantizedBitmap.Save(imageOutputFilePath, pOptions.OutputImageFormat);
                        oResults.Add(string.Format("Limited {0} to {1} bits using OctreeQuantizer", imageOutputFilePath, 8));
                    }
                }
                else
                {
                    bitmap.Save(imageOutputFilePath, pOptions.OutputImageFormat);
                }

                //if (pOptions.MakeTransparentUsingColorKey)
                //{
                //    Color c = bitmap.GetPixel(0, 0); or use passed in coordinate or color
                //    bitmap.MakeTransparent(c);
                //    var bitmapT = Transparency.MakeTransparent(bitmap, Color.Transparent);
                //}

                oResults.Add(string.Format("New sprite: {0} ", imageOutputFilePath));
                outputFileSize = new FileInfo(imageOutputFilePath).Length;
                oResults.Add(string.Format("Successfully converted {0} files of cumulative size {1} bytes into one file of size {2} bytes."
                    , pOptions.ImageFilePaths.Count, inputFileTotalSize, outputFileSize));

            }
            return true;
        }
        public static ImageFrame LayImageFrames(List<ImageFrame> pFrameList, int pMaxWidth, bool pDebug = false)
        {
            if (pFrameList == null)
                return null;
            if (pFrameList.Count == 1)
                return pFrameList[0];

            pFrameList.Sort(CompareImageFrameHeightDescending);
            pFrameList.Sort(CompareImageFrameWidthDescending); //Now we should have them sorted by Height and Width
            var rowList = new List<ImageFrame>(); //we'll stack these together at the end

            var rowFrame = pFrameList[0];
            pFrameList.RemoveAt(0);
            int currentWidth = rowFrame.Width;

            Log(pDebug, "Row Loops: {0}", pFrameList.Count - 1);

            foreach (var f in pFrameList)
            {
                Log(pDebug, "Row Loop {0}x{1}: {2}", f.Width, f.Height, f.IdShort);
                int proposedWidth = currentWidth + f.Width;
                if (proposedWidth > pMaxWidth)
                {
                    //we don't want to add this to the current row we're building
                    //close our our current row, and start a new row
                    rowList.Add(rowFrame);
                    rowFrame = f;
                    Log(pDebug, "Sealing row at {0}px width", currentWidth);
                    Log(pDebug, "Next row starting with {0}x{1}: {2}", rowFrame.Width, rowFrame.Height, rowFrame.IdShort);
                    currentWidth = rowFrame.Width;
                }
                else
                {
                    currentWidth += f.Width;
                    rowFrame = new ImageFrame(rowFrame, f, ImageFrame.CombineMode.SideBySide);
                    Log(pDebug, "Adding to row {0}x{1}: {2}", f.Width, f.Height, f.IdShort);
                }
            }
            rowList.Add(rowFrame);

            //now stack all of the rowList frames
            ImageFrame stackedFrame = rowList[0];
            for (int i = 1; i < rowList.Count; i++)
            {
                Log(pDebug, "Stacking row {0}x{1}: {2}", rowList[i].Width, rowList[i].Height, rowList[i].IdShort);
                stackedFrame = new ImageFrame(stackedFrame, rowList[i], ImageFrame.CombineMode.OneBelowOther);
            }

            return stackedFrame;
        }