Browse Source

Merge remote-tracking branch 'origin/master' into bulk-pixels

pull/125/head
Anton Firszov 9 years ago
parent
commit
8cc1820d53
  1. 24
      .vscode/tasks.json
  2. 7
      README.md
  3. BIN
      build/icons/imagesharp-logo-128.png
  4. BIN
      build/icons/imagesharp-logo-256.png
  5. BIN
      build/icons/imagesharp-logo-32.png
  6. BIN
      build/icons/imagesharp-logo-512.png
  7. BIN
      build/icons/imagesharp-logo-64.png
  8. BIN
      build/icons/imagesharp-logo-heading.png
  9. BIN
      build/icons/imagesharp-logo.png
  10. 60
      build/icons/imagesharp-logo.svg
  11. 16
      features.md
  12. 2
      src/ImageSharp.Drawing.Paths/project.json
  13. 1
      src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs
  14. 1
      src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs
  15. 2
      src/ImageSharp.Formats.Bmp/BmpDecoder.cs
  16. 24
      src/ImageSharp.Formats.Bmp/BmpEncoder.cs
  17. 24
      src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs
  18. 45
      src/ImageSharp.Formats.Bmp/BmpEncoderOptions.cs
  19. 18
      src/ImageSharp.Formats.Bmp/IBmpEncoderOptions.cs
  20. 14
      src/ImageSharp.Formats.Gif/GifConstants.cs
  21. 19
      src/ImageSharp.Formats.Gif/GifDecoder.cs
  22. 38
      src/ImageSharp.Formats.Gif/GifDecoderCore.cs
  23. 47
      src/ImageSharp.Formats.Gif/GifDecoderOptions.cs
  24. 37
      src/ImageSharp.Formats.Gif/GifEncoder.cs
  25. 68
      src/ImageSharp.Formats.Gif/GifEncoderCore.cs
  26. 65
      src/ImageSharp.Formats.Gif/GifEncoderOptions.cs
  27. 20
      src/ImageSharp.Formats.Gif/IGifDecoderOptions.cs
  28. 38
      src/ImageSharp.Formats.Gif/IGifEncoderOptions.cs
  29. 27
      src/ImageSharp.Formats.Gif/ImageExtensions.cs
  30. 26
      src/ImageSharp.Formats.Jpeg/IJpegEncoderOptions.cs
  31. 27
      src/ImageSharp.Formats.Jpeg/ImageExtensions.cs
  32. 4
      src/ImageSharp.Formats.Jpeg/JpegDecoder.cs
  33. 11
      src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs
  34. 75
      src/ImageSharp.Formats.Jpeg/JpegEncoder.cs
  35. 40
      src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs
  36. 56
      src/ImageSharp.Formats.Jpeg/JpegEncoderOptions.cs
  37. 5
      src/ImageSharp.Formats.Png/Filters/AverageFilter.cs
  38. 3
      src/ImageSharp.Formats.Png/Filters/NoneFilter.cs
  39. 4
      src/ImageSharp.Formats.Png/Filters/PaethFilter.cs
  40. 4
      src/ImageSharp.Formats.Png/Filters/SubFilter.cs
  41. 4
      src/ImageSharp.Formats.Png/Filters/UpFilter.cs
  42. 20
      src/ImageSharp.Formats.Png/IPngDecoderOptions.cs
  43. 54
      src/ImageSharp.Formats.Png/IPngEncoderOptions.cs
  44. 30
      src/ImageSharp.Formats.Png/ImageExtensions.cs
  45. 14
      src/ImageSharp.Formats.Png/PngDecoder.cs
  46. 33
      src/ImageSharp.Formats.Png/PngDecoderCore.cs
  47. 49
      src/ImageSharp.Formats.Png/PngDecoderOptions.cs
  48. 68
      src/ImageSharp.Formats.Png/PngEncoder.cs
  49. 92
      src/ImageSharp.Formats.Png/PngEncoderCore.cs
  50. 82
      src/ImageSharp.Formats.Png/PngEncoderOptions.cs
  51. 37
      src/ImageSharp/Formats/DecoderOptions.cs
  52. 37
      src/ImageSharp/Formats/EncoderOptions.cs
  53. 18
      src/ImageSharp/Formats/IDecoderOptions.cs
  54. 18
      src/ImageSharp/Formats/IEncoderOptions.cs
  55. 3
      src/ImageSharp/Formats/IImageDecoder.cs
  56. 3
      src/ImageSharp/Formats/IImageEncoder.cs
  57. 149
      src/ImageSharp/Image.cs
  58. 243
      src/ImageSharp/Image/Image{TColor}.cs
  59. 3
      tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs
  60. 3
      tests/ImageSharp.Tests/FileTestBase.cs
  61. 10
      tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
  62. 66
      tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
  63. 90
      tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
  64. 39
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  65. 67
      tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
  66. 5
      tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs
  67. 66
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
  68. 6
      tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
  69. 2
      tests/ImageSharp.Tests/Image/ImageTests.cs
  70. 12
      tests/ImageSharp.Tests/TestFile.cs
  71. 9
      tests/ImageSharp.Tests/TestImages.cs
  72. BIN
      tests/ImageSharp.Tests/TestImages/Formats/Gif/rings.gif
  73. BIN
      tests/ImageSharp.Tests/TestImages/Formats/Png/chunklength2.png
  74. 5
      tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs

24
.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"
}
]
}

7
README.md

@ -1,5 +1,5 @@
# <img src="build/icons/imagesharp-logo-64.png" width="52" height="52"/> ImageSharp
# <img src="build/icons/imagesharp-logo-heading.png" alt="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

BIN
build/icons/imagesharp-logo-128.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

BIN
build/icons/imagesharp-logo-256.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 14 KiB

BIN
build/icons/imagesharp-logo-32.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 979 B

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
build/icons/imagesharp-logo-512.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 30 KiB

BIN
build/icons/imagesharp-logo-64.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
build/icons/imagesharp-logo-heading.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
build/icons/imagesharp-logo.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 58 KiB

60
build/icons/imagesharp-logo.svg

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 22 KiB

16
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\<TPacked\>).
- [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.

2
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"

1
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);

1
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);

2
src/ImageSharp.Formats.Bmp/BmpDecoder.cs

@ -26,7 +26,7 @@ namespace ImageSharp.Formats
public class BmpDecoder : IImageDecoder
{
/// <inheritdoc/>
public void Decode<TColor>(Image<TColor> image, Stream stream)
public void Decode<TColor>(Image<TColor> image, Stream stream, IDecoderOptions options)
where TColor : struct, IPixel<TColor>
{
Guard.NotNull(image, "image");

24
src/ImageSharp.Formats.Bmp/BmpEncoder.cs

@ -14,17 +14,27 @@ namespace ImageSharp.Formats
/// <remarks>The encoder can currently only write 24-bit rgb images to streams.</remarks>
public class BmpEncoder : IImageEncoder
{
/// <inheritdoc/>
public void Encode<TColor>(Image<TColor> image, Stream stream, IEncoderOptions options)
where TColor : struct, IPixel<TColor>
{
IBmpEncoderOptions bmpOptions = BmpEncoderOptions.Create(options);
this.Encode(image, stream, bmpOptions);
}
/// <summary>
/// Gets or sets the number of bits per pixel.
/// Encodes the image to the specified stream from the <see cref="Image{TColor}"/>.
/// </summary>
public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24;
/// <inheritdoc/>
public void Encode<TColor>(Image<TColor> image, Stream stream)
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TColor}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="options">The options for the encoder.</param>
public void Encode<TColor>(Image<TColor> image, Stream stream, IBmpEncoderOptions options)
where TColor : struct, IPixel<TColor>
{
BmpEncoderCore encoder = new BmpEncoderCore();
encoder.Encode(image, stream, this.BitsPerPixel);
BmpEncoderCore encoder = new BmpEncoderCore(options);
encoder.Encode(image, stream);
}
}
}

24
src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs

@ -16,34 +16,40 @@ namespace ImageSharp.Formats
internal sealed class BmpEncoderCore
{
/// <summary>
/// The number of bits per pixel.
/// The options for the encoder.
/// </summary>
private BmpBitsPerPixel bmpBitsPerPixel;
private readonly IBmpEncoderOptions options;
/// <summary>
/// The amount to pad each row by.
/// </summary>
private int padding;
/// <summary>
/// Initializes a new instance of the <see cref="BmpEncoderCore"/> class.
/// </summary>
/// <param name="options">The options for the encoder.</param>
public BmpEncoderCore(IBmpEncoderOptions options)
{
this.options = options ?? new BmpEncoderOptions();
}
/// <summary>
/// Encodes the image to the specified stream from the <see cref="ImageBase{TColor}"/>.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageBase{TColor}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="bitsPerPixel">The <see cref="BmpBitsPerPixel"/></param>
public void Encode<TColor>(ImageBase<TColor> image, Stream stream, BmpBitsPerPixel bitsPerPixel)
public void Encode<TColor>(ImageBase<TColor> image, Stream stream)
where TColor : struct, IPixel<TColor>
{
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<TColor> pixels = image.Lock())
{
switch (this.bmpBitsPerPixel)
switch (this.options.BitsPerPixel)
{
case BmpBitsPerPixel.Pixel32:
this.Write32Bit(writer, pixels);

45
src/ImageSharp.Formats.Bmp/BmpEncoderOptions.cs

@ -0,0 +1,45 @@
// <copyright file="BmpEncoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
/// <summary>
/// Encapsulates the options for the <see cref="BmpEncoder"/>.
/// </summary>
public sealed class BmpEncoderOptions : EncoderOptions, IBmpEncoderOptions
{
/// <summary>
/// Initializes a new instance of the <see cref="BmpEncoderOptions"/> class.
/// </summary>
public BmpEncoderOptions()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BmpEncoderOptions"/> class.
/// </summary>
/// <param name="options">The options for the encoder.</param>
private BmpEncoderOptions(IEncoderOptions options)
: base(options)
{
}
/// <summary>
/// Gets or sets the number of bits per pixel.
/// </summary>
public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24;
/// <summary>
/// Converts the options to a <see cref="IBmpEncoderOptions"/> instance with a cast
/// or by creating a new instance with the specfied options.
/// </summary>
/// <param name="options">The options for the encoder.</param>
/// <returns>The options for the <see cref="BmpEncoder"/>.</returns>
internal static IBmpEncoderOptions Create(IEncoderOptions options)
{
return options as IBmpEncoderOptions ?? new BmpEncoderOptions(options);
}
}
}

18
src/ImageSharp.Formats.Bmp/IBmpEncoderOptions.cs

@ -0,0 +1,18 @@
// <copyright file="IBmpEncoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
/// <summary>
/// Encapsulates the options for the <see cref="BmpEncoder"/>.
/// </summary>
public interface IBmpEncoderOptions : IEncoderOptions
{
/// <summary>
/// Gets the number of bits per pixel.
/// </summary>
BmpBitsPerPixel BitsPerPixel { get; }
}
}

14
src/ImageSharp.Formats.Gif/GifConstants.cs

@ -5,10 +5,12 @@
namespace ImageSharp.Formats
{
using System.Text;
/// <summary>
/// Constants that define specific points within a gif.
/// </summary>
internal sealed class GifConstants
internal static class GifConstants
{
/// <summary>
/// The file type.
@ -50,6 +52,11 @@ namespace ImageSharp.Formats
/// </summary>
public const byte CommentLabel = 0xFE;
/// <summary>
/// The name of the property inside the image properties for the comments.
/// </summary>
public const string Comments = "Comments";
/// <summary>
/// The maximum comment length.
/// </summary>
@ -79,5 +86,10 @@ namespace ImageSharp.Formats
/// The end introducer trailer <value>;</value>.
/// </summary>
public const byte EndIntroducer = 0x3B;
/// <summary>
/// Gets the default encoding to use when reading comments.
/// </summary>
public static Encoding DefaultEncoding { get; } = Encoding.GetEncoding("ASCII");
}
}

19
src/ImageSharp.Formats.Gif/GifDecoder.cs

@ -14,10 +14,25 @@ namespace ImageSharp.Formats
public class GifDecoder : IImageDecoder
{
/// <inheritdoc/>
public void Decode<TColor>(Image<TColor> image, Stream stream)
public void Decode<TColor>(Image<TColor> image, Stream stream, IDecoderOptions options)
where TColor : struct, IPixel<TColor>
{
new GifDecoderCore<TColor>().Decode(image, stream);
IGifDecoderOptions gifOptions = GifDecoderOptions.Create(options);
this.Decode(image, stream, gifOptions);
}
/// <summary>
/// Decodes the image from the specified stream to the <see cref="ImageBase{TColor}"/>.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageBase{TColor}"/> to decode to.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="options">The options for the decoder.</param>
public void Decode<TColor>(Image<TColor> image, Stream stream, IGifDecoderOptions options)
where TColor : struct, IPixel<TColor>
{
new GifDecoderCore<TColor>(options).Decode(image, stream);
}
}
}

38
src/ImageSharp.Formats.Gif/GifDecoderCore.cs

@ -8,6 +8,7 @@ namespace ImageSharp.Formats
using System;
using System.Buffers;
using System.IO;
using System.Text;
/// <summary>
/// Performs the gif decoding operation.
@ -21,6 +22,11 @@ namespace ImageSharp.Formats
/// </summary>
private readonly byte[] buffer = new byte[16];
/// <summary>
/// The decoder options.
/// </summary>
private readonly IGifDecoderOptions options;
/// <summary>
/// The image to decode the information to.
/// </summary>
@ -61,6 +67,15 @@ namespace ImageSharp.Formats
/// </summary>
private GifGraphicsControlExtension graphicsControlExtension;
/// <summary>
/// Initializes a new instance of the <see cref="GifDecoderCore{TColor}"/> class.
/// </summary>
/// <param name="options">The decoder options.</param>
public GifDecoderCore(IGifDecoderOptions options)
{
this.options = options ?? new GifDecoderOptions();
}
/// <summary>
/// Decodes the stream to the image.
/// </summary>
@ -225,25 +240,32 @@ namespace ImageSharp.Formats
/// </summary>
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<byte>.Shared.Rent(flag);
byte[] commentsBuffer = ArrayPool<byte>.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<byte>.Shared.Return(flagBuffer);
ArrayPool<byte>.Shared.Return(commentsBuffer);
}
}
}

47
src/ImageSharp.Formats.Gif/GifDecoderOptions.cs

@ -0,0 +1,47 @@
// <copyright file="GifDecoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System.Text;
/// <summary>
/// Encapsulates the options for the <see cref="GifDecoder"/>.
/// </summary>
public sealed class GifDecoderOptions : DecoderOptions, IGifDecoderOptions
{
/// <summary>
/// Initializes a new instance of the <see cref="GifDecoderOptions"/> class.
/// </summary>
public GifDecoderOptions()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GifDecoderOptions"/> class.
/// </summary>
/// <param name="options">The options for the decoder.</param>
private GifDecoderOptions(IDecoderOptions options)
: base(options)
{
}
/// <summary>
/// Gets or sets the encoding that should be used when reading comments.
/// </summary>
public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding;
/// <summary>
/// Converts the options to a <see cref="IGifDecoderOptions"/> instance with a cast
/// or by creating a new instance with the specfied options.
/// </summary>
/// <param name="options">The options for the decoder.</param>
/// <returns>The options for the <see cref="GifDecoder"/>.</returns>
internal static IGifDecoderOptions Create(IDecoderOptions options)
{
return options as IGifDecoderOptions ?? new GifDecoderOptions(options);
}
}
}

