Using bundling to generate base64 encoded images in CSS

9. juni 2013 14:59 by martijn in .net, bundling, css

Should I use base64?

 

In this era of optimization and mobilization having a lot of request to load your website is considered a bad thing. One of the things you can do is 'inline' your images in your CSS file using a base64 format. This way the browser can get the CSS and the images in one big request.

There are some cons to this :

  • Images are not cached separately and when you change your css the download will be bigger.
  • IE6 and IE7 don't support base64 images in CSS. You can use a fallback tag for these browsers.
  • Your development gets more complex as you have to remember to update the base64 images in your css when you modify an image.
  • Images in css are about 37% larger than in a separate file.
  • Works for images up to 37k in IE8
  • Stylesheets get really large 

Also you might consider using a spritesheet which can be cached seperately (like jQuery UI does for example) or create a separate stylesheet for your inlined-images so your cache will not be invalidated when you change something else in you stylesheet. Some more considerations and opinions at Stackoverflow....

Creating a bundle transform to inline the images

So still interested? Some of the development disadvantages can be avoided by using a bundle transform. A bundle transform is part of the System.Web.Optimization library by Microsoft. The basic idea of bundling is combining multiple css or jss files into one big file at runtime. You can configure System.Web.Optimization to only optimize when you are in release mode so that during development you have separate readably resources. This post is about creating a bundle that replaces background-image:url('/yourimage') which base64 urls during optimization.

As you can see the image is inlined (in the 28796 characters) and a fallback for IE6/IE7 is supplied as well. 

This can be done at development time easily with the web essentials extension by Mads Kristensen. The idea of this transform is to perform the operation at runtime. The main advantage is that your development is not cluttered with having to regenerate images and having optimization only on production code.

Here's the code

So let's get into the details. Sorry for the missing syntax highlighting. I have not fixed that yet.

[code:c#]

 public class CssBaseEncodeTransform : IBundleTransform

    {

        public void Process(BundleContext context, BundleResponse response)

        {

            response.Content = InlineBase64BackgroundImages(response.Content, BundleTable.MapPathMethod);

        }

 

        public static string InlineBase64BackgroundImages(string content, Func<string, string> mappath)

        {

            //find background-images

 

            var parser = new CSSParser();

            var cssDocument = parser.ParseText(content);

            foreach (RuleSet rule in cssDocument.RuleSets)

            {

                var declarations = rule.Declarations.ToList();

                foreach (var declaration in declarations)

                {

                    if (declaration.Name == "background-image")

                    {

                        rule.Declarations.Add(GetIe6Declaration(declaration));

                        EmbedUrlInBackground(declaration,mappath);

                    }

                }

            }

 

            return cssDocument.ToString();

        }

 

        private static Declaration GetIe6Declaration(Declaration declaration)

        {

            var copy = new Declaration();

            copy.Expression = new Expression()

                {

                    Terms = new List<Term>{new Term() {Value = declaration.Expression.Terms.First().Value}}

                };

            copy.Name = "*background-image";

            return copy;

        }

 

        private static void EmbedUrlInBackground(Declaration declaration, Func<string, string> mappath)

        {

            var imageUrl = declaration.Expression.Terms[0].Value;

            var image = Image.FromFile(Path.GetFullPath(mappath(imageUrl)));

            var data = ImageToBase64(image, ImageFormat.Jpeg);

            var url = string.Format(@"data:image/jpg;base64,{0}", data);

            

 

            declaration.Expression.Terms[0].Value = url;

        }

 

 

        public static string ImageToBase64(Image image,ImageFormat format)

        {

            using (MemoryStream ms = new MemoryStream())

            {

                // Convert Image to byte[]

                image.Save(ms, format);

                byte[] imageBytes = ms.ToArray();

 

                // Convert byte[] to Base64 String

                string base64String = Convert.ToBase64String(imageBytes);

                return base64String;

            }

        }

    }

[code]

  1. The code basically looks for background-image declarations in your style sheet.
  2. If found replace the url('/image') with the base64 equivalent.
  3. A *background-image declaration is added for IE6/7 support.

Usage

Just add the transform to the bundle :

The code use the simple css parser project which can be found here (and is included in the zip). The source code is also included at the bottom. Hope is this of some use or inspiration!

Base64Bundling.zip (997.47 kb)