Skip to content

Instantly share code, notes, and snippets.

@SurinderBhomra
Last active April 20, 2019 20:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SurinderBhomra/f4d03cab9af096d8d13629b891aefbb7 to your computer and use it in GitHub Desktop.
Save SurinderBhomra/f4d03cab9af096d8d13629b891aefbb7 to your computer and use it in GitHub Desktop.
Responsive Images: Convert Image To Picture Tag
using HtmlAgilityPack;
using Site.Common.Kentico;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Web;
namespace SurinderBhomra.Common.Extensions
{
public static class ContentManipulatorExtensions
{
/// <summary>
/// Transforms all image tags to a picture tag inside parsed HTML.
/// All source image URL's need to contain a "width" query parameter in order to have a resize starting point.
/// </summary>
/// <param name="content"></param>
/// <param name="percentageReduction"></param>
/// <param name="minimumWidth">The minimum width an image has to be to warrant resizing.</param>
/// <param name="viewPorts"></param>
/// <returns></returns>
public static string ConvertImageToPictureTag(this string content, int percentageReduction = 10, int minimumWidth = 200, params int[] viewPorts)
{
if (viewPorts?.Length == 0)
throw new Exception("Viewport parameter is required.");
if (!string.IsNullOrEmpty(content))
{
//Create a new document parser object.
HtmlDocument document = new HtmlDocument();
//Load the content.
document.LoadHtml(content);
//Get all image tags.
List<HtmlNode> imageNodes = document.DocumentNode.Descendants("img").ToList();
if (imageNodes.Any())
{
// Loop through all image tags.
foreach (HtmlNode imgNode in imageNodes)
{
// Make sure there is an image source and it is not externally linked.
if (imgNode.Attributes.Contains("src") && !imgNode.Attributes["src"].Value.StartsWith("http", StringComparison.Ordinal))
{
#region Image Attributes - src, class, alt, style
string imageSrc = imgNode.Attributes["src"].Value.Replace("~", string.Empty);
string imageClass = imgNode.Attributes.Contains("class") ? imgNode.Attributes["class"].Value : string.Empty;
string imageAlt = imgNode.Attributes.Contains("alt") ? imgNode.Attributes["alt"].Value : string.Empty;
string imageStyle = imgNode.Attributes.Contains("style") ? imgNode.Attributes["style"].Value : string.Empty;
#endregion
#region If Image Source has a width query parameter, this will be used as the starting size to reduce images
int imageWidth = 0;
UriBuilder imageSrcUri = new UriBuilder($"http://www.surinderbhomra.com{imageSrc}");
NameValueCollection imageSrcQueryParams = HttpUtility.ParseQueryString(imageSrcUri.Query);
if (imageSrcQueryParams?.Count > 0 && !string.IsNullOrEmpty(imageSrcQueryParams["width"]))
imageWidth = int.Parse(imageSrcQueryParams["width"]);
// If there is no width parameter, then we cannot resize this image.
// Might be an older non-responsive image link.
if (imageWidth == 0 || imageWidth <= minimumWidth)
continue;
// Clear the query string from image source.
imageSrc = imageSrc.ClearQueryStrings();
#endregion
// Create picture tag.
HtmlNode pictureNode = document.CreateElement("picture");
if (!string.IsNullOrEmpty(imageStyle))
pictureNode.Attributes.Add("style", imageStyle);
#region Add multiple source tags
StringBuilder sourceHtml = new StringBuilder();
int newImageWidth = imageWidth;
for (int vp = 0; vp < viewPorts.Length; vp++)
{
int viewPort = viewPorts[vp];
// We do not not want to apply the percentage reduction to the first viewport size.
// The first image should always be the original size.
if (vp != 0)
newImageWidth = newImageWidth - (newImageWidth * percentageReduction / 100);
sourceHtml.Append($"<source srcset=\"{imageSrc}?width={newImageWidth}\" data-srcset=\"{imageSrc}?width={newImageWidth}\" media=\"(min-width: {viewPort}px)\">");
}
// Add fallback image.
sourceHtml.Append($"<img src=\"{imageSrc}?width=50\" style=\"width: {imageWidth}px\" class=\"{imageClass} lazyload\" alt=\"{imageAlt}\" />");
pictureNode.InnerHtml = sourceHtml.ToString();
#endregion
// Replace the image node with the new picture node.
imgNode.ParentNode.ReplaceChild(pictureNode, imgNode);
}
}
return document.DocumentNode.OuterHtml;
}
}
return content;
}
}
}
@SurinderBhomra
Copy link
Author

I am referencing a ClearQueryStrings method. The code for this is below:

/// <summary>
/// Returns media url without query string values.
/// </summary>
/// <param name="mediaUrl"></param>
/// <returns></returns>
public static string ClearQueryStrings(this string mediaUrl)
{
    if (mediaUrl.Contains("?"))
        mediaUrl = mediaUrl.Split('?').ToList()[0];

    return mediaUrl.Replace("~", string.Empty);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment