Skip to content

Commit 338c719

Browse files
Merge pull request #396 from SixLabors/js/smart-encoder-options
Handle ICC profiles and default encoders
2 parents 037fa9a + 175edfa commit 338c719

File tree

4 files changed

+81
-3
lines changed

4 files changed

+81
-3
lines changed

samples/ImageSharp.Web.Sample/Pages/Index.cshtml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,29 @@
120120
<img src="sixlabors.imagesharp.web.png" imagesharp-width="300" imagesharp-rsampler="Resampler.NearestNeighbor" />
121121
</p>
122122
</div>
123+
123124
</section>
125+
<hr />
126+
<section>
127+
<h2>ICC Profiles</h2>
128+
<div>
129+
<p>
130+
<code>issue_2723.jpg?width=2000</code>
131+
</p>
132+
<p>
133+
<img src="issue_2723.jpg" imagesharp-width="2000" />
134+
</p>
135+
</div>
136+
<div>
137+
<p>
138+
<code>issue_2723.jpg?width=2000&format=webp</code>
139+
</p>
140+
<p>
141+
<img src="issue_2723.jpg" imagesharp-width="2000" imagesharp-format="Format.WebP" />
142+
</p>
143+
</div>
144+
</section>
145+
124146
<hr />
125147
<section>
126148
<h2>Format</h2>
Lines changed: 3 additions & 0 deletions
Loading

src/ImageSharp.Web/Middleware/ImageSharpMiddleware.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,8 +344,22 @@ private async Task ProcessRequestAsync(
344344

345345
await using (Stream inStream = await sourceImageResolver.OpenReadAsync())
346346
{
347-
DecoderOptions decoderOptions = await this.options.OnBeforeLoadAsync.Invoke(imageCommandContext, this.options.Configuration)
348-
?? new() { Configuration = this.options.Configuration };
347+
DecoderOptions decoderOptions =
348+
await this.options.OnBeforeLoadAsync.Invoke(imageCommandContext, this.options.Configuration)
349+
350+
// If custom decoder options have not been provided and we know our options are the
351+
// default options it is safe to configure the decoder options to convert color profiles
352+
// since we know tha the JPEG encoder will always use YCbCr encoding instead of preserving the
353+
// original color encoding (potentially CMYK, Ycck) which can cause color loss.
354+
// Compaction is always safe as this simply removes sRGB color profile data from the image
355+
// metadata which is not required for correct processing.
356+
?? new()
357+
{
358+
Configuration = this.options.Configuration,
359+
ColorProfileHandling = this.options.HasDefaultConfiguration
360+
? ColorProfileHandling.Convert
361+
: ColorProfileHandling.Compact
362+
};
349363

350364
FormattedImage? image = null;
351365
try

src/ImageSharp.Web/Middleware/ImageSharpMiddlewareOptions.cs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
using Microsoft.AspNetCore.Http;
66
using Microsoft.IO;
77
using SixLabors.ImageSharp.Formats;
8+
using SixLabors.ImageSharp.Formats.Jpeg;
9+
using SixLabors.ImageSharp.Formats.Png;
10+
using SixLabors.ImageSharp.Formats.Webp;
811
using SixLabors.ImageSharp.Web.Commands;
912
using SixLabors.ImageSharp.Web.Providers;
1013

@@ -15,6 +18,8 @@ namespace SixLabors.ImageSharp.Web.Middleware;
1518
/// </summary>
1619
public class ImageSharpMiddlewareOptions
1720
{
21+
private static readonly Configuration DefaultConfiguration = CreateDefaultConfiguration();
22+
1823
private Func<ImageCommandContext, byte[], string> onComputeHMAC = (context, secret) =>
1924
{
2025
string uri = CaseHandlingUriBuilder.BuildRelative(
@@ -35,7 +40,12 @@ public class ImageSharpMiddlewareOptions
3540
/// <summary>
3641
/// Gets or sets the base library configuration.
3742
/// </summary>
38-
public Configuration Configuration { get; set; } = Configuration.Default;
43+
public Configuration Configuration { get; set; } = DefaultConfiguration;
44+
45+
/// <summary>
46+
/// Gets a value indicating whether the current configuration is the default configuration.
47+
/// </summary>
48+
internal bool HasDefaultConfiguration => ReferenceEquals(this.Configuration, DefaultConfiguration);
3949

4050
/// <summary>
4151
/// Gets or sets the recyclable memorystream manager used for managing pooled stream
@@ -176,4 +186,33 @@ public Func<HttpContext, Task> OnPrepareResponseAsync
176186
this.onPrepareResponseAsync = value;
177187
}
178188
}
189+
190+
private static Configuration CreateDefaultConfiguration()
191+
{
192+
// Build a Configuration for the requests that replaces the default JPEG, PNG, and WebP encoders
193+
// with ones with compression options that are more suitable for web use.
194+
// We do not skip metadata as that can affect things like orientation.
195+
Configuration configuration = Configuration.Default.Clone();
196+
configuration.ImageFormatsManager.SetEncoder(JpegFormat.Instance, new JpegEncoder()
197+
{
198+
Quality = 75,
199+
Progressive = true,
200+
Interleaved = true,
201+
ColorType = JpegColorType.YCbCrRatio420,
202+
});
203+
204+
configuration.ImageFormatsManager.SetEncoder(PngFormat.Instance, new PngEncoder()
205+
{
206+
CompressionLevel = PngCompressionLevel.BestCompression,
207+
FilterMethod = PngFilterMethod.Adaptive,
208+
});
209+
210+
configuration.ImageFormatsManager.SetEncoder(WebpFormat.Instance, new WebpEncoder()
211+
{
212+
Quality = 75,
213+
Method = WebpEncodingMethod.BestQuality,
214+
});
215+
216+
return configuration;
217+
}
179218
}

0 commit comments

Comments
 (0)