Skip to content
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

Allow ImageSharp to determine the best pixel format on load. #235

Merged
merged 21 commits into from
Mar 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions samples/ImageSharp.Web.Sample/wwwroot/imagesharp-logo.png

This file was deleted.

388 changes: 194 additions & 194 deletions samples/ImageSharp.Web.Sample/wwwroot/index.html

Large diffs are not rendered by default.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 0 additions & 4 deletions src/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,7 @@
<Import Project="$(MSBuildThisFileDirectory)..\Directory.Build.targets" />

<ItemGroup>
<PackageReference Update="AWSSDK.S3" Version="3.7.7.16" />
<PackageReference Update="Azure.Storage.Blobs" Version="12.10.0" />
<PackageReference Update="Microsoft.IO.RecyclableMemoryStream" Version="2.2.0" />
<PackageReference Update="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
<PackageReference Update="SixLabors.ImageSharp" Version="2.0.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp2.1'">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="AWSSDK.S3" />
<PackageReference Include="AWSSDK.S3" Version="3.7.8.8" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Azure.Storage.Blobs" />
<!--TODO: Do not upgrade this. Last Version that supports .NET Core 2.1-->
<PackageReference Include="Azure.Storage.Blobs" Version="12.10.0" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/ImageSharp.Web/Caching/PhysicalFileSystemCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ internal static string GetCacheRoot(PhysicalFileSystemCacheOptions cacheOptions,
/// <inheritdoc/>
public Task<IImageCacheResolver> GetAsync(string key)
{
string path = ToFilePath(key, this.cacheFolderDepth);
string path = Path.Combine(this.cacheRootPath, ToFilePath(key, this.cacheFolderDepth));

var metaFileInfo = new FileInfo(this.ToMetaDataFilePath(path));
if (!metaFileInfo.Exists)
Expand Down
33 changes: 14 additions & 19 deletions src/ImageSharp.Web/FormattedImage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,17 @@ public sealed class FormattedImage : IDisposable
/// </summary>
/// <param name="image">The image.</param>
/// <param name="format">The format.</param>
internal FormattedImage(Image<Rgba32> image, IImageFormat format)
internal FormattedImage(Image image, IImageFormat format)
{
this.Image = image;
this.imageFormatsManager = image.GetConfiguration().ImageFormatsManager;
this.Format = format;
}

/// <summary>
/// Gets the image.
/// Gets the decoded image.
/// </summary>
public Image<Rgba32> Image { get; private set; }
public Image Image { get; private set; }

/// <summary>
/// Gets or sets the format.
Expand Down Expand Up @@ -82,41 +82,36 @@ public IImageEncoder Encoder
}

/// <summary>
/// Loads the specified source.
/// Create a new instance of the <see cref="FormattedImage"/> class from the given stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="source">The source.</param>
/// <returns>The <see cref="FormattedImage"/>.</returns>
public static FormattedImage Load(Configuration configuration, Stream source)
/// <returns>A <see cref="Task{FormattedImage}"/> representing the asynchronous operation.</returns>
internal static async Task<FormattedImage> LoadAsync<TPixel>(Configuration configuration, Stream source)
where TPixel : unmanaged, IPixel<TPixel>
{
var image = ImageSharp.Image.Load<Rgba32>(configuration, source, out IImageFormat format);
(Image<TPixel> image, IImageFormat format) = await Image.LoadWithFormatAsync<TPixel>(configuration, source);
return new FormattedImage(image, format);
}

/// <summary>
/// Loads the specified source.
/// Create a new instance of the <see cref="FormattedImage"/> class from the given stream.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="source">The source.</param>
/// <returns>A <see cref="Task{FormattedImage}"/> representing the asynchronous operation.</returns>
public static async Task<FormattedImage> LoadAsync(Configuration configuration, Stream source)
internal static async Task<FormattedImage> LoadAsync(Configuration configuration, Stream source)
{
(Image<Rgba32> image, IImageFormat format) = await ImageSharp.Image.LoadWithFormatAsync<Rgba32>(configuration, source);
(Image image, IImageFormat format) = await Image.LoadWithFormatAsync(configuration, source);
return new FormattedImage(image, format);
}

/// <summary>
/// Saves image to the specified destination stream.
/// </summary>
/// <param name="destination">The destination stream.</param>
public void Save(Stream destination) => this.Image.Save(destination, this.encoder);

/// <summary>
/// Saves image to the specified destination stream.
/// Saves the <see cref="FormattedImage"/> to the specified destination stream.
/// </summary>
/// <param name="destination">The destination stream.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public Task SaveAsync(Stream destination) => this.Image.SaveAsync(destination, this.encoder);
internal void Save(Stream destination) => this.Image.Save(destination, this.encoder);

/// <summary>
/// Gets the EXIF orientation metata for the <see cref="FormattedImage"/>.
Expand Down
6 changes: 3 additions & 3 deletions src/ImageSharp.Web/ImageSharp.Web.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<AssemblyTitle>SixLabors.ImageSharp.Web</AssemblyTitle>
Expand Down Expand Up @@ -44,8 +44,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" />
<PackageReference Include="SixLabors.ImageSharp" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.2.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.0" />
</ItemGroup>

<Import Project="..\..\shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems" Label="Shared" />
Expand Down
51 changes: 38 additions & 13 deletions src/ImageSharp.Web/Middleware/ImageSharpMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Microsoft.Extensions.Options;
using Microsoft.IO;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Web.Caching;
using SixLabors.ImageSharp.Web.Commands;
using SixLabors.ImageSharp.Web.Processors;
Expand Down Expand Up @@ -334,19 +335,43 @@ private async Task ProcessRequestAsync(
}
else
{
using FormattedImage image = await FormattedImage.LoadAsync(this.options.Configuration, inStream);

image.Process(
this.logger,
this.processors,
commands,
this.commandParser,
this.parserCulture);

await this.options.OnBeforeSaveAsync.Invoke(image);

image.Save(outStream);
format = image.Format;
FormattedImage image = null;
try
{
// Now we can finally process the image.
// We first sort the processor collection by command order then use that collection to determine whether the decoded image pixel format
// explicitly requires an alpha component in order to allow correct processing.
//
// The non-generic variant will decode to the correct pixel format based upon the encoded image metadata which can yield
// massive memory savings.
IReadOnlyList<(int Index, IImageWebProcessor Processor)> sortedProcessors = this.processors.OrderBySupportedCommands(commands);
bool requiresAlpha = sortedProcessors.RequiresTrueColorPixelFormat(commands, this.commandParser, this.parserCulture);

if (requiresAlpha)
{
image = await FormattedImage.LoadAsync<Rgba32>(this.options.Configuration, inStream);
}
else
{
image = await FormattedImage.LoadAsync(this.options.Configuration, inStream);
}

image.Process(
this.logger,
sortedProcessors,
commands,
this.commandParser,
this.parserCulture);

await this.options.OnBeforeSaveAsync.Invoke(image);

image.Save(outStream);
format = image.Format;
}
finally
{
image?.Dispose();
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/ImageSharp.Web/Processors/BackgroundColorWebProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,8 @@ public FormattedImage Process(

return image;
}

/// <inheritdoc/>
public bool RequiresTrueColorPixelFormat(CommandCollection commands, CommandParser parser, CultureInfo culture) => true;
}
}
3 changes: 3 additions & 0 deletions src/ImageSharp.Web/Processors/FormatWebProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,8 @@ public FormattedImage Process(

return image;
}

/// <inheritdoc/>
public bool RequiresTrueColorPixelFormat(CommandCollection commands, CommandParser parser, CultureInfo culture) => false;
}
}
15 changes: 15 additions & 0 deletions src/ImageSharp.Web/Processors/IImageWebProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,20 @@ FormattedImage Process(
CommandCollection commands,
CommandParser parser,
CultureInfo culture);

