Skip to content

Commit 2cb7763

Browse files
Allow ImageSharp to determine the best pixel format.
1 parent 39c9a8c commit 2cb7763

11 files changed

Lines changed: 145 additions & 40 deletions

File tree

src/ImageSharp.Web/FormattedImage.cs

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,17 @@ public sealed class FormattedImage : IDisposable
2626
/// </summary>
2727
/// <param name="image">The image.</param>
2828
/// <param name="format">The format.</param>
29-
internal FormattedImage(Image<Rgba32> image, IImageFormat format)
29+
internal FormattedImage(Image image, IImageFormat format)
3030
{
3131
this.Image = image;
3232
this.imageFormatsManager = image.GetConfiguration().ImageFormatsManager;
3333
this.Format = format;
3434
}
3535

3636
/// <summary>
37-
/// Gets the image.
37+
/// Gets the decoded image.
3838
/// </summary>
39-
public Image<Rgba32> Image { get; private set; }
39+
public Image Image { get; private set; }
4040

4141
/// <summary>
4242
/// Gets or sets the format.
@@ -81,14 +81,42 @@ public IImageEncoder Encoder
8181
}
8282

8383
/// <summary>
84-
/// Loads the specified source.
84+
/// Create a new instance of the <see cref="Image"/> class from the given stream.
85+
/// </summary>
86+
/// <typeparam name="TPixel">The pixel format.</typeparam>
87+
/// <param name="configuration">The configuration.</param>
88+
/// <param name="source">The source.</param>
89+
/// <returns>The <see cref="FormattedImage"/>.</returns>
90+
public static FormattedImage Load<TPixel>(Configuration configuration, Stream source)
91+
where TPixel : unmanaged, IPixel<TPixel>
92+
{
93+
var image = Image.Load<TPixel>(configuration, source, out IImageFormat format);
94+
return new FormattedImage(image, format);
95+
}
96+
97+
/// <summary>
98+
/// Create a new instance of the <see cref="Image"/> class from the given stream.
8599
/// </summary>
86100
/// <param name="configuration">The configuration.</param>
87101
/// <param name="source">The source.</param>
88102
/// <returns>The <see cref="FormattedImage"/>.</returns>
89103
public static FormattedImage Load(Configuration configuration, Stream source)
90104
{
91-
var image = ImageSharp.Image.Load<Rgba32>(configuration, source, out IImageFormat format);
105+
var image = Image.Load(configuration, source, out IImageFormat format);
106+
return new FormattedImage(image, format);
107+
}
108+
109+
/// <summary>
110+
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
111+
/// </summary>
112+
/// <typeparam name="TPixel">The pixel format.</typeparam>
113+
/// <param name="configuration">The configuration.</param>
114+
/// <param name="source">The source.</param>
115+
/// <returns>A <see cref="Task{FormattedImage}"/> representing the asynchronous operation.</returns>
116+
public static async Task<FormattedImage> LoadAsync<TPixel>(Configuration configuration, Stream source)
117+
where TPixel : unmanaged, IPixel<TPixel>
118+
{
119+
(Image<TPixel> image, IImageFormat format) = await Image.LoadWithFormatAsync<TPixel>(configuration, source);
92120
return new FormattedImage(image, format);
93121
}
94122

@@ -100,7 +128,7 @@ public static FormattedImage Load(Configuration configuration, Stream source)
100128
/// <returns>A <see cref="Task{FormattedImage}"/> representing the asynchronous operation.</returns>
101129
public static async Task<FormattedImage> LoadAsync(Configuration configuration, Stream source)
102130
{
103-
(Image<Rgba32> image, IImageFormat format) = await ImageSharp.Image.LoadWithFormatAsync<Rgba32>(configuration, source);
131+
(Image image, IImageFormat format) = await Image.LoadWithFormatAsync(configuration, source);
104132
return new FormattedImage(image, format);
105133
}
106134

