Browse Source

Implemented the IEncoderOptions inside the jpeg encoder.

pull/22/merge
Dirk Lemstra 9 years ago
committed by Dirk Lemstra
parent
commit
826fb4eb91
  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>
/// <param name="source">The image this method extends.</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>
/// <returns>
/// The <see cref="Image{TColor}"/>.
/// </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>
=> 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
{
using System;
using System.IO;
/// <summary>
@ -13,73 +12,27 @@ namespace ImageSharp.Formats
/// </summary>
public class JpegEncoder : IImageEncoder
{
/// <summary>
/// The quality used to encode the image.
/// </summary>
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
/// <inheritdoc/>
public void Encode<TColor>(Image<TColor> image, Stream stream, IEncoderOptions options)
where TColor : struct, IPixel<TColor>
{
get { return this.quality; }
set { this.quality = value.Clamp(1, 100); }
IJpegEncoderOptions gifOptions = JpegEncoderOptions.Create(options);
this.Encode(image, stream, gifOptions);
}
/// <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>
/// <value>The subsample ratio of the jpg image.</value>
public JpegSubsample Subsample
{
get
{
return this.subsample;
}
set
{
this.subsample = value;
this.subsampleSet = true;
}
}
/// <inheritdoc/>
public void Encode<TColor>(Image<TColor> image, Stream stream, IEncoderOptions options)
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <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>
/// <param name="options">The options for the encoder.</param>
public void Encode<TColor>(Image<TColor> image, Stream stream, IJpegEncoderOptions options)
where TColor : struct, IPixel<TColor>
{
// 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);
}
}
}

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

@ -118,6 +118,11 @@ namespace ImageSharp.Formats
/// </summary>
private readonly byte[] huffmanBuffer = new byte[179];
/// <summary>
/// The options for the encoder.
/// </summary>
private readonly IJpegEncoderOptions options;
/// <summary>
/// The accumulated bits to write to the stream.
/// </summary>
@ -148,15 +153,22 @@ namespace ImageSharp.Formats
/// </summary>
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>
/// Encode writes the image to the jpeg baseline format with the given options.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="image">The image to write from.</param>
/// <param name="stream">The stream to write to.</param>
/// <param name="quality">The quality.</param>
/// <param name="sample">The subsampling mode.</param>
public void Encode<TColor>(Image<TColor> image, Stream stream, int quality, JpegSubsample sample)
public void Encode<TColor>(Image<TColor> image, Stream stream)
where TColor : struct, IPixel<TColor>
{
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<TColor>(Image<TColor> image)
where TColor : struct, IPixel<TColor>
{
if (this.options.IgnoreMetadata)
{
return;
}
image.MetaData.SyncProfiles();
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;
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];
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.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);
}
}
}
}

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

@ -81,8 +81,9 @@ namespace ImageSharp.Tests
{
foreach (Image<Color> 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);
}
},

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

@ -75,7 +75,8 @@ namespace ImageSharp.Tests
/// <param name="image">The image instance</param>
/// <param name="extension">The requested extension</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>
{
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);
}
}

Loading…
Cancel
Save