From 19f41b103e400da587f09aec9602474e85b86956 Mon Sep 17 00:00:00 2001 From: Erik Henningson Date: Fri, 19 Jul 2024 21:08:02 +0200 Subject: [PATCH 1/6] Possible to add custom attributes to img element. --- PictureRenderer/Picture.cs | 115 +++++++++++++++++- PictureRenderer/PictureAttributes.cs | 51 ++++++++ PictureRenderer/PictureUtils.cs | 2 +- .../Profiles/PictureProfileBase.cs | 6 +- PictureRendererTests/ImageSharpTests.cs | 77 ++++++++++-- 5 files changed, 238 insertions(+), 13 deletions(-) create mode 100644 PictureRenderer/PictureAttributes.cs diff --git a/PictureRenderer/Picture.cs b/PictureRenderer/Picture.cs index f0125d6..6031d74 100644 --- a/PictureRenderer/Picture.cs +++ b/PictureRenderer/Picture.cs @@ -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 { + + /// + /// Render picture element. + /// + [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); @@ -16,24 +23,30 @@ public static string Render(string imagePath, PictureProfileBase profile, LazyLo /// /// Render different images in the same picture element. /// + [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); } + /// + /// Render picture element. + /// 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); } /// /// Render different images in the same picture element. /// + [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); @@ -42,11 +55,13 @@ public static string Render(string imagePath, PictureProfileBase profile, string /// /// Render different images in the same picture element. /// + [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); @@ -55,17 +70,54 @@ public static string Render(string imagePath, PictureProfileBase profile, string /// /// Render different images in the same picture element. /// + [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); } + /// + /// Render picture element. + /// + //public static string Render(string imagePath, PictureProfileBase profile, PictureAttributes attributes = null) + //{ + // return Render(imagePath, profile, attributes, default); + //} + + /// + /// Render picture element. + /// + /// Value range: 0-1 for ImageSharp, 1-[image width/height] for Storyblok. + 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 = $"{sourceElementWebp}{sourceElement}{imgElement}"; //Webp source element must be rendered first. Browser selects the first version it supports. + var infoElements = RenderInfoElements(profile, pictureData); + + return $"{pictureElement}{infoElements}"; + } + /// /// Render picture element. /// /// Value range: 0-1 for ImageSharp, 1-[image width/height] for Storyblok. /// - 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); @@ -87,6 +139,7 @@ public static string Render(string imagePath, PictureProfileBase profile, string /// /// Render different images in the same picture element. /// + [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); @@ -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 $""; + } + + private static string GetAdditionalAttributes(Dictionary 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}\""; @@ -111,6 +194,34 @@ private static string RenderImgElement(PictureData pictureData, PictureProfileBa return $""; } + 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)) diff --git a/PictureRenderer/PictureAttributes.cs b/PictureRenderer/PictureAttributes.cs new file mode 100644 index 0000000..039d070 --- /dev/null +++ b/PictureRenderer/PictureAttributes.cs @@ -0,0 +1,51 @@ + +using System.Collections.Generic; + +namespace PictureRenderer +{ + public class PictureAttributes + { + /// + /// img element alt attribute + /// + public string ImgAlt { get; set; } + + /// + /// img element class attribute + /// + public string ImgClass { get; set; } + + /// + /// img element decoding attribute. Default value: async. + /// + public ImageDecoding ImgDecoding { get; set; } + + /// + /// img element fetchPriority attribute. Default value: auto + /// + public FetchPriority ImgFetchPriority { get; set; } + + /// + /// Type of lazy loading. Currently supports browser native or none. Default value: browser native) + /// + public LazyLoading LazyLoading { get; set; } + + /// + /// If true, width and height attributes will be rendered on the img element. + /// + public bool RenderImgWidthHeight { get; set; } + + /// + /// May be used to add additional attributes (like data or itemprop attributes) to the img element. + /// + public Dictionary ImgAdditionalAttributes { get; set; } + + public PictureAttributes() { + ImgDecoding = ImageDecoding.Async; + ImgFetchPriority = FetchPriority.None; + RenderImgWidthHeight = false; + LazyLoading = LazyLoading.Browser; + ImgAdditionalAttributes = new Dictionary(); + } + } +} diff --git a/PictureRenderer/PictureUtils.cs b/PictureRenderer/PictureUtils.cs index 2e4ed2e..b1819ed 100644 --- a/PictureRenderer/PictureUtils.cs +++ b/PictureRenderer/PictureUtils.cs @@ -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), diff --git a/PictureRenderer/Profiles/PictureProfileBase.cs b/PictureRenderer/Profiles/PictureProfileBase.cs index 023f864..4931406 100644 --- a/PictureRenderer/Profiles/PictureProfileBase.cs +++ b/PictureRenderer/Profiles/PictureProfileBase.cs @@ -21,11 +21,12 @@ public abstract class PictureProfileBase /// public int? Quality { get; set; } - + /// /// Image width for browsers without support for picture element. Will use the largest image if not set. /// + [Obsolete("Setting a custom value for FallbackWidth will not be possible in next major version.")] public int FallbackWidth { get @@ -60,16 +61,19 @@ public int FallbackWidth /// /// If true, width and height attributes will be rendered on the img element. /// + [Obsolete("Use PictureAttribute instead. Will be removed in next major version.")] public bool ImgWidthHeight { get; set; } /// /// Img element decoding attribute. /// + [Obsolete("Use PictureAttribute instead. Will be removed in next major version.")] public ImageDecoding ImageDecoding {get; set;} /// /// Img element fetchPriority attribute. /// + [Obsolete("Use PictureAttribute instead. Will be removed in next major version.")] public FetchPriority FetchPriority {get; set;} public bool ShowInfo { get; set; } diff --git a/PictureRendererTests/ImageSharpTests.cs b/PictureRendererTests/ImageSharpTests.cs index ea0b4ce..9541dc8 100644 --- a/PictureRendererTests/ImageSharpTests.cs +++ b/PictureRendererTests/ImageSharpTests.cs @@ -26,9 +26,9 @@ public void RenderWithoutWebpTest() } [Fact()] - public void RenderWithWebpTest() + public void RenderWithWebpTestOld() { - const string expected = "\"alt"; + const string expected = "\"alt"; var profile = GetTestImageProfile(); var result = PictureRenderer.Picture.Render("/myImage.jpg", profile, "alt text"); @@ -37,9 +37,20 @@ public void RenderWithWebpTest() } [Fact()] - public void RenderWithStyleTest() + public void RenderWithWebpTest() + { + const string expected = "\"alt"; + var profile = GetTestImageProfile(); + + var result = PictureRenderer.Picture.Render("/myImage.jpg", profile, new PictureAttributes() { ImgAlt = "alt text"}); + + Assert.Equal(expected, result); + } + + [Fact()] + public void RenderWithStyleTestOld() { - const string expected = "\"alt"; + const string expected = "\"alt"; var profile = GetTestImageProfile(); var result = PictureRenderer.Picture.Render("/myImage.jpg", profile, "alt text", style: "float: right;"); @@ -48,7 +59,21 @@ public void RenderWithStyleTest() } [Fact()] - public void RenderWithCssClassAndImageDecodingAuto() + public void RenderWithStyleTest() + { + const string expected = "\"alt"; + var profile = GetTestImageProfile(); + + var attributes = new PictureAttributes() {ImgAlt = "alt text"}; + attributes.ImgAdditionalAttributes.Add("style", "float: right;"); + + var result = PictureRenderer.Picture.Render("/myImage.jpg", profile, attributes); + + Assert.Equal(expected, result); + } + + [Fact()] + public void RenderWithCssClassAndImageDecodingAutoOLD() { const string expected = "\"alt"; var profile = new ImageSharpProfile() @@ -65,7 +90,24 @@ public void RenderWithCssClassAndImageDecodingAuto() } [Fact()] - public void RenderWithWidthAndHeightAndNoDecoding() + public void RenderWithCssClassAndImageDecodingAuto() + { + const string expected = "\"alt"; + var profile = new ImageSharpProfile() + { + SrcSetWidths = new[] { 150, 300 }, + Sizes = new[] { "150px" }, + AspectRatio = 1, + }; + var attributes = new PictureAttributes() { ImgAlt = "alt text", ImgClass = "my-css-class", ImgDecoding = ImageDecoding.Auto}; + + var result = PictureRenderer.Picture.Render("/myImage.jpg", profile, attributes); + + Assert.Equal(expected, result); + } + + [Fact()] + public void RenderWithWidthAndHeightAndNoDecodingOLD() { const string expected = "\"alt"; var profile = new ImageSharpProfile() @@ -82,6 +124,23 @@ public void RenderWithWidthAndHeightAndNoDecoding() Assert.Equal(expected, result); } + [Fact()] + public void RenderWithWidthAndHeightAndNoDecoding() + { + const string expected = "\"alt"; + var profile = new ImageSharpProfile() + { + SrcSetWidths = new[] { 150, 300 }, + Sizes = new[] { "150px" }, + AspectRatio = 1, + }; + var attributes = new PictureAttributes() { ImgAlt = "alt text", RenderImgWidthHeight = true, ImgDecoding = ImageDecoding.None }; + + var result = PictureRenderer.Picture.Render("/myImage.jpg", profile, attributes); + + Assert.Equal(expected, result); + } + [Fact()] public void RenderWithWidthAndHeightAndFetchPriorityNone() { @@ -213,7 +272,7 @@ public void RenderMultiImageWithEmptyFocalPointsTest() [Fact()] public void RenderWithImgWidthTest() { - const string expected = "\"alt"; + const string expected = "\"alt"; var profile = GetTestImageProfile(); var result = PictureRenderer.Picture.Render("/myImage.jpg", profile, "alt text", LazyLoading.Browser, default, "", "50%"); @@ -224,7 +283,7 @@ public void RenderWithImgWidthTest() [Fact()] public void RenderWithQuerystringTest() { - const string expected = "\"alt"; + const string expected = "\"alt"; var profile = GetTestImageProfile(); var result = PictureRenderer.Picture.Render("/myImage.jpg?quality=20", profile, "alt text"); @@ -235,7 +294,7 @@ public void RenderWithQuerystringTest() [Fact()] public void RenderWithDomainTest() { - const string expected = "\"alt"; + const string expected = "\"alt"; var profile = GetTestImageProfile(); var result = PictureRenderer.Picture.Render("https://mydomain.com/myImage.jpg?quality=7", profile, "alt text"); From 1a1a8e38b1a3068d0d5238503eb906d3c8d31546 Mon Sep 17 00:00:00 2001 From: Erik Henningson Date: Fri, 19 Jul 2024 22:02:13 +0200 Subject: [PATCH 2/6] Update unit tests --- PictureRendererTests/CloudflareTests.cs | 2 +- PictureRendererTests/ImageSharpTests.cs | 74 +++++++++++++++++++++++-- PictureRendererTests/StoryblokTests.cs | 4 +- 3 files changed, 70 insertions(+), 10 deletions(-) diff --git a/PictureRendererTests/CloudflareTests.cs b/PictureRendererTests/CloudflareTests.cs index 51d88ed..bce6917 100644 --- a/PictureRendererTests/CloudflareTests.cs +++ b/PictureRendererTests/CloudflareTests.cs @@ -32,7 +32,7 @@ public void RenderWithAltTextTest() const string expected = "\"alt"; 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); } diff --git a/PictureRendererTests/ImageSharpTests.cs b/PictureRendererTests/ImageSharpTests.cs index 9541dc8..183f055 100644 --- a/PictureRendererTests/ImageSharpTests.cs +++ b/PictureRendererTests/ImageSharpTests.cs @@ -142,7 +142,7 @@ public void RenderWithWidthAndHeightAndNoDecoding() } [Fact()] - public void RenderWithWidthAndHeightAndFetchPriorityNone() + public void RenderWithWidthAndHeightAndFetchPriorityNoneOLD() { const string expected = "\"alt"; var profile = new ImageSharpProfile() @@ -160,7 +160,24 @@ public void RenderWithWidthAndHeightAndFetchPriorityNone() } [Fact()] - public void RenderWithWidthAndHeightAndFetchPriorityAuto() + public void RenderWithWidthAndHeightAndFetchPriorityNone() + { + const string expected = "\"alt"; + var profile = new ImageSharpProfile() + { + SrcSetWidths = new[] { 150, 300 }, + Sizes = new[] { "150px" }, + AspectRatio = 1, + }; + var attributes = new PictureAttributes() { ImgAlt = "alt text", RenderImgWidthHeight = true, ImgFetchPriority = FetchPriority.None}; + + var result = PictureRenderer.Picture.Render("/myImage.jpg", profile, attributes); + + Assert.Equal(expected, result); + } + + [Fact()] + public void RenderWithWidthAndHeightAndFetchPriorityAutoOLD() { const string expected = "\"alt"; var profile = new ImageSharpProfile() @@ -177,6 +194,22 @@ public void RenderWithWidthAndHeightAndFetchPriorityAuto() Assert.Equal(expected, result); } + [Fact()] + public void RenderWithWidthAndHeightAndFetchPriorityAuto() + { + const string expected = "\"alt"; + var profile = new ImageSharpProfile() + { + SrcSetWidths = new[] { 150, 300 }, + Sizes = new[] { "150px" }, + AspectRatio = 1, + }; + var attributes = new PictureAttributes() { ImgAlt = "alt text", RenderImgWidthHeight = true, ImgFetchPriority = FetchPriority.Auto }; + var result = PictureRenderer.Picture.Render("/myImage.jpg", profile, attributes); + + Assert.Equal(expected, result); + } + [Fact()] public void RenderWithWidthAndHeightAndFetchPriorityHigh() { @@ -214,7 +247,7 @@ public void RenderWithWidthAndHeightAndFetchPriorityLow() } [Fact()] - public void RenderWithFixedHeight() + public void RenderWithFixedHeightOLD() { const string expected = "\"alt"; var profile = new ImageSharpProfile() @@ -231,6 +264,23 @@ public void RenderWithFixedHeight() Assert.Equal(expected, result); } + [Fact()] + public void RenderWithFixedHeight() + { + const string expected = "\"alt"; + var profile = new ImageSharpProfile() + { + SrcSetWidths = new[] { 150, 300 }, + Sizes = new[] { "150px" }, + FixedHeight = 100, + }; + var attributes = new PictureAttributes() { ImgAlt = "alt text", RenderImgWidthHeight = true }; + + var result = Picture.Render("/myImage.jpg", profile, attributes); + + Assert.Equal(expected, result); + } + [Fact()] public void RenderMultiImageTest() { @@ -270,7 +320,7 @@ public void RenderMultiImageWithEmptyFocalPointsTest() } [Fact()] - public void RenderWithImgWidthTest() + public void RenderWithImgWidthTestOLD() { const string expected = "\"alt"; var profile = GetTestImageProfile(); @@ -280,13 +330,25 @@ public void RenderWithImgWidthTest() Assert.Equal(expected, result); } + [Fact()] + public void RenderWithImgWidthTest() + { + const string expected = "\"alt"; + var profile = GetTestImageProfile(); + var attributes = new PictureAttributes() { ImgAlt = "alt text", LazyLoading = LazyLoading.Browser }; + attributes.ImgAdditionalAttributes.Add("width", "50%"); + var result = PictureRenderer.Picture.Render("/myImage.jpg", profile, attributes); + + Assert.Equal(expected, result); + } + [Fact()] public void RenderWithQuerystringTest() { const string expected = "\"alt"; var profile = GetTestImageProfile(); - var result = PictureRenderer.Picture.Render("/myImage.jpg?quality=20", profile, "alt text"); + var result = PictureRenderer.Picture.Render("/myImage.jpg?quality=20", profile, new PictureAttributes() { ImgAlt = "alt text"}); Assert.Equal(expected, result); } @@ -297,7 +359,7 @@ public void RenderWithDomainTest() const string expected = "\"alt"; var profile = GetTestImageProfile(); - var result = PictureRenderer.Picture.Render("https://mydomain.com/myImage.jpg?quality=7", profile, "alt text"); + var result = PictureRenderer.Picture.Render("https://mydomain.com/myImage.jpg?quality=7", profile, new PictureAttributes() { ImgAlt = "alt text" }); Assert.Equal(expected, result); } diff --git a/PictureRendererTests/StoryblokTests.cs b/PictureRendererTests/StoryblokTests.cs index d4812fb..e1ba38f 100644 --- a/PictureRendererTests/StoryblokTests.cs +++ b/PictureRendererTests/StoryblokTests.cs @@ -45,7 +45,7 @@ public void RenderWithDomainAndAltTest() const string expected = "\"alt"; 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); } @@ -83,10 +83,8 @@ public void RenderWithInfoTest() private static StoryblokProfile GetTestImageProfile() { - //use this to test with both single and multiple images return new StoryblokProfile() { - //MultiImageMediaConditions = new[] { new MediaCondition("(min-width: 1200px)", 400), new MediaCondition("(min-width: 600px)", 200), new MediaCondition("(min-width: 300px)", 100) }, SrcSetWidths = new[] { 150, 300 }, Sizes = new[] { "150px" }, AspectRatio = 1 From 03c0d6a3550603af50c852caddc892405276e87e Mon Sep 17 00:00:00 2001 From: Erik Henningson Date: Wed, 24 Jul 2024 20:56:25 +0200 Subject: [PATCH 3/6] Documentation --- PictureRenderer/PictureAttributes.cs | 2 +- PictureRenderer/PictureRenderer.csproj | 7 +++- PictureRenderer/_Build/PackageReadMe.md | 13 +++++++ README.md | 50 +++++++++++-------------- 4 files changed, 41 insertions(+), 31 deletions(-) create mode 100644 PictureRenderer/_Build/PackageReadMe.md diff --git a/PictureRenderer/PictureAttributes.cs b/PictureRenderer/PictureAttributes.cs index 039d070..175f5a0 100644 --- a/PictureRenderer/PictureAttributes.cs +++ b/PictureRenderer/PictureAttributes.cs @@ -21,7 +21,7 @@ public class PictureAttributes public ImageDecoding ImgDecoding { get; set; } /// - /// img element fetchPriority attribute. Default value: auto + /// img element fetchPriority attribute. Default value: none. /// public FetchPriority ImgFetchPriority { get; set; } diff --git a/PictureRenderer/PictureRenderer.csproj b/PictureRenderer/PictureRenderer.csproj index 4b47a1e..68ae954 100644 --- a/PictureRenderer/PictureRenderer.csproj +++ b/PictureRenderer/PictureRenderer.csproj @@ -2,7 +2,7 @@ netstandard2.0 - 3.11 + 3.12-preview Erik Henningson @@ -12,8 +12,13 @@ Picture;element Responsive ImageSharp Storyblok Cloudflare Webp 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). MIT + PackageReadMe.md + + + + diff --git a/PictureRenderer/_Build/PackageReadMe.md b/PictureRenderer/_Build/PackageReadMe.md new file mode 100644 index 0000000..ad7a009 --- /dev/null +++ b/PictureRenderer/_Build/PackageReadMe.md @@ -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.
+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.
+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). diff --git a/README.md b/README.md index f3f3ec7..03063d4 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ Storyblok and Cloudflare image services automatically converts images to Webp or * Create Picture profiles (ImageSharpProfile, StoryblokProfile, or CloudflareProfile) for the different types of images that you have on your web site. A Picture profile describes how an image should be scaled in various cases.
You could for example create Picture profiles for: “Top hero image”, “Teaser image”, “Image gallery thumbnail”. * Let Picture Renderer create the picture HTML element. +* Fine-tune how the picture element is rendered by setting parameters like css class, fetch priority, etc. If using ImageSharp you need to make sure that [ImageSharp.Web](https://www.nuget.org/packages/SixLabors.ImageSharp.Web/) is enabled on the server that takes care of the actual resizing of the images (see also [Setup and configuration](https://docs.sixlabors.com/articles/imagesharp.web/gettingstarted.html#setup-and-configuration)). @@ -73,42 +74,35 @@ public static class PictureProfiles Sizes = new[] { "150px" }, AspectRatio = 1 //square image (equal height and width). }; - - // Multi-image - // Show different images depending on media conditions (e.g. different image for mobile sized screen). - public static readonly ImageSharpProfile SampleImage2 = new() - { - MultiImageMediaConditions = new[] { new MediaCondition("(min-width: 1200px)", 600), new MediaCondition("(min-width: 600px)", 300) }, - AspectRatio = 1.777 - }; } ``` -* **SrcSetWidths (for single image)** – The different image widths you want the browser to select from. These values are used when rendering the srcset attribute. Ignored when rendering multiple images. -* **Sizes (for single image)** – Define the size (width) the image should be according to a set of “media conditions” (similar to css media queries). Values are used to render the sizes attribute. Ignored when rendering multiple images. -* **MultiImageMediaConditions (for multi image)** - Define image widths for different media conditions. +* **SrcSetWidths** – The different image widths you want the browser to select from. These values are used when rendering the srcset attribute. +* **Sizes** – Define the size (width) the image should be according to a set of “media conditions” (similar to css media queries). Values are used to render the sizes attribute. * **AspectRatio (optional)** – The wanted aspect ratio of the image (width/height). Ex: An image with aspect ratio 16:9 = 16/9 = 1.777. * **FixedHeight (optional)** – Set a fixed height for all image sizes. Fixed height is ignored if aspect ratio is set. * **CreateWebpForFormat (optional, ImageSharp only)** - The image formats that should be offered as webp versions. Jpg format is aded by default. * **Quality (optional)** - Image quality. Lower value = less file size. Not valid for all image formats. Default value: `80`. -* **FallbackWidth (optional)** – This image width will be used in browsers that don’t support the picture element. Will use the largest width if not set. -* **ImageDecoding (optional)** - Value for img element `decoding` attribute. Default value: `async`. -* **ImgWidthHeight (optional)** - If true, `width` and `height` attributes will be rendered on the img element. -* **ShowInfo (optional)** - If true, an overlay will show info about the currently selected image. -* **FetchPriority (optional)** - Value for img element `fetchPriority` attribute. Default value: `none` (unset) + ### Render picture element Render the picture element by calling `Picture.Render`
#### Parameters -* **ImagePath/ImagePaths** - Single image path, or array of image paths. -* **profile** - The Picture profile that specifies image widths, etc.. -* **altText (optional)** - Img element `alt` attribute. -* **lazyLoading (optional)** - Type of lazy loading. Currently only [browser native lazy loading](https://developer.mozilla.org/en-US/docs/Web/Performance/Lazy_loading#images_and_iframes) (or none). -* **focalPoint/focalPoints (optional)** - Use focal point when image is cropped (multiple points for multiple image paths). -* **cssClass (optional)** - Css class for img element. - -Picture.Render returns a string, so you need to make sure the string is not HTML-escaped by using Html.Raw or similar. +* **imagePath** - Relative path, or full Url to the image. +* **profile** - The Picture profile that specifies image widths, etc. +* **focalPoint (optional)** - Use focal point when image is cropped. +* **attributes (optional)** - A PictureAttributes object with additional attributes/settings that fine-tunes the rendering.
+ * **ImgAlt** - `alt` attribute for img element. + * **ImgClass** - `class` attribute for img element. + * **ImgFetchPriority** - `fetchPriority` attribute for img element. Default value: none. + * **ImgDecoding** - `decoding` attribute for img element. Default value: async. + * **LazyLoading** - Type of lazy loading. Currently supports browser native or none. Default value: browser native. + * **RenderImgWidthHeight** - If true, width and height attributes will be rendered on the img element. Default value: false. + * **ImgAdditionalAttributes** - May be used to add additional attributes (like data or itemprop attributes) to the img element. + +Picture.Render returns a string, so you need to make sure the string is not HTML-escaped by using Html.Raw or similar.
+I recommend wrapping the Picture.Render an Html helper/Tag helper (for MVC/Razor pages) or a component (for Blazor).
Basic MVC/Razor page sample @@ -128,13 +122,11 @@ See also [sample projects](https://github.com/ErikHen/PictureRenderer.Samples).

### How to see that it actually works -If you set ```ShowInfo = true``` in the picture profile, an overlay with information about the currently selected image will be rendered.
-You can see that different images are selected for different devices and screen sizes. Note that the Chrome (Chromium based) browser will not select a smaller image if a larger one is already downloaded. It may be easier to see the actual behaviour when using e.g. Firefox. -
-This setting should of course never be true in your live/production environment, it's only meant for testing.
-Note that this may not work if using Blazor. + ## Version history +* **3.12** Possible to set any attribute on the img element.
Switch to using a PictureAttributes object for various settings, instead of individual parameters.
+Prepare for v4.
* **3.11** Make PictureUtil/GetPictureData public as requested in [#19](https://github.com/ErikHen/PictureRenderer/issues/19) * **3.10** Possible to set style attribute (added to img element). * **3.9** Possible to set [fetchpriority](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/fetchPriority) attribute. Thanks [Karol](https://github.com/karolberezicki)! From db97b9b5e7d3df7f50633a96f2bce063f383d299 Mon Sep 17 00:00:00 2001 From: Erik Henningson Date: Fri, 26 Jul 2024 16:15:01 +0200 Subject: [PATCH 4/6] Add test for additional attributes --- .../Profiles/PictureProfileBase.cs | 6 +- PictureRendererTests/ImageSharpTests.cs | 87 +++++++++++-------- 2 files changed, 53 insertions(+), 40 deletions(-) diff --git a/PictureRenderer/Profiles/PictureProfileBase.cs b/PictureRenderer/Profiles/PictureProfileBase.cs index 4931406..cd82353 100644 --- a/PictureRenderer/Profiles/PictureProfileBase.cs +++ b/PictureRenderer/Profiles/PictureProfileBase.cs @@ -61,19 +61,19 @@ public int FallbackWidth /// /// If true, width and height attributes will be rendered on the img element. /// - [Obsolete("Use PictureAttribute instead. Will be removed in next major version.")] + [Obsolete("Use PictureAttributes object to set this value. Will be removed from profile in next major version.")] public bool ImgWidthHeight { get; set; } /// /// Img element decoding attribute. /// - [Obsolete("Use PictureAttribute instead. Will be removed in next major version.")] + [Obsolete("Use PictureAttributes object to set this value. Will be removed from profile in next major version.")] public ImageDecoding ImageDecoding {get; set;} /// /// Img element fetchPriority attribute. /// - [Obsolete("Use PictureAttribute instead. Will be removed in next major version.")] + [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; } diff --git a/PictureRendererTests/ImageSharpTests.cs b/PictureRendererTests/ImageSharpTests.cs index 183f055..f13316f 100644 --- a/PictureRendererTests/ImageSharpTests.cs +++ b/PictureRendererTests/ImageSharpTests.cs @@ -1,4 +1,5 @@ -using PictureRenderer.Profiles; +using System.Collections.Generic; +using PictureRenderer.Profiles; using Xunit; using Assert = Xunit.Assert; @@ -99,7 +100,7 @@ public void RenderWithCssClassAndImageDecodingAuto() Sizes = new[] { "150px" }, AspectRatio = 1, }; - var attributes = new PictureAttributes() { ImgAlt = "alt text", ImgClass = "my-css-class", ImgDecoding = ImageDecoding.Auto}; + var attributes = new PictureAttributes { ImgAlt = "alt text", ImgClass = "my-css-class", ImgDecoding = ImageDecoding.Auto}; var result = PictureRenderer.Picture.Render("/myImage.jpg", profile, attributes); @@ -210,41 +211,41 @@ public void RenderWithWidthAndHeightAndFetchPriorityAuto() Assert.Equal(expected, result); } - [Fact()] - public void RenderWithWidthAndHeightAndFetchPriorityHigh() - { - const string expected = "\"alt"; - var profile = new ImageSharpProfile() - { - SrcSetWidths = new[] { 150, 300 }, - Sizes = new[] { "150px" }, - AspectRatio = 1, - ImgWidthHeight = true, - FetchPriority = FetchPriority.High, - }; - - var result = PictureRenderer.Picture.Render("/myImage.jpg", profile, "alt text"); - - Assert.Equal(expected, result); - } - - [Fact()] - public void RenderWithWidthAndHeightAndFetchPriorityLow() - { - const string expected = "\"alt"; - var profile = new ImageSharpProfile() - { - SrcSetWidths = new[] { 150, 300 }, - Sizes = new[] { "150px" }, - AspectRatio = 1, - ImgWidthHeight = true, - FetchPriority = FetchPriority.Low, - }; - - var result = PictureRenderer.Picture.Render("/myImage.jpg", profile, "alt text"); - - Assert.Equal(expected, result); - } + //[Fact()] + //public void RenderWithWidthAndHeightAndFetchPriorityHigh() + //{ + // const string expected = "\"alt"; + // var profile = new ImageSharpProfile() + // { + // SrcSetWidths = new[] { 150, 300 }, + // Sizes = new[] { "150px" }, + // AspectRatio = 1, + // ImgWidthHeight = true, + // FetchPriority = FetchPriority.High, + // }; + + // var result = PictureRenderer.Picture.Render("/myImage.jpg", profile, "alt text"); + + // Assert.Equal(expected, result); + //} + + //[Fact()] + //public void RenderWithWidthAndHeightAndFetchPriorityLow() + //{ + // const string expected = "\"alt"; + // var profile = new ImageSharpProfile() + // { + // SrcSetWidths = new[] { 150, 300 }, + // Sizes = new[] { "150px" }, + // AspectRatio = 1, + // ImgWidthHeight = true, + // FetchPriority = FetchPriority.Low, + // }; + + // var result = PictureRenderer.Picture.Render("/myImage.jpg", profile, "alt text"); + + // Assert.Equal(expected, result); + //} [Fact()] public void RenderWithFixedHeightOLD() @@ -364,6 +365,18 @@ public void RenderWithDomainTest() Assert.Equal(expected, result); } + [Fact()] + public void RenderWithAdditionalAttributeTest() + { + const string expected = "\"\""; + var profile = GetTestImageProfile(); + profile.CreateWebpForFormat = null; + + var result = PictureRenderer.Picture.Render("/myImage.jpg", profile, new PictureAttributes() { ImgAdditionalAttributes = new Dictionary() { {"itemprop", "test"} } }); + + Assert.Equal(expected, result); + } + [Fact()] public void RenderWithInfoTest() { From 1f8f1010e3716cea9527b25e51fd4da8105d61a6 Mon Sep 17 00:00:00 2001 From: Erik Henningson Date: Fri, 26 Jul 2024 18:48:12 +0200 Subject: [PATCH 5/6] v 3.12 --- PictureRenderer/PictureRenderer.csproj | 2 +- PictureRenderer/_Build/PackageReadMe.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PictureRenderer/PictureRenderer.csproj b/PictureRenderer/PictureRenderer.csproj index 68ae954..2fb8b5f 100644 --- a/PictureRenderer/PictureRenderer.csproj +++ b/PictureRenderer/PictureRenderer.csproj @@ -2,7 +2,7 @@ netstandard2.0 - 3.12-preview + 3.12 Erik Henningson diff --git a/PictureRenderer/_Build/PackageReadMe.md b/PictureRenderer/_Build/PackageReadMe.md index ad7a009..36dd8cf 100644 --- a/PictureRenderer/_Build/PackageReadMe.md +++ b/PictureRenderer/_Build/PackageReadMe.md @@ -4,7 +4,7 @@ Images will be responsive, and can be lazy loaded.
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.
+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.
From 606c0e79ce9b8d2ced9ec87d7cbb437d63ff381d Mon Sep 17 00:00:00 2001 From: Erik Henningson Date: Fri, 26 Jul 2024 18:50:47 +0200 Subject: [PATCH 6/6] readme --- README.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 03063d4..5911770 100644 --- a/README.md +++ b/README.md @@ -77,12 +77,14 @@ public static class PictureProfiles } ``` -* **SrcSetWidths** – The different image widths you want the browser to select from. These values are used when rendering the srcset attribute. -* **Sizes** – Define the size (width) the image should be according to a set of “media conditions” (similar to css media queries). Values are used to render the sizes attribute. +* **SrcSetWidths** – The different image widths you want the browser to select from. These values are used when rendering the `srcset` attribute. +* **Sizes** – Define the size (width) the image should be according to a set of [media conditions](https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images) (similar to css media queries). Values are used to render the `sizes` attribute. * **AspectRatio (optional)** – The wanted aspect ratio of the image (width/height). Ex: An image with aspect ratio 16:9 = 16/9 = 1.777. * **FixedHeight (optional)** – Set a fixed height for all image sizes. Fixed height is ignored if aspect ratio is set. * **CreateWebpForFormat (optional, ImageSharp only)** - The image formats that should be offered as webp versions. Jpg format is aded by default. * **Quality (optional)** - Image quality. Lower value = less file size. Not valid for all image formats. Default value: `80`. +* **IsDisabled (optional, Cloudflare only)** - Do not alter the image url at all. May be useful in a development environment where you are not using the CloudFlare CDN. + ### Render picture element @@ -99,7 +101,7 @@ Render the picture element by calling `Picture.Render` * **ImgDecoding** - `decoding` attribute for img element. Default value: async. * **LazyLoading** - Type of lazy loading. Currently supports browser native or none. Default value: browser native. * **RenderImgWidthHeight** - If true, width and height attributes will be rendered on the img element. Default value: false. - * **ImgAdditionalAttributes** - May be used to add additional attributes (like data or itemprop attributes) to the img element. + * **ImgAdditionalAttributes** - Key-value dictionary that may be used to add additional attributes (like data or itemprop attributes) to the img element. Picture.Render returns a string, so you need to make sure the string is not HTML-escaped by using Html.Raw or similar.
I recommend wrapping the Picture.Render an Html helper/Tag helper (for MVC/Razor pages) or a component (for Blazor). @@ -108,6 +110,7 @@ I recommend wrapping the Picture.Render an Html helper/Tag helper (for MVC/Razor Basic MVC/Razor page sample ``` @Html.Raw(Picture.Render("/img/test.jpg", PictureProfiles.SampleImage)) +@Html.Raw(Picture.Render("/img/test.jpg", PictureProfiles.SampleImage, new PictureAttributes { ImgAlt = "alt text", ImgClass = "my-css-class")) ```
@@ -121,12 +124,15 @@ Basic Blazor sample See also [sample projects](https://github.com/ErikHen/PictureRenderer.Samples).

-### How to see that it actually works + ## Version history -* **3.12** Possible to set any attribute on the img element.
Switch to using a PictureAttributes object for various settings, instead of individual parameters.
-Prepare for v4.
+* **3.12** Possible to set any attribute on the img element. Prepare for v4. + > [!NOTE] + > Switch to using a PictureAttributes object, instead of individual parameters, when calling `Picture.Render` .
+ Some settings have been moved from PictureProfile to the new PictureAttributes object.
+ * **3.11** Make PictureUtil/GetPictureData public as requested in [#19](https://github.com/ErikHen/PictureRenderer/issues/19) * **3.10** Possible to set style attribute (added to img element). * **3.9** Possible to set [fetchpriority](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/fetchPriority) attribute. Thanks [Karol](https://github.com/karolberezicki)!