37
src/ImageSharp.Formats.Gif/GifEncoder.cs

@ -8,40 +8,31 @@ namespace ImageSharp.Formats
using System;
using System.IO;
using ImageSharp.Quantizers;
/// <summary>
/// Image encoder for writing image data to a stream in gif format.
/// </summary>
public class GifEncoder : IImageEncoder
{
/// <summary>
/// Gets or sets the quality of output for images.
/// </summary>
/// <remarks>For gifs the value ranges from 1 to 256.</remarks>
public int Quality { get; set; }
/// <inheritdoc/>
public void Encode<TColor>(Image<TColor> image, Stream stream, IEncoderOptions options)
where TColor : struct, IPixel<TColor>
{
IGifEncoderOptions gifOptions = GifEncoderOptions.Create(options);
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
public byte Threshold { get; set; } = 128;
this.Encode(image, stream, gifOptions);
}
/// <summary>
/// Gets or sets the quantizer for reducing the color count.
/// Encodes the image to the specified stream from the <see cref="Image{TColor}"/>.
/// </summary>
public IQuantizer Quantizer { get; set; }
/// <inheritdoc/>
public void Encode<TColor>(Image<TColor> image, Stream stream)
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TColor}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="options">The options for the encoder.</param>
public void Encode<TColor>(Image<TColor> image, Stream stream, IGifEncoderOptions options)
where TColor : struct, IPixel<TColor>
{
GifEncoderCore encoder = new GifEncoderCore
{
Quality = this.Quality,
Quantizer = this.Quantizer,
Threshold = this.Threshold
};
GifEncoderCore encoder = new GifEncoderCore(options);
encoder.Encode(image, stream);
}
}

68
src/ImageSharp.Formats.Gif/GifEncoderCore.cs

@ -24,20 +24,23 @@ namespace ImageSharp.Formats
private readonly byte[] buffer = new byte[16];
/// <summary>
/// The number of bits requires to store the image palette.
/// The options for the encoder.
/// </summary>
private int bitDepth;
private readonly IGifEncoderOptions options;
/// <summary>
/// Gets or sets the quality of output for images.
/// The number of bits requires to store the image palette.
/// </summary>
/// <remarks>For gifs the value ranges from 1 to 256.</remarks>
public int Quality { get; set; }
private int bitDepth;
/// <summary>
/// Gets or sets the transparency threshold.
/// Initializes a new instance of the <see cref="GifEncoderCore"/> class.
/// </summary>
public byte Threshold { get; set; } = 128;
/// <param name="options">The options for the encoder.</param>
public GifEncoderCore(IGifEncoderOptions options)
{
this.options = options ?? new GifEncoderOptions();
}
/// <summary>
/// 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<TColor>();
}
this.Quantizer = this.options.Quantizer ?? new OctreeQuantizer<TColor>();
// 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<TColor> quantized = ((IQuantizer<TColor>)this.Quantizer).Quantize(image, this.Quality);
QuantizedImage<TColor> quantized = ((IQuantizer<TColor>)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<TColor> frame = image.Frames[i];
QuantizedImage<TColor> quantizedFrame = ((IQuantizer<TColor>)this.Quantizer).Quantize(frame, this.Quality);
QuantizedImage<TColor> quantizedFrame = ((IQuantizer<TColor>)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
}
}
/// <summary>
/// Writes the image comments to the stream.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageBase{TColor}"/> to be encoded.</param>
/// <param name="writer">The stream to write to.</param>
private void WriteComments<TColor>(Image<TColor> image, EndianBinaryWriter writer)
where TColor : struct, IPixel<TColor>
{
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);
}
/// <summary>
/// Writes the graphics control extension to the stream.
/// </summary>

65
src/ImageSharp.Formats.Gif/GifEncoderOptions.cs

@ -0,0 +1,65 @@
// <copyright file="GifEncoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System.Text;
using Quantizers;
/// <summary>
/// Encapsulates the options for the <see cref="GifEncoder"/>.
/// </summary>
public sealed class GifEncoderOptions : EncoderOptions, IGifEncoderOptions
{
/// <summary>
/// Initializes a new instance of the <see cref="GifEncoderOptions"/> class.
/// </summary>
public GifEncoderOptions()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GifEncoderOptions"/> class.
/// </summary>
/// <param name="options">The options for the encoder.</param>
private GifEncoderOptions(IEncoderOptions options)
: base(options)
{
}
/// <summary>
/// Gets or sets the encoding that should be used when writing comments.
/// </summary>
public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding;
/// <summary>
/// Gets or sets the quality of output for images.
/// </summary>
/// <remarks>For gifs the value ranges from 1 to 256.</remarks>
public int Quality { get; set; }
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
public byte Threshold { get; set; } = 128;
/// <summary>
/// Gets or sets the quantizer for reducing the color count.
/// </summary>
public IQuantizer Quantizer { get; set; }
/// <summary>
/// Converts the options to a <see cref="IGifEncoderOptions"/> instance with a
/// cast or by creating a new instance with the specfied options.
/// </summary>
/// <param name="options">The options for the encoder.</param>
/// <returns>The options for the <see cref="GifEncoder"/>.</returns>
internal static IGifEncoderOptions Create(IEncoderOptions options)
{
return options as IGifEncoderOptions ?? new GifEncoderOptions(options);
}
}
}

20
src/ImageSharp.Formats.Gif/IGifDecoderOptions.cs

@ -0,0 +1,20 @@
// <copyright file="IGifDecoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System.Text;
/// <summary>
/// Encapsulates the options for the <see cref="GifDecoder"/>.
/// </summary>
public interface IGifDecoderOptions : IDecoderOptions
{
/// <summary>
/// Gets the encoding that should be used when reading comments.
/// </summary>
Encoding TextEncoding { get; }
}
}

38
src/ImageSharp.Formats.Gif/IGifEncoderOptions.cs

@ -0,0 +1,38 @@
// <copyright file="IGifEncoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System.Text;
using Quantizers;
/// <summary>
/// Encapsulates the options for the <see cref="GifEncoder"/>.
/// </summary>
public interface IGifEncoderOptions : IEncoderOptions
{
/// <summary>
/// Gets the encoding that should be used when writing comments.
/// </summary>
Encoding TextEncoding { get; }
/// <summary>
/// Gets the quality of output for images.
/// </summary>
/// <remarks>For gifs the value ranges from 1 to 256.</remarks>
int Quality { get; }
/// <summary>
/// Gets the transparency threshold.
/// </summary>
byte Threshold { get; }
/// <summary>
/// Gets the quantizer for reducing the color count.
/// </summary>
IQuantizer Quantizer { get; }
}
}

27
src/ImageSharp.Formats.Gif/ImageExtensions.cs

