Jun 04 2009

Optimize PNG image or just replace it with silverlight content to minimize page size.

Today I’ve opened my first blog post and firebug showed me that page size is almost 150KB. No, no, no.. too much for a simple page with a couple of lines of text. 

So, I decided to optimize my blog load time. I found that the most traffic consuming part is a blog skin. It's a really huge.

But if you look at it you will fined that it is simple enough to be created as vector graphic. Great, I could use my favorite Silverlight to replace skin images and backgrounds with it!!!

Lets start!

Prerequisites

  1. I will use Visual Studio 2010 Beta for web development
  2. Expression Blend 3 to create a Silverlight control
  3. And of course Silverlight 3 Beta SDK

Picture

Lets take this cute 'Web 2.0' image as our first patient Smile

Really I'm not so good designer Smile so I will use a simplest technic to create 'clone' of this picture. So, lets open Expression Blend 3 and create a new project named Web20Image:

 

 Then add new UserControl named 'Web20.xaml'. Now we are ready to 'clone' our picture.

I put source 'Web 2.0' image to the canvas of the created UserControl, so that I could use it as a base:

 

Then with Pen tool I will add a polyhedron.

Remark: There is no build in polyhedron in Blend so I need to create it manually

 

Now we need to make sides of the polyhedron round. To do it just hold 'alt' key, press left button on middle of side and drug it out.

 

We could remove source 'Web 2.0' image now case we don't need it anymore.
The next step is to color our shape, set border thikness and etc. At the end we should have shape like this:

 

 In the source picture was a gradient area with bevelled side. To create it simply copy shape and past it again. Then remove unnessary vertexes and make a bevel. Set gradient for border and background of the shape. At the end you should get this:

 

The only thing left is a text 'web 2.0'. I don't have a font which was used to create initial picture and I don't want to enlarge size of xap file by adding external font in it. I will use standard font instead. I'd like 'Times New Roman' font.

Also I'd like to add drop shadow effect to the text to make it look same as on source picture. You coulf find it in Miscellaneous section (showed in red ellipse on picture below).

Great! Most difficult part is done and we have silverlight user control which looks like below:

 

If we will compile project now then we find that the size is pretty big. Ok, don't worry we will optimize it.

XAP Optimization

Lets look at xaml code which we got for our shape.

 

   1:  <UserControl
   2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   5:      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   6:      mc:Ignorable="d"
   7:      x:Class="Web20Image.Web20"
   8:      d:DesignWidth="640" d:DesignHeight="480">
   9:      <Canvas>
  10:          <Path Stretch="Fill" 
  11:              Data="M167.83333...125.16666 z" 
  12:              StrokeThickness="2">
  13:              <Path.Stroke>
  14:                  <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
  15:                      <GradientStop Color="#FFCBE248"/>
  16:                      <GradientStop Color="#FF84A718" Offset="1"/>
  17:                  </LinearGradientBrush>
  18:              </Path.Stroke>
  19:              <Path.Fill>
  20:                  <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
  21:                      <GradientStop Color="#FFE1EF97" Offset="0"/>
  22:                      <GradientStop Color="#FFDBEA85" Offset="1"/>
  23:                  </LinearGradientBrush>
  24:              </Path.Fill>
  25:          </Path>
  26:          <Path Stretch="Fill" StrokeThickness="2" 
  27:              Data="M235.95343...119.59759 z" Canvas.Top="17.333" Canvas.Left="16" 
  28:              Visibility="Visible">
  29:              <Path.Fill>
  30:                  <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
  31:                      <GradientStop Color="#FFC5DF33" Offset="0"/>
  32:                      <GradientStop Color="#FF84A718" Offset="1"/>
  33:                      <GradientStop Color="#FFB6D22D" Offset="0.691"/>
  34:                      <GradientStop Color="#FF94B51F" Offset="0.957"/>
  35:                  </LinearGradientBrush>
  36:              </Path.Fill>
  37:              <Path.Stroke>
  38:                  <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
  39:                      <GradientStop Color="#FFC5DF33" Offset="0"/>
  40:                      <GradientStop Color="#FF84A718" Offset="1"/>
  41:                      <GradientStop Color="#FFB6D22D" Offset="0.691"/>
  42:                      <GradientStop Color="#FF94B51F" Offset="0.957"/>
  43:                  </LinearGradientBrush>
  44:              </Path.Stroke>
  45:          </Path>
  46:          <TextBlock Text="web" FontSize="24" Canvas.Top="22" Canvas.Left="25"
  47:              FontFamily="Times New Roman" FontWeight="Bold"
  48:              Foreground="#FFFFFFFF" TextAlignment="Center">
  49:              <TextBlock.Effect>
  50:                  <DropShadowEffect ShadowDepth="1" BlurRadius="1" Color="#FF8B8989"/>
  51:              </TextBlock.Effect>
  52:          </TextBlock>
  53:          <TextBlock Text="2.0" FontSize="32" Canvas.Top="39" Canvas.Left="26"
  54:              FontFamily="Times New Roman" FontWeight="Bold"
  55:              Foreground="#FFFFFFFF" TextAlignment="Center">
  56:              <TextBlock.Effect>
  57:                  <DropShadowEffect ShadowDepth="1" BlurRadius="1" Color="#FF8B8989"/>
  58:              </TextBlock.Effect>
  59:          </TextBlock>
  60:      </Canvas>
