Using T4 Templates for caching image resources

In my project I'm doing a lot of native UI rendering1, soon I will blog on why.
Naturally I have many images to render using DrawImage methods, that requires an Image instance.
There are couple of ways to load an image but the best2 and easies option is using a Resource File, just dragging and dropping the images into the resource designer will add the file to the project and generate a nice static property by the name of the file in the static class generated by the name of the resource file.
The problem is that each time image resource property is called a new image instance is created with the requested image, so if the same image is used in multiple places3 it will be loaded multiple times in memory4.
 

The obvious solution is to cache the returned image and reuse the same instance.
Not dwell in the different considerations I needed the simplest cache that lazy loads the objects and hold them as long as the application is running. Using a simple dictionary is the obvious choice but it creates a lot of work around it when adding/removing the images used in the project, this is where T4 Templates come into play.
 

T4 Templates

From MSDN: In Visual Studio, a T4 text template is a mixture of text blocks and control logic that can generate a text file. The control logic is written as fragments of program code in Visual C# or Visual Basic. The generated file can be text of any kind, such as a Web page, or a resource file, or program source code in any language.
 

Cached Resources T4 Template

For my propose T4 Template is a script file that is located in the C# project next to the resource "Resources.resx" file and has the same name "Resources.tt" apart from the extension as it needs to be ".tt".
When the template is executed, either by context menu "Run Custom Tool" or saving the template file, it generate a new ".cs" file containing static "CachedResources" class with property for every image resource in the same way they are generated on .NET resource file, the difference is that on first call to the property it retrieves the image from the resource manager and caches it in a private static field of the class, all subsequent calls will return the cached instance.
This way the image is cached and not loaded multiple times and also there is no dictionary overhead as each cached image has its own cache field.
 

public static Bitmap  SomeImage      
{      
    get        
    {      
        if(_SomeImage  == null)      
            _UnreadIcon  = (Bitmap)Resources.ResourceManager.GetObject("SomeImage");      
        return  _SomeImage;      
    }      
}      

 

Template

<#@  template debug="false" hostspecific="true"  language="C#" #>      
<#@ output  extension=".cs"  encoding="UTF8" #>      
<#@  assembly name="System.Core" #>      
<#@  assembly name="System.Xml" #>      
<#@  assembly name="System.Xml.Linq" #>      
<#@ import  namespace="System.IO" #>      
<#@ import  namespace="System.Xml" #>      
<#@ import  namespace="System.Xml.Linq" #>      
<#      
//      
//  Configurations:      
// ----      
bool  useCulture = false;      
string appName  = "CachedResources";      
string version  = "1.0.1.0";      
string  accessibility = "public";      
string ns =  (string)System.Runtime.Remoting.Messaging.CallContext.LogicalGetData("NamespaceHint");      
string  resxFileName = Path.ChangeExtension(Host.TemplateFile, ".resx");      
string  resxClassName =  Path.GetFileNameWithoutExtension(Host.TemplateFile);      
string  proxyClassName = string.Format("Cached{0}", resxClassName);      
XDocument  document = XDocument.Parse(File.ReadAllText(resxFileName));      

// DO NOT  CHANGE THE TEMPLATE UNDER THIS LINE!!      

#>//  "Therefore those skilled at the unorthodox      
// are  infinite as heaven and earth,      
//  inexhaustible as the great rivers.      
// When they  come to an end,      
// they bagin  again,      
// like the  days and months;      
// they die  and are reborn,      
// like the  four seasons."      
//       
// - Sun Tsu,      
// "The  Art of War"      

// ---      
//  This code was generated by Resource extnsions  template for T4 C#      
//      
//  DO NOT CHANGE THIS FILE!      
//Changes  to this file may cause incorrect behavior and will be lost if the code is  regenerated.      

using  System.Drawing;      

