diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..aeae5c6ca --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,24 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "0.1.0", + "command": "dotnet", + "isShellCommand": true, + "args": [], + "tasks": [ + { + "taskName": "build", + "args": [ "src/*/project.json", "-f", "netstandard1.1" ], + "isBuildCommand": true, + "showOutput": "always", + "problemMatcher": "$msCompile" + }, + { + "taskName": "test", + "args": ["tests/ImageSharp.Tests/project.json", "-f", "netcoreapp1.1"], + "isTestCommand": true, + "showOutput": "always", + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index cfbd18de6..9d5c9788a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# ImageSharp +# ImageSharp **ImageSharp** is a new cross-platform 2D graphics API designed to allow the processing of images without the use of `System.Drawing`. @@ -56,6 +56,11 @@ If you prefer, you can compile ImageSharp yourself (please do and help!), you'll - [Visual Studio 2015 with Update 3 (or above)](https://www.visualstudio.com/news/releasenotes/vs2015-update3-vs) - The [.NET Core 1.0 SDK Installer](https://www.microsoft.com/net/core#windows) - Non VSCode link. +Alternatively on Linux you can use: + +- [Visual Studio Code](https://code.visualstudio.com/) with [C# Extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.csharp) +- [.Net Core 1.1](https://www.microsoft.com/net/core#linuxubuntu) + To clone it locally click the "Clone in Windows" button above or run the following git commands. ```bash diff --git a/build/icons/imagesharp-logo-128.png b/build/icons/imagesharp-logo-128.png index 07b040547..6d05c222c 100644 Binary files a/build/icons/imagesharp-logo-128.png and b/build/icons/imagesharp-logo-128.png differ diff --git a/build/icons/imagesharp-logo-256.png b/build/icons/imagesharp-logo-256.png index 082859f24..fc59b03e1 100644 Binary files a/build/icons/imagesharp-logo-256.png and b/build/icons/imagesharp-logo-256.png differ diff --git a/build/icons/imagesharp-logo-32.png b/build/icons/imagesharp-logo-32.png index 868a8713f..0fd4411ce 100644 Binary files a/build/icons/imagesharp-logo-32.png and b/build/icons/imagesharp-logo-32.png differ diff --git a/build/icons/imagesharp-logo-512.png b/build/icons/imagesharp-logo-512.png index d89ac3fca..5d5fb854e 100644 Binary files a/build/icons/imagesharp-logo-512.png and b/build/icons/imagesharp-logo-512.png differ diff --git a/build/icons/imagesharp-logo-64.png b/build/icons/imagesharp-logo-64.png index cfe391e5c..4e97906e5 100644 Binary files a/build/icons/imagesharp-logo-64.png and b/build/icons/imagesharp-logo-64.png differ diff --git a/build/icons/imagesharp-logo-heading.png b/build/icons/imagesharp-logo-heading.png new file mode 100644 index 000000000..b10d367bf Binary files /dev/null and b/build/icons/imagesharp-logo-heading.png differ diff --git a/build/icons/imagesharp-logo.png b/build/icons/imagesharp-logo.png index eb9fc6d17..ed1c36c5e 100644 Binary files a/build/icons/imagesharp-logo.png and b/build/icons/imagesharp-logo.png differ diff --git a/build/icons/imagesharp-logo.svg b/build/icons/imagesharp-logo.svg index 9638e9785..2df3cc80c 100644 --- a/build/icons/imagesharp-logo.svg +++ b/build/icons/imagesharp-logo.svg @@ -1,59 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/features.md b/features.md index 764d7c4a6..6bc5630ee 100644 --- a/features.md +++ b/features.md @@ -10,13 +10,23 @@ We've achieved a lot so far and hope to do a lot more in the future. We're alway - [x] Bmp (Read: 32bit, 24bit, 16 bit. Write: 32bit, 24bit just now) - [x] Png (Read: Rgb, Rgba, Grayscale, Grayscale + alpha, Palette. Write: Rgb, Rgba, Grayscale, Grayscale + alpha, Palette) Supports interlaced decoding - [x] Gif (Includes animated) - - [ ] Tiff + - [ ] Tiff (Help needed) - **Metadata** - [x] EXIF Read/Write (Jpeg just now) -- **Quantizers (IQuantizer with alpha channel support + thresholding)** +- **Quantizers (IQuantizer with alpha channel support, dithering, and thresholding)** - [x] Octree - [x] Xiaolin Wu - [x] Palette +- **DIthering (Error diffusion and Ordered)** + - [x] Atkinson + - [x] Burks + - [x] FloydSteinburg + - [x] JarvisJudiceNinke + - [x] Sieera2 + - [x] Sierra3 + - [x] SerraLite + - [x] Bayer + - [x] Ordered - **Basic color structs with implicit operators.** - [x] Color - 32bit color in RGBA order (IPackedPixel\). - [x] Bgra32 @@ -133,5 +143,5 @@ We've achieved a lot so far and hope to do a lot more in the future. We're alway - [x] DrawImage - [ ] Gradient brush (Need help) - **DrawingText** - - [x] DrawString (Single variant support just now, no italic,bold) + - [ ] DrawString (In-progress. Single variant support just now, no italic,bold) - Other stuff I haven't thought of. \ No newline at end of file diff --git a/src/ImageSharp.Drawing.Paths/project.json b/src/ImageSharp.Drawing.Paths/project.json index bf6b1fae8..b761233c3 100644 --- a/src/ImageSharp.Drawing.Paths/project.json +++ b/src/ImageSharp.Drawing.Paths/project.json @@ -44,7 +44,7 @@ "ImageSharp.Drawing": { "target": "project" }, - "SixLabors.Shapes": "0.1.0-alpha0005", + "SixLabors.Shapes": "0.1.0-alpha0006", "StyleCop.Analyzers": { "version": "1.0.0", "type": "build" diff --git a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs index 913293ff3..95f4ab472 100644 --- a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs @@ -110,7 +110,6 @@ namespace ImageSharp.Drawing.Processors Vector4 sourceVector = color.Color.ToVector4(); Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); - finalColor.W = backgroundVector.W; TColor packed = default(TColor); packed.PackFromVector4(finalColor); diff --git a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs index fed97275d..4f468c707 100644 --- a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs @@ -202,7 +202,6 @@ namespace ImageSharp.Drawing.Processors Vector4 sourceVector = applicator[x, y].ToVector4(); Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); - finalColor.W = backgroundVector.W; TColor packed = default(TColor); packed.PackFromVector4(finalColor); diff --git a/src/ImageSharp.Formats.Bmp/BmpDecoder.cs b/src/ImageSharp.Formats.Bmp/BmpDecoder.cs index 4b7da38af..9f490a3a9 100644 --- a/src/ImageSharp.Formats.Bmp/BmpDecoder.cs +++ b/src/ImageSharp.Formats.Bmp/BmpDecoder.cs @@ -26,7 +26,7 @@ namespace ImageSharp.Formats public class BmpDecoder : IImageDecoder { /// - public void Decode(Image image, Stream stream) + public void Decode(Image image, Stream stream, IDecoderOptions options) where TColor : struct, IPixel { Guard.NotNull(image, "image"); diff --git a/src/ImageSharp.Formats.Bmp/BmpEncoder.cs b/src/ImageSharp.Formats.Bmp/BmpEncoder.cs index 6edaf178b..d0a3550f6 100644 --- a/src/ImageSharp.Formats.Bmp/BmpEncoder.cs +++ b/src/ImageSharp.Formats.Bmp/BmpEncoder.cs @@ -14,17 +14,27 @@ namespace ImageSharp.Formats /// The encoder can currently only write 24-bit rgb images to streams. public class BmpEncoder : IImageEncoder { + /// + public void Encode(Image image, Stream stream, IEncoderOptions options) + where TColor : struct, IPixel + { + IBmpEncoderOptions bmpOptions = BmpEncoderOptions.Create(options); + + this.Encode(image, stream, bmpOptions); + } + /// - /// Gets or sets the number of bits per pixel. + /// Encodes the image to the specified stream from the . /// - public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; - - /// - public void Encode(Image image, Stream stream) + /// 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, IBmpEncoderOptions options) where TColor : struct, IPixel { - BmpEncoderCore encoder = new BmpEncoderCore(); - encoder.Encode(image, stream, this.BitsPerPixel); + BmpEncoderCore encoder = new BmpEncoderCore(options); + encoder.Encode(image, stream); } } } diff --git a/src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs b/src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs index 02d270a0a..df62fb6f4 100644 --- a/src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs @@ -16,34 +16,40 @@ namespace ImageSharp.Formats internal sealed class BmpEncoderCore { /// - /// The number of bits per pixel. + /// The options for the encoder. /// - private BmpBitsPerPixel bmpBitsPerPixel; + private readonly IBmpEncoderOptions options; /// /// The amount to pad each row by. /// private int padding; + /// + /// Initializes a new instance of the class. + /// + /// The options for the encoder. + public BmpEncoderCore(IBmpEncoderOptions options) + { + this.options = options ?? new BmpEncoderOptions(); + } + /// /// Encodes the image to the specified stream from the . /// /// The pixel format. /// The to encode from. /// The to encode the image data to. - /// The - public void Encode(ImageBase image, Stream stream, BmpBitsPerPixel bitsPerPixel) + public void Encode(ImageBase image, Stream stream) where TColor : struct, IPixel { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); - this.bmpBitsPerPixel = bitsPerPixel; - // Cast to int will get the bytes per pixel - short bpp = (short)(8 * (int)bitsPerPixel); + short bpp = (short)(8 * (int)this.options.BitsPerPixel); int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32); - this.padding = bytesPerLine - (image.Width * (int)bitsPerPixel); + this.padding = bytesPerLine - (image.Width * (int)this.options.BitsPerPixel); // Do not use IDisposable pattern here as we want to preserve the stream. EndianBinaryWriter writer = new EndianBinaryWriter(Endianness.LittleEndian, stream); @@ -128,7 +134,7 @@ namespace ImageSharp.Formats { using (PixelAccessor pixels = image.Lock()) { - switch (this.bmpBitsPerPixel) + switch (this.options.BitsPerPixel) { case BmpBitsPerPixel.Pixel32: this.Write32Bit(writer, pixels); diff --git a/src/ImageSharp.Formats.Bmp/BmpEncoderOptions.cs b/src/ImageSharp.Formats.Bmp/BmpEncoderOptions.cs new file mode 100644 index 000000000..a0f9ff8e0 --- /dev/null +++ b/src/ImageSharp.Formats.Bmp/BmpEncoderOptions.cs @@ -0,0 +1,45 @@ +// +// 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 BmpEncoderOptions : EncoderOptions, IBmpEncoderOptions + { + /// + /// Initializes a new instance of the class. + /// + public BmpEncoderOptions() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The options for the encoder. + private BmpEncoderOptions(IEncoderOptions options) + : base(options) + { + } + + /// + /// Gets or sets the number of bits per pixel. + /// + public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; + + /// + /// 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 IBmpEncoderOptions Create(IEncoderOptions options) + { + return options as IBmpEncoderOptions ?? new BmpEncoderOptions(options); + } + } +} diff --git a/src/ImageSharp.Formats.Bmp/IBmpEncoderOptions.cs b/src/ImageSharp.Formats.Bmp/IBmpEncoderOptions.cs new file mode 100644 index 000000000..6cf37cbae --- /dev/null +++ b/src/ImageSharp.Formats.Bmp/IBmpEncoderOptions.cs @@ -0,0 +1,18 @@ +// +// 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 IBmpEncoderOptions : IEncoderOptions + { + /// + /// Gets the number of bits per pixel. + /// + BmpBitsPerPixel BitsPerPixel { get; } + } +} diff --git a/src/ImageSharp.Formats.Gif/GifConstants.cs b/src/ImageSharp.Formats.Gif/GifConstants.cs index 5334bcba3..4af291c2b 100644 --- a/src/ImageSharp.Formats.Gif/GifConstants.cs +++ b/src/ImageSharp.Formats.Gif/GifConstants.cs @@ -5,10 +5,12 @@ namespace ImageSharp.Formats { + using System.Text; + /// /// Constants that define specific points within a gif. /// - internal sealed class GifConstants + internal static class GifConstants { /// /// The file type. @@ -50,6 +52,11 @@ namespace ImageSharp.Formats /// public const byte CommentLabel = 0xFE; + /// + /// The name of the property inside the image properties for the comments. + /// + public const string Comments = "Comments"; + /// /// The maximum comment length. /// @@ -79,5 +86,10 @@ namespace ImageSharp.Formats /// The end introducer trailer ;. /// public const byte EndIntroducer = 0x3B; + + /// + /// Gets the default encoding to use when reading comments. + /// + public static Encoding DefaultEncoding { get; } = Encoding.GetEncoding("ASCII"); } } diff --git a/src/ImageSharp.Formats.Gif/GifDecoder.cs b/src/ImageSharp.Formats.Gif/GifDecoder.cs index 76530dc50..b1e8ba928 100644 --- a/src/ImageSharp.Formats.Gif/GifDecoder.cs +++ b/src/ImageSharp.Formats.Gif/GifDecoder.cs @@ -14,10 +14,25 @@ namespace ImageSharp.Formats public class GifDecoder : IImageDecoder { /// - public void Decode(Image image, Stream stream) + public void Decode(Image image, Stream stream, IDecoderOptions options) where TColor : struct, IPixel { - new GifDecoderCore().Decode(image, stream); + IGifDecoderOptions gifOptions = GifDecoderOptions.Create(options); + + this.Decode(image, stream, gifOptions); + } + + /// + /// Decodes the image from the specified stream to the . + /// + /// The pixel format. + /// The to decode to. + /// The containing image data. + /// The options for the decoder. + public void Decode(Image image, Stream stream, IGifDecoderOptions options) + where TColor : struct, IPixel + { + new GifDecoderCore(options).Decode(image, stream); } } } diff --git a/src/ImageSharp.Formats.Gif/GifDecoderCore.cs b/src/ImageSharp.Formats.Gif/GifDecoderCore.cs index 5812b9f29..ab1edc2c7 100644 --- a/src/ImageSharp.Formats.Gif/GifDecoderCore.cs +++ b/src/ImageSharp.Formats.Gif/GifDecoderCore.cs @@ -8,6 +8,7 @@ namespace ImageSharp.Formats using System; using System.Buffers; using System.IO; + using System.Text; /// /// Performs the gif decoding operation. @@ -21,6 +22,11 @@ namespace ImageSharp.Formats /// private readonly byte[] buffer = new byte[16]; + /// + /// The decoder options. + /// + private readonly IGifDecoderOptions options; + /// /// The image to decode the information to. /// @@ -61,6 +67,15 @@ namespace ImageSharp.Formats /// private GifGraphicsControlExtension graphicsControlExtension; + /// + /// Initializes a new instance of the class. + /// + /// The decoder options. + public GifDecoderCore(IGifDecoderOptions options) + { + this.options = options ?? new GifDecoderOptions(); + } + /// /// Decodes the stream to the image. /// @@ -225,25 +240,32 @@ namespace ImageSharp.Formats /// private void ReadComments() { - int flag; + int length; - while ((flag = this.currentStream.ReadByte()) != 0) + while ((length = this.currentStream.ReadByte()) != 0) { - if (flag > GifConstants.MaxCommentLength) + if (length > GifConstants.MaxCommentLength) + { + throw new ImageFormatException($"Gif comment length '{length}' exceeds max '{GifConstants.MaxCommentLength}'"); + } + + if (this.options.IgnoreMetadata) { - throw new ImageFormatException($"Gif comment length '{flag}' exceeds max '{GifConstants.MaxCommentLength}'"); + this.currentStream.Seek(length, SeekOrigin.Current); + continue; } - byte[] flagBuffer = ArrayPool.Shared.Rent(flag); + byte[] commentsBuffer = ArrayPool.Shared.Rent(length); try { - this.currentStream.Read(flagBuffer, 0, flag); - this.decodedImage.MetaData.Properties.Add(new ImageProperty("Comments", BitConverter.ToString(flagBuffer, 0, flag))); + this.currentStream.Read(commentsBuffer, 0, length); + string comments = this.options.TextEncoding.GetString(commentsBuffer, 0, length); + this.decodedImage.MetaData.Properties.Add(new ImageProperty(GifConstants.Comments, comments)); } finally { - ArrayPool.Shared.Return(flagBuffer); + ArrayPool.Shared.Return(commentsBuffer); } } } diff --git a/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs b/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs new file mode 100644 index 000000000..bc7709f75 --- /dev/null +++ b/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System.Text; + + /// + /// Encapsulates the options for the . + /// + public sealed class GifDecoderOptions : DecoderOptions, IGifDecoderOptions + { + /// + /// Initializes a new instance of the class. + /// + public GifDecoderOptions() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The options for the decoder. + private GifDecoderOptions(IDecoderOptions options) + : base(options) + { + } + + /// + /// Gets or sets the encoding that should be used when reading comments. + /// + public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding; + + /// + /// Converts the options to a instance with a cast + /// or by creating a new instance with the specfied options. + /// + /// The options for the decoder. + /// The options for the . + internal static IGifDecoderOptions Create(IDecoderOptions options) + { + return options as IGifDecoderOptions ?? new GifDecoderOptions(options); + } + } +} diff --git a/src/ImageSharp.Formats.Gif/GifEncoder.cs b/src/ImageSharp.Formats.Gif/GifEncoder.cs index de7e03322..cc8516ed9 100644 --- a/src/ImageSharp.Formats.Gif/GifEncoder.cs +++ b/src/ImageSharp.Formats.Gif/GifEncoder.cs @@ -8,40 +8,31 @@ namespace ImageSharp.Formats using System; using System.IO; - using ImageSharp.Quantizers; - /// /// Image encoder for writing image data to a stream in gif format. /// public class GifEncoder : IImageEncoder { - /// - /// Gets or sets the quality of output for images. - /// - /// For gifs the value ranges from 1 to 256. - public int Quality { get; set; } + /// + public void Encode(Image image, Stream stream, IEncoderOptions options) + where TColor : struct, IPixel + { + IGifEncoderOptions gifOptions = GifEncoderOptions.Create(options); - /// - /// Gets or sets the transparency threshold. - /// - public byte Threshold { get; set; } = 128; + this.Encode(image, stream, gifOptions); + } /// - /// Gets or sets the quantizer for reducing the color count. + /// Encodes the image to the specified stream from the . /// - public IQuantizer Quantizer { get; set; } - - /// - public void Encode(Image image, Stream stream) + /// 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, IGifEncoderOptions options) where TColor : struct, IPixel { - GifEncoderCore encoder = new GifEncoderCore - { - Quality = this.Quality, - Quantizer = this.Quantizer, - Threshold = this.Threshold - }; - + GifEncoderCore encoder = new GifEncoderCore(options); encoder.Encode(image, stream); } } diff --git a/src/ImageSharp.Formats.Gif/GifEncoderCore.cs b/src/ImageSharp.Formats.Gif/GifEncoderCore.cs index 36cf614d9..38cbba850 100644 --- a/src/ImageSharp.Formats.Gif/GifEncoderCore.cs +++ b/src/ImageSharp.Formats.Gif/GifEncoderCore.cs @@ -24,20 +24,23 @@ namespace ImageSharp.Formats private readonly byte[] buffer = new byte[16]; /// - /// The number of bits requires to store the image palette. + /// The options for the encoder. /// - private int bitDepth; + private readonly IGifEncoderOptions options; /// - /// Gets or sets the quality of output for images. + /// The number of bits requires to store the image palette. /// - /// For gifs the value ranges from 1 to 256. - public int Quality { get; set; } + private int bitDepth; /// - /// Gets or sets the transparency threshold. + /// Initializes a new instance of the class. /// - public byte Threshold { get; set; } = 128; + /// The options for the encoder. + public GifEncoderCore(IGifEncoderOptions options) + { + this.options = options ?? new GifEncoderOptions(); + } /// /// Gets or sets the quantizer for reducing the color count. @@ -56,23 +59,20 @@ namespace ImageSharp.Formats Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); - if (this.Quantizer == null) - { - this.Quantizer = new OctreeQuantizer(); - } + this.Quantizer = this.options.Quantizer ?? new OctreeQuantizer(); // Do not use IDisposable pattern here as we want to preserve the stream. EndianBinaryWriter writer = new EndianBinaryWriter(Endianness.LittleEndian, stream); // Ensure that quality can be set but has a fallback. - int quality = this.Quality > 0 ? this.Quality : image.MetaData.Quality; - this.Quality = quality > 0 ? quality.Clamp(1, 256) : 256; + int quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality; + quality = quality > 0 ? quality.Clamp(1, 256) : 256; // Get the number of bits. - this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(this.Quality); + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quality); // Quantize the image returning a palette. - QuantizedImage quantized = ((IQuantizer)this.Quantizer).Quantize(image, this.Quality); + QuantizedImage quantized = ((IQuantizer)this.Quantizer).Quantize(image, quality); int index = this.GetTransparentIndex(quantized); @@ -84,6 +84,7 @@ namespace ImageSharp.Formats // Write the first frame. this.WriteGraphicalControlExtension(image, writer, index); + this.WriteComments(image, writer); this.WriteImageDescriptor(image, writer); this.WriteColorTable(quantized, writer); this.WriteImageData(quantized, writer); @@ -97,7 +98,7 @@ namespace ImageSharp.Formats for (int i = 0; i < image.Frames.Count; i++) { ImageFrame frame = image.Frames[i]; - QuantizedImage quantizedFrame = ((IQuantizer)this.Quantizer).Quantize(frame, this.Quality); + QuantizedImage quantizedFrame = ((IQuantizer)this.Quantizer).Quantize(frame, quality); this.WriteGraphicalControlExtension(frame, writer, this.GetTransparentIndex(quantizedFrame)); this.WriteImageDescriptor(frame, writer); @@ -106,7 +107,7 @@ namespace ImageSharp.Formats } } - // TODO: Write Comments extension etc + // TODO: Write extension etc writer.Write(GifConstants.EndIntroducer); } @@ -229,6 +230,39 @@ namespace ImageSharp.Formats } } + /// + /// Writes the image comments to the stream. + /// + /// The pixel format. + /// The to be encoded. + /// The stream to write to. + private void WriteComments(Image image, EndianBinaryWriter writer) + where TColor : struct, IPixel + { + if (this.options.IgnoreMetadata == true) + { + return; + } + + ImageProperty property = image.MetaData.Properties.FirstOrDefault(p => p.Name == GifConstants.Comments); + if (property == null || string.IsNullOrEmpty(property.Value)) + { + return; + } + + byte[] comments = this.options.TextEncoding.GetBytes(property.Value); + + int count = Math.Min(comments.Length, 255); + + this.buffer[0] = GifConstants.ExtensionIntroducer; + this.buffer[1] = GifConstants.CommentLabel; + this.buffer[2] = (byte)count; + + writer.Write(this.buffer, 0, 3); + writer.Write(comments, 0, count); + writer.Write(GifConstants.Terminator); + } + /// /// Writes the graphics control extension to the stream. /// diff --git a/src/ImageSharp.Formats.Gif/GifEncoderOptions.cs b/src/ImageSharp.Formats.Gif/GifEncoderOptions.cs new file mode 100644 index 000000000..5d7c6e40b --- /dev/null +++ b/src/ImageSharp.Formats.Gif/GifEncoderOptions.cs @@ -0,0 +1,65 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System.Text; + + using Quantizers; + + /// + /// Encapsulates the options for the . + /// + public sealed class GifEncoderOptions : EncoderOptions, IGifEncoderOptions + { + /// + /// Initializes a new instance of the class. + /// + public GifEncoderOptions() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The options for the encoder. + private GifEncoderOptions(IEncoderOptions options) + : base(options) + { + } + + /// + /// Gets or sets the encoding that should be used when writing comments. + /// + public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding; + + /// + /// Gets or sets the quality of output for images. + /// + /// For gifs the value ranges from 1 to 256. + public int Quality { get; set; } + + /// + /// Gets or sets the transparency threshold. + /// + public byte Threshold { get; set; } = 128; + + /// + /// Gets or sets the quantizer for reducing the color count. + /// + public IQuantizer Quantizer { 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 IGifEncoderOptions Create(IEncoderOptions options) + { + return options as IGifEncoderOptions ?? new GifEncoderOptions(options); + } + } +} diff --git a/src/ImageSharp.Formats.Gif/IGifDecoderOptions.cs b/src/ImageSharp.Formats.Gif/IGifDecoderOptions.cs new file mode 100644 index 000000000..729bf1d11 --- /dev/null +++ b/src/ImageSharp.Formats.Gif/IGifDecoderOptions.cs @@ -0,0 +1,20 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System.Text; + + /// + /// Encapsulates the options for the . + /// + public interface IGifDecoderOptions : IDecoderOptions + { + /// + /// Gets the encoding that should be used when reading comments. + /// + Encoding TextEncoding { get; } + } +} diff --git a/src/ImageSharp.Formats.Gif/IGifEncoderOptions.cs b/src/ImageSharp.Formats.Gif/IGifEncoderOptions.cs new file mode 100644 index 000000000..c1d6b7ad8 --- /dev/null +++ b/src/ImageSharp.Formats.Gif/IGifEncoderOptions.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System.Text; + + using Quantizers; + + /// + /// Encapsulates the options for the . + /// + public interface IGifEncoderOptions : IEncoderOptions + { + /// + /// Gets the encoding that should be used when writing comments. + /// + Encoding TextEncoding { get; } + + /// + /// Gets the quality of output for images. + /// + /// For gifs the value ranges from 1 to 256. + int Quality { get; } + + /// + /// Gets the transparency threshold. + /// + byte Threshold { get; } + + /// + /// Gets the quantizer for reducing the color count. + /// + IQuantizer Quantizer { get; } + } +} diff --git a/src/ImageSharp.Formats.Gif/ImageExtensions.cs b/src/ImageSharp.Formats.Gif/ImageExtensions.cs index e0ec2e8c8..1ba03ed35 100644 --- a/src/ImageSharp.Formats.Gif/ImageExtensions.cs +++ b/src/ImageSharp.Formats.Gif/ImageExtensions.cs @@ -21,13 +21,34 @@ namespace ImageSharp /// The pixel format. /// The image this method extends. /// The stream to save the image to. - /// The quality to save the image to representing the number of colors. Between 1 and 256. /// Thrown if the stream is null. /// /// The . /// - public static Image SaveAsGif(this Image source, Stream stream, int quality = 256) + public static Image SaveAsGif(this Image source, Stream stream) where TColor : struct, IPixel - => source.Save(stream, new GifEncoder { Quality = quality }); + { + return SaveAsGif(source, stream, null); + } + + /// + /// Saves the image to the given stream with the gif format. + /// + /// The pixel format. + /// The image this method extends. + /// The stream to save the image to. + /// The options for the encoder. + /// Thrown if the stream is null. + /// + /// The . + /// + public static Image SaveAsGif(this Image source, Stream stream, IGifEncoderOptions options) + where TColor : struct, IPixel + { + GifEncoder encoder = new GifEncoder(); + encoder.Encode(source, stream, options); + + return source; + } } } diff --git a/src/ImageSharp.Formats.Jpeg/IJpegEncoderOptions.cs b/src/ImageSharp.Formats.Jpeg/IJpegEncoderOptions.cs new file mode 100644 index 000000000..a54517965 --- /dev/null +++ b/src/ImageSharp.Formats.Jpeg/IJpegEncoderOptions.cs @@ -0,0 +1,26 @@ +// +// 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). + /// + /// 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..351275ebb 100644 --- a/src/ImageSharp.Formats.Jpeg/ImageExtensions.cs +++ b/src/ImageSharp.Formats.Jpeg/ImageExtensions.cs @@ -21,13 +21,34 @@ 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. /// 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) where TColor : struct, IPixel - => source.Save(stream, new JpegEncoder { Quality = quality }); + { + return SaveAsJpeg(source, stream, null); + } + + /// + /// Saves the image to the given stream with the jpeg format. + /// + /// The pixel format. + /// The image this method extends. + /// The stream to save the image to. + /// The options for the encoder. + /// Thrown if the stream is null. + /// + /// The . + /// + public static Image SaveAsJpeg(this Image source, Stream stream, IJpegEncoderOptions options) + where TColor : struct, IPixel + { + JpegEncoder encoder = new JpegEncoder(); + encoder.Encode(source, stream, options); + + return source; + } } } diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoder.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoder.cs index 435ae51cf..eeb371d1e 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoder.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoder.cs @@ -14,13 +14,13 @@ namespace ImageSharp.Formats public class JpegDecoder : IImageDecoder { /// - public void Decode(Image image, Stream stream) + public void Decode(Image image, Stream stream, IDecoderOptions options) where TColor : struct, IPixel { Guard.NotNull(image, "image"); Guard.NotNull(stream, "stream"); - using (JpegDecoderCore decoder = new JpegDecoderCore()) + using (JpegDecoderCore decoder = new JpegDecoderCore(options)) { decoder.Decode(image, stream, false); } diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs index 657787682..f1b85fa0b 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs @@ -37,6 +37,11 @@ namespace ImageSharp.Formats public InputProcessor InputProcessor; #pragma warning restore SA401 + /// + /// The decoder options. + /// + private readonly IDecoderOptions options; + /// /// The App14 marker color-space /// @@ -85,8 +90,10 @@ namespace ImageSharp.Formats /// /// Initializes a new instance of the class. /// - public JpegDecoderCore() + /// The decoder options. + public JpegDecoderCore(IDecoderOptions options) { + this.options = options ?? new DecoderOptions(); this.HuffmanTrees = HuffmanTree.CreateHuffmanTrees(); this.QuantizationTables = new Block8x8F[MaxTq + 1]; this.Temp = new byte[2 * Block8x8F.ScalarCount]; @@ -958,7 +965,7 @@ namespace ImageSharp.Formats private void ProcessApp1Marker(int remaining, Image image) where TColor : struct, IPixel { - if (remaining < 6) + if (remaining < 6 || this.options.IgnoreMetadata) { this.InputProcessor.Skip(remaining); return; diff --git a/src/ImageSharp.Formats.Jpeg/JpegEncoder.cs b/src/ImageSharp.Formats.Jpeg/JpegEncoder.cs index 07d9b24cd..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) + /// 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..73e483164 --- /dev/null +++ b/src/ImageSharp.Formats.Jpeg/JpegEncoderOptions.cs @@ -0,0 +1,56 @@ +// +// 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 90, 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) + { + return options as IJpegEncoderOptions ?? new JpegEncoderOptions(options); + } + } +} diff --git a/src/ImageSharp.Formats.Png/Filters/AverageFilter.cs b/src/ImageSharp.Formats.Png/Filters/AverageFilter.cs index d5f810708..b4ec49946 100644 --- a/src/ImageSharp.Formats.Png/Filters/AverageFilter.cs +++ b/src/ImageSharp.Formats.Png/Filters/AverageFilter.cs @@ -5,6 +5,8 @@ namespace ImageSharp.Formats { + using System.Runtime.CompilerServices; + /// /// The Average filter uses the average of the two neighboring pixels (left and above) to predict /// the value of a pixel. @@ -19,6 +21,7 @@ namespace ImageSharp.Formats /// The previous scanline. /// The number of bytes per scanline /// The bytes per pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Decode(byte[] scanline, byte[] previousScanline, int bytesPerScanline, int bytesPerPixel) { // Average(x) + floor((Raw(x-bpp)+Prior(x))/2) @@ -42,6 +45,7 @@ namespace ImageSharp.Formats /// The previous scanline. /// The filtered scanline result. /// The bytes per pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result, int bytesPerPixel) { // Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) @@ -67,6 +71,7 @@ namespace ImageSharp.Formats /// The left byte /// The above byte /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int Average(byte left, byte above) { return (left + above) >> 1; diff --git a/src/ImageSharp.Formats.Png/Filters/NoneFilter.cs b/src/ImageSharp.Formats.Png/Filters/NoneFilter.cs index e5787a944..5abd89296 100644 --- a/src/ImageSharp.Formats.Png/Filters/NoneFilter.cs +++ b/src/ImageSharp.Formats.Png/Filters/NoneFilter.cs @@ -6,6 +6,7 @@ namespace ImageSharp.Formats { using System; + using System.Runtime.CompilerServices; /// /// The None filter, the scanline is transmitted unmodified; it is only necessary to @@ -19,6 +20,7 @@ namespace ImageSharp.Formats /// /// The scanline to decode /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static byte[] Decode(byte[] scanline) { // No change required. @@ -30,6 +32,7 @@ namespace ImageSharp.Formats /// /// The scanline to encode /// The filtered scanline result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(byte[] scanline, byte[] result) { // Insert a byte before the data. diff --git a/src/ImageSharp.Formats.Png/Filters/PaethFilter.cs b/src/ImageSharp.Formats.Png/Filters/PaethFilter.cs index ff208f3d7..a43d4f080 100644 --- a/src/ImageSharp.Formats.Png/Filters/PaethFilter.cs +++ b/src/ImageSharp.Formats.Png/Filters/PaethFilter.cs @@ -6,6 +6,7 @@ namespace ImageSharp.Formats { using System; + using System.Runtime.CompilerServices; /// /// The Paeth filter computes a simple linear function of the three neighboring pixels (left, above, upper left), @@ -22,6 +23,7 @@ namespace ImageSharp.Formats /// The previous scanline. /// The number of bytes per scanline /// The bytes per pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Decode(byte[] scanline, byte[] previousScanline, int bytesPerScanline, int bytesPerPixel) { // Paeth(x) + PaethPredictor(Raw(x-bpp), Prior(x), Prior(x-bpp)) @@ -46,6 +48,7 @@ namespace ImageSharp.Formats /// The previous scanline. /// The filtered scanline result. /// The bytes per pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result, int bytesPerPixel) { // Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp)) @@ -76,6 +79,7 @@ namespace ImageSharp.Formats /// /// The . /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static byte PaethPredicator(byte left, byte above, byte upperLeft) { int p = left + above - upperLeft; diff --git a/src/ImageSharp.Formats.Png/Filters/SubFilter.cs b/src/ImageSharp.Formats.Png/Filters/SubFilter.cs index 65e86f4f1..4610ed0ae 100644 --- a/src/ImageSharp.Formats.Png/Filters/SubFilter.cs +++ b/src/ImageSharp.Formats.Png/Filters/SubFilter.cs @@ -5,6 +5,8 @@ namespace ImageSharp.Formats { + using System.Runtime.CompilerServices; + /// /// The Sub filter transmits the difference between each byte and the value of the corresponding byte /// of the prior pixel. @@ -18,6 +20,7 @@ namespace ImageSharp.Formats /// The scanline to decode /// The number of bytes per scanline /// The bytes per pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Decode(byte[] scanline, int bytesPerScanline, int bytesPerPixel) { // Sub(x) + Raw(x-bpp) @@ -37,6 +40,7 @@ namespace ImageSharp.Formats /// The scanline to encode /// The filtered scanline result. /// The bytes per pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(byte[] scanline, byte[] result, int bytesPerPixel) { // Sub(x) = Raw(x) - Raw(x-bpp) diff --git a/src/ImageSharp.Formats.Png/Filters/UpFilter.cs b/src/ImageSharp.Formats.Png/Filters/UpFilter.cs index 036862ddc..525f50d0f 100644 --- a/src/ImageSharp.Formats.Png/Filters/UpFilter.cs +++ b/src/ImageSharp.Formats.Png/Filters/UpFilter.cs @@ -5,6 +5,8 @@ namespace ImageSharp.Formats { + using System.Runtime.CompilerServices; + /// /// The Up filter is just like the Sub filter except that the pixel immediately above the current pixel, /// rather than just to its left, is used as the predictor. @@ -18,6 +20,7 @@ namespace ImageSharp.Formats /// The scanline to decode /// The previous scanline. /// The number of bytes per scanline + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Decode(byte[] scanline, byte[] previousScanline, int bytesPerScanline) { // Up(x) + Prior(x) @@ -39,6 +42,7 @@ namespace ImageSharp.Formats /// The scanline to encode /// The previous scanline. /// The filtered scanline result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result) { // Up(x) = Raw(x) - Prior(x) diff --git a/src/ImageSharp.Formats.Png/IPngDecoderOptions.cs b/src/ImageSharp.Formats.Png/IPngDecoderOptions.cs new file mode 100644 index 000000000..cc6d194bf --- /dev/null +++ b/src/ImageSharp.Formats.Png/IPngDecoderOptions.cs @@ -0,0 +1,20 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System.Text; + + /// + /// Encapsulates the options for the . + /// + public interface IPngDecoderOptions : IDecoderOptions + { + /// + /// Gets the encoding that should be used when reading text chunks. + /// + Encoding TextEncoding { get; } + } +} diff --git a/src/ImageSharp.Formats.Png/IPngEncoderOptions.cs b/src/ImageSharp.Formats.Png/IPngEncoderOptions.cs new file mode 100644 index 000000000..0008080d3 --- /dev/null +++ b/src/ImageSharp.Formats.Png/IPngEncoderOptions.cs @@ -0,0 +1,54 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using Quantizers; + + /// + /// Encapsulates the options for the . + /// + public interface IPngEncoderOptions : IEncoderOptions + { + /// + /// Gets the quality of output for images. + /// + int Quality { get; } + + /// + /// Gets the png color type + /// + PngColorType PngColorType { get; } + + /// + /// Gets the compression level 1-9. + /// + int CompressionLevel { get; } + + /// + /// Gets the gamma value, that will be written + /// the the stream, when the property + /// is set to true. + /// + /// The gamma value of the image. + float Gamma { get; } + + /// + /// Gets quantizer for reducing the color count. + /// + IQuantizer Quantizer { get; } + + /// + /// Gets the transparency threshold. + /// + byte Threshold { get; } + + /// + /// Gets a value indicating whether this instance should write + /// gamma information to the stream. + /// + bool WriteGamma { get; } + } +} diff --git a/src/ImageSharp.Formats.Png/ImageExtensions.cs b/src/ImageSharp.Formats.Png/ImageExtensions.cs index dcb1c988b..79e96175c 100644 --- a/src/ImageSharp.Formats.Png/ImageExtensions.cs +++ b/src/ImageSharp.Formats.Png/ImageExtensions.cs @@ -5,7 +5,6 @@ namespace ImageSharp { - using System; using System.IO; using Formats; @@ -21,15 +20,34 @@ namespace ImageSharp /// The pixel format. /// The image this method extends. /// The stream to save the image to. - /// The quality to save the image to representing the number of colors. - /// Anything equal to 256 and below will cause the encoder to save the image in an indexed format. - /// /// Thrown if the stream is null. /// /// The . /// - public static Image SaveAsPng(this Image source, Stream stream, int quality = int.MaxValue) + public static Image SaveAsPng(this Image source, Stream stream) where TColor : struct, IPixel - => source.Save(stream, new PngEncoder { Quality = quality }); + { + return SaveAsPng(source, stream, null); + } + + /// + /// Saves the image to the given stream with the png format. + /// + /// The pixel format. + /// The image this method extends. + /// The stream to save the image to. + /// The options for the encoder. + /// Thrown if the stream is null. + /// + /// The . + /// + public static Image SaveAsPng(this Image source, Stream stream, IPngEncoderOptions options) + where TColor : struct, IPixel + { + PngEncoder encoder = new PngEncoder(); + encoder.Encode(source, stream, options); + + return source; + } } } diff --git a/src/ImageSharp.Formats.Png/PngDecoder.cs b/src/ImageSharp.Formats.Png/PngDecoder.cs index 088bea591..d527e1654 100644 --- a/src/ImageSharp.Formats.Png/PngDecoder.cs +++ b/src/ImageSharp.Formats.Png/PngDecoder.cs @@ -30,16 +30,26 @@ namespace ImageSharp.Formats /// public class PngDecoder : IImageDecoder { + /// + public void Decode(Image image, Stream stream, IDecoderOptions options) + where TColor : struct, IPixel + { + IPngDecoderOptions pngOptions = PngDecoderOptions.Create(options); + + this.Decode(image, stream, pngOptions); + } + /// /// Decodes the image from the specified stream to the . /// /// The pixel format. /// The to decode to. /// The containing image data. - public void Decode(Image image, Stream stream) + /// The options for the decoder. + public void Decode(Image image, Stream stream, IPngDecoderOptions options) where TColor : struct, IPixel { - new PngDecoderCore().Decode(image, stream); + new PngDecoderCore(options).Decode(image, stream); } } } diff --git a/src/ImageSharp.Formats.Png/PngDecoderCore.cs b/src/ImageSharp.Formats.Png/PngDecoderCore.cs index 47b09d5ff..a7765342e 100644 --- a/src/ImageSharp.Formats.Png/PngDecoderCore.cs +++ b/src/ImageSharp.Formats.Png/PngDecoderCore.cs @@ -10,7 +10,7 @@ namespace ImageSharp.Formats using System.Collections.Generic; using System.IO; using System.Linq; - using System.Text; + using System.Runtime.CompilerServices; using static ComparableExtensions; @@ -64,6 +64,11 @@ namespace ImageSharp.Formats /// private readonly char[] chars = new char[4]; + /// + /// The decoder options. + /// + private readonly IPngDecoderOptions options; + /// /// Reusable crc for validating chunks. /// @@ -120,6 +125,15 @@ namespace ImageSharp.Formats ColorTypes.Add((int)PngColorType.RgbWithAlpha, new byte[] { 8 }); } + /// + /// Initializes a new instance of the class. + /// + /// The decoder options. + public PngDecoderCore(IPngDecoderOptions options) + { + this.options = options ?? new PngDecoderOptions(); + } + /// /// Gets or sets the png color type /// @@ -763,6 +777,11 @@ namespace ImageSharp.Formats private void ReadTextChunk(Image image, byte[] data, int length) where TColor : struct, IPixel { + if (this.options.IgnoreMetadata) + { + return; + } + int zeroIndex = 0; for (int i = 0; i < length; i++) @@ -774,8 +793,8 @@ namespace ImageSharp.Formats } } - string name = Encoding.Unicode.GetString(data, 0, zeroIndex); - string value = Encoding.Unicode.GetString(data, zeroIndex + 1, length - zeroIndex - 1); + string name = this.options.TextEncoding.GetString(data, 0, zeroIndex); + string value = this.options.TextEncoding.GetString(data, zeroIndex + 1, length - zeroIndex - 1); image.MetaData.Properties.Add(new ImageProperty(name, value)); } @@ -927,12 +946,7 @@ namespace ImageSharp.Formats private void ReadChunkLength(PngChunk chunk) { int numBytes = this.currentStream.Read(this.chunkLengthBuffer, 0, 4); - if (numBytes > 1 && numBytes <= 3) - { - throw new ImageFormatException("Image stream is not valid!"); - } - - if (numBytes <= 1) + if (numBytes < 4) { chunk.Length = -1; return; @@ -948,6 +962,7 @@ namespace ImageSharp.Formats /// /// Th current pass index /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] private int ComputeColumnsAdam7(int pass) { int width = this.header.Width; diff --git a/src/ImageSharp.Formats.Png/PngDecoderOptions.cs b/src/ImageSharp.Formats.Png/PngDecoderOptions.cs new file mode 100644 index 000000000..e8990ec45 --- /dev/null +++ b/src/ImageSharp.Formats.Png/PngDecoderOptions.cs @@ -0,0 +1,49 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System.Text; + + /// + /// Encapsulates the options for the . + /// + public sealed class PngDecoderOptions : DecoderOptions, IPngDecoderOptions + { + private static readonly Encoding DefaultEncoding = Encoding.GetEncoding("ASCII"); + + /// + /// Initializes a new instance of the class. + /// + public PngDecoderOptions() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The options for the decoder. + private PngDecoderOptions(IDecoderOptions options) + : base(options) + { + } + + /// + /// Gets or sets the encoding that should be used when reading text chunks. + /// + public Encoding TextEncoding { get; set; } = DefaultEncoding; + + /// + /// Converts the options to a instance with a cast + /// or by creating a new instance with the specfied options. + /// + /// The options for the decoder. + /// The options for the . + internal static IPngDecoderOptions Create(IDecoderOptions options) + { + return options as IPngDecoderOptions ?? new PngDecoderOptions(options); + } + } +} diff --git a/src/ImageSharp.Formats.Png/PngEncoder.cs b/src/ImageSharp.Formats.Png/PngEncoder.cs index ba790f4ae..e583f381f 100644 --- a/src/ImageSharp.Formats.Png/PngEncoder.cs +++ b/src/ImageSharp.Formats.Png/PngEncoder.cs @@ -5,72 +5,34 @@ namespace ImageSharp.Formats { - using System; using System.IO; - using ImageSharp.Quantizers; - /// /// Image encoder for writing image data to a stream in png format. /// public class PngEncoder : IImageEncoder { - /// - /// Gets or sets the quality of output for images. - /// - public int Quality { get; set; } - - /// - /// Gets or sets the png color type - /// - public PngColorType PngColorType { get; set; } = PngColorType.RgbWithAlpha; - - /// - /// Gets or sets the compression level 1-9. - /// Defaults to 6. - /// - public int CompressionLevel { get; set; } = 6; - - /// - /// Gets or sets the gamma value, that will be written - /// the the stream, when the property - /// is set to true. The default value is 2.2F. - /// - /// The gamma value of the image. - public float Gamma { get; set; } = 2.2F; - - /// - /// Gets or sets quantizer for reducing the color count. - /// - public IQuantizer Quantizer { get; set; } + /// + public void Encode(Image image, Stream stream, IEncoderOptions options) + where TColor : struct, IPixel + { + IPngEncoderOptions pngOptions = PngEncoderOptions.Create(options); - /// - /// Gets or sets the transparency threshold. - /// - public byte Threshold { get; set; } = 0; + this.Encode(image, stream, pngOptions); + } /// - /// Gets or sets a value indicating whether this instance should write - /// gamma information to the stream. The default value is false. + /// Encodes the image to the specified stream from the . /// - public bool WriteGamma { get; set; } - - /// - public void Encode(Image image, Stream stream) + /// 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, IPngEncoderOptions options) where TColor : struct, IPixel { - PngEncoderCore encoder = new PngEncoderCore - { - CompressionLevel = this.CompressionLevel, - Gamma = this.Gamma, - Quality = this.Quality, - PngColorType = this.PngColorType, - Quantizer = this.Quantizer, - WriteGamma = this.WriteGamma, - Threshold = this.Threshold - }; - - encoder.Encode(image, stream); + PngEncoderCore encode = new PngEncoderCore(options); + encode.Encode(image, stream); } } } diff --git a/src/ImageSharp.Formats.Png/PngEncoderCore.cs b/src/ImageSharp.Formats.Png/PngEncoderCore.cs index 2324853cb..7950d260c 100644 --- a/src/ImageSharp.Formats.Png/PngEncoderCore.cs +++ b/src/ImageSharp.Formats.Png/PngEncoderCore.cs @@ -40,6 +40,11 @@ namespace ImageSharp.Formats /// private readonly Crc32 crc = new Crc32(); + /// + /// The options for the encoder. + /// + private readonly IPngEncoderOptions options; + /// /// Contains the raw pixel data from an indexed image. /// @@ -86,44 +91,28 @@ namespace ImageSharp.Formats private byte[] paeth; /// - /// Gets or sets the quality of output for images. - /// - public int Quality { get; set; } - - /// - /// Gets or sets the png color type + /// The quality of output for images. /// - public PngColorType PngColorType { get; set; } + private int quality; /// - /// Gets or sets the compression level 1-9. - /// Defaults to 6. + /// The png color type. /// - public int CompressionLevel { get; set; } = 6; + private PngColorType pngColorType; /// - /// Gets or sets a value indicating whether this instance should write - /// gamma information to the stream. The default value is false. + /// The quantizer for reducing the color count. /// - public bool WriteGamma { get; set; } + private IQuantizer quantizer; /// - /// Gets or sets the gamma value, that will be written - /// the the stream, when the property - /// is set to true. The default value is 2.2F. + /// Initializes a new instance of the class. /// - /// The gamma value of the image. - public float Gamma { get; set; } = 2.2F; - - /// - /// Gets or sets the quantizer for reducing the color count. - /// - public IQuantizer Quantizer { get; set; } - - /// - /// Gets or sets the transparency threshold. - /// - public byte Threshold { get; set; } + /// The options for the encoder. + public PngEncoderCore(IPngEncoderOptions options) + { + this.options = options ?? new PngEncoderOptions(); + } /// /// Encodes the image to the specified stream from the . @@ -153,23 +142,26 @@ namespace ImageSharp.Formats stream.Write(this.chunkDataBuffer, 0, 8); // Ensure that quality can be set but has a fallback. - int quality = this.Quality > 0 ? this.Quality : image.MetaData.Quality; - this.Quality = quality > 0 ? quality.Clamp(1, int.MaxValue) : int.MaxValue; + this.quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality; + this.quality = this.quality > 0 ? this.quality.Clamp(1, int.MaxValue) : int.MaxValue; + + this.pngColorType = this.options.PngColorType; + this.quantizer = this.options.Quantizer; // Set correct color type if the color count is 256 or less. - if (this.Quality <= 256) + if (this.quality <= 256) { - this.PngColorType = PngColorType.Palette; + this.pngColorType = PngColorType.Palette; } - if (this.PngColorType == PngColorType.Palette && this.Quality > 256) + if (this.pngColorType == PngColorType.Palette && this.quality > 256) { - this.Quality = 256; + this.quality = 256; } // Set correct bit depth. - this.bitDepth = this.Quality <= 256 - ? (byte)ImageMaths.GetBitsNeededForColorDepth(this.Quality).Clamp(1, 8) + this.bitDepth = this.quality <= 256 + ? (byte)ImageMaths.GetBitsNeededForColorDepth(this.quality).Clamp(1, 8) : (byte)8; // Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk @@ -188,7 +180,7 @@ namespace ImageSharp.Formats { Width = image.Width, Height = image.Height, - ColorType = (byte)this.PngColorType, + ColorType = (byte)this.pngColorType, BitDepth = this.bitDepth, FilterMethod = 0, // None CompressionMethod = 0, @@ -198,7 +190,7 @@ namespace ImageSharp.Formats this.WriteHeaderChunk(stream, header); // Collect the indexed pixel data - if (this.PngColorType == PngColorType.Palette) + if (this.pngColorType == PngColorType.Palette) { this.CollectIndexedBytes(image, stream, header); } @@ -334,7 +326,7 @@ namespace ImageSharp.Formats private byte[] EncodePixelRow(PixelAccessor pixels, int row, byte[] previousScanline, byte[] rawScanline, byte[] result) where TColor : struct, IPixel { - switch (this.PngColorType) + switch (this.pngColorType) { case PngColorType.Palette: Buffer.BlockCopy(this.palettePixelData, row * rawScanline.Length, rawScanline, 0, rawScanline.Length); @@ -362,7 +354,7 @@ namespace ImageSharp.Formats private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, byte[] result) { // Palette images don't compress well with adaptive filtering. - if (this.PngColorType == PngColorType.Palette || this.bitDepth < 8) + if (this.pngColorType == PngColorType.Palette || this.bitDepth < 8) { NoneFilter.Encode(rawScanline, result); return result; @@ -436,7 +428,7 @@ namespace ImageSharp.Formats /// The private int CalculateBytesPerPixel() { - switch (this.PngColorType) + switch (this.pngColorType) { case PngColorType.Grayscale: return 1; @@ -488,18 +480,18 @@ namespace ImageSharp.Formats private QuantizedImage WritePaletteChunk(Stream stream, PngHeader header, ImageBase image) where TColor : struct, IPixel { - if (this.Quality > 256) + if (this.quality > 256) { return null; } - if (this.Quantizer == null) + if (this.quantizer == null) { - this.Quantizer = new OctreeQuantizer(); + this.quantizer = new OctreeQuantizer(); } // Quantize the image returning a palette. This boxing is icky. - QuantizedImage quantized = ((IQuantizer)this.Quantizer).Quantize(image, this.Quality); + QuantizedImage quantized = ((IQuantizer)this.quantizer).Quantize(image, this.quality); // Grab the palette and write it to the stream. TColor[] palette = quantized.Palette; @@ -524,7 +516,7 @@ namespace ImageSharp.Formats colorTable[offset + 1] = bytes[1]; colorTable[offset + 2] = bytes[2]; - if (alpha <= this.Threshold) + if (alpha <= this.options.Threshold) { transparentPixels.Add((byte)offset); } @@ -578,9 +570,9 @@ namespace ImageSharp.Formats /// The containing image data. private void WriteGammaChunk(Stream stream) { - if (this.WriteGamma) + if (this.options.WriteGamma) { - int gammaValue = (int)(this.Gamma * 100000F); + int gammaValue = (int)(this.options.Gamma * 100000F); byte[] size = BitConverter.GetBytes(gammaValue); @@ -608,7 +600,7 @@ namespace ImageSharp.Formats int resultLength = bytesPerScanline + 1; byte[] result = new byte[resultLength]; - if (this.PngColorType != PngColorType.Palette) + if (this.pngColorType != PngColorType.Palette) { this.sub = new byte[resultLength]; this.up = new byte[resultLength]; @@ -622,7 +614,7 @@ namespace ImageSharp.Formats try { memoryStream = new MemoryStream(); - using (ZlibDeflateStream deflateStream = new ZlibDeflateStream(memoryStream, this.CompressionLevel)) + using (ZlibDeflateStream deflateStream = new ZlibDeflateStream(memoryStream, this.options.CompressionLevel)) { for (int y = 0; y < this.height; y++) { diff --git a/src/ImageSharp.Formats.Png/PngEncoderOptions.cs b/src/ImageSharp.Formats.Png/PngEncoderOptions.cs new file mode 100644 index 000000000..2891f1974 --- /dev/null +++ b/src/ImageSharp.Formats.Png/PngEncoderOptions.cs @@ -0,0 +1,82 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using Quantizers; + + /// + /// Encapsulates the options for the . + /// + public sealed class PngEncoderOptions : EncoderOptions, IPngEncoderOptions + { + /// + /// Initializes a new instance of the class. + /// + public PngEncoderOptions() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The options for the encoder. + private PngEncoderOptions(IEncoderOptions options) + : base(options) + { + } + + /// + /// Gets or sets the quality of output for images. + /// + public int Quality { get; set; } + + /// + /// Gets or sets the png color type + /// + public PngColorType PngColorType { get; set; } = PngColorType.RgbWithAlpha; + + /// + /// Gets or sets the compression level 1-9. + /// Defaults to 6. + /// + public int CompressionLevel { get; set; } = 6; + + /// + /// Gets or sets the gamma value, that will be written + /// the the stream, when the property + /// is set to true. The default value is 2.2F. + /// + /// The gamma value of the image. + public float Gamma { get; set; } = 2.2F; + + /// + /// Gets or sets quantizer for reducing the color count. + /// + public IQuantizer Quantizer { get; set; } + + /// + /// Gets or sets the transparency threshold. + /// + public byte Threshold { get; set; } = 0; + + /// + /// Gets or sets a value indicating whether this instance should write + /// gamma information to the stream. The default value is false. + /// + public bool WriteGamma { 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 IPngEncoderOptions Create(IEncoderOptions options) + { + return options as IPngEncoderOptions ?? new PngEncoderOptions(options); + } + } +} diff --git a/src/ImageSharp/Formats/DecoderOptions.cs b/src/ImageSharp/Formats/DecoderOptions.cs new file mode 100644 index 000000000..5257b07b3 --- /dev/null +++ b/src/ImageSharp/Formats/DecoderOptions.cs @@ -0,0 +1,37 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Encapsulates the shared decoder options. + /// + public class DecoderOptions : IDecoderOptions + { + /// + /// Initializes a new instance of the class. + /// + public DecoderOptions() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The decoder options + protected DecoderOptions(IDecoderOptions options) + { + if (options != null) + { + this.IgnoreMetadata = options.IgnoreMetadata; + } + } + + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; set; } = false; + } +} diff --git a/src/ImageSharp/Formats/EncoderOptions.cs b/src/ImageSharp/Formats/EncoderOptions.cs new file mode 100644 index 000000000..27a7e9781 --- /dev/null +++ b/src/ImageSharp/Formats/EncoderOptions.cs @@ -0,0 +1,37 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Encapsulates the shared encoder options. + /// + public class EncoderOptions : IEncoderOptions + { + /// + /// Initializes a new instance of the class. + /// + public EncoderOptions() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The encoder options + protected EncoderOptions(IEncoderOptions options) + { + if (options != null) + { + this.IgnoreMetadata = options.IgnoreMetadata; + } + } + + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being encoded. + /// + public bool IgnoreMetadata { get; set; } = false; + } +} diff --git a/src/ImageSharp/Formats/IDecoderOptions.cs b/src/ImageSharp/Formats/IDecoderOptions.cs new file mode 100644 index 000000000..cdfd90d5e --- /dev/null +++ b/src/ImageSharp/Formats/IDecoderOptions.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Encapsulates the shared decoder options. + /// + public interface IDecoderOptions + { + /// + /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + bool IgnoreMetadata { get; } + } +} diff --git a/src/ImageSharp/Formats/IEncoderOptions.cs b/src/ImageSharp/Formats/IEncoderOptions.cs new file mode 100644 index 000000000..0fd3d1c43 --- /dev/null +++ b/src/ImageSharp/Formats/IEncoderOptions.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Encapsulates the shared encoder options. + /// + public interface IEncoderOptions + { + /// + /// Gets a value indicating whether the metadata should be ignored when the image is being encoded. + /// + bool IgnoreMetadata { get; } + } +} diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index 28bda7837..df98870dd 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -19,7 +19,8 @@ namespace ImageSharp.Formats /// The pixel format. /// The to decode to. /// The containing image data. - void Decode(Image image, Stream stream) + /// The options for the decoder. + void Decode(Image image, Stream stream, IDecoderOptions options) where TColor : struct, IPixel; } } diff --git a/src/ImageSharp/Formats/IImageEncoder.cs b/src/ImageSharp/Formats/IImageEncoder.cs index 0ba56477a..918f0d273 100644 --- a/src/ImageSharp/Formats/IImageEncoder.cs +++ b/src/ImageSharp/Formats/IImageEncoder.cs @@ -19,7 +19,8 @@ namespace ImageSharp.Formats /// The pixel format. /// The to encode from. /// The to encode the image data to. - void Encode(Image image, Stream stream) + /// The options for the encoder. + void Encode(Image image, Stream stream, IEncoderOptions options) where TColor : struct, IPixel; } } diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index bdb6b49a9..af31eff79 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -8,6 +8,8 @@ namespace ImageSharp using System.Diagnostics; using System.IO; + using Formats; + /// /// Represents an image. Each pixel is a made up four 8-bit components red, green, blue, and alpha /// packed into a single unsigned integer value. @@ -29,6 +31,33 @@ namespace ImageSharp { } + /// + /// Initializes a new instance of the class. + /// + /// + /// The stream containing image information. + /// + /// Thrown if the is null. + public Image(Stream stream) + : base(stream, null, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The stream containing image information. + /// + /// + /// The options for the decoder. + /// + /// Thrown if the is null. + public Image(Stream stream, IDecoderOptions options) + : base(stream, options, null) + { + } + /// /// Initializes a new instance of the class. /// @@ -39,12 +68,57 @@ namespace ImageSharp /// The configuration providing initialization code which allows extending the library. /// /// Thrown if the is null. - public Image(Stream stream, Configuration configuration = null) - : base(stream, configuration) + public Image(Stream stream, Configuration configuration) + : base(stream, null, configuration) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The stream containing image information. + /// + /// + /// The options for the decoder. + /// + /// + /// The configuration providing initialization code which allows extending the library. + /// + /// Thrown if the is null. + public Image(Stream stream, IDecoderOptions options, Configuration configuration) + : base(stream, options, configuration) { } #if !NETSTANDARD1_1 + /// + /// Initializes a new instance of the class. + /// + /// + /// A file path to read image information. + /// + /// Thrown if the is null. + public Image(string filePath) + : base(filePath, null, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A file path to read image information. + /// + /// + /// The options for the decoder. + /// + /// Thrown if the is null. + public Image(string filePath, IDecoderOptions options) + : base(filePath, options, null) + { + } + /// /// Initializes a new instance of the class. /// @@ -55,8 +129,26 @@ namespace ImageSharp /// The configuration providing initialization code which allows extending the library. /// /// Thrown if the is null. - public Image(string filePath, Configuration configuration = null) - : base(filePath, configuration) + public Image(string filePath, Configuration configuration) + : base(filePath, null, configuration) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A file path to read image information. + /// + /// + /// The options for the decoder. + /// + /// + /// The configuration providing initialization code which allows extending the library. + /// + /// Thrown if the is null. + public Image(string filePath, IDecoderOptions options, Configuration configuration) + : base(filePath, options, configuration) { } #endif @@ -67,12 +159,57 @@ namespace ImageSharp /// /// The byte array containing image information. /// + /// Thrown if the is null. + public Image(byte[] bytes) + : base(bytes, null, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The byte array containing image information. + /// + /// + /// The options for the decoder. + /// + /// Thrown if the is null. + public Image(byte[] bytes, IDecoderOptions options) + : base(bytes, options, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The byte array containing image information. + /// + /// + /// The configuration providing initialization code which allows extending the library. + /// + /// Thrown if the is null. + public Image(byte[] bytes, Configuration configuration) + : base(bytes, null, configuration) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The byte array containing image information. + /// + /// + /// The options for the decoder. + /// /// /// The configuration providing initialization code which allows extending the library. /// /// Thrown if the is null. - public Image(byte[] bytes, Configuration configuration = null) - : base(bytes, configuration) + public Image(byte[] bytes, IDecoderOptions options, Configuration configuration) + : base(bytes, options, configuration) { } diff --git a/src/ImageSharp/Image/Image{TColor}.cs b/src/ImageSharp/Image/Image{TColor}.cs index bbd839d16..27dee5434 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -46,6 +46,33 @@ namespace ImageSharp this.CurrentImageFormat = this.Configuration.ImageFormats.First(); } + /// + /// Initializes a new instance of the class. + /// + /// + /// The stream containing image information. + /// + /// Thrown if the is null. + public Image(Stream stream) + : this(stream, null, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The stream containing image information. + /// + /// + /// The options for the decoder. + /// + /// Thrown if the is null. + public Image(Stream stream, IDecoderOptions options) + : this(stream, options, null) + { + } + /// /// Initializes a new instance of the class. /// @@ -56,14 +83,59 @@ namespace ImageSharp /// The configuration providing initialization code which allows extending the library. /// /// Thrown if the is null. - public Image(Stream stream, Configuration configuration = null) + public Image(Stream stream, Configuration configuration) + : this(stream, null, configuration) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The stream containing image information. + /// + /// + /// The options for the decoder. + /// + /// + /// The configuration providing initialization code which allows extending the library. + /// + /// Thrown if the is null. + public Image(Stream stream, IDecoderOptions options, Configuration configuration) : base(configuration) { Guard.NotNull(stream, nameof(stream)); - this.Load(stream); + this.Load(stream, options); } #if !NETSTANDARD1_1 + /// + /// Initializes a new instance of the class. + /// + /// + /// The file containing image information. + /// + /// Thrown if the is null. + public Image(string filePath) + : this(filePath, null, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The file containing image information. + /// + /// + /// The options for the decoder. + /// + /// Thrown if the is null. + public Image(string filePath, IDecoderOptions options) + : this(filePath, options, null) + { + } + /// /// Initializes a new instance of the class. /// @@ -74,17 +146,62 @@ namespace ImageSharp /// The configuration providing initialization code which allows extending the library. /// /// Thrown if the is null. - public Image(string filePath, Configuration configuration = null) + public Image(string filePath, Configuration configuration) + : this(filePath, null, configuration) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The file containing image information. + /// + /// + /// The options for the decoder. + /// + /// + /// The configuration providing initialization code which allows extending the library. + /// + /// Thrown if the is null. + public Image(string filePath, IDecoderOptions options, Configuration configuration) : base(configuration) { Guard.NotNull(filePath, nameof(filePath)); using (var fs = File.OpenRead(filePath)) { - this.Load(fs); + this.Load(fs, options); } } #endif + /// + /// Initializes a new instance of the class. + /// + /// + /// The byte array containing image information. + /// + /// Thrown if the is null. + public Image(byte[] bytes) + : this(bytes, null, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The byte array containing image information. + /// + /// + /// The options for the decoder. + /// + /// Thrown if the is null. + public Image(byte[] bytes, IDecoderOptions options) + : this(bytes, options, null) + { + } + /// /// Initializes a new instance of the class. /// @@ -95,14 +212,32 @@ namespace ImageSharp /// The configuration providing initialization code which allows extending the library. /// /// Thrown if the is null. - public Image(byte[] bytes, Configuration configuration = null) + public Image(byte[] bytes, Configuration configuration) + : this(bytes, null, configuration) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The byte array containing image information. + /// + /// + /// The options for the decoder. + /// + /// + /// The configuration providing initialization code which allows extending the library. + /// + /// Thrown if the is null. + public Image(byte[] bytes, IDecoderOptions options, Configuration configuration) : base(configuration) { Guard.NotNull(bytes, nameof(bytes)); using (MemoryStream stream = new MemoryStream(bytes, false)) { - this.Load(stream); + this.Load(stream, options); } } @@ -200,9 +335,21 @@ namespace ImageSharp /// Thrown if the stream is null. /// The public Image Save(Stream stream) + { + return this.Save(stream, (IEncoderOptions)null); + } + + /// + /// Saves the image to the given stream using the currently loaded image format. + /// + /// The stream to save the image to. + /// The options for the encoder. + /// Thrown if the stream is null. + /// The + public Image Save(Stream stream, IEncoderOptions options) { Guard.NotNull(stream, nameof(stream)); - this.CurrentImageFormat.Encoder.Encode(this, stream); + this.CurrentImageFormat.Encoder.Encode(this, stream, options); return this; } @@ -211,13 +358,24 @@ namespace ImageSharp /// /// The stream to save the image to. /// The format to save the image as. - /// Thrown if the stream or format is null. /// The public Image Save(Stream stream, IImageFormat format) + { + return this.Save(stream, format, null); + } + + /// + /// Saves the image to the given stream using the given image format. + /// + /// The stream to save the image to. + /// The format to save the image as. + /// The options for the encoder. + /// The + public Image Save(Stream stream, IImageFormat format, IEncoderOptions options) { Guard.NotNull(stream, nameof(stream)); Guard.NotNull(format, nameof(format)); - format.Encoder.Encode(this, stream); + format.Encoder.Encode(this, stream, options); return this; } @@ -231,10 +389,25 @@ namespace ImageSharp /// The . /// public Image Save(Stream stream, IImageEncoder encoder) + { + return this.Save(stream, encoder, null); + } + + /// + /// Saves the image to the given stream using the given image encoder. + /// + /// The stream to save the image to. + /// The encoder to save the image with. + /// The options for the encoder. + /// Thrown if the stream or encoder is null. + /// + /// The . + /// + public Image Save(Stream stream, IImageEncoder encoder, IEncoderOptions options) { Guard.NotNull(stream, nameof(stream)); Guard.NotNull(encoder, nameof(encoder)); - encoder.Encode(this, stream); + encoder.Encode(this, stream, options); // Reset to the start of the stream. if (stream.CanSeek) @@ -253,6 +426,18 @@ namespace ImageSharp /// Thrown if the stream is null. /// The public Image Save(string filePath) + { + return this.Save(filePath, (IEncoderOptions)null); + } + + /// + /// Saves the image to the given stream using the currently loaded image format. + /// + /// The file path to save the image to. + /// The options for the encoder. + /// Thrown if the stream is null. + /// The + public Image Save(string filePath, IEncoderOptions options) { string ext = Path.GetExtension(filePath).Trim('.'); IImageFormat format = this.Configuration.ImageFormats.SingleOrDefault(f => f.SupportedExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase)); @@ -272,6 +457,19 @@ namespace ImageSharp /// Thrown if the format is null. /// The public Image Save(string filePath, IImageFormat format) + { + return this.Save(filePath, format, null); + } + + /// + /// Saves the image to the given stream using the currently loaded image format. + /// + /// The file path to save the image to. + /// The format to save the image as. + /// The options for the encoder. + /// Thrown if the format is null. + /// The + public Image Save(string filePath, IImageFormat format, IEncoderOptions options) { Guard.NotNull(format, nameof(format)); using (FileStream fs = File.Create(filePath)) @@ -288,6 +486,19 @@ namespace ImageSharp /// Thrown if the encoder is null. /// The public Image Save(string filePath, IImageEncoder encoder) + { + return this.Save(filePath, encoder, null); + } + + /// + /// Saves the image to the given stream using the currently loaded image format. + /// + /// The file path to save the image to. + /// The encoder to save the image with. + /// The options for the encoder. + /// Thrown if the encoder is null. + /// The + public Image Save(string filePath, IImageEncoder encoder, IEncoderOptions options) { Guard.NotNull(encoder, nameof(encoder)); using (FileStream fs = File.Create(filePath)) @@ -397,10 +608,11 @@ namespace ImageSharp /// Loads the image from the given stream. /// /// The stream containing image information. + /// The options for the decoder. /// /// Thrown if the stream is not readable nor seekable. /// - private void Load(Stream stream) + private void Load(Stream stream, IDecoderOptions options) { if (!this.Configuration.ImageFormats.Any()) { @@ -414,7 +626,7 @@ namespace ImageSharp if (stream.CanSeek) { - if (this.Decode(stream)) + if (this.Decode(stream, options)) { return; } @@ -427,7 +639,7 @@ namespace ImageSharp stream.CopyTo(ms); ms.Position = 0; - if (this.Decode(ms)) + if (this.Decode(ms, options)) { return; } @@ -449,10 +661,11 @@ namespace ImageSharp /// Decodes the image stream to the current image. /// /// The stream. + /// The options for the decoder. /// /// The . /// - private bool Decode(Stream stream) + private bool Decode(Stream stream, IDecoderOptions options) { int maxHeaderSize = this.Configuration.MaxHeaderSize; if (maxHeaderSize <= 0) @@ -479,7 +692,7 @@ namespace ImageSharp return false; } - format.Decoder.Decode(this, stream); + format.Decoder.Decode(this, stream, options); this.CurrentImageFormat = format; return true; } diff --git a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs index a41afd333..1d3ead81f 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs @@ -32,7 +32,6 @@ namespace ImageSharp.Tests.Drawing using (FileStream output = File.OpenWrite($"{path}/Simple.png")) { image - .BackgroundColor(Color.Blue) .FillPolygon(Color.HotPink, simplePath, new GraphicsOptions(true)) .Save(output); } @@ -45,7 +44,7 @@ namespace ImageSharp.Tests.Drawing Assert.Equal(Color.HotPink, sourcePixels[50, 50]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.NotEqual(Color.HotPink, sourcePixels[2, 2]); } } } diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs index b147e97e8..765ff3a42 100644 --- a/tests/ImageSharp.Tests/FileTestBase.cs +++ b/tests/ImageSharp.Tests/FileTestBase.cs @@ -30,7 +30,8 @@ namespace ImageSharp.Tests TestFile.Create(TestImages.Bmp.Car), // TestFile.Create(TestImages.Bmp.Neg_height), // Perf: Enable for local testing only TestFile.Create(TestImages.Png.Splash), - // TestFile.Create(TestImages.Png.ChunkLength), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.ChunkLength1), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.ChunkLength2), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Powerpoint), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Blur), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Indexed), // Perf: Enable for local testing only diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs similarity index 80% rename from tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs rename to tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index c1275335d..497abb7d5 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -7,16 +7,14 @@ using ImageSharp.Formats; namespace ImageSharp.Tests { - using System.IO; - using Xunit; - public class BitmapTests : FileTestBase + public class BmpEncoderTests : FileTestBase { public static readonly TheoryData BitsPerPixel = new TheoryData { - BmpBitsPerPixel.Pixel24 , + BmpBitsPerPixel.Pixel24, BmpBitsPerPixel.Pixel32 }; @@ -31,7 +29,7 @@ namespace ImageSharp.Tests string filename = file.GetFileNameWithoutExtension(bitsPerPixel); using (Image image = file.CreateImage()) { - image.Save($"{path}/{filename}.bmp", new BmpEncoder { BitsPerPixel = bitsPerPixel }); + image.Save($"{path}/{filename}.bmp", new BmpEncoderOptions { BitsPerPixel = bitsPerPixel }); } } } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs new file mode 100644 index 000000000..b874a1585 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -0,0 +1,66 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.Text; + using Xunit; + + using ImageSharp.Formats; + + public class GifDecoderTests + { + [Fact] + public void Decode_IgnoreMetadataIsFalse_CommentsAreRead() + { + var options = new DecoderOptions() + { + IgnoreMetadata = false + }; + + TestFile testFile = TestFile.Create(TestImages.Gif.Rings); + + using (Image image = testFile.CreateImage(options)) + { + Assert.Equal(1, image.MetaData.Properties.Count); + Assert.Equal("Comments", image.MetaData.Properties[0].Name); + Assert.Equal("ImageSharp", image.MetaData.Properties[0].Value); + } + } + + [Fact] + public void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored() + { + var options = new DecoderOptions() + { + IgnoreMetadata = true + }; + + TestFile testFile = TestFile.Create(TestImages.Gif.Rings); + + using (Image image = testFile.CreateImage(options)) + { + Assert.Equal(0, image.MetaData.Properties.Count); + } + } + + [Fact] + public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding() + { + var options = new GifDecoderOptions() + { + TextEncoding = Encoding.Unicode + }; + + TestFile testFile = TestFile.Create(TestImages.Gif.Rings); + + using (Image image = testFile.CreateImage(options)) + { + Assert.Equal(1, image.MetaData.Properties.Count); + Assert.Equal("浉条卥慨灲", image.MetaData.Properties[0].Value); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs new file mode 100644 index 000000000..da1323627 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -0,0 +1,90 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + using Xunit; + + using ImageSharp.Formats; + + public class GifEncoderTests + { + [Fact] + public void Encode_IgnoreMetadataIsFalse_CommentsAreWritten() + { + var options = new EncoderOptions() + { + IgnoreMetadata = false + }; + + TestFile testFile = TestFile.Create(TestImages.Gif.Rings); + + using (Image input = testFile.CreateImage()) + { + using (MemoryStream memStream = new MemoryStream()) + { + input.Save(memStream, new GifFormat(), options); + + memStream.Position = 0; + using (Image output = new Image(memStream)) + { + Assert.Equal(1, output.MetaData.Properties.Count); + Assert.Equal("Comments", output.MetaData.Properties[0].Name); + Assert.Equal("ImageSharp", output.MetaData.Properties[0].Value); + } + } + } + } + + [Fact] + public void Encode_IgnoreMetadataIsTrue_CommentsAreNotWritten() + { + var options = new GifEncoderOptions() + { + IgnoreMetadata = true + }; + + TestFile testFile = TestFile.Create(TestImages.Gif.Rings); + + using (Image input = testFile.CreateImage()) + { + using (MemoryStream memStream = new MemoryStream()) + { + input.SaveAsGif(memStream, options); + + memStream.Position = 0; + using (Image output = new Image(memStream)) + { + Assert.Equal(0, output.MetaData.Properties.Count); + } + } + } + } + + [Fact] + public void Encode_CommentIsToLong_CommentIsTrimmed() + { + using (Image input = new Image(1, 1)) + { + string comments = new string('c', 256); + input.MetaData.Properties.Add(new ImageProperty("Comments", comments)); + + using (MemoryStream memStream = new MemoryStream()) + { + input.Save(memStream, new GifFormat()); + + memStream.Position = 0; + using (Image output = new Image(memStream)) + { + Assert.Equal(1, output.MetaData.Properties.Count); + Assert.Equal("Comments", output.MetaData.Properties[0].Name); + Assert.Equal(255, output.MetaData.Properties[0].Value.Length); + } + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 71ce4e165..7cb9a7cf2 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); } } @@ -90,7 +91,7 @@ namespace ImageSharp.Tests ms.Seek(0, SeekOrigin.Begin); Image mirror = provider.Factory.CreateImage(1, 1); - using (JpegDecoderCore decoder = new JpegDecoderCore()) + using (JpegDecoderCore decoder = new JpegDecoderCore(null)) { decoder.Decode(mirror, ms, true); @@ -120,5 +121,37 @@ namespace ImageSharp.Tests Assert.Equal(72, image.MetaData.VerticalResolution); } } + + [Fact] + public void Decode_IgnoreMetadataIsFalse_ExifProfileIsRead() + { + var options = new DecoderOptions() + { + IgnoreMetadata = false + }; + + TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan); + + using (Image image = testFile.CreateImage(options)) + { + Assert.NotNull(image.MetaData.ExifProfile); + } + } + + [Fact] + public void Decode_IgnoreMetadataIsTrue_ExifProfileIgnored() + { + var options = new DecoderOptions() + { + IgnoreMetadata = true + }; + + TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan); + + using (Image image = testFile.CreateImage(options)) + { + Assert.Null(image.MetaData.ExifProfile); + } + } } } \ No newline at end of file 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/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs new file mode 100644 index 000000000..921530806 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -0,0 +1,66 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.Text; + using Xunit; + + using ImageSharp.Formats; + + public class PngDecoderTests + { + [Fact] + public void Decode_IgnoreMetadataIsFalse_TextChunckIsRead() + { + var options = new PngDecoderOptions() + { + IgnoreMetadata = false + }; + + TestFile testFile = TestFile.Create(TestImages.Png.Blur); + + using (Image image = testFile.CreateImage(options)) + { + Assert.Equal(1, image.MetaData.Properties.Count); + Assert.Equal("Software", image.MetaData.Properties[0].Name); + Assert.Equal("paint.net 4.0.6", image.MetaData.Properties[0].Value); + } + } + + [Fact] + public void Decode_IgnoreMetadataIsTrue_TextChunksAreIgnored() + { + var options = new PngDecoderOptions() + { + IgnoreMetadata = true + }; + + TestFile testFile = TestFile.Create(TestImages.Png.Blur); + + using (Image image = testFile.CreateImage(options)) + { + Assert.Equal(0, image.MetaData.Properties.Count); + } + } + + [Fact] + public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding() + { + var options = new PngDecoderOptions() + { + TextEncoding = Encoding.Unicode + }; + + TestFile testFile = TestFile.Create(TestImages.Png.Blur); + + using (Image image = testFile.CreateImage(options)) + { + Assert.Equal(1, image.MetaData.Properties.Count); + Assert.Equal("潓瑦慷敲", image.MetaData.Properties[0].Name); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Png/PngTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs similarity index 91% rename from tests/ImageSharp.Tests/Formats/Png/PngTests.cs rename to tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 5ba00eb4d..49be75139 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -10,11 +10,9 @@ namespace ImageSharp.Tests using System.IO; using System.Threading.Tasks; - using Formats; - using Xunit; - public class PngTests : FileTestBase + public class PngEncoderTests : FileTestBase { [Fact] public void ImageCanSaveIndexedPng() diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index aea4330c6..0ed724fad 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -59,7 +59,7 @@ namespace ImageSharp.Tests ArgumentNullException ex = Assert.Throws( () => { - new Image(null); + new Image((string) null); }); } diff --git a/tests/ImageSharp.Tests/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs index 891a45cec..0c9cc5f47 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -130,6 +130,18 @@ namespace ImageSharp.Tests return new Image(this.image); } + /// + /// Creates a new image. + /// + /// The options for the decoder. + /// + /// The . + /// + public Image CreateImage(IDecoderOptions options) + { + return new Image(this.Bytes, options); + } + /// /// Gets the correct path to the formats directory. /// diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 4924cc1de..f0a0e8dd8 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -25,18 +25,19 @@ namespace ImageSharp.Tests public const string SplashInterlaced = "Png/splash-interlaced.png"; public const string Interlaced = "Png/interlaced.png"; - // filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html + // Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html public const string Filter0 = "Png/filter0.png"; public const string Filter1 = "Png/filter1.png"; public const string Filter2 = "Png/filter2.png"; public const string Filter3 = "Png/filter3.png"; public const string Filter4 = "Png/filter4.png"; - // filter changing per scanline + // Filter changing per scanline public const string FilterVar = "Png/filterVar.png"; - // Chunk length of 1 by end marker - public const string ChunkLength = "Png/chunklength1.png"; + // Odd chunk lengths + public const string ChunkLength1 = "Png/chunklength1.png"; + public const string ChunkLength2 = "Png/chunklength2.png"; } public static class Jpeg diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Gif/rings.gif b/tests/ImageSharp.Tests/TestImages/Formats/Gif/rings.gif index 53f2890fa..76f093a20 100644 Binary files a/tests/ImageSharp.Tests/TestImages/Formats/Gif/rings.gif and b/tests/ImageSharp.Tests/TestImages/Formats/Gif/rings.gif differ diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Png/chunklength2.png b/tests/ImageSharp.Tests/TestImages/Formats/Png/chunklength2.png new file mode 100644 index 000000000..0d14abdc4 Binary files /dev/null and b/tests/ImageSharp.Tests/TestImages/Formats/Png/chunklength2.png differ 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); } }