/// <summary>
/// <para>
/// Returns a value indicating whether the image to be processed should be decoded using a 32 bit True Color pixel format - 8 bits per color component
/// plus an 8 bit alpha channel <see href="https://en.wikipedia.org/wiki/Color_depth#True_color_(24-bit)"/>.
/// </para>
/// <para>This method is used to determine whether optimizations can be enabled to reduce memory consumption during processing.</para>
/// </summary>
/// <param name="commands">The ordered collection containing the processing commands.</param>
/// <param name="parser">The command parser use for parting commands.</param>
/// <param name="culture">
/// The <see cref="CultureInfo"/> to use as the current parsing culture.
/// </param>
/// <returns>The <see cref="bool"/> indicating whether a 32 bit True Color pixel format is required.</returns>
bool RequiresTrueColorPixelFormat(CommandCollection commands, CommandParser parser, CultureInfo culture);
}
}
3 changes: 3 additions & 0 deletions src/ImageSharp.Web/Processors/QualityWebProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,8 @@ public FormattedImage Process(

return image;
}

/// <inheritdoc/>
public bool RequiresTrueColorPixelFormat(CommandCollection commands, CommandParser parser, CultureInfo culture) => false;
}
}
16 changes: 15 additions & 1 deletion src/ImageSharp.Web/Processors/ResizeWebProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ public class ResizeWebProcessor : IImageWebProcessor
/// </summary>
public const string Anchor = "ranchor";

/// <summary>
/// The command constant for the resize padding background color.
/// </summary>
public const string Color = "rcolor";

/// <summary>
/// The command constant for the resize orientation handling mode.
/// </summary>
Expand All @@ -67,7 +72,8 @@ private static readonly IEnumerable<string> ResizeCommands
Sampler,
Anchor,
Compand,
Orient
Orient,
Color
};

/// <inheritdoc/>
Expand Down Expand Up @@ -132,10 +138,18 @@ internal static ResizeOptions GetResizeOptions(

// Defaults to Bicubic if not set.
options.Sampler = GetSampler(commands);
options.PadColor = parser.ParseValue<Color>(commands.GetValueOrDefault(Color), culture);

return options;
}