@@ -115,7 +143,7 @@ public static async Task<FormattedImage> LoadAsync(Configuration configuration,
115143
/// </summary>
116144
/// <param name="destination">The destination stream.</param>
117145
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
118-
public async Task SaveAsync(Stream destination) => await this.Image.SaveAsync(destination, this.encoder);
146+
public Task SaveAsync(Stream destination) => this.Image.SaveAsync(destination, this.encoder);
119147

120148
/// <summary>
121149
/// Performs application-defined tasks associated with freeing, releasing, or resetting

src/ImageSharp.Web/Middleware/ImageSharpMiddleware.cs

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using Microsoft.Extensions.Options;
1515
using Microsoft.IO;
1616
using SixLabors.ImageSharp.Formats;
17+
using SixLabors.ImageSharp.PixelFormats;
1718
using SixLabors.ImageSharp.Web.Caching;
1819
using SixLabors.ImageSharp.Web.Commands;
1920
using SixLabors.ImageSharp.Web.Processors;
@@ -335,19 +336,43 @@ private async Task ProcessRequestAsync(
335336
}
336337
else
337338
{
338-
using FormattedImage image = await FormattedImage.LoadAsync(this.options.Configuration, inStream);
339-
340-
image.Process(
341-
this.logger,
342-
this.processors,
343-
commands,
344-
this.commandParser,
345-
this.parserCulture);
346-
347-
await this.options.OnBeforeSaveAsync.Invoke(image);
348-
349-
image.Save(outStream);
350-
format = image.Format;
339+
FormattedImage image = null;
340+
try
341+
{
342+
// Now we ca finally process the image.
343+
// We first sort the processor collection by command order then use that collection to determine whether the decoded image pixel format
344+
// explicitly requires an alpha component in order to allow correct processing.
345+
//
346+
// The non-generic variant will decode to the correct pixel format based upon the encoded image metadata which can yield
347+
// massive memory savings.
348+
IReadOnlyList<(int Index, IImageWebProcessor Processor)> sortedProcessors = this.processors.OrderBySupportedCommands(commands);
349+
bool requiresAlpha = sortedProcessors.RequiresAlphaComponent(commands, this.commandParser, this.parserCulture);
350+
351+
if (requiresAlpha)
352+
{
353+
image = await FormattedImage.LoadAsync<Rgba32>(this.options.Configuration, inStream);
354+
}
355+
else
356+
{
357+
image = await FormattedImage.LoadAsync(this.options.Configuration, inStream);
358+
}
359+
360+
image.Process(
361+
this.logger,
362+
sortedProcessors,
363+
commands,
364+
this.commandParser,
365+
this.parserCulture);
366+
367+
await this.options.OnBeforeSaveAsync.Invoke(image);
368+
369+
image.Save(outStream);
370+
format = image.Format;
371+
}
372+
finally
373+
{
374+
image?.Dispose();
375+
}
351376
}
352377
}
353378

src/ImageSharp.Web/Processors/BackgroundColorWebProcessor.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,8 @@ public FormattedImage Process(
4545

4646
return image;
4747
}
48+
49+
/// <inheritdoc/>
50+
public bool RequiresAlphaComponent(CommandCollection commands, CommandParser parser, CultureInfo culture) => true;
4851
}
4952
}

src/ImageSharp.Web/Processors/FormatWebProcessor.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,8 @@ public FormattedImage Process(
6464

6565
return image;
6666
}
67+
68+
/// <inheritdoc/>
69+
public bool RequiresAlphaComponent(CommandCollection commands, CommandParser parser, CultureInfo culture) => true;
6770
}
6871
}

