Click here to Skip to main content
Click here to Skip to main content

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

, 10 Jun 2012 CPOL
Rate this:
Please Sign up or sign in to vote.
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.

Introduction

On most web pages, images make up most of the files loaded by the browser to show the page. This is especially important for mobile sites, because of  low download speeds,  high network latencies and high bandwidth costs for your visitors.

As a result, you can significantly reduce bandwidth costs and page load times by compressing large images,  properly resizing thumbnails and combining small images into  CSS sprites  to cut down on request/response overhead.

The CSS Sprite Generator does all this for you. All you need to do is install it in your web site. It is all controlled from your web.config, so no need to make changes to your site.

The package generates compressed and combined images on the fly when a page loads for the first time. That way, there is no need to change your build process. CPU overhead is kept to a minimum through caching, so after the initial page load, in most cases there is no more image processing for that page.

To make it easy to get started,  this package has sensible default settings, so it works out of the box without additional configuration  (you can fine tune later with over 25 settings).

Additionally, there is a  Quick Start section right after the  Installation section.  And the solution in the download contains 10 simple but fully functional demo sites that show various features of the package. 

If you like this article, please  vote for it.

Contents

Requirements

  • ASP.NET 4 or higher
  • IIS 6 or higher for your live site

This package can run in shared hosting accounts that only provide  Medium trust, such as those provided by GoDaddy.  It can also run in accounts that provide Full trust.

Features compared with the ASP.NET Sprite and Image Optimization Library

This section compares the ASP.NET CSS Sprite Generator package against the other major package that generates sprites on the fly for ASP.NET sites, Microsoft's ASP.NET Sprite and Image Optimization Library.

The biggest difference between the two packages is that the  ASP.NET CSS Sprite Generator package is much easier to install and configure:

  • With the ASP.NET Sprite and Image Optimization Library, you have to move all images that you want combined into sprites to a separate App_Sprites directory. You also have to manually replace the img tags of those images with special user controls.  Additional configuration involves adding special settings.xml files.

  • With the ASP.NET CSS Sprite Generator package, you just add a few lines to your web.config - no need to move images or change your pages.

Below is a more detailed feature comparison of the ASP.NET CSS Sprite Generator package and the ASP.NET Sprite and Image Optimization Library:

CSS Sprite
Generator package
ASP.NET Sprite and Image
Optimization Library
Combines images (except animated images) into sprites on the fly. When you add an image to a page, it will be picked up, without requiring additional build steps. 
Processes dynamically generated image tags, such as from a database.
Images can optionally be combined based on file type, width, height and other properties (details).
Allows you to combine all images in a specific directory into sprites that are shared amongst your pages, to maximise browser caching (details).
Uses sophisticated  algorithms  to generate the smallest possible sprites.
Processes PNG GIF and JPEG images.
Processes CSS background images. Caters for repeating background images and background images used with the sliding door technique (such as used with flexible buttons and tabs) (details).
Very easy to install - just add a .dll to your site and add a few lines to your web.config (installation instructions). All  configuration  is done in your web.config. No need to reorganize your images or to use special controls.
Allows you to switch features on or off per page or per group of pages (details).
Lets you compress PNG and GIF files on the fly by reducing their color depth. Choose one of 6 built in algorithms or use the default (details).
Lets you compress JPEG files on the fly by reducing their image quality  (details).
Lets you physically resize images on the fly (for example for thumbnails), either via configuration in web.config  (details) or by setting width and/or height attributes on your img tags (details).
Lets you set the maximum file size for sprites, so sprites that grow too big are automatically split into smaller sprites (details).
The additional generated CSS required to correctly show the generated sprites  (details) can be inlined or automatically placed in a separate CSS file (details).
Can be used with sites hosted on low cost shared hosting plans, such as GoDaddy's (details).
Allows you to configure the package so it only kicks in in Release mode.  That way, you see your individual unprocessed images while developing, and  reap the performance improvement in your live site (details).
Has the option to throw an exception when an image is missing, so you quickly detect missing images (details).
Generates inlined images
Supports web farms
Supports MVC3 Razor
To reduce CPU overhead and disk accesses caused by the package, caches intermediate results. Cache entries are removed when the underlying file is changed,  so you'll never serve outdated files. 

The features shown above can all be switched on and off individually in the ASP.NET CSS Sprite Generator package via the web.config file (full description). If you just install the package and not do any further configuration, it  combines all PNG and GIF images (except animated images) that are no more than 100px wide and high into sprites - which is most commonly  what you want. By default, it only kicks in when the site is in Release mode (details).

This package is just one way of improving the performance of your web site.  My recently released book  ASP.NET Performance Secrets  shows how to pinpoint the biggest performance bottlenecks in your  web site, using various tools and performance counters built into Windows, IIS and SQL Server.  It then shows how to fix those bottlenecks. It covers all environments used by a web site -  the web server, the database server, and the browser. The book is extremely hands on - the aim is to  improve web site performance today, without wading through a lot of theory first. 

Introduction to CSS Sprites

What are CSS sprites

Web pages often contain many small images, which all have to be requested by the  browser individually and  then served up by the server.  You can save bandwidth and page load time by combining these small images into a single slightly larger image, for these reasons:

  • Each file request going from the browser to the server incurs a fixed overhead imposed by the network protocols.  That overhead can be significant for small image files. Sending fewer files means less request/response overhead.
  • Each image file carries some housekeeping overhead within the file itself. Combining small images gives you a combined image that is a lot smaller than the sum of the individual images.

Take for example this HTML:

<img src="cssspritegenerator/contactus.png" width="50" height="50" />
<img src="cssspritegenerator/cart.png" width="50" height="50" />
<img src="cssspritegenerator/print.png" width="46" height="50" />

Which loads these images:

cart.png contactus.png print.png
Size: 2.21 KB Size: 2.02KB Size: 2.05KB Total Size: 6.28KB
50px x 50px 50px x 50px 46px x 50px

Combining these images gives you this combined image:

combined.png
Size: 3.94KB
146px x 50px

At 3.94KB, the size of this combined image is only 63% of the combined size of the three individual images.

Showing the individual images

Although the three images have now been combined into one single image,  they can still be shown as individual images using a CSS technique called CSS Sprites.

The CSS  background  property lets you specify an offset into the background image from where you want to show the background image as background.  That can be used to slide the combined image over each img tag, such that the correct area is visible:

<img style="width: 50px; height: 50px; background: url(combined.png) -0px -0px;" />
<img style="width: 50px; height: 50px; background: url(combined.png) -50px -0px;" />
<img style="width: 46px; height: 50px; background: url(combined.png) -100px -0px;" />
  • The cart image sits right at the left edge horizontally and right at the top vertically within the combined image, so it gets offset 0px 0px. 
  • The contactus image sits 50px away from the left edge, and right at the top vertically, so it gets -50px 0px. 
  • And finally the print image sits 100px  away from the left edge, and right at the top vertically, so it gets -100px 0px. 

Note that the img tags have a width and height equal to the width and height of the original individual images.  That way, you only see the portions of the combined image that correspond with the individual images.

One problem remains: without a src attribute, the img tags may show as a broken image. You can circumvent this by setting the src to a transparent 1 by 1 pixel image. Because this image takes only 60 bytes, the most  efficient solution is to inline it:

<img src="data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" style="width: 50px; height: 50px; background: url(combined.png) -0px -0px;" />
<img src="data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" style="width: 50px; height: 50px; background: url(combined.png) -50px -0px;" />
<img src="data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" style="width: 46px; height: 50px; background: url(combined.png) -100px -0px;" />

Although the additional inlined image and CSS look big, their overhead should be minimal because they are highly compressible as they have lots of repeating text. This assumes that your web server has compression switched on (details), which is normally the case.

Additionally, instead of having the additional CSS inlined in the img tags, you can put this in an external .css file  and use CSS classes to link the img tags with their CSS. The package supports both options via the  inlineSpriteStyles property.

In case you're wondering about writing all this CSS and HTML, this is all done for you by the package. This section simply shows how it's done.

Inlined images are not supported by Internet Explorer 7 and 6. These browsers have about 5% traffic share (June 2012).  Rather than introducing a complicated solution for this small and declining traffic share, the package is simply  switched off for requests from these browsers, so they receive the original images.

Controlling the size of your sprites

If you were to combine images indiscriminately into sprites, you might wind up with very large sprite images that are slow to load. Now that most browsers in use today load at least 6 images concurrently, you may actually be better off having a couple of medium sized sprites rather than one big sprite.

To manage sprite sizes, you can determine the maximum width, height  and file size of the images that will be combined. You can also set a maximum sprite size - so sprites that grow too big get split in two.

Using Shared Hosting

Your site may be using a shared hosting plan, which means that it shares a web server with many other web site. Many companies that offer these plans, such as GoDaddy,  restrict the operations that each web site can perform so they don't affect each other.

In technical terms, this means your live site has  trust level Medium. In your development environment it has has trust level Full, meaning it can do whatever it wants.

The package was specifically build so it can run with trust level Medium, so you should have no problem using it with your site in a shared hosting plan.

The only issue is that the sprites generated by the package may have somewhat greater file sizes in  trust level Medium than in trust level Full. This is because some of the algorithms required to reduce the number of colors in an image are not available  in  trust level Medium. More details  here.

Installation

Take these steps to add the ASP.NET CSS Sprite Generator package to your web site:

  1. Compile the package: 
    1. Download the zip file with the source code, and unzip in a directory.
    2. Open the CssSpriteGenerator.sln file in Visual Studio 2010 or later.
    3. You'll find that the sources are organized in a solution, with these elements: 
      1. Project CssSpriteGenerator is the actual package.
      2. A number of demo sites with examples of the use of the package. You can ignore these if you want.
    4. Compile the solution. This will produce a CssSpriteGenerator.dll file in the CssSpriteGenerator\bin folder.

  2. Update your web site: 
    1. Add a reference to CssSpriteGenerator.dll to your web site (in Visual Studio, right click your web site, choose Add Reference).
    2. Add the custom section cssSpriteGenerator to the configSections section of your web.config: 
      <configuration>
          ...
          <configSections>
              ...
              <section name="cssSpriteGenerator" type="CssSpriteGenerator.ConfigSection" requirePermission="false"/>
              ...
          </configSections>
          ...
      </configuration>
    3. Add some initial configuration settings:
      <configuration>
          ...
          <configSections>
              ...
              <section name="cssSpriteGenerator" type="CssSpriteGenerator.ConfigSection" requirePermission="false"/>
              ...
          </configSections>
          ...
          <cssSpriteGenerator active="Always">
              <imageGroups>
                  <add maxWidth = "100" maxHeight = "100" maxSpriteSize = "30000" samePixelFormat = "true" />
              </imageGroups>
          </cssSpriteGenerator>
          ...
      </configuration>

      In the  Quick Start you'll see what these settings do.

    4. Add a folder called App_Browsers to the root folder of your web site: Right click your web site in Visual Studio, choose Add ASP.NET Folder, choose App_Browsers
    5. Use your favorite text editor (such as Notepad) to create a text file in the App_Browsers folder. Call it PageAdapter.browser. Into that file, copy and paste this code: 
      <browsers>
          <browser refID="Default">
              <controlAdapters>
                  <adapter controlType="System.Web.UI.Page"
                     adapterType="CssSpriteGenerator.CSSSpriteGenerator_PageAdapter" />
              </controlAdapters>
          </browser>
      </browsers>
      This tells ASP.NET to leave processing of the page to the class CssSpriteGenerator.CSSSpriteGenerator_PageAdapter (which is part of the package). 

Quick Start

The full story about all the options and how to configure them is in the  configuration section. But there are many options, so this section introduces you to the ones that may be most relevant to you, to get you started quickly.

The downloaded solution contains a series of demo sites with working examples. The demo site DemoSite_QuickStart was set up to make it easy to follow this section - apply the examples further on in this section to its web.config and than run the site to see how the changes leads to different sprites being generated.

This section looks at these scenarios:

Default configuration

Right after you've done the installation:

  • The package is active in both Debug and Release mode.
  • Only images no wider or higher than 100 pixels will be combined into sprites.
  • Sprites will be split when grower larger than 30,000 bytes. 
  • Images will be segragated by pixel format, so images with 8 bits per pixel won't go in the same sprite as images with 24 bits per pixel (why).

To give it a try:

  • Open a page on your site that has multiple images with width and height less than 100px.
  • View the source of the page to see the html for the sprite(s).  
  • You will find a new directory ___spritegen in the root of your site with the generated sprite(s).

Using the package in Release mode only

You can control when the package is active with the active property in the cssSpriteGenerator element in your web.config file.

To make sure the package is only active in Release mode, set it to  ReleaseModeOnly:

<configuration>
    ...
    <cssSpriteGenerator active="ReleaseModeOnly">
        ...
    </cssSpriteGenerator>
    ...
</configuration>

Processing bigger images

To allow bigger images to be combined into sprites, change the  maxWidth or maxHeight properties on the  imageGroup in your cssSpriteGenerator element, like so: 

<configuration>
    ...
    <cssSpriteGenerator active="Always">
        <imageGroups>
            <add maxWidth="200" maxHeight="300" maxSpriteSize = "30000" samePixelFormat = "true"/>
        </imageGroups>
    </cssSpriteGenerator>
    ...
</configuration>

Image groups are a fundamental concept in the package. When an image is found  on the current page,   in a group of background images, or  in a directory on the server, it is first added to a group. You can set limitations on the images that can be added to a group, such as  maximum width, maximum height, file path, etc. The images in the group are then combined into one or more sprites. If an image doesn't get added to any group, it is simply ignored.

Later on,  you'll see how you can have multiple groups, and post processing options per group.

Shared sprites

By default, the package checks the current page for images to combine into a sprite. However, you might want it to generate sprite(s) for all your small images, irrespective of which page they appear on. That way, you might get a single sprite with all your little icons.

Those sprites can then be shared among your pages.  That way, when your visitor moves to another page on your site, the shared sprite is probably still in their  browser cache, so their browser doesn't need  to retrieve it from your server.

You can get the package to read images not just from the web page, but also from a folder on the web server with the  processFolderImages and imageFolderPathMatch properties. They then get assigned to groups, so you can filter on image width and height, etc.

Assuming your images sit in the directory images on your web server, you'd write something like this:

<configuration>
    ...
    <cssSpriteGenerator active="Always" 
    processFolderImages="true" imageFolderPathMatch="\\images\\" ... >
        <imageGroups>
            <add maxWidth = "100" maxHeight = "100" maxSpriteSize = "30000" samePixelFormat = "true" />
        </imageGroups>
    </cssSpriteGenerator>
    ...
</configuration>

The package now reads images from both the page and the images folder. As before, only those images no wider or higher than 100 pixels will be combined into sprites.

Why the double back slashes in \\images\\imageFolderPathMatch takes a regular expression, and the backslash is a special character in regular expressions, so it needs to be  escaped with another backslash.

Compressing big JPEG images

Many web sites have large photos that would take a lot less bandwidth if  someone compressed them. Compressing images can save a lot of bytes without visible loss of image quality, but often people don't take the time to do it.

You can compress a JPEG image by  adding your photos to a sprite, and then  reducing the image quality of that sprite with the  jpegQuality property. Setting it to 70% often has good results. 

However, you wouldn't want to combine multiple big photo files into a sprite, because you might wind up with very large sprites that are slow to load. To ensure that each photo goes into its own sprite, use the  giveOwnSprite property. 

You can make this all work by using multiple groups:

  • One group for your big photos. These will be compressed and there will be only one image per sprite.
  • Another group for your small images. Here multiple images can be combined into a sprite.

The result looks like this:

<configuration>
    ...
    <cssSpriteGenerator active="Always" >
        <imageGroups>
             <add filePathMatch="\\photos\\" 
                 jpegQuality="70" giveOwnSprite="true" />
            <add maxWidth = "100" maxHeight = "100" maxSpriteSize = "30000" samePixelFormat = "true" />
        </imageGroups>
    </cssSpriteGenerator>
    ...
</configuration>

The first group will only contain images that have \photos\ in their file path. Any image matching this will get its own sprite, which will be reduced to 70% quality. Any images that don't have \photos\ in their file path are then matched against the second group - so they will be combined into sprites if they are no wider or higher then 100 pixels. Images not matching the second group either won't be combined into sprites.

The compressed photos wind up in the ___spritegen directory along with the other generated images. Check their file sizes to see how many bytes you're saving. If you're trying this out on the DemoSite_QuickStart site, you'll find that the file size of the large photo  is reduced from 79.3KB to 58.6KB - a 26% saving without visible loss of quality.

Generating thumbnails

Your site may have thumbnails that link to the full sized photos. If you have a few photos, making the thumbnails yourself isn't hard. But if you have many photos, that becomes laborious.

Or you could use the original image and  use the width and height attributes of the img tag  to reduce its size on the page. However, this causes the browser to load the full sized original  image file, which is not an optimal use of your bandwidth.

To cater for this, the package physically resizes images whose  width and height attributes are different from their physical width and height.

Or you could use the resizeWidth and/or resizeHeight property:

<configuration>
    ...
    <cssSpriteGenerator ... >
        <imageGroups>
             <add filePathMatch="\\photos\\" resizeHeight="200" ... />
        </imageGroups>
    </cssSpriteGenerator>
    ...
</configuration>

Use the pageUrlMatch property to only  resize the photos on your thumbnail pages, and not on the pages with the original images. If your thumbnail page is called thumbnail.aspx, you'd write:

<configuration>
    ...
    <cssSpriteGenerator ... >
        <imageGroups>
             <add filePathMatch="\\photos\\" resizeHeight="200" 
                 pageUrlMatch="thumbnail.aspx" ... />
        </imageGroups>
    </cssSpriteGenerator>
    ...
</configuration>

Configuration

Configuration for the package falls in three categories:

  • Overall Configuration - settings that apply to the package as a whole, such as whether it is active or not, whether it processes page images, etc.
  • Image Groups - determine which images are processed and how.  You've already come across groups in the quick start section.
  • cssImages elements - tell the package which CSS background images to process, and set restrictions on how they can be combined into sprites to cater for repeating background images.

These categories are discussed in the following sections.

Overall Configuration

The package supports these package wide configuration settings:

Overall Switches

Image Gathering

Options that determine how the package finds the images to be processed

Sprite Generation

Options that determine how the package generates sprites


Overall Switches


active

Determines when the package is active. When it is not active, it doesn't affect your site at all and none of the other attributes or child elements listed in this section have any effect.

Value Description
Never Package is never active, irrespective of debug mode
Always Package is always active, irrespective of debug mode
ReleaseModeOnly
(default)
Package is only active in Release mode
DebugModeOnly Package is only active in Debug mode

Example

<cssSpriteGenerator active="Always" >
    ...
</cssSpriteGenerator>

Whether your site is in Debug or Release mode depends on the debug attribute of the compilation element  in your web.config file. If that attribute is set to false, your site is in Release mode (as it should be when live).  When it is set true, it is in Debug mode (as it should be in development). It looks like this in web.config:

<configuration>
    ...
    <system.web>
        <compilation debug="true">
            ...
        </compilation>
        ...
    </system.web>
    ...
</configuration>

By default, the package is only active in Release mode - so you won't see any effect while you're developing.  To ensure that the package is active in both Release mode and in Debug mode, set active to Always, as shown in  the example above.

Note that the active property acts as a master switch for the whole package. If it isn't active, none of the other properties have any effect.  

exceptionOnMissingFile

Determines whether the package throws an exception when an image file is missing.

Value Description
Never
(default)
The package never throws an exception when an image file is missing.
Always The package always throws an exception when an image file is missing.
ReleaseModeOnly The package only throws an exception if the site is in Release mode.
DebugModeOnly The package only throws an exception if the site is in Debug mode.

Example

<cssSpriteGenerator exceptionOnMissingFile="DebugModeOnly" ... >
    ...
</cssSpriteGenerator>

In order to process an image, the package has to actually read that image. What happens if the image file cannot be found on the web server?  That is determined by the exceptionOnMissingFile attribute:

  • If exceptionOnMissingFile is active (see table above) and the package finds that an image file cannot be found,  it throws an exception with the path of the image. That makes it easier to find missing images.
  • If exceptionOnMissingFile is not active, the package doesn't throw an exception and recovers by not processing the image. For example, if the image was found via a img tag, it will leave the tag alone.

If all images should be present in your development environment, than it makes sense to set exceptionOnMissingFile  to DebugModeOnly. That way, you quickly find broken images while developing your site, while preventing exceptions in   your live site where you probably prefer a broken image over an exception.

Keep in mind that if you want exceptions while the site is in Debug mode, you have to ensure that the package is actually  active in Debug mode - set active to Always to make that happen.  


Image Gathering


processPageImages

Determines whether images on the current page are processed

Value Description
Never Images on the current page are never processed
Always
(default)
Images on the current page are always processed
ReleaseModeOnly Images on the current page are processed if the site is in Release mode.
DebugModeOnly Images on the current page are processed if the site is in Debug mode.

Example

<cssSpriteGenerator processPageImages="Never" ... >
</cssSpriteGenerator>

Normally when a page on your site opens, the package finds all the img tags on the page, assigns them to image groups and processes the groups into sprites. It then replaces the img tags with div tags that show that part of the sprite matching the original image - so your page still looks the same but loads quicker (details).

When you switch off processPageImages, the package no longer goes through the page looking for img tags. It also won't  replace any img tags.

You would switch processPageImages off if you only wanted to  process CSS background images.

processCssImages

Determines whether the CSS background images listed in  cssImages are processed

Value Description
Never CSS background images are never processed
Always
(default)
CSS background images are always processed
ReleaseModeOnly CSS background images are processed if the site is in Release mode.
DebugModeOnly CSS background images are processed if the site is in Debug mode.

Example

<cssSpriteGenerator processCssImages="Never" ... >
</cssSpriteGenerator>

If this option is active (the default), than the CSS background images you've listed in  the  cssImages element are processed by the package.

processFolderImages

Determines whether images stored in one or more folders on the web server are processed.  See the sample web site DemoSite_FolderImages in the downloaded solution.

Value Description
Never
(default)
The package never processes images from folders on the web server
Always The package always processes images from folders on the web server
ReleaseModeOnly The package only processes images from folders on the web server if the site is in Release mode
DebugModeOnly The package only processes images from folders on the web server if the site is in Debug mode

Example

<cssSpriteGenerator processFolderImages="Always" ... >
</cssSpriteGenerator>

In addition to having the package find images on the current web page (if processPageImages is active) and from the  cssImages element (if processCssImages is active), you can also get it to find images from one or more folders on the web server.

Why would you want to add images to a sprite that are not actually on the page? Take a very simple web site that uses 3 small icons on its pages, but not all icons appear on all pages:

PageIcons used
default.aspxcontactus.png, cart.png, print.png
contactus.aspxcart.png, print.png
cart.aspxcontactus.png, print.png

If you had the package only read images from the current web page, than it would create different sprites for each page, because each page has a different set of icons. However, it would be far more efficient to have all 3 icons in the one sprite. That way, when a visitor moves from  one page to the other, that single sprite is still in browser cache, so doesn't have to be loaded again over the Internet. The way to do that is to get the package to read all icons when it creates a sprite, not just the ones that are on the current web page.

To get the package to find images from one or more folders on the web server, set processFolderImages active. You then also need to set imageFolderPathMatch to determine specifically which images should be  read.

Just as with images taken from the current web page and from  the cssImages elements, images taken from one or more folders are first assigned to  image groups. Only images assigned to a group can become part of a sprite.

This means that if you want to add particular images to a sprite even if they are not on the current web page and not in the cssImages element, you have to take these steps: 

  1. Set processFolderImages active;
  2. Make sure that the file paths of the desired images match imageFolderPathMatch;
  3. Make sure that the images match a group, so they can be worked into a sprite. The images taken from folders do not all have to match the same group.

For example, if you have a folder images\icons with PNG and GIF images and you want all those images to be made into a sprite irrespective of whether they appeared on the current page, you could use:

<!--switch on processing of images from web server folders, but only process those 
that are in images\icons and that end in PNG or GIF-->
<cssSpriteGenerator processFolderImages="Always" 
imageFolderPathMatch="\\images\\icons\\.*?(png|gif)$" ... >
      <imageGroups>
          ...
          <!--add PNG images that live in images\icons to this group-->
          <add groupName="icons" filePathMatch="\\images\\icons\\.*?png$" />




          <!--add GIF images that live in images\icons to this second group-->
          <add groupName="icons" filePathMatch="\\images\\icons\\.*?gif$" />
      </imageGroups>
</cssSpriteGenerator>

imageFolderPathMatch

If the  processFolderImages property is active, than this property is required. It determines which image files are read.

Type Default
string
(regular expression)
none

Example

<cssSpriteGenerator imageFolderPathMatch="\\images\\img1" ... >
</cssSpriteGenerator>

For details on the folder images feature, see processFolderImages.

The imageFolderPathMatch property is a regular expression.  If processFolderImages is active, than the package looks at the file paths of all PNG, GIF and JPEG files in the root directory of the site and its subdirectories. Those image files whose paths match imageFolderPathMatch will be processed.

For example, to process all image files in directory images\img1, you would use:

<!--all images in images\img1-->
<cssSpriteGenerator imageFolderPathMatch="\\images\\img1\\" ... >
</cssSpriteGenerator>

(it uses double backslashes because the backslash is a special character in regular expressions, so needs to be escaped with another backslash)

If you wanted to process only the PNG files in directory images\img1, you would use:

<!--all PNG images in images\img1-->
<cssSpriteGenerator imageFolderPathMatch="\\images\\img1\\.*?png$" ... >
</cssSpriteGenerator>

If you want to process all image files in the root directory of the site and its subdirectories, use:

<!--all images in the site-->
<cssSpriteGenerator imageFolderPathMatch="\\" ... >
</cssSpriteGenerator>

If your site writes images to the root directory of the site or one of its sub directories (for example if visitors can upload images), than make sure that those images are stored in a directory that isn't matched by  imageFolderPathMatch, otherwise they could wind up in sprites. Those sprites would get bigger with every image added.

Additionally, if your site writes images frequently, such as more than once every 5 minutes, consider storing these images outside the root directory of the site. For faster processing, the package keeps the structure of the root directory and its sub directories in cache. Each time an image is added to the root directory or one of its sub directories, that cache entry is removed to ensure it isn't outdated, meaning that the package has to rebuild that cache entry again.


Sprite Generation


inlineSpriteStyles

Determines whether the styling needed with the div tags that replace the img tags is inline or in a separate .css file.

Value Description
falseAdditional styling is placed in a separate .css file
true
(default)
Additional styling is inline

Example

<cssSpriteGenerator inlineSpriteStyles="false" ... >
</cssSpriteGenerator>

In the section about  how sprites work, you saw how the div tags that replace your img tags use additional styling to show the correct  area of the sprite - using background, width, height, etc.

If you leave inlineSpriteStyles at true, the package inlines all the additional styling. That gives you div tags such as this:

<div style="width: 32px; height: 32px; background: url(/TestSite/___spritegen/2-0-0-90- ... -53.png) -200px -0px; display:inline-block;"></div>

However, if you set inlineSpriteStyles to false,  the additional styling is placed in a separate .css file which is generated in the same directory as the sprite images themselves. The generated div tags will refer to the styling via CSS classes.  This gives you div tags such as this:

<div class="c0___spritegen" >&nbsp;</div>

In that case, make sure that the head tags of your pages have runat="server", so the package can  add a link tag for the .css file to the head section:

<head runat="server">

Here are the advantages/disadvantages of setting inlineSpriteStyles to false:

  • The advantage of using a separate .css file is that the next time the page is loaded, the .css file may still be in browser cache - so all that additional styling doesn't have to be loaded over the Internet. This would apply if your visitors tend to hang around on your site, hitting several pages in the one visit.
  • The drawback of using a separate .css file is that if that file is not in browser cache, the browser has one more file to load. Also, if your web server uses  compression,  the additional inlined styles don't add much to the number of bytes going over the Internet because they are highly compressible.

There are a few cases where it may make sense to place the additional CSS in a separate CSS file by  setting inlineSpriteStyles to false:

  • You're pretty sure that on many visits the separate CSS file will be in browser cache.
  • You use the cssImages element to process CSS background images. In that case, the package always generates a separate CSS file with styles that override existing  CSS styles to make them work with the new sprites. If you now set inlineSpriteStyles to false,  the additional CSS for the div tags will  go into the same CSS file as the CSS for the background images, rather than a separate CSS file. This means you've reduced the size of your .aspx pages by taking out the inlined styles, without incurring an additional CSS file load.

generatedFolder

Sets the folder where the generated .css file (if there is one) and the sprites are stored.

Type Default
string___spritegen

Example

<cssSpriteGenerator generatedFolder="generated\sprites" ... >
</cssSpriteGenerator>

This folder will be created (if it doesn't already exist) in the root folder of the site.

You can include one or more \ in this property. In that case, you'll get a folder and one or more subfolders.

classPostfix

Postfix used to ensure that generated class names are unique.

Type Default
string___spritegen

Example

<cssSpriteGenerator classPostfix="___generated" ... >
</cssSpriteGenerator>

If you set inlineSpriteStyles to false, the package generates a .css file with the additional CSS needed to  make the sprites work. To make sure that the names of the generated CSS classes do not clash with the other CSS classes in your site, the contents of classPostfix is appended to the generated class names.

If you find that the names of the generated CSS classes clash with the names of other CSS classes in your site, you can change classPostfix to fix this.

Image Groups

You use image groups to determine which images will be processed, and how they will be processed. For a full introduction, refer to the  quick start section.

You can have multiple image groups. This is useful when:

  • You want to use different post processing options for specific groups of images.  For example, you might only want to use jpegQuality on images over a given size.
  • You want to make your sprites more cacheable, by having sprites common to all pages or common to a group of pages. That makes it easy for the package to reuse sprites for different pages.

Each group can have the following properties:

Image Filtering

These properties determine which images go into which image group.

Sprite Generation

These properties influence how sprites are generated, such as their image type.

Image Processing

These properties let you manipulate the individual images in the group.

Group Creation

These properties make it easier to manage your image groups.


Image Filtering


maxSize

Sets the maximum size in bytes of images belonging to this group. Images whose file size is larger than this will not be added to this group.

Type Default
Int32 No size limit

Example

<cssSpriteGenerator ... >
      <imageGroups>
          <add maxSize="2000" ... />
      </imageGroups>
</cssSpriteGenerator>

If you use width and/or height attributes in your img tags that are different from the physical width and/or height of the images, read on. Otherwise, you can skip the rest of this section.

If you use width and/or height attributes that are different from the physical width and/or height of the images, the package will auto resize the physical image in memory (not on disk!) before adding it to the sprite, unless  the disableAutoResize property is true  (more details about this feature).

Because of this, the package will estimate the size in bytes of the resized image in order to work out whether to add it to a group. Take this situation:

physical width physical height physical size
physical image 100px 200px 3000 bytes

If you set maxSize to 2000 for a group, than normally this image would not be added because its file size is too big.

Now if you use that image with this tag:

<img src="..." width="100" height="100" />

The image as shown on the page will now have half the height of the physical image. The package than makes a very rough estimate of the file size that this image would have had if it had been physically resized to the given width and height

In this case, the area of the image (width x height) has been halved, so the package divides the physical size of the image by 2:

width in sprite height in sprite estimated size
image in sprite 100px 100px 1500 bytes

Because the estimated size is now only 1500 bytes, it will now be added to a group with maxSize set to 2000.

One last twist here is that the size of the image as it goes into the sprite can not only be set by the width and height properties on the img tag, but also by the resizeWidth and resizeHeight properties of the image group. However, these are only applied after an image has been added to a group, so they are not used to determine whether to add an image to the group in the first place.

maxWidth

Sets the maximum width in pixels of images belonging to this group. Images whose width is larger than this will not be added to this group.

Type Default
Int32 No width limit

Example

<cssSpriteGenerator ... >
      <imageGroups>
          <add maxWidth="50" ... />
      </imageGroups>
</cssSpriteGenerator>

If you used a  width attribute in an img tag, than that width will be used to decide whether the image is not too wide, rather than the physical width of the image (provided you didn't set disableAutoResize to true).

For example, if maxWidth is 50 for a group, than an image that is 60px wide will normally not be included in that group. However, if you had the following image tag, the width property will be used and the image will be included:

<img src="width60px.png" width="50" />

This feature is not meant to encourage you to use width or height properties that are inconsistent with the physical image size. But if you did, than this is how the package will handle this.

maxHeight

Sets the maximum height in pixels of images belonging to this group. Images whose height is larger than this will not be added to this group.

Type Default
Int32 No height limit

Example

<cssSpriteGenerator ... >
      <imageGroups>
          <add maxHeight="50" ... />
      </imageGroups>
</cssSpriteGenerator>

Similar to  maxWidth, if you used a  height property in an img tag, than that height will be used to decide whether the image is not too high, rather than the physical height of the image (provided you didn't set disableAutoResize to true).

maxPixelFormat

Sets the maximum bits per pixel of images belonging to this group. Images that have more bits per pixel than this will not be added to this group. JPEG images will not be added to this group either, irrespective of the value of maxPixelFormat.  

Type Default
PixelFormat No limit

Example

<cssSpriteGenerator ... >
      <imageGroups>
          <add maxPixelFormat="Format8bppIndexed" ... />
      </imageGroups>
</cssSpriteGenerator>

filePathMatch

This is a regular expression (tutorial).  If this is set, images whose file path does not match this will not be included in the group.

Type Default
string
(regular expression)
empty
(no restriction)

Example

<cssSpriteGenerator ... >
      <imageGroups>
          <!--only include GIF and PNG images in the group-->
          <add filePathMatch="(png|gif)$" ... />
      </imageGroups>
</cssSpriteGenerator>

Note that filePathMatch matches against the file path of the image on the web server, not against the url of the image on your site. To only include images in the icons directory,  set filePathMatch to \\icons\\, not to /icons/. You need to double the backslashes (\\), because the backslash is a special character in regular expressions, so needs to be escaped with another backslash.

pageUrlMatch

This is a regular expression. If this is set, than this group is only used if the url of the current page matches this.  If the url of the current page does not match pageUrlMatch, the package acts as though the group doesn't exist.

Type Default
string
(regular expression)
empty
(no restriction)

Example

<cssSpriteGenerator ... >
      <imageGroups>
          <!--do not use this group if the current page has "/excludeme/" in its url-->
          <add pageUrlMatch="^((?!/excludeme/).)*$" ... />
      </imageGroups>
</cssSpriteGenerator>

Note that whereas filePathMatch matches against the file path of an image, pageUrlMatch matches against the absolute url of the current page. To use an image group only with pages in directory special, set pageUrlMatch to /special/, not to \\special\\

The example above shows how to make sure that an image group is used for all pages, except those in a particular directory. As you see, making this happen in a regular expression is a bit awkward (details).

The demo site DemoSite_Gallery in the solution in the download shows how  pageUrlMatch can be used to resize images only on the home page, while keeping their sizes the same on all other pages.


Sprite Generation


maxSpriteSize

Sets the maximum size of a sprite in bytes.

Type Default
Int32 30000

Example

<cssSpriteGenerator ... >
      <imageGroups>
          <add maxSpriteSize="10000" ... />
      </imageGroups>
</cssSpriteGenerator>

If you have a lot of images to put into sprites, it's better to spread them over a number of reasonably sized sprites, rather than one very big sprite. That allows the browser to  load the sprites in parallel.

To achieve this, you can set  maxSpriteSize. While adding images to a sprite, the package keeps track of the total file size of all images added. If that goes over maxSpriteSize, it writes the sprite and starts a new one. As a result, one group could generate multiple sprites.

Note that the package doesn't attempt to work out how big the sprite will be after it has been written to disk - that would take a lot of CPU cycles. It simply adds up the file sizes of the images going into the sprite.

You may have resized one or more images in the group with the resizeWidth and resizeHeight properties,  or with the  width and/or height attributes on the img tag. In that case, the package estimates the file size of the resized image and uses that to calculate the current size of the sprite.

samePixelFormat

Specifies whether all images in a sprite have the same pixel format.

Type Default
bool true

Example

<cssSpriteGenerator ... >
      <imageGroups>
          <add samePixelFormat="false" ... />
      </imageGroups>
</cssSpriteGenerator>

If this property is true, images will be segregated by pixel format when they are combined into a sprite. So images with 4 bits per pixel will go into a sprite with 4 bits per pixel, images with 8 bits per pixel will go into a sprite with 8 bits per pixel, etc. JPEG images will go into a separate sprite.

Setting samePixelFormat to true gives you more sprites, and therefore more request/response overhead, but  also far fewer bytes going over the wire. Assume you have a number of 4 bit per pixel PNG images and an 8 bits per pixel PNG image on your page. If they all go into a single sprite, that sprite will have 8 bits per pixel to accommodate the 8 bits per pixel image. But this means that the original 4 bits per pixel images now take 8 bits per pixel as well. The result is often a sprite that takes more bytes than the total sizes of the individual images.

If it is more important to you to reduce the number of sprites to a minimum, rather than minimize sprites sizes, set samePixelFormat to false.  However, this may give you ballooning sprite sizes when PNG (typically 8 bits per pixel) and JPEG images (24 bits per pixel) are combined.

You can avoid this by segragating images by image type using sameImageType. That way, 4 bits per pixel PNGs and 8 bits per pixel PNGs can be combined into a single sprite, but JPEG images go into their own sprite.

sameImageType

Specifies whether all images in a sprite have the same image type (GIF, PNG or JPEG).

Type Default
bool false

Example

<cssSpriteGenerator ... >
      <imageGroups>
          <add sameImageType="true" ... />
      </imageGroups>
</cssSpriteGenerator>

If this property is true, images will be segregated by image type when they are combined into a sprite. So PNG images will go into a PNG sprite, JPEG images into a JPEG sprite and GIF images into a GIF sprite (assuming you didn't set spriteImageType).

Also see samePixelFormat.

spriteImageType

Sets the image type of the sprite.

Value Description
Png
(default, but see below)
Sprite will be written to disk as a PNG file. Recommended for sprites containing simple icons, drawings, etc.
Gif Sprite will be written to disk as a GIF file. This option is included for completeness. PNG images tend to be more efficient than GIF images, so use Png if you can.
Jpg Sprite will be written to disk as a JPEG file. Recommended for sprites containing compressed photos, etc.

Example

<cssSpriteGenerator ... >
      <imageGroups>
          <add spriteImageType="Jpg" ... />
      </imageGroups>
</cssSpriteGenerator>

Here is how the package works out what image type to use for a sprite:

  1. If spriteImageType has been set, that image type is used.
  2. Otherwise, if all images making up the sprite have the same image type, that image type is used. Also see sameImageType.
  3. Otherwise, PNG is used as the image type.

giveOwnSprite

Lets you give all images in the group a sprite of their own.

Value Description
false
(default)
Images in the group are combined into sprites.
trueInstead of combining images into sprites, each image in the group gets its own sprite.

Example

<cssSpriteGenerator ... >
      <imageGroups>
          <add giveOwnSprite="true" ... />
      </imageGroups>
</cssSpriteGenerator>

The reason you combine images into sprites is to reduce the request/response overhead for the browser  of loading each individual image. For bigger images however, the request/response overhead is not significant, so normally you wouldn't combine those into sprites. Otherwise you could wind up with very big sprites that take a long time to load by the browser.

On the other hand, the package allows you to do all sorts of good things with sprites, such as compressing JPEG sprites, or resizing images to make thumbnails on the fly. It would be good if you could use those features with bigger images as well.

The solution is to add the bigger images to a group and to set  giveOwnSprite to true. That way, the images in the group will all get a sprite of their own, so they are not combined with other images. Than you can use  jpegQuality or pixelFormat to compress the resulting sprite and/or resizeWidth and resizeHeight to resize them - without winding up with massive sprites.

When you look at the html generated by the package, you will find that it generates normal img tags for sprites that contain only one image. This because such a sprite is essentially a normal image, so there is no need for additional CSS.

The demo site DemoSite_CompressedJpeg in the downloaded solution uses the giveOwnSprite property to stop big images from being combined into sprites.


Image Processing


resizeWidth

Lets you set the width of all images in the group. Also see resizeHeight.

Type Default
Int32 Don't resize

Example

<cssSpriteGenerator ... >
      <imageGroups>
          <add resizeWidth="50" ... />
      </imageGroups>
</cssSpriteGenerator>

resizeWidth can be used to create thumbnails on the fly, so you don't have to make them yourself.

Take for example a page "thumbnails.aspx" where you want to show thumbnails of bigger images. You want each thumbnail to be 50px wide. Normally, you would have to create separate thumbnail images - but with resizeWidth you can simply use image tags that refer to the full sized images:

<!--thumbnails.aspx-->
<img src="bigimage1.jpg" />
<img src="bigimage2.jpg" />
<img src="bigimage3.jpg" />
<img src="bigimage4.jpg" />

To resize the big images on the fly so they are only 50px wide, you'd make sure that the JPEG images are included in a group. For that group, set resizeWidth to 50. And make sure that the group is only used for page thumbnails.aspx:

<cssSpriteGenerator ... >
      <imageGroups>
          <add filePathMatch="\.jpg" resizeWidth="50" pageUrlMatch="thumbnails\.aspx$" ... />
      </imageGroups>
</cssSpriteGenerator>

Note that the images are physically resized before they are added to the sprite, so you will get both a smaller image and savings in bandwidth. Your original image files will not be changed though - it all happens in memory.

If that is more convenient, you could also achieve the same smaller size and the same bandwidth savings without using resizeWidth, by simply adding a width property to the image tags (details):

<!--thumbnails.aspx-->
<img src="bigimage1.jpg" width="50" />
<img src="bigimage2.jpg" width="50" />
<img src="bigimage3.jpg" width="50" />
<img src="bigimage4.jpg" width="50" />

resizeHeight

Lets you set the height of all images in the group. Also see resizeWidth.

Type Default
Int32 Don't resize

Example

<cssSpriteGenerator ... >
      <imageGroups>
          <add resizeHeight="100" ... />
      </imageGroups>
</cssSpriteGenerator>

You would use this to generate thumbnails on the fly with a given height, in exactly the same way as you would generate thumbnails with a given width using resizeWidth.

You can combine resizeHeight and resizeWidth. If you use only one, than the package will adjust the other dimension so the image keeps the same aspect ratio. So if you cut the height in half (such as from 200px to 100px), it cuts the width in half as well. If you set both, it simply uses both. For example: 

Original Image Group Resulting Image
WidthHeightresizeWidthresizeHeightWidthHeight
100200not setnot set100200
10020050not set50100
100200not set201020
10020050205020

Note that if you set both resizeWidth and resizeHeight,  you can easily change the aspect ratio of the image, which may not look good.

jpegQuality

Only works if the sprite generated via this group is a JPEG image. In that case, this lets you reduce the image quality, and thereby the file size, of the sprite.

Type Default
Int32 (between 0 and 100) No compression

Example

<cssSpriteGenerator ... >
      <imageGroups>
          <add jpegQuality="70" ... />
      </imageGroups>
</cssSpriteGenerator>

jpegQuality is a percentage of the quality of the sprite as it would have been if you hadn't specified jpegQuality. For example, if you set jpegQuality to 70, than the quality of the sprite will be reduced to 70% of its "natural" quality. This can dramatically reduce the file size of the sprite.

The optimal setting for jpegQuality depends on the sprite - you would determine this through experimentation.  Setting quality higher than 90 may actually result in a greater file size. Values between 50 and 70 tend to give good reductions in size without being too noticable to human eyes.

The best use of jpegQuality is probably to reduce the file size of photos. Image files produced by digital cameras tend to be very big, and can be easily compressed without visible loss of quality.

To effectively compress large JPEG images, you would use these properties:

  1. You may decide you don't want to combine these large images into sprites because the resulting sprites would be very big (although you certainly could). To achieve that you'd set  giveOwnSprite to true.
  2. You want to set filePathMatch to "jpg$", so the group only picks up JPEG images. Because this is a regular expression, you can use this to only select images from particular directories as well.
  3. Finally, set  spriteImageType to"Jpg". Otherwise the images will be converted to PNG images, which for photos is not optimal.

This would result in something like:

<cssSpriteGenerator ... >
      <imageGroups>
          <add jpegQuality="70" giveOwnSprite="true" filePathMatch="jpg$" 
          spriteImageType="Jpg" ... />
      </imageGroups>
</cssSpriteGenerator>

Suppose you want to combine small JPEG images into sprites along with small PNG and GIF images, while compressing the big JPEG images? You can do this by using the fact that the package matches images with whatever group comes first:

<cssSpriteGenerator ... >
      <imageGroups>
          <!--matches all images that are 200px by 300px or smaller-->
          <add maxWidth="200" maxHeight="300"/>




          <!--matches all remaining JPEG images. 
          These images will be bigger than 200px by 300px otherwise they would have 
          matched the preceding group.-->
          <add jpegQuality="70" giveOwnSprite="true" 
          filePathMatch="jpg$" spriteImageType="Jpg" ... />
      </imageGroups>
</cssSpriteGenerator>

The demo site DemoSite_CompressedJpeg in the downloaded solution uses the jpegQuality property to reduce the quality of big jpeg files to 70%.

pixelFormat

Only applies if the sprite is generated as a PNG or GIF image. Sets the pixel format of the sprite.

ValueNbr. ColorsBit Depth
(bits per pixel)
Resulting pixel format
DontCare
(default)
Pixel format of the constituent image with the highest bits per pixel.  Format48bppRgb when combining an image using Format16bppGrayScale with colored images.
Format1bppIndexed21Uses color table with 2 colors (black and white).
Format4bppIndexed164Uses color table with 16 colors.
Format8bppIndexed2568Uses color table with 256 colors.
Format16bppGrayScale65,536
shades of gray
16The color information specifies 65536 shades of gray.
Format16bppRgb55532,768165 bits each for the red, green, and blue components. The remaining bit is not used.
Format16bppRgb56565,536165 bits for the red component, 6 bits for the green component, and 5 bits for the blue component.
Format16bppArgb155532,768165 bits each for the red, green and blue components, and 1 bit is alpha.
Format24bppRgb16,777,216248 bits each for the red, green, and blue components.
Format32bppRgb16,777,216328 bits each for the red, green, and blue components. Remaining 8 bits not used.
Format32bppArgb4,294,967,296328 bits each for the alpha, red, green, and blue components.
Format32bppPArgb4,294,967,296328 bits each for the alpha, red, green, and blue components. The red, green, and blue components are premultiplied, according to the alpha component.

Example

<cssSpriteGenerator ... >
      <imageGroups>
          <add pixelFormat="Format24bppRgb" ... />
      </imageGroups>
</cssSpriteGenerator>

Images in PNG and GIF format can have different pixel formats. This helps in reducing the image file size. For example, if you produce a PNG image in Photoshop that has no more than 16 colors, you would give it a color depth of no more than 16 colors, giving you an image that takes 4 bits per pixel (Format4bppIndexed).  If you used more than 16, you'd have to give it the next higher color depth of 256 colors, taking 8 bits per pixel (Format8bppIndexed).  The higher the color depth, the bigger the file size.

Normally, the package generates sprites with the right pixel format. But sometimes you want to override the pixel format:

  • If the images that went into the sprite have very different colors, you may wind up with a sprite whose color depth is  too low - you'll find out because it looks bad. To fix that, you could try pixel formats with more bits per pixel, to get a greater color depth.

  • You can try pixel formats with fewer bits per pixel to reduce the file size of the sprite. This is especially useful if you combine JPEG images into PNG sprites. JPEG images use 24 bits per pixel, so the resulting sprites will have at least 24 bits per pixel as well. Adding an 8 bit per pixel PNG image to such a sprite means that same image now takes 24 bits per pixel, so unless you change the pixel format, the sprite may be much bigger than the total size of the individual images.

    Also see  samePixelFormat and sameImageType.

The demo site DemoSite_CompressedPng in the downloaded solution uses pixelFormat to reduce the color depth of a sprite.

Pixel format is quite a topic in its own right. Here we'll look at:

Finding out bits per pixel of an image

Finding out the bit depth, dimensions, etc. of an image doesn't require an image editor, at least if you use Windows 7 and possibly Vista:

  1. Right click the image file in Windows Explorer;
  2. Choose Properties;
  3. Open the Details tab.

Combining images with different pixel formats

When the package combines multiple images into the one sprite, those images may have different pixel formats -  one image may use Format4bppIndexed because it has fewer than 16 colors,  while another one may use Format8bppIndexed because it uses more than 16 colors, etc.

To ensure that the constituent images all look good on your page, by default the package sets the pixel format of the sprite to that of the constituent image with the highest pixel format - which would be Format8bppIndexed in the above example. You can override this by setting pixelFormat.

Combining images with same bit depth but different palettes

When you create an image in Photoshop or some other image editing program and you give it a color depth of  16 or 256 colors (corresponding to pixel formats Format4bppIndexed and Format8bppIndexed), the program will create a palette of colors inside the image file with the colors you used in the image.  The 4 or 8 bits for each pixel than form an index into the palette. 

This means that an image with lots of shades of red would have a palette with lots of shades of red. Another image with the same color depth but with lots of shades of blue would have a palette with lots of shaded of blue. So both images would have completely different palettes, even though their color depths are the same.

To cope with this, the package initially creates a sprite image with pixel format Format32bppArgb (32 bits per pixel) and then adds  the constituent images. That way, even if those images have widely different palettes, they will all keep their colors in the inital sprite.

However, to reduce the file size of the sprite, the package  then reduces the color depth of the sprite to that of the constituent image with the most colors. If the image with the highest color depth uses 4 or 8 bits, that means the sprite itself will also use 4 or 8 bits per pixel -  meaning it uses a palette. The challenge then is to come up with a palette that suits the entire sprite, even if the original images had widely different palettes.

The package has a number of clever algorithms built in to work out the colors on the sprite's palette (choose one yourself or let the package choose one for you).  But if the constituent images have widely different colors,  this may not work well and you could wind up with images on the page that  don't look right. In that case, you can force the package to go for a pixel format with more bits per pixel (such as Format24bppRgb), to keep image quality up at the expense of a bigger sprite.

Because of all this, if you have lots of images that use a palette (4 or 8 bits per pixel), it makes sense to group images that have similar colors together -  all the "red" images in one sprite, all the "blue" images in another sprite, etc.

This issue doesn't arise with higher color depths, because in that case the image no longer has a palette.  If you allow 24 bits per pixel or more (pixel format Format24bppRgb or higher), any palette would contain 16,777,216 colors or more - which doesn't make sense. So in that case the bits for each  pixel represent a color directly, rather than a position in a palette.

Resizing changes the pixel format

If you use resizeWidth, resizeHeight or auto resizing, than the package has to resize the image on the fly.

The problem is that the resized image often requires more colors than the original, to give it good color transitions. Because this is a complicated issue that takes many CPU cycles to optimize,  the package keeps it simple and generates a resized image with at least 24 bits per pixel (pixel format Format24bppRgb), to cater for  all the possibly required colors.

As a result, because at least one of the constituent images now has 24 bits per pixel or more, you'll wind up with a sprite that itself has at least 24 bits per pixel ore more as well. However, that could be more than actually necessary. You can set pixelFormat to a lower pixel format, such as  Format8bppIndexed, to see if that reduces the file size of the sprite while maintaining an acceptable image quality.

Group images with similar pixel formats together

If you have 9 simple images that use no more than 16 colors (4 bits per pixel is enough) while a 10th more colorful image  uses more than 16 colors (requires 8 bits per pixel or more), you may want to group only the 9 simple images together.

That way, the resulting sprite takes only 4 bits per pixel.  If you added the 10th more colorful image to the group, that would force the sprite to have 8 bits per pixel - meaning  it would have a greater file size.

Optimizing images

Some images on your site may be unoptimized - such as images that have  a higher color depth than necessary, or that use the JPG format even though they have few colors.

If you combine these unoptimized images into a sprite, the sprite will wind up with a pixel format that  is higher than necessary, causing it to have a higher file size. Rather than editing each icon yourself, you can get the package to effectively do this for  you by setting pixelFormat to a lower pixel format.

Palette based pixel formats not used with some types of shared hosting

As you saw in this section, the package initially creates sprites in a pixel format that doesn't use a palette. It may then try to convert the sprite to a pixel format with only 4 or 8 bits to reduce its file size, meaning it will have a palette.

The problem is that the algorithms that calculate the palette use low level code to access all colors in the sprite so it can work out the optimal palette. This needs to be low level code to make it fast enough. 

However, running this low level code can only be done if your site runs in an environment with  trust level Full.

That is not an issue in your development environment or if you have your own web server - there you always have trust level Full. It even works fine with many cheap shared hosting plans that give you  trust level Full even though your site shares a web server with other sites.

However, some shared hosting plans, such as GoDaddy only give your site trust level  Medium, to ensure that the sites sharing the web server don't affect each other. That prevents the package from using the low level code to work out the palette.

As a result, if your site runs in an account with trust level Medium, it doesn't convert sprites to any pixel format that requires a palette (Format1bppIndexed, Format4bppIndexed or Format8bppIndexed), even if you tell it to by setting pixelFormat. Instead, it will use Format24bppRgb, which doesn't use a palette.

paletteAlgorithm

Lets you choose which algorithm is used to find the optimal palette, when the package  produces a sprite with a pixel format that uses palettes.

Algorithm
Windows Uses palette with standard Windows colors. Very fast. Best choice for images that only use  safe colors.  When target pixel format is Format1bppIndexed (1 bit per pixel), the package always uses this algorithm.
HSBSlower, excellent results with images with many colors
MedianCutSlower, often good results
OctreeQuick, reasonable results
PopularityVery quick, but results are poor with images with many colors
UniformVery qick, but results are poor with images with many colors. Only works when targeting 8 bits per pixel (Format8bppIndexed).  For 4 bits per pixel, the package will use HSB instead of Uniform

More information about these algorithms is  here.

Example

<cssSpriteGenerator ... >
      <imageGroups>
          <add paletteAlgorithm="Windows" ... />
      </imageGroups>
</cssSpriteGenerator>

For details related to the pixel format of the sprites generated from this group, see pixelFormat.

Say you have an image of 1000 colors, and you want to reduce the color depth so it takes only 8 bits per pixel, to reduce its file size. 8 bits per pixel means you'll be using a palette with only 256 colors. What 256 colors will you choose so the new image still looks like the original to human eyes? 

This is obviously a tricky task, especially seeing that the algorithm also needs to minimize CPU usage. People have come up with different algorithms to  achieve this. The issue is that an algorithm that is best in some situations is not necessarily the best in other situations. So rather than locking you into one algorithm, the package allows you to expirement with different algorithms if the default doesn't work for you. 

The demo site DemoSite_CompressedPng in the downloaded solution uses paletteAlgorithm when reducing the color depth of a sprite.

disableAutoResize

Lets you switch off the Auto Resize feature (described below).

Value Description
false
(default)
Images automatically resized according to width and height image tag properties.
trueAuto resizing switched off. Do not combine with giveOwnSprite="false".

Example

<cssSpriteGenerator ... >
      <imageGroups>
          <add disableAutoResize="true" ... />
      </imageGroups>
</cssSpriteGenerator>

As you have seen, when the package turns images into sprites, it replace img tags with div tags - where the div has a width and height that match the width and height of the original image, so it can show precisely the area of the sprite taken by the original image.

However, you may have  an img tag with width and/or height attributes that do not correspond with the  width and height of the physical image. For example:

<!--physical image width is 100px, but shown on page 50px wide-->
<img src="physically100wide.png" width="50" />

The issue is that you cannot resize background images with a CSS tag, like you can with img tags. To overcome this, the package physically resizes the image before adding it to the sprite - a feature called Auto Resize. This happens in memory - your original image file is not affected. It also makes sure that the width and height of the div are as specified by the  width and/or height properties of the img tag.

If you set only the width property in the img tag, or only the height property,  the package will preserve the aspect ratio of the image - so it still looks the same on the page.

If there are multipe img tags on the page referring to the same physical image but with different width and/or height attributes, than the package generates versions for each width/height before adding them to the sprite.

Auto Resize only works with the width and/or height attributes on an img tag.  It doesn't work if you set the width or height in CSS.

Keep in mind that if you use  resizeWidth and/or resizeHeight with your group, those override any width and/or height properties on the img tag - so the Auto Resize feature does not come into play then.

The DemoSite_AutoResized web site in the downloaded solution shows auto resizing in action.

If you want to, you can disable the Auto Resize feature by setting disableAutoResize to true. However, as shown above, that wouldn't work when combining images into sprites. So the package only allows you to do this if you also set giveOwnSprite to true, because in that case the sprite can be shown with an img tag with width and height attributes, rather than with a background image.

It would make sense to disable the Auto Resize feature if your page showed the same physical image a number of times, with different sizes. In that case, you would want to use one physical image rather than multiple resized images - so the browser needs to load only one physical image.


Group Creation


groupName

Sets the name of the group. You can't have more than one group with the same name (but you can have multiple groups without a name).

Type Default
string emtpy

Example

<cssSpriteGenerator ... >
      <imageGroups>
          <add groupName="largejpg" ... />
      </imageGroups>
</cssSpriteGenerator>

You would give a group a name for these reasons:

  • So another group can refer to it using subsetOf.
  • To give it a descriptive name, to make it easier to later on remember what the group is for.

subsetOf

Lets groups inherit properties from other groups.

Type Default
string empty

Example

<cssSpriteGenerator ... >
      <imageGroups>
          <add subsetOf="basegroup" ... />
          <add groupName="basegroup" ... />
      </imageGroups>
</cssSpriteGenerator>

When you set subsetOf for a group to the name of some other group, the group inherits all properties from the other group - except for the ones that it sets itself.

Take for example:

<cssSpriteGenerator ... >
      <imageGroups>
          <add subsetOf="pnggroup" filePathMatch="\.jpg" />
          <add groupName="pnggroup" maxHeight="100" filePathMatch="\.png" />
      </imageGroups>
</cssSpriteGenerator>

The lowest group has maxHeight set to 100 and filePathMatch set to \.png. So it matches  PNG files that are not higher than 100px.

The group above it inherits from pnggroup. It doesn't set maxHeight itself, so it inherits that from pnggroup.  But it does set filePathMatch to \.jpg, thereby overriding the filePathMatch it gets from  pnggroup. As a result, it matches JPEG files that are no higher than 100px.

CSS Background Images

The package has the ability to combine CSS background images into sprites, to compress them, etc. This is very useful because background images tend to be very small which means that their request/response  overhead is proportionally high,  so there is a lot to be gained from combining them into sprites.

The cssImages element

Whereas the package can interpret the current page to find all the images used there, it can't yet interpret CSS files. To overcome this, the package lets you specify the background images you want processed in cssImages elements. 

Here is what this looks like this:

<cssSpriteGenerator ... >
      <cssImages>
          <add imageUrl="css/img/logo.png" cssSelector=".backgrounglogo" ... />
          <add imageUrl="css/img/biglogo.png" cssSelector=".backgroungbiglogo" ... />
          ...
    </cssImages>
</cssSpriteGenerator>

As you see here, the package also needs the CSS selector where the background image is used.  This is because it generates additional CSS statements that partly override the original CSS, to  ensure that the background image still shows up correctly on the page after it has been put into a sprite. 

After the images have been read from the cssImages elements, they are processed the same way as  images read from the page. This  means that some background images could be combined with images used in img tags. It also means they need to be matched to an  image group before they can be put into a sprite.

For example, if you wanted to place the two "logo" background images in their own group, you could use a group with the filePathMatch property, like this:

<cssSpriteGenerator ... >
      <cssImages>
          <add cssSelector=".backgrounglogo" imageUrl="css/img/logo.png" ... />
          <add cssSelector=".backgroungbiglogo" imageUrl="css/img/biglogo.png" ... />
          ...
    </cssImages>
    <imageGroups>
          <!--only include images ending in logo.png in the group-->
          <add filePathMatch="logo\.png$" ... />
    </imageGroups>
</cssSpriteGenerator>

In addition to the  imageUrl and cssSelector properties,  the combineRestriction property caters for background images that  are repeated horizontally or vertically, and the  backgroundAlignment property caters for background images used with the  sliding door technique.

The description of the imageUrl property shows in detail how to create a cssImages element based on a CSS style.

runat=server in head tag

If you use  cssImages elements to process CSS background images,  the package will always generate a separate .css file to override existing  CSS styles to make them work with the new sprites. 

So the package can  add a link tag for the .css file to the head section, make sure that the head tags of your pages have runat="server":

<head runat="server">

Properties Index

cssImages elements have the following properties:

imageUrl (required)

The url of the background image to be processed.  This can be an absolute url or a url relative to the root of the web site - but not a url that is relative to the directory of the CSS file.

Type Default
stringnone

Example

<cssSpriteGenerator ... >
      <cssImages>
          <add imageUrl="css/img/logo.png" ... />
    </cssImages>
</cssSpriteGenerator>

As an example, suppose you have a CSS file site.css in directory css with the following CSS:

.backgrounglogo 
{
    height: 32px; width: 32px;
    background: #000000 url(img/logo.png);
}

To have the logo.png image combined into a sprite, you would take these steps:

  1. Create a new entry in cssImages:
    <cssImages>
        ...
        <add />
    </cssImages>
    
  2. Add the CSS selector .backgrounglogo:
    <cssImages>
        <add cssSelector=".backgrounglogo" />
    </cssImages>
    

    If you have multiple selectors using the same background image, you need to create an entry for each selector.

  3. Add the url of the background image. 

    Here the image url - img/logo.png - is relative to the directory of the CSS file, which is css. However, the package doesn't know where the CSS file is located, so it needs the image url relative to the root of the web site -  css/img/logo.png:

    <cssImages>
        <add cssSelector=".backgrounglogo" imageUrl="css/img/logo.png" />
    </cssImages>
    
  4. If the style uses a repeating background image, or if you use the sliding door technique,  you may need to add combineRestriction and backgroundAlignment properties - see their descriptions for more details.

  5. Finally, make sure there is an  image group that matches the background image, otherwise it won't be combined into a sprite. If there is no such group yet, add one:
    <imageGroups>
        <add ... />
    </imageGroups>
    <cssImages>
        <add cssSelector=".backgrounglogo" imageUrl="css/img/logo.png" />
    </cssImages>
    

cssSelector (required)

The selector of the style that uses the background image.

Type Default
stringnone

Example

<cssSpriteGenerator ... >
      <cssImages>
          <add cssSelector=".backgrounglogo" ... />
    </cssImages>
</cssSpriteGenerator>

See the discussion at the imageUrl property.

combineRestriction (optional)

Sets restrictions on the way the background image can be combined with other images in a sprite.

Value Description
None
(default)
No combine restrictions
HorizontalOnlyImage will only be combined horizontally, and only with images of same height.  Use with styles that use repeat-y.
VerticalOnlyImage will only be combined vertically, and only with images of same width.  Use with styles that use repeat-x.

Example

<cssSpriteGenerator ... >
      <cssImages>
          <add combineRestriction="VerticalOnly" ... />
    </cssImages>
</cssSpriteGenerator>

Whether to use a combine restriction depends on whether you use a repeating background image:

When usingExampleUse combineRestriction
repeat-xbackground: url(bg.png) repeat-xVerticalOnly
repeat-ybackground: url(bg.png) repeat-yHorizontalOnly

Example for vertically repeating background image

To see how this works, have a look at this screen shot:

This uses the following HTML:

<table cellpadding="10"><tr>
<td><div class="hor-gradient-lightblue">B<br />l<br />u<br />e</div></td>
<td><div class="hor-gradient-red">R<br />e<br />d</div></td>
</tr></table>

With this CSS:

.hor-gradient-lightblue
{
    width: 10px;
    background: #ffffff url(img/gradient-hor-lightblue-w10h1.png) repeat-y;
}




.hor-gradient-red
{
    width: 10px; 
    background: #ffffff url(img/gradient-hor-red-w10h1.png) repeat-y;
}

And these background images, which are both 10px wide - as wide as the div:

gradient-hor-lightblue-w10h1.png
(zoomed in 8 times)
gradient-hor-red-w10h1.png
(zoomed in 8 times)

Because each background image is tiny, it makes perfect sense to combine them into a sprite, so the browser needs to load only one image instead of two. However, you wouldn't want a sprite like this with the images stacked on top of each other:

sprite with images stacked vertically
(zoomed in 8 times)

Because that would produce this less than stellar result:

Both background images now show up in both backgrounds!  We need to tell the package to only combine these background images horizontally. That can be done with the combineRestriction property:

<cssImages>
    ...
    <add ... combineRestriction="HorizontalOnly"/>
    ...
</cssImages>

Combining the background images horizontally gives us this sprite:

sprite with images combined horizontally
(zoomed in 8 times)

This allows the package to generate CSS that shifts the sprite over the visible area:

correct sprite area
shifted over visible area
(zoomed in 4 times)

Wrapping this up, you would use these entries in cssImages for your two background images:

<cssImages>
    ...
    <add imageUrl="css/img/gradient-hor-lightblue-w10h1.png" 
            cssSelector=".hor-gradient-lightblue" 
            combineRestriction="HorizontalOnly"/>
    <add imageUrl="css/img/gradient-hor-red-w10h1.png" 
            cssSelector=".hor-gradient-red" 
            combineRestriction="HorizontalOnly"/>
</cssImages>

Horizontally repeating background image

The story for background images that repeat horizontally instead of vertically is the same, except that  you would set combineRestriction to VerticalOnly, so the images are guaranteed to be stacked vertically in the sprite.

Sprites and narrow background images

So far, the background images have been precisely as wide as the parent div element. What happens if we make the background images narrower, say 5px, while the div is still 10px wide?

Without sprites, the browser will show:

However, if we combine the two 5px wide background images into a sprite:

sprite with 5px wide images combined horizontally
(10px wide, zoomed in 8 times)

Than this is the result if we use that sprite as the background image:

The red background looks fine, but the blue background seems to have combined with the red background! The reason why is obvious when you consider  how the CSS sprite technique works -  the sprite is shifted over the div element so the correct image within that sprite becomes visible. The width and height of the div then ensure that only that correct image is visible.

However, that breaks down here for the blue background image, because the background image that we want to show is 5px wide, while the div is 10px wide. As a result, the red background image to its right shows up as well. It happens to work for the red background image, but only because here the sprite has been shifted 5px to the left and the sprite doesn't contain an image to the right of the red background image. 

Moral of this story: If a background image is both lower and narrower than the div with which is is used, than it cannot be combined into a sprite.

backgroundAlignment (optional)

Determines how the CSS generated by the package aligns the background image.

Value Description
None
(default)
Image will not be aligned
TopImage will be top aligned
BottomImage will be bottom aligned
LeftImage will be left aligned
RightImage will be right aligned

Example

<cssSpriteGenerator ... >
      <cssImages>
          <add backgroundAlignment="Left" ... />
    </cssImages>
</cssSpriteGenerator>

In your CSS, you may be aligning background images, like this:

background: url(img/button-green-left.png) left;

To ensure that the package generates the correct sprite and CSS to cater for alignments,  you need to add not only a combineRestriction property but also a  backgroundAlignment property to your cssImages elements in the following cases:

Background ImageAlignmentExample CSScombineRestrictionbackgroundAlignment
Narrower and as high or higher than parentLeftbackground: url(bg.png) left;VerticalOnlyLeft
As high or higher than parentRightbackground: url(bg.png) right;VerticalOnlyRight
Lower and as wide or wider than parentTopbackground: url(bg.png) top;HorizontalOnlyTop
As wide or wider than parentBottombackground: url(bg.png) bottom;HorizontalOnlyBottom

The parent is for example a div tag that uses the background image. Keep in mind that if the background image is both narrower and lower than the parent element,  it can't be combined with other images into a sprite.

Lets look at a practical example of all this. Have a look at this screen shot:

Both buttons are normally green. When you hover over one, it goes orange.

Here is the HTML for the buttons. Note that rather than using an image per button, both buttons use the same CSS class flexible-width-button - only the text is different.  This uses the sliding door technique, which relies on background image alignment.

<table cellpadding="10"><tr><td>
<div class="flexible-width-button"><a href="delivery.aspx">Delivery</a></div>
</td><td>
<div class="flexible-width-button"><a href="buy.aspx">Buy</a></div>
</td></tr></table>

Here is the CSS class flexible-width-button (some irrelevant bits have been left out). Note that the height of a button is 25px:

div.flexible-width-button {
    background: #ffffff url(img/button-green-left.png) top left no-repeat;
    ...
}




div.flexible-width-button a {
    background: transparent url(img/button-green-right.png) top right no-repeat;
    line-height: 25px;
    ...
}




div.flexible-width-button:hover, div.flexible-width-button:focus {
    background: #ffffff url(img/button-orange-left.png) top left no-repeat;
}




div.flexible-width-button:hover a, div.flexible-width-button:focus a {
    background: transparent url(img/button-orange-right.png) top right no-repeat;
}

The CSS uses these background images:

button-green-left.png button-green-right.png button-orange-left.png button-orange-right.png

All four images are 25px high, which is the same height as their parent elements. button-green-left.png and button-orange-left.png are also wider and they are left aligned, so according to the table above, there is no need to add combineRestriction and  backgroundAlignment to their cssImages elements:

<cssSpriteGenerator ... >
      <cssImages>
          ...
          <add imageUrl="css/img/button-green-left.png" 
               cssSelector="div.flexible-width-button" />
          <add imageUrl="css/img/button-orange-left.png"
               cssSelector="div.flexible-width-button:hover, div.flexible-width-button:focus" />
    </cssImages>
</cssSpriteGenerator>

It's a different story for button-green-right.png and button-orange-right.png: they are both narrower than their parent elements, and they are right aligned. According to the table above, that's two reasons to add not only a combineRestriction but also a  backgroundAlignment to their cssImages elements:

<cssSpriteGenerator ... >
      <cssImages>
          ...
          <add imageUrl="css/img/button-green-right.png" 
               cssSelector="div.flexible-width-button a" 
               combineRestriction="VerticalOnly" backgroundAlignment="Right" />
          <add imageUrl="css/img/button-orange-right.png" 
               cssSelector="div.flexible-width-button:hover a, div.flexible-width-button:focus a" 
               combineRestriction="VerticalOnly" backgroundAlignment="Right" />




          <add imageUrl="css/img/button-green-left.png" 
               cssSelector="div.flexible-width-button" />
          <add imageUrl="css/img/button-orange-left.png"
               cssSelector="div.flexible-width-button:hover, div.flexible-width-button:focus" />
    </cssImages>
</cssSpriteGenerator>

Conclusion

As you see from the length of this article, combining images into sprites looks easy at first sight but then it gets  more complicated!

Why not download the package and give it a try on your site.  I would be interested in your comments and if you like what you saw, please give me a vote.

History

Version Released Description
1.0 3 Aug 2011 Initial release.
1.1 15 Aug 2011 If an image's file size is greater than the  maxSpriteSize of a group, it won't be added to that group, regardless of the group's maxSize.
1.2 10 Jun 2012 Added the  samePixelFormatsameImageType and maxPixelFormat properties. Removed the copiedImgAttributes property. A sprite only containing JPEG images now by default has JPEG image type instead of PNG. Bug fixes.

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+

Comments and Discussions

 
QuestionAre you setting the client cache headers somewhere? PinmemberMinerThreat20-May-13 21:58 
GeneralMy vote of 5 PinmemberRobert Hoffmann3-Apr-13 12:45 
QuestionGithub Pinmemberzcrar7018-Sep-12 0:32 
BugBug #3 PinmemberCirrusABS9-Nov-11 12:16 
BugBug #2 PinmemberCirrusABS8-Nov-11 10:25 
BugFound a bug Pinmembermatthew minke4-Nov-11 7:48 
QuestionParse only first image PinmemberIdanShechter5-Oct-11 3:33 
AnswerRe: Parse only first image PinmemberMatt Perdeck6-Oct-11 12:24 
QuestionDegradation and Images in CSS Pinmemberoriginal_scrabaeus22-Sep-11 14:53 
AnswerRe: Degradation and Images in CSS PinmemberMatt Perdeck23-Sep-11 1:35 
SuggestionHow did you do it, article size Pinmembertobias42124-Aug-11 4:38 
GeneralRe: How did you do it, article size PinmemberPhat (Phillip) H. VU8-Jan-13 20:01 
QuestionScaling? Pinmembergstolarov17-Aug-11 9:44 
AnswerRe: Scaling? PinmemberMatt Perdeck17-Aug-11 13:14 
Questionwill it work in a CMS? Pinmemberrodrigo.ratan8-Aug-11 21:07 
AnswerRe: will it work in a CMS? PinmemberMatt Perdeck8-Aug-11 21:42 
GeneralRe: will it work in a CMS? Pinmemberrodrigo.ratan8-Aug-11 22:22 
GeneralRe: will it work in a CMS? Pinmemberanubis1212122-Dec-11 3:07 
QuestionMVC3 project? PinmemberMember 5181538-Aug-11 14:53 
Bugerror Pinmemberhasankhan6-Aug-11 3:02 
GeneralRe: error PinmemberMatt Perdeck8-Aug-11 3:52 
GeneralRe: error PinmemberMatt Perdeck15-Aug-11 1:51 
GeneralRe: error Pinmemberhasancs8711-Sep-11 19:16 
GeneralRe: error PinmemberMatt Perdeck11-Sep-11 23:42 
QuestionOne question. PinmemberHaBiX5-Aug-11 0:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

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