Click here to Skip to main content
Click here to Skip to main content
Articles » Web Development » ASP.NET » General » Downloads
 
Add your own
alternative version

Generate CSS sprites and thumbnail images on the fly in ASP.NET sites

, 9 Jun 2012 CPOL
Reduces page load times of ASP.NET web sites by combining page images and CSS background images into CSS sprites. Compresses and physically resizes images to make thumbnails. Caters for repeating background images.
CssSpriteGenerator.zip
Code
CssSpriteGenerator.suo
CssSpriteGenerator.vssscc
CssSpriteGenerator
bin
Debug
CssSpriteGenerator.dll
CssSpriteGenerator.pdb
Release
CssSpriteGenerator.dll
CssSpriteGenerator.pdb
ConfigElements
DTOs
Enums
External
SimplePaletteQuantizer
Extensions
Helpers
Quantizers
HSB
Median
Octree
Popularity
Uniform
GeneratorNs
ImageReferences
Mapper
obj
Debug
CssSpriteGenerator.dll
CssSpriteGenerator.pdb
DesignTimeResolveAssemblyReferencesInput.cache
TempPE
Properties.Resources.Designer.cs.dll
Release
CssSpriteGenerator.dll
CssSpriteGenerator.pdb
DesignTimeResolveAssemblyReferencesInput.cache
TempPE
Properties
Utils
DemoSite_AutoResized
App_Browsers
PageAdapter.browser
Bin
CssSpriteGenerator.dll
CssSpriteGenerator.pdb
largeimages
vanuatu1.JPG
DemoSite_CombineAndMinify
___generated
Bin
CombineAndMinify.dll
CombineAndMinify.pdb
CssSpriteGenerator.dll
CssSpriteGenerator.pdb
EcmaScript.NET.modified.dll
Yahoo.Yui.Compressor.dll
DemoSite_CompressedJpeg
App_Browsers
PageAdapter.browser
Bin
CssSpriteGenerator.dll
CssSpriteGenerator.pdb
largeimages
vanuatu1.JPG
vanuatu2.JPG
DemoSite_CompressedPng
App_Browsers
PageAdapter.browser
Bin
CssSpriteGenerator.dll
CssSpriteGenerator.pdb
images
cart.png
contactus.png
print.png
DemoSite_CssBackgroundImages
App_Browsers
PageAdapter.browser
Bin
CssSpriteGenerator.dll
CssSpriteGenerator.pdb
css
images
button-green-left.png
button-green-right.png
button-green-w100.png
button-orange-left.png
button-orange-right.png
button-orange-w100.png
gradient-hor-blue-w20h1.png
gradient-hor-green-w20h1.png
gradient-vert-blue-w1h20.png
gradient-vert-green-w1h20.png
gradient-vert-lightblue-w1h10.png
gradient-vert-orange-w1h10.png
DemoSite_Database
App_Browsers
PageAdapter.browser
App_Code
Bin
CssSpriteGenerator.dll
CssSpriteGenerator.pdb
images
cart.png
contactus.png
print.png
DemoSite_DefaultConfig
App_Browsers
PageAdapter.browser
Bin
CssSpriteGenerator.dll
CssSpriteGenerator.pdb
images
band-4bits.png
cart.png
circles-8bits.gif
contactus.png
dragonboats-thumbnail1.jpg
dragonboats-thumbnail2.jpg
dragonboats-thumbnail3.jpg
print.png
rectangles-4bits.png
smallrectangles-4bits.png
DemoSite_FolderImages
App_Browsers
PageAdapter.browser
Bin
CssSpriteGenerator.dll
CssSpriteGenerator.pdb
images
cart.png
contactus.png
print.png
DemoSite_Gallery
App_Browsers
PageAdapter.browser
Bin
CssSpriteGenerator.dll
CssSpriteGenerator.pdb
images
cart.png
contactus.png
print.png
largeimages
vanuatu1.JPG
vanuatu2.JPG
DemoSite_QuickStart
App_Browsers
PageAdapter.browser
Bin
CssSpriteGenerator.dll
CssSpriteGenerator.pdb
images
200x200.png
90x90.gif
90x90.jpg
90x90.png
photos
vanuatu2.JPG
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
using System.Text.RegularExpressions;

namespace CssSpriteGenerator
{
    public class HtmlUtils
    {

