Enhances image manipulation with format (webp encoder, defaults to png)

- computes etag with all manipulation parameters
This commit is contained in:
David Montesinos 2024-10-13 17:20:18 +02:00
parent aa5b84a214
commit 3adb7ecb1c
4 changed files with 124 additions and 54 deletions

View File

@ -685,14 +685,16 @@ namespace Oqtane.Controllers
{ {
if (!bool.TryParse(recreate, out _)) recreate = "false"; if (!bool.TryParse(recreate, out _)) recreate = "false";
string imagepath = filepath.Replace(Path.GetExtension(filepath), "." + width.ToString() + "x" + height.ToString() + ".png"); string format = "png";
string imagepath = filepath.Replace(Path.GetExtension(filepath), "." + width.ToString() + "x" + height.ToString() + "." + format);
if (!System.IO.File.Exists(imagepath) || bool.Parse(recreate)) if (!System.IO.File.Exists(imagepath) || bool.Parse(recreate))
{ {
// user has edit access to folder or folder supports the image size being created // user has edit access to folder or folder supports the image size being created
if (_userPermissions.IsAuthorized(User, PermissionNames.Edit, file.Folder.PermissionList) || if (_userPermissions.IsAuthorized(User, PermissionNames.Edit, file.Folder.PermissionList) ||
(!string.IsNullOrEmpty(file.Folder.ImageSizes) && (file.Folder.ImageSizes == "*" || file.Folder.ImageSizes.ToLower().Split(",").Contains(width.ToString() + "x" + height.ToString())))) (!string.IsNullOrEmpty(file.Folder.ImageSizes) && (file.Folder.ImageSizes == "*" || file.Folder.ImageSizes.ToLower().Split(",").Contains(width.ToString() + "x" + height.ToString()))))
{ {
imagepath = _imageService.CreateImage(filepath, width, height, mode, position, background, rotate, imagepath); imagepath = _imageService.CreateImage(filepath, width, height, mode, position, background, rotate, format, imagepath);
} }
else else
{ {

View File

@ -102,7 +102,16 @@ namespace Oqtane.Pages
// appends the query string to the redirect url // appends the query string to the redirect url
if (Request.QueryString.HasValue && !string.IsNullOrWhiteSpace(Request.QueryString.Value)) if (Request.QueryString.HasValue && !string.IsNullOrWhiteSpace(Request.QueryString.Value))
{ {
url += Request.QueryString.Value; if (url.Contains('?'))
{
url += "&";
}
else
{
url += "?";
}
url += Request.QueryString.Value.Substring(1);
} }
return RedirectPermanent(url); return RedirectPermanent(url);
@ -122,25 +131,49 @@ namespace Oqtane.Pages
string downloadName = file.Name; string downloadName = file.Name;
string filepath = _files.GetFilePath(file); string filepath = _files.GetFilePath(file);
bool hasWidthParam = Request.Query.TryGetValue("width", out var widthStr); var etagValue = file.ModifiedOn.Ticks ^ file.Size;
bool hasHeightParam = Request.Query.TryGetValue("height", out var heightStr);
bool isRequestingImageManipulation = false;
int width = 0; int width = 0;
int height = 0; int height = 0;
if (Request.Query.TryGetValue("width", out var widthStr) && int.TryParse(widthStr, out width) && width > 0)
bool isRequestingImageResize =
hasWidthParam && int.TryParse(widthStr, out width) && width > 0 &&
hasHeightParam && int.TryParse(heightStr, out height) && height > 0;
if (isRequestingImageResize)
{ {
etag = Convert.ToString(file.ModifiedOn.Ticks ^ file.Size ^ (width * 31) ^ (height * 17), 16); isRequestingImageManipulation = true;
etagValue ^= (width * 31);
} }
else if (Request.Query.TryGetValue("height", out var heightStr) && int.TryParse(heightStr, out height) && height > 0)
{ {
etag = Convert.ToString(file.ModifiedOn.Ticks ^ file.Size, 16); isRequestingImageManipulation = true;
etagValue ^= (height * 17);
} }
Request.Query.TryGetValue("mode", out var mode);
Request.Query.TryGetValue("position", out var position);
Request.Query.TryGetValue("background", out var background);
if (width > 0 || height > 0)
{
if (!string.IsNullOrWhiteSpace(mode)) etagValue ^= mode.ToString().GetHashCode();
if (!string.IsNullOrWhiteSpace(position)) etagValue ^= position.ToString().GetHashCode();
if (!string.IsNullOrWhiteSpace(background)) etagValue ^= background.ToString().GetHashCode();
}
int rotate;
if (Request.Query.TryGetValue("rotate", out var rotateStr) && int.TryParse(rotateStr, out rotate) && 360 > rotate && rotate > 0)
{
isRequestingImageManipulation = true;
etagValue ^= (rotate * 13);
}
if (Request.Query.TryGetValue("format", out var format) && _imageService.GetAvailableFormats().Contains(format.ToString()))
{
isRequestingImageManipulation = true;
etagValue ^= format.ToString().GetHashCode();
}
etag = Convert.ToString(etagValue, 16);
var header = ""; var header = "";
if (HttpContext.Request.Headers.TryGetValue(HeaderNames.IfNoneMatch, out var ifNoneMatch)) if (HttpContext.Request.Headers.TryGetValue(HeaderNames.IfNoneMatch, out var ifNoneMatch))
{ {
@ -160,7 +193,7 @@ namespace Oqtane.Pages
return BrokenFile(); return BrokenFile();
} }
if (isRequestingImageResize) if (isRequestingImageManipulation)
{ {
var _ImageFiles = _settingRepository.GetSetting(EntityNames.Site, _alias.SiteId, "ImageFiles")?.SettingValue; var _ImageFiles = _settingRepository.GetSetting(EntityNames.Site, _alias.SiteId, "ImageFiles")?.SettingValue;
_ImageFiles = (string.IsNullOrEmpty(_ImageFiles)) ? Constants.ImageFiles : _ImageFiles; _ImageFiles = (string.IsNullOrEmpty(_ImageFiles)) ? Constants.ImageFiles : _ImageFiles;
@ -172,22 +205,24 @@ namespace Oqtane.Pages
return BrokenFile(); return BrokenFile();
} }
Request.Query.TryGetValue("mode", out var mode);
Request.Query.TryGetValue("position", out var position);
Request.Query.TryGetValue("background", out var background);
Request.Query.TryGetValue("rotate", out var rotate);
Request.Query.TryGetValue("recreate", out var recreate); Request.Query.TryGetValue("recreate", out var recreate);
if (!bool.TryParse(recreate, out _)) recreate = "false"; if (!bool.TryParse(recreate, out _)) recreate = "false";
if (!_imageService.GetAvailableFormats().Contains(format.ToString())) format = "png";
if (width == 0 && height == 0)
{
width = file.ImageWidth;
height = file.ImageHeight;
}
string imagepath = filepath.Replace(Path.GetExtension(filepath), "." + width.ToString() + "x" + height.ToString() + ".png"); string imagepath = filepath.Replace(Path.GetExtension(filepath), "." + width.ToString() + "x" + height.ToString() + "." + format);
if (!System.IO.File.Exists(imagepath) || bool.Parse(recreate)) if (!System.IO.File.Exists(imagepath) || bool.Parse(recreate))
{ {
// user has edit access to folder or folder supports the image size being created // user has edit access to folder or folder supports the image size being created
if (_userPermissions.IsAuthorized(User, PermissionNames.Edit, file.Folder.PermissionList) || if (_userPermissions.IsAuthorized(User, PermissionNames.Edit, file.Folder.PermissionList) ||
(!string.IsNullOrEmpty(file.Folder.ImageSizes) && (file.Folder.ImageSizes == "*" || file.Folder.ImageSizes.ToLower().Split(",").Contains(width.ToString() + "x" + height.ToString())))) (!string.IsNullOrEmpty(file.Folder.ImageSizes) && (file.Folder.ImageSizes == "*" || file.Folder.ImageSizes.ToLower().Split(",").Contains(width.ToString() + "x" + height.ToString()))))
{ {
imagepath = _imageService.CreateImage(filepath, width, height, mode, position, background, rotate, imagepath); imagepath = _imageService.CreateImage(filepath, width, height, mode, position, background, rotateStr, format, imagepath);
} }
else else
{ {
@ -204,7 +239,7 @@ namespace Oqtane.Pages
return BrokenFile(); return BrokenFile();
} }
downloadName = file.Name.Replace(Path.GetExtension(filepath), "." + width.ToString() + "x" + height.ToString() + ".png"); downloadName = file.Name.Replace(Path.GetExtension(filepath), "." + width.ToString() + "x" + height.ToString() + "." + format);
filepath = imagepath; filepath = imagepath;
} }

View File

@ -5,21 +5,29 @@ using System.IO;
using System; using System;
using SixLabors.ImageSharp; using SixLabors.ImageSharp;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using Oqtane.Interfaces;
using Oqtane.Shared; using Oqtane.Shared;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Webp;
using System.Linq;
namespace Oqtane.Services namespace Oqtane.Services
{ {
public class ImageService : IImageService public class ImageService : IImageService
{ {
private readonly ILogManager _logger; private readonly ILogManager _logger;
private static readonly string[] _formats = ["png", "webp"];
public ImageService(ILogManager logger) public ImageService(ILogManager logger)
{ {
_logger = logger; _logger = logger;
} }
public string CreateImage(string filepath, int width, int height, string mode, string position, string background, string rotate, string imagepath) public string[] GetAvailableFormats()
{
return _formats;
}
public string CreateImage(string filepath, int width, int height, string mode, string position, string background, string rotate, string format, string imagepath)
{ {
try try
{ {
@ -29,6 +37,7 @@ namespace Oqtane.Services
if (!Color.TryParseHex("#" + background, out _)) background = "transparent"; if (!Color.TryParseHex("#" + background, out _)) background = "transparent";
if (!int.TryParse(rotate, out _)) rotate = "0"; if (!int.TryParse(rotate, out _)) rotate = "0";
rotate = (int.Parse(rotate) < 0 || int.Parse(rotate) > 360) ? "0" : rotate; rotate = (int.Parse(rotate) < 0 || int.Parse(rotate) > 360) ? "0" : rotate;
if (!_formats.Contains(format)) format = "png";
using (var stream = new FileStream(filepath, FileMode.Open, FileAccess.Read)) using (var stream = new FileStream(filepath, FileMode.Open, FileAccess.Read))
{ {
@ -39,43 +48,34 @@ namespace Oqtane.Services
Enum.TryParse(mode, true, out ResizeMode resizemode); Enum.TryParse(mode, true, out ResizeMode resizemode);
Enum.TryParse(position, true, out AnchorPositionMode anchorpositionmode); Enum.TryParse(position, true, out AnchorPositionMode anchorpositionmode);
PngEncoder encoder; if (width == 0 && height == 0)
{
width = image.Width;
height = image.Height;
}
IImageEncoder encoder;
var resizeOptions = new ResizeOptions
{
Mode = resizemode,
Position = anchorpositionmode,
Size = new Size(width, height)
};
if (background != "transparent") if (background != "transparent")
{ {
image.Mutate(x => x resizeOptions.PadColor = Color.ParseHex("#" + background);
.AutoOrient() // auto orient the image encoder = GetEncoder(format, transparent: false);
.Rotate(angle)
.Resize(new ResizeOptions
{
Mode = resizemode,
Position = anchorpositionmode,
Size = new Size(width, height),
PadColor = Color.ParseHex("#" + background)
}));
encoder = new PngEncoder();
} }
else else
{ {
image.Mutate(x => x encoder = GetEncoder(format, transparent: true);
}
image.Mutate(x => x
.AutoOrient() // auto orient the image .AutoOrient() // auto orient the image
.Rotate(angle) .Rotate(angle)
.Resize(new ResizeOptions .Resize(resizeOptions));
{
Mode = resizemode,
Position = anchorpositionmode,
Size = new Size(width, height)
}));
encoder = new PngEncoder
{
ColorType = PngColorType.RgbWithAlpha,
TransparentColorMode = PngTransparentColorMode.Preserve,
BitDepth = PngBitDepth.Bit8,
CompressionLevel = PngCompressionLevel.BestSpeed
};
}
image.Save(imagepath, encoder); image.Save(imagepath, encoder);
} }
@ -89,5 +89,36 @@ namespace Oqtane.Services
return imagepath; return imagepath;
} }
private static IImageEncoder GetEncoder(string format, bool transparent)
{
return format switch
{
"png" => GetPngEncoder(transparent),
"webp" => GetWebpEncoder(transparent),
_ => GetPngEncoder(transparent),
};
}
private static PngEncoder GetPngEncoder(bool transparent)
{
return new PngEncoder()
{
ColorType = transparent ? PngColorType.RgbWithAlpha : PngColorType.Rgb,
TransparentColorMode = transparent ? PngTransparentColorMode.Preserve : PngTransparentColorMode.Clear,
BitDepth = PngBitDepth.Bit8,
CompressionLevel = PngCompressionLevel.BestSpeed
};
}
private static WebpEncoder GetWebpEncoder(bool transparent)
{
return new WebpEncoder()
{
FileFormat = WebpFileFormatType.Lossy,
Quality = 60,
TransparentColorMode = transparent ? WebpTransparentColorMode.Preserve : WebpTransparentColorMode.Clear,
};
}
} }
} }

View File

@ -8,6 +8,8 @@ namespace Oqtane.Services
{ {
public interface IImageService public interface IImageService
{ {
public string CreateImage(string filepath, int width, int height, string mode, string position, string background, string rotate, string imagepath); public string[] GetAvailableFormats();
public string CreateImage(string filepath, int width, int height, string mode, string position, string background, string rotate, string format, string imagepath);
} }
} }