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