Browse Source

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

pull/125/head
Anton Firszov 10 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. 8
      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. 59
      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`. **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) - [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. - 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. To clone it locally click the "Clone in Windows" button above or run the following git commands.
```bash ```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] 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] Png (Read: Rgb, Rgba, Grayscale, Grayscale + alpha, Palette. Write: Rgb, Rgba, Grayscale, Grayscale + alpha, Palette) Supports interlaced decoding
- [x] Gif (Includes animated) - [x] Gif (Includes animated)
- [ ] Tiff - [ ] Tiff (Help needed)
- **Metadata** - **Metadata**
- [x] EXIF Read/Write (Jpeg just now) - [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] Octree
- [x] Xiaolin Wu - [x] Xiaolin Wu
- [x] Palette - [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.** - **Basic color structs with implicit operators.**
- [x] Color - 32bit color in RGBA order (IPackedPixel\<TPacked\>). - [x] Color - 32bit color in RGBA order (IPackedPixel\<TPacked\>).
- [x] Bgra32 - [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 - [x] DrawImage
- [ ] Gradient brush (Need help) - [ ] Gradient brush (Need help)
- **DrawingText** - **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. - Other stuff I haven't thought of.

2
src/ImageSharp.Drawing.Paths/project.json

@ -44,7 +44,7 @@
"ImageSharp.Drawing": { "ImageSharp.Drawing": {
"target": "project" "target": "project"
}, },
"SixLabors.Shapes": "0.1.0-alpha0005", "SixLabors.Shapes": "0.1.0-alpha0006",
"StyleCop.Analyzers": { "StyleCop.Analyzers": {
"version": "1.0.0", "version": "1.0.0",
"type": "build" "type": "build"

1
src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs

@ -110,7 +110,6 @@ namespace ImageSharp.Drawing.Processors
Vector4 sourceVector = color.Color.ToVector4(); Vector4 sourceVector = color.Color.ToVector4();
Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity);
finalColor.W = backgroundVector.W;
TColor packed = default(TColor); TColor packed = default(TColor);
packed.PackFromVector4(finalColor); 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 sourceVector = applicator[x, y].ToVector4();
Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity);
finalColor.W = backgroundVector.W;
TColor packed = default(TColor); TColor packed = default(TColor);
packed.PackFromVector4(finalColor); packed.PackFromVector4(finalColor);

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

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

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

@ -16,34 +16,40 @@ namespace ImageSharp.Formats
internal sealed class BmpEncoderCore internal sealed class BmpEncoderCore
{ {
/// <summary> /// <summary>
/// The number of bits per pixel. /// The options for the encoder.
/// </summary> /// </summary>
private BmpBitsPerPixel bmpBitsPerPixel; private readonly IBmpEncoderOptions options;
/// <summary> /// <summary>
/// The amount to pad each row by. /// The amount to pad each row by.
/// </summary> /// </summary>
private int padding; 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> /// <summary>
/// Encodes the image to the specified stream from the <see cref="ImageBase{TColor}"/>. /// Encodes the image to the specified stream from the <see cref="ImageBase{TColor}"/>.
/// </summary> /// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam> /// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageBase{TColor}"/> to encode from.</param> /// <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="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)
public void Encode<TColor>(ImageBase<TColor> image, Stream stream, BmpBitsPerPixel bitsPerPixel)
where TColor : struct, IPixel<TColor> where TColor : struct, IPixel<TColor>
{ {
Guard.NotNull(image, nameof(image)); Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
this.bmpBitsPerPixel = bitsPerPixel;
// Cast to int will get the bytes per pixel // 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); 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. // Do not use IDisposable pattern here as we want to preserve the stream.
EndianBinaryWriter writer = new EndianBinaryWriter(Endianness.LittleEndian, stream); EndianBinaryWriter writer = new EndianBinaryWriter(Endianness.LittleEndian, stream);
@ -128,7 +134,7 @@ namespace ImageSharp.Formats
{ {
using (PixelAccessor<TColor> pixels = image.Lock()) using (PixelAccessor<TColor> pixels = image.Lock())
{ {
switch (this.bmpBitsPerPixel) switch (this.options.BitsPerPixel)
{ {
case BmpBitsPerPixel.Pixel32: case BmpBitsPerPixel.Pixel32:
this.Write32Bit(writer, pixels); 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 namespace ImageSharp.Formats
{ {
using System.Text;
/// <summary> /// <summary>
/// Constants that define specific points within a gif. /// Constants that define specific points within a gif.
/// </summary> /// </summary>
internal sealed class GifConstants internal static class GifConstants
{ {
/// <summary> /// <summary>
/// The file type. /// The file type.
@ -50,6 +52,11 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
public const byte CommentLabel = 0xFE; 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> /// <summary>
/// The maximum comment length. /// The maximum comment length.
/// </summary> /// </summary>
@ -79,5 +86,10 @@ namespace ImageSharp.Formats
/// The end introducer trailer <value>;</value>. /// The end introducer trailer <value>;</value>.
/// </summary> /// </summary>
public const byte EndIntroducer = 0x3B; 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 public class GifDecoder : IImageDecoder
{ {
/// <inheritdoc/> /// <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> 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;
using System.Buffers; using System.Buffers;
using System.IO; using System.IO;
using System.Text;
/// <summary> /// <summary>
/// Performs the gif decoding operation. /// Performs the gif decoding operation.
@ -21,6 +22,11 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private readonly byte[] buffer = new byte[16]; private readonly byte[] buffer = new byte[16];
/// <summary>
/// The decoder options.
/// </summary>
private readonly IGifDecoderOptions options;
/// <summary> /// <summary>
/// The image to decode the information to. /// The image to decode the information to.
/// </summary> /// </summary>
@ -61,6 +67,15 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private GifGraphicsControlExtension graphicsControlExtension; 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> /// <summary>
/// Decodes the stream to the image. /// Decodes the stream to the image.
/// </summary> /// </summary>
@ -225,25 +240,32 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private void ReadComments() 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 try
{ {
this.currentStream.Read(flagBuffer, 0, flag); this.currentStream.Read(commentsBuffer, 0, length);
this.decodedImage.MetaData.Properties.Add(new ImageProperty("Comments", BitConverter.ToString(flagBuffer, 0, flag))); string comments = this.options.TextEncoding.GetString(commentsBuffer, 0, length);
this.decodedImage.MetaData.Properties.Add(new ImageProperty(GifConstants.Comments, comments));
} }
finally 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;
using System.IO; using System.IO;
using ImageSharp.Quantizers;
/// <summary> /// <summary>
/// Image encoder for writing image data to a stream in gif format. /// Image encoder for writing image data to a stream in gif format.
/// </summary> /// </summary>
public class GifEncoder : IImageEncoder public class GifEncoder : IImageEncoder
{ {
/// <summary> /// <inheritdoc/>
/// Gets or sets the quality of output for images. public void Encode<TColor>(Image<TColor> image, Stream stream, IEncoderOptions options)
/// </summary> where TColor : struct, IPixel<TColor>
/// <remarks>For gifs the value ranges from 1 to 256.</remarks> {
public int Quality { get; set; } IGifEncoderOptions gifOptions = GifEncoderOptions.Create(options);
/// <summary> this.Encode(image, stream, gifOptions);
/// Gets or sets the transparency threshold. }
/// </summary>
public byte Threshold { get; set; } = 128;
/// <summary> /// <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> /// </summary>
public IQuantizer Quantizer { get; set; } /// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TColor}"/> to encode from.</param>
/// <inheritdoc/> /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public void Encode<TColor>(Image<TColor> image, Stream stream) /// <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> where TColor : struct, IPixel<TColor>
{ {
GifEncoderCore encoder = new GifEncoderCore GifEncoderCore encoder = new GifEncoderCore(options);
{
Quality = this.Quality,
Quantizer = this.Quantizer,
Threshold = this.Threshold
};
encoder.Encode(image, stream); 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]; private readonly byte[] buffer = new byte[16];
/// <summary> /// <summary>
/// The number of bits requires to store the image palette. /// The options for the encoder.
/// </summary> /// </summary>
private int bitDepth; private readonly IGifEncoderOptions options;
/// <summary> /// <summary>
/// Gets or sets the quality of output for images. /// The number of bits requires to store the image palette.
/// </summary> /// </summary>
/// <remarks>For gifs the value ranges from 1 to 256.</remarks> private int bitDepth;
public int Quality { get; set; }
/// <summary> /// <summary>
/// Gets or sets the transparency threshold. /// Initializes a new instance of the <see cref="GifEncoderCore"/> class.
/// </summary> /// </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> /// <summary>
/// Gets or sets the quantizer for reducing the color count. /// Gets or sets the quantizer for reducing the color count.
@ -56,23 +59,20 @@ namespace ImageSharp.Formats
Guard.NotNull(image, nameof(image)); Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
if (this.Quantizer == null) this.Quantizer = this.options.Quantizer ?? new OctreeQuantizer<TColor>();
{
this.Quantizer = new OctreeQuantizer<TColor>();
}
// Do not use IDisposable pattern here as we want to preserve the stream. // Do not use IDisposable pattern here as we want to preserve the stream.
EndianBinaryWriter writer = new EndianBinaryWriter(Endianness.LittleEndian, stream); EndianBinaryWriter writer = new EndianBinaryWriter(Endianness.LittleEndian, stream);
// Ensure that quality can be set but has a fallback. // Ensure that quality can be set but has a fallback.
int quality = this.Quality > 0 ? this.Quality : image.MetaData.Quality; int quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality;
this.Quality = quality > 0 ? quality.Clamp(1, 256) : 256; quality = quality > 0 ? quality.Clamp(1, 256) : 256;
// Get the number of bits. // Get the number of bits.
this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(this.Quality); this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quality);
// Quantize the image returning a palette. // 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); int index = this.GetTransparentIndex(quantized);
@ -84,6 +84,7 @@ namespace ImageSharp.Formats
// Write the first frame. // Write the first frame.
this.WriteGraphicalControlExtension(image, writer, index); this.WriteGraphicalControlExtension(image, writer, index);
this.WriteComments(image, writer);
this.WriteImageDescriptor(image, writer); this.WriteImageDescriptor(image, writer);
this.WriteColorTable(quantized, writer); this.WriteColorTable(quantized, writer);
this.WriteImageData(quantized, writer); this.WriteImageData(quantized, writer);
@ -97,7 +98,7 @@ namespace ImageSharp.Formats
for (int i = 0; i < image.Frames.Count; i++) for (int i = 0; i < image.Frames.Count; i++)
{ {
ImageFrame<TColor> frame = image.Frames[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.WriteGraphicalControlExtension(frame, writer, this.GetTransparentIndex(quantizedFrame));
this.WriteImageDescriptor(frame, writer); this.WriteImageDescriptor(frame, writer);
@ -106,7 +107,7 @@ namespace ImageSharp.Formats
} }
} }
// TODO: Write Comments extension etc // TODO: Write extension etc
writer.Write(GifConstants.EndIntroducer); 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> /// <summary>
/// Writes the graphics control extension to the stream. /// Writes the graphics control extension to the stream.
/// </summary> /// </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> /// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param> /// <param name="stream">The stream to save the image to.</param>
/// <param name="quality">The quality to save the image to representing the number of colors. Between 1 and 256.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception> /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns> /// <returns>
/// The <see cref="Image{TColor}"/>. /// The <see cref="Image{TColor}"/>.
/// </returns> /// </returns>
public static Image<TColor> 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> 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> /// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param> /// <param name="stream">The stream to save the image to.</param>
/// <param name="quality">The quality to save the image to. Between 1 and 100.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception> /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns> /// <returns>
/// The <see cref="Image{TColor}"/>. /// The <see cref="Image{TColor}"/>.
/// </returns> /// </returns>
public static Image<TColor> SaveAsJpeg<TColor>(this Image<TColor> source, Stream stream, int quality = 75) public static Image<TColor> SaveAsJpeg<TColor>(this Image<TColor> source, Stream stream)
where TColor : struct, IPixel<TColor> 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 public class JpegDecoder : IImageDecoder
{ {
/// <inheritdoc/> /// <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> where TColor : struct, IPixel<TColor>
{ {
Guard.NotNull(image, "image"); Guard.NotNull(image, "image");
Guard.NotNull(stream, "stream"); Guard.NotNull(stream, "stream");
using (JpegDecoderCore decoder = new JpegDecoderCore()) using (JpegDecoderCore decoder = new JpegDecoderCore(options))
{ {
decoder.Decode(image, stream, false); decoder.Decode(image, stream, false);
} }

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

@ -37,6 +37,11 @@ namespace ImageSharp.Formats
public InputProcessor InputProcessor; public InputProcessor InputProcessor;
#pragma warning restore SA401 #pragma warning restore SA401
/// <summary>
/// The decoder options.
/// </summary>
private readonly IDecoderOptions options;
/// <summary> /// <summary>
/// The App14 marker color-space /// The App14 marker color-space
/// </summary> /// </summary>
@ -85,8 +90,10 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JpegDecoderCore" /> class. /// Initializes a new instance of the <see cref="JpegDecoderCore" /> class.
/// </summary> /// </summary>
public JpegDecoderCore() /// <param name="options">The decoder options.</param>
public JpegDecoderCore(IDecoderOptions options)
{ {
this.options = options ?? new DecoderOptions();
this.HuffmanTrees = HuffmanTree.CreateHuffmanTrees(); this.HuffmanTrees = HuffmanTree.CreateHuffmanTrees();
this.QuantizationTables = new Block8x8F[MaxTq + 1]; this.QuantizationTables = new Block8x8F[MaxTq + 1];
this.Temp = new byte[2 * Block8x8F.ScalarCount]; this.Temp = new byte[2 * Block8x8F.ScalarCount];
@ -958,7 +965,7 @@ namespace ImageSharp.Formats
private void ProcessApp1Marker<TColor>(int remaining, Image<TColor> image) private void ProcessApp1Marker<TColor>(int remaining, Image<TColor> image)
where TColor : struct, IPixel<TColor> where TColor : struct, IPixel<TColor>
{ {
if (remaining < 6) if (remaining < 6 || this.options.IgnoreMetadata)
{ {
this.InputProcessor.Skip(remaining); this.InputProcessor.Skip(remaining);
return; return;

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

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

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

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

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 namespace ImageSharp.Formats
{ {
using System.Runtime.CompilerServices;
/// <summary> /// <summary>
/// The Average filter uses the average of the two neighboring pixels (left and above) to predict /// The Average filter uses the average of the two neighboring pixels (left and above) to predict
/// the value of a pixel. /// the value of a pixel.
@ -19,6 +21,7 @@ namespace ImageSharp.Formats
/// <param name="previousScanline">The previous scanline.</param> /// <param name="previousScanline">The previous scanline.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param> /// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param> /// <param name="bytesPerPixel">The bytes per pixel.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode(byte[] scanline, byte[] previousScanline, int bytesPerScanline, int bytesPerPixel) public static void Decode(byte[] scanline, byte[] previousScanline, int bytesPerScanline, int bytesPerPixel)
{ {
// Average(x) + floor((Raw(x-bpp)+Prior(x))/2) // 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="previousScanline">The previous scanline.</param>
/// <param name="result">The filtered scanline result.</param> /// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param> /// <param name="bytesPerPixel">The bytes per pixel.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result, int bytesPerPixel) public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result, int bytesPerPixel)
{ {
// Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) // 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="left">The left byte</param>
/// <param name="above">The above byte</param> /// <param name="above">The above byte</param>
/// <returns>The <see cref="int"/></returns> /// <returns>The <see cref="int"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Average(byte left, byte above) private static int Average(byte left, byte above)
{ {
return (left + above) >> 1; return (left + above) >> 1;

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

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

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

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

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

@ -5,6 +5,8 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System.Runtime.CompilerServices;
/// <summary> /// <summary>
/// The Sub filter transmits the difference between each byte and the value of the corresponding byte /// The Sub filter transmits the difference between each byte and the value of the corresponding byte
/// of the prior pixel. /// of the prior pixel.
@ -18,6 +20,7 @@ namespace ImageSharp.Formats
/// <param name="scanline">The scanline to decode</param> /// <param name="scanline">The scanline to decode</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param> /// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param> /// <param name="bytesPerPixel">The bytes per pixel.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode(byte[] scanline, int bytesPerScanline, int bytesPerPixel) public static void Decode(byte[] scanline, int bytesPerScanline, int bytesPerPixel)
{ {
// Sub(x) + Raw(x-bpp) // Sub(x) + Raw(x-bpp)
@ -37,6 +40,7 @@ namespace ImageSharp.Formats
/// <param name="scanline">The scanline to encode</param> /// <param name="scanline">The scanline to encode</param>
/// <param name="result">The filtered scanline result.</param> /// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param> /// <param name="bytesPerPixel">The bytes per pixel.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(byte[] scanline, byte[] result, int bytesPerPixel) public static void Encode(byte[] scanline, byte[] result, int bytesPerPixel)
{ {
// Sub(x) = Raw(x) - Raw(x-bpp) // Sub(x) = Raw(x) - Raw(x-bpp)

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

@ -5,6 +5,8 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System.Runtime.CompilerServices;
/// <summary> /// <summary>
/// The Up filter is just like the Sub filter except that the pixel immediately above the current pixel, /// 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. /// 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="scanline">The scanline to decode</param>
/// <param name="previousScanline">The previous scanline.</param> /// <param name="previousScanline">The previous scanline.</param>
/// <param name="bytesPerScanline">The number of bytes per 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) public static void Decode(byte[] scanline, byte[] previousScanline, int bytesPerScanline)
{ {
// Up(x) + Prior(x) // Up(x) + Prior(x)
@ -39,6 +42,7 @@ namespace ImageSharp.Formats
/// <param name="scanline">The scanline to encode</param> /// <param name="scanline">The scanline to encode</param>
/// <param name="previousScanline">The previous scanline.</param> /// <param name="previousScanline">The previous scanline.</param>
/// <param name="result">The filtered scanline result.</param> /// <param name="result">The filtered scanline result.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result) public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result)
{ {
// Up(x) = Raw(x) - Prior(x) // 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 namespace ImageSharp
{ {
using System;
using System.IO; using System.IO;
using Formats; using Formats;
@ -21,15 +20,34 @@ namespace ImageSharp
/// <typeparam name="TColor">The pixel format.</typeparam> /// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param> /// <param name="stream">The stream to save the image to.</param>
/// <param name="quality">The quality to save the image to 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> /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns> /// <returns>
/// The <see cref="Image{TColor}"/>. /// The <see cref="Image{TColor}"/>.
/// </returns> /// </returns>
public static Image<TColor> 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> 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> /// </remarks>
public class PngDecoder : IImageDecoder 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> /// <summary>
/// Decodes the image from the specified stream to the <see cref="ImageBase{TColor}"/>. /// Decodes the image from the specified stream to the <see cref="ImageBase{TColor}"/>.
/// </summary> /// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam> /// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageBase{TColor}"/> to decode to.</param> /// <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="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> 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.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Runtime.CompilerServices;
using static ComparableExtensions; using static ComparableExtensions;
@ -64,6 +64,11 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private readonly char[] chars = new char[4]; private readonly char[] chars = new char[4];
/// <summary>
/// The decoder options.
/// </summary>
private readonly IPngDecoderOptions options;
/// <summary> /// <summary>
/// Reusable crc for validating chunks. /// Reusable crc for validating chunks.
/// </summary> /// </summary>
@ -120,6 +125,15 @@ namespace ImageSharp.Formats
ColorTypes.Add((int)PngColorType.RgbWithAlpha, new byte[] { 8 }); 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> /// <summary>
/// Gets or sets the png color type /// Gets or sets the png color type
/// </summary> /// </summary>
@ -763,6 +777,11 @@ namespace ImageSharp.Formats
private void ReadTextChunk<TColor>(Image<TColor> image, byte[] data, int length) private void ReadTextChunk<TColor>(Image<TColor> image, byte[] data, int length)
where TColor : struct, IPixel<TColor> where TColor : struct, IPixel<TColor>
{ {
if (this.options.IgnoreMetadata)
{
return;
}
int zeroIndex = 0; int zeroIndex = 0;
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
@ -774,8 +793,8 @@ namespace ImageSharp.Formats
} }
} }
string name = Encoding.Unicode.GetString(data, 0, zeroIndex); string name = this.options.TextEncoding.GetString(data, 0, zeroIndex);
string value = Encoding.Unicode.GetString(data, zeroIndex + 1, length - zeroIndex - 1); string value = this.options.TextEncoding.GetString(data, zeroIndex + 1, length - zeroIndex - 1);
image.MetaData.Properties.Add(new ImageProperty(name, value)); image.MetaData.Properties.Add(new ImageProperty(name, value));
} }
@ -927,12 +946,7 @@ namespace ImageSharp.Formats
private void ReadChunkLength(PngChunk chunk) private void ReadChunkLength(PngChunk chunk)
{ {
int numBytes = this.currentStream.Read(this.chunkLengthBuffer, 0, 4); int numBytes = this.currentStream.Read(this.chunkLengthBuffer, 0, 4);
if (numBytes > 1 && numBytes <= 3) if (numBytes < 4)
{
throw new ImageFormatException("Image stream is not valid!");
}
if (numBytes <= 1)
{ {
chunk.Length = -1; chunk.Length = -1;
return; return;
@ -948,6 +962,7 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
/// <param name="pass">Th current pass index</param> /// <param name="pass">Th current pass index</param>
/// <returns>The <see cref="int"/></returns> /// <returns>The <see cref="int"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int ComputeColumnsAdam7(int pass) private int ComputeColumnsAdam7(int pass)
{ {
int width = this.header.Width; 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 namespace ImageSharp.Formats
{ {
using System;
using System.IO; using System.IO;
using ImageSharp.Quantizers;
/// <summary> /// <summary>
/// Image encoder for writing image data to a stream in png format. /// Image encoder for writing image data to a stream in png format.
/// </summary> /// </summary>
public class PngEncoder : IImageEncoder public class PngEncoder : IImageEncoder
{ {
/// <summary> /// <inheritdoc/>
/// Gets or sets the quality of output for images. public void Encode<TColor>(Image<TColor> image, Stream stream, IEncoderOptions options)
/// </summary> where TColor : struct, IPixel<TColor>
public int Quality { get; set; } {
IPngEncoderOptions pngOptions = PngEncoderOptions.Create(options);
/// <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> this.Encode(image, stream, pngOptions);
/// Gets or sets the transparency threshold. }
/// </summary>
public byte Threshold { get; set; } = 0;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether this instance should write /// Encodes the image to the specified stream from the <see cref="Image{TColor}"/>.
/// gamma information to the stream. The default value is false.
/// </summary> /// </summary>
public bool WriteGamma { get; set; } /// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TColor}"/> to encode from.</param>
/// <inheritdoc/> /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public void Encode<TColor>(Image<TColor> image, Stream stream) /// <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> where TColor : struct, IPixel<TColor>
{ {
PngEncoderCore encoder = new PngEncoderCore PngEncoderCore encode = new PngEncoderCore(options);
{ encode.Encode(image, stream);
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);
} }
} }
} }

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

@ -40,6 +40,11 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private readonly Crc32 crc = new Crc32(); private readonly Crc32 crc = new Crc32();
/// <summary>
/// The options for the encoder.
/// </summary>
private readonly IPngEncoderOptions options;
/// <summary> /// <summary>
/// Contains the raw pixel data from an indexed image. /// Contains the raw pixel data from an indexed image.
/// </summary> /// </summary>
@ -86,44 +91,28 @@ namespace ImageSharp.Formats
private byte[] paeth; private byte[] paeth;
/// <summary> /// <summary>
/// Gets or sets the quality of output for images. /// The quality of output for images.
/// </summary>
public int Quality { get; set; }
/// <summary>
/// Gets or sets the png color type
/// </summary> /// </summary>
public PngColorType PngColorType { get; set; } private int quality;
/// <summary> /// <summary>
/// Gets or sets the compression level 1-9. /// The png color type.
/// <remarks>Defaults to 6.</remarks>
/// </summary> /// </summary>
public int CompressionLevel { get; set; } = 6; private PngColorType pngColorType;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether this instance should write /// The quantizer for reducing the color count.
/// gamma information to the stream. The default value is false.
/// </summary> /// </summary>
public bool WriteGamma { get; set; } private IQuantizer quantizer;
/// <summary> /// <summary>
/// Gets or sets the gamma value, that will be written /// Initializes a new instance of the <see cref="PngEncoderCore"/> class.
/// the the stream, when the <see cref="WriteGamma"/> property
/// is set to true. The default value is 2.2F.
/// </summary> /// </summary>
/// <value>The gamma value of the image.</value> /// <param name="options">The options for the encoder.</param>
public float Gamma { get; set; } = 2.2F; public PngEncoderCore(IPngEncoderOptions options)
{
/// <summary> this.options = options ?? new PngEncoderOptions();
/// 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; }
/// <summary> /// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TColor}"/>. /// 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); stream.Write(this.chunkDataBuffer, 0, 8);
// Ensure that quality can be set but has a fallback. // Ensure that quality can be set but has a fallback.
int quality = this.Quality > 0 ? this.Quality : image.MetaData.Quality; this.quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality;
this.Quality = quality > 0 ? quality.Clamp(1, int.MaxValue) : int.MaxValue; 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. // 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. // Set correct bit depth.
this.bitDepth = this.Quality <= 256 this.bitDepth = this.quality <= 256
? (byte)ImageMaths.GetBitsNeededForColorDepth(this.Quality).Clamp(1, 8) ? (byte)ImageMaths.GetBitsNeededForColorDepth(this.quality).Clamp(1, 8)
: (byte)8; : (byte)8;
// Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk // 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, Width = image.Width,
Height = image.Height, Height = image.Height,
ColorType = (byte)this.PngColorType, ColorType = (byte)this.pngColorType,
BitDepth = this.bitDepth, BitDepth = this.bitDepth,
FilterMethod = 0, // None FilterMethod = 0, // None
CompressionMethod = 0, CompressionMethod = 0,
@ -198,7 +190,7 @@ namespace ImageSharp.Formats
this.WriteHeaderChunk(stream, header); this.WriteHeaderChunk(stream, header);
// Collect the indexed pixel data // Collect the indexed pixel data
if (this.PngColorType == PngColorType.Palette) if (this.pngColorType == PngColorType.Palette)
{ {
this.CollectIndexedBytes(image, stream, header); 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) private byte[] EncodePixelRow<TColor>(PixelAccessor<TColor> pixels, int row, byte[] previousScanline, byte[] rawScanline, byte[] result)
where TColor : struct, IPixel<TColor> where TColor : struct, IPixel<TColor>
{ {
switch (this.PngColorType) switch (this.pngColorType)
{ {
case PngColorType.Palette: case PngColorType.Palette:
Buffer.BlockCopy(this.palettePixelData, row * rawScanline.Length, rawScanline, 0, rawScanline.Length); 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) private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, byte[] result)
{ {
// Palette images don't compress well with adaptive filtering. // 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); NoneFilter.Encode(rawScanline, result);
return result; return result;
@ -436,7 +428,7 @@ namespace ImageSharp.Formats
/// <returns>The <see cref="int"/></returns> /// <returns>The <see cref="int"/></returns>
private int CalculateBytesPerPixel() private int CalculateBytesPerPixel()
{ {
switch (this.PngColorType) switch (this.pngColorType)
{ {
case PngColorType.Grayscale: case PngColorType.Grayscale:
return 1; return 1;
@ -488,18 +480,18 @@ namespace ImageSharp.Formats
private QuantizedImage<TColor> WritePaletteChunk<TColor>(Stream stream, PngHeader header, ImageBase<TColor> image) private QuantizedImage<TColor> WritePaletteChunk<TColor>(Stream stream, PngHeader header, ImageBase<TColor> image)
where TColor : struct, IPixel<TColor> where TColor : struct, IPixel<TColor>
{ {
if (this.Quality > 256) if (this.quality > 256)
{ {
return null; 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. // 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. // Grab the palette and write it to the stream.
TColor[] palette = quantized.Palette; TColor[] palette = quantized.Palette;
@ -524,7 +516,7 @@ namespace ImageSharp.Formats
colorTable[offset + 1] = bytes[1]; colorTable[offset + 1] = bytes[1];
colorTable[offset + 2] = bytes[2]; colorTable[offset + 2] = bytes[2];
if (alpha <= this.Threshold) if (alpha <= this.options.Threshold)
{ {
transparentPixels.Add((byte)offset); transparentPixels.Add((byte)offset);
} }
@ -578,9 +570,9 @@ namespace ImageSharp.Formats
/// <param name="stream">The <see cref="Stream"/> containing image data.</param> /// <param name="stream">The <see cref="Stream"/> containing image data.</param>
private void WriteGammaChunk(Stream stream) 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); byte[] size = BitConverter.GetBytes(gammaValue);
@ -608,7 +600,7 @@ namespace ImageSharp.Formats
int resultLength = bytesPerScanline + 1; int resultLength = bytesPerScanline + 1;
byte[] result = new byte[resultLength]; byte[] result = new byte[resultLength];
if (this.PngColorType != PngColorType.Palette) if (this.pngColorType != PngColorType.Palette)
{ {
this.sub = new byte[resultLength]; this.sub = new byte[resultLength];
this.up = new byte[resultLength]; this.up = new byte[resultLength];
@ -622,7 +614,7 @@ namespace ImageSharp.Formats
try try
{ {
memoryStream = new MemoryStream(); 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++) 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> /// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageBase{TColor}"/> to decode to.</param> /// <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="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>; 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> /// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TColor}"/> to encode from.</param> /// <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="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>; where TColor : struct, IPixel<TColor>;
} }
} }

149
src/ImageSharp/Image.cs

@ -8,6 +8,8 @@ namespace ImageSharp
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using Formats;
/// <summary> /// <summary>
/// Represents an image. Each pixel is a made up four 8-bit components red, green, blue, and alpha /// 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. /// 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> /// <summary>
/// Initializes a new instance of the <see cref="Image"/> class. /// Initializes a new instance of the <see cref="Image"/> class.
/// </summary> /// </summary>
@ -39,12 +68,57 @@ namespace ImageSharp
/// The configuration providing initialization code which allows extending the library. /// The configuration providing initialization code which allows extending the library.
/// </param> /// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="stream"/> is null.</exception> /// <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)
: base(stream, 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 #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> /// <summary>
/// Initializes a new instance of the <see cref="Image"/> class. /// Initializes a new instance of the <see cref="Image"/> class.
/// </summary> /// </summary>
@ -55,8 +129,26 @@ namespace ImageSharp
/// The configuration providing initialization code which allows extending the library. /// The configuration providing initialization code which allows extending the library.
/// </param> /// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="filePath"/> is null.</exception> /// <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)
: base(filePath, 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 #endif
@ -67,12 +159,57 @@ namespace ImageSharp
/// <param name="bytes"> /// <param name="bytes">
/// The byte array containing image information. /// The byte array containing image information.
/// </param> /// </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"> /// <param name="configuration">
/// The configuration providing initialization code which allows extending the library. /// The configuration providing initialization code which allows extending the library.
/// </param> /// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="bytes"/> is null.</exception> /// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="bytes"/> is null.</exception>
public Image(byte[] bytes, Configuration configuration = null) public Image(byte[] bytes, IDecoderOptions options, Configuration configuration)
: base(bytes, configuration) : base(bytes, options, configuration)
{ {
} }

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

@ -46,6 +46,33 @@ namespace ImageSharp
this.CurrentImageFormat = this.Configuration.ImageFormats.First(); 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> /// <summary>
/// Initializes a new instance of the <see cref="Image{TColor}"/> class. /// Initializes a new instance of the <see cref="Image{TColor}"/> class.
/// </summary> /// </summary>
@ -56,14 +83,59 @@ namespace ImageSharp
/// The configuration providing initialization code which allows extending the library. /// The configuration providing initialization code which allows extending the library.
/// </param> /// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="stream"/> is null.</exception> /// <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) : base(configuration)
{ {
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
this.Load(stream); this.Load(stream, options);
} }
#if !NETSTANDARD1_1 #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> /// <summary>
/// Initializes a new instance of the <see cref="Image{TColor}"/> class. /// Initializes a new instance of the <see cref="Image{TColor}"/> class.
/// </summary> /// </summary>
@ -74,17 +146,62 @@ namespace ImageSharp
/// The configuration providing initialization code which allows extending the library. /// The configuration providing initialization code which allows extending the library.
/// </param> /// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="filePath"/> is null.</exception> /// <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) : base(configuration)
{ {
Guard.NotNull(filePath, nameof(filePath)); Guard.NotNull(filePath, nameof(filePath));
using (var fs = File.OpenRead(filePath)) using (var fs = File.OpenRead(filePath))
{ {
this.Load(fs); this.Load(fs, options);
} }
} }
#endif #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> /// <summary>
/// Initializes a new instance of the <see cref="Image{TColor}"/> class. /// Initializes a new instance of the <see cref="Image{TColor}"/> class.
/// </summary> /// </summary>
@ -95,14 +212,32 @@ namespace ImageSharp
/// The configuration providing initialization code which allows extending the library. /// The configuration providing initialization code which allows extending the library.
/// </param> /// </param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="bytes"/> is null.</exception> /// <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) : base(configuration)
{ {
Guard.NotNull(bytes, nameof(bytes)); Guard.NotNull(bytes, nameof(bytes));
using (MemoryStream stream = new MemoryStream(bytes, false)) 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> /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>The <see cref="Image{TColor}"/></returns> /// <returns>The <see cref="Image{TColor}"/></returns>
public Image<TColor> Save(Stream stream) 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)); Guard.NotNull(stream, nameof(stream));
this.CurrentImageFormat.Encoder.Encode(this, stream); this.CurrentImageFormat.Encoder.Encode(this, stream, options);
return this; return this;
} }
@ -211,13 +358,24 @@ namespace ImageSharp
/// </summary> /// </summary>
/// <param name="stream">The stream to save the image to.</param> /// <param name="stream">The stream to save the image to.</param>
/// <param name="format">The format to save the image as.</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> /// <returns>The <see cref="Image{TColor}"/></returns>
public Image<TColor> Save(Stream stream, IImageFormat format) 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(stream, nameof(stream));
Guard.NotNull(format, nameof(format)); Guard.NotNull(format, nameof(format));
format.Encoder.Encode(this, stream); format.Encoder.Encode(this, stream, options);
return this; return this;
} }
@ -231,10 +389,25 @@ namespace ImageSharp
/// The <see cref="Image{TColor}"/>. /// The <see cref="Image{TColor}"/>.
/// </returns> /// </returns>
public Image<TColor> Save(Stream stream, IImageEncoder encoder) 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(stream, nameof(stream));
Guard.NotNull(encoder, nameof(encoder)); Guard.NotNull(encoder, nameof(encoder));
encoder.Encode(this, stream); encoder.Encode(this, stream, options);
// Reset to the start of the stream. // Reset to the start of the stream.
if (stream.CanSeek) if (stream.CanSeek)
@ -253,6 +426,18 @@ namespace ImageSharp
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception> /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>The <see cref="Image{TColor}"/></returns> /// <returns>The <see cref="Image{TColor}"/></returns>
public Image<TColor> Save(string filePath) 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('.'); string ext = Path.GetExtension(filePath).Trim('.');
IImageFormat format = this.Configuration.ImageFormats.SingleOrDefault(f => f.SupportedExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase)); 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> /// <exception cref="System.ArgumentNullException">Thrown if the format is null.</exception>
/// <returns>The <see cref="Image{TColor}"/></returns> /// <returns>The <see cref="Image{TColor}"/></returns>
public Image<TColor> Save(string filePath, IImageFormat format) 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)); Guard.NotNull(format, nameof(format));
using (FileStream fs = File.Create(filePath)) using (FileStream fs = File.Create(filePath))
@ -288,6 +486,19 @@ namespace ImageSharp
/// <exception cref="System.ArgumentNullException">Thrown if the encoder is null.</exception> /// <exception cref="System.ArgumentNullException">Thrown if the encoder is null.</exception>
/// <returns>The <see cref="Image{TColor}"/></returns> /// <returns>The <see cref="Image{TColor}"/></returns>
public Image<TColor> Save(string filePath, IImageEncoder encoder) 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)); Guard.NotNull(encoder, nameof(encoder));
using (FileStream fs = File.Create(filePath)) using (FileStream fs = File.Create(filePath))
@ -397,10 +608,11 @@ namespace ImageSharp
/// Loads the image from the given stream. /// Loads the image from the given stream.
/// </summary> /// </summary>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="options">The options for the decoder.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
private void Load(Stream stream) private void Load(Stream stream, IDecoderOptions options)
{ {
if (!this.Configuration.ImageFormats.Any()) if (!this.Configuration.ImageFormats.Any())
{ {
@ -414,7 +626,7 @@ namespace ImageSharp
if (stream.CanSeek) if (stream.CanSeek)
{ {
if (this.Decode(stream)) if (this.Decode(stream, options))
{ {
return; return;
} }
@ -427,7 +639,7 @@ namespace ImageSharp
stream.CopyTo(ms); stream.CopyTo(ms);
ms.Position = 0; ms.Position = 0;
if (this.Decode(ms)) if (this.Decode(ms, options))
{ {
return; return;
} }
@ -449,10 +661,11 @@ namespace ImageSharp
/// Decodes the image stream to the current image. /// Decodes the image stream to the current image.
/// </summary> /// </summary>
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
/// <param name="options">The options for the decoder.</param>
/// <returns> /// <returns>
/// The <see cref="bool"/>. /// The <see cref="bool"/>.
/// </returns> /// </returns>
private bool Decode(Stream stream) private bool Decode(Stream stream, IDecoderOptions options)
{ {
int maxHeaderSize = this.Configuration.MaxHeaderSize; int maxHeaderSize = this.Configuration.MaxHeaderSize;
if (maxHeaderSize <= 0) if (maxHeaderSize <= 0)
@ -479,7 +692,7 @@ namespace ImageSharp
return false; return false;
} }
format.Decoder.Decode(this, stream); format.Decoder.Decode(this, stream, options);
this.CurrentImageFormat = format; this.CurrentImageFormat = format;
return true; 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")) using (FileStream output = File.OpenWrite($"{path}/Simple.png"))
{ {
image image
.BackgroundColor(Color.Blue)
.FillPolygon(Color.HotPink, simplePath, new GraphicsOptions(true)) .FillPolygon(Color.HotPink, simplePath, new GraphicsOptions(true))
.Save(output); .Save(output);
} }
@ -45,7 +44,7 @@ namespace ImageSharp.Tests.Drawing
Assert.Equal(Color.HotPink, sourcePixels[50, 50]); 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.Car),
// TestFile.Create(TestImages.Bmp.Neg_height), // Perf: Enable for local testing only // TestFile.Create(TestImages.Bmp.Neg_height), // Perf: Enable for local testing only
TestFile.Create(TestImages.Png.Splash), 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.Powerpoint), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Png.Blur), // 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 // TestFile.Create(TestImages.Png.Indexed), // Perf: Enable for local testing only

8
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. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
@ -7,11 +7,9 @@ using ImageSharp.Formats;
namespace ImageSharp.Tests namespace ImageSharp.Tests
{ {
using System.IO;
using Xunit; using Xunit;
public class BitmapTests : FileTestBase public class BmpEncoderTests : FileTestBase
{ {
public static readonly TheoryData<BmpBitsPerPixel> BitsPerPixel public static readonly TheoryData<BmpBitsPerPixel> BitsPerPixel
= new TheoryData<BmpBitsPerPixel> = new TheoryData<BmpBitsPerPixel>
@ -31,7 +29,7 @@ namespace ImageSharp.Tests
string filename = file.GetFileNameWithoutExtension(bitsPerPixel); string filename = file.GetFileNameWithoutExtension(bitsPerPixel);
using (Image image = file.CreateImage()) 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; byte[] data;
using (Image<TColor> image = provider.GetImage()) using (Image<TColor> image = provider.GetImage())
{ {
JpegEncoder encoder = new JpegEncoder() { Subsample = subsample, Quality = quality }; JpegEncoder encoder = new JpegEncoder();
JpegEncoderOptions options = new JpegEncoderOptions { Subsample = subsample, Quality = quality };
data = new byte[65536]; data = new byte[65536];
using (MemoryStream ms = new MemoryStream(data)) using (MemoryStream ms = new MemoryStream(data))
{ {
image.Save(ms, encoder); image.Save(ms, encoder, options);
} }
} }
@ -90,7 +91,7 @@ namespace ImageSharp.Tests
ms.Seek(0, SeekOrigin.Begin); ms.Seek(0, SeekOrigin.Begin);
Image<TColor> mirror = provider.Factory.CreateImage(1, 1); Image<TColor> mirror = provider.Factory.CreateImage(1, 1);
using (JpegDecoderCore decoder = new JpegDecoderCore()) using (JpegDecoderCore decoder = new JpegDecoderCore(null))
{ {
decoder.Decode(mirror, ms, true); decoder.Decode(mirror, ms, true);
@ -120,5 +121,37 @@ namespace ImageSharp.Tests
Assert.Equal(72, image.MetaData.VerticalResolution); 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);
}
}
} }
} }

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

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

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

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

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. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
@ -10,11 +10,9 @@ namespace ImageSharp.Tests
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Formats;
using Xunit; using Xunit;
public class PngTests : FileTestBase public class PngEncoderTests : FileTestBase
{ {
[Fact] [Fact]
public void ImageCanSaveIndexedPng() public void ImageCanSaveIndexedPng()

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

@ -59,7 +59,7 @@ namespace ImageSharp.Tests
ArgumentNullException ex = Assert.Throws<ArgumentNullException>( 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); 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> /// <summary>
/// Gets the correct path to the formats directory. /// Gets the correct path to the formats directory.
/// </summary> /// </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 SplashInterlaced = "Png/splash-interlaced.png";
public const string Interlaced = "Png/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 Filter0 = "Png/filter0.png";
public const string Filter1 = "Png/filter1.png"; public const string Filter1 = "Png/filter1.png";
public const string Filter2 = "Png/filter2.png"; public const string Filter2 = "Png/filter2.png";
public const string Filter3 = "Png/filter3.png"; public const string Filter3 = "Png/filter3.png";
public const string Filter4 = "Png/filter4.png"; public const string Filter4 = "Png/filter4.png";
// filter changing per scanline // Filter changing per scanline
public const string FilterVar = "Png/filterVar.png"; public const string FilterVar = "Png/filterVar.png";
// Chunk length of 1 by end marker // Odd chunk lengths
public const string ChunkLength = "Png/chunklength1.png"; public const string ChunkLength1 = "Png/chunklength1.png";
public const string ChunkLength2 = "Png/chunklength2.png";
} }
public static class Jpeg 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="image">The image instance</param>
/// <param name="extension">The requested extension</param> /// <param name="extension">The requested extension</param>
/// <param name="encoder">Optional encoder</param> /// <param name="encoder">Optional encoder</param>
public void SaveTestOutputFile<TColor>(Image<TColor> image, string extension = null, IImageEncoder encoder = null) /// <param name="options">Optional encoder options</param>
public void SaveTestOutputFile<TColor>(Image<TColor> image, string extension = null, IImageEncoder encoder = null, IEncoderOptions options = null)
where TColor : struct, IPixel<TColor> where TColor : struct, IPixel<TColor>
{ {
string path = this.GetTestOutputFileName(extension); string path = this.GetTestOutputFileName(extension);
@ -86,7 +87,7 @@ namespace ImageSharp.Tests
using (var stream = File.OpenWrite(path)) using (var stream = File.OpenWrite(path))
{ {
image.Save(stream, encoder); image.Save(stream, encoder, options);
} }
} }

Loading…
Cancel
Save