@ -21,13 +21,34 @@ namespace ImageSharp
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="quality">The quality to save the image to representing the number of colors. Between 1 and 256.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>
/// The <see cref="Image{TColor}"/>.
/// </returns>
public static Image<TColor> SaveAsGif<TColor>(this Image<TColor> source, Stream stream, int quality = 256)
public static Image<TColor> SaveAsGif<TColor>(this Image<TColor> source, Stream stream)
where TColor : struct, IPixel<TColor>
=> source.Save(stream, new GifEncoder { Quality = quality });
{
return SaveAsGif(source, stream, null);
}
/// <summary>
/// Saves the image to the given stream with the gif format.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="options">The options for the encoder.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>
/// The <see cref="Image{TColor}"/>.
/// </returns>
public static Image<TColor> SaveAsGif<TColor>(this Image<TColor> source, Stream stream, IGifEncoderOptions options)
where TColor : struct, IPixel<TColor>
{
GifEncoder encoder = new GifEncoder();
encoder.Encode(source, stream, options);
return source;
}
}
}

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

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

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

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

4
src/ImageSharp.Formats.Jpeg/JpegDecoder.cs

@ -14,13 +14,13 @@ namespace ImageSharp.Formats
public class JpegDecoder : IImageDecoder
{
/// <inheritdoc/>
public void Decode<TColor>(Image<TColor> image, Stream stream)
public void Decode<TColor>(Image<TColor> image, Stream stream, IDecoderOptions options)
where TColor : struct, IPixel<TColor>
{
Guard.NotNull(image, "image");
Guard.NotNull(stream, "stream");
using (JpegDecoderCore decoder = new JpegDecoderCore())
using (JpegDecoderCore decoder = new JpegDecoderCore(options))
{
decoder.Decode(image, stream, false);
}

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

@ -37,6 +37,11 @@ namespace ImageSharp.Formats
public InputProcessor InputProcessor;
#pragma warning restore SA401
/// <summary>
/// The decoder options.
/// </summary>
private readonly IDecoderOptions options;
/// <summary>
/// The App14 marker color-space
/// </summary>
@ -85,8 +90,10 @@ namespace ImageSharp.Formats
/// <summary>
/// Initializes a new instance of the <see cref="JpegDecoderCore" /> class.
/// </summary>
public JpegDecoderCore()
/// <param name="options">The decoder options.</param>
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<TColor>(int remaining, Image<TColor> image)
where TColor : struct, IPixel<TColor>
{
if (remaining < 6)
if (remaining < 6 || this.options.IgnoreMetadata)
{
this.InputProcessor.Skip(remaining);
return;

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

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

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

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

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

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

5
src/ImageSharp.Formats.Png/Filters/AverageFilter.cs

@ -5,6 +5,8 @@
namespace ImageSharp.Formats
{
using System.Runtime.CompilerServices;
/// <summary>
/// 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
/// <param name="previousScanline">The previous scanline.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
[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
/// <param name="previousScanline">The previous scanline.</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
[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
/// <param name="left">The left byte</param>
/// <param name="above">The above byte</param>
/// <returns>The <see cref="int"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Average(byte left, byte above)
{
return (left + above) >> 1;

3
src/ImageSharp.Formats.Png/Filters/NoneFilter.cs

@ -6,6 +6,7 @@
namespace ImageSharp.Formats
{
using System;
using System.Runtime.CompilerServices;
/// <summary>
/// The None filter, the scanline is transmitted unmodified; it is only necessary to
@ -19,6 +20,7 @@ namespace ImageSharp.Formats
/// </summary>
/// <param name="scanline">The scanline to decode</param>
/// <returns>The <see cref="T:byte[]"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte[] Decode(byte[] scanline)
{
// No change required.
@ -30,6 +32,7 @@ namespace ImageSharp.Formats
/// </summary>
/// <param name="scanline">The scanline to encode</param>
/// <param name="result">The filtered scanline result.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(byte[] scanline, byte[] result)
{
// Insert a byte before the data.

4
src/ImageSharp.Formats.Png/Filters/PaethFilter.cs

@ -6,6 +6,7 @@
namespace ImageSharp.Formats
{
using System;
using System.Runtime.CompilerServices;
/// <summary>
/// The Paeth filter computes a simple linear function of the three neighboring pixels (left, above, upper left),
@ -22,6 +23,7 @@ namespace ImageSharp.Formats
/// <param name="previousScanline">The previous scanline.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
[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
/// <param name="previousScanline">The previous scanline.</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
[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
/// <returns>
/// The <see cref="byte"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte PaethPredicator(byte left, byte above, byte upperLeft)
{
int p = left + above - upperLeft;

4
src/ImageSharp.Formats.Png/Filters/SubFilter.cs

@ -5,6 +5,8 @@
namespace ImageSharp.Formats
{
using System.Runtime.CompilerServices;
/// <summary>
/// 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
/// <param name="scanline">The scanline to decode</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
[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
/// <param name="scanline">The scanline to encode</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(byte[] scanline, byte[] result, int bytesPerPixel)
{
// Sub(x) = Raw(x) - Raw(x-bpp)

4
src/ImageSharp.Formats.Png/Filters/UpFilter.cs

@ -5,6 +5,8 @@
namespace ImageSharp.Formats
{
using System.Runtime.CompilerServices;
/// <summary>
/// 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
/// <param name="scanline">The scanline to decode</param>
/// <param name="previousScanline">The previous scanline.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode(byte[] scanline, byte[] previousScanline, int bytesPerScanline)
{
// Up(x) + Prior(x)
@ -39,6 +42,7 @@ namespace ImageSharp.Formats
/// <param name="scanline">The scanline to encode</param>
/// <param name="previousScanline">The previous scanline.</param>
/// <param name="result">The filtered scanline result.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result)
{
// Up(x) = Raw(x) - Prior(x)

20
src/ImageSharp.Formats.Png/IPngDecoderOptions.cs

@ -0,0 +1,20 @@
// <copyright file="IPngDecoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System.Text;
/// <summary>
/// Encapsulates the options for the <see cref="PngDecoder"/>.
/// </summary>
public interface IPngDecoderOptions : IDecoderOptions
{
/// <summary>
/// Gets the encoding that should be used when reading text chunks.
/// </summary>
Encoding TextEncoding { get; }
}
}

54
src/ImageSharp.Formats.Png/IPngEncoderOptions.cs

@ -0,0 +1,54 @@
// <copyright file="IPngEncoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using Quantizers;
/// <summary>
/// Encapsulates the options for the <see cref="PngEncoder"/>.
/// </summary>
public interface IPngEncoderOptions : IEncoderOptions
{
/// <summary>
/// Gets the quality of output for images.
/// </summary>
int Quality { get; }
/// <summary>
/// Gets the png color type
/// </summary>
PngColorType PngColorType { get; }
/// <summary>
/// Gets the compression level 1-9.
/// </summary>
int CompressionLevel { get; }
/// <summary>
/// Gets the gamma value, that will be written
/// the the stream, when the <see cref="WriteGamma"/> property
/// is set to true.
/// </summary>
/// <value>The gamma value of the image.</value>
float Gamma { get; }
/// <summary>
/// Gets quantizer for reducing the color count.
/// </summary>
IQuantizer Quantizer { get; }
/// <summary>
/// Gets the transparency threshold.
/// </summary>
byte Threshold { get; }
/// <summary>
/// Gets a value indicating whether this instance should write
/// gamma information to the stream.
/// </summary>
bool WriteGamma { get; }
}
}

30
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
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="quality">The quality to save the image to representing the number of colors.
/// Anything equal to 256 and below will cause the encoder to save the image in an indexed format.
/// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>
/// The <see cref="Image{TColor}"/>.
/// </returns>
public static Image<TColor> SaveAsPng<TColor>(this Image<TColor> source, Stream stream, int quality = int.MaxValue)
public static Image<TColor> SaveAsPng<TColor>(this Image<TColor> source, Stream stream)
where TColor : struct, IPixel<TColor>
=> source.Save(stream, new PngEncoder { Quality = quality });
{
return SaveAsPng(source, stream, null);
}
/// <summary>
/// Saves the image to the given stream with the png format.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="options">The options for the encoder.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>
/// The <see cref="Image{TColor}"/>.
/// </returns>
public static Image<TColor> SaveAsPng<TColor>(this Image<TColor> source, Stream stream, IPngEncoderOptions options)
where TColor : struct, IPixel<TColor>
{
PngEncoder encoder = new PngEncoder();
encoder.Encode(source, stream, options);
return source;
}
}
}

14
src/ImageSharp.Formats.Png/PngDecoder.cs

@ -30,16 +30,26 @@ namespace ImageSharp.Formats
/// </remarks>
public class PngDecoder : IImageDecoder
{
/// <inheritdoc/>
public void Decode<TColor>(Image<TColor> image, Stream stream, IDecoderOptions options)
where TColor : struct, IPixel<TColor>
{
IPngDecoderOptions pngOptions = PngDecoderOptions.Create(options);
this.Decode(image, stream, pngOptions);
}
/// <summary>
/// Decodes the image from the specified stream to the <see cref="ImageBase{TColor}"/>.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageBase{TColor}"/> to decode to.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
public void Decode<TColor>(Image<TColor> image, Stream stream)
/// <param name="options">The options for the decoder.</param>
public void Decode<TColor>(Image<TColor> image, Stream stream, IPngDecoderOptions options)
where TColor : struct, IPixel<TColor>
{
new PngDecoderCore().Decode(image, stream);
new PngDecoderCore(options).Decode(image, stream);
}
}
}

33
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
/// </summary>
private readonly char[] chars = new char[4];
/// <summary>
/// The decoder options.
/// </summary>
private readonly IPngDecoderOptions options;
/// <summary>
/// Reusable crc for validating chunks.
/// </summary>
@ -120,6 +125,15 @@ namespace ImageSharp.Formats
ColorTypes.Add((int)PngColorType.RgbWithAlpha, new byte[] { 8 });
}
/// <summary>
/// Initializes a new instance of the <see cref="PngDecoderCore"/> class.
/// </summary>
/// <param name="options">The decoder options.</param>
public PngDecoderCore(IPngDecoderOptions options)
{
this.options = options ?? new PngDecoderOptions();
}
/// <summary>
/// Gets or sets the png color type
/// </summary>
@ -763,6 +777,11 @@ namespace ImageSharp.Formats
private void ReadTextChunk<TColor>(Image<TColor> image, byte[] data, int length)
where TColor : struct, IPixel<TColor>
{
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
/// </summary>
/// <param name="pass">Th current pass index</param>
/// <returns>The <see cref="int"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int ComputeColumnsAdam7(int pass)
{
int width = this.header.Width;

49
src/ImageSharp.Formats.Png/PngDecoderOptions.cs

@ -0,0 +1,49 @@
// <copyright file="PngDecoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System.Text;
/// <summary>
/// Encapsulates the options for the <see cref="PngDecoder"/>.
/// </summary>
public sealed class PngDecoderOptions : DecoderOptions, IPngDecoderOptions
{
private static readonly Encoding DefaultEncoding = Encoding.GetEncoding("ASCII");
/// <summary>
/// Initializes a new instance of the <see cref="PngDecoderOptions"/> class.
/// </summary>
public PngDecoderOptions()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PngDecoderOptions"/> class.
/// </summary>
/// <param name="options">The options for the decoder.</param>
private PngDecoderOptions(IDecoderOptions options)
: base(options)
{
}
/// <summary>
/// Gets or sets the encoding that should be used when reading text chunks.
/// </summary>
public Encoding TextEncoding { get; set; } = DefaultEncoding;
/// <summary>
/// Converts the options to a <see cref="IPngDecoderOptions"/> instance with a cast
/// or by creating a new instance with the specfied options.
/// </summary>
/// <param name="options">The options for the decoder.</param>
/// <returns>The options for the <see cref="PngDecoder"/>.</returns>
internal static IPngDecoderOptions Create(IDecoderOptions options)
{
return options as IPngDecoderOptions ?? new PngDecoderOptions(options);
}
}
}

68
src/ImageSharp.Formats.Png/PngEncoder.cs

@ -5,72 +5,34 @@
namespace ImageSharp.Formats
{
using System;
using System.IO;
using ImageSharp.Quantizers;
/// <summary>
/// Image encoder for writing image data to a stream in png format.
/// </summary>
public class PngEncoder : IImageEncoder
{
/// <summary>
/// Gets or sets the quality of output for images.
/// </summary>
public int Quality { get; set; }
/// <summary>
/// Gets or sets the png color type
/// </summary>
public PngColorType PngColorType { get; set; } = PngColorType.RgbWithAlpha;
/// <summary>
/// Gets or sets the compression level 1-9.
/// <remarks>Defaults to 6.</remarks>
/// </summary>
public int CompressionLevel { get; set; } = 6;
/// <summary>
/// Gets or sets the gamma value, that will be written
/// the the stream, when the <see cref="WriteGamma"/> property
/// is set to true. The default value is 2.2F.
/// </summary>
/// <value>The gamma value of the image.</value>
public float Gamma { get; set; } = 2.2F;
/// <summary>
/// Gets or sets quantizer for reducing the color count.
/// </summary>
public IQuantizer Quantizer { get; set; }
/// <inheritdoc/>
public void Encode<TColor>(Image<TColor> image, Stream stream, IEncoderOptions options)
where TColor : struct, IPixel<TColor>
{
IPngEncoderOptions pngOptions = PngEncoderOptions.Create(options);
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
public byte Threshold { get; set; } = 0;
this.Encode(image, stream, pngOptions);
}
/// <summary>
/// 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 <see cref="Image{TColor}"/>.
/// </summary>
public bool WriteGamma { get; set; }
/// <inheritdoc/>
public void Encode<TColor>(Image<TColor> image, Stream stream)
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TColor}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="options">The options for the encoder.</param>
public void Encode<TColor>(Image<TColor> image, Stream stream, IPngEncoderOptions options)
where TColor : struct, IPixel<TColor>
{
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);
}
}
}

92
src/ImageSharp.Formats.Png/PngEncoderCore.cs

@ -40,6 +40,11 @@ namespace ImageSharp.Formats
/// </summary>
private readonly Crc32 crc = new Crc32();
/// <summary>
/// The options for the encoder.
/// </summary>
private readonly IPngEncoderOptions options;
/// <summary>
/// Contains the raw pixel data from an indexed image.
/// </summary>
@ -86,44 +91,28 @@ namespace ImageSharp.Formats
private byte[] paeth;
/// <summary>
/// Gets or sets the quality of output for images.
/// </summary>
public int Quality { get; set; }
/// <summary>
/// Gets or sets the png color type
/// The quality of output for images.
/// </summary>
public PngColorType PngColorType { get; set; }
private int quality;
/// <summary>
/// Gets or sets the compression level 1-9.
/// <remarks>Defaults to 6.</remarks>
/// The png color type.
/// </summary>
public int CompressionLevel { get; set; } = 6;
private PngColorType pngColorType;
/// <summary>
/// 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.
/// </summary>
public bool WriteGamma { get; set; }
private IQuantizer quantizer;
/// <summary>
/// Gets or sets the gamma value, that will be written
/// the the stream, when the <see cref="WriteGamma"/> property
/// is set to true. The default value is 2.2F.
/// Initializes a new instance of the <see cref="PngEncoderCore"/> class.
/// </summary>
/// <value>The gamma value of the image.</value>
public float Gamma { get; set; } = 2.2F;
/// <summary>
/// Gets or sets the quantizer for reducing the color count.
/// </summary>
public IQuantizer Quantizer { get; set; }
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
public byte Threshold { get; set; }
/// <param name="options">The options for the encoder.</param>
public PngEncoderCore(IPngEncoderOptions options)
{
this.options = options ?? new PngEncoderOptions();
}
/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TColor}"/>.
@ -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<TColor>(PixelAccessor<TColor> pixels, int row, byte[] previousScanline, byte[] rawScanline, byte[] result)
where TColor : struct, IPixel<TColor>
{
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
/// <returns>The <see cref="int"/></returns>
private int CalculateBytesPerPixel()
{
switch (this.PngColorType)
switch (this.pngColorType)
{
case PngColorType.Grayscale:
return 1;
@ -488,18 +480,18 @@ namespace ImageSharp.Formats
private QuantizedImage<TColor> WritePaletteChunk<TColor>(Stream stream, PngHeader header, ImageBase<TColor> image)
where TColor : struct, IPixel<TColor>
{
if (this.Quality > 256)
if (this.quality > 256)
{
return null;
}
if (this.Quantizer == null)
if (this.quantizer == null)
{
this.Quantizer = new OctreeQuantizer<TColor>();
this.quantizer = new OctreeQuantizer<TColor>();
}
// Quantize the image returning a palette. This boxing is icky.
QuantizedImage<TColor> quantized = ((IQuantizer<TColor>)this.Quantizer).Quantize(image, this.Quality);
QuantizedImage<TColor> quantized = ((IQuantizer<TColor>)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
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
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++)
{

82
src/ImageSharp.Formats.Png/PngEncoderOptions.cs

@ -0,0 +1,82 @@
// <copyright file="PngEncoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using Quantizers;
/// <summary>
/// Encapsulates the options for the <see cref="PngEncoder"/>.
/// </summary>
public sealed class PngEncoderOptions : EncoderOptions, IPngEncoderOptions
{
/// <summary>
/// Initializes a new instance of the <see cref="PngEncoderOptions"/> class.
/// </summary>
public PngEncoderOptions()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PngEncoderOptions"/> class.
/// </summary>
/// <param name="options">The options for the encoder.</param>
private PngEncoderOptions(IEncoderOptions options)
: base(options)
{
}
/// <summary>
/// Gets or sets the quality of output for images.
/// </summary>
public int Quality { get; set; }
/// <summary>
/// Gets or sets the png color type
/// </summary>
public PngColorType PngColorType { get; set; } = PngColorType.RgbWithAlpha;
/// <summary>
/// Gets or sets the compression level 1-9.
/// <remarks>Defaults to 6.</remarks>
/// </summary>
public int CompressionLevel { get; set; } = 6;
/// <summary>
/// Gets or sets the gamma value, that will be written
/// the the stream, when the <see cref="WriteGamma"/> property
/// is set to true. The default value is 2.2F.
/// </summary>
/// <value>The gamma value of the image.</value>
public float Gamma { get; set; } = 2.2F;
/// <summary>
/// Gets or sets quantizer for reducing the color count.
/// </summary>
public IQuantizer Quantizer { get; set; }
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
public byte Threshold { get; set; } = 0;
/// <summary>
/// Gets or sets a value indicating whether this instance should write
/// gamma information to the stream. The default value is false.
/// </summary>
public bool WriteGamma { get; set; }
/// <summary>
/// Converts the options to a <see cref="IPngEncoderOptions"/> instance with a
/// cast or by creating a new instance with the specfied options.
/// </summary>
/// <param name="options">The options for the encoder.</param>
/// <returns>The options for the <see cref="PngEncoder"/>.</returns>
internal static IPngEncoderOptions Create(IEncoderOptions options)
{
return options as IPngEncoderOptions ?? new PngEncoderOptions(options);
}
}
}

37
src/ImageSharp/Formats/DecoderOptions.cs

@ -0,0 +1,37 @@
// <copyright file="DecoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
/// <summary>
/// Encapsulates the shared decoder options.
/// </summary>
public class DecoderOptions : IDecoderOptions
{
/// <summary>
/// Initializes a new instance of the <see cref="DecoderOptions"/> class.
/// </summary>
public DecoderOptions()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DecoderOptions"/> class.
/// </summary>
/// <param name="options">The decoder options</param>
protected DecoderOptions(IDecoderOptions options)
{
if (options != null)
{
this.IgnoreMetadata = options.IgnoreMetadata;
}
}
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
public bool IgnoreMetadata { get; set; } = false;
}
}

37
src/ImageSharp/Formats/EncoderOptions.cs

@ -0,0 +1,37 @@
// <copyright file="EncoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
/// <summary>
/// Encapsulates the shared encoder options.
/// </summary>
public class EncoderOptions : IEncoderOptions
{
/// <summary>
/// Initializes a new instance of the <see cref="EncoderOptions"/> class.
/// </summary>
public EncoderOptions()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="EncoderOptions"/> class.
/// </summary>
/// <param name="options">The encoder options</param>
protected EncoderOptions(IEncoderOptions options)
{
if (options != null)
{
this.IgnoreMetadata = options.IgnoreMetadata;
}
}
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being encoded.
/// </summary>
public bool IgnoreMetadata { get; set; } = false;
}
}

18
src/ImageSharp/Formats/IDecoderOptions.cs

@ -0,0 +1,18 @@
// <copyright file="IDecoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
/// <summary>
/// Encapsulates the shared decoder options.
/// </summary>
public interface IDecoderOptions
{
/// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
bool IgnoreMetadata { get; }
}
}

18
src/ImageSharp/Formats/IEncoderOptions.cs

@ -0,0 +1,18 @@
// <copyright file="IEncoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
/// <summary>
/// Encapsulates the shared encoder options.
/// </summary>
public interface IEncoderOptions
{
/// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being encoded.
/// </summary>
bool IgnoreMetadata { get; }
}
}

3
src/ImageSharp/Formats/IImageDecoder.cs

@ -19,7 +19,8 @@ namespace ImageSharp.Formats
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageBase{TColor}"/> to decode to.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
void Decode<TColor>(Image<TColor> image, Stream stream)
/// <param name="options">The options for the decoder.</param>
void Decode<TColor>(Image<TColor> image, Stream stream, IDecoderOptions options)
where TColor : struct, IPixel<TColor>;
}
}

3
src/ImageSharp/Formats/IImageEncoder.cs

@ -19,7 +19,8 @@ namespace ImageSharp.Formats
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TColor}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
void Encode<TColor>(Image<TColor> image, Stream stream)
/// <param name="options">The options for the encoder.</param>
void Encode<TColor>(Image<TColor> image, Stream stream, IEncoderOptions options)
where TColor : struct, IPixel<TColor>;
}
}

149
src/ImageSharp/Image.cs

@ -8,6 +8,8 @@ namespace ImageSharp
using System.Diagnostics;
using System.IO;
using Formats;
/// <summary>
/// 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
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class.
/// </summary>
/// <param name="stream">
/// The stream containing image information.
/// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="stream"/> is null.</exception>
public Image(Stream stream)
: base(stream, null, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class.
/// </summary>
/// <param name="stream">
/// The stream containing image information.
/// </param>
/// <param name="options">
/// The options for the decoder.
/// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="stream"/> is null.</exception>
public Image(Stream stream, IDecoderOptions options)
: base(stream, options, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class.
/// </summary>
@ -39,12 +68,57 @@ namespace ImageSharp
/// The configuration providing initialization code which allows extending the library.
/// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="stream"/> is null.</exception>
public Image(Stream stream, Configuration configuration = null)
: base(stream, configuration)
public Image(Stream stream, Configuration configuration)
: base(stream, null, configuration)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class.
/// </summary>
/// <param name="stream">
/// The stream containing image information.
/// </param>
/// <param name="options">
/// The options for the decoder.
/// </param>
/// <param name="configuration">
/// The configuration providing initialization code which allows extending the library.
/// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="stream"/> is null.</exception>
public Image(Stream stream, IDecoderOptions options, Configuration configuration)
: base(stream, options, configuration)
{
}
#if !NETSTANDARD1_1
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class.
/// </summary>
/// <param name="filePath">
/// A file path to read image information.
/// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="filePath"/> is null.</exception>
public Image(string filePath)
: base(filePath, null, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class.
/// </summary>
/// <param name="filePath">
/// A file path to read image information.
/// </param>
/// <param name="options">
/// The options for the decoder.
/// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="filePath"/> is null.</exception>
public Image(string filePath, IDecoderOptions options)
: base(filePath, options, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class.
/// </summary>
@ -55,8 +129,26 @@ namespace ImageSharp
/// The configuration providing initialization code which allows extending the library.
/// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="filePath"/> is null.</exception>
public Image(string filePath, Configuration configuration = null)
: base(filePath, configuration)
public Image(string filePath, Configuration configuration)
: base(filePath, null, configuration)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class.
/// </summary>
/// <param name="filePath">
/// A file path to read image information.
/// </param>
/// <param name="options">
/// The options for the decoder.
/// </param>
/// <param name="configuration">
/// The configuration providing initialization code which allows extending the library.
/// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="filePath"/> is null.</exception>
public Image(string filePath, IDecoderOptions options, Configuration configuration)
: base(filePath, options, configuration)
{
}
#endif
@ -67,12 +159,57 @@ namespace ImageSharp
/// <param name="bytes">
/// The byte array containing image information.
/// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="bytes"/> is null.</exception>
public Image(byte[] bytes)
: base(bytes, null, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class.
/// </summary>
/// <param name="bytes">
/// The byte array containing image information.
/// </param>
/// <param name="options">
/// The options for the decoder.
/// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="bytes"/> is null.</exception>
public Image(byte[] bytes, IDecoderOptions options)
: base(bytes, options, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class.
/// </summary>
/// <param name="bytes">
/// The byte array containing image information.
/// </param>
/// <param name="configuration">
/// The configuration providing initialization code which allows extending the library.
/// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="bytes"/> is null.</exception>
public Image(byte[] bytes, Configuration configuration)
: base(bytes, null, configuration)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class.
/// </summary>
/// <param name="bytes">
/// The byte array containing image information.
/// </param>
/// <param name="options">
/// The options for the decoder.
/// </param>
/// <param name="configuration">
/// The configuration providing initialization code which allows extending the library.
/// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="bytes"/> is null.</exception>
public Image(byte[] bytes, Configuration configuration = null)
: base(bytes, configuration)
public Image(byte[] bytes, IDecoderOptions options, Configuration configuration)
: base(bytes, options, configuration)
{
}

243
src/ImageSharp/Image/Image{TColor}.cs

@ -46,6 +46,33 @@ namespace ImageSharp
this.CurrentImageFormat = this.Configuration.ImageFormats.First();
}
/// <summary>
/// Initializes a new instance of the <see cref="Image{TColor}"/> class.
/// </summary>
/// <param name="stream">
/// The stream containing image information.
/// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="stream"/> is null.</exception>
public Image(Stream stream)
: this(stream, null, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Image{TColor}"/> class.
/// </summary>
/// <param name="stream">
/// The stream containing image information.
/// </param>
/// <param name="options">
/// The options for the decoder.
/// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="stream"/> is null.</exception>
public Image(Stream stream, IDecoderOptions options)
: this(stream, options, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Image{TColor}"/> class.
/// </summary>
@ -56,14 +83,59 @@ namespace ImageSharp
/// The configuration providing initialization code which allows extending the library.
/// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="stream"/> is null.</exception>
public Image(Stream stream, Configuration configuration = null)
public Image(Stream stream, Configuration configuration)
: this(stream, null, configuration)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Image{TColor}"/> class.
/// </summary>
/// <param name="stream">
/// The stream containing image information.
/// </param>
/// <param name="options">
/// The options for the decoder.
/// </param>
/// <param name="configuration">
/// The configuration providing initialization code which allows extending the library.
/// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="stream"/> is null.</exception>
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
/// <summary>
/// Initializes a new instance of the <see cref="Image{TColor}"/> class.
/// </summary>
/// <param name="filePath">
/// The file containing image information.
/// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="filePath"/> is null.</exception>
public Image(string filePath)
: this(filePath, null, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Image{TColor}"/> class.
/// </summary>
/// <param name="filePath">
/// The file containing image information.
/// </param>
/// <param name="options">
/// The options for the decoder.
/// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="filePath"/> is null.</exception>
public Image(string filePath, IDecoderOptions options)
: this(filePath, options, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Image{TColor}"/> class.
/// </summary>
@ -74,17 +146,62 @@ namespace ImageSharp
/// The configuration providing initialization code which allows extending the library.
/// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="filePath"/> is null.</exception>
public Image(string filePath, Configuration configuration = null)
public Image(string filePath, Configuration configuration)
: this(filePath, null, configuration)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Image{TColor}"/> class.
/// </summary>
/// <param name="filePath">
/// The file containing image information.
/// </param>
/// <param name="options">
/// The options for the decoder.
/// </param>
/// <param name="configuration">
/// The configuration providing initialization code which allows extending the library.
/// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="filePath"/> is null.</exception>
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
/// <summary>
/// Initializes a new instance of the <see cref="Image{TColor}"/> class.
/// </summary>
/// <param name="bytes">
/// The byte array containing image information.
/// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="bytes"/> is null.</exception>
public Image(byte[] bytes)
: this(bytes, null, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Image{TColor}"/> class.
/// </summary>
/// <param name="bytes">
/// The byte array containing image information.
/// </param>
/// <param name="options">
/// The options for the decoder.
/// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="bytes"/> is null.</exception>
public Image(byte[] bytes, IDecoderOptions options)
: this(bytes, options, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Image{TColor}"/> class.
/// </summary>
@ -95,14 +212,32 @@ namespace ImageSharp
/// The configuration providing initialization code which allows extending the library.
/// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="bytes"/> is null.</exception>
public Image(byte[] bytes, Configuration configuration = null)
public Image(byte[] bytes, Configuration configuration)
: this(bytes, null, configuration)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Image{TColor}"/> class.
/// </summary>
/// <param name="bytes">
/// The byte array containing image information.
/// </param>
/// <param name="options">
/// The options for the decoder.
/// </param>
/// <param name="configuration">
/// The configuration providing initialization code which allows extending the library.
/// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="bytes"/> is null.</exception>
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
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>The <see cref="Image{TColor}"/></returns>
public Image<TColor> Save(Stream stream)
{
return this.Save(stream, (IEncoderOptions)null);
}
/// <summary>
/// Saves the image to the given stream using the currently loaded image format.
/// </summary>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="options">The options for the encoder.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>The <see cref="Image{TColor}"/></returns>
public Image<TColor> 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
/// </summary>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="format">The format to save the image as.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream or format is null.</exception>
/// <returns>The <see cref="Image{TColor}"/></returns>
public Image<TColor> Save(Stream stream, IImageFormat format)
{
return this.Save(stream, format, null);
}
/// <summary>
/// Saves the image to the given stream using the given image format.
/// </summary>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="format">The format to save the image as.</param>
/// <param name="options">The options for the encoder.</param>
/// <returns>The <see cref="Image{TColor}"/></returns>
public Image<TColor> 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 <see cref="Image{TColor}"/>.
/// </returns>
public Image<TColor> Save(Stream stream, IImageEncoder encoder)
{
return this.Save(stream, encoder, null);
}
/// <summary>
/// Saves the image to the given stream using the given image encoder.
/// </summary>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="options">The options for the encoder.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream or encoder is null.</exception>
/// <returns>
/// The <see cref="Image{TColor}"/>.
/// </returns>
public Image<TColor> 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
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>The <see cref="Image{TColor}"/></returns>
public Image<TColor> Save(string filePath)
{
return this.Save(filePath, (IEncoderOptions)null);
}
/// <summary>
/// Saves the image to the given stream using the currently loaded image format.
/// </summary>
/// <param name="filePath">The file path to save the image to.</param>
/// <param name="options">The options for the encoder.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>The <see cref="Image{TColor}"/></returns>
public Image<TColor> 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
/// <exception cref="System.ArgumentNullException">Thrown if the format is null.</exception>
/// <returns>The <see cref="Image{TColor}"/></returns>
public Image<TColor> Save(string filePath, IImageFormat format)
{
return this.Save(filePath, format, null);
}
/// <summary>
/// Saves the image to the given stream using the currently loaded image format.
/// </summary>
/// <param name="filePath">The file path to save the image to.</param>
/// <param name="format">The format to save the image as.</param>
/// <param name="options">The options for the encoder.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the format is null.</exception>
/// <returns>The <see cref="Image{TColor}"/></returns>
public Image<TColor> Save(string filePath, IImageFormat format, IEncoderOptions options)
{
Guard.NotNull(format, nameof(format));
using (FileStream fs = File.Create(filePath))
@ -288,6 +486,19 @@ namespace ImageSharp
/// <exception cref="System.ArgumentNullException">Thrown if the encoder is null.</exception>
/// <returns>The <see cref="Image{TColor}"/></returns>
public Image<TColor> Save(string filePath, IImageEncoder encoder)
{
return this.Save(filePath, encoder, null);
}
/// <summary>
/// Saves the image to the given stream using the currently loaded image format.
/// </summary>
/// <param name="filePath">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="options">The options for the encoder.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the encoder is null.</exception>
/// <returns>The <see cref="Image{TColor}"/></returns>
public Image<TColor> 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.
/// </summary>
/// <param name="stream">The stream containing image information.</param>
/// <param name="options">The options for the decoder.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
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.
/// </summary>
/// <param name="stream">The stream.</param>
/// <param name="options">The options for the decoder.</param>
/// <returns>
/// The <see cref="bool"/>.
/// </returns>
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;
}

3
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]);
}
}
}

3
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

10
tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs → tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs

@ -1,4 +1,4 @@
// <copyright file="BitmapTests.cs" company="James Jackson-South">
// <copyright file="BmpEncoderTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
@ -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<BmpBitsPerPixel> BitsPerPixel
= new TheoryData<BmpBitsPerPixel>
{
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 });
}
}
}

66
tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs

@ -0,0 +1,66 @@
// <copyright file="GifDecoderTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
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);
}
}
}
}

90
tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs

@ -0,0 +1,90 @@
// <copyright file="GifEncoderTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
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);
}
}
}
}
}
}

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