        /// <summary>
        /// Analyses a string with html, finds all image tags, and returns
        /// a collection of ImageTags, one for each image in the html.
        /// </summary>
        /// <param name="html"></param>
        /// <returns></returns>
        public static IList<ImageTag> ImagesInHtml(string html)
        {
            List<ImageTag> result = new List<ImageTag>();

            string regexpImgGroup =
                RegexTagWithAttributes("img"); // just an img tag

            Regex r = new Regex(regexpImgGroup, RegexOptions.IgnoreCase);
            Match m = r.Match(html);

            while (m.Success)
            {
                string tagText = m.Value;

                ImageAttributeDictionary imgAttributes = TagAttributes("img", m);

                result.Add(new ImageTag { ImgAttributes = imgAttributes, TagText = tagText });

                m = m.NextMatch();
            }

            return result;
        }

        /// <summary>
        /// Generates the regular expression that matches an html tag and its attributes.
        /// </summary>
        /// <param name="tag"></param>
        /// <returns></returns>
        private static string RegexTagWithAttributes(string tag)
        {
            const string regexpTagGroup =
                @"<{0}" +
                @"(?:" + // start attributes definition
                @"\s*" + // starts with zero more more white spaces
                @"(?<{0}attrname>\w*)" + // attribute name, must consist of letter, digits or underscore
                @"\s*=\s*" + // followed by zero or more space, then an equals sign, then zero or more spaces
                @"(?<{0}quote>""|')" + // followed by quote (" or ')
                @"(?<{0}attrvalue>.*?)" + // followed by value. Using a non greedy match on zero or more characters .*?
                @"\k<{0}quote>" + // followed by the same quote as we saw before. The \k<{0}quote> refers to the first quote.
                @")*" + // zero or more attributes of the form attr="value"
                @"(?:[^>]*)" + // followed by zero or more non-> characters
                @">"; // followed by closing >

            string result = string.Format(regexpTagGroup, tag);
            return result;
        }

        /// <summary>
        /// Generates the regular expression that matches an html end tag and its attributes.
        /// </summary>
        /// <param name="tag"></param>
        /// <returns></returns>
        private static string RegexEndTag(string tag)
        {
            const string regexpEndTagGroup =
                @"</{0}>";

            string result = string.Format(regexpEndTagGroup, tag);
            return result;
        }

        /// <summary>
        /// After the regex generated by RegexTagWithAttributes has been matched,
        /// use this method to retrieve the attributes of the tag and return them in a
        /// ImageAttributeDictionary.
        /// </summary>
        /// <param name="tag">
        /// Tag that was passed to RegexTagWithAttributes to generate the regular expression
        /// that has now been matched
        /// </param>
        /// <param name="m">
        /// Match of the regular expression.
        /// </param>
        /// <returns></returns>
        private static ImageAttributeDictionary TagAttributes(string tag, Match m)
        {
            CaptureCollection attrNames = m.Groups[tag + "attrname"].Captures;
            CaptureCollection attrValues = m.Groups[tag + "attrvalue"].Captures;

            int nbrNames = attrNames.Count;
            int nbrValues = attrValues.Count;

            if (nbrNames != nbrValues)
            {
                throw new Exception(
                    string.Format("Image tag {0} in {1} has {2} attribute names, but {3} attribute values", tag, m.Value, nbrNames, nbrValues));
            }

            // All attributes will be stored in this dictionary (attribute name = key, attribute value = value)
            ImageAttributeDictionary attributes = new ImageAttributeDictionary();

            // If an image tag has the same attribute multiple times, the browser uses the first occurrance (tested on IE, Firefox, Chrome).
            // So, go backwards through the list of attributes. That way, if there are duplicates, the first one will
            // remain in the dictionary.
            for (int i = nbrNames - 1; i >= 0; i--)
            {
                attributes[attrNames[i].Value.ToLower()] = attrValues[i].Value;
            }

            return attributes;
        }

        
        /// <summary>
        /// Produces the CSS to be used with a div tag, in order to make that div tag show a sprite.
        /// </summary>
        /// <param name="spriteUrl">
        /// Url of the sprite image.
        /// </param>
        /// <param name="xOffset">
        /// X offset within the sprite where the original image starts.
        /// </param>
        /// <param name="yOffset">
        /// Y offset within the sprite where the original image starts.
        /// </param>
        /// <param name="imageWidth">
        /// Width in px of the original image.
        /// </param>
        /// <param name="imageHeight">
        /// Height in px of the original image.
        /// </param>
        /// <returns></returns>
        public static string PageSpriteCss(
            string spriteUrl, int xOffset, int yOffset, int imageWidth, int imageHeight)
        {
            string css =
                string.Format(
                    "width: {0}px; height: {1}px; background: url({2}) -{3}px -{4}px;",
                    imageWidth, imageHeight, UrlUtils.EscapedUrl(spriteUrl), xOffset, yOffset);

            return css;
        }

        /// <summary>
        /// Generates the html for a sprite that lives on the page (rather than a sprite used with the css).
        /// Essentially, this will be a div tag that is styled via a class or via inline style.
        /// However, if the original img was enclosed in an anchor, you get a span enclosed in an anchor.
        /// 
        /// Example for simple img translation:
        /// 
        /// <div style="width: 50px; height: 50px; background: url(___spritegen/2-0-0-B3-45-8A-FF-3E-C3-78-19-4B-89-7E-E0-91-04-8B-70.png) -50px -0px; display:inline-block;
        ///             text-indent:-9999px;">the alt text</div>
        ///
        /// Example for img enclosed with anchor:
        /// 
        /// <a href="http://google.com" target="_blank"
        ///    style="text-decoration: none;
        ///           width: 50px; height: 50px;background: url(___spritegen/2-0-0-B3-45-8A-FF-3E-C3-78-19-4B-89-7E-E0-91-04-8B-70.png) -50px -0px;display:inline-block;
        ///           >
        ///     <span style="display:inline-block;text-indent:-9999px; ">the alt text</span>
        /// </a>
        /// 
        /// </summary>
        /// <param name="pageSpriteCss">
        /// CSS to be used with the div tag.
        /// </param>
        /// <param name="imageAttributes">
        /// Attributes of the original image.
        /// </param>
        /// <param name="inlineSpriteStyles">
        /// True: add the css to the div tag as inline style.
        /// False: add the css via a css class.
        /// </param>
        /// <param name="altCopyOptions">
        /// Determines how the alt attribute of the img is treated. See description of altCopyOptions in ConfigSection.cs.
        /// </param>
        /// <param name="classPostfix">
        /// If you need to create a new css class, give its name this postfix.
        /// </param>
        /// <param name="additionalCss">
        /// If you need to add CSS to a stylesheet, return it here.
        /// The caller is reponsible for creating a stylesheet that contains this CSS.
        /// </param>
        /// <returns>
        /// Complete html representing the sprite.
        /// </returns>
        public static string PageSpriteHtml(
            string spriteUrl, int xOffset, int yOffset, int imageWidth, int imageHeight, 
            ImageAttributeDictionary imageAttributes, 
            bool inlineSpriteStyles, 
            Stylesheet additionalCss)
        {
            string spriteCss = PageSpriteCss(spriteUrl, xOffset, yOffset, imageWidth, imageHeight);

            // -----------------
            // create opening tag

            List<string> imgExcludes = new List<string>
                                           {
                                                "src",
                                                "width",
                                                "height",
                                                "class",
                                                "style"
                                           };

            // -----------------
            // copy over the attributes from the original img tag

            string tagAttributesString = imageAttributes.ToString(imgExcludes, null);

            // ----------------

            string styleAttributeValue = imageAttributes.AttributeValue("style");
            string classAttributeValue = imageAttributes.AttributeValue("class");

            if (inlineSpriteStyles)
            {
                styleAttributeValue = CombinedCss(styleAttributeValue, spriteCss);
            }
            else
            {
                string additionalClass = additionalCss.AddDeclaration(spriteCss);
                classAttributeValue = CombinedClasses(classAttributeValue, additionalClass);
            }

            // ---------------
            // assemble final html

            string html =
                "<img src=\"" + UrlUtils.UrlTransparent1x1Png() + "\"" +
                tagAttributesString +
                NonEmptyHtmlAttribute("style", styleAttributeValue) +
                NonEmptyHtmlAttribute("class", classAttributeValue) +
                @" />";

            return html;
        }

        private static string NonEmptyHtmlAttribute(string attributeName, string attributeValue)
        {
            string htmlAttribute = "";
            if (!string.IsNullOrWhiteSpace(attributeValue))
            {
                htmlAttribute = string.Format(@" {0}=""{1}""", attributeName, attributeValue);
            }

            return htmlAttribute;
        }

