Skip to content

Commit

Permalink
Watermark option.
Browse files Browse the repository at this point in the history
  • Loading branch information
SebastianStehle committed Nov 18, 2024
1 parent 923b0d9 commit 0ed2715
Show file tree
Hide file tree
Showing 19 changed files with 365 additions and 87 deletions.
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<PackageProjectUrl>https://github.com/squidex/squidex</PackageProjectUrl>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Version>6.19.0</Version>
<Version>6.20.0</Version>
</PropertyGroup>

<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
Expand Down
9 changes: 8 additions & 1 deletion assets/Benchmarks/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using Microsoft.Extensions.DependencyInjection;
using Squidex.Assets;

#pragma warning disable MA0048 // File name must match type name
Expand Down Expand Up @@ -69,7 +70,13 @@ public static async Task Main(string[] args)
{
if (args.Contains("--resize"))
{
var generator = new ImageSharpThumbnailGenerator();
var httpClientFactory =
new ServiceCollection()
.AddHttpClient()
.BuildServiceProvider()
.GetRequiredService<IHttpClientFactory>();

var generator = new ImageSharpThumbnailGenerator(httpClientFactory);

await using (var source = new FileStream("file_example_PNG_1MB.png", FileMode.Open))
{
Expand Down
64 changes: 36 additions & 28 deletions assets/Squidex.Assets.ImageMagick/ImageMagickThumbnailGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
using System.Threading;
using System.Threading.Tasks;
using ImageMagick;
using SixLabors.ImageSharp;
using Squidex.Assets.Internal;

namespace Squidex.Assets;
Expand Down Expand Up @@ -55,36 +54,13 @@ protected override async Task CreateThumbnailCoreAsync(Stream source, string mim
foreach (var image in images)
{
var clone = image.Clone();

var color = options.ParseColor();

if (w > 0 || h > 0)
{
var isCropUpsize = options.Mode == ResizeMode.CropUpsize;

var resizeMode = options.Mode;

if (isCropUpsize)
{
resizeMode = ResizeMode.Crop;
}

if (w >= image.Width && h >= image.Height && resizeMode == ResizeMode.Crop && !isCropUpsize)
{
resizeMode = ResizeMode.BoxPad;
}
var resizeMode = GetResizeMode(options, w, h, image);
var resizeAnchor = GetResizeAnchor(options);

PointF? centerCoordinates = null;

if (options.FocusX.HasValue && options.FocusY.HasValue)
{
centerCoordinates = new PointF(
+(options.FocusX.Value / 2f) + 0.5f,
-(options.FocusY.Value / 2f) + 0.5f
);
}

var (size, pad) = ResizeHelper.CalculateTargetLocationAndBounds(resizeMode, new Size(image.Width, image.Height), w, h, centerCoordinates);
var (size, pad) = ResizeHelper.CalculateTargetLocationAndBounds(resizeMode, new Size(image.Width, image.Height), w, h, resizeAnchor);

var sourceRectangle = new MagickGeometry(pad.Width, pad.Height)
{
Expand All @@ -94,7 +70,6 @@ protected override async Task CreateThumbnailCoreAsync(Stream source, string mim
clone.Resize(sourceRectangle);

image.Extent(size.Width, size.Height);

image.CompositeClear(color);
image.Composite(clone, pad.X, pad.Y, CompositeOperator.Over);
}
Expand Down Expand Up @@ -171,6 +146,39 @@ protected override async Task FixCoreAsync(Stream source, string mimeType, Strea
}
}

private static PointF? GetResizeAnchor(ResizeOptions options)
{
PointF? centerCoordinates = null;

if (options.FocusX.HasValue && options.FocusY.HasValue)
{
centerCoordinates = new PointF(
+(options.FocusX.Value / 2f) + 0.5f,
-(options.FocusY.Value / 2f) + 0.5f
);
}

return centerCoordinates;
}

private static ResizeMode GetResizeMode(ResizeOptions options, int w, int h, IMagickImage<byte> image)
{
var isCropUpsize = options.Mode == ResizeMode.CropUpsize;

var resizeMode = options.Mode;
if (isCropUpsize)
{
resizeMode = ResizeMode.Crop;
}

if (w >= image.Width && h >= image.Height && resizeMode == ResizeMode.Crop && !isCropUpsize)
{
resizeMode = ResizeMode.BoxPad;
}

return resizeMode;
}

private static MagickFormat GetFormat(string mimeType)
{
var format = MagickFormat.Unknown;
Expand Down
56 changes: 50 additions & 6 deletions assets/Squidex.Assets.ImageMagick/Internal/ResizeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,18 @@
// ==========================================================================

using System;
using SixLabors.ImageSharp;

#pragma warning disable MA0048 // File name must match type name
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter

namespace Squidex.Assets.Internal;

public readonly record struct PointF(float X, float Y);

public readonly record struct Size(int Width, int Height);

public readonly record struct Rectangle(int X, int Y, int Width, int Height);

internal static class ResizeHelper
{
public static (Size Size, Rectangle Rectangle) CalculateTargetLocationAndBounds(ResizeMode mode, Size sourceSize, int width, int height, PointF? centerCoordinates)
Expand Down Expand Up @@ -75,7 +83,16 @@ private static (Size Size, Rectangle Rectangle) CalculateBoxPadRectangle(
var targetX = (desiredWidth - sourceWidth) / 2;

// Target image width and height can be different to the rectangle width and height.
return (new Size(Sanitize(desiredWidth), Sanitize(desiredHeight)), new Rectangle(targetX, targetY, Sanitize(targetWidth), Sanitize(targetHeight)));
return (
new Size(
Sanitize(desiredWidth),
Sanitize(desiredHeight)),
new Rectangle(
targetX,
targetY,
Sanitize(targetWidth),
Sanitize(targetHeight))
);
}

// Switch to pad mode to downscale and calculate from there.
Expand All @@ -89,9 +106,9 @@ private static (Size Size, Rectangle Rectangle) CalculateCropRectangle(
PointF? centerCoordinates)
{
float ratio;

var sourceWidth = source.Width;
var sourceHeight = source.Height;

var targetX = 0;
var targetY = 0;
var targetWidth = desiredWidth;
Expand Down Expand Up @@ -154,7 +171,16 @@ private static (Size Size, Rectangle Rectangle) CalculateCropRectangle(
}

// Target image width and height can be different to the rectangle width and height.
return (new Size(Sanitize(desiredWidth), Sanitize(desiredHeight)), new Rectangle(targetX, targetY, Sanitize(targetWidth), Sanitize(targetHeight)));
return (
new Size(
Sanitize(desiredWidth),
Sanitize(desiredHeight)),
new Rectangle(
targetX,
targetY,
Sanitize(targetWidth),
Sanitize(targetHeight))
);
}

private static (Size Size, Rectangle Rectangle) CalculateMaxRectangle(
Expand Down Expand Up @@ -184,7 +210,16 @@ private static (Size Size, Rectangle Rectangle) CalculateMaxRectangle(
}

// Replace the size to match the rectangle.
return (new Size(Sanitize(targetWidth), Sanitize(targetHeight)), new Rectangle(0, 0, Sanitize(targetWidth), Sanitize(targetHeight)));
return (
new Size(
Sanitize(targetWidth),
Sanitize(targetHeight)),
new Rectangle(
0,
0,
Sanitize(targetWidth),
Sanitize(targetHeight))
);
}

private static (Size Size, Rectangle Rectangle) CalculateMinRectangle(
Expand Down Expand Up @@ -266,7 +301,16 @@ private static (Size Size, Rectangle Rectangle) CalculatePadRectangle(
}

// Target image width and height can be different to the rectangle width and height.
return (new Size(Sanitize(desiredWidth), Sanitize(desiredHeight)), new Rectangle(targetX, targetY, Sanitize(targetWidth), Sanitize(targetHeight)));
return (
new Size(
Sanitize(desiredWidth),
Sanitize(desiredHeight)),
new Rectangle(
targetX,
targetY,
Sanitize(targetWidth),
Sanitize(targetHeight))
);
}

private static int Sanitize(int input) => Math.Max(1, input);
Expand Down
103 changes: 69 additions & 34 deletions assets/Squidex.Assets.ImageSharp/ImageSharpThumbnailGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Blurhash.ImageSharp;
Expand All @@ -23,18 +24,21 @@
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Squidex.Assets.Internal;
using ISImageInfo = SixLabors.ImageSharp.ImageInfo;
using ISResizeMode = SixLabors.ImageSharp.Processing.ResizeMode;
using ISResizeOptions = SixLabors.ImageSharp.Processing.ResizeOptions;
using ImageSharpInfo = SixLabors.ImageSharp.ImageInfo;
using ImageSharpMode = SixLabors.ImageSharp.Processing.ResizeMode;
using ImageSharpOptions = SixLabors.ImageSharp.Processing.ResizeOptions;

namespace Squidex.Assets;

public sealed class ImageSharpThumbnailGenerator : AssetThumbnailGeneratorBase
{
private readonly HashSet<string> mimeTypes;
private readonly IHttpClientFactory httpClientFactory;

public ImageSharpThumbnailGenerator()
public ImageSharpThumbnailGenerator(IHttpClientFactory httpClientFactory)
{
this.httpClientFactory = httpClientFactory;

mimeTypes = Configuration.Default.ImageFormatsManager.ImageFormats.SelectMany(x => x.MimeTypes).ToHashSet();
}

Expand Down Expand Up @@ -72,39 +76,25 @@ protected override async Task CreateThumbnailCoreAsync(Stream source, string mim

using (var image = await Image.LoadAsync(source, ct))
{
image.Mutate(x => x.AutoOrient());
var watermark = await GetWatermarkAsync(options, ct);

if (w > 0 || h > 0)
image.Mutate(operation =>
{
var isCropUpsize = options.Mode == ResizeMode.CropUpsize;

if (!Enum.TryParse<ISResizeMode>(options.Mode.ToString(), true, out var resizeMode))
{
resizeMode = ISResizeMode.Max;
}

if (isCropUpsize)
{
resizeMode = ISResizeMode.Crop;
}
operation.AutoOrient();

if (w >= image.Width && h >= image.Height && resizeMode == ISResizeMode.Crop && !isCropUpsize)
if (w > 0 || h > 0)
{
resizeMode = ISResizeMode.BoxPad;
}
var resizeMode = GetResizeMode(options, w, h, image);
var resizeOptions = new ImageSharpOptions { Size = new Size(w, h), Mode = resizeMode, PremultiplyAlpha = true };

var resizeOptions = new ISResizeOptions { Size = new Size(w, h), Mode = resizeMode, PremultiplyAlpha = true };

if (options.FocusX.HasValue && options.FocusY.HasValue)
{
resizeOptions.CenterCoordinates = new PointF(
+(options.FocusX.Value / 2f) + 0.5f,
-(options.FocusY.Value / 2f) + 0.5f
);
}
if (options.FocusX.HasValue && options.FocusY.HasValue)
{
resizeOptions.CenterCoordinates = new PointF(
+(options.FocusX.Value / 2f) + 0.5f,
-(options.FocusY.Value / 2f) + 0.5f
);
}

image.Mutate(operation =>
{
operation.Resize(resizeOptions);

if (options.Background != null && Color.TryParse(options.Background, out var color))
Expand All @@ -115,15 +105,42 @@ protected override async Task CreateThumbnailCoreAsync(Stream source, string mim
{
operation.BackgroundColor(Color.Transparent);
}
});
}
}

if (watermark != null)
{
operation.Watermark(watermark, options.WatermarkAnchor, options.WatermarkOpacity);
}
});

var encoder = options.GetEncoder(image.Metadata.DecodedImageFormat);

await image.SaveAsync(destination, encoder, ct);
}
}

private static ImageSharpMode GetResizeMode(ResizeOptions options, int w, int h, Image image)
{
var isCropUpsize = options.Mode == ResizeMode.CropUpsize;

if (!Enum.TryParse<ImageSharpMode>(options.Mode.ToString(), true, out var resizeMode))
{
resizeMode = ImageSharpMode.Max;
}

if (isCropUpsize)
{
resizeMode = ImageSharpMode.Crop;
}

if (w >= image.Width && h >= image.Height && resizeMode == ImageSharpMode.Crop && !isCropUpsize)
{
resizeMode = ImageSharpMode.BoxPad;
}

return resizeMode;
}

protected override async Task<ImageInfo?> GetImageInfoCoreAsync(Stream source, string mimeType,
CancellationToken ct = default)
{
Expand Down Expand Up @@ -166,7 +183,25 @@ protected override async Task FixCoreAsync(Stream source, string mimeType, Strea
}
}

private static ImageInfo GetImageInfo(ISImageInfo imageInfo)
private async Task<Image?> GetWatermarkAsync(ResizeOptions options,
CancellationToken ct)
{
if (options.WatermarkUrl == null)
{
return null;
}

try
{
return await httpClientFactory.GetImageAsync(options.WatermarkUrl, ct);
}
catch
{
return null;
}
}

private static ImageInfo GetImageInfo(ImageSharpInfo imageInfo)
{
var orientation = ImageOrientation.None;

Expand Down
Loading

0 comments on commit 0ed2715

Please sign in to comment.