Skip to content

Commit

Permalink
Possible to add custom attributes to img element + prepare for v4 (#23)
Browse files Browse the repository at this point in the history
Introduce the PictureAttributes object as parameter, and add possibility to add custom attributes to img element.
  • Loading branch information
ErikHen committed Jul 26, 2024
1 parent 3c41b08 commit 094ef5b
Show file tree
Hide file tree
Showing 10 changed files with 382 additions and 68 deletions.
115 changes: 113 additions & 2 deletions PictureRenderer/Picture.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Xml.Linq;
using PictureRenderer.Profiles;

namespace PictureRenderer
{
public static class Picture
{

/// <summary>
/// Render picture element.
/// </summary>
[Obsolete("Use method overload that takes PictureAttributes as input parameter instead. This method overload will be removed in next major version.")]
public static string Render(string imagePath, PictureProfileBase profile, LazyLoading lazyLoading)
{
return Render(imagePath, profile, string.Empty, lazyLoading);
Expand All @@ -16,24 +23,30 @@ public static string Render(string imagePath, PictureProfileBase profile, LazyLo
/// <summary>
/// Render different images in the same picture element.
/// </summary>
[Obsolete("Multi-image support will be removed in next major version.")]
public static string Render(string[] imagePaths, PictureProfileBase profile, LazyLoading lazyLoading)
{
return Render(imagePaths, profile, string.Empty, lazyLoading);
}

/// <summary>
/// Render picture element.
/// </summary>
public static string Render(string imagePath, PictureProfileBase profile, (double x, double y) focalPoint)
{
return Render(imagePath, profile, string.Empty, LazyLoading.Browser, focalPoint);
return Render(imagePath, profile, new PictureAttributes(), focalPoint);
}

/// <summary>
/// Render different images in the same picture element.
/// </summary>
[Obsolete("Multi-image support will be removed in next major version.")]
public static string Render(string[] imagePaths, PictureProfileBase profile, (double x, double y)[] focalPoints)
{
return Render(imagePaths, profile, string.Empty, LazyLoading.Browser, focalPoints);
}

[Obsolete("Use method overload that takes PictureAttributes as input parameter instead. This method overload will be removed in next major version.")]
public static string Render(string imagePath, PictureProfileBase profile, string altText, (double x, double y) focalPoints)
{
return Render(imagePath, profile, altText, LazyLoading.Browser, focalPoints);
Expand All @@ -42,11 +55,13 @@ public static string Render(string imagePath, PictureProfileBase profile, string
/// <summary>
/// Render different images in the same picture element.
/// </summary>
[Obsolete("Multi-image support will be removed in next major version.")]
public static string Render(string[] imagePaths, PictureProfileBase profile, string altText, (double x, double y)[] focalPoints)
{
return Render(imagePaths, profile, altText, LazyLoading.Browser, focalPoints);
}

[Obsolete("Use method overload that takes PictureAttributes as input parameter instead. This method overload will be removed in next major version.")]
public static string Render(string imagePath, PictureProfileBase profile, string altText, string cssClass)
{
return Render(imagePath, profile, altText, LazyLoading.Browser, cssClass: cssClass);
Expand All @@ -55,17 +70,54 @@ public static string Render(string imagePath, PictureProfileBase profile, string
/// <summary>
/// Render different images in the same picture element.
/// </summary>
[Obsolete("Multi-image support will be removed in next major version.")]
public static string Render(string[] imagePaths, PictureProfileBase profile, string altText, string cssClass)
{
return Render(imagePaths, profile, altText, LazyLoading.Browser, focalPoints: default, cssClass: cssClass);
}

/// <summary>
/// Render picture element.
/// </summary>
//public static string Render(string imagePath, PictureProfileBase profile, PictureAttributes attributes = null)
//{
// return Render(imagePath, profile, attributes, default);
//}

/// <summary>
/// Render picture element.
/// </summary>
/// <param name="focalPoint">Value range: 0-1 for ImageSharp, 1-[image width/height] for Storyblok.</param>
public static string Render(string imagePath, PictureProfileBase profile, PictureAttributes attributes = null, (double x, double y) focalPoint = default)
{
if (attributes == null)
{
attributes = new PictureAttributes();
}

var pictureData = PictureUtils.GetPictureData(imagePath, profile, attributes.ImgAlt, focalPoint, attributes.ImgClass);

var sourceElement = RenderSourceElement(pictureData);

var sourceElementWebp = string.Empty;
if (!string.IsNullOrEmpty(pictureData.SrcSetWebp))
{
sourceElementWebp = RenderSourceElement(pictureData, ImageFormat.Webp);
}

var imgElement = RenderImgElement(pictureData, profile, attributes);
var pictureElement = $"<picture>{sourceElementWebp}{sourceElement}{imgElement}</picture>"; //Webp source element must be rendered first. Browser selects the first version it supports.
var infoElements = RenderInfoElements(profile, pictureData);

return $"{pictureElement}{infoElements}";
}

/// <summary>
/// Render picture element.
/// </summary>
/// <param name="focalPoint">Value range: 0-1 for ImageSharp, 1-[image width/height] for Storyblok.</param>
/// <returns></returns>
public static string Render(string imagePath, PictureProfileBase profile, string altText = "", LazyLoading lazyLoading = LazyLoading.Browser, (double x, double y) focalPoint = default, string cssClass = "", string imgWidth = "", string style = "")
public static string Render(string imagePath, PictureProfileBase profile, string altText, LazyLoading lazyLoading = LazyLoading.Browser, (double x, double y) focalPoint = default, string cssClass = "", string imgWidth = "", string style = "")
{
var pictureData = PictureUtils.GetPictureData(imagePath, profile, altText, focalPoint, cssClass);

Expand All @@ -87,6 +139,7 @@ public static string Render(string imagePath, PictureProfileBase profile, string
/// <summary>
/// Render different images in the same picture element.
/// </summary>
[Obsolete("Multi-image support will be removed in next major version.")]
public static string Render(string[] imagePaths, PictureProfileBase profile, string altText = "", LazyLoading lazyLoading = LazyLoading.Browser, (double x, double y)[] focalPoints = null, string cssClass = "")
{
var pictureData = PictureUtils.GetMultiImagePictureData(imagePaths, profile, altText, focalPoints, cssClass);
Expand All @@ -98,6 +151,36 @@ public static string Render(string[] imagePaths, PictureProfileBase profile, str
return $"{pictureElement}{infoElements}";
}

private static string RenderImgElement(PictureData pictureData, PictureProfileBase profile, PictureAttributes attributes)
{
var idAttribute = string.IsNullOrEmpty(pictureData.UniqueId) ? string.Empty : $" id=\"{pictureData.UniqueId}\"";
var widthAndHeightAttributes = GetImgWidthAndHeightAttributes(profile, attributes);
var loadingAttribute = attributes.LazyLoading == LazyLoading.Browser ? "loading=\"lazy\" " : string.Empty;
var classAttribute = string.IsNullOrEmpty(pictureData.CssClass) ? string.Empty : $"class=\"{HttpUtility.HtmlEncode(pictureData.CssClass)}\"";
var decodingAttribute = attributes.ImgDecoding == ImageDecoding.None ? string.Empty : $"decoding=\"{Enum.GetName(typeof(ImageDecoding), attributes.ImgDecoding)?.ToLower()}\" ";
var fetchPriorityAttribute = attributes.ImgFetchPriority == FetchPriority.None ? string.Empty : $"fetchPriority=\"{Enum.GetName(typeof(FetchPriority), attributes.ImgFetchPriority)?.ToLower()}\" ";
var additionalAttributes = GetAdditionalAttributes(attributes.ImgAdditionalAttributes);

return $"<img{idAttribute} alt=\"{HttpUtility.HtmlEncode(pictureData.AltText)}\" src=\"{pictureData.ImgSrc}\" {widthAndHeightAttributes}{loadingAttribute}{decodingAttribute}{fetchPriorityAttribute}{classAttribute}{additionalAttributes}/>";
}

private static string GetAdditionalAttributes(Dictionary<string, string> additionalAttributes)
{
var additionalAttributesBuilder = new StringBuilder();
foreach (var key in additionalAttributes.Keys)
{
if (key == "width" || key == "height" || key == "loading" || key == "class" || key == "decoding" || key == "fetchPriority")
{
//these attributes are handled separately, so ignore them if added to additionalAttributes.
continue;
}
additionalAttributesBuilder.Append($"{key}=\"{additionalAttributes[key]}\" ");
}

return additionalAttributesBuilder.ToString();
}

[Obsolete]
private static string RenderImgElement(PictureData pictureData, PictureProfileBase profile, LazyLoading lazyLoading, string imgWidth, string style)
{
var idAttribute = string.IsNullOrEmpty(pictureData.UniqueId) ? string.Empty : $" id=\"{pictureData.UniqueId}\"";
Expand All @@ -111,6 +194,34 @@ private static string RenderImgElement(PictureData pictureData, PictureProfileBa
return $"<img{idAttribute} alt=\"{HttpUtility.HtmlEncode(pictureData.AltText)}\" src=\"{pictureData.ImgSrc}\" {widthAndHeightAttributes}{loadingAttribute}{decodingAttribute}{fetchPriorityAttribute}{classAttribute}{styleAttribute}/>";
}

private static string GetImgWidthAndHeightAttributes(PictureProfileBase profile, PictureAttributes attributes)
{
if (attributes.ImgAdditionalAttributes.TryGetValue("width", out var width))
{
return $"width=\"{width}\" ";
}

if (attributes.RenderImgWidthHeight)
{
var maxWidth = profile.SrcSetWidths.Max();
var widthAttribute = $"width=\"{maxWidth}\" ";
var heightAttribute = "";
if (profile.AspectRatio > 0)
{
heightAttribute = $"height=\"{Math.Round(maxWidth / profile.AspectRatio)}\" ";
}
else if (profile.FixedHeight != null && profile.FixedHeight > 0)
{
heightAttribute = $"height=\"{profile.FixedHeight}\" ";

}
return widthAttribute + heightAttribute;
}

return string.Empty;
}

[Obsolete]
private static string GetImgWidthAndHeightAttributes(PictureProfileBase profile, string imgWidth)
{
if (!string.IsNullOrEmpty(imgWidth))
Expand Down
51 changes: 51 additions & 0 deletions PictureRenderer/PictureAttributes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@

using System.Collections.Generic;

namespace PictureRenderer
{
public class PictureAttributes
{
/// <summary>
/// img element alt attribute
/// </summary>
public string ImgAlt { get; set; }

/// <summary>
/// img element class attribute
/// </summary>
public string ImgClass { get; set; }

/// <summary>
/// img element decoding attribute. Default value: async.
/// </summary>
public ImageDecoding ImgDecoding { get; set; }

/// <summary>
/// img element fetchPriority attribute. Default value: none.
/// </summary>
public FetchPriority ImgFetchPriority { get; set; }

/// <summary>
/// Type of lazy loading. Currently supports browser native or none. Default value: browser native)
/// </summary>
public LazyLoading LazyLoading { get; set; }

/// <summary>
/// If true, width and height attributes will be rendered on the img element.
/// </summary>
public bool RenderImgWidthHeight { get; set; }

/// <summary>
/// May be used to add additional attributes (like data or itemprop attributes) to the img element.
/// </summary>
public Dictionary<string, string> ImgAdditionalAttributes { get; set; }

public PictureAttributes() {
ImgDecoding = ImageDecoding.Async;
ImgFetchPriority = FetchPriority.None;
RenderImgWidthHeight = false;
LazyLoading = LazyLoading.Browser;
ImgAdditionalAttributes = new Dictionary<string, string>();
}
}
}
7 changes: 6 additions & 1 deletion PictureRenderer/PictureRenderer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Version>3.11</Version>
<Version>3.12</Version>
<Authors>Erik Henningson</Authors>
<Company />
<Product />
Expand All @@ -12,8 +12,13 @@
<PackageTags>Picture;element Responsive ImageSharp Storyblok Cloudflare Webp</PackageTags>
<Description>Simplify rendering of HTML picture element. With support for responsive, lazy loaded images in the most optimal format. Works with Storyblok Image Service, Cloudflare Image Resizing, and ImageSharp (used by for example Umbraco CMS and Optimizely CMS).</Description>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>PackageReadMe.md</PackageReadmeFile>
</PropertyGroup>

<ItemGroup>
<None Include="_Build\PackageReadMe.md" Pack="true" PackagePath="\"/>
</ItemGroup>


<ItemGroup>
<None Include="_Build\picture_icon.png">
Expand Down
2 changes: 1 addition & 1 deletion PictureRenderer/PictureUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public static PictureData GetPictureData(string imagePath, PictureProfileBase pr
var pData = new PictureData
{
AltText = altText,
ImgSrc = BuildImageUrl(uri, profile, profile.FallbackWidth, string.Empty, focalPoint),
ImgSrc = BuildImageUrl(uri, profile, profile.SrcSetWidths.Max(), string.Empty, focalPoint),
CssClass = cssClass,
SrcSet = BuildSrcSet(uri, profile, string.Empty, focalPoint),
SizesAttribute = string.Join(", ", profile.Sizes),
Expand Down
6 changes: 5 additions & 1 deletion PictureRenderer/Profiles/PictureProfileBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ public abstract class PictureProfileBase
/// </summary>
public int? Quality { get; set; }



/// <summary>
/// Image width for browsers without support for picture element. Will use the largest image if not set.
/// </summary>
[Obsolete("Setting a custom value for FallbackWidth will not be possible in next major version.")]
public int FallbackWidth
{
get
Expand Down Expand Up @@ -60,16 +61,19 @@ public int FallbackWidth
/// <summary>
/// If true, width and height attributes will be rendered on the img element.
/// </summary>
[Obsolete("Use PictureAttributes object to set this value. Will be removed from profile in next major version.")]
public bool ImgWidthHeight { get; set; }

/// <summary>
/// Img element decoding attribute.
/// </summary>
[Obsolete("Use PictureAttributes object to set this value. Will be removed from profile in next major version.")]
public ImageDecoding ImageDecoding {get; set;}

/// <summary>
/// Img element fetchPriority attribute.
/// </summary>
[Obsolete("Use PictureAttributes object to set this value. Will be removed from profile in next major version.")]
public FetchPriority FetchPriority {get; set;}

public bool ShowInfo { get; set; }
Expand Down
13 changes: 13 additions & 0 deletions PictureRenderer/_Build/PackageReadMe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# ASP.Net Picture Renderer
Makes it easy to optimize images in (pixel) size, quality, file size, and image format.
Images will be responsive, and can be lazy loaded.<br>
It's a light-weight library, suitable for Blazor Webassembly.

The Picture Renderer renders an [HTML picture element](https://webdesign.tutsplus.com/tutorials/quick-tip-how-to-use-html5-picture-for-responsive-images--cms-21015). The picture element presents a set of images in different sizes and formats.
It’s then up to the browser to select the most appropriate image depending on screen resolution, viewport width, network speed, and the rules that you set up.

Picture Renderer works very well together with a CMS where you might not be in control of the exact images that will be used.
The content editor doesn't have to care about what aspect ratio, or size, the image has. The most optimal image will always be used.<br>
Picture Renderer currently works together with [SixLabors/ImageSharp.Web](https://github.com/SixLabors/ImageSharp.Web), [Storyblok's Image service](https://www.storyblok.com/docs/image-service), and [Cloudflare image resizing](https://developers.cloudflare.com/images/image-resizing/).

Read more about Picture Renderer [here](https://github.com/ErikHen/PictureRenderer).
2 changes: 1 addition & 1 deletion PictureRendererTests/CloudflareTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public void RenderWithAltTextTest()
const string expected = "<picture><source srcset=\"/cdn-cgi/image/width=150,format=auto,fit=crop,height=150/https://mydomain.com/myImage.jpg 150w, /cdn-cgi/image/width=300,format=auto,fit=crop,height=300/https://mydomain.com/myImage.jpg 300w\" sizes=\"150px\" /><img alt=\"alt text\" src=\"/cdn-cgi/image/width=300,format=auto,fit=crop,height=300/https://mydomain.com/myImage.jpg\" loading=\"lazy\" decoding=\"async\" /></picture>";
var profile = GetTestImageProfile();

var result = PictureRenderer.Picture.Render("https://mydomain.com/myImage.jpg", profile, "alt text");
var result = PictureRenderer.Picture.Render("https://mydomain.com/myImage.jpg", profile, new PictureAttributes() { ImgAlt = "alt text" });

Assert.Equal(expected, result);
}
Expand Down
Loading

0 comments on commit 094ef5b

Please sign in to comment.