Tuesday 1 November 2011

Image Gen, Image Cropper, DAMP, and Sencha with Umbraco


In this post I just want to share some tricks that have helped me with Umbraco and images.

I've been using Image Gen in conjunction with the Umbraco Image Cropper. It's great because you can specify a crop (with Image Cropper), then apply as many sizes of that crop as you want using Image Gen. Imagine you have a 16:9 crop and 4 different sizes of them.

I've also discovered the magic of Digibiz Advanced Media Picker (DAMP). This is a great package which allows you to upload and select images when using documents. Unfortunately the image cropper (when inserted in a document) does not directly work with DAMP, because DAMP saves as XML, and Image Cropper requires a url (eg. /media/123456/file.jpg). So there is a little trick to get that wired up.

Finally, when I was working for Cogworks, a requirement was to use Sencha for device specific image optimisation. This is a great easy to use online service which will download your image, then pass it to the brower in an optimised size for the device.

In this post I will show how to
- Wire up the Umbraco Image cropper to use the Digibiz Advanced Media Picker
- Use ImageGen in a cshtml file to make different sizes for those cropped images
- Use Sencha to make images device optimised

Update: This post assumes you have put your Image Cropper in a document (not media).

Wiring up the Umbraco Image cropper to use the Digibiz Advanced Media Picker 
The Umbraco Image Cropper requires a url (eg. /media/123456/file.jpg). DAMP stores XML. E.g:
<DAMP fullMedia="">
  <mediaItem>
 <Image id="1489" version="23113919-ca7a-4ab2-9e0e-3160cb3168af" parentID="1488" level="3" writerID="0" nodeType="1032" template="0" sortOrder="1" createDate="2011-08-12T12:06:09" updateDate="2011-08-12T12:05:52" nodeName="promo-img" urlName="promo-img" writerName="admin" nodeTypeAlias="Image" path="-1,1243,1488,1489">
   <umbracoFile>/media/36523/promo-img.jpg</umbracoFile>
   <umbracoWidth>1200</umbracoWidth>
   <umbracoHeight>519</umbracoHeight>
   <umbracoBytes>118873</umbracoBytes>
   <umbracoExtension>jpg</umbracoExtension>
   <imageCaption />
 </Image>
  </mediaItem>
</DAMP>
To work around this, all you need is to add a property (in my case a label), and assign the value of umbracoFile to that property on the AfterSave event. Then you can crop as normal.
void Document_AfterSave(Document sender, SaveEventArgs e)
{
 // make sure "umbracoFile" has value of the DAMP parsed umbracoFile
 string dampString = sender.getProperty("dampImage").Value.ToString();
 if (dampString.Contains("<DAMP"))
 {
  string url = XDocument.Parse(dampString).Descendants("umbracoFile").Single().Value;
  sender.getProperty("umbracoFile").Value = url;
 }
 else
 {
  sender.getProperty("umbracoFile").Value = string.Empty;
 }
} 

Now that you have your image uploaded and cropped, wouldn't it be great to be able to render your image like this?
@RenderPage("ShowImage.cshtml", node.Id, "dampImage", "some suffix", "152", "115", "some css class", "some title")
The parameters: node Id, the property alias (DAMP or normal file upload), a suffix for the image, width, height, a css class, a title for the image.



The magic script
The following razor script allows you to render DAMP images using ImageGen and Sencha! The great thing about this script is that it be called from another script or from macro markup. It will give you a default image if the image does not exist. It allows you to pass in a file suffix (useful because image cropper creates new files), define width and height, add css class, and a title! AND it will use Sencha to optimise your images depending on the viewing device!


@{ /* Displays a list of news items. */}
@using System.Linq;
@using umbraco.MacroEngines;
@using System.Xml.Linq;
@{
    DynamicNode d;
    string alias;
    string suffix;
    string width;
    string height;
    string cssClass;
    string title;
      
    if (PageData.Count > 0){
        // get values from PageData   
        d = new DynamicNode(PageData[0]);
        alias = PageData[1];
        suffix = PageData[2];
        width = PageData[3];
        height = PageData[4];
        cssClass = PageData[5];
        title = PageData[6];
    }
    else{
        d = new DynamicNode(@Model.Id);
        alias = @Parameter.PropertyAlias; 
        suffix = @Parameter.Suffix;
        width = @Parameter.ImageWidth;
        height = @Parameter.ImageHeight;
        cssClass = @Parameter.ImageClass;
        title = @Parameter.ImageTitle
    }
   
    string url = string.Empty;
    if(d.GetProperty(alias) != null)
    {
        // get image url or DAMP xml
        string storedUrlValue = d.GetProperty(alias).Value;
        if (!string.IsNullOrEmpty(storedUrlValue))
        {
            if (storedUrlValue.Contains("<DAMP"))
            {
                // parse DAMP xml to get image url 
                url = XDocument.Parse(storedUrlValue).Descendants("umbracoFile").FirstOrDefault().Value.GetUrl(suffix);
            }
            else
            {
                // not DAMP XML, just a url
                url = storedUrlValue.GetUrl(@suffix);
            }
        }
        else
        {
            // no value so render a default image
            <img src="/images/no-image.png"  alt="No-Image" />
            return;
        }

        // add image gen parameters
        url += "&width=" + width + "&height=" + height + "&constrain=true&compression=100";

        // get host - check for https if you wish
        var host = string.Format("http://{0}/", HttpContext.Current.Request.ServerVariables["HTTP_HOST"]);

        // finally render the image
        <img src="http://src.sencha.io/jpg95/@host/ImageGen.ashx?image=@url" alt="@title" class="@cssClass" />
    }
    else
    {
        <img src="/images/no-image.png"  alt="No-Image" />
    }
}

Here is the extension method for adding a suffix to the filename
public static string GetUrl(this string s, string suffix)
{
     return string.Format("{0}/{1}{2}{3}", Path.GetDirectoryName(s).Replace("\\", "/"), Path.GetFileNameWithoutExtension(s), suffix, Path.GetExtension(s));
}
I hope you find this useful. I definitely use it in every project... with the odd tweak here and there :)

3 comments:

  1. Hello,

    Thanks for this blog post. In your code do you use the Image Cropper on the documenttype? I always add the crop to the mediatype (and use custom mediatypes for this). A mediatype already has the umbracoFile property so you don't need to do anything special. Here you can see a demo how this will look: http://www.screenr.com/gz0s If you want more info about this I will demo it at the Umbraco UK festival.

    Also I see in your Razor code you don't use any code which is created for Umbraco like the DynamicXml code. Here are some blogposts about it:

    http://umbraco.com/follow-us/blog-archive/2011/2/28/umbraco-razor-feature-walkthrough-%E2%80%93-part-3

    http://umbraco.com/follow-us/blog-archive/2011/9/15/umbraco-razor-feature-walkthrough%E2%80%93part-6.aspx


    If you want some examples on how easy it is to get the umbracoFile you can install the DAMP 2.0 Samples package. This install also has a Razor file with some code. You can download it here: http://our.umbraco.org/FileDownload?id=3145

    Thanks for writting this blogpost. It's very useful!

    Regards,
    Jeroen Breuer

    ReplyDelete
  2. Hey Jeroen

    Yes it is on a document. One project specifically required image cropping in a document, not in the media section. BUT required media to be uploaded through the document in the content section. Hence using DAMP. That is when the AfterSave code was born back in Umbraco 4.5.2 :)

    Yes, when uploading in the Media section, the Image cropper will work out of the box if it is on the media node.

    Regarding DynamicXml...

    The macro script has 2 modes which allows being called from <umbraco:macro /> or using @RenderPage from another cshtml file.

    I instantiate a DynamicNode using either PageData[0] or Model.Id. Although you can do Model.SomeProperty.SomeArray[0].someAttrib etc, you cannot do it on an instantiated DynamicNode.

    I normally deal with an instantiated DynamicNode over Model because I like the intellisense, and I have extension methods. Model does not like extension methods. There is also an apparent performance hit when using Model.

    ReplyDelete