CatherineLynwood/CatherineLynwood/TagHelpers/ResponsiveImageTagHelper.cs
2025-06-20 19:59:10 +01:00

132 lines
5.1 KiB
C#

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Razor.TagHelpers;
using SixLabors.Fonts;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Webp;
namespace CatherineLynwood.TagHelpers
{
[HtmlTargetElement("responsive-image")]
public class ResponsiveImageTagHelper : TagHelper
{
private readonly IHttpContextAccessor _httpContextAccessor;
public ResponsiveImageTagHelper(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
[HtmlAttributeName("src")]
public string Source { get; set; }
[HtmlAttributeName("alt")]
public string AltText { get; set; }
[HtmlAttributeName("class")]
public string CssClass { get; set; }
[HtmlAttributeName("display-width-percentage")]
public int DisplayWidthPercentage { get; set; } = 100;
[HtmlAttributeName("no-index")]
public bool NoIndex { get; set; }
private readonly string[] _resolutions = { "1920", "1400", "1200", "992", "768", "576" };
private readonly string _wwwRoot = "wwwroot";
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
// Adjust for mobile display width if applicable
if (IsMobile(_httpContextAccessor.HttpContext.Request))
{
DisplayWidthPercentage = 100;
}
string rootPath = Path.Combine(Directory.GetCurrentDirectory(), _wwwRoot, "images", Source);
string directory = Path.GetDirectoryName(rootPath);
string fileName = Path.GetFileNameWithoutExtension(rootPath);
string extension = Path.GetExtension(rootPath);
string year = DateTime.Now.Year.ToString();
// Define paths for "webp" and "jpg" subfolders
string webpDirectory = Path.Combine(directory, "webp");
string jpgDirectory = Path.Combine(directory, "jpg");
string watermarkText = $"© Catherine Lynwood {year}";
// Ensure the subdirectories exist
Directory.CreateDirectory(webpDirectory);
Directory.CreateDirectory(jpgDirectory);
// Generate HTML for <picture> tag with WebP sources only
output.TagName = "picture";
foreach (string res in _resolutions)
{
int effectiveWidth = int.Parse(res) * DisplayWidthPercentage / 100;
string webpPath = Path.Combine(webpDirectory, $"{fileName}-{effectiveWidth}.webp");
if (!File.Exists(webpPath))
{
await CreateResizedImage(rootPath, webpPath, effectiveWidth, watermarkText, true); // WebP format
}
output.Content.AppendHtml(
$"<source srcset='/images/webp/{fileName}-{effectiveWidth}.webp' media='(min-width: {res}px)'>");
}
string webpPathFallback = Path.Combine(webpDirectory, $"{fileName}-400.webp");
if (!File.Exists(webpPathFallback))
{
await CreateResizedImage(rootPath, webpPathFallback, 400, watermarkText, true); // WebP format
}
// Generate the 400px JPEG for RSS feed or other uses (without adding to the <picture> element)
string jpgFallbackPath = Path.Combine(jpgDirectory, $"{fileName}-400.jpg");
if (!File.Exists(jpgFallbackPath))
{
await CreateResizedImage(rootPath, jpgFallbackPath, 400, watermarkText, false); // JPEG format
}
// Add the default WebP fallback in the <img> tag (JPEG is not included in HTML)
string noIndexAttr = NoIndex ? " data-nosnippet" : "";
output.Content.AppendHtml(
$"<img src='/images/webp/{fileName}-400.webp' class='{CssClass}' alt='{AltText}'{noIndexAttr}>");
}
private async Task CreateResizedImage(string originalPath, string outputPath, int width, string watermarkText, bool isWebP)
{
using (var image = await Image.LoadAsync(originalPath))
{
image.Mutate(x => x
.Resize(width, 0)
.DrawText(watermarkText, SystemFonts.CreateFont("Arial", 12), Color.White, new PointF(30, image.Height - 20)));
if (isWebP)
{
var webpEncoder = new WebpEncoder { Quality = 75 };
await image.SaveAsync(outputPath, webpEncoder);
}
else
{
var jpegEncoder = new JpegEncoder { Quality = 75 };
await image.SaveAsync(outputPath, jpegEncoder);
}
}
}
private bool IsMobile(HttpRequest request)
{
string userAgent = request.Headers["User-Agent"].ToString().ToLower();
return userAgent.Contains("mobi") ||
userAgent.Contains("android") ||
userAgent.Contains("iphone") ||
userAgent.Contains("ipad");
}
}
}