@ -61,12 +61,13 @@ namespace ImageSharp.Tests
byte[] data;
using (Image<TColor> image = provider.GetImage())
{
JpegEncoder encoder = new JpegEncoder() { Subsample = subsample, Quality = quality };
JpegEncoder encoder = new JpegEncoder();
JpegEncoderOptions options = new JpegEncoderOptions { Subsample = subsample, Quality = quality };
data = new byte[65536];
using (MemoryStream ms = new MemoryStream(data))
{
image.Save(ms, encoder);
image.Save(ms, encoder, options);
}
}
@ -90,7 +91,7 @@ namespace ImageSharp.Tests
ms.Seek(0, SeekOrigin.Begin);
Image<TColor> 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);
}
}
}
}

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

@ -38,11 +38,12 @@ namespace ImageSharp.Tests
{
image.MetaData.Quality = quality;
image.MetaData.ExifProfile = null; // Reduce the size of the file
JpegEncoder encoder = new JpegEncoder { Subsample = subsample, Quality = quality };
JpegEncoder encoder = new JpegEncoder();
JpegEncoderOptions options = new JpegEncoderOptions { Subsample = subsample, Quality = quality };
provider.Utility.TestName += $"{subsample}_Q{quality}";
provider.Utility.SaveTestOutputFile(image, "png");
provider.Utility.SaveTestOutputFile(image, "jpg", encoder);
provider.Utility.SaveTestOutputFile(image, "jpg", encoder, options);
}
}
@ -59,13 +60,63 @@ namespace ImageSharp.Tests
using (FileStream outputStream = File.OpenWrite(utility.GetTestOutputFileName("jpg")))
{
JpegEncoder encoder = new JpegEncoder()
{
Subsample = subSample,
Quality = quality
};
JpegEncoder encoder = new JpegEncoder();
image.Save(outputStream, encoder);
image.Save(outputStream, encoder, new JpegEncoderOptions()
{
Subsample = subSample,
Quality = quality
});
}
}
}
[Fact]
public void Encode_IgnoreMetadataIsFalse_ExifProfileIsWritten()
{
var options = new EncoderOptions()
{
IgnoreMetadata = false
};
TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan);
using (Image input = testFile.CreateImage())
{
using (MemoryStream memStream = new MemoryStream())
{
input.Save(memStream, new JpegFormat(), options);
memStream.Position = 0;
using (Image output = new Image(memStream))
{
Assert.NotNull(output.MetaData.ExifProfile);
}
}
}
}
[Fact]
public void Encode_IgnoreMetadataIsTrue_ExifProfileIgnored()
{
var options = new JpegEncoderOptions()
{
IgnoreMetadata = true
};
TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan);
using (Image input = testFile.CreateImage())
{
using (MemoryStream memStream = new MemoryStream())
{
input.SaveAsJpeg(memStream, options);
memStream.Position = 0;
using (Image output = new Image(memStream))
{
Assert.Null(output.MetaData.ExifProfile);
}
}
}
}

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

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