61: </UserControl>

 

We see a lot of code here. XAML will be compeleted to the byte representation called BAML but it will have the same structure as XAML so we need to clean-up code of our user control to make it as less as possible. Lets go throw the code now: 

  1. First of all we need to remove lines 6, 7 and 8 case they usefull only for Blend designer
  2. Next Path.Fill and Path.Stroke on lines 29-36 and 37-44 are the same. We could move them to resources
  3. Same as 2. could be done with TextBlock.Effect sections, lines 49-51 and 56-58
  4. And at the and both text boxes has pretty same parameters lets extract them to resources also

At the end we will have next xaml: 

   1:  <UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   2:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   3:      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   4:      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
   5:      <Canvas>
   6:          <Canvas.Resources>
   7:              <LinearGradientBrush x:Key="darkPartBrush" 
   8:                  EndPoint="0.5,1" StartPoint="0.5,0">
   9:                  <GradientStop Color="#FFC5DF33" Offset="0"/>
  10:                  <GradientStop Color="#FF84A718" Offset="1"/>
  11:                  <GradientStop Color="#FFB6D22D" Offset="0.691"/>
  12:                  <GradientStop Color="#FF94B51F" Offset="0.957"/>
  13:              </LinearGradientBrush>
  14:              <DropShadowEffect x:Key="textShadowEffect" ShadowDepth="1" BlurRadius="1" 
  15:                  Color="#FF8B8989"/>
  16:              <Style TargetType="TextBlock" x:Key="textStyle">
  17:                  <Setter Property="FontFamily" Value="Times New Roman"/>
  18:                  <Setter Property="FontWeight" Value="Bold"/>
  19:                  <Setter Property="Foreground" Value="#FFFFFFFF"/>
  20:                  <Setter Property="TextAlignment" Value="Center"/>
  21:                  <Setter Property="Effect" Value="{StaticResource textShadowEffect}"/>
  22:              </Style>
  23:          </Canvas.Resources>
  24:          <Path Stretch="Fill" Data="M167.83333...125.16666 z" StrokeThickness="2">
  25:              <Path.Stroke>
  26:                  <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
  27:                      <GradientStop Color="#FFCBE248"/>
  28:                      <GradientStop Color="#FF84A718" Offset="1"/>
  29:                  </LinearGradientBrush>
  30:              </Path.Stroke>
  31:              <Path.Fill>
  32:                  <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
  33:                      <GradientStop Color="#FFE1EF97" Offset="0"/>
  34:                      <GradientStop Color="#FFDBEA85" Offset="1"/>
  35:                  </LinearGradientBrush>
  36:              </Path.Fill>
  37:          </Path>
  38:          <Path Stretch="Fill" Fill="{StaticResource darkPartBrush}" 
  39:              Stroke="{StaticResource darkPartBrush}" StrokeThickness="2" 
  40:              Data="M235.95343...119.59759 z" Canvas.Top="17.333" Canvas.Left="16"/>
  41:          <TextBlock Text="web" Style="{StaticResource textStyle}" 
  42:              FontSize="24" Canvas.Top="22" Canvas.Left="25"/>
  43:          <TextBlock Text="2.0" Style="{StaticResource textStyle}" 
  44:              FontSize="32" Canvas.Top="39" Canvas.Left="26"/>
  45:      </Canvas>
  46:  </UserControl>
 

 

Looks better now

The next step will be to remove unessary files (which makes no sense for us) from a project. It's next files:

Next step will be to remove all unessary code and comments from app.xaml.cs and web20.xaml.cs. Don't forget to replace 'this.RootVisual = new MainControl();' with 'this.RootVisual = new Web20();' in app.xaml.cs in OnStartup method.

Now we are done. Choose Release and build the project.

Repack XAP

Really XAP file is a simple zip file and during compilation not the best method of compression is used. So unpack files from Web20Image.xap file and repack them againg using highest zip compression.

After repackprocedure I got ~3KB XAP file. PNG picture was ~5,5KB in size. Not bad for such small image Smile

Let's embede XAP file into website skin.

Embede Silverlight plugin into website

