Browse Source

Implemented the IEncoderOptions inside the jpeg encoder.

af/merge-core
Dirk Lemstra 9 years ago
committed by Dirk Lemstra
parent
commit
e429b812cc
  1. 29
      src/ImageSharp.Formats.Jpeg/IJpegEncoderOptions.cs
  2. 11
      src/ImageSharp.Formats.Jpeg/ImageExtensions.cs
  3. 75
      src/ImageSharp.Formats.Jpeg/JpegEncoder.cs
  4. 40
      src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs
  5. 62
      src/ImageSharp.Formats.Jpeg/JpegEncoderOptions.cs
  6. 5
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  7. 67
      tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
  8. 5
      tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs
  9. 5
      tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs

29
src/ImageSharp.Formats.Jpeg/IJpegEncoderOptions.cs

@ -0,0 +1,29 @@
// <copyright file="IJpegEncoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
/// <summary>
/// Encapsulates the options for the <see cref="JpegEncoder"/>.
/// </summary>
public interface IJpegEncoderOptions : IEncoderOptions
{
/// <summary>
/// Gets the quality, that will be used to encode the image. Quality
/// index must be between 0 and 100 (compression from max to min).
/// </summary>
/// <remarks>
/// If the quality is less than or equal to 80, the subsampling ratio will switch to <see cref="JpegSubsample.Ratio420"/>
/// </remarks>
/// <value>The quality of the jpg image from 0 to 100.</value>
int Quality { get; }
/// <summary>
/// Gets the subsample ration, that will be used to encode the image.
/// </summary>
/// <value>The subsample ratio of the jpg image.</value>
JpegSubsample? Subsample { get; }
}
}

11
src/ImageSharp.Formats.Jpeg/ImageExtensions.cs

@ -21,13 +21,18 @@ namespace ImageSharp
/// <typeparam name="TColor">The pixel format.</typeparam> /// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param> /// <param name="stream">The stream to save the image to.</param>
/// <param name="quality">The quality to save the image to. Between 1 and 100.</param> /// <param name="options">The options for the encoder.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception> /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns> /// <returns>
/// The <see cref="Image{TColor}"/>. /// The <see cref="Image{TColor}"/>.
/// </returns> /// </returns>
public static Image<TColor> SaveAsJpeg<TColor>(this Image<TColor> source, Stream stream, int quality = 75) public static Image<TColor> SaveAsJpeg<TColor>(this Image<TColor> source, Stream stream, IJpegEncoderOptions options = null)
where TColor : struct, IPixel<TColor> where TColor : struct, IPixel<TColor>
=> source.Save(stream, new JpegEncoder { Quality = quality }); {
JpegEncoder encoder = new JpegEncoder();
encoder.Encode(source, stream, options);
return source;
}
} }
} }

75
src/ImageSharp.Formats.Jpeg/JpegEncoder.cs

@ -5,7 +5,6 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System;
using System.IO; using System.IO;
/// <summary> /// <summary>
@ -13,73 +12,27 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
public class JpegEncoder : IImageEncoder public class JpegEncoder : IImageEncoder
{ {
/// <summary> /// <inheritdoc/>
/// The quality used to encode the image. public void Encode<TColor>(Image<TColor> image, Stream stream, IEncoderOptions options)
/// </summary> where TColor : struct, IPixel<TColor>
private int quality = 75;
/// <summary>
/// The subsamples scheme used to encode the image.
/// </summary>
private JpegSubsample subsample = JpegSubsample.Ratio420;
/// <summary>
/// Whether subsampling has been specifically set.
/// </summary>
private bool subsampleSet;
/// <summary>
/// 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).
/// </summary>
/// <remarks>
/// If the quality is less than or equal to 80, the subsampling ratio will switch to <see cref="JpegSubsample.Ratio420"/>
/// </remarks>
/// <value>The quality of the jpg image from 0 to 100.</value>
public int Quality
{ {
get { return this.quality; } IJpegEncoderOptions gifOptions = JpegEncoderOptions.Create(options);
set { this.quality = value.Clamp(1, 100); }
this.Encode(image, stream, gifOptions);
} }
/// <summary> /// <summary>
/// Gets or sets the subsample ration, that will be used to encode the image. /// Encodes the image to the specified stream from the <see cref="Image{TColor}"/>.
/// </summary> /// </summary>
/// <value>The subsample ratio of the jpg image.</value> /// <typeparam name="TColor">The pixel format.</typeparam>
public JpegSubsample Subsample /// <param name="image">The <see cref="Image{TColor}"/> to encode from.</param>
{ /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
get /// <param name="options">The options for the encoder.</param>
{ public void Encode<TColor>(Image<TColor> image, Stream stream, IJpegEncoderOptions options)
return this.subsample;
}
set
{
this.subsample = value;
this.subsampleSet = true;
}
}
/// <inheritdoc/>
public void Encode<TColor>(Image<TColor> image, Stream stream, IEncoderOptions options)
where TColor : struct, IPixel<TColor> where TColor : struct, IPixel<TColor>
{ {
// Ensure that quality can be set but has a fallback. JpegEncoderCore encode = new JpegEncoderCore(options);
if (image.MetaData.Quality > 0) encode.Encode(image, stream);
{
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);
}
} }
} }
} }

40
src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs

@ -118,6 +118,11 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private readonly byte[] huffmanBuffer = new byte[179]; private readonly byte[] huffmanBuffer = new byte[179];
/// <summary>
/// The options for the encoder.
/// </summary>
private readonly IJpegEncoderOptions options;
/// <summary> /// <summary>
/// The accumulated bits to write to the stream. /// The accumulated bits to write to the stream.
/// </summary> /// </summary>
@ -148,15 +153,22 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private JpegSubsample subsample; private JpegSubsample subsample;
/// <summary>
/// Initializes a new instance of the <see cref="JpegEncoderCore"/> class.
/// </summary>
/// <param name="options">The options for the encoder.</param>
public JpegEncoderCore(IJpegEncoderOptions options)
{
this.options = options ?? new JpegEncoderOptions();
}
/// <summary> /// <summary>
/// Encode writes the image to the jpeg baseline format with the given options. /// Encode writes the image to the jpeg baseline format with the given options.
/// </summary> /// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam> /// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="image">The image to write from.</param> /// <param name="image">The image to write from.</param>
/// <param name="stream">The stream to write to.</param> /// <param name="stream">The stream to write to.</param>
/// <param name="quality">The quality.</param> public void Encode<TColor>(Image<TColor> image, Stream stream)
/// <param name="sample">The subsampling mode.</param>
public void Encode<TColor>(Image<TColor> image, Stream stream, int quality, JpegSubsample sample)
where TColor : struct, IPixel<TColor> where TColor : struct, IPixel<TColor>
{ {
Guard.NotNull(image, nameof(image)); 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}."); throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}.");
} }
this.outputStream = stream; // Ensure that quality can be set but has a fallback.
this.subsample = sample; int quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality;
if (quality == 0)
if (quality < 1)
{ {
quality = 1; quality = 75;
} }
if (quality > 100) quality = quality.Clamp(1, 100);
{
quality = 100; this.outputStream = stream;
} this.subsample = this.options.Subsample ?? (quality >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420);
// Convert from a quality rating to a scaling factor. // Convert from a quality rating to a scaling factor.
int scale; int scale;
@ -706,6 +717,11 @@ namespace ImageSharp.Formats
private void WriteProfiles<TColor>(Image<TColor> image) private void WriteProfiles<TColor>(Image<TColor> image)
where TColor : struct, IPixel<TColor> where TColor : struct, IPixel<TColor>
{ {
if (this.options.IgnoreMetadata)
{
return;
}
image.MetaData.SyncProfiles(); image.MetaData.SyncProfiles();
this.WriteProfile(image.MetaData.ExifProfile); this.WriteProfile(image.MetaData.ExifProfile);
} }

62
src/ImageSharp.Formats.Jpeg/JpegEncoderOptions.cs