src/ImageSharp.Web/Processors/IImageWebProcessor.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,17 @@ FormattedImage Process(
3535
CommandCollection commands,
3636
CommandParser parser,
3737
CultureInfo culture);
38+
39+
/// <summary>
40+
/// Returns a value indicating whether the image to be processed should be decoded using a pixel format that supports
41+
/// an alpha component for correct processing.
42+
/// </summary>
43+
/// <param name="commands">The ordered collection containing the processing commands.</param>
44+
/// <param name="parser">The command parser use for parting commands.</param>
45+
/// <param name="culture">
46+
/// The <see cref="CultureInfo"/> to use as the current parsing culture.
47+
/// </param>
48+
/// <returns>The <see cref="bool"/> indicating whether an alpha component is required.</returns>
49+
bool RequiresAlphaComponent(CommandCollection commands, CommandParser parser, CultureInfo culture);
3850
}
3951
}

src/ImageSharp.Web/Processors/QualityWebProcessor.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,5 +82,8 @@ public FormattedImage Process(
8282

8383
return image;
8484
}
85+
86+
/// <inheritdoc/>
87+
public bool RequiresAlphaComponent(CommandCollection commands, CommandParser parser, CultureInfo culture) => false;
8588
}
8689
}

src/ImageSharp.Web/Processors/ResizeWebProcessor.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,13 @@ private static ResizeOptions GetResizeOptions(
123123
return options;
124124
}
125125

126+
/// <inheritdoc/>
127+
public bool RequiresAlphaComponent(CommandCollection commands, CommandParser parser, CultureInfo culture)
128+
{
129+
ResizeMode mode = parser.ParseValue<ResizeMode>(commands.GetValueOrDefault(Mode), culture);
130+
return mode is ResizeMode.Pad or ResizeMode.BoxPad;
131+
}
132+
126133
private static Size ParseSize(
127134
Image image,
128135
CommandCollection commands,

src/ImageSharp.Web/Processors/WebProcessingExtensions.cs

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,22 @@ internal static class WebProcessingExtensions
2121
/// <param name="logger">The type used for performing logging.</param>
2222
/// <param name="processors">The collection of available processors.</param>
2323
/// <param name="commands">The parsed collection of processing commands.</param>
24-
/// <param name="commandParser">The command parser use for parting commands.</param>
24+
/// <param name="parser">The command parser use for parting commands.</param>
2525
/// <param name="culture">
2626
/// The <see cref="CultureInfo"/> to use as the current parsing culture.
2727
/// </param>
2828
/// <returns>The <see cref="FormattedImage"/>.</returns>
2929
public static FormattedImage Process(
3030
this FormattedImage source,
3131
ILogger logger,
32-
IEnumerable<IImageWebProcessor> processors,
32+
IReadOnlyList<(int Index, IImageWebProcessor Processor)> processors,
3333
CommandCollection commands,
34-
CommandParser commandParser,
34+
CommandParser parser,
3535
CultureInfo culture)
3636
{
37-
foreach (IImageWebProcessor processor in processors.GetBySupportedCommands(commands))
37+
foreach ((int Index, IImageWebProcessor Processor) processor in processors)
3838
{
39-
source = processor.Process(source, logger, commands, commandParser, culture);
39+
source = processor.Processor.Process(source, logger, commands, parser, culture);
4040
}
4141

4242
return source;
@@ -50,10 +50,9 @@ public static FormattedImage Process(
5050
/// <returns>
5151
/// The sorted proccessors that supports any of the specified commands.
5252
/// </returns>
53-
public static IEnumerable<IImageWebProcessor> GetBySupportedCommands(this IEnumerable<IImageWebProcessor> processors, CommandCollection commands)
53+
public static IReadOnlyList<(int Index, IImageWebProcessor Processor)> OrderBySupportedCommands(this IEnumerable<IImageWebProcessor> processors, CommandCollection commands)
5454
{
55-
var indexedProcessors = new List<(int Index, IImageWebProcessor Processor)>();
56-
55+
List<(int Index, IImageWebProcessor Processor)> indexedProcessors = new();
5756
foreach (IImageWebProcessor processor in processors)
5857
{
5958
// Get index of first supported command
@@ -65,12 +64,7 @@ public static IEnumerable<IImageWebProcessor> GetBySupportedCommands(this IEnume
6564
}
6665

6766
indexedProcessors.Sort((x, y) => x.Index.CompareTo(y.Index));
68-
69-
// Return sorted processors
70-
foreach ((int _, IImageWebProcessor processor) in indexedProcessors)
71-
{
72-
yield return processor;
73-
}
67+
return indexedProcessors;
7468
}
7569

7670
/// <summary>
@@ -93,5 +87,31 @@ public static bool IsSupportedCommand(this IImageWebProcessor processor, string
9387

9488
return false;
9589
}
90+
91+
/// <summary>
92+
/// Returns a value indicating whether the image to be processed should be decoded using a pixel format that supports
93+
/// an alpha component for correct processing.
94+
/// </summary>
95+
/// <param name="processors">The collection of ordered processors.</param>
96+
/// <param name="commands">The ordered collection containing the processing commands.</param>
97+
/// <param name="parser">The command parser use for parting commands.</param>
98+
/// <param name="culture">
99+
/// The <see cref="CultureInfo"/> to use as the current parsing culture.
100+
/// </param>
101+
/// <returns>The <see cref="bool"/> indicating whether an alpha component is required.</returns>
102+
public static bool RequiresAlphaComponent(
103+
this IReadOnlyList<(int Index, IImageWebProcessor Processor)> processors,
104+
CommandCollection commands,
105+
CommandParser parser,
106+
CultureInfo culture)
107+
{
108+
bool requiresAlpha = false;
109+
foreach ((int Index, IImageWebProcessor Processor) processor in processors)
110+
{
111+
requiresAlpha |= processor.Processor.RequiresAlphaComponent(commands, parser, culture);
112+
}
113+
114+
return requiresAlpha;
115+
}
96116
}
97117
}

tests/ImageSharp.Web.Tests/DependencyInjection/MockWebProcessor.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,7 @@ public FormattedImage Process(
2121
CommandParser parser,
2222
CultureInfo culture)
2323
=> image;
24+
25+
public bool RequiresAlphaComponent(CommandCollection commands, CommandParser parser, CultureInfo culture) => false;
2426
}
2527
}

