173 lines
4.1 KiB
C#
173 lines
4.1 KiB
C#
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
using Microsoft.Extensions.Primitives;
|
|
using SixLabors.ImageSharp.Web.Commands;
|
|
|
|
namespace zero.Media.ImageSharp;
|
|
|
|
/// <summary>
|
|
/// Parses commands from the request querystring.
|
|
/// </summary>
|
|
public sealed class PresetRequestParser : IRequestParser
|
|
{
|
|
MediaOptions _options;
|
|
ILogger<PresetRequestParser> _logger;
|
|
|
|
const char COLON = ':';
|
|
const string PREFIX = "/:";
|
|
const char SLASH = '/';
|
|
|
|
const string MIME_AVIF = "image/avif";
|
|
const string MIME_WEBP = "image/webp";
|
|
|
|
|
|
public PresetRequestParser(ILogger<PresetRequestParser> logger, IOptionsMonitor<MediaOptions> monitor)
|
|
{
|
|
_logger = logger;
|
|
_options = monitor.CurrentValue;
|
|
monitor.OnChange(opts => _options = opts);
|
|
}
|
|
|
|
|
|
/// <inheritdoc/>
|
|
public CommandCollection ParseRequestCommands(string preset, HttpContext context = null)
|
|
{
|
|
string focalPoint = null;
|
|
|
|
int focalIndex = preset.IndexOf(COLON);
|
|
|
|
if (focalIndex > -1)
|
|
{
|
|
focalPoint = preset.Substring(focalIndex + 1, preset.Length - focalIndex - 1);
|
|
preset = preset.Substring(0, focalIndex);
|
|
}
|
|
|
|
if (preset == "{rx}")
|
|
{
|
|
return [];
|
|
}
|
|
|
|
if (!_options.ImageSharp.Presets.TryGetValue(preset, out string[] transformed))
|
|
{
|
|
_logger.LogWarning("Could not load imaging preset {preset}", preset);
|
|
return [];
|
|
}
|
|
|
|
// get default processing commands (which are added to each request) from options
|
|
Dictionary<string, string> defaults = _options.ImageSharp.DefaultCommands.Select(x => x.Split("=", 2)).ToDictionary(x => x[0], x => x[1]);
|
|
|
|
|
|
// fall back to webp, as avif is not supported yet by ImageSharp
|
|
FallbackFormat fallbackFormat = FindFallbackFormatInContext(context);
|
|
string format = defaults.GetValueOrDefault("format", fallbackFormat != FallbackFormat.None ? "webp" : null);
|
|
string quality = defaults.GetValueOrDefault("quality");
|
|
|
|
CommandCollection filters = [];
|
|
|
|
foreach (KeyValuePair<string, string> kv in defaults)
|
|
{
|
|
string key = kv.Key;
|
|
if (key != "format" && key != "quality")
|
|
{
|
|
filters[key] = kv.Value;
|
|
}
|
|
}
|
|
|
|
foreach (string[] kv in transformed.Select(x => x.Split("=", 2)))
|
|
{
|
|
string key = kv[0];
|
|
string value = kv[1];
|
|
|
|
// skip format/quality filters because they need to be appended at the end
|
|
if (key == "format")
|
|
{
|
|
format = value;
|
|
continue;
|
|
}
|
|
if (key == "quality")
|
|
{
|
|
quality = value;
|
|
continue;
|
|
}
|
|
|
|
filters[key] = value;
|
|
}
|
|
|
|
// add positioning filters
|
|
if (focalPoint != null)
|
|
{
|
|
filters["rxy"] = focalPoint;
|
|
}
|
|
else
|
|
{
|
|
filters["ranchor"] = "center";
|
|
}
|
|
|
|
// add format/quality filters
|
|
if (format.HasValue())
|
|
{
|
|
filters["format"] = format;
|
|
}
|
|
if (quality.HasValue())
|
|
{
|
|
filters["quality"] = quality;
|
|
}
|
|
|
|
return filters;
|
|
}
|
|
|
|
|
|
/// <inheritdoc/>
|
|
public FallbackFormat FindFallbackFormatInContext(HttpContext context)
|
|
{
|
|
string acceptKey = Microsoft.Net.Http.Headers.HeaderNames.Accept;
|
|
|
|
if (context == null || context.Request == null || !context.Request.Headers.ContainsKey(acceptKey))
|
|
{
|
|
return FallbackFormat.None;
|
|
}
|
|
|
|
string acceptHeader = context.Request.Headers[acceptKey].ToString();
|
|
|
|
if (acceptHeader.IsNullOrEmpty())
|
|
{
|
|
return FallbackFormat.None;
|
|
}
|
|
if (acceptHeader.Contains(MIME_AVIF))
|
|
{
|
|
return FallbackFormat.Avif;
|
|
}
|
|
if (acceptHeader.Contains(MIME_WEBP))
|
|
{
|
|
return FallbackFormat.Webp;
|
|
}
|
|
return FallbackFormat.None;
|
|
}
|
|
|
|
|
|
/// <inheritdoc/>
|
|
public CommandCollection ParseRequestCommands(HttpContext context)
|
|
{
|
|
string path = context.Request.Path.Value;
|
|
int startSlash = path.IndexOf(PREFIX);
|
|
int lastSlash = path.LastIndexOf(SLASH);
|
|
|
|
if (startSlash < 0)
|
|
{
|
|
return new();
|
|
}
|
|
|
|
string preset = path.Substring(startSlash + PREFIX.Length, lastSlash - startSlash - PREFIX.Length);
|
|
return ParseRequestCommands(preset, context);
|
|
}
|
|
|
|
|
|
public enum FallbackFormat
|
|
{
|
|
None = 0,
|
|
Webp = 1,
|
|
Avif = 2
|
|
}
|
|
}
|