Automatically Resize, Link, and Compress Images Using Sitecore

We had a client who wanted us to redesign their blog website. After having a discussion with the design team, we realized that the existing article images were not correctly sized, and that we had to create multiple aspect ratios for images to be hosted on various devices, like desktops, and mobile, tablet, and Retina-enabled devices.

Another requirement was we had to compress the images so they could render better in low bandwidth devices. The client was fine with manually uploading images because there were thousands of articles and each article had more than 10 images. However, after some time we realized that we also needed to create specifically sized images for the article widgets, but this size ratio could not be determined until the design of the site was finalized. We then had to take over uploading all of the images to the articles.

Considering these points, we started thinking about automating the resizing and compression of images. We used the Sitecore CMS to manage the content of the site, as well as all of our images on the site. Initially, we created a utility to resize the images on the fly by supplying the height and width. We stored high-resolution images with every aspect ratio in the CMS and the utility was able to resize and compress all of them. However, there were issues with this approach. First, the on-the-fly processing was taking time, especially when it was rendering pages with a multitude of images. Second, we lost the CDN advantage and kept resizing the same images again and again in each article request.

After going through this, we realized that it was better to resize and compress every image ratio first, and then store them in the Sitecore image fields to deliver when requested. This allowed us to increase our performance and regain the CDN advantage.

Major Benefits of our Solution

We were able to generate compressed images of different sizes, such as a 6 MB image in 350 KBS with a high-quality display.

We reduced almost 80% of the time of the Sitecore content manager.

We achieved faster article page rendering using the device-based respective image size.

Flow Chart

sitecore1

Screenshot of the Resized Images in Fields

sitecore2

Steps Performed to Accomplish The Task

1. Input the article image fields in Sitecore for every aspect ratio, as well as a respective Boolean flag to turn the resizing on and off. For example:

RatioImage Fields to link (high resolution images)Boolean Flag to turn resizing on and off
1:1Square_2880x2880Square_1920x1920Square_960x960SquareImageResizeAndLink
2:1Wide_4320x4320Wide_2880x1440Wide_1920x960Wide_960x480WideImagesResizeAndLink
8:3Extrawide_4320x1620Extrawide_2880x1080Extrawide_1920x720Extrawide_960x360ExtraWideImagesResizeAndLink

The Boolean flag will turn off after the image is resized so as to avoid accidentally triggering while the item is saving.

2. Create a configuration file to manage image resizing, with specific templates and fields with dimension details.

<CMS.resizeAndLinkImages>
  <ResizeAndLinkImages>
	<ResizeAndLinkImage TemplateIds="{3A29EA5F-26E6-4F1B-B2FB-0EF633F4ECBE}">
  	<MainImages>
    	<MainImage FieldName ="Square_2880x2880" FlagFieldName="SquareImagesResizeAndLink">
      	<LinkToImages>
        	<LinkToImage FieldName="Square_1920x1920" Height="1920" Width="1920"/>
        	<LinkToImage FieldName="Square_960x960" Height="960" Width="960"/>
        	<LinkToImage FieldName="Square_2880x2880" Height="2880" Width="2880"/>
      	</LinkToImages>
    	</MainImage>
    	<MainImage FieldName ="Wide_4320x2160" FlagFieldName="WideImagesResizeAndLink">
      	<LinkToImages>
        	<LinkToImage FieldName="Wide_2880x1440" Height="1440" Width="2880"/>
        	<LinkToImage FieldName="Wide_1920x960" Height="960" Width="1920"/>
        	<LinkToImage FieldName="Wide_960x480" Height="480" Width="960"/>
        	<LinkToImage FieldName="Wide_4320x2160" Height="2160" Width="4320"/>
      	</LinkToImages>
    	</MainImage>
    	<MainImage FieldName ="Extrawide_4320x1620" FlagFieldName="ExtraWideImagesResizeAndLink">
      	<LinkToImages>
        	<LinkToImage FieldName="Extrawide_2880x1080" Height="1080" Width="2880"/>
        	<LinkToImage FieldName="Extrawide_1920x720" Height="720" Width="1920"/>
        	<LinkToImage FieldName="Extrawide_960x360" Height="360" Width="960"/>
        	<LinkToImage FieldName="Extrawide_4320x1620" Height="1620" Width="4320"/>
      	</LinkToImages>
    	</MainImage>
  	</MainImages>
	</ResizeAndLinkImage>