        /// <summary>
        /// Creates an image tag out of the passed in imageAttributes.
        /// If anchorAttributes contains attributes, than an anchor tag is created around the img.
        /// </summary>
        /// <param name="imageUrl">
        /// Overrides the src attribute
        /// </param>
        /// <param name="imageAttributes">
        /// </param>
        /// <param name="imageWidth">
        /// Overrides the width attribute
        /// </param>
        /// <param name="imageHeight">
        /// Overrides the height attribute
        /// </param>
        /// <param name="overrideImgTagDimensionProperties">
        /// If this is true, than any width and height properties of the img tag will be
        /// removed, and new width and height properties created based on the imageWidth and imageHeight.
        /// </param>
        /// <returns></returns>
        public static string ImgHtml(
            string imageUrl, ImageAttributeDictionary imageAttributes, 
            int imageWidth, int imageHeight, bool overrideImgTagDimensionProperties)
        {
            // ------------
            // Create the html for the image.

            List<string> imgExcludes = new List<string>();
            imgExcludes.Add("src");
            if (overrideImgTagDimensionProperties)
            {
                imgExcludes.Add("width");
                imgExcludes.Add("height");
            }

            string imgAttributesString = imageAttributes.ToString(imgExcludes, null);

            string imgHtml = "<img";
            
            if (overrideImgTagDimensionProperties)
            {
                // Make sure width and height are added
                imgHtml += string.Format(@" width=""{0}""", imageWidth);
                imgHtml += string.Format(@" height=""{0}""", imageHeight);
            }

            imgHtml +=
                string.Format(@" src=""{0}""", UrlUtils.EscapedUrl(imageUrl)) +
                imgAttributesString +
                @" />";

            return imgHtml;
        }

        private static string CombinedClasses(string class1, string class2)
        {
            if (string.IsNullOrWhiteSpace(class1))
            {
                return class2;
            }

            string trimmedClass1 = class1.Trim(new char[] { ' ' });
            return trimmedClass1 + " " + class2;
        }

        private static string CombinedCss(string css1, string css2)
        {
            if (string.IsNullOrWhiteSpace(css1))
            {
                return css2;
            }

            string trimmedCss1 = css1.Trim(new char[] { ' ', ';' });
            return trimmedCss1 + ";" + css2;
        }

        /// <summary>
        /// Produces the CSS to be used with a sprite that will be used for CSS background images.
        /// </summary>
        /// <param name="spriteUrl">
        /// Url of the sprite image.
        /// </param>
        /// <param name="xOffset">
        /// X offset within the sprite where the original image starts.
        /// </param>
        /// <param name="yOffset">
        /// Y offset within the sprite where the original image starts.
        /// </param>
        /// <param name="alignment">
        /// Required alignment of the background image.
        /// </param>
        /// <returns></returns>
        public static string CssSpriteCss(
            string spriteUrl, int xOffset, int yOffset, Alignment alignment)
        {
            string xPos = posString(xOffset, "xOffset", alignment, Alignment.Left, Alignment.Right);
            string yPos = posString(yOffset, "yOffset", alignment, Alignment.Top, Alignment.Bottom);

            string result = 
                string.Format(
                    "background-image: url({0}); background-position: {1} {2};",
                    UrlUtils.EscapedUrl(spriteUrl), xPos, yPos);

            return result;
        }

        private static string posString(int offset, string offsetName, Alignment alignment, Alignment checkAlignment1, Alignment checkAlignment2)
        {
            string result = null;
            if ((alignment == checkAlignment1) || (alignment == checkAlignment2))
            {
                if (offset != 0)
                {
                    throw new Exception(
                        string.Format("Alignment is {0} but {1} is {2} while it should be 0", alignment, offsetName, offset));
                }

                result = alignment.ToString();
            }
            else
            {
                result = (-1 * offset).ToString() + "px";
            }

            return result;
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Matt Perdeck
Web Developer
Australia Australia
Twitter: @MattPerdeck
Blog: mattperdeck.com
Current project: JSNLog JavaScript Logging Package
 
Matt has over 6 years .NET and SQL Server development experience. Before getting into .Net, he worked on a number of systems, ranging from the largest ATM network in The Netherlands to embedded software in advanced Wide Area Networks and the largest ticketing web site in Australia. He has lived and worked in Australia, The Netherlands, Slovakia and Thailand.
 
He recently wrote a book, ASP.NET Performance Secrets (www.packtpub.com/asp-net-site-performance-secrets/book) in which he shows in clear and practical terms how to quickly find the biggest bottlenecks holding back the performance of your web site, and how to then remove those bottlenecks. The book deals with all environments affecting a web site - the web server, the database server and the browser.
 
Matt currently lives in Sydney, Australia. After 2 years at Readify, he now works at the global professional services company PwC. His current contract ends at 29 June 2014.
Follow on   Twitter   Google+

| Advertise | Privacy | Mobile
Web03 | 2.8.141022.1 | Last Updated 10 Jun 2012
Article Copyright 2011 by Matt Perdeck
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid