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

by Matt Perdeck 14. September 2011 20:59

This Series

Introduction

This is part 3 of a 3 part series about the ASP.NET CSS Sprite Generator package. It allows you to compress and resize the images in your ASP.NET web site on the fly, and to combine them into sprites. Simply add the ASP.NET CSS Sprite Generator dll and configure the package in your web.config - no need to change the site itself.

Contents

Part 1

Part 2

Part 3

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.

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
Int64 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).

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
Int64 No limit

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.

spriteImageType

Sets the image type of the sprite.

Value Description
Png
(default)
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 .jpg file. Recommended for sprites containing compressed photos, etc.

Example

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

Because sprites tend to be used to group simple icons, the default image type, Png, is most often want you want. However, if you are combining thumbnails of photos, you may want to set spriteImageType to Jpg. Another reason to use Jpg is if you are using the package to compress big .jpg images, using jpegQuality to compress the images and giveOwnSprite to give each image their own sprite.

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 .jpg 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 .jpg 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 .jpg 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 .jpg 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 .jpg 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 .jpg images into sprites along with small .png and .gif images, while compressing the big .jpg 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 .jpg 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 .jpg images into .png sprites, because the package assumes that .jpg images use many colors and so gives the sprite a greater color depth than it really needs.

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 .jpg 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

This was the last part of this 3 part series. If you haven't given the ASP.NET CSS Sprite Generator package a try yet, now would be a good time! Download it here.

Book: ASP.NET Site Performance Secrets If you enjoyed reading this series, consider buying my book ASP.NET Site Performance Secrets. In a structured approach towards improving performance, it shows how to first identify the most important bottlenecks in your site, and then how to fix them. Very hands on, with a minimum of theory.

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.

Pingbacks and trackbacks (1)+

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading

Books

Book: ASP.NET Site Performance Secrets

ASP.NET Site Performance Secrets

By Matt Perdeck

Details and Purchase

About Matt Perdeck

Matt Perdeck Presenting

Matt has written extensively on ways to improve web site performance.

more >>