-
Notifications
You must be signed in to change notification settings - Fork 2.8k
V9: Fix ImageSharp integration and add support for custom crops #10623
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
a8dd90d
Add CropWebProcessor
nikolajlauridsen 865a5b9
Add ParseDecimal method
nikolajlauridsen 753f6f6
Clean url generator a bit
nikolajlauridsen d7795cf
Fix unit tests
nikolajlauridsen 6376a32
Use CommandParser to parse commands
nikolajlauridsen ba2e4cd
Use decimal instead of float for coordinates
nikolajlauridsen 1814b70
Only remove the resize processor
nikolajlauridsen cacef54
Round to int instead of just converting
nikolajlauridsen c93fe0d
Add method descriptions
nikolajlauridsen c255729
Change back to ClearProcessors
nikolajlauridsen f4ac0a5
Merge branch 'v9/dev' into v9/bugfix/image-cropping
ronaldbarendse ca5af1e
Only add our custom CropWebProcessor to ImageSharp
ronaldbarendse 20007db
Add cropmode support and align logic to ImageProcessors Crop processor
ronaldbarendse 760c15e
Add cropmode paramerter back to tests
ronaldbarendse 4b85edd
Update ImageSharpImageUrlGenerator
ronaldbarendse 6430b36
Update resize mode and anchor query string parameters
ronaldbarendse fd93248
Move CropMode enum into seperate file
ronaldbarendse 5cdcd02
Remove support for whole percentage crop values
ronaldbarendse c21656a
Change processor order and fix crop percentage calculation
ronaldbarendse 50d8e74
Obsolete unsupported image URL generation options and update ImageSha…
ronaldbarendse 0b9f1f4
Remove usage of obsolete image URL generation properties (width/heigh…
ronaldbarendse 474b63a
Update AngularJS image URL parameters
ronaldbarendse 0753353
Remove obsolete/unused properties and parameters
ronaldbarendse 766530f
Swap focal point order to left,top to match resize X,Y coordinates
ronaldbarendse 2281403
Update FocalPointPosition and CropCoordinates constructors/properties
ronaldbarendse 01559ed
Only support crop coordinates in CropWebProcessor
ronaldbarendse c5361ec
Improve crop coordinate calculations
ronaldbarendse 902984f
Added unit tests for CropWebProcessor and ImageCropperTemplateCoreExt…
AndyButland 8b1bd14
Remove ImageCropRatioMode and clean up GetCropUrl overloads
ronaldbarendse 8ca35f5
Removed two effectively duplicate tests.
AndyButland 74ea311
Remove width/height when both are above the configured maximums, add …
ronaldbarendse 36568a0
Move ImageSharpImageUrlGenerator to Web.Common and add NoopImageUrlGe…
ronaldbarendse a0184d5
Remove repeated ImageSharp default configuration
ronaldbarendse 42c6aa8
Register default ImageSharp configuration for application-wide use
ronaldbarendse 992eb58
Move ImageSharpImageUrlGenerator back to Infrastructure and remove No…
ronaldbarendse 43704da
Remove dimensions if either one is above the configured maximum
ronaldbarendse 0b53249
Make ImageSharpConfigurationOptions public
ronaldbarendse File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,28 @@ | ||
using System.Collections.Generic; | ||
using System.Collections.Generic; | ||
using Umbraco.Cms.Core.Models; | ||
|
||
namespace Umbraco.Cms.Core.Media | ||
{ | ||
/// <summary> | ||
/// Exposes a method that generates an image URL based on the specified options. | ||
/// </summary> | ||
public interface IImageUrlGenerator | ||
{ | ||
/// <summary> | ||
/// Gets the supported image file types/extensions. | ||
/// </summary> | ||
/// <value> | ||
/// The supported image file types/extensions. | ||
/// </value> | ||
IEnumerable<string> SupportedImageFileTypes { get; } | ||
|
||
/// <summary> | ||
/// Gets the image URL based on the specified <paramref name="options" />. | ||
/// </summary> | ||
/// <param name="options">The image URL generation options.</param> | ||
/// <returns> | ||
/// The generated image URL. | ||
/// </returns> | ||
string GetImageUrl(ImageUrlGenerationOptions options); | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,66 +1,68 @@ | ||
namespace Umbraco.Cms.Core.Models | ||
namespace Umbraco.Cms.Core.Models | ||
{ | ||
/// <summary> | ||
/// These are options that are passed to the IImageUrlGenerator implementation to determine | ||
/// the propery URL that is needed | ||
/// These are options that are passed to the IImageUrlGenerator implementation to determine the URL that is generated. | ||
/// </summary> | ||
public class ImageUrlGenerationOptions | ||
{ | ||
public ImageUrlGenerationOptions (string imageUrl) | ||
{ | ||
ImageUrl = imageUrl; | ||
} | ||
public ImageUrlGenerationOptions(string imageUrl) => ImageUrl = imageUrl; | ||
|
||
public string ImageUrl { get; } | ||
|
||
public int? Width { get; set; } | ||
|
||
public int? Height { get; set; } | ||
public decimal? WidthRatio { get; set; } | ||
public decimal? HeightRatio { get; set; } | ||
|
||
public int? Quality { get; set; } | ||
|
||
public ImageCropMode? ImageCropMode { get; set; } | ||
|
||
public ImageCropAnchor? ImageCropAnchor { get; set; } | ||
public bool DefaultCrop { get; set; } | ||
|
||
public FocalPointPosition FocalPoint { get; set; } | ||
|
||
public CropCoordinates Crop { get; set; } | ||
|
||
public string CacheBusterValue { get; set; } | ||
|
||
public string FurtherOptions { get; set; } | ||
public bool UpScale { get; set; } = true; | ||
public string AnimationProcessMode { get; set; } | ||
|
||
/// <summary> | ||
/// The focal point position, in whatever units the registered IImageUrlGenerator uses, | ||
/// typically a percentage of the total image from 0.0 to 1.0. | ||
/// The focal point position, in whatever units the registered IImageUrlGenerator uses, typically a percentage of the total image from 0.0 to 1.0. | ||
/// </summary> | ||
public class FocalPointPosition | ||
{ | ||
public FocalPointPosition (decimal top, decimal left) | ||
public FocalPointPosition(decimal left, decimal top) | ||
{ | ||
Left = left; | ||
Top = top; | ||
} | ||
|
||
public decimal Left { get; } | ||
|
||
public decimal Top { get; } | ||
} | ||
|
||
/// <summary> | ||
/// The bounds of the crop within the original image, in whatever units the registered | ||
/// IImageUrlGenerator uses, typically a percentage between 0 and 100. | ||
/// The bounds of the crop within the original image, in whatever units the registered IImageUrlGenerator uses, typically a percentage between 0.0 and 1.0. | ||
/// </summary> | ||
public class CropCoordinates | ||
{ | ||
public CropCoordinates (decimal x1, decimal y1, decimal x2, decimal y2) | ||
public CropCoordinates(decimal left, decimal top, decimal right, decimal bottom) | ||
{ | ||
X1 = x1; | ||
Y1 = y1; | ||
X2 = x2; | ||
Y2 = y2; | ||
Left = left; | ||
Top = top; | ||
Right = right; | ||
Bottom = bottom; | ||
} | ||
|
||
public decimal X1 { get; } | ||
public decimal Y1 { get; } | ||
public decimal X2 { get; } | ||
public decimal Y2 { get; } | ||
public decimal Left { get; } | ||
|
||
public decimal Top { get; } | ||
|
||
public decimal Right { get; } | ||
|
||
public decimal Bottom { get; } | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
123 changes: 81 additions & 42 deletions
123
src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,68 +1,107 @@ | ||
using System.Collections.Generic; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Globalization; | ||
using System.Linq; | ||
using System.Text; | ||
using SixLabors.ImageSharp; | ||
using Umbraco.Cms.Core.Media; | ||
using Umbraco.Cms.Core.Models; | ||
using Umbraco.Extensions; | ||
|
||
namespace Umbraco.Cms.Infrastructure.Media | ||
{ | ||
/// <summary> | ||
/// Exposes a method that generates an image URL based on the specified options that can be processed by ImageSharp. | ||
/// </summary> | ||
/// <seealso cref="Umbraco.Cms.Core.Media.IImageUrlGenerator" /> | ||
public class ImageSharpImageUrlGenerator : IImageUrlGenerator | ||
{ | ||
public IEnumerable<string> SupportedImageFileTypes => new[] { "jpeg", "jpg", "gif", "bmp", "png" }; | ||
/// <inheritdoc /> | ||
public IEnumerable<string> SupportedImageFileTypes { get; } | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="ImageSharpImageUrlGenerator" /> class. | ||
/// </summary> | ||
/// <param name="configuration">The ImageSharp configuration.</param> | ||
public ImageSharpImageUrlGenerator(Configuration configuration) | ||
: this(configuration.ImageFormats.SelectMany(f => f.FileExtensions).ToArray()) | ||
{ } | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="ImageSharpImageUrlGenerator" /> class. | ||
/// </summary> | ||
/// <param name="supportedImageFileTypes">The supported image file types/extensions.</param> | ||
/// <remarks> | ||
/// This constructor is only used for testing. | ||
/// </remarks> | ||
internal ImageSharpImageUrlGenerator(IEnumerable<string> supportedImageFileTypes) => SupportedImageFileTypes = supportedImageFileTypes; | ||
|
||
/// <inheritdoc/> | ||
public string GetImageUrl(ImageUrlGenerationOptions options) | ||
{ | ||
if (options == null) return null; | ||
if (options == null) | ||
{ | ||
return null; | ||
} | ||
|
||
var imageProcessorUrl = new StringBuilder(options.ImageUrl ?? string.Empty); | ||
var imageUrl = new StringBuilder(options.ImageUrl); | ||
|
||
if (options.FocalPoint != null) AppendFocalPoint(imageProcessorUrl, options); | ||
else if (options.Crop != null) AppendCrop(imageProcessorUrl, options); | ||
else if (options.DefaultCrop) imageProcessorUrl.Append("?anchor=center&mode=crop"); | ||
else | ||
bool queryStringHasStarted = false; | ||
void AppendQueryString(string value) | ||
{ | ||
imageProcessorUrl.Append("?mode=").Append((options.ImageCropMode ?? ImageCropMode.Crop).ToString().ToLower()); | ||
imageUrl.Append(queryStringHasStarted ? '&' : '?'); | ||
queryStringHasStarted = true; | ||
|
||
if (options.ImageCropAnchor != null) imageProcessorUrl.Append("&anchor=").Append(options.ImageCropAnchor.ToString().ToLower()); | ||
imageUrl.Append(value); | ||
} | ||
void AddQueryString(string key, params IConvertible[] values) | ||
=> AppendQueryString(key + '=' + string.Join(",", values.Select(x => x.ToString(CultureInfo.InvariantCulture)))); | ||
ronaldbarendse marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
var hasFormat = options.FurtherOptions != null && options.FurtherOptions.InvariantContains("&format="); | ||
if (options.FocalPoint != null) | ||
{ | ||
AddQueryString("rxy", options.FocalPoint.Left, options.FocalPoint.Top); | ||
} | ||
|
||
//Only put quality here, if we don't have a format specified. | ||
//Otherwise we need to put quality at the end to avoid it being overridden by the format. | ||
if (options.Quality.HasValue && hasFormat == false) imageProcessorUrl.Append("&quality=").Append(options.Quality); | ||
if (options.HeightRatio.HasValue) imageProcessorUrl.Append("&heightratio=").Append(options.HeightRatio.Value.ToString(CultureInfo.InvariantCulture)); | ||
if (options.WidthRatio.HasValue) imageProcessorUrl.Append("&widthratio=").Append(options.WidthRatio.Value.ToString(CultureInfo.InvariantCulture)); | ||
if (options.Width.HasValue) imageProcessorUrl.Append("&width=").Append(options.Width); | ||
if (options.Height.HasValue) imageProcessorUrl.Append("&height=").Append(options.Height); | ||
if (options.UpScale == false) imageProcessorUrl.Append("&upscale=false"); | ||
if (!string.IsNullOrWhiteSpace(options.AnimationProcessMode)) imageProcessorUrl.Append("&animationprocessmode=").Append(options.AnimationProcessMode); | ||
if (!string.IsNullOrWhiteSpace(options.FurtherOptions)) imageProcessorUrl.Append(options.FurtherOptions); | ||
if (options.Crop != null) | ||
{ | ||
AddQueryString("cc", options.Crop.Left, options.Crop.Top, options.Crop.Right, options.Crop.Bottom); | ||
} | ||
|
||
//If furtherOptions contains a format, we need to put the quality after the format. | ||
if (options.Quality.HasValue && hasFormat) imageProcessorUrl.Append("&quality=").Append(options.Quality); | ||
if (!string.IsNullOrWhiteSpace(options.CacheBusterValue)) imageProcessorUrl.Append("&rnd=").Append(options.CacheBusterValue); | ||
if (options.ImageCropMode.HasValue) | ||
{ | ||
AddQueryString("rmode", options.ImageCropMode.Value.ToString().ToLowerInvariant()); | ||
} | ||
|
||
return imageProcessorUrl.ToString(); | ||
} | ||
if (options.ImageCropAnchor.HasValue) | ||
{ | ||
AddQueryString("ranchor", options.ImageCropAnchor.Value.ToString().ToLowerInvariant()); | ||
} | ||
|
||
private void AppendFocalPoint(StringBuilder imageProcessorUrl, ImageUrlGenerationOptions options) | ||
{ | ||
imageProcessorUrl.Append("?center="); | ||
imageProcessorUrl.Append(options.FocalPoint.Top.ToString(CultureInfo.InvariantCulture)).Append(","); | ||
imageProcessorUrl.Append(options.FocalPoint.Left.ToString(CultureInfo.InvariantCulture)); | ||
imageProcessorUrl.Append("&mode=crop"); | ||
} | ||
if (options.Width.HasValue) | ||
{ | ||
AddQueryString("width", options.Width.Value); | ||
} | ||
|
||
private void AppendCrop(StringBuilder imageProcessorUrl, ImageUrlGenerationOptions options) | ||
{ | ||
imageProcessorUrl.Append("?crop="); | ||
imageProcessorUrl.Append(options.Crop.X1.ToString(CultureInfo.InvariantCulture)).Append(","); | ||
imageProcessorUrl.Append(options.Crop.Y1.ToString(CultureInfo.InvariantCulture)).Append(","); | ||
imageProcessorUrl.Append(options.Crop.X2.ToString(CultureInfo.InvariantCulture)).Append(","); | ||
imageProcessorUrl.Append(options.Crop.Y2.ToString(CultureInfo.InvariantCulture)); | ||
imageProcessorUrl.Append("&cropmode=percentage"); | ||
if (options.Height.HasValue) | ||
{ | ||
AddQueryString("height", options.Height.Value); | ||
} | ||
|
||
if (options.Quality.HasValue) | ||
{ | ||
AddQueryString("quality", options.Quality.Value); | ||
} | ||
|
||
if (string.IsNullOrWhiteSpace(options.FurtherOptions) == false) | ||
{ | ||
AppendQueryString(options.FurtherOptions.TrimStart('?', '&')); | ||
ronaldbarendse marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
if (string.IsNullOrWhiteSpace(options.CacheBusterValue) == false) | ||
{ | ||
AddQueryString("rnd", options.CacheBusterValue); | ||
} | ||
|
||
return imageUrl.ToString(); | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.