It's pretty simple step.

Create ClientBin folder at root of the web site. Copy Web20Image.xap file into that folder.

Add Silverlight.js file to web site. Add reference for it in the site.master headers section.

Then add javascript procedure for errors handling:

   1:  <script type="text/javascript">
   2:          function onSilverlightError(sender, args) {
   3:              if (args.ErrorCode == 8001) {
   4:                   var msg = "This sample application was built with Silverlight 3 Beta.\n";
   5:                   msg += "You currently have Silverlight installed, but not the version required to view this sample.\n\n";
   6:                   msg += "For more information about Silverlight 3, please visit: \n";
   7:                   msg += "<a href=\"http://go.microsoft.com/fwlink/?LinkID=141205\">Silverlight 3 Coming Soon</a>.";
   8:   
   9:                   var hostContainer = document.getElementById("silverlightControlHost");
  10:                   hostContainer.innerHTML = msg;
  11:               }
  12:              
  13:              var appSource = "";
  14:              if (sender != null && sender != 0) {
  15:                  appSource = sender.getHost().Source;
  16:              }
  17:              var errorType = args.ErrorType;
  18:              var iErrorCode = args.ErrorCode;
  19:   
  20:              var errMsg = "Unhandled Error in Silverlight 2 Application " + appSource + "\n";
  21:   
  22:              errMsg += "Code: " + iErrorCode + "    \n";
  23:              errMsg += "Category: " + errorType + "       \n";
  24:              errMsg += "Message: " + args.ErrorMessage + "     \n";
  25:   
  26:              if (errorType == "ParserError") {
  27:                  errMsg += "File: " + args.xamlFile + "     \n";
  28:                  errMsg += "Line: " + args.lineNumber + "     \n";
  29:                  errMsg += "Position: " + args.charPosition + "     \n";
  30:              }
  31:              else if (errorType == "RuntimeError") {
  32:                  if (args.lineNumber != 0) {
  33:                      errMsg += "Line: " + args.lineNumber + "     \n";
  34:                      errMsg += "Position: " + args.charPosition + "     \n";
  35:                  }
  36:                  errMsg += "MethodName: " + args.methodName + "     \n";
  37:              }
  38:   
  39:              throw new Error(errMsg);
  40:          }
  41:      </script>

 

And now place Silverlight Control Host definition:

 

   1:  <div id="web-20">
   2:      <div id="silverlightControlHost">
   3:          <object data="data:application/x-silverlight-2," 
                           type="application/x-silverlight-2" width="95" height="98">
   4:              <param name="source" value="../../ClientBin/Web20Image.xap"/>
   5:              <param name="onerror" value="onSilverlightError" />
   6:              <param name="minRuntimeVersion" value="3.0.40307.0" />
   7:              <param name="background" value="#00FFFFFF" />
   8:              <param name="windowless" value="true" /> 
   9:              <param name="autoUpgrade" value="false" />
  10:          </object>
  11:          <iframe style='visibility:hidden;height:0;width:0;border:0px'></iframe>
  12:      </div>
  13:  </div>

This code is almost usual, but there is some special moment which I need to highlite a little bit.

First thing is that I use Silverlight 3 beta. It's not release version and by default if user has Silverlight 2 installed then he will be forwarded to latest release version download page. Which is actualy Silverlight 2...

To avoid this redirection we set 'minRuntimeVersion' to "3.0.40307.0" (version of the Silverlight 3 beta), 'autoUpgrade' option to false and point 'onerror' to the our javascript function for errors handling. At the begin of the function you could find next block of code:

   1:  if (args.ErrorCode == 8001) {
   2:       var msg = "This sample application was built with Silverlight 3 Beta.\n";
   3:       msg += "You currently have Silverlight installed, but not the version required to view this sample.\n\n";
   4:       msg += "For more information about Silverlight 3, please visit: \n";
   5:       msg += "<a href=\"http://go.microsoft.com/fwlink/?LinkID=141205\">Silverlight 3 Coming Soon</a>.";
   6:   
   7:       var hostContainer = document.getElementById("silverlightControlHost");
   8:       hostContainer.innerHTML = msg;
   9:   }

The 8001 code mean that version of the Silverlight installed on a client computer is lower then 'minRuntimeVersion' value (in our case it is "3.0.40307.0"). In that case message box with description of the problem and with a link to Silverlight download page will be shown to the user.

Summary

Congratulations, now we have page element which looks same as source picture but written with help of Silverlight and has less size then initial image. Sure, 2KB is a not so big difference and added silverlight.js file adds some more kilobytes to our page. But we need to include js file only onec and many silverlight controls will be able to reuse it.

Pingbacks and trackbacks (1)+

Add comment




biuquote
  • Comment
  • Preview
Loading