namespace  <#=ns#>      
{      
    // ReSharper disable InconsistentNaming      
    // ReSharper disable ResourceItemNotResolved      

/// <summary>      
    /// Represent a cached resources class for  "<#= resxClassName #>" resources.      
    /// </summary>      
    [System.Diagnostics.DebuggerStepThroughAttribute]      
    [System.CodeDom.Compiler.GeneratedCode("<#= appName #>",  "<#= version #>")]      
    <#= accessibility #> static class  <#= proxyClassName #>      
    {      
        #region Fields and Consts      

<#       
    foreach(var item in  document.Element("root").Elements("data"))       
    {       
        if (IsHandled(item))      
        {      
#>      
        private static <#= GetType(item) #> <#= GetFieldName(item)  #>;      
<#      
        }      
    }      
#>      

        #endregion      

<#       
    foreach(var item in  document.Element("root").Elements("data"))       
    {       
        if (IsHandled(item))      
        {      
#>      
        /// <summary>      
        /// Get the cached bitmap image of  "<#= item.Attribute("name").Value #>"  resource.<br/>      
        /// On first call the image is cached, all subsequent calls will return  the same image object.      
        /// </summary>      
        public static <#= GetType(item) #> <#= GetPropName(item) #>      
        {      
            get       
            {      
            if(<#= GetFieldName(item) #> == null)      
                    <#= GetFieldName(item) #> = (<#= GetType(item) #>)<#=  resxClassName #>.ResourceManager.GetObject("<#= GetName(item)  #>"<#if(useCulture) {#>, <#= resxClassName #>.Culture  <#}#>);      
            return <#= GetFieldName(item) #>;      
            }      
        }      
<#      
        }      
    }      
#>      

        /// <summary>      
        /// Clear all cached images.      
        /// </summary>      
        public static void ClearCache()      
        {      
<#       
        foreach(var item in  document.Element("root").Elements("data"))       
        {       
        if (IsHandled(item))      
        {      
#>      
            var <#= GetName(item) #>2 = <#= GetFieldName(item) #>;      
            <#= GetFieldName(item) #> = null;      
            if( <#= GetName(item) #>2 != null)      
            <#= GetName(item) #>2.Dispose();      

<#      
        }      
    }      
#>      
        }      
    }      

    // ReSharper restore ResourceItemNotResolved      
    // ReSharper restore InconsistentNaming      
}      

<#+      
public bool  IsHandled(XElement item)      
{      
    return item.Value.Contains("System.Drawing.Bitmap") ||  item.Value.Contains("System.Drawing.Icon");      
}      

public string  GetType(XElement item)      
{      
    if(item.Value.Contains("System.Drawing.Bitmap"))      
        return "Bitmap";      
    if(item.Value.Contains("System.Drawing.Icon"))      
        return "Icon";      
    return "Unknown";      
}      

public string  GetName(XElement item)      
{      
    return item.Attribute("name").Value;      
}      

public string  GetPropName(XElement item)      
{      
    var str = item.Attribute("name").Value;      
    return char.ToUpper(str[0]) + str.Substring(1);      
}      

public string  GetFieldName(XElement item)      
{      
    return "_" + item.Attribute("name").Value;      
}      
#>      

 
 


  1. Overwriting the OnPaint method of a control and doing all the UI rendering manually using the past Graphics instance.
      
  2. Loading image from stream requires the stream to remain open during the use of the image or it is possible that for some images the DrawImage method will throw generic GDI exception.
      
  3. Also because Image object has a Finalize method it can remain in memory for quite some time.
      
  4. for reasons beyond my understanding the memory footprint of an image is 7-9 times the size of the image on disk so it can significant. 
Advertisement

4 comments on “Using T4 Templates for caching image resources

  1. […] itself this may look futile as there is no way to lower this overhead**, but as I have explained in my previous post the most common use of images in .NET GUI applications is via resource files (controls use same […]

  2. […] can get the template and read all about it in my “Using T4 Templates for caching image resources” […]

  3. […] itself this may look futile as there is no way to lower this overhead2, but as I have explained in my previous post the most common use of images in .NET GUI applications is via resource files (controls use same […]

  4. […] request will return the same 'Image' instance.   My proposal is to use 'Resources.resx' to add the images to the project and then create 'CachedResources' static class […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s