tests/ImageSharp.Web.Tests/Processors/WebProcessingExtensionsTests.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Apache License, Version 2.0.
33

4-
using System.Linq;
4+
using System.Collections.Generic;
55
using SixLabors.ImageSharp.Web.Commands;
66
using SixLabors.ImageSharp.Web.Processors;
77
using SixLabors.ImageSharp.Web.Tests.DependencyInjection;
@@ -29,11 +29,11 @@ public void WebProcessingExtensions_GetBySupportedCommands()
2929
new(ResizeWebProcessor.Height, null)
3030
};
3131

32-
IImageWebProcessor[] supportedProcessors = processors.GetBySupportedCommands(commands).ToArray();
32+
IReadOnlyList<(int Index, IImageWebProcessor Processor)> supportedProcessors = processors.OrderBySupportedCommands(commands);
3333

34-
Assert.Equal(2, supportedProcessors.Length);
35-
Assert.IsType<ResizeWebProcessor>(supportedProcessors[0]);
36-
Assert.IsType<QualityWebProcessor>(supportedProcessors[1]);
34+
Assert.Equal(2, supportedProcessors.Count);
35+
Assert.IsType<ResizeWebProcessor>(supportedProcessors[0].Processor);
36+
Assert.IsType<QualityWebProcessor>(supportedProcessors[1].Processor);
3737
}
3838

3939
[Fact]
@@ -46,7 +46,7 @@ public void WebProcessingExtensions_GetBySupportedCommands_Empty()
4646

4747
CommandCollection commands = new();
4848

49-
IImageWebProcessor[] supportedProcessors = processors.GetBySupportedCommands(commands).ToArray();
49+
IReadOnlyList<(int Index, IImageWebProcessor Processor)> supportedProcessors = processors.OrderBySupportedCommands(commands);
5050

5151
Assert.Empty(supportedProcessors);
5252
}

0 commit comments

Comments
 (0)