66
tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs

@ -0,0 +1,66 @@
// <copyright file="PngDecoderTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
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);
}
}
}
}

6
tests/ImageSharp.Tests/Formats/Png/PngTests.cs → tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs

@ -1,4 +1,4 @@
// <copyright file="PngTests.cs" company="James Jackson-South">
// <copyright file="PngEncoderTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
@ -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()

2
tests/ImageSharp.Tests/Image/ImageTests.cs

@ -59,7 +59,7 @@ namespace ImageSharp.Tests
ArgumentNullException ex = Assert.Throws<ArgumentNullException>(
() =>
{
new Image(null);
new Image((string) null);
});
}

12
tests/ImageSharp.Tests/TestFile.cs

@ -130,6 +130,18 @@ namespace ImageSharp.Tests
return new Image(this.image);
}
/// <summary>
/// Creates a new image.
/// </summary>
/// <param name="options">The options for the decoder.</param>
/// <returns>
/// The <see cref="Image"/>.
/// </returns>
public Image CreateImage(IDecoderOptions options)
{
return new Image(this.Bytes, options);
}
/// <summary>
/// Gets the correct path to the formats directory.
/// </summary>

9
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

BIN
tests/ImageSharp.Tests/TestImages/Formats/Gif/rings.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

BIN
tests/ImageSharp.Tests/TestImages/Formats/Png/chunklength2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

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

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

Loading…
Cancel
Save