diff --git a/src/ImageSharp.Formats.Jpeg/IJpegEncoderOptions.cs b/src/ImageSharp.Formats.Jpeg/IJpegEncoderOptions.cs
new file mode 100644
index 000000000..7d62dd4ef
--- /dev/null
+++ b/src/ImageSharp.Formats.Jpeg/IJpegEncoderOptions.cs
@@ -0,0 +1,29 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats
+{
+ ///
+ /// Encapsulates the options for the .
+ ///
+ public interface IJpegEncoderOptions : IEncoderOptions
+ {
+ ///
+ /// Gets the quality, that will be used to encode the image. Quality
+ /// index must be between 0 and 100 (compression from max to min).
+ ///
+ ///
+ /// If the quality is less than or equal to 80, the subsampling ratio will switch to
+ ///
+ /// The quality of the jpg image from 0 to 100.
+ int Quality { get; }
+
+ ///
+ /// Gets the subsample ration, that will be used to encode the image.
+ ///
+ /// The subsample ratio of the jpg image.
+ JpegSubsample? Subsample { get; }
+ }
+}
diff --git a/src/ImageSharp.Formats.Jpeg/ImageExtensions.cs b/src/ImageSharp.Formats.Jpeg/ImageExtensions.cs
index 2cbba02a9..311bcc643 100644
--- a/src/ImageSharp.Formats.Jpeg/ImageExtensions.cs
+++ b/src/ImageSharp.Formats.Jpeg/ImageExtensions.cs
@@ -21,13 +21,18 @@ namespace ImageSharp
/// The pixel format.
/// The image this method extends.
/// The stream to save the image to.
- /// The quality to save the image to. Between 1 and 100.
+ /// The options for the encoder.
/// Thrown if the stream is null.
///
/// The .
///
- public static Image SaveAsJpeg(this Image source, Stream stream, int quality = 75)
+ public static Image SaveAsJpeg(this Image source, Stream stream, IJpegEncoderOptions options = null)
where TColor : struct, IPixel
- => source.Save(stream, new JpegEncoder { Quality = quality });
+ {
+ JpegEncoder encoder = new JpegEncoder();
+ encoder.Encode(source, stream, options);
+
+ return source;
+ }
}
}
diff --git a/src/ImageSharp.Formats.Jpeg/JpegEncoder.cs b/src/ImageSharp.Formats.Jpeg/JpegEncoder.cs
index 46972cd93..2f2823fa2 100644
--- a/src/ImageSharp.Formats.Jpeg/JpegEncoder.cs
+++ b/src/ImageSharp.Formats.Jpeg/JpegEncoder.cs
@@ -5,7 +5,6 @@
namespace ImageSharp.Formats
{
- using System;
using System.IO;
///
@@ -13,73 +12,27 @@ namespace ImageSharp.Formats
///
public class JpegEncoder : IImageEncoder
{
- ///
- /// The quality used to encode the image.
- ///
- private int quality = 75;
-
- ///
- /// The subsamples scheme used to encode the image.
- ///
- private JpegSubsample subsample = JpegSubsample.Ratio420;
-
- ///
- /// Whether subsampling has been specifically set.
- ///
- private bool subsampleSet;
-
- ///
- /// Gets or sets the quality, that will be used to encode the image. Quality
- /// index must be between 0 and 100 (compression from max to min).
- ///
- ///
- /// If the quality is less than or equal to 80, the subsampling ratio will switch to
- ///
- /// The quality of the jpg image from 0 to 100.
- public int Quality
+ ///
+ public void Encode(Image image, Stream stream, IEncoderOptions options)
+ where TColor : struct, IPixel
{
- get { return this.quality; }
- set { this.quality = value.Clamp(1, 100); }
+ IJpegEncoderOptions gifOptions = JpegEncoderOptions.Create(options);
+
+ this.Encode(image, stream, gifOptions);
}
///
- /// Gets or sets the subsample ration, that will be used to encode the image.
+ /// Encodes the image to the specified stream from the .
///
- /// The subsample ratio of the jpg image.
- public JpegSubsample Subsample
- {
- get
- {
- return this.subsample;
- }
-
- set
- {
- this.subsample = value;
- this.subsampleSet = true;
- }
- }
-
- ///
- public void Encode(Image image, Stream stream, IEncoderOptions options)
+ /// The pixel format.
+ /// The to encode from.
+ /// The to encode the image data to.
+ /// The options for the encoder.
+ public void Encode(Image image, Stream stream, IJpegEncoderOptions options)
where TColor : struct, IPixel
{
- // Ensure that quality can be set but has a fallback.
- if (image.MetaData.Quality > 0)
- {
- this.Quality = image.MetaData.Quality;
- }
-
- JpegEncoderCore encode = new JpegEncoderCore();
- if (this.subsampleSet)
- {
- encode.Encode(image, stream, this.Quality, this.Subsample);
- }
- else
- {
- // Use 4:2:0 Subsampling at quality < 91% for reduced filesize.
- encode.Encode(image, stream, this.Quality, this.Quality >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420);
- }
+ JpegEncoderCore encode = new JpegEncoderCore(options);
+ encode.Encode(image, stream);
}
}
}
diff --git a/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs
index 0309af129..66f400c01 100644
--- a/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs
+++ b/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs
@@ -118,6 +118,11 @@ namespace ImageSharp.Formats
///
private readonly byte[] huffmanBuffer = new byte[179];
+ ///
+ /// The options for the encoder.
+ ///
+ private readonly IJpegEncoderOptions options;
+
///
/// The accumulated bits to write to the stream.
///
@@ -148,15 +153,22 @@ namespace ImageSharp.Formats
///
private JpegSubsample subsample;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The options for the encoder.
+ public JpegEncoderCore(IJpegEncoderOptions options)
+ {
+ this.options = options ?? new JpegEncoderOptions();
+ }
+
///
/// Encode writes the image to the jpeg baseline format with the given options.
///
/// The pixel format.
/// The image to write from.
/// The stream to write to.
- /// The quality.
- /// The subsampling mode.
- public void Encode(Image image, Stream stream, int quality, JpegSubsample sample)
+ public void Encode(Image image, Stream stream)
where TColor : struct, IPixel
{
Guard.NotNull(image, nameof(image));
@@ -168,18 +180,17 @@ namespace ImageSharp.Formats
throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}.");
}
- this.outputStream = stream;
- this.subsample = sample;
-
- if (quality < 1)
+ // Ensure that quality can be set but has a fallback.
+ int quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality;
+ if (quality == 0)
{
- quality = 1;
+ quality = 75;
}
- if (quality > 100)
- {
- quality = 100;
- }
+ quality = quality.Clamp(1, 100);
+
+ this.outputStream = stream;
+ this.subsample = this.options.Subsample ?? (quality >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420);
// Convert from a quality rating to a scaling factor.
int scale;
@@ -706,6 +717,11 @@ namespace ImageSharp.Formats
private void WriteProfiles(Image image)
where TColor : struct, IPixel
{
+ if (this.options.IgnoreMetadata)
+ {
+ return;
+ }
+
image.MetaData.SyncProfiles();
this.WriteProfile(image.MetaData.ExifProfile);
}
diff --git a/src/ImageSharp.Formats.Jpeg/JpegEncoderOptions.cs b/src/ImageSharp.Formats.Jpeg/JpegEncoderOptions.cs
new file mode 100644
index 000000000..6be36627c
--- /dev/null
+++ b/src/ImageSharp.Formats.Jpeg/JpegEncoderOptions.cs
@@ -0,0 +1,62 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats
+{
+ ///
+ /// Encapsulates the options for the .
+ ///
+ public sealed class JpegEncoderOptions : EncoderOptions, IJpegEncoderOptions
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public JpegEncoderOptions()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The options for the encoder.
+ private JpegEncoderOptions(IEncoderOptions options)
+ : base(options)
+ {
+ }
+
+ ///
+ /// Gets or sets the quality, that will be used to encode the image. Quality
+ /// index must be between 0 and 100 (compression from max to min).
+ ///
+ ///
+ /// If the quality is less than or equal to 80, the subsampling ratio will switch to
+ ///
+ /// The quality of the jpg image from 0 to 100.
+ public int Quality { get; set; }
+
+ ///
+ /// Gets or sets the subsample ration, that will be used to encode the image.
+ ///
+ /// The subsample ratio of the jpg image.
+ public JpegSubsample? Subsample { get; set; }
+
+ ///
+ /// Converts the options to a instance with a
+ /// cast or by creating a new instance with the specfied options.
+ ///
+ /// The options for the encoder.
+ /// The options for the .
+ internal static IJpegEncoderOptions Create(IEncoderOptions options)
+ {
+ IJpegEncoderOptions jpegOptions = options as IJpegEncoderOptions;
+ if (jpegOptions != null)
+ {
+ return jpegOptions;
+ }
+
+ return new JpegEncoderOptions(options);
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
index b8ec9d49e..476de95ff 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
@@ -61,12 +61,13 @@ namespace ImageSharp.Tests
byte[] data;
using (Image image = provider.GetImage())
{
- JpegEncoder encoder = new JpegEncoder() { Subsample = subsample, Quality = quality };
+ JpegEncoder encoder = new JpegEncoder();
+ JpegEncoderOptions options = new JpegEncoderOptions { Subsample = subsample, Quality = quality };
data = new byte[65536];
using (MemoryStream ms = new MemoryStream(data))
{
- image.Save(ms, encoder);
+ image.Save(ms, encoder, options);
}
}
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
index 6518c3e6b..741e785c0 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
@@ -38,11 +38,12 @@ namespace ImageSharp.Tests
{
image.MetaData.Quality = quality;
image.MetaData.ExifProfile = null; // Reduce the size of the file
- JpegEncoder encoder = new JpegEncoder { Subsample = subsample, Quality = quality };
+ JpegEncoder encoder = new JpegEncoder();
+ JpegEncoderOptions options = new JpegEncoderOptions { Subsample = subsample, Quality = quality };
provider.Utility.TestName += $"{subsample}_Q{quality}";
provider.Utility.SaveTestOutputFile(image, "png");
- provider.Utility.SaveTestOutputFile(image, "jpg", encoder);
+ provider.Utility.SaveTestOutputFile(image, "jpg", encoder, options);
}
}
@@ -59,13 +60,63 @@ namespace ImageSharp.Tests
using (FileStream outputStream = File.OpenWrite(utility.GetTestOutputFileName("jpg")))
{
- JpegEncoder encoder = new JpegEncoder()
- {
- Subsample = subSample,
- Quality = quality
- };
+ JpegEncoder encoder = new JpegEncoder();
- image.Save(outputStream, encoder);
+ image.Save(outputStream, encoder, new JpegEncoderOptions()
+ {
+ Subsample = subSample,
+ Quality = quality
+ });
+ }
+ }
+ }
+
+ [Fact]
+ public void Encode_IgnoreMetadataIsFalse_ExifProfileIsWritten()
+ {
+ var options = new EncoderOptions()
+ {
+ IgnoreMetadata = false
+ };
+
+ TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan);
+
+ using (Image input = testFile.CreateImage())
+ {
+ using (MemoryStream memStream = new MemoryStream())
+ {
+ input.Save(memStream, new JpegFormat(), options);
+
+ memStream.Position = 0;
+ using (Image output = new Image(memStream))
+ {
+ Assert.NotNull(output.MetaData.ExifProfile);
+ }
+ }
+ }
+ }
+
+ [Fact]
+ public void Encode_IgnoreMetadataIsTrue_ExifProfileIgnored()
+ {
+ var options = new JpegEncoderOptions()
+ {
+ IgnoreMetadata = true
+ };
+
+ TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan);
+
+ using (Image input = testFile.CreateImage())
+ {
+ using (MemoryStream memStream = new MemoryStream())
+ {
+ input.SaveAsJpeg(memStream, options);
+
+ memStream.Position = 0;
+ using (Image output = new Image(memStream))
+ {
+ Assert.Null(output.MetaData.ExifProfile);
+ }
}
}
}
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs
index bfe1f1e76..50e678bf0 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs
@@ -81,8 +81,9 @@ namespace ImageSharp.Tests
{
foreach (Image img in testImages)
{
- JpegEncoder encoder = new JpegEncoder() { Quality = quality, Subsample = subsample };
- img.Save(ms, encoder);
+ JpegEncoder encoder = new JpegEncoder();
+ JpegEncoderOptions options = new JpegEncoderOptions { Quality = quality, Subsample = subsample };
+ img.Save(ms, encoder, options);
ms.Seek(0, SeekOrigin.Begin);
}
},
diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs
index 54be62d37..38429b278 100644
--- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs
@@ -75,7 +75,8 @@ namespace ImageSharp.Tests
/// The image instance
/// The requested extension
/// Optional encoder
- public void SaveTestOutputFile(Image image, string extension = null, IImageEncoder encoder = null)
+ /// Optional encoder options
+ public void SaveTestOutputFile(Image image, string extension = null, IImageEncoder encoder = null, IEncoderOptions options = null)
where TColor : struct, IPixel
{
string path = this.GetTestOutputFileName(extension);
@@ -86,7 +87,7 @@ namespace ImageSharp.Tests
using (var stream = File.OpenWrite(path))
{
- image.Save(stream, encoder);
+ image.Save(stream, encoder, options);
}
}