/// <inheritdoc/>
public bool RequiresTrueColorPixelFormat(CommandCollection commands, CommandParser parser, CultureInfo culture)
{
ResizeMode mode = parser.ParseValue<ResizeMode>(commands.GetValueOrDefault(Mode), culture);
return mode is ResizeMode.Pad or ResizeMode.BoxPad;
}

private static Size ParseSize(
ushort orientation,
CommandCollection commands,
Expand Down
51 changes: 37 additions & 14 deletions src/ImageSharp.Web/Processors/WebProcessingExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,22 @@ internal static class WebProcessingExtensions
/// <param name="logger">The type used for performing logging.</param>
/// <param name="processors">The collection of available processors.</param>
/// <param name="commands">The parsed collection of processing commands.</param>
/// <param name="commandParser">The command parser use for parting commands.</param>
/// <param name="parser">The command parser use for parting commands.</param>
/// <param name="culture">
/// The <see cref="CultureInfo"/> to use as the current parsing culture.
/// </param>
/// <returns>The <see cref="FormattedImage"/>.</returns>
public static FormattedImage Process(
this FormattedImage source,
ILogger logger,
IEnumerable<IImageWebProcessor> processors,
IReadOnlyList<(int Index, IImageWebProcessor Processor)> processors,
CommandCollection commands,
CommandParser commandParser,
CommandParser parser,
CultureInfo culture)
{
foreach (IImageWebProcessor processor in processors.GetBySupportedCommands(commands))
foreach ((int Index, IImageWebProcessor Processor) processor in processors)
{
source = processor.Process(source, logger, commands, commandParser, culture);
source = processor.Processor.Process(source, logger, commands, parser, culture);
}

return source;
Expand All @@ -50,10 +50,9 @@ public static FormattedImage Process(
/// <returns>
/// The sorted proccessors that supports any of the specified commands.
/// </returns>
public static IEnumerable<IImageWebProcessor> GetBySupportedCommands(this IEnumerable<IImageWebProcessor> processors, CommandCollection commands)
public static IReadOnlyList<(int Index, IImageWebProcessor Processor)> OrderBySupportedCommands(this IEnumerable<IImageWebProcessor> processors, CommandCollection commands)
{
var indexedProcessors = new List<(int Index, IImageWebProcessor Processor)>();

List<(int Index, IImageWebProcessor Processor)> indexedProcessors = new();
foreach (IImageWebProcessor processor in processors)
{
// Get index of first supported command
Expand All @@ -65,12 +64,7 @@ public static IEnumerable<IImageWebProcessor> GetBySupportedCommands(this IEnume
}

indexedProcessors.Sort((x, y) => x.Index.CompareTo(y.Index));

// Return sorted processors
foreach ((int _, IImageWebProcessor processor) in indexedProcessors)
{
yield return processor;
}
return indexedProcessors;
}

/// <summary>
Expand All @@ -93,5 +87,34 @@ public static bool IsSupportedCommand(this IImageWebProcessor processor, string

return false;
}

/// <summary>
/// <para>
/// Returns a value indicating whether the image to be processed should be decoded using a 32 bit True Color pixel format - 8 bits per color component
/// plus an 8 bit alpha channel <see href="https://en.wikipedia.org/wiki/Color_depth#True_color_(24-bit)"/>.
/// </para>
/// <para>This method is used to determine whether optimizations can be enabled to reduce memory consumption during processing.</para>
/// </summary>
/// <param name="processors">The collection of ordered processors.</param>
/// <param name="commands">The ordered collection containing the processing commands.</param>
/// <param name="parser">The command parser use for parting commands.</param>
/// <param name="culture">
/// The <see cref="CultureInfo"/> to use as the current parsing culture.
/// </param>
/// <returns>The <see cref="bool"/> indicating whether a 32 bit True Color pixel format is required.</returns>
public static bool RequiresTrueColorPixelFormat(
this IReadOnlyList<(int Index, IImageWebProcessor Processor)> processors,
CommandCollection commands,
CommandParser parser,
CultureInfo culture)
{
bool requiresAlpha = false;
foreach ((int Index, IImageWebProcessor Processor) processor in processors)
{
requiresAlpha |= processor.Processor.RequiresTrueColorPixelFormat(commands, parser, culture);
}

return requiresAlpha;
}
}
}
4 changes: 0 additions & 4 deletions tests/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,7 @@

<!-- Test Dependencies -->
<ItemGroup>
<PackageReference Update="Azure.Storage.Blobs" Version="12.10.0" />
<PackageReference Update="BenchmarkDotNet" Version="0.13.1" />
<PackageReference Update="Microsoft.AspNetCore.Http" Version="2.2.2" />
<PackageReference Update="Microsoft.Azure.KeyVault.Core" Version="3.0.4" />
<PackageReference Update="Microsoft.Azure.Storage.Blob" Version="11.1.2" />

<!--
Don't update these. We're getting AmbiguousMatchException for
Expand Down
Loading