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; + } }