diff --git a/samples/ImageSharp.Web.Sample/Pages/Index.cshtml b/samples/ImageSharp.Web.Sample/Pages/Index.cshtml
index 74cb5f15..d2012729 100644
--- a/samples/ImageSharp.Web.Sample/Pages/Index.cshtml
+++ b/samples/ImageSharp.Web.Sample/Pages/Index.cshtml
@@ -120,7 +120,29 @@
+
+
+
+ ICC Profiles
+
+
+ issue_2723.jpg?width=2000
+
+
+
+
+
+
+
+ issue_2723.jpg?width=2000&format=webp
+
+
+
+
+
+
+
Format
diff --git a/samples/ImageSharp.Web.Sample/wwwroot/issue_2723.jpg b/samples/ImageSharp.Web.Sample/wwwroot/issue_2723.jpg
new file mode 100644
index 00000000..4a342e07
--- /dev/null
+++ b/samples/ImageSharp.Web.Sample/wwwroot/issue_2723.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:56dc6400d8f4c42c505ec562e4ade0b23e613b2c16e1666eccbbaa8e997efcc7
+size 766523
diff --git a/src/ImageSharp.Web/Middleware/ImageSharpMiddleware.cs b/src/ImageSharp.Web/Middleware/ImageSharpMiddleware.cs
index bfa8ac33..55710f9b 100644
--- a/src/ImageSharp.Web/Middleware/ImageSharpMiddleware.cs
+++ b/src/ImageSharp.Web/Middleware/ImageSharpMiddleware.cs
@@ -344,8 +344,22 @@ private async Task ProcessRequestAsync(
await using (Stream inStream = await sourceImageResolver.OpenReadAsync())
{
- DecoderOptions decoderOptions = await this.options.OnBeforeLoadAsync.Invoke(imageCommandContext, this.options.Configuration)
- ?? new() { Configuration = this.options.Configuration };
+ DecoderOptions decoderOptions =
+ await this.options.OnBeforeLoadAsync.Invoke(imageCommandContext, this.options.Configuration)
+
+ // If custom decoder options have not been provided and we know our options are the
+ // default options it is safe to configure the decoder options to convert color profiles
+ // since we know tha the JPEG encoder will always use YCbCr encoding instead of preserving the
+ // original color encoding (potentially CMYK, Ycck) which can cause color loss.
+ // Compaction is always safe as this simply removes sRGB color profile data from the image
+ // metadata which is not required for correct processing.
+ ?? new()
+ {
+ Configuration = this.options.Configuration,
+ ColorProfileHandling = this.options.HasDefaultConfiguration
+ ? ColorProfileHandling.Convert
+ : ColorProfileHandling.Compact
+ };
FormattedImage? image = null;
try
diff --git a/src/ImageSharp.Web/Middleware/ImageSharpMiddlewareOptions.cs b/src/ImageSharp.Web/Middleware/ImageSharpMiddlewareOptions.cs
index 8653d4d4..2cc85bcb 100644
--- a/src/ImageSharp.Web/Middleware/ImageSharpMiddlewareOptions.cs
+++ b/src/ImageSharp.Web/Middleware/ImageSharpMiddlewareOptions.cs
@@ -5,6 +5,9 @@
using Microsoft.AspNetCore.Http;
using Microsoft.IO;
using SixLabors.ImageSharp.Formats;
+using SixLabors.ImageSharp.Formats.Jpeg;
+using SixLabors.ImageSharp.Formats.Png;
+using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Web.Commands;
using SixLabors.ImageSharp.Web.Providers;
@@ -15,6 +18,8 @@ namespace SixLabors.ImageSharp.Web.Middleware;
///
public class ImageSharpMiddlewareOptions
{
+ private static readonly Configuration DefaultConfiguration = CreateDefaultConfiguration();
+
private Func onComputeHMAC = (context, secret) =>
{
string uri = CaseHandlingUriBuilder.BuildRelative(
@@ -35,7 +40,12 @@ public class ImageSharpMiddlewareOptions
///
/// Gets or sets the base library configuration.
///
- public Configuration Configuration { get; set; } = Configuration.Default;
+ public Configuration Configuration { get; set; } = DefaultConfiguration;
+
+ ///
+ /// Gets a value indicating whether the current configuration is the default configuration.
+ ///
+ internal bool HasDefaultConfiguration => ReferenceEquals(this.Configuration, DefaultConfiguration);
///
/// Gets or sets the recyclable memorystream manager used for managing pooled stream
@@ -176,4 +186,33 @@ public Func OnPrepareResponseAsync
this.onPrepareResponseAsync = value;
}
}
+
+ private static Configuration CreateDefaultConfiguration()
+ {
+ // Build a Configuration for the requests that replaces the default JPEG, PNG, and WebP encoders
+ // with ones with compression options that are more suitable for web use.
+ // We do not skip metadata as that can affect things like orientation.
+ Configuration configuration = Configuration.Default.Clone();
+ configuration.ImageFormatsManager.SetEncoder(JpegFormat.Instance, new JpegEncoder()
+ {
+ Quality = 75,
+ Progressive = true,
+ Interleaved = true,
+ ColorType = JpegColorType.YCbCrRatio420,
+ });
+
+ configuration.ImageFormatsManager.SetEncoder(PngFormat.Instance, new PngEncoder()
+ {
+ CompressionLevel = PngCompressionLevel.BestCompression,
+ FilterMethod = PngFilterMethod.Adaptive,
+ });
+
+ configuration.ImageFormatsManager.SetEncoder(WebpFormat.Instance, new WebpEncoder()
+ {
+ Quality = 75,
+ Method = WebpEncodingMethod.BestQuality,
+ });
+
+ return configuration;
+ }
}