</CMS.resizeAndLinkImages>

3. Write both a class and methods by using Sitecore DLLs and the .Net system. Draw namespace to resize and link images in the Sitecore content items. There is a code snippet below for reference.

The main methods for this are:

  • OnItemSaved: An OnItemSaved event is an entry point to trigger the resize feature. It takes the Sitecore item as EventArgs and manages the editing of the item by using other helper functions like reading configuration, call auto resize, and create new media item function.
  • CompressAndResizeImage: This function returns the byte array of a resized image and inputs the stream, new height, new width, and an image format.
  • CreateMediaItem: This function is used to create a new media item by supplying the stream, CMS path, file name, extension, and CMS DB name.
public void OnItemSaved(object sender, EventArgs args)
    	{
        	global::Sitecore.Events.SitecoreEventArgs eventArgs = args as global::Sitecore.Events.SitecoreEventArgs;
        	global::Sitecore.Diagnostics.Assert.IsNotNull(eventArgs, "eventArgs");
        	global::Sitecore.Data.Items.Item item = eventArgs.Parameters[0] as global::Sitecore.Data.Items.Item;
        	global::Sitecore.Diagnostics.Assert.IsNotNull(item, "item");
 
        	if (item.Database != null && String.Compare(item.Database.Name, this.Database) != 0)
        	{
            	return;
        	}
        	if (_inProcess.Contains(item.ID))
        	{
            	return;
        	}
 
            	var configuration =
                	(ImageResizingConfiguration)ConfigurationManager.GetSection("CMS.resizeAndLinkImages");
                	// fetch configuration value of saved containt item template if any
            	ResizeAndLinkImageElement itemResizeAndLinkImageElement = configuration.ResizeAndLinkImageElementCollection.FirstOrDefault(itemResizeAndLinkImage => itemResizeAndLinkImage.TemplateIds.Contains(item.TemplateID.ToString()));
 
            	global::Sitecore.Diagnostics.Assert.IsNotNull(item.TemplateID, "item.TemplateID");
            	if (itemResizeAndLinkImageElement != null)
            	{
                	_inProcess.Add(item.ID);
                	try
                	{
                    	using (new global::Sitecore.SecurityModel.SecurityDisabler())
                    	{
                        	try
                        	{
                            	// start editing item
item.Editing.BeginEdit();
                            	
                            	foreach (var itemResizeAndLinkImage in itemResizeAndLinkImageElement.MainImages)
                            	{
                                    Sitecore.Data.Fields.CheckboxField flagFieldName =
                                        item.Fields[itemResizeAndLinkImage.FlagFieldName];
                                	if (flagFieldName.Checked)
                                	{
                                        flagFieldName.Checked = false;
                                        Sitecore.Data.Fields.ImageField mainImageFieldName =
                                            item.Fields[itemResizeAndLinkImage.FieldName];
                                    	if (mainImageFieldName != null && mainImageFieldName.MediaItem != null)
                                    	{
                                        	var mainImage =
                                                new Sitecore.Data.Items.MediaItem(mainImageFieldName.MediaItem);
 
                                        	foreach (var itemLinkToImages in itemResizeAndLinkImage.LinkToImages)
                                        	{
                                                string newMediaItemName = string.Format("{0}_{1}_{2}", mainImage.Name,itemLinkToImages.Width.ToString(),itemLinkToImages.Height.ToString ());
                                                string newMediaSiteCorePath = GetSitecorePath(mainImage.Path, mainImage.Name);
                                                Stream stream =
                                                    new MemoryStream(CompressAndResizeImage(mainImage.GetMediaStream(),
      	                                                                       itemLinkToImages.Width, itemLinkToImages.Height, GetImageFormat(mainImage.Extension)));
                                            	// create new media item
            	                                var newMediaItem = CreateMediaItem(stream, newMediaItemName, newMediaSiteCorePath, mainImage.Extension, this.Database);
 
                                                Sitecore.Data.Fields.ImageField imageFieldToLink =
                                                    item.Fields[itemLinkToImages.FieldName];
                                            	// link image field to media
 
                                                imageFieldToLink.MediaID = newMediaItem.ID;
                                        	}
 	                                   }
                                	}
                            	}
                        	}
                        	finally
                        	{
                                item.Editing.EndEdit();
                        	}
                    	}
                	}
                	finally
                	{
                        _inProcess.Remove(item.ID);
                	}
            	}
    	}
 
 
    	public MediaItem CreateMediaItem(System.IO.Stream image, string mediaItemName, string sitecorePath, string extension, string databaseName)
    	{
        	var options = new Sitecore.Resources.Media.MediaCreatorOptions
            	{
                	AlternateText = mediaItemName,
                	FileBased = false,
                	IncludeExtensionInItemName = false,
                	KeepExisting = false,
                	Versioned = false,
                	Destination = sitecorePath + mediaItemName,
                	Database = Sitecore.Configuration.Factory.GetDatabase(databaseName)
            	};
 
        	var creator = new MediaCreator();
        	var mediaItem = creator.CreateFromStream(image, sitecorePath + mediaItemName + extension, options);
        	return mediaItem;
    	}
 
 
    	public byte[] CompressAndResizeImage(System.IO.Stream imageStream, int width, int hight, System.Drawing.Imaging.ImageFormat imageFormat)
    	{
        	try
        	{
            	using (var originalImage = System.Drawing.Image.FromStream(imageStream))
            	{
                	// calculate aspect ratio
                	float xRatio = (float)width / (float) originalImage.Width;
                	float yRatio = (float)hight / (float) originalImage.Height;
                	float ratio = Math.Min(xRatio, yRatio);
 
                	// calculated width and height based on aspect ratio
                	int newWidth = (int)( originalImage.Width * ratio);
                	int newHeight = (int)( originalImage.Height * ratio);
 
                	using (var resizeImage = new System.Drawing.Bitmap(originalImage, newWidth, newHeight))
                	{
                    	using (Graphics newGraphics = Graphics.FromImage(resizeImage))
                    	{
                        	newGraphics.CompositingQuality = CompositingQuality.HighQuality;
                        	newGraphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
                        	newGraphics.SmoothingMode = SmoothingMode.HighQuality;
                        	newGraphics.DrawImage(resizeImage, 0, 0, newWidth, newHeight);
                    	}
 
                    	using (var stream = new System.IO.MemoryStream())
                    	{
                        	resizeImage.Save(stream, imageFormat);
                        	return stream.GetBuffer();
                    	}
                	}
            	}
        	}
        	catch (Exception exp)
        	{
            	Log.Error(exp);
        	}
 
        	return null;
    	}

4. The Sitecore configuration changes to register and trigger the resize class in the OnItemSaved method.

<event name="item:saved">
    	<handler type="Sitecore.Admin.Web.Interface.ResizeImage, Sitecore.Admin.Web.Interface" method="OnItemSaved" >
    	<database>master</database> 	
    	</handler>
  	</event>

After all of this was done, we demonstrated it to the client with Sitecore users running through with some article images and the design team integrating new images into the site. The client highly appreciated the way we automated and achieved the goal, as it would have been a painstaking and costly process to manage manually.

Dinesh Verma

Dinesh Verma

Senior Technical Lead

Dinesh Kumar Verma is a Senior Technical Lead in 3Pillar’s Noida, India office. Dinesh specializes in web development using Microsoft technologies. He has over ten years of adept experience in web-based business application architecting, designing, development, and support. His specialties include C#.NET, ASP.NET, MVC, SQL Server, Sitecore, and Web API. In his present role, he develops and manages software solutions with a team for various business needs using the Agile methodology.

Leave a Reply

Related Posts

3 Cloud Optimization Projects That Will Pay for Themselves i... AWS introduced 1,430 new features and tools in 2017, including 497 in the 4th quarter alone. This means that it can be a challenge for even the mos...
The Connection Between Innovation & Story On this episode of The Innovation Engine, we'll be looking at the connection between story and innovation. Among the topics we'll cover are why story ...
Go Native (App) or Go Home, and Other Key Takeaways from App... I just returned from my first WWDC. I feel like I learned more in a week at Apple’s annual developer’s conference than I have in years of actually dev...
Automated Testing IS Engineering…and Manual Testing is... Automated testing IS engineering. End of story. It’s not testing, it’s not quality assurance (although it's done in support of that), it is engineerin...
Take 3, Scene 26: The Value of Retrospectives On this episode of Take 3, Jonathan Rivers and Alexis Ireland discuss the lessons that Agile development teams can learn when they have retrospectives...