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.
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.
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:
Ratio | Image Fields to link (high resolution images) | Boolean Flag to turn resizing on and off | |||
1:1 | Square_2880x2880 | Square_1920x1920 | Square_960x960 | SquareImageResizeAndLink | |
2:1 | Wide_4320x4320 | Wide_2880x1440 | Wide_1920x960 | Wide_960x480 | WideImagesResizeAndLink |
8:3 | Extrawide_4320x1620 | Extrawide_2880x1080 | Extrawide_1920x720 | Extrawide_960x360 | ExtraWideImagesResizeAndLink |
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:
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.
I really like your approach and I am thinking of implementing this for our site.But could you help me understand how did mapped that which image version would be rendered for which devices