@ -0,0 +1,62 @@
// <copyright file="JpegEncoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
/// <summary>
/// Encapsulates the options for the <see cref="JpegEncoder"/>.
/// </summary>
public sealed class JpegEncoderOptions : EncoderOptions, IJpegEncoderOptions
{
/// <summary>
/// Initializes a new instance of the <see cref="JpegEncoderOptions"/> class.
/// </summary>
public JpegEncoderOptions()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="JpegEncoderOptions"/> class.
/// </summary>
/// <param name="options">The options for the encoder.</param>
private JpegEncoderOptions(IEncoderOptions options)
: base(options)
{
}
/// <summary>
/// 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).
/// </summary>
/// <remarks>
/// If the quality is less than or equal to 80, the subsampling ratio will switch to <see cref="JpegSubsample.Ratio420"/>
/// </remarks>
/// <value>The quality of the jpg image from 0 to 100.</value>
public int Quality { get; set; }
/// <summary>
/// Gets or sets the subsample ration, that will be used to encode the image.
/// </summary>
/// <value>The subsample ratio of the jpg image.</value>
public JpegSubsample? Subsample { get; set; }
/// <summary>
/// Converts the options to a <see cref="IJpegEncoderOptions"/> instance with a
/// cast or by creating a new instance with the specfied options.
/// </summary>
/// <param name="options">The options for the encoder.</param>
/// <returns>The options for the <see cref="JpegEncoder"/>.</returns>
internal static IJpegEncoderOptions Create(IEncoderOptions options)
{
IJpegEncoderOptions jpegOptions = options as IJpegEncoderOptions;
if (jpegOptions != null)
{
return jpegOptions;
}
return new JpegEncoderOptions(options);
}
}
}

5
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs

@ -61,12 +61,13 @@ namespace ImageSharp.Tests
byte[] data; byte[] data;
using (Image<TColor> image = provider.GetImage()) using (Image<TColor> 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]; data = new byte[65536];
using (MemoryStream ms = new MemoryStream(data)) using (MemoryStream ms = new MemoryStream(data))
{ {
image.Save(ms, encoder); image.Save(ms, encoder, options);
} }
} }

67
tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs

@ -38,11 +38,12 @@ namespace ImageSharp.Tests
{ {
image.MetaData.Quality = quality; image.MetaData.Quality = quality;
image.MetaData.ExifProfile = null; // Reduce the size of the file 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.TestName += $"{subsample}_Q{quality}";
provider.Utility.SaveTestOutputFile(image, "png"); 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"))) using (FileStream outputStream = File.OpenWrite(utility.GetTestOutputFileName("jpg")))
{ {
JpegEncoder encoder = new JpegEncoder() JpegEncoder encoder = new JpegEncoder();
{
Subsample = subSample,
Quality = quality
};
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);
}
} }
} }
} }

5
tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs

@ -81,8 +81,9 @@ namespace ImageSharp.Tests
{ {
foreach (Image<Color> img in testImages) foreach (Image<Color> img in testImages)
{ {
JpegEncoder encoder = new JpegEncoder() { Quality = quality, Subsample = subsample }; JpegEncoder encoder = new JpegEncoder();
img.Save(ms, encoder); JpegEncoderOptions options = new JpegEncoderOptions { Quality = quality, Subsample = subsample };
img.Save(ms, encoder, options);
ms.Seek(0, SeekOrigin.Begin); ms.Seek(0, SeekOrigin.Begin);
} }
}, },

5
tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs

@ -75,7 +75,8 @@ namespace ImageSharp.Tests
/// <param name="image">The image instance</param> /// <param name="image">The image instance</param>
/// <param name="extension">The requested extension</param> /// <param name="extension">The requested extension</param>
/// <param name="encoder">Optional encoder</param> /// <param name="encoder">Optional encoder</param>
public void SaveTestOutputFile<TColor>(Image<TColor> image, string extension = null, IImageEncoder encoder = null) /// <param name="options">Optional encoder options</param>
public void SaveTestOutputFile<TColor>(Image<TColor> image, string extension = null, IImageEncoder encoder = null, IEncoderOptions options = null)
where TColor : struct, IPixel<TColor> where TColor : struct, IPixel<TColor>
{ {
string path = this.GetTestOutputFileName(extension); string path = this.GetTestOutputFileName(extension);
@ -86,7 +87,7 @@ namespace ImageSharp.Tests
using (var stream = File.OpenWrite(path)) using (var stream = File.OpenWrite(path))
{ {
image.Save(stream, encoder); image.Save(stream, encoder, options);
} }
} }

Loading…
Cancel
Save