Browse Source

Merge pull request #11 from SixLabors/master

Changes from master
af/merge-core
Sergio Pedri 7 years ago
committed by GitHub
parent
commit
5a63bc5695
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .gitattributes
  2. 2
      Directory.Build.targets
  3. 53
      src/ImageSharp.Drawing/Common/Extensions/GraphicsOptionsExtensions.cs
  4. 6
      src/ImageSharp.Drawing/Primitives/ShapeRegion.cs
  5. 61
      src/ImageSharp.Drawing/Processing/BrushApplicator.cs
  6. 41
      src/ImageSharp.Drawing/Processing/EllipticGradientBrush.cs
  7. 40
      src/ImageSharp.Drawing/Processing/Extensions/DrawImageExtensions.cs
  8. 2
      src/ImageSharp.Drawing/Processing/Extensions/DrawPathCollectionExtensions.cs
  9. 2
      src/ImageSharp.Drawing/Processing/Extensions/DrawPathExtensions.cs
  10. 2
      src/ImageSharp.Drawing/Processing/Extensions/DrawPolygonExtensions.cs
  11. 2
      src/ImageSharp.Drawing/Processing/Extensions/DrawRectangleExtensions.cs
  12. 8
      src/ImageSharp.Drawing/Processing/Extensions/DrawTextExtensions.cs
  13. 2
      src/ImageSharp.Drawing/Processing/Extensions/FillPathBuilderExtensions.cs
  14. 2
      src/ImageSharp.Drawing/Processing/Extensions/FillPathCollectionExtensions.cs
  15. 2
      src/ImageSharp.Drawing/Processing/Extensions/FillPathExtensions.cs
  16. 12
      src/ImageSharp.Drawing/Processing/Extensions/FillRegionExtensions.cs
  17. 49
      src/ImageSharp.Drawing/Processing/GradientBrush.cs
  18. 16
      src/ImageSharp.Drawing/Processing/IBrush.cs
  19. 57
      src/ImageSharp.Drawing/Processing/ImageBrush.cs
  20. 41
      src/ImageSharp.Drawing/Processing/LinearGradientBrush.cs
  21. 34
      src/ImageSharp.Drawing/Processing/PathGradientBrush.cs
  22. 56
      src/ImageSharp.Drawing/Processing/PatternBrush.cs
  23. 4
      src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs
  24. 13
      src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor{TPixel}.cs
  25. 4
      src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor.cs
  26. 6
      src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor{TPixel}.cs
  27. 8
      src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor{TPixel}.cs
  28. 30
      src/ImageSharp.Drawing/Processing/RadialGradientBrush.cs
  29. 57
      src/ImageSharp.Drawing/Processing/RecolorBrush.cs
  30. 76
      src/ImageSharp.Drawing/Processing/SolidBrush.cs
  31. 180
      src/ImageSharp.Drawing/Processing/TextGraphicsOptions.cs
  32. 29
      src/ImageSharp.Drawing/Utils/NumberUtils.cs
  33. 2
      src/ImageSharp.Drawing/Utils/QuickSort.cs
  34. 35
      src/ImageSharp/Color/Color.cs
  35. 17
      src/ImageSharp/Common/Helpers/ImageMaths.cs
  36. 5
      src/ImageSharp/Configuration.cs
  37. 6
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  38. 17
      src/ImageSharp/Formats/Png/PngChunk.cs
  39. 64
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  40. 4
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  41. 33
      src/ImageSharp/Formats/Png/PngThrowHelper.cs
  42. 11
      src/ImageSharp/Formats/Png/Zlib/Adler32.cs
  43. 6
      src/ImageSharp/Formats/Png/Zlib/Crc32.cs
  44. 35
      src/ImageSharp/Formats/Png/Zlib/DeflateThrowHelper.cs
  45. 295
      src/ImageSharp/Formats/Png/Zlib/Deflater.cs
  46. 151
      src/ImageSharp/Formats/Png/Zlib/DeflaterConstants.cs
  47. 859
      src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs
  48. 949
      src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs
  49. 153
      src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs
  50. 179
      src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs
  51. 7
      src/ImageSharp/Formats/Png/Zlib/README.md
  52. 83
      src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs
  53. 6
      src/ImageSharp/Formats/README.md
  54. 12
      src/ImageSharp/Formats/Tga/ITgaDecoderOptions.cs
  55. 21
      src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs
  56. BIN
      src/ImageSharp/Formats/Tga/TGA_Specification.pdf
  57. 31
      src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs
  58. 21
      src/ImageSharp/Formats/Tga/TgaCompression.cs
  59. 19
      src/ImageSharp/Formats/Tga/TgaConfigurationModule.cs
  60. 25
      src/ImageSharp/Formats/Tga/TgaConstants.cs
  61. 34
      src/ImageSharp/Formats/Tga/TgaDecoder.cs
  62. 588
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  63. 34
      src/ImageSharp/Formats/Tga/TgaEncoder.cs
  64. 348
      src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
  65. 147
      src/ImageSharp/Formats/Tga/TgaFileHeader.cs
  66. 33
      src/ImageSharp/Formats/Tga/TgaFormat.cs
  67. 40
      src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs
  68. 48
      src/ImageSharp/Formats/Tga/TgaImageType.cs
  69. 49
      src/ImageSharp/Formats/Tga/TgaImageTypeExtensions.cs
  70. 35
      src/ImageSharp/Formats/Tga/TgaMetadata.cs
  71. 31
      src/ImageSharp/Formats/Tga/TgaThrowHelper.cs
  72. 178
      src/ImageSharp/GraphicsOptions.cs
  73. 27
      src/ImageSharp/Memory/MemoryAllocatorExtensions.cs
  74. 4
      src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt
  75. 8
      src/ImageSharp/Primitives/DenseMatrix{T}.cs
  76. 12
      src/ImageSharp/Processing/Extensions/BackgroundColorExtensions.cs
  77. 18
      src/ImageSharp/Processing/Extensions/GlowExtensions.cs
  78. 18
      src/ImageSharp/Processing/Extensions/VignetteExtensions.cs
  79. 4
      src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs
  80. 11
      src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs
  81. 4
      src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs
  82. 14
      src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs
  83. 10
      src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs
  84. 42
      tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs
  85. 8
      tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs
  86. 54
      tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs
  87. 10
      tests/ImageSharp.Benchmarks/Drawing/DrawText.cs
  88. 14
      tests/ImageSharp.Benchmarks/Drawing/DrawTextOutline.cs
  89. 1
      tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
  90. 11
      tests/ImageSharp.Tests/ConfigurationTests.cs
  91. 2
      tests/ImageSharp.Tests/Drawing/DrawImageTests.cs
  92. 22
      tests/ImageSharp.Tests/Drawing/DrawLinesTests.cs
  93. 2
      tests/ImageSharp.Tests/Drawing/DrawPolygonTests.cs
  94. 45
      tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs
  95. 2
      tests/ImageSharp.Tests/Drawing/FillPolygonTests.cs
  96. 22
      tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs
  97. 24
      tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs
  98. 17
      tests/ImageSharp.Tests/Drawing/Paths/DrawPathCollection.cs
  99. 17
      tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs
  100. 17
      tests/ImageSharp.Tests/Drawing/Paths/FillPathCollection.cs

1
.gitattributes

@ -68,6 +68,7 @@
*.gif binary
*.jpg binary
*.png binary
*.tga binary
*.ttf binary
*.snk binary
# diff as plain text

2
Directory.Build.targets

@ -24,7 +24,7 @@
<ItemGroup>
<PackageReference Update="BenchmarkDotNet" Version="0.11.5" />
<PackageReference Update="Colourful" Version="2.0.2" />
<PackageReference Update="Magick.NET-Q16-AnyCPU" Version="7.12.0" />
<PackageReference Update="Magick.NET-Q16-AnyCPU" Version="7.14.4" />
<PackageReference Update="Microsoft.Net.Compilers.Toolset" Version="3.3.1" />
<PackageReference Update="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Update="Moq" Version="4.10.0" />

53
src/ImageSharp.Drawing/Common/Extensions/GraphicsOptionsExtensions.cs

@ -0,0 +1,53 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Extensions methods fpor the <see cref="GraphicsOptions"/> class.
/// </summary>
internal static class GraphicsOptionsExtensions
{
/// <summary>
/// Evaluates if a given SOURCE color can completely replace a BACKDROP color given the current blending and composition settings.
/// </summary>
/// <param name="options">The graphics options.</param>
/// <param name="color">The source color.</param>
/// <returns>true if the color can be considered opaque</returns>
/// <remarks>
/// Blending and composition is an expensive operation, in some cases, like
/// filling with a solid color, the blending can be avoided by a plain color replacement.
/// This method can be useful for such processors to select the fast path.
/// </remarks>
public static bool IsOpaqueColorWithoutBlending(this GraphicsOptions options, Color color)
{
if (options.ColorBlendingMode != PixelColorBlendingMode.Normal)
{
return false;
}
if (options.AlphaCompositionMode != PixelAlphaCompositionMode.SrcOver
&& options.AlphaCompositionMode != PixelAlphaCompositionMode.Src)
{
return false;
}
const float Opaque = 1F;
if (options.BlendPercentage != Opaque)
{
return false;
}
if (((Vector4)color).W != Opaque)
{
return false;
}
return true;
}
}
}

6
src/ImageSharp.Drawing/Primitives/ShapeRegion.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Primitives
using (IMemoryOwner<PointF> tempBuffer = configuration.MemoryAllocator.Allocate<PointF>(buffer.Length))
{
Span<PointF> innerBuffer = tempBuffer.GetSpan();
Span<PointF> innerBuffer = tempBuffer.Memory.Span;
int count = this.Shape.FindIntersections(start, end, innerBuffer);
for (int i = 0; i < count; i++)
@ -61,4 +61,4 @@ namespace SixLabors.ImageSharp.Primitives
}
}
}
}
}

61
src/ImageSharp.Drawing/Processing/BrushApplicator.cs

@ -1,68 +1,85 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// primitive that converts a point in to a color for discovering the fill color based on an implementation
/// A primitive that converts a point into a color for discovering the fill color based on an implementation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <seealso cref="System.IDisposable" />
public abstract class BrushApplicator<TPixel> : IDisposable // disposable will be required if/when there is an ImageBrush
/// <seealso cref="IDisposable" />
public abstract class BrushApplicator<TPixel> : IDisposable
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="BrushApplicator{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration instance to use when performing operations.</param>
/// <param name="options">The graphics options.</param>
/// <param name="target">The target.</param>
/// <param name="options">The options.</param>
internal BrushApplicator(ImageFrame<TPixel> target, GraphicsOptions options)
internal BrushApplicator(Configuration configuration, GraphicsOptions options, ImageFrame<TPixel> target)
{
this.Configuration = configuration;
this.Target = target;
this.Options = options;
this.Blender = PixelOperations<TPixel>.Instance.GetPixelBlender(options);
}
/// <summary>
/// Gets the blender
/// Gets the configuration instance to use when performing operations.
/// </summary>
protected Configuration Configuration { get; }
/// <summary>
/// Gets the pixel blender.
/// </summary>
internal PixelBlender<TPixel> Blender { get; }
/// <summary>
/// Gets the destination
/// Gets the target image.
/// </summary>
protected ImageFrame<TPixel> Target { get; }
/// <summary>
/// Gets the blend percentage
/// Gets thegraphics options
/// </summary>
protected GraphicsOptions Options { get; }
/// <summary>
/// Gets the color for a single pixel.
/// Gets the overlay pixel at the specified position.
/// </summary>
/// <param name="x">The x coordinate.</param>
/// <param name="y">The y coordinate.</param>
/// <returns>The a <typeparamref name="TPixel"/> that should be applied to the pixel.</returns>
/// <param name="x">The x-coordinate.</param>
/// <param name="y">The y-coordinate.</param>
/// <returns>The <see typeparam="TPixel"/> at the specified position.</returns>
internal abstract TPixel this[int x, int y] { get; }
/// <inheritdoc/>
public abstract void Dispose();
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
/// <param name="disposing">Whether to dispose managed and unmanaged objects.</param>
protected virtual void Dispose(bool disposing)
{
}
/// <summary>
/// Applies the opacity weighting for each pixel in a scanline to the target based on the pattern contained in the brush.
/// </summary>
/// <param name="scanline">The a collection of opacity values between 0 and 1 to be merged with the brushed color value before being applied to the target.</param>
/// <param name="x">The x position in the target pixel space that the start of the scanline data corresponds to.</param>
/// <param name="y">The y position in the target pixel space that whole scanline corresponds to.</param>
/// <param name="scanline">A collection of opacity values between 0 and 1 to be merged with the brushed color value before being applied to the target.</param>
/// <param name="x">The x-position in the target pixel space that the start of the scanline data corresponds to.</param>
/// <param name="y">The y-position in the target pixel space that whole scanline corresponds to.</param>
/// <remarks>scanlineBuffer will be > scanlineWidth but provide and offset in case we want to share a larger buffer across runs.</remarks>
internal virtual void Apply(Span<float> scanline, int x, int y)
{
@ -71,8 +88,8 @@ namespace SixLabors.ImageSharp.Processing
using (IMemoryOwner<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length))
using (IMemoryOwner<TPixel> overlay = memoryAllocator.Allocate<TPixel>(scanline.Length))
{
Span<float> amountSpan = amountBuffer.GetSpan();
Span<TPixel> overlaySpan = overlay.GetSpan();
Span<float> amountSpan = amountBuffer.Memory.Span;
Span<TPixel> overlaySpan = overlay.Memory.Span;
for (int i = 0; i < scanline.Length; i++)
{
@ -89,7 +106,7 @@ namespace SixLabors.ImageSharp.Processing
}
Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
this.Blender.Blend(this.Target.Configuration, destinationRow, destinationRow, overlaySpan, amountSpan);
this.Blender.Blend(this.Configuration, destinationRow, destinationRow, overlaySpan, amountSpan);
}
}
}

41
src/ImageSharp.Drawing/Processing/EllipticGradientBrush.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@ -47,12 +47,14 @@ namespace SixLabors.ImageSharp.Processing
/// <inheritdoc />
public override BrushApplicator<TPixel> CreateApplicator<TPixel>(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
RectangleF region,
GraphicsOptions options) =>
RectangleF region) =>
new RadialGradientBrushApplicator<TPixel>(
source,
configuration,
options,
source,
this.center,
this.referenceAxisEnd,
this.axisRatio,
@ -86,24 +88,26 @@ namespace SixLabors.ImageSharp.Processing
/// <summary>
/// Initializes a new instance of the <see cref="RadialGradientBrushApplicator{TPixel}" /> class.
/// </summary>
/// <param name="target">The target image</param>
/// <param name="options">The options</param>
/// <param name="center">Center of the ellipse</param>
/// <param name="configuration">The configuration instance to use when performing operations.</param>
/// <param name="options">The graphics options.</param>
/// <param name="target">The target image.</param>
/// <param name="center">Center of the ellipse.</param>
/// <param name="referenceAxisEnd">Point on one angular points of the ellipse.</param>
/// <param name="axisRatio">
/// Ratio of the axis length's. Used to determine the length of the second axis,
/// the first is defined by <see cref="center"/> and <see cref="referenceAxisEnd"/>.</param>
/// <param name="colorStops">Definition of colors</param>
/// <param name="colorStops">Definition of colors.</param>
/// <param name="repetitionMode">Defines how the gradient colors are repeated.</param>
public RadialGradientBrushApplicator(
ImageFrame<TPixel> target,
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> target,
PointF center,
PointF referenceAxisEnd,
float axisRatio,
ColorStop[] colorStops,
GradientRepetitionMode repetitionMode)
: base(target, options, colorStops, repetitionMode)
: base(configuration, options, target, colorStops, repetitionMode)
{
this.center = center;
this.referenceAxisEnd = referenceAxisEnd;
@ -122,11 +126,6 @@ namespace SixLabors.ImageSharp.Processing
this.cosRotation = (float)Math.Cos(this.rotation);
}
/// <inheritdoc />
public override void Dispose()
{
}
/// <inheritdoc />
protected override float PositionOnGradient(float xt, float yt)
{
@ -139,16 +138,13 @@ namespace SixLabors.ImageSharp.Processing
float xSquared = x * x;
float ySquared = y * y;
var inBoundaryChecker = (xSquared / this.referenceRadiusSquared)
+ (ySquared / this.secondRadiusSquared);
return inBoundaryChecker;
return (xSquared / this.referenceRadiusSquared) + (ySquared / this.secondRadiusSquared);
}
private float AngleBetween(PointF junction, PointF a, PointF b)
{
var vA = a - junction;
var vB = b - junction;
PointF vA = a - junction;
PointF vB = b - junction;
return MathF.Atan2(vB.Y, vB.X) - MathF.Atan2(vA.Y, vA.X);
}
@ -156,6 +152,7 @@ namespace SixLabors.ImageSharp.Processing
PointF p1,
PointF p2)
{
// TODO: Can we not just use Vector2 distance here?
float dX = p1.X - p2.X;
float dXsquared = dX * dX;
@ -165,4 +162,4 @@ namespace SixLabors.ImageSharp.Processing
}
}
}
}
}

40
src/ImageSharp.Drawing/Processing/Extensions/DrawImageExtensions.cs

@ -22,14 +22,17 @@ namespace SixLabors.ImageSharp.Processing
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
Image image,
float opacity) =>
source.ApplyProcessor(
float opacity)
{
var options = new GraphicsOptions();
return source.ApplyProcessor(
new DrawImageProcessor(
image,
Point.Empty,
GraphicsOptions.Default.ColorBlendingMode,
GraphicsOptions.Default.AlphaCompositionMode,
opacity));
image,
Point.Empty,
options.ColorBlendingMode,
options.AlphaCompositionMode,
opacity));
}
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
@ -49,7 +52,7 @@ namespace SixLabors.ImageSharp.Processing
image,
Point.Empty,
colorBlending,
GraphicsOptions.Default.AlphaCompositionMode,
new GraphicsOptions().AlphaCompositionMode,
opacity));
/// <summary>
@ -100,14 +103,17 @@ namespace SixLabors.ImageSharp.Processing
this IImageProcessingContext source,
Image image,
Point location,
float opacity) =>
source.ApplyProcessor(
float opacity)
{
var options = new GraphicsOptions();
return source.ApplyProcessor(
new DrawImageProcessor(
image,
location,
GraphicsOptions.Default.ColorBlendingMode,
GraphicsOptions.Default.AlphaCompositionMode,
opacity));
image,
location,
options.ColorBlendingMode,
options.AlphaCompositionMode,
opacity));
}
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
@ -129,7 +135,7 @@ namespace SixLabors.ImageSharp.Processing
image,
location,
colorBlending,
GraphicsOptions.Default.AlphaCompositionMode,
new GraphicsOptions().AlphaCompositionMode,
opacity));
/// <summary>
@ -172,4 +178,4 @@ namespace SixLabors.ImageSharp.Processing
options.AlphaCompositionMode,
options.BlendPercentage));
}
}
}

2
src/ImageSharp.Drawing/Processing/Extensions/DrawPathCollectionExtensions.cs

@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Processing
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext
Draw(this IImageProcessingContext source, IPen pen, IPathCollection paths) =>
source.Draw(GraphicsOptions.Default, pen, paths);
source.Draw(new GraphicsOptions(), pen, paths);
/// <summary>
/// Draws the outline of the polygon with the provided brush at the provided thickness.

2
src/ImageSharp.Drawing/Processing/Extensions/DrawPathExtensions.cs

@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="path">The path.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Draw(this IImageProcessingContext source, IPen pen, IPath path) =>
source.Draw(GraphicsOptions.Default, pen, path);
source.Draw(new GraphicsOptions(), pen, path);
/// <summary>
/// Draws the outline of the polygon with the provided brush at the provided thickness.

2
src/ImageSharp.Drawing/Processing/Extensions/DrawPolygonExtensions.cs

@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.Processing
this IImageProcessingContext source,
IPen pen,
params PointF[] points) =>
source.Draw(GraphicsOptions.Default, pen, new Polygon(new LinearLineSegment(points)));
source.Draw(new GraphicsOptions(), pen, new Polygon(new LinearLineSegment(points)));
/// <summary>
/// Draws the provided Points as a closed Linear Polygon with the provided Pen.

2
src/ImageSharp.Drawing/Processing/Extensions/DrawRectangleExtensions.cs

@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="shape">The shape.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Draw(this IImageProcessingContext source, IPen pen, RectangleF shape) =>
source.Draw(GraphicsOptions.Default, pen, shape);
source.Draw(new GraphicsOptions(), pen, shape);
/// <summary>
/// Draws the outline of the rectangle with the provided brush at the provided thickness.

8
src/ImageSharp.Drawing/Processing/Extensions/DrawTextExtensions.cs

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Processing
Font font,
Color color,
PointF location) =>
source.DrawText(TextGraphicsOptions.Default, text, font, color, location);
source.DrawText(new TextGraphicsOptions(), text, font, color, location);
/// <summary>
/// Draws the text onto the the image filled via the brush.
@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Processing
Font font,
IBrush brush,
PointF location) =>
source.DrawText(TextGraphicsOptions.Default, text, font, brush, location);
source.DrawText(new TextGraphicsOptions(), text, font, brush, location);
/// <summary>
/// Draws the text onto the the image filled via the brush.
@ -109,7 +109,7 @@ namespace SixLabors.ImageSharp.Processing
Font font,
IPen pen,
PointF location) =>
source.DrawText(TextGraphicsOptions.Default, text, font, pen, location);
source.DrawText(new TextGraphicsOptions(), text, font, pen, location);
/// <summary>
/// Draws the text onto the the image outlined via the pen.
@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Processing
IBrush brush,
IPen pen,
PointF location) =>
source.DrawText(TextGraphicsOptions.Default, text, font, brush, pen, location);
source.DrawText(new TextGraphicsOptions(), text, font, brush, pen, location);
/// <summary>
/// Draws the text using the default resolution of <value>72dpi</value> onto the the image filled via the brush then outlined via the pen.

2
src/ImageSharp.Drawing/Processing/Extensions/FillPathBuilderExtensions.cs

@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Processing
this IImageProcessingContext source,
IBrush brush,
Action<PathBuilder> path) =>
source.Fill(GraphicsOptions.Default, brush, path);
source.Fill(new GraphicsOptions(), brush, path);
/// <summary>
/// Flood fills the image in the shape of the provided polygon with the specified brush.

2
src/ImageSharp.Drawing/Processing/Extensions/FillPathCollectionExtensions.cs

@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Processing
this IImageProcessingContext source,
IBrush brush,
IPathCollection paths) =>
source.Fill(GraphicsOptions.Default, brush, paths);
source.Fill(new GraphicsOptions(), brush, paths);
/// <summary>
/// Flood fills the image in the shape of the provided polygon with the specified brush.

2
src/ImageSharp.Drawing/Processing/Extensions/FillPathExtensions.cs

@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="path">The path.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Fill(this IImageProcessingContext source, IBrush brush, IPath path) =>
source.Fill(GraphicsOptions.Default, brush, new ShapeRegion(path));
source.Fill(new GraphicsOptions(), brush, new ShapeRegion(path));
/// <summary>
/// Flood fills the image in the shape of the provided polygon with the specified brush..

12
src/ImageSharp.Drawing/Processing/Extensions/FillRegionExtensions.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Primitives;
@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="brush">The details how to fill the region of interest.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Fill(this IImageProcessingContext source, IBrush brush) =>
source.Fill(GraphicsOptions.Default, brush);
source.Fill(new GraphicsOptions(), brush);
/// <summary>
/// Flood fills the image with the specified color.
@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="region">The region.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext Fill(this IImageProcessingContext source, IBrush brush, Region region) =>
source.Fill(GraphicsOptions.Default, brush, region);
source.Fill(new GraphicsOptions(), brush, region);
/// <summary>
/// Flood fills the image with in the region with the specified color.
@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Processing
GraphicsOptions options,
IBrush brush,
Region region) =>
source.ApplyProcessor(new FillRegionProcessor(brush, region, options));
source.ApplyProcessor(new FillRegionProcessor(options, brush, region));
/// <summary>
/// Flood fills the image with the specified brush.
@ -90,6 +90,6 @@ namespace SixLabors.ImageSharp.Processing
this IImageProcessingContext source,
GraphicsOptions options,
IBrush brush) =>
source.ApplyProcessor(new FillProcessor(brush, options));
source.ApplyProcessor(new FillProcessor(options, brush));
}
}
}

49
src/ImageSharp.Drawing/Processing/GradientBrush.cs

@ -1,11 +1,9 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.PixelFormats.PixelBlenders;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing
@ -38,9 +36,10 @@ namespace SixLabors.ImageSharp.Processing
/// <inheritdoc />
public abstract BrushApplicator<TPixel> CreateApplicator<TPixel>(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
RectangleF region,
GraphicsOptions options)
RectangleF region)
where TPixel : struct, IPixel<TPixel>;
/// <summary>
@ -58,27 +57,24 @@ namespace SixLabors.ImageSharp.Processing
/// <summary>
/// Initializes a new instance of the <see cref="GradientBrushApplicator{TPixel}"/> class.
/// </summary>
/// <param name="target">The target.</param>
/// <param name="options">The options.</param>
/// <param name="configuration">The configuration instance to use when performing operations.</param>
/// <param name="options">The graphics options.</param>
/// <param name="target">The target image.</param>
/// <param name="colorStops">An array of color stops sorted by their position.</param>
/// <param name="repetitionMode">Defines if and how the gradient should be repeated.</param>
protected GradientBrushApplicator(
ImageFrame<TPixel> target,
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> target,
ColorStop[] colorStops,
GradientRepetitionMode repetitionMode)
: base(target, options)
: base(configuration, options, target)
{
this.colorStops = colorStops; // TODO: requires colorStops to be sorted by position - should that be checked?
this.repetitionMode = repetitionMode;
}
/// <summary>
/// Base implementation of the indexer for gradients
/// (follows the facade pattern, using abstract methods)
/// </summary>
/// <param name="x">X coordinate of the Pixel.</param>
/// <param name="y">Y coordinate of the Pixel.</param>
/// <inheritdoc/>
internal override TPixel this[int x, int y]
{
get
@ -92,10 +88,10 @@ namespace SixLabors.ImageSharp.Processing
// onLocalGradient = Math.Min(0, Math.Max(1, onLocalGradient));
break;
case GradientRepetitionMode.Repeat:
positionOnCompleteGradient = positionOnCompleteGradient % 1;
positionOnCompleteGradient %= 1;
break;
case GradientRepetitionMode.Reflect:
positionOnCompleteGradient = positionOnCompleteGradient % 2;
positionOnCompleteGradient %= 2;
if (positionOnCompleteGradient > 1)
{
positionOnCompleteGradient = 2 - positionOnCompleteGradient;
@ -121,19 +117,8 @@ namespace SixLabors.ImageSharp.Processing
}
else
{
var fromAsVector = from.Color.ToVector4();
var toAsVector = to.Color.ToVector4();
float onLocalGradient = (positionOnCompleteGradient - from.Ratio) / (to.Ratio - from.Ratio);
// TODO: this should be changeble for different gradienting functions
Vector4 result = PorterDuffFunctions.NormalSrcOver(
fromAsVector,
toAsVector,
onLocalGradient);
TPixel resultColor = default;
resultColor.FromVector4(result);
return resultColor;
return new Color(Vector4.Lerp((Vector4)from.Color, (Vector4)to.Color, onLocalGradient)).ToPixel<TPixel>();
}
}
}
@ -142,8 +127,8 @@ namespace SixLabors.ImageSharp.Processing
/// calculates the position on the gradient for a given point.
/// This method is abstract as it's content depends on the shape of the gradient.
/// </summary>
/// <param name="x">The x coordinate of the point</param>
/// <param name="y">The y coordinate of the point</param>
/// <param name="x">The x-coordinate of the point.</param>
/// <param name="y">The y-coordinate of the point.</param>
/// <returns>
/// The position the given point has on the gradient.
/// The position is not bound to the [0..1] interval.
@ -176,4 +161,4 @@ namespace SixLabors.ImageSharp.Processing
}
}
}
}
}

16
src/ImageSharp.Drawing/Processing/IBrush.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
@ -19,20 +19,22 @@ namespace SixLabors.ImageSharp.Processing
/// Creates the applicator for this brush.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="configuration">The configuration instance to use when performing operations.</param>
/// <param name="options">The graphic options.</param>
/// <param name="source">The source image.</param>
/// <param name="region">The region the brush will be applied to.</param>
/// <param name="options">The graphic options</param>
/// <returns>
/// The brush applicator for this brush
/// The <see cref="BrushApplicator{TPixel}"/> for this brush.
/// </returns>
/// <remarks>
/// The <paramref name="region" /> when being applied to things like shapes would usually be the
/// bounding box of the shape not necessarily the bounds of the whole image
/// bounding box of the shape not necessarily the bounds of the whole image.
/// </remarks>
BrushApplicator<TPixel> CreateApplicator<TPixel>(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
RectangleF region,
GraphicsOptions options)
RectangleF region)
where TPixel : struct, IPixel<TPixel>;
}
}
}

57
src/ImageSharp.Drawing/Processing/ImageBrush.cs

@ -1,11 +1,10 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
@ -32,19 +31,20 @@ namespace SixLabors.ImageSharp.Processing
/// <inheritdoc />
public BrushApplicator<TPixel> CreateApplicator<TPixel>(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
RectangleF region,
GraphicsOptions options)
RectangleF region)
where TPixel : struct, IPixel<TPixel>
{
if (this.image is Image<TPixel> specificImage)
{
return new ImageBrushApplicator<TPixel>(source, specificImage, region, options, false);
return new ImageBrushApplicator<TPixel>(configuration, options, source, specificImage, region, false);
}
specificImage = this.image.CloneAs<TPixel>();
return new ImageBrushApplicator<TPixel>(source, specificImage, region, options, true);
return new ImageBrushApplicator<TPixel>(configuration, options, source, specificImage, region, true);
}
/// <summary>
@ -79,21 +79,25 @@ namespace SixLabors.ImageSharp.Processing
/// </summary>
private readonly int offsetX;
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="ImageBrushApplicator{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration instance to use when performing operations.</param>
/// <param name="options">The graphics options.</param>
/// <param name="target">The target image.</param>
/// <param name="image">The image.</param>
/// <param name="region">The region.</param>
/// <param name="options">The options</param>
/// <param name="shouldDisposeImage">Whether to dispose the image on disposal of the applicator.</param>
public ImageBrushApplicator(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> target,
Image<TPixel> image,
RectangleF region,
GraphicsOptions options,
bool shouldDisposeImage)
: base(target, options)
: base(configuration, options, target)
{
this.sourceImage = image;
this.sourceFrame = image.Frames.RootFrame;
@ -104,14 +108,7 @@ namespace SixLabors.ImageSharp.Processing
this.offsetX = (int)MathF.Max(MathF.Floor(region.Left), 0);
}
/// <summary>
/// Gets the color for a single pixel.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>
/// The color
/// </returns>
/// <inheritdoc/>
internal override TPixel this[int x, int y]
{
get
@ -123,14 +120,21 @@ namespace SixLabors.ImageSharp.Processing
}
/// <inheritdoc />
public override void Dispose()
protected override void Dispose(bool disposing)
{
if (this.shouldDisposeImage)
if (this.isDisposed)
{
return;
}
if (disposing && this.shouldDisposeImage)
{
this.sourceImage?.Dispose();
this.sourceImage = null;
this.sourceFrame = null;
}
this.sourceImage = null;
this.sourceFrame = null;
this.isDisposed = true;
}
/// <inheritdoc />
@ -140,8 +144,8 @@ namespace SixLabors.ImageSharp.Processing
using (IMemoryOwner<float> amountBuffer = this.Target.MemoryAllocator.Allocate<float>(scanline.Length))
using (IMemoryOwner<TPixel> overlay = this.Target.MemoryAllocator.Allocate<TPixel>(scanline.Length))
{
Span<float> amountSpan = amountBuffer.GetSpan();
Span<TPixel> overlaySpan = overlay.GetSpan();
Span<float> amountSpan = amountBuffer.Memory.Span;
Span<TPixel> overlaySpan = overlay.Memory.Span;
int sourceY = (y - this.offsetY) % this.yLength;
int offsetX = x - this.offsetX;
@ -152,13 +156,12 @@ namespace SixLabors.ImageSharp.Processing
amountSpan[i] = scanline[i] * this.Options.BlendPercentage;
int sourceX = (i + offsetX) % this.xLength;
TPixel pixel = sourceRow[sourceX];
overlaySpan[i] = pixel;
overlaySpan[i] = sourceRow[sourceX];
}
Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
this.Blender.Blend(
this.sourceFrame.Configuration,
this.Configuration,
destinationRow,
destinationRow,
overlaySpan,
@ -167,4 +170,4 @@ namespace SixLabors.ImageSharp.Processing
}
}
}
}
}

41
src/ImageSharp.Drawing/Processing/LinearGradientBrush.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@ -39,16 +39,18 @@ namespace SixLabors.ImageSharp.Processing
/// <inheritdoc />
public override BrushApplicator<TPixel> CreateApplicator<TPixel>(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
RectangleF region,
GraphicsOptions options) =>
RectangleF region) =>
new LinearGradientBrushApplicator<TPixel>(
configuration,
options,
source,
this.p1,
this.p2,
this.ColorStops,
this.RepetitionMode,
options);
this.RepetitionMode);
/// <summary>
/// The linear gradient brush applicator.
@ -93,20 +95,22 @@ namespace SixLabors.ImageSharp.Processing
/// <summary>
/// Initializes a new instance of the <see cref="LinearGradientBrushApplicator{TPixel}" /> class.
/// </summary>
/// <param name="source">The source</param>
/// <param name="start">start point of the gradient</param>
/// <param name="end">end point of the gradient</param>
/// <param name="colorStops">tuple list of colors and their respective position between 0 and 1 on the line</param>
/// <param name="repetitionMode">defines how the gradient colors are repeated.</param>
/// <param name="options">the graphics options</param>
/// <param name="configuration">The configuration instance to use when performing operations.</param>
/// <param name="options">The graphics options.</param>
/// <param name="source">The source image.</param>
/// <param name="start">The start point of the gradient.</param>
/// <param name="end">The end point of the gradient.</param>
/// <param name="colorStops">A tuple list of colors and their respective position between 0 and 1 on the line.</param>
/// <param name="repetitionMode">Defines how the gradient colors are repeated.</param>
public LinearGradientBrushApplicator(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
PointF start,
PointF end,
ColorStop[] colorStops,
GradientRepetitionMode repetitionMode,
GraphicsOptions options)
: base(source, options, colorStops, repetitionMode)
GradientRepetitionMode repetitionMode)
: base(configuration, options, source, colorStops, repetitionMode)
{
this.start = start;
this.end = end;
@ -148,14 +152,9 @@ namespace SixLabors.ImageSharp.Processing
float distance = MathF.Sqrt(MathF.Pow(x4 - this.start.X, 2) + MathF.Pow(y4 - this.start.Y, 2));
// get and return ratio
float ratio = distance / this.length;
return ratio;
return distance / this.length;
}
}
public override void Dispose()
{
}
}
}
}
}

34
src/ImageSharp.Drawing/Processing/PathGradientBrush.cs

@ -83,12 +83,13 @@ namespace SixLabors.ImageSharp.Processing
/// <inheritdoc />
public BrushApplicator<TPixel> CreateApplicator<TPixel>(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
RectangleF region,
GraphicsOptions options)
RectangleF region)
where TPixel : struct, IPixel<TPixel>
{
return new PathGradientBrushApplicator<TPixel>(source, this.edges, this.centerColor, options);
return new PathGradientBrushApplicator<TPixel>(configuration, options, source, this.edges, this.centerColor);
}
private static Color CalculateCenterColor(Color[] colors)
@ -105,7 +106,7 @@ namespace SixLabors.ImageSharp.Processing
"One or more color is needed to construct a path gradient brush.");
}
return new Color(colors.Select(c => c.ToVector4()).Aggregate((p1, p2) => p1 + p2) / colors.Length);
return new Color(colors.Select(c => (Vector4)c).Aggregate((p1, p2) => p1 + p2) / colors.Length);
}
private static float DistanceBetween(PointF p1, PointF p2) => ((Vector2)(p2 - p1)).Length();
@ -141,10 +142,10 @@ namespace SixLabors.ImageSharp.Processing
Vector2[] points = path.LineSegments.SelectMany(s => s.Flatten()).Select(p => (Vector2)p).ToArray();
this.Start = points.First();
this.StartColor = startColor.ToVector4();
this.StartColor = (Vector4)startColor;
this.End = points.Last();
this.EndColor = endColor.ToVector4();
this.EndColor = (Vector4)endColor;
this.length = DistanceBetween(this.End, this.Start);
this.buffer = new PointF[this.path.MaxIntersections];
@ -199,23 +200,25 @@ namespace SixLabors.ImageSharp.Processing
/// <summary>
/// Initializes a new instance of the <see cref="PathGradientBrushApplicator{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration instance to use when performing operations.</param>
/// <param name="options">The graphics options.</param>
/// <param name="source">The source image.</param>
/// <param name="edges">Edges of the polygon.</param>
/// <param name="centerColor">Color at the center of the gradient area to which the other colors converge.</param>
/// <param name="options">The options.</param>
public PathGradientBrushApplicator(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
IList<Edge> edges,
Color centerColor,
GraphicsOptions options)
: base(source, options)
Color centerColor)
: base(configuration, options, source)
{
this.edges = edges;
PointF[] points = edges.Select(s => s.Start).ToArray();
this.center = points.Aggregate((p1, p2) => p1 + p2) / edges.Count;
this.centerColor = centerColor.ToVector4();
this.centerColor = (Vector4)centerColor;
this.maxDistance = points.Select(p => (Vector2)(p - this.center)).Select(d => d.Length()).Max();
}
@ -232,7 +235,7 @@ namespace SixLabors.ImageSharp.Processing
return new Color(this.centerColor).ToPixel<TPixel>();
}
Vector2 direction = Vector2.Normalize(point - this.center);
var direction = Vector2.Normalize(point - this.center);
PointF end = point + (PointF)(direction * this.maxDistance);
@ -250,7 +253,7 @@ namespace SixLabors.ImageSharp.Processing
float length = DistanceBetween(intersection, this.center);
float ratio = length > 0 ? DistanceBetween(intersection, point) / length : 0;
Vector4 color = Vector4.Lerp(edgeColor, this.centerColor, ratio);
var color = Vector4.Lerp(edgeColor, this.centerColor, ratio);
return new Color(color).ToPixel<TPixel>();
}
@ -277,11 +280,6 @@ namespace SixLabors.ImageSharp.Processing
return closest;
}
/// <inheritdoc />
public override void Dispose()
{
}
}
}
}

56
src/ImageSharp.Drawing/Processing/PatternBrush.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@ -61,8 +61,8 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="pattern">The pattern.</param>
internal PatternBrush(Color foreColor, Color backColor, in DenseMatrix<bool> pattern)
{
var foreColorVector = foreColor.ToVector4();
var backColorVector = backColor.ToVector4();
var foreColorVector = (Vector4)foreColor;
var backColorVector = (Vector4)backColor;
this.pattern = new DenseMatrix<Color>(pattern.Columns, pattern.Rows);
this.patternVector = new DenseMatrix<Vector4>(pattern.Columns, pattern.Rows);
for (int i = 0; i < pattern.Data.Length; i++)
@ -92,14 +92,16 @@ namespace SixLabors.ImageSharp.Processing
/// <inheritdoc />
public BrushApplicator<TPixel> CreateApplicator<TPixel>(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
RectangleF region,
GraphicsOptions options)
RectangleF region)
where TPixel : struct, IPixel<TPixel> =>
new PatternBrushApplicator<TPixel>(
configuration,
options,
source,
this.pattern.ToPixelMatrix<TPixel>(source.Configuration),
options);
this.pattern.ToPixelMatrix<TPixel>(configuration));
/// <summary>
/// The pattern brush applicator.
@ -115,41 +117,33 @@ namespace SixLabors.ImageSharp.Processing
/// <summary>
/// Initializes a new instance of the <see cref="PatternBrushApplicator{TPixel}" /> class.
/// </summary>
/// <param name="configuration">The configuration instance to use when performing operations.</param>
/// <param name="options">The graphics options.</param>
/// <param name="source">The source image.</param>
/// <param name="pattern">The pattern.</param>
/// <param name="options">The options</param>
public PatternBrushApplicator(ImageFrame<TPixel> source, in DenseMatrix<TPixel> pattern, GraphicsOptions options)
: base(source, options)
public PatternBrushApplicator(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
in DenseMatrix<TPixel> pattern)
: base(configuration, options, source)
{
this.pattern = pattern;
}
/// <summary>
/// Gets the color for a single pixel.
/// </summary>#
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>
/// The Color.
/// </returns>
/// <inheritdoc/>
internal override TPixel this[int x, int y]
{
get
{
x = x % this.pattern.Columns;
y = y % this.pattern.Rows;
x %= this.pattern.Columns;
y %= this.pattern.Rows;
// 2d array index at row/column
return this.pattern[y, x];
}
}
/// <inheritdoc />
public override void Dispose()
{
// noop
}
/// <inheritdoc />
internal override void Apply(Span<float> scanline, int x, int y)
{
@ -159,12 +153,12 @@ namespace SixLabors.ImageSharp.Processing
using (IMemoryOwner<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length))
using (IMemoryOwner<TPixel> overlay = memoryAllocator.Allocate<TPixel>(scanline.Length))
{
Span<float> amountSpan = amountBuffer.GetSpan();
Span<TPixel> overlaySpan = overlay.GetSpan();
Span<float> amountSpan = amountBuffer.Memory.Span;
Span<TPixel> overlaySpan = overlay.Memory.Span;
for (int i = 0; i < scanline.Length; i++)
{
amountSpan[i] = (scanline[i] * this.Options.BlendPercentage).Clamp(0, 1);
amountSpan[i] = NumberUtils.ClampFloat(scanline[i] * this.Options.BlendPercentage, 0, 1F);
int patternX = (x + i) % this.pattern.Columns;
overlaySpan[i] = this.pattern[patternY, patternX];
@ -172,7 +166,7 @@ namespace SixLabors.ImageSharp.Processing
Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
this.Blender.Blend(
this.Target.Configuration,
this.Configuration,
destinationRow,
destinationRow,
overlaySpan,
@ -181,4 +175,4 @@ namespace SixLabors.ImageSharp.Processing
}
}
}
}
}

4
src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs

@ -15,9 +15,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
/// <summary>
/// Initializes a new instance of the <see cref="FillProcessor"/> class.
/// </summary>
/// <param name="brush">The brush to use for filling.</param>
/// <param name="options">The <see cref="GraphicsOptions"/> defining how to blend the brush pixels over the image pixels.</param>
public FillProcessor(IBrush brush, GraphicsOptions options)
/// <param name="brush">The brush to use for filling.</param>
public FillProcessor(GraphicsOptions options, IBrush brush)
{
this.Brush = brush;
this.Options = options;

13
src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor{TPixel}.cs

@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
int width = maxX - minX;
Rectangle workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY);
var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY);
IBrush brush = this.definition.Brush;
GraphicsOptions options = this.definition.Options;
@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration)
.MultiplyMinimumPixelsPerTask(4);
var colorPixel = solidBrush.Color.ToPixel<TPixel>();
TPixel colorPixel = solidBrush.Color.ToPixel<TPixel>();
ParallelHelper.IterateRows(
workingRect,
@ -84,11 +84,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
using (IMemoryOwner<float> amount = source.MemoryAllocator.Allocate<float>(width))
using (BrushApplicator<TPixel> applicator = brush.CreateApplicator(
configuration,
options,
source,
sourceRectangle,
options))
sourceRectangle))
{
amount.GetSpan().Fill(1f);
amount.Memory.Span.Fill(1f);
ParallelHelper.IterateRows(
workingRect,
@ -100,7 +101,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
int offsetY = y - startY;
int offsetX = minX - startX;
applicator.Apply(amount.GetSpan(), offsetX, offsetY);
applicator.Apply(amount.Memory.Span, offsetX, offsetY);
}
});
}

4
src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor.cs

@ -16,10 +16,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
/// <summary>
/// Initializes a new instance of the <see cref="FillRegionProcessor" /> class.
/// </summary>
/// <param name="options">The graphics options.</param>
/// <param name="brush">The details how to fill the region of interest.</param>
/// <param name="region">The region of interest to be filled.</param>
/// <param name="options">The configuration options.</param>
public FillRegionProcessor(IBrush brush, Region region, GraphicsOptions options)
public FillRegionProcessor(GraphicsOptions options, IBrush brush, Region region)
{
this.Region = region;
this.Brush = brush;

6
src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor{TPixel}.cs

@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
}
}
using (BrushApplicator<TPixel> applicator = brush.CreateApplicator(source, rect, options))
using (BrushApplicator<TPixel> applicator = brush.CreateApplicator(configuration, options, source, rect))
{
int scanlineWidth = maxX - minX;
using (IMemoryOwner<float> bBuffer = source.MemoryAllocator.Allocate<float>(maxIntersections))
@ -81,8 +81,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
float subpixelFraction = 1f / subpixelCount;
float subpixelFractionPoint = subpixelFraction / subpixelCount;
Span<float> buffer = bBuffer.GetSpan();
Span<float> scanline = bScanline.GetSpan();
Span<float> buffer = bBuffer.Memory.Span;
Span<float> scanline = bScanline.Memory.Span;
bool isSolidBrushWithoutBlending = this.IsSolidBrushWithoutBlending(out SolidBrush solidBrush);
TPixel solidBrushColor = isSolidBrushWithoutBlending ? solidBrush.Color.ToPixel<TPixel>() : default;

8
src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor{TPixel}.cs

@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Text
VerticalAlignment = this.Options.VerticalAlignment
};
this.textRenderer = new CachingGlyphRenderer(this.Source.GetMemoryAllocator(), this.Text.Length, this.Pen, this.Brush != null);
this.textRenderer = new CachingGlyphRenderer(this.Configuration.MemoryAllocator, this.Text.Length, this.Pen, this.Brush != null);
this.textRenderer.Options = (GraphicsOptions)this.Options;
var renderer = new TextRenderer(this.textRenderer);
renderer.RenderText(this.Text, style);
@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Text
{
if (operations?.Count > 0)
{
using (BrushApplicator<TPixel> app = brush.CreateApplicator(source, this.SourceRectangle, this.textRenderer.Options))
using (BrushApplicator<TPixel> app = brush.CreateApplicator(this.Configuration, this.textRenderer.Options, source, this.SourceRectangle))
{
foreach (DrawingOperation operation in operations)
{
@ -326,6 +326,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Text
{
float subpixelFraction = 1f / subpixelCount;
float subpixelFractionPoint = subpixelFraction / subpixelCount;
Span<PointF> intersectionSpan = rowIntersectionBuffer.Memory.Span;
Span<float> buffer = bufferBacking.Memory.Span;
for (int y = 0; y <= size.Height; y++)
{
@ -337,8 +339,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Text
{
var start = new PointF(path.Bounds.Left - 1, subPixel);
var end = new PointF(path.Bounds.Right + 1, subPixel);
Span<PointF> intersectionSpan = rowIntersectionBuffer.GetSpan();
Span<float> buffer = bufferBacking.GetSpan();
int pointsFound = path.FindIntersections(start, end, intersectionSpan);
if (pointsFound == 0)

30
src/ImageSharp.Drawing/Processing/RadialGradientBrush.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@ -9,7 +9,7 @@ using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// A Circular Gradient Brush, defined by center point and radius.
/// A radial gradient brush, defined by center point and radius.
/// </summary>
public sealed class RadialGradientBrush : GradientBrush
{
@ -35,12 +35,14 @@ namespace SixLabors.ImageSharp.Processing
/// <inheritdoc />
public override BrushApplicator<TPixel> CreateApplicator<TPixel>(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
RectangleF region,
GraphicsOptions options) =>
RectangleF region) =>
new RadialGradientBrushApplicator<TPixel>(
source,
configuration,
options,
source,
this.center,
this.radius,
this.ColorStops,
@ -57,30 +59,27 @@ namespace SixLabors.ImageSharp.Processing
/// <summary>
/// Initializes a new instance of the <see cref="RadialGradientBrushApplicator{TPixel}" /> class.
/// </summary>
/// <param name="target">The target image</param>
/// <param name="options">The options.</param>
/// <param name="configuration">The configuration instance to use when performing operations.</param>
/// <param name="options">The graphics options.</param>
/// <param name="target">The target image.</param>
/// <param name="center">Center point of the gradient.</param>
/// <param name="radius">Radius of the gradient.</param>
/// <param name="colorStops">Definition of colors.</param>
/// <param name="repetitionMode">How the colors are repeated beyond the first gradient.</param>
public RadialGradientBrushApplicator(
ImageFrame<TPixel> target,
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> target,
PointF center,
float radius,
ColorStop[] colorStops,
GradientRepetitionMode repetitionMode)
: base(target, options, colorStops, repetitionMode)
: base(configuration, options, target, colorStops, repetitionMode)
{
this.center = center;
this.radius = radius;
}
/// <inheritdoc cref="Dispose" />
public override void Dispose()
{
}
/// <summary>
/// As this is a circular gradient, the position on the gradient is based on
/// the distance of the point to the center.
@ -90,6 +89,7 @@ namespace SixLabors.ImageSharp.Processing
/// <returns>the position on the color gradient.</returns>
protected override float PositionOnGradient(float x, float y)
{
// TODO: Can this not use Vector2 distance?
float distance = MathF.Sqrt(MathF.Pow(this.center.X - x, 2) + MathF.Pow(this.center.Y - y, 2));
return distance / this.radius;
}
@ -101,4 +101,4 @@ namespace SixLabors.ImageSharp.Processing
}
}
}
}
}

57
src/ImageSharp.Drawing/Processing/RecolorBrush.cs

@ -1,11 +1,10 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives;
@ -38,9 +37,6 @@ namespace SixLabors.ImageSharp.Processing
/// <summary>
/// Gets the source color.
/// </summary>
/// <value>
/// The color of the source.
/// </value>
public Color SourceColor { get; }
/// <summary>
@ -50,17 +46,19 @@ namespace SixLabors.ImageSharp.Processing
/// <inheritdoc />
public BrushApplicator<TPixel> CreateApplicator<TPixel>(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
RectangleF region,
GraphicsOptions options)
RectangleF region)
where TPixel : struct, IPixel<TPixel>
{
return new RecolorBrushApplicator<TPixel>(
configuration,
options,
source,
this.SourceColor.ToPixel<TPixel>(),
this.TargetColor.ToPixel<TPixel>(),
this.Threshold,
options);
this.Threshold);
}
/// <summary>
@ -74,11 +72,6 @@ namespace SixLabors.ImageSharp.Processing
/// </summary>
private readonly Vector4 sourceColor;
/// <summary>
/// The target color.
/// </summary>
private readonly Vector4 targetColor;
/// <summary>
/// The threshold.
/// </summary>
@ -89,16 +82,22 @@ namespace SixLabors.ImageSharp.Processing
/// <summary>
/// Initializes a new instance of the <see cref="RecolorBrushApplicator{TPixel}" /> class.
/// </summary>
/// <param name="configuration">The configuration instance to use when performing operations.</param>
/// <param name="options">The options</param>
/// <param name="source">The source image.</param>
/// <param name="sourceColor">Color of the source.</param>
/// <param name="targetColor">Color of the target.</param>
/// <param name="threshold">The threshold .</param>
/// <param name="options">The options</param>
public RecolorBrushApplicator(ImageFrame<TPixel> source, TPixel sourceColor, TPixel targetColor, float threshold, GraphicsOptions options)
: base(source, options)
public RecolorBrushApplicator(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
TPixel sourceColor,
TPixel targetColor,
float threshold)
: base(configuration, options, source)
{
this.sourceColor = sourceColor.ToVector4();
this.targetColor = targetColor.ToVector4();
this.targetColorPixel = targetColor;
// Lets hack a min max extremes for a color space by letting the IPackedPixel clamp our values to something in the correct spaces :)
@ -109,14 +108,7 @@ namespace SixLabors.ImageSharp.Processing
this.threshold = Vector4.DistanceSquared(maxColor.ToVector4(), minColor.ToVector4()) * threshold;
}
/// <summary>
/// Gets the color for a single pixel.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>
/// The color
/// </returns>
/// <inheritdoc />
internal override TPixel this[int x, int y]
{
get
@ -138,11 +130,6 @@ namespace SixLabors.ImageSharp.Processing
}
}
/// <inheritdoc />
public override void Dispose()
{
}
/// <inheritdoc />
internal override void Apply(Span<float> scanline, int x, int y)
{
@ -151,8 +138,8 @@ namespace SixLabors.ImageSharp.Processing
using (IMemoryOwner<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length))
using (IMemoryOwner<TPixel> overlay = memoryAllocator.Allocate<TPixel>(scanline.Length))
{
Span<float> amountSpan = amountBuffer.GetSpan();
Span<TPixel> overlaySpan = overlay.GetSpan();
Span<float> amountSpan = amountBuffer.Memory.Span;
Span<TPixel> overlaySpan = overlay.Memory.Span;
for (int i = 0; i < scanline.Length; i++)
{
@ -167,7 +154,7 @@ namespace SixLabors.ImageSharp.Processing
Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
this.Blender.Blend(
this.Target.Configuration,
this.Configuration,
destinationRow,
destinationRow,
overlaySpan,
@ -176,4 +163,4 @@ namespace SixLabors.ImageSharp.Processing
}
}
}
}
}

76
src/ImageSharp.Drawing/Processing/SolidBrush.cs

@ -1,11 +1,10 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives;
@ -17,33 +16,29 @@ namespace SixLabors.ImageSharp.Processing
/// </summary>
public class SolidBrush : IBrush
{
/// <summary>
/// The color to paint.
/// </summary>
private readonly Color color;
/// <summary>
/// Initializes a new instance of the <see cref="SolidBrush"/> class.
/// </summary>
/// <param name="color">The color.</param>
public SolidBrush(Color color)
{
this.color = color;
this.Color = color;
}
/// <summary>
/// Gets the color.
/// </summary>
/// <value>
/// The color.
/// </value>
public Color Color => this.color;
public Color Color { get; }
/// <inheritdoc />
public BrushApplicator<TPixel> CreateApplicator<TPixel>(ImageFrame<TPixel> source, RectangleF region, GraphicsOptions options)
public BrushApplicator<TPixel> CreateApplicator<TPixel>(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
RectangleF region)
where TPixel : struct, IPixel<TPixel>
{
return new SolidBrushApplicator<TPixel>(source, this.color.ToPixel<TPixel>(), options);
return new SolidBrushApplicator<TPixel>(configuration, options, source, this.Color.ToPixel<TPixel>());
}
/// <summary>
@ -52,38 +47,49 @@ namespace SixLabors.ImageSharp.Processing
private class SolidBrushApplicator<TPixel> : BrushApplicator<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="SolidBrushApplicator{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration instance to use when performing operations.</param>
/// <param name="options">The graphics options.</param>
/// <param name="source">The source image.</param>
/// <param name="color">The color.</param>
/// <param name="options">The options</param>
public SolidBrushApplicator(ImageFrame<TPixel> source, TPixel color, GraphicsOptions options)
: base(source, options)
public SolidBrushApplicator(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> source,
TPixel color)
: base(configuration, options, source)
{
this.Colors = source.MemoryAllocator.Allocate<TPixel>(source.Width);
this.Colors.GetSpan().Fill(color);
this.Colors.Memory.Span.Fill(color);
}
/// <summary>
/// Gets the colors.
/// </summary>
protected IMemoryOwner<TPixel> Colors { get; }
protected IMemoryOwner<TPixel> Colors { get; private set; }
/// <summary>
/// Gets the color for a single pixel.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>
/// The color
/// </returns>
internal override TPixel this[int x, int y] => this.Colors.GetSpan()[x];
/// <inheritdoc/>
internal override TPixel this[int x, int y] => this.Colors.Memory.Span[x];
/// <inheritdoc />
public override void Dispose()
protected override void Dispose(bool disposing)
{
this.Colors.Dispose();
if (this.isDisposed)
{
return;
}
if (disposing)
{
this.Colors.Dispose();
}
this.Colors = null;
this.isDisposed = true;
}
/// <inheritdoc />
@ -102,17 +108,17 @@ namespace SixLabors.ImageSharp.Processing
}
MemoryAllocator memoryAllocator = this.Target.MemoryAllocator;
Configuration configuration = this.Target.Configuration;
Configuration configuration = this.Configuration;
if (this.Options.BlendPercentage == 1f)
{
this.Blender.Blend(configuration, destinationRow, destinationRow, this.Colors.GetSpan(), scanline);
this.Blender.Blend(configuration, destinationRow, destinationRow, this.Colors.Memory.Span, scanline);
}
else
{
using (IMemoryOwner<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length))
{
Span<float> amountSpan = amountBuffer.GetSpan();
Span<float> amountSpan = amountBuffer.Memory.Span;
for (int i = 0; i < scanline.Length; i++)
{
@ -123,11 +129,11 @@ namespace SixLabors.ImageSharp.Processing
configuration,
destinationRow,
destinationRow,
this.Colors.GetSpan(),
this.Colors.Memory.Span,
amountSpan);
}
}
}
}
}
}
}

180
src/ImageSharp.Drawing/Processing/TextGraphicsOptions.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.Fonts;
@ -9,120 +9,169 @@ namespace SixLabors.ImageSharp.Processing
/// <summary>
/// Options for influencing the drawing functions.
/// </summary>
public struct TextGraphicsOptions
public class TextGraphicsOptions : IDeepCloneable<TextGraphicsOptions>
{
private const int DefaultTextDpi = 72;
private int antialiasSubpixelDepth = 16;
private float blendPercentage = 1F;
private float tabWidth = 4F;
private float dpiX = 72F;
private float dpiY = 72F;
/// <summary>
/// Represents the default <see cref="TextGraphicsOptions"/>.
/// Initializes a new instance of the <see cref="TextGraphicsOptions"/> class.
/// </summary>
public static readonly TextGraphicsOptions Default = new TextGraphicsOptions(true);
private float? blendPercentage;
private int? antialiasSubpixelDepth;
private bool? antialias;
private bool? applyKerning;
private float? tabWidth;
private float? dpiX;
private float? dpiY;
private HorizontalAlignment? horizontalAlignment;
private VerticalAlignment? verticalAlignment;
public TextGraphicsOptions()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="TextGraphicsOptions" /> struct.
/// </summary>
/// <param name="enableAntialiasing">If set to <c>true</c> [enable antialiasing].</param>
public TextGraphicsOptions(bool enableAntialiasing)
private TextGraphicsOptions(TextGraphicsOptions source)
{
this.applyKerning = true;
this.tabWidth = 4;
this.WrapTextWidth = 0;
this.horizontalAlignment = HorizontalAlignment.Left;
this.verticalAlignment = VerticalAlignment.Top;
this.antialiasSubpixelDepth = 16;
this.ColorBlendingMode = PixelColorBlendingMode.Normal;
this.AlphaCompositionMode = PixelAlphaCompositionMode.SrcOver;
this.blendPercentage = 1;
this.antialias = enableAntialiasing;
this.dpiX = DefaultTextDpi;
this.dpiY = DefaultTextDpi;
this.AlphaCompositionMode = source.AlphaCompositionMode;
this.Antialias = source.Antialias;
this.AntialiasSubpixelDepth = source.AntialiasSubpixelDepth;
this.ApplyKerning = source.ApplyKerning;
this.BlendPercentage = source.BlendPercentage;
this.ColorBlendingMode = source.ColorBlendingMode;
this.DpiX = source.DpiX;
this.DpiY = source.DpiY;
this.HorizontalAlignment = source.HorizontalAlignment;
this.TabWidth = source.TabWidth;
this.WrapTextWidth = source.WrapTextWidth;
this.VerticalAlignment = source.VerticalAlignment;
}
/// <summary>
/// Gets or sets a value indicating whether antialiasing should be applied.
/// Defaults to true.
/// </summary>
public bool Antialias { get => this.antialias ?? true; set => this.antialias = value; }
public bool Antialias { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating the number of subpixels to use while rendering with antialiasing enabled.
/// </summary>
public int AntialiasSubpixelDepth { get => this.antialiasSubpixelDepth ?? 16; set => this.antialiasSubpixelDepth = value; }
public int AntialiasSubpixelDepth
{
get
{
return this.antialiasSubpixelDepth;
}
set
{
Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.AntialiasSubpixelDepth));
this.antialiasSubpixelDepth = value;
}
}
/// <summary>
/// Gets or sets a value indicating the blending percentage to apply to the drawing operation
/// Gets or sets a value indicating the blending percentage to apply to the drawing operation.
/// </summary>
public float BlendPercentage { get => (this.blendPercentage ?? 1).Clamp(0, 1); set => this.blendPercentage = value; }
public float BlendPercentage
{
get
{
return this.blendPercentage;
}
// In the future we could expose a PixelBlender<TPixel> directly on here
// or some forms of PixelBlender factory for each pixel type. Will need
// some API thought post V1.
set
{
Guard.MustBeBetweenOrEqualTo(value, 0, 1F, nameof(this.BlendPercentage));
this.blendPercentage = value;
}
}
/// <summary>
/// Gets or sets a value indicating the color blending percentage to apply to the drawing operation
/// Gets or sets a value indicating the color blending percentage to apply to the drawing operation.
/// Defaults to <see cref= "PixelColorBlendingMode.Normal" />.
/// </summary>
public PixelColorBlendingMode ColorBlendingMode { get; set; }
public PixelColorBlendingMode ColorBlendingMode { get; set; } = PixelColorBlendingMode.Normal;
/// <summary>
/// Gets or sets a value indicating the color blending percentage to apply to the drawing operation
/// Defaults to <see cref= "PixelAlphaCompositionMode.SrcOver" />.
/// </summary>
public PixelAlphaCompositionMode AlphaCompositionMode { get; set; }
public PixelAlphaCompositionMode AlphaCompositionMode { get; set; } = PixelAlphaCompositionMode.SrcOver;
/// <summary>
/// Gets or sets a value indicating whether the text should be drawing with kerning enabled.
/// Defaults to true;
/// </summary>
public bool ApplyKerning { get => this.applyKerning ?? true; set => this.applyKerning = value; }
public bool ApplyKerning { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating the number of space widths a tab should lock to.
/// Defaults to 4.
/// </summary>
public float TabWidth { get => this.tabWidth ?? 4; set => this.tabWidth = value; }
public float TabWidth
{
get
{
return this.tabWidth;
}
set
{
Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.TabWidth));
this.tabWidth = value;
}
}
/// <summary>
/// Gets or sets a value indicating if greater than zero determine the width at which text should wrap.
/// Gets or sets a value, if greater than 0, indicating the width at which text should wrap.
/// Defaults to 0.
/// </summary>
public float WrapTextWidth { get; set; }
/// <summary>
/// Gets or sets a value indicating the DPI to render text along the X axis.
/// Gets or sets a value indicating the DPI (Dots Per Inch) to render text along the X axis.
/// Defaults to 72.
/// </summary>
public float DpiX { get => this.dpiX ?? DefaultTextDpi; set => this.dpiX = value; }
public float DpiX
{
get
{
return this.dpiX;
}
set
{
Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.DpiX));
this.dpiX = value;
}
}
/// <summary>
/// Gets or sets a value indicating the DPI to render text along the Y axis.
/// Gets or sets a value indicating the DPI (Dots Per Inch) to render text along the Y axis.
/// Defaults to 72.
/// </summary>
public float DpiY { get => this.dpiY ?? DefaultTextDpi; set => this.dpiY = value; }
public float DpiY
{
get
{
return this.dpiY;
}
set
{
Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.DpiY));
this.dpiY = value;
}
}
/// <summary>
/// Gets or sets a value indicating how to align the text relative to the rendering space.
/// If <see cref="WrapTextWidth"/> is greater than zero it will align relative to the space
/// defined by the location and width, if <see cref="WrapTextWidth"/> equals zero, and thus
/// wrapping disabled, then the alignment is relative to the drawing location.
/// Defaults to <see cref="HorizontalAlignment.Left"/>.
/// </summary>
public HorizontalAlignment HorizontalAlignment { get => this.horizontalAlignment ?? HorizontalAlignment.Left; set => this.horizontalAlignment = value; }
public HorizontalAlignment HorizontalAlignment { get; set; } = HorizontalAlignment.Left;
/// <summary>
/// Gets or sets a value indicating how to align the text relative to the rendering space.
/// Defaults to <see cref="VerticalAlignment.Top"/>.
/// </summary>
public VerticalAlignment VerticalAlignment { get => this.verticalAlignment ?? VerticalAlignment.Top; set => this.verticalAlignment = value; }
public VerticalAlignment VerticalAlignment { get; set; } = VerticalAlignment.Top;
/// <summary>
/// Performs an implicit conversion from <see cref="GraphicsOptions"/> to <see cref="TextGraphicsOptions"/>.
@ -133,8 +182,9 @@ namespace SixLabors.ImageSharp.Processing
/// </returns>
public static implicit operator TextGraphicsOptions(GraphicsOptions options)
{
return new TextGraphicsOptions(options.Antialias)
return new TextGraphicsOptions()
{
Antialias = options.Antialias,
AntialiasSubpixelDepth = options.AntialiasSubpixelDepth,
blendPercentage = options.BlendPercentage,
ColorBlendingMode = options.ColorBlendingMode,
@ -151,13 +201,17 @@ namespace SixLabors.ImageSharp.Processing
/// </returns>
public static explicit operator GraphicsOptions(TextGraphicsOptions options)
{
return new GraphicsOptions(options.Antialias)
return new GraphicsOptions()
{
Antialias = options.Antialias,
AntialiasSubpixelDepth = options.AntialiasSubpixelDepth,
ColorBlendingMode = options.ColorBlendingMode,
AlphaCompositionMode = options.AlphaCompositionMode,
BlendPercentage = options.BlendPercentage
};
}
/// <inheritdoc/>
public TextGraphicsOptions DeepClone() => new TextGraphicsOptions(this);
}
}
}

29
src/ImageSharp.Drawing/Utils/NumberUtils.cs

@ -0,0 +1,29 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Utility methods for numeric primitives.
/// </summary>
internal static class NumberUtils
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float ClampFloat(float value, float min, float max)
{
if (value >= max)
{
return max;
}
if (value <= min)
{
return min;
}
return value;
}
}
}

2
src/ImageSharp.Drawing/Utils/QuickSort.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;

35
src/ImageSharp/Color/Color.cs

@ -133,8 +133,7 @@ namespace SixLabors.ImageSharp
public override string ToString() => this.ToHex();
/// <summary>
/// Converts the color instance to an <see cref="IPixel{TSelf}"/>
/// implementation defined by <typeparamref name="TPixel"/>.
/// Converts the color instance to a specified <see cref="IPixel{TSelf}"/> type.
/// </summary>
/// <typeparam name="TPixel">The pixel type to convert to.</typeparam>
/// <returns>The pixel value.</returns>
@ -147,6 +146,24 @@ namespace SixLabors.ImageSharp
return pixel;
}
/// <summary>
/// Bulk converts a span of <see cref="Color"/> to a span of a specified <see cref="IPixel{TSelf}"/> type.
/// </summary>
/// <typeparam name="TPixel">The pixel type to convert to.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="source">The source color span.</param>
/// <param name="destination">The destination pixel span.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ToPixel<TPixel>(
Configuration configuration,
ReadOnlySpan<Color> source,
Span<TPixel> destination)
where TPixel : struct, IPixel<TPixel>
{
ReadOnlySpan<Rgba64> rgba64Span = MemoryMarshal.Cast<Color, Rgba64>(source);
PixelOperations<TPixel>.Instance.FromRgba64(configuration, rgba64Span, destination);
}
/// <inheritdoc />
[MethodImpl(InliningOptions.ShortMethod)]
public bool Equals(Color other)
@ -166,19 +183,5 @@ namespace SixLabors.ImageSharp
{
return this.data.PackedValue.GetHashCode();
}
/// <summary>
/// Bulk convert a span of <see cref="Color"/> to a span of a specified pixel type.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
internal static void ToPixel<TPixel>(
Configuration configuration,
ReadOnlySpan<Color> source,
Span<TPixel> destination)
where TPixel : struct, IPixel<TPixel>
{
ReadOnlySpan<Rgba64> rgba64Span = MemoryMarshal.Cast<Color, Rgba64>(source);
PixelOperations<TPixel>.Instance.FromRgba64(configuration, rgba64Span, destination);
}
}
}

17
src/ImageSharp/Common/Helpers/ImageMaths.cs

@ -1,7 +1,8 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
@ -14,6 +15,20 @@ namespace SixLabors.ImageSharp
/// </summary>
internal static class ImageMaths
{
/// <summary>
/// Vector for converting pixel to gray value as specified by ITU-R Recommendation BT.709.
/// </summary>
private static readonly Vector4 Bt709 = new Vector4(.2126f, .7152f, .0722f, 0.0f);
/// <summary>
/// Convert a pixel value to grayscale using ITU-R Recommendation BT.709.
/// </summary>
/// <param name="vector">The vector to get the luminance from.</param>
/// <param name="luminanceLevels">The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images)</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static int GetBT709Luminance(ref Vector4 vector, int luminanceLevels)
=> (int)MathF.Round(Vector4.Dot(vector, Bt709) * (luminanceLevels - 1));
/// <summary>
/// Gets the luminance from the rgb components using the formula as specified by ITU-R Recommendation BT.709.
/// </summary>

5
src/ImageSharp/Configuration.cs

@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Processing;
using SixLabors.Memory;
@ -150,6 +151,7 @@ namespace SixLabors.ImageSharp
/// <see cref="JpegConfigurationModule"/>
/// <see cref="GifConfigurationModule"/>
/// <see cref="BmpConfigurationModule"/>.
/// <see cref="TgaConfigurationModule"/>.
/// </summary>
/// <returns>The default configuration of <see cref="Configuration"/>.</returns>
internal static Configuration CreateDefaultInstance()
@ -158,7 +160,8 @@ namespace SixLabors.ImageSharp
new PngConfigurationModule(),
new JpegConfigurationModule(),
new GifConfigurationModule(),
new BmpConfigurationModule());
new BmpConfigurationModule(),
new TgaConfigurationModule());
}
}
}

6
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

@ -387,9 +387,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp
if (rowHasUndefinedPixels)
{
// Slow path with undefined pixels.
int rowStartIdx = y * width * 3;
for (int x = 0; x < width; x++)
{
int idx = (y * width * 3) + (x * 3);
int idx = rowStartIdx + (x * 3);
if (undefinedPixels[x, y])
{
switch (this.options.RleSkippedPixelHandling)
@ -418,9 +419,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp
else
{
// Fast path without any undefined pixels.
int rowStartIdx = y * width * 3;
for (int x = 0; x < width; x++)
{
int idx = (y * width * 3) + (x * 3);
int idx = rowStartIdx + (x * 3);
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref bufferSpan[idx]));
pixelRow[x] = color;
}

17
src/ImageSharp/Formats/Png/PngChunk.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.Memory;
@ -10,12 +10,11 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
internal readonly struct PngChunk
{
public PngChunk(int length, PngChunkType type, IManagedByteBuffer data = null, uint crc = 0)
public PngChunk(int length, PngChunkType type, IManagedByteBuffer data = null)
{
this.Length = length;
this.Type = type;
this.Data = data;
this.Crc = crc;
}
/// <summary>
@ -38,20 +37,12 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
public IManagedByteBuffer Data { get; }
/// <summary>
/// Gets a CRC (Cyclic Redundancy Check) calculated on the preceding bytes in the chunk,
/// including the chunk type code and chunk data fields, but not including the length field.
/// The CRC is always present, even for chunks containing no data
/// </summary>
public uint Crc { get; }
/// <summary>
/// Gets a value indicating whether the given chunk is critical to decoding
/// </summary>
public bool IsCritical =>
this.Type == PngChunkType.Header ||
this.Type == PngChunkType.Palette ||
this.Type == PngChunkType.Data ||
this.Type == PngChunkType.End;
this.Type == PngChunkType.Data;
}
}
}

64
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -221,7 +221,7 @@ namespace SixLabors.ImageSharp.Formats.Png
if (image is null)
{
throw new ImageFormatException("PNG Image does not contain a data chunk");
PngThrowHelper.ThrowNoData();
}
return image;
@ -285,7 +285,7 @@ namespace SixLabors.ImageSharp.Formats.Png
if (this.header.Width == 0 && this.header.Height == 0)
{
throw new ImageFormatException("PNG Image does not contain a header chunk");
PngThrowHelper.ThrowNoHeader();
}
return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metadata);
@ -407,7 +407,8 @@ namespace SixLabors.ImageSharp.Formats.Png
case PngColorType.RgbWithAlpha:
return this.header.BitDepth * 4;
default:
throw new NotSupportedException("Unsupported PNG color type");
PngThrowHelper.ThrowNotSupportedColor();
return -1;
}
}
@ -528,7 +529,8 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
default:
throw new ImageFormatException("Unknown filter type.");
PngThrowHelper.ThrowUnknownFilter();
break;
}
this.ProcessDefilteredScanline(scanlineSpan, image, pngMetadata);
@ -601,7 +603,8 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
default:
throw new ImageFormatException("Unknown filter type.");
PngThrowHelper.ThrowUnknownFilter();
break;
}
Span<TPixel> rowSpan = image.GetPixelRowSpan(this.currentRow);
@ -1119,13 +1122,9 @@ namespace SixLabors.ImageSharp.Formats.Png
chunk = new PngChunk(
length: length,
type: type,
data: this.ReadChunkData(length),
crc: this.ReadChunkCrc());
data: this.ReadChunkData(length));
if (chunk.IsCritical)
{
this.ValidateChunk(chunk);
}
this.ValidateChunk(chunk);
return true;
}
@ -1136,6 +1135,11 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="chunk">The <see cref="PngChunk"/>.</param>
private void ValidateChunk(in PngChunk chunk)
{
if (!chunk.IsCritical)
{
return;
}
Span<byte> chunkType = stackalloc byte[4];
BinaryPrimitives.WriteUInt32BigEndian(chunkType, (uint)chunk.Type);
@ -1144,31 +1148,34 @@ namespace SixLabors.ImageSharp.Formats.Png
this.crc.Update(chunkType);
this.crc.Update(chunk.Data.GetSpan());
if (this.crc.Value != chunk.Crc)
uint crc = this.ReadChunkCrc();
if (this.crc.Value != crc)
{
string chunkTypeName = Encoding.ASCII.GetString(chunkType);
throw new ImageFormatException($"CRC Error. PNG {chunkTypeName} chunk is corrupt!");
PngThrowHelper.ThrowInvalidChunkCrc(chunkTypeName);
}
}
/// <summary>
/// Reads the cycle redundancy chunk from the data.
/// </summary>
/// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid or corrupt.
/// </exception>
[MethodImpl(InliningOptions.ShortMethod)]
private uint ReadChunkCrc()
{
return this.currentStream.Read(this.buffer, 0, 4) == 4
? BinaryPrimitives.ReadUInt32BigEndian(this.buffer)
: throw new ImageFormatException("Image stream is not valid!");
uint crc = 0;
if (this.currentStream.Read(this.buffer, 0, 4) == 4)
{
crc = BinaryPrimitives.ReadUInt32BigEndian(this.buffer);
}
return crc;
}
/// <summary>
/// Skips the chunk data and the cycle redundancy chunk read from the data.
/// </summary>
/// <param name="chunk">The image format chunk.</param>
[MethodImpl(InliningOptions.ShortMethod)]
private void SkipChunkDataAndCrc(in PngChunk chunk)
{
this.currentStream.Skip(chunk.Length);
@ -1179,6 +1186,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Reads the chunk data from the stream.
/// </summary>
/// <param name="length">The length of the chunk data to read.</param>
[MethodImpl(InliningOptions.ShortMethod)]
private IManagedByteBuffer ReadChunkData(int length)
{
// We rent the buffer here to return it afterwards in Decode()
@ -1195,11 +1203,20 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid.
/// </exception>
[MethodImpl(InliningOptions.ShortMethod)]
private PngChunkType ReadChunkType()
{
return this.currentStream.Read(this.buffer, 0, 4) == 4
? (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer)
: throw new ImageFormatException("Invalid PNG data.");
if (this.currentStream.Read(this.buffer, 0, 4) == 4)
{
return (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer);
}
else
{
PngThrowHelper.ThrowInvalidChunkType();
// The IDE cannot detect the throw here.
return default;
}
}
/// <summary>
@ -1208,6 +1225,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <returns>
/// Whether the length was read.
/// </returns>
[MethodImpl(InliningOptions.ShortMethod)]
private bool TryReadChunkLength(out int result)
{
if (this.currentStream.Read(this.buffer, 0, 4) == 4)

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

@ -699,7 +699,7 @@ namespace SixLabors.ImageSharp.Formats.Png
{
using (var memoryStream = new MemoryStream())
{
using (var deflateStream = new ZlibDeflateStream(memoryStream, this.options.CompressionLevel))
using (var deflateStream = new ZlibDeflateStream(this.memoryAllocator, memoryStream, this.options.CompressionLevel))
{
deflateStream.Write(textBytes);
}
@ -790,7 +790,7 @@ namespace SixLabors.ImageSharp.Formats.Png
using (var memoryStream = new MemoryStream())
{
using (var deflateStream = new ZlibDeflateStream(memoryStream, this.options.CompressionLevel))
using (var deflateStream = new ZlibDeflateStream(this.memoryAllocator, memoryStream, this.options.CompressionLevel))
{
if (this.options.InterlaceMethod == PngInterlaceMode.Adam7)
{

33
src/ImageSharp/Formats/Png/PngThrowHelper.cs

@ -0,0 +1,33 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Png
{
/// <summary>
/// Cold path optimizations for throwing png format based exceptions.
/// </summary>
internal static class PngThrowHelper
{
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNoHeader() => throw new ImageFormatException("PNG Image does not contain a header chunk");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNoData() => throw new ImageFormatException("PNG Image does not contain a data chunk");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowInvalidChunkType() => throw new ImageFormatException("Invalid PNG data.");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowInvalidChunkCrc(string chunkTypeName) => throw new ImageFormatException($"CRC Error. PNG {chunkTypeName} chunk is corrupt!");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNotSupportedColor() => new NotSupportedException("Unsupported PNG color type");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowUnknownFilter() => throw new ImageFormatException("Unknown filter type.");
}
}

11
src/ImageSharp/Formats/Png/Zlib/Adler32.cs

@ -1,8 +1,9 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
@ -112,7 +113,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Update(ReadOnlySpan<byte> data)
{
// (By Per Bothner)
ref byte dataRef = ref MemoryMarshal.GetReference(data);
uint s1 = this.checksum & 0xFFFF;
uint s2 = this.checksum >> 16;
@ -133,8 +134,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
count -= n;
while (--n >= 0)
{
s1 = s1 + (uint)(data[offset++] & 0xff);
s2 = s2 + s1;
s1 += Unsafe.Add(ref dataRef, offset++);
s2 += s1;
}
s1 %= Base;
@ -144,4 +145,4 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.checksum = (s2 << 16) | s1;
}
}
}
}

6
src/ImageSharp/Formats/Png/Zlib/Crc32.cs

@ -1,8 +1,9 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
@ -141,9 +142,10 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
this.crc ^= CrcSeed;
ref uint crcTableRef = ref MemoryMarshal.GetReference(CrcTable.AsSpan());
for (int i = 0; i < data.Length; i++)
{
this.crc = CrcTable[(this.crc ^ data[i]) & 0xFF] ^ (this.crc >> 8);
this.crc = Unsafe.Add(ref crcTableRef, (int)((this.crc ^ data[i]) & 0xFF)) ^ (this.crc >> 8);
}
this.crc ^= CrcSeed;

35
src/ImageSharp/Formats/Png/Zlib/DeflateThrowHelper.cs

@ -0,0 +1,35 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
internal static class DeflateThrowHelper
{
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowAlreadyFinished() => throw new InvalidOperationException("Finish() already called.");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowAlreadyClosed() => throw new InvalidOperationException("Deflator already closed.");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowUnknownCompression() => throw new InvalidOperationException("Unknown compression function.");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNotProcessed() => throw new InvalidOperationException("Old input was not completely processed.");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNull(string name) => throw new ArgumentNullException(name);
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowOutOfRange(string name) => throw new ArgumentOutOfRangeException(name);
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowHeapViolated() => throw new InvalidOperationException("Huffman heap invariant violated.");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNoDeflate() => throw new ImageFormatException("Cannot deflate all input.");
}
}

295
src/ImageSharp/Formats/Png/Zlib/Deflater.cs

@ -0,0 +1,295 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
/// <summary>
/// This class compresses input with the deflate algorithm described in RFC 1951.
/// It has several compression levels and three different strategies described below.
/// </summary>
internal sealed class Deflater : IDisposable
{
/// <summary>
/// The best and slowest compression level. This tries to find very
/// long and distant string repetitions.
/// </summary>
public const int BestCompression = 9;
/// <summary>
/// The worst but fastest compression level.
/// </summary>
public const int BestSpeed = 1;
/// <summary>
/// The default compression level.
/// </summary>
public const int DefaultCompression = -1;
/// <summary>
/// This level won't compress at all but output uncompressed blocks.
/// </summary>
public const int NoCompression = 0;
/// <summary>
/// The compression method. This is the only method supported so far.
/// There is no need to use this constant at all.
/// </summary>
public const int Deflated = 8;
/// <summary>
/// Compression level.
/// </summary>
private int level;
/// <summary>
/// The current state.
/// </summary>
private int state;
private DeflaterEngine engine;
private bool isDisposed;
private const int IsFlushing = 0x04;
private const int IsFinishing = 0x08;
private const int BusyState = 0x10;
private const int FlushingState = 0x14;
private const int FinishingState = 0x1c;
private const int FinishedState = 0x1e;
private const int ClosedState = 0x7f;
/// <summary>
/// Initializes a new instance of the <see cref="Deflater"/> class.
/// </summary>
/// <param name="memoryAllocator">The memory allocator to use for buffer allocations.</param>
/// <param name="level">The compression level, a value between NoCompression and BestCompression.
/// </param>
/// <exception cref="ArgumentOutOfRangeException">if level is out of range.</exception>
public Deflater(MemoryAllocator memoryAllocator, int level)
{
if (level == DefaultCompression)
{
level = 6;
}
else if (level < NoCompression || level > BestCompression)
{
throw new ArgumentOutOfRangeException(nameof(level));
}
// TODO: Possibly provide DeflateStrategy as an option.
this.engine = new DeflaterEngine(memoryAllocator, DeflateStrategy.Default);
this.SetLevel(level);
this.Reset();
}
/// <summary>
/// Compression Level as an enum for safer use
/// </summary>
public enum CompressionLevel
{
/// <summary>
/// The best and slowest compression level. This tries to find very
/// long and distant string repetitions.
/// </summary>
BestCompression = Deflater.BestCompression,
/// <summary>
/// The worst but fastest compression level.
/// </summary>
BestSpeed = Deflater.BestSpeed,
/// <summary>
/// The default compression level.
/// </summary>
DefaultCompression = Deflater.DefaultCompression,
/// <summary>
/// This level won't compress at all but output uncompressed blocks.
/// </summary>
NoCompression = Deflater.NoCompression,
/// <summary>
/// The compression method. This is the only method supported so far.
/// There is no need to use this constant at all.
/// </summary>
Deflated = Deflater.Deflated
}
/// <summary>
/// Gets a value indicating whetherthe stream was finished and no more output bytes
/// are available.
/// </summary>
public bool IsFinished => (this.state == FinishedState) && this.engine.Pending.IsFlushed;
/// <summary>
/// Gets a value indicating whether the input buffer is empty.
/// You should then call setInput().
/// NOTE: This method can also return true when the stream
/// was finished.
/// </summary>
public bool IsNeedingInput => this.engine.NeedsInput();
/// <summary>
/// Resets the deflater. The deflater acts afterwards as if it was
/// just created with the same compression level and strategy as it
/// had before.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void Reset()
{
this.state = BusyState;
this.engine.Pending.Reset();
this.engine.Reset();
}
/// <summary>
/// Flushes the current input block. Further calls to Deflate() will
/// produce enough output to inflate everything in the current input
/// block. It is used by DeflaterOutputStream to implement Flush().
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void Flush() => this.state |= IsFlushing;
/// <summary>
/// Finishes the deflater with the current input block. It is an error
/// to give more input after this method was called. This method must
/// be called to force all bytes to be flushed.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void Finish() => this.state |= IsFlushing | IsFinishing;
/// <summary>
/// Sets the data which should be compressed next. This should be
/// only called when needsInput indicates that more input is needed.
/// The given byte array should not be changed, before needsInput() returns
/// true again.
/// </summary>
/// <param name="input">The buffer containing the input data.</param>
/// <param name="offset">The start of the data.</param>
/// <param name="count">The number of data bytes of input.</param>
/// <exception cref="InvalidOperationException">
/// if the buffer was finished or if previous input is still pending.
/// </exception>
[MethodImpl(InliningOptions.ShortMethod)]
public void SetInput(byte[] input, int offset, int count)
{
if ((this.state & IsFinishing) != 0)
{
DeflateThrowHelper.ThrowAlreadyFinished();
}
this.engine.SetInput(input, offset, count);
}
/// <summary>
/// Sets the compression level. There is no guarantee of the exact
/// position of the change, but if you call this when needsInput is
/// true the change of compression level will occur somewhere near
/// before the end of the so far given input.
/// </summary>
/// <param name="level">
/// the new compression level.
/// </param>
public void SetLevel(int level)
{
if (level == DefaultCompression)
{
level = 6;
}
else if (level < NoCompression || level > BestCompression)
{
throw new ArgumentOutOfRangeException(nameof(level));
}
if (this.level != level)
{
this.level = level;
this.engine.SetLevel(level);
}
}
/// <summary>
/// Deflates the current input block to the given array.
/// </summary>
/// <param name="output">Buffer to store the compressed data.</param>
/// <param name="offset">Offset into the output array.</param>
/// <param name="length">The maximum number of bytes that may be stored.</param>
/// <returns>
/// The number of compressed bytes added to the output, or 0 if either
/// <see cref="IsNeedingInput"/> or <see cref="IsFinished"/> returns true or length is zero.
/// </returns>
public int Deflate(byte[] output, int offset, int length)
{
int origLength = length;
if (this.state == ClosedState)
{
DeflateThrowHelper.ThrowAlreadyClosed();
}
while (true)
{
int count = this.engine.Pending.Flush(output, offset, length);
offset += count;
length -= count;
if (length == 0 || this.state == FinishedState)
{
break;
}
if (!this.engine.Deflate((this.state & IsFlushing) != 0, (this.state & IsFinishing) != 0))
{
switch (this.state)
{
case BusyState:
// We need more input now
return origLength - length;
case FlushingState:
if (this.level != NoCompression)
{
// We have to supply some lookahead. 8 bit lookahead
// is needed by the zlib inflater, and we must fill
// the next byte, so that all bits are flushed.
int neededbits = 8 + ((-this.engine.Pending.BitCount) & 7);
while (neededbits > 0)
{
// Write a static tree block consisting solely of an EOF:
this.engine.Pending.WriteBits(2, 10);
neededbits -= 10;
}
}
this.state = BusyState;
break;
case FinishingState:
this.engine.Pending.AlignToByte();
this.state = FinishedState;
break;
}
}
}
return origLength - length;
}
/// <inheritdoc/>
public void Dispose()
{
if (!this.isDisposed)
{
this.engine.Dispose();
this.engine = null;
this.isDisposed = true;
}
GC.SuppressFinalize(this);
}
}
}

151
src/ImageSharp/Formats/Png/Zlib/DeflaterConstants.cs

@ -0,0 +1,151 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
// <auto-generated/>
using System;
using System.Collections.Generic;
using System.Text;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
/// <summary>
/// This class contains constants used for deflation.
/// </summary>
public static class DeflaterConstants
{
/// <summary>
/// Set to true to enable debugging
/// </summary>
public const bool DEBUGGING = false;
/// <summary>
/// Written to Zip file to identify a stored block
/// </summary>
public const int STORED_BLOCK = 0;
/// <summary>
/// Identifies static tree in Zip file
/// </summary>
public const int STATIC_TREES = 1;
/// <summary>
/// Identifies dynamic tree in Zip file
/// </summary>
public const int DYN_TREES = 2;
/// <summary>
/// Header flag indicating a preset dictionary for deflation
/// </summary>
public const int PRESET_DICT = 0x20;
/// <summary>
/// Sets internal buffer sizes for Huffman encoding
/// </summary>
public const int DEFAULT_MEM_LEVEL = 8;
/// <summary>
/// Internal compression engine constant
/// </summary>
public const int MAX_MATCH = 258;
/// <summary>
/// Internal compression engine constant
/// </summary>
public const int MIN_MATCH = 3;
/// <summary>
/// Internal compression engine constant
/// </summary>
public const int MAX_WBITS = 15;
/// <summary>
/// Internal compression engine constant
/// </summary>
public const int WSIZE = 1 << MAX_WBITS;
/// <summary>
/// Internal compression engine constant
/// </summary>
public const int WMASK = WSIZE - 1;
/// <summary>
/// Internal compression engine constant
/// </summary>
public const int HASH_BITS = DEFAULT_MEM_LEVEL + 7;
/// <summary>
/// Internal compression engine constant
/// </summary>
public const int HASH_SIZE = 1 << HASH_BITS;
/// <summary>
/// Internal compression engine constant
/// </summary>
public const int HASH_MASK = HASH_SIZE - 1;
/// <summary>
/// Internal compression engine constant
/// </summary>
public const int HASH_SHIFT = (HASH_BITS + MIN_MATCH - 1) / MIN_MATCH;
/// <summary>
/// Internal compression engine constant
/// </summary>
public const int MIN_LOOKAHEAD = MAX_MATCH + MIN_MATCH + 1;
/// <summary>
/// Internal compression engine constant
/// </summary>
public const int MAX_DIST = WSIZE - MIN_LOOKAHEAD;
/// <summary>
/// Internal compression engine constant
/// </summary>
public const int PENDING_BUF_SIZE = 1 << (DEFAULT_MEM_LEVEL + 8);
/// <summary>
/// Internal compression engine constant
/// </summary>
public static int MAX_BLOCK_SIZE = Math.Min(65535, PENDING_BUF_SIZE - 5);
/// <summary>
/// Internal compression engine constant
/// </summary>
public const int DEFLATE_STORED = 0;
/// <summary>
/// Internal compression engine constant
/// </summary>
public const int DEFLATE_FAST = 1;
/// <summary>
/// Internal compression engine constant
/// </summary>
public const int DEFLATE_SLOW = 2;
/// <summary>
/// Internal compression engine constant
/// </summary>
public static int[] GOOD_LENGTH = { 0, 4, 4, 4, 4, 8, 8, 8, 32, 32 };
/// <summary>
/// Internal compression engine constant
/// </summary>
public static int[] MAX_LAZY = { 0, 4, 5, 6, 4, 16, 16, 32, 128, 258 };
/// <summary>
/// Internal compression engine constant
/// </summary>
public static int[] NICE_LENGTH = { 0, 8, 16, 32, 16, 32, 128, 128, 258, 258 };
/// <summary>
/// Internal compression engine constant
/// </summary>
public static int[] MAX_CHAIN = { 0, 4, 8, 32, 16, 32, 128, 256, 1024, 4096 };
/// <summary>
/// Internal compression engine constant
/// </summary>
public static int[] COMPR_FUNC = { 0, 1, 1, 1, 1, 2, 2, 2, 2, 2 };
}
}

859
src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs

@ -0,0 +1,859 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
/// <summary>
/// Strategies for deflater
/// </summary>
public enum DeflateStrategy
{
/// <summary>
/// The default strategy
/// </summary>
Default = 0,
/// <summary>
/// This strategy will only allow longer string repetitions. It is
/// useful for random data with a small character set.
/// </summary>
Filtered = 1,
/// <summary>
/// This strategy will not look for string repetitions at all. It
/// only encodes with Huffman trees (which means, that more common
/// characters get a smaller encoding.
/// </summary>
HuffmanOnly = 2
}
// DEFLATE ALGORITHM:
//
// The uncompressed stream is inserted into the window array. When
// the window array is full the first half is thrown away and the
// second half is copied to the beginning.
//
// The head array is a hash table. Three characters build a hash value
// and they the value points to the corresponding index in window of
// the last string with this hash. The prev array implements a
// linked list of matches with the same hash: prev[index & WMASK] points
// to the previous index with the same hash.
//
/// <summary>
/// Low level compression engine for deflate algorithm which uses a 32K sliding window
/// with secondary compression from Huffman/Shannon-Fano codes.
/// </summary>
internal sealed unsafe class DeflaterEngine : IDisposable
{
private const int TooFar = 4096;
// Hash index of string to be inserted
private int insertHashIndex;
private int matchStart;
// Length of best match
private int matchLen;
// Set if previous match exists
private bool prevAvailable;
private int blockStart;
/// <summary>
/// Points to the current character in the window.
/// </summary>
private int strstart;
/// <summary>
/// lookahead is the number of characters starting at strstart in
/// window that are valid.
/// So window[strstart] until window[strstart+lookahead-1] are valid
/// characters.
/// </summary>
private int lookahead;
/// <summary>
/// The current compression function.
/// </summary>
private int compressionFunction;
/// <summary>
/// The input data for compression.
/// </summary>
private byte[] inputBuf;
/// <summary>
/// The offset into inputBuf, where input data starts.
/// </summary>
private int inputOff;
/// <summary>
/// The end offset of the input data.
/// </summary>
private int inputEnd;
private readonly DeflateStrategy strategy;
private DeflaterHuffman huffman;
private bool isDisposed;
/// <summary>
/// Hashtable, hashing three characters to an index for window, so
/// that window[index]..window[index+2] have this hash code.
/// Note that the array should really be unsigned short, so you need
/// to and the values with 0xFFFF.
/// </summary>
private IMemoryOwner<short> headMemoryOwner;
private MemoryHandle headMemoryHandle;
private readonly Memory<short> head;
private readonly short* pinnedHeadPointer;
/// <summary>
/// <code>prev[index &amp; WMASK]</code> points to the previous index that has the
/// same hash code as the string starting at index. This way
/// entries with the same hash code are in a linked list.
/// Note that the array should really be unsigned short, so you need
/// to and the values with 0xFFFF.
/// </summary>
private IMemoryOwner<short> prevMemoryOwner;
private MemoryHandle prevMemoryHandle;
private readonly Memory<short> prev;
private readonly short* pinnedPrevPointer;
/// <summary>
/// This array contains the part of the uncompressed stream that
/// is of relevance. The current character is indexed by strstart.
/// </summary>
private IManagedByteBuffer windowMemoryOwner;
private MemoryHandle windowMemoryHandle;
private readonly byte[] window;
private readonly byte* pinnedWindowPointer;
private int maxChain;
private int maxLazy;
private int niceLength;
private int goodLength;
/// <summary>
/// Initializes a new instance of the <see cref="DeflaterEngine"/> class.
/// </summary>
/// <param name="memoryAllocator">The memory allocator to use for buffer allocations.</param>
/// <param name="strategy">The deflate strategy to use.</param>
public DeflaterEngine(MemoryAllocator memoryAllocator, DeflateStrategy strategy)
{
this.huffman = new DeflaterHuffman(memoryAllocator);
this.Pending = this.huffman.Pending;
this.strategy = strategy;
// Create pinned pointers to the various buffers to allow indexing
// without bounds checks.
this.windowMemoryOwner = memoryAllocator.AllocateManagedByteBuffer(2 * DeflaterConstants.WSIZE);
this.window = this.windowMemoryOwner.Array;
this.windowMemoryHandle = this.windowMemoryOwner.Memory.Pin();
this.pinnedWindowPointer = (byte*)this.windowMemoryHandle.Pointer;
this.headMemoryOwner = memoryAllocator.Allocate<short>(DeflaterConstants.HASH_SIZE);
this.head = this.headMemoryOwner.Memory;
this.headMemoryHandle = this.headMemoryOwner.Memory.Pin();
this.pinnedHeadPointer = (short*)this.headMemoryHandle.Pointer;
this.prevMemoryOwner = memoryAllocator.Allocate<short>(DeflaterConstants.WSIZE);
this.prev = this.prevMemoryOwner.Memory;
this.prevMemoryHandle = this.prevMemoryOwner.Memory.Pin();
this.pinnedPrevPointer = (short*)this.prevMemoryHandle.Pointer;
// We start at index 1, to avoid an implementation deficiency, that
// we cannot build a repeat pattern at index 0.
this.blockStart = this.strstart = 1;
}
/// <summary>
/// Gets the pending buffer to use.
/// </summary>
public DeflaterPendingBuffer Pending { get; }
/// <summary>
/// Deflate drives actual compression of data
/// </summary>
/// <param name="flush">True to flush input buffers</param>
/// <param name="finish">Finish deflation with the current input.</param>
/// <returns>Returns true if progress has been made.</returns>
public bool Deflate(bool flush, bool finish)
{
bool progress = false;
do
{
this.FillWindow();
bool canFlush = flush && (this.inputOff == this.inputEnd);
switch (this.compressionFunction)
{
case DeflaterConstants.DEFLATE_STORED:
progress = this.DeflateStored(canFlush, finish);
break;
case DeflaterConstants.DEFLATE_FAST:
progress = this.DeflateFast(canFlush, finish);
break;
case DeflaterConstants.DEFLATE_SLOW:
progress = this.DeflateSlow(canFlush, finish);
break;
default:
DeflateThrowHelper.ThrowUnknownCompression();
break;
}
}
while (this.Pending.IsFlushed && progress); // repeat while we have no pending output and progress was made
return progress;
}
/// <summary>
/// Sets input data to be deflated. Should only be called when <see cref="NeedsInput"/>
/// returns true
/// </summary>
/// <param name="buffer">The buffer containing input data.</param>
/// <param name="offset">The offset of the first byte of data.</param>
/// <param name="count">The number of bytes of data to use as input.</param>
public void SetInput(byte[] buffer, int offset, int count)
{
if (buffer is null)
{
DeflateThrowHelper.ThrowNull(nameof(buffer));
}
if (offset < 0)
{
DeflateThrowHelper.ThrowOutOfRange(nameof(offset));
}
if (count < 0)
{
DeflateThrowHelper.ThrowOutOfRange(nameof(count));
}
if (this.inputOff < this.inputEnd)
{
DeflateThrowHelper.ThrowNotProcessed();
}
int end = offset + count;
// We want to throw an ArgumentOutOfRangeException early.
// The check is very tricky: it also handles integer wrap around.
if ((offset > end) || (end > buffer.Length))
{
DeflateThrowHelper.ThrowOutOfRange(nameof(count));
}
this.inputBuf = buffer;
this.inputOff = offset;
this.inputEnd = end;
}
/// <summary>
/// Determines if more <see cref="SetInput">input</see> is needed.
/// </summary>
/// <returns>Return true if input is needed via <see cref="SetInput">SetInput</see></returns>
[MethodImpl(InliningOptions.ShortMethod)]
public bool NeedsInput() => this.inputEnd == this.inputOff;
/// <summary>
/// Reset internal state
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void Reset()
{
this.huffman.Reset();
this.blockStart = this.strstart = 1;
this.lookahead = 0;
this.prevAvailable = false;
this.matchLen = DeflaterConstants.MIN_MATCH - 1;
this.head.Span.Slice(0, DeflaterConstants.HASH_SIZE).Clear();
this.prev.Span.Slice(0, DeflaterConstants.WSIZE).Clear();
}
/// <summary>
/// Set the deflate level (0-9)
/// </summary>
/// <param name="level">The value to set the level to.</param>
public void SetLevel(int level)
{
if ((level < 0) || (level > 9))
{
DeflateThrowHelper.ThrowOutOfRange(nameof(level));
}
this.goodLength = DeflaterConstants.GOOD_LENGTH[level];
this.maxLazy = DeflaterConstants.MAX_LAZY[level];
this.niceLength = DeflaterConstants.NICE_LENGTH[level];
this.maxChain = DeflaterConstants.MAX_CHAIN[level];
if (DeflaterConstants.COMPR_FUNC[level] != this.compressionFunction)
{
switch (this.compressionFunction)
{
case DeflaterConstants.DEFLATE_STORED:
if (this.strstart > this.blockStart)
{
this.huffman.FlushStoredBlock(this.window, this.blockStart, this.strstart - this.blockStart, false);
this.blockStart = this.strstart;
}
this.UpdateHash();
break;
case DeflaterConstants.DEFLATE_FAST:
if (this.strstart > this.blockStart)
{
this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, false);
this.blockStart = this.strstart;
}
break;
case DeflaterConstants.DEFLATE_SLOW:
if (this.prevAvailable)
{
this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart - 1] & 0xFF);
}
if (this.strstart > this.blockStart)
{
this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, false);
this.blockStart = this.strstart;
}
this.prevAvailable = false;
this.matchLen = DeflaterConstants.MIN_MATCH - 1;
break;
}
this.compressionFunction = DeflaterConstants.COMPR_FUNC[level];
}
}
/// <summary>
/// Fill the window
/// </summary>
public void FillWindow()
{
// If the window is almost full and there is insufficient lookahead,
// move the upper half to the lower one to make room in the upper half.
if (this.strstart >= DeflaterConstants.WSIZE + DeflaterConstants.MAX_DIST)
{
this.SlideWindow();
}
// If there is not enough lookahead, but still some input left, read in the input.
if (this.lookahead < DeflaterConstants.MIN_LOOKAHEAD && this.inputOff < this.inputEnd)
{
int more = (2 * DeflaterConstants.WSIZE) - this.lookahead - this.strstart;
if (more > this.inputEnd - this.inputOff)
{
more = this.inputEnd - this.inputOff;
}
Array.Copy(this.inputBuf, this.inputOff, this.window, this.strstart + this.lookahead, more);
this.inputOff += more;
this.lookahead += more;
}
if (this.lookahead >= DeflaterConstants.MIN_MATCH)
{
this.UpdateHash();
}
}
/// <inheritdoc/>
public void Dispose()
{
if (!this.isDisposed)
{
this.huffman.Dispose();
this.windowMemoryHandle.Dispose();
this.windowMemoryOwner.Dispose();
this.headMemoryHandle.Dispose();
this.headMemoryOwner.Dispose();
this.prevMemoryHandle.Dispose();
this.prevMemoryOwner.Dispose();
this.windowMemoryOwner = null;
this.headMemoryOwner = null;
this.prevMemoryOwner = null;
this.huffman = null;
this.isDisposed = true;
}
GC.SuppressFinalize(this);
}
[MethodImpl(InliningOptions.ShortMethod)]
private void UpdateHash()
{
byte* pinned = this.pinnedWindowPointer;
this.insertHashIndex = (pinned[this.strstart] << DeflaterConstants.HASH_SHIFT) ^ pinned[this.strstart + 1];
}
/// <summary>
/// Inserts the current string in the head hash and returns the previous
/// value for this hash.
/// </summary>
/// <returns>The previous hash value</returns>
[MethodImpl(InliningOptions.ShortMethod)]
private int InsertString()
{
short match;
int hash = ((this.insertHashIndex << DeflaterConstants.HASH_SHIFT) ^ this.pinnedWindowPointer[this.strstart + (DeflaterConstants.MIN_MATCH - 1)]) & DeflaterConstants.HASH_MASK;
short* pinnedHead = this.pinnedHeadPointer;
this.pinnedPrevPointer[this.strstart & DeflaterConstants.WMASK] = match = pinnedHead[hash];
pinnedHead[hash] = unchecked((short)this.strstart);
this.insertHashIndex = hash;
return match & 0xFFFF;
}
private void SlideWindow()
{
Unsafe.CopyBlockUnaligned(ref this.window[0], ref this.window[DeflaterConstants.WSIZE], DeflaterConstants.WSIZE);
this.matchStart -= DeflaterConstants.WSIZE;
this.strstart -= DeflaterConstants.WSIZE;
this.blockStart -= DeflaterConstants.WSIZE;
// Slide the hash table (could be avoided with 32 bit values
// at the expense of memory usage).
short* pinnedHead = this.pinnedHeadPointer;
for (int i = 0; i < DeflaterConstants.HASH_SIZE; ++i)
{
int m = pinnedHead[i] & 0xFFFF;
pinnedHead[i] = (short)(m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0);
}
// Slide the prev table.
short* pinnedPrev = this.pinnedPrevPointer;
for (int i = 0; i < DeflaterConstants.WSIZE; i++)
{
int m = pinnedPrev[i] & 0xFFFF;
pinnedPrev[i] = (short)(m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0);
}
}
/// <summary>
/// <para>
/// Find the best (longest) string in the window matching the
/// string starting at strstart.
/// </para>
/// <para>
/// Preconditions:
/// <code>
/// strstart + DeflaterConstants.MAX_MATCH &lt;= window.length.</code>
/// </para>
/// </summary>
/// <param name="curMatch">The current match.</param>
/// <returns>True if a match greater than the minimum length is found</returns>
private bool FindLongestMatch(int curMatch)
{
int match;
int scan = this.strstart;
// scanMax is the highest position that we can look at
int scanMax = scan + Math.Min(DeflaterConstants.MAX_MATCH, this.lookahead) - 1;
int limit = Math.Max(scan - DeflaterConstants.MAX_DIST, 0);
int chainLength = this.maxChain;
int niceLength = Math.Min(this.niceLength, this.lookahead);
int matchStrt = this.matchStart;
this.matchLen = Math.Max(this.matchLen, DeflaterConstants.MIN_MATCH - 1);
int matchLength = this.matchLen;
if (scan + matchLength > scanMax)
{
return false;
}
byte* pinnedWindow = this.pinnedWindowPointer;
int scanStart = this.strstart;
byte scanEnd1 = pinnedWindow[scan + matchLength - 1];
byte scanEnd = pinnedWindow[scan + matchLength];
// Do not waste too much time if we already have a good match:
if (matchLength >= this.goodLength)
{
chainLength >>= 2;
}
short* pinnedPrev = this.pinnedPrevPointer;
do
{
match = curMatch;
scan = scanStart;
if (pinnedWindow[match + matchLength] != scanEnd
|| pinnedWindow[match + matchLength - 1] != scanEnd1
|| pinnedWindow[match] != pinnedWindow[scan]
|| pinnedWindow[++match] != pinnedWindow[++scan])
{
continue;
}
// scan is set to strstart+1 and the comparison passed, so
// scanMax - scan is the maximum number of bytes we can compare.
// below we compare 8 bytes at a time, so first we compare
// (scanMax - scan) % 8 bytes, so the remainder is a multiple of 8
// n & (8 - 1) == n % 8.
switch ((scanMax - scan) & 7)
{
case 1:
if (pinnedWindow[++scan] == pinnedWindow[++match])
{
break;
}
break;
case 2:
if (pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match])
{
break;
}
break;
case 3:
if (pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match])
{
break;
}
break;
case 4:
if (pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match])
{
break;
}
break;
case 5:
if (pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match])
{
break;
}
break;
case 6:
if (pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match])
{
break;
}
break;
case 7:
if (pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match])
{
break;
}
break;
}
if (pinnedWindow[scan] == pinnedWindow[match])
{
// We check for insufficient lookahead only every 8th comparison;
// the 256th check will be made at strstart + 258 unless lookahead is
// exhausted first.
do
{
if (scan == scanMax)
{
++scan; // advance to first position not matched
++match;
break;
}
}
while (pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match]
&& pinnedWindow[++scan] == pinnedWindow[++match]);
}
if (scan - scanStart > matchLength)
{
matchStrt = curMatch;
matchLength = scan - scanStart;
if (matchLength >= niceLength)
{
break;
}
scanEnd1 = pinnedWindow[scan - 1];
scanEnd = pinnedWindow[scan];
}
}
while ((curMatch = pinnedPrev[curMatch & DeflaterConstants.WMASK] & 0xFFFF) > limit && --chainLength != 0);
this.matchStart = matchStrt;
this.matchLen = matchLength;
return matchLength >= DeflaterConstants.MIN_MATCH;
}
private bool DeflateStored(bool flush, bool finish)
{
if (!flush && (this.lookahead == 0))
{
return false;
}
this.strstart += this.lookahead;
this.lookahead = 0;
int storedLength = this.strstart - this.blockStart;
if ((storedLength >= DeflaterConstants.MAX_BLOCK_SIZE) || // Block is full
(this.blockStart < DeflaterConstants.WSIZE && storedLength >= DeflaterConstants.MAX_DIST) || // Block may move out of window
flush)
{
bool lastBlock = finish;
if (storedLength > DeflaterConstants.MAX_BLOCK_SIZE)
{
storedLength = DeflaterConstants.MAX_BLOCK_SIZE;
lastBlock = false;
}
this.huffman.FlushStoredBlock(this.window, this.blockStart, storedLength, lastBlock);
this.blockStart += storedLength;
return !(lastBlock || storedLength == 0);
}
return true;
}
private bool DeflateFast(bool flush, bool finish)
{
if (this.lookahead < DeflaterConstants.MIN_LOOKAHEAD && !flush)
{
return false;
}
while (this.lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush)
{
if (this.lookahead == 0)
{
// We are flushing everything
this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, finish);
this.blockStart = this.strstart;
return false;
}
if (this.strstart > (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD)
{
// slide window, as FindLongestMatch needs this.
// This should only happen when flushing and the window
// is almost full.
this.SlideWindow();
}
int hashHead;
if (this.lookahead >= DeflaterConstants.MIN_MATCH &&
(hashHead = this.InsertString()) != 0 &&
this.strategy != DeflateStrategy.HuffmanOnly &&
this.strstart - hashHead <= DeflaterConstants.MAX_DIST &&
this.FindLongestMatch(hashHead))
{
// longestMatch sets matchStart and matchLen
bool full = this.huffman.TallyDist(this.strstart - this.matchStart, this.matchLen);
this.lookahead -= this.matchLen;
if (this.matchLen <= this.maxLazy && this.lookahead >= DeflaterConstants.MIN_MATCH)
{
while (--this.matchLen > 0)
{
++this.strstart;
this.InsertString();
}
++this.strstart;
}
else
{
this.strstart += this.matchLen;
if (this.lookahead >= DeflaterConstants.MIN_MATCH - 1)
{
this.UpdateHash();
}
}
this.matchLen = DeflaterConstants.MIN_MATCH - 1;
if (!full)
{
continue;
}
}
else
{
// No match found
this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart] & 0xff);
++this.strstart;
--this.lookahead;
}
if (this.huffman.IsFull())
{
bool lastBlock = finish && (this.lookahead == 0);
this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, lastBlock);
this.blockStart = this.strstart;
return !lastBlock;
}
}
return true;
}
private bool DeflateSlow(bool flush, bool finish)
{
if (this.lookahead < DeflaterConstants.MIN_LOOKAHEAD && !flush)
{
return false;
}
while (this.lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush)
{
if (this.lookahead == 0)
{
if (this.prevAvailable)
{
this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart - 1] & 0xff);
}
this.prevAvailable = false;
// We are flushing everything
this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, finish);
this.blockStart = this.strstart;
return false;
}
if (this.strstart >= (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD)
{
// slide window, as FindLongestMatch needs this.
// This should only happen when flushing and the window
// is almost full.
this.SlideWindow();
}
int prevMatch = this.matchStart;
int prevLen = this.matchLen;
if (this.lookahead >= DeflaterConstants.MIN_MATCH)
{
int hashHead = this.InsertString();
if (this.strategy != DeflateStrategy.HuffmanOnly &&
hashHead != 0 &&
this.strstart - hashHead <= DeflaterConstants.MAX_DIST &&
this.FindLongestMatch(hashHead))
{
// longestMatch sets matchStart and matchLen
// Discard match if too small and too far away
if (this.matchLen <= 5 && (this.strategy == DeflateStrategy.Filtered || (this.matchLen == DeflaterConstants.MIN_MATCH && this.strstart - this.matchStart > TooFar)))
{
this.matchLen = DeflaterConstants.MIN_MATCH - 1;
}
}
}
// previous match was better
if ((prevLen >= DeflaterConstants.MIN_MATCH) && (this.matchLen <= prevLen))
{
this.huffman.TallyDist(this.strstart - 1 - prevMatch, prevLen);
prevLen -= 2;
do
{
this.strstart++;
this.lookahead--;
if (this.lookahead >= DeflaterConstants.MIN_MATCH)
{
this.InsertString();
}
}
while (--prevLen > 0);
this.strstart++;
this.lookahead--;
this.prevAvailable = false;
this.matchLen = DeflaterConstants.MIN_MATCH - 1;
}
else
{
if (this.prevAvailable)
{
this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart - 1] & 0xff);
}
this.prevAvailable = true;
this.strstart++;
this.lookahead--;
}
if (this.huffman.IsFull())
{
int len = this.strstart - this.blockStart;
if (this.prevAvailable)
{
len--;
}
bool lastBlock = finish && (this.lookahead == 0) && !this.prevAvailable;
this.huffman.FlushBlock(this.window, this.blockStart, len, lastBlock);
this.blockStart += len;
return !lastBlock;
}
}
return true;
}
}
}

949
src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs

@ -0,0 +1,949 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
/// <summary>
/// Performs Deflate Huffman encoding.
/// </summary>
internal sealed unsafe class DeflaterHuffman : IDisposable
{
private const int BufferSize = 1 << (DeflaterConstants.DEFAULT_MEM_LEVEL + 6);
// The number of literal codes.
private const int LiteralNumber = 286;
// Number of distance codes
private const int DistanceNumber = 30;
// Number of codes used to transfer bit lengths
private const int BitLengthNumber = 19;
// Repeat previous bit length 3-6 times (2 bits of repeat count)
private const int Repeat3To6 = 16;
// Repeat a zero length 3-10 times (3 bits of repeat count)
private const int Repeat3To10 = 17;
// Repeat a zero length 11-138 times (7 bits of repeat count)
private const int Repeat11To138 = 18;
private const int EofSymbol = 256;
// The lengths of the bit length codes are sent in order of decreasing
// probability, to avoid transmitting the lengths for unused bit length codes.
private static readonly int[] BitLengthOrder = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
private static readonly byte[] Bit4Reverse = { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 };
private static readonly short[] StaticLCodes;
private static readonly byte[] StaticLLength;
private static readonly short[] StaticDCodes;
private static readonly byte[] StaticDLength;
private Tree literalTree;
private Tree distTree;
private Tree blTree;
// Buffer for distances
private readonly IMemoryOwner<short> distanceManagedBuffer;
private readonly short* pinnedDistanceBuffer;
private MemoryHandle distanceBufferHandle;
private readonly IMemoryOwner<short> literalManagedBuffer;
private readonly short* pinnedLiteralBuffer;
private MemoryHandle literalBufferHandle;
private int lastLiteral;
private int extraBits;
private bool isDisposed;
// TODO: These should be pre-generated array/readonlyspans.
static DeflaterHuffman()
{
// See RFC 1951 3.2.6
// Literal codes
StaticLCodes = new short[LiteralNumber];
StaticLLength = new byte[LiteralNumber];
int i = 0;
while (i < 144)
{
StaticLCodes[i] = BitReverse((0x030 + i) << 8);
StaticLLength[i++] = 8;
}
while (i < 256)
{
StaticLCodes[i] = BitReverse((0x190 - 144 + i) << 7);
StaticLLength[i++] = 9;
}
while (i < 280)
{
StaticLCodes[i] = BitReverse((0x000 - 256 + i) << 9);
StaticLLength[i++] = 7;
}
while (i < LiteralNumber)
{
StaticLCodes[i] = BitReverse((0x0c0 - 280 + i) << 8);
StaticLLength[i++] = 8;
}
// Distance codes
StaticDCodes = new short[DistanceNumber];
StaticDLength = new byte[DistanceNumber];
for (i = 0; i < DistanceNumber; i++)
{
StaticDCodes[i] = BitReverse(i << 11);
StaticDLength[i] = 5;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="DeflaterHuffman"/> class.
/// </summary>
/// <param name="memoryAllocator">The memory allocator to use for buffer allocations.</param>
public DeflaterHuffman(MemoryAllocator memoryAllocator)
{
this.Pending = new DeflaterPendingBuffer(memoryAllocator);
this.literalTree = new Tree(memoryAllocator, LiteralNumber, 257, 15);
this.distTree = new Tree(memoryAllocator, DistanceNumber, 1, 15);
this.blTree = new Tree(memoryAllocator, BitLengthNumber, 4, 7);
this.distanceManagedBuffer = memoryAllocator.Allocate<short>(BufferSize);
this.distanceBufferHandle = this.distanceManagedBuffer.Memory.Pin();
this.pinnedDistanceBuffer = (short*)this.distanceBufferHandle.Pointer;
this.literalManagedBuffer = memoryAllocator.Allocate<short>(BufferSize);
this.literalBufferHandle = this.literalManagedBuffer.Memory.Pin();
this.pinnedLiteralBuffer = (short*)this.literalBufferHandle.Pointer;
}
/// <summary>
/// Gets the pending buffer to use.
/// </summary>
public DeflaterPendingBuffer Pending { get; private set; }
/// <summary>
/// Reset internal state
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void Reset()
{
this.lastLiteral = 0;
this.extraBits = 0;
this.literalTree.Reset();
this.distTree.Reset();
this.blTree.Reset();
}
/// <summary>
/// Write all trees to pending buffer
/// </summary>
/// <param name="blTreeCodes">The number/rank of treecodes to send.</param>
public void SendAllTrees(int blTreeCodes)
{
this.blTree.BuildCodes();
this.literalTree.BuildCodes();
this.distTree.BuildCodes();
this.Pending.WriteBits(this.literalTree.NumCodes - 257, 5);
this.Pending.WriteBits(this.distTree.NumCodes - 1, 5);
this.Pending.WriteBits(blTreeCodes - 4, 4);
for (int rank = 0; rank < blTreeCodes; rank++)
{
this.Pending.WriteBits(this.blTree.Length[BitLengthOrder[rank]], 3);
}
this.literalTree.WriteTree(this.Pending, this.blTree);
this.distTree.WriteTree(this.Pending, this.blTree);
}
/// <summary>
/// Compress current buffer writing data to pending buffer
/// </summary>
public void CompressBlock()
{
DeflaterPendingBuffer pendingBuffer = this.Pending;
short* pinnedDistance = this.pinnedDistanceBuffer;
short* pinnedLiteral = this.pinnedLiteralBuffer;
for (int i = 0; i < this.lastLiteral; i++)
{
int litlen = pinnedLiteral[i] & 0xFF;
int dist = pinnedDistance[i];
if (dist-- != 0)
{
int lc = Lcode(litlen);
this.literalTree.WriteSymbol(pendingBuffer, lc);
int bits = (lc - 261) / 4;
if (bits > 0 && bits <= 5)
{
this.Pending.WriteBits(litlen & ((1 << bits) - 1), bits);
}
int dc = Dcode(dist);
this.distTree.WriteSymbol(pendingBuffer, dc);
bits = (dc >> 1) - 1;
if (bits > 0)
{
this.Pending.WriteBits(dist & ((1 << bits) - 1), bits);
}
}
else
{
this.literalTree.WriteSymbol(pendingBuffer, litlen);
}
}
this.literalTree.WriteSymbol(pendingBuffer, EofSymbol);
}
/// <summary>
/// Flush block to output with no compression
/// </summary>
/// <param name="stored">Data to write</param>
/// <param name="storedOffset">Index of first byte to write</param>
/// <param name="storedLength">Count of bytes to write</param>
/// <param name="lastBlock">True if this is the last block</param>
[MethodImpl(InliningOptions.ShortMethod)]
public void FlushStoredBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock)
{
this.Pending.WriteBits((DeflaterConstants.STORED_BLOCK << 1) + (lastBlock ? 1 : 0), 3);
this.Pending.AlignToByte();
this.Pending.WriteShort(storedLength);
this.Pending.WriteShort(~storedLength);
this.Pending.WriteBlock(stored, storedOffset, storedLength);
this.Reset();
}
/// <summary>
/// Flush block to output with compression
/// </summary>
/// <param name="stored">Data to flush</param>
/// <param name="storedOffset">Index of first byte to flush</param>
/// <param name="storedLength">Count of bytes to flush</param>
/// <param name="lastBlock">True if this is the last block</param>
public void FlushBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock)
{
this.literalTree.Frequencies[EofSymbol]++;
// Build trees
this.literalTree.BuildTree();
this.distTree.BuildTree();
// Calculate bitlen frequency
this.literalTree.CalcBLFreq(this.blTree);
this.distTree.CalcBLFreq(this.blTree);
// Build bitlen tree
this.blTree.BuildTree();
int blTreeCodes = 4;
for (int i = 18; i > blTreeCodes; i--)
{
if (this.blTree.Length[BitLengthOrder[i]] > 0)
{
blTreeCodes = i + 1;
}
}
int opt_len = 14 + (blTreeCodes * 3) + this.blTree.GetEncodedLength()
+ this.literalTree.GetEncodedLength() + this.distTree.GetEncodedLength()
+ this.extraBits;
int static_len = this.extraBits;
ref byte staticLLengthRef = ref MemoryMarshal.GetReference<byte>(StaticLLength);
for (int i = 0; i < LiteralNumber; i++)
{
static_len += this.literalTree.Frequencies[i] * Unsafe.Add(ref staticLLengthRef, i);
}
ref byte staticDLengthRef = ref MemoryMarshal.GetReference<byte>(StaticDLength);
for (int i = 0; i < DistanceNumber; i++)
{
static_len += this.distTree.Frequencies[i] * Unsafe.Add(ref staticDLengthRef, i);
}
if (opt_len >= static_len)
{
// Force static trees
opt_len = static_len;
}
if (storedOffset >= 0 && storedLength + 4 < opt_len >> 3)
{
// Store Block
this.FlushStoredBlock(stored, storedOffset, storedLength, lastBlock);
}
else if (opt_len == static_len)
{
// Encode with static tree
this.Pending.WriteBits((DeflaterConstants.STATIC_TREES << 1) + (lastBlock ? 1 : 0), 3);
this.literalTree.SetStaticCodes(StaticLCodes, StaticLLength);
this.distTree.SetStaticCodes(StaticDCodes, StaticDLength);
this.CompressBlock();
this.Reset();
}
else
{
// Encode with dynamic tree
this.Pending.WriteBits((DeflaterConstants.DYN_TREES << 1) + (lastBlock ? 1 : 0), 3);
this.SendAllTrees(blTreeCodes);
this.CompressBlock();
this.Reset();
}
}
/// <summary>
/// Get value indicating if internal buffer is full
/// </summary>
/// <returns>true if buffer is full</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public bool IsFull() => this.lastLiteral >= BufferSize;
/// <summary>
/// Add literal to buffer
/// </summary>
/// <param name="literal">Literal value to add to buffer.</param>
/// <returns>Value indicating internal buffer is full</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public bool TallyLit(int literal)
{
this.pinnedDistanceBuffer[this.lastLiteral] = 0;
this.pinnedLiteralBuffer[this.lastLiteral++] = (byte)literal;
this.literalTree.Frequencies[literal]++;
return this.IsFull();
}
/// <summary>
/// Add distance code and length to literal and distance trees
/// </summary>
/// <param name="distance">Distance code</param>
/// <param name="length">Length</param>
/// <returns>Value indicating if internal buffer is full</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public bool TallyDist(int distance, int length)
{
this.pinnedDistanceBuffer[this.lastLiteral] = (short)distance;
this.pinnedLiteralBuffer[this.lastLiteral++] = (byte)(length - 3);
int lc = Lcode(length - 3);
this.literalTree.Frequencies[lc]++;
if (lc >= 265 && lc < 285)
{
this.extraBits += (lc - 261) / 4;
}
int dc = Dcode(distance - 1);
this.distTree.Frequencies[dc]++;
if (dc >= 4)
{
this.extraBits += (dc >> 1) - 1;
}
return this.IsFull();
}
/// <summary>
/// Reverse the bits of a 16 bit value.
/// </summary>
/// <param name="toReverse">Value to reverse bits</param>
/// <returns>Value with bits reversed</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static short BitReverse(int toReverse)
{
return (short)(Bit4Reverse[toReverse & 0xF] << 12
| Bit4Reverse[(toReverse >> 4) & 0xF] << 8
| Bit4Reverse[(toReverse >> 8) & 0xF] << 4
| Bit4Reverse[toReverse >> 12]);
}
/// <inheritdoc/>
public void Dispose()
{
if (!this.isDisposed)
{
this.Pending.Dispose();
this.distanceBufferHandle.Dispose();
this.distanceManagedBuffer.Dispose();
this.literalBufferHandle.Dispose();
this.literalManagedBuffer.Dispose();
this.literalTree.Dispose();
this.blTree.Dispose();
this.distTree.Dispose();
this.Pending = null;
this.literalTree = null;
this.blTree = null;
this.distTree = null;
this.isDisposed = true;
}
GC.SuppressFinalize(this);
}
[MethodImpl(InliningOptions.ShortMethod)]
private static int Lcode(int length)
{
if (length == 255)
{
return 285;
}
int code = 257;
while (length >= 8)
{
code += 4;
length >>= 1;
}
return code + length;
}
[MethodImpl(InliningOptions.ShortMethod)]
private static int Dcode(int distance)
{
int code = 0;
while (distance >= 4)
{
code += 2;
distance >>= 1;
}
return code + distance;
}
private sealed class Tree : IDisposable
{
private readonly int minNumCodes;
private readonly int[] bitLengthCounts;
private readonly int maxLength;
private bool isDisposed;
private readonly int elementCount;
private readonly MemoryAllocator memoryAllocator;
private IMemoryOwner<short> codesMemoryOwner;
private MemoryHandle codesMemoryHandle;
private readonly short* codes;
private IMemoryOwner<short> frequenciesMemoryOwner;
private MemoryHandle frequenciesMemoryHandle;
private IManagedByteBuffer lengthsMemoryOwner;
private MemoryHandle lengthsMemoryHandle;
public Tree(MemoryAllocator memoryAllocator, int elements, int minCodes, int maxLength)
{
this.memoryAllocator = memoryAllocator;
this.elementCount = elements;
this.minNumCodes = minCodes;
this.maxLength = maxLength;
this.frequenciesMemoryOwner = memoryAllocator.Allocate<short>(elements);
this.frequenciesMemoryHandle = this.frequenciesMemoryOwner.Memory.Pin();
this.Frequencies = (short*)this.frequenciesMemoryHandle.Pointer;
this.lengthsMemoryOwner = memoryAllocator.AllocateManagedByteBuffer(elements);
this.lengthsMemoryHandle = this.lengthsMemoryOwner.Memory.Pin();
this.Length = (byte*)this.lengthsMemoryHandle.Pointer;
this.codesMemoryOwner = memoryAllocator.Allocate<short>(elements);
this.codesMemoryHandle = this.codesMemoryOwner.Memory.Pin();
this.codes = (short*)this.codesMemoryHandle.Pointer;
// Maxes out at 15.
this.bitLengthCounts = new int[maxLength];
}
public int NumCodes { get; private set; }
public short* Frequencies { get; }
public byte* Length { get; }
/// <summary>
/// Resets the internal state of the tree
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void Reset()
{
this.frequenciesMemoryOwner.Memory.Span.Clear();
this.lengthsMemoryOwner.Memory.Span.Clear();
this.codesMemoryOwner.Memory.Span.Clear();
}
[MethodImpl(InliningOptions.ShortMethod)]
public void WriteSymbol(DeflaterPendingBuffer pendingBuffer, int code)
=> pendingBuffer.WriteBits(this.codes[code] & 0xFFFF, this.Length[code]);
/// <summary>
/// Set static codes and length
/// </summary>
/// <param name="staticCodes">new codes</param>
/// <param name="staticLengths">length for new codes</param>
[MethodImpl(InliningOptions.ShortMethod)]
public void SetStaticCodes(ReadOnlySpan<short> staticCodes, ReadOnlySpan<byte> staticLengths)
{
staticCodes.CopyTo(this.codesMemoryOwner.Memory.Span);
staticLengths.CopyTo(this.lengthsMemoryOwner.Memory.Span);
}
/// <summary>
/// Build dynamic codes and lengths
/// </summary>
public void BuildCodes()
{
// Maxes out at 15 * 4
Span<int> nextCode = stackalloc int[this.maxLength];
ref int nextCodeRef = ref MemoryMarshal.GetReference(nextCode);
ref int bitLengthCountsRef = ref MemoryMarshal.GetReference<int>(this.bitLengthCounts);
int code = 0;
for (int bits = 0; bits < this.maxLength; bits++)
{
Unsafe.Add(ref nextCodeRef, bits) = code;
code += Unsafe.Add(ref bitLengthCountsRef, bits) << (15 - bits);
}
for (int i = 0; i < this.NumCodes; i++)
{
int bits = this.Length[i];
if (bits > 0)
{
this.codes[i] = BitReverse(Unsafe.Add(ref nextCodeRef, bits - 1));
Unsafe.Add(ref nextCodeRef, bits - 1) += 1 << (16 - bits);
}
}
}
public void BuildTree()
{
int numSymbols = this.elementCount;
// heap is a priority queue, sorted by frequency, least frequent
// nodes first. The heap is a binary tree, with the property, that
// the parent node is smaller than both child nodes. This assures
// that the smallest node is the first parent.
//
// The binary tree is encoded in an array: 0 is root node and
// the nodes 2*n+1, 2*n+2 are the child nodes of node n.
// Maxes out at 286 * 4 so too large for the stack.
using (IMemoryOwner<int> heapMemoryOwner = this.memoryAllocator.Allocate<int>(numSymbols))
{
ref int heapRef = ref MemoryMarshal.GetReference(heapMemoryOwner.Memory.Span);
int heapLen = 0;
int maxCode = 0;
for (int n = 0; n < numSymbols; n++)
{
int freq = this.Frequencies[n];
if (freq != 0)
{
// Insert n into heap
int pos = heapLen++;
int ppos;
while (pos > 0 && this.Frequencies[Unsafe.Add(ref heapRef, ppos = (pos - 1) >> 1)] > freq)
{
Unsafe.Add(ref heapRef, pos) = Unsafe.Add(ref heapRef, ppos);
pos = ppos;
}
Unsafe.Add(ref heapRef, pos) = n;
maxCode = n;
}
}
// We could encode a single literal with 0 bits but then we
// don't see the literals. Therefore we force at least two
// literals to avoid this case. We don't care about order in
// this case, both literals get a 1 bit code.
while (heapLen < 2)
{
Unsafe.Add(ref heapRef, heapLen++) = maxCode < 2 ? ++maxCode : 0;
}
this.NumCodes = Math.Max(maxCode + 1, this.minNumCodes);
int numLeafs = heapLen;
int childrenLength = (4 * heapLen) - 2;
using (IMemoryOwner<int> childrenMemoryOwner = this.memoryAllocator.Allocate<int>(childrenLength))
using (IMemoryOwner<int> valuesMemoryOwner = this.memoryAllocator.Allocate<int>((2 * heapLen) - 1))
{
ref int childrenRef = ref MemoryMarshal.GetReference(childrenMemoryOwner.Memory.Span);
ref int valuesRef = ref MemoryMarshal.GetReference(valuesMemoryOwner.Memory.Span);
int numNodes = numLeafs;
for (int i = 0; i < heapLen; i++)
{
int node = Unsafe.Add(ref heapRef, i);
int i2 = 2 * i;
Unsafe.Add(ref childrenRef, i2) = node;
Unsafe.Add(ref childrenRef, i2 + 1) = -1;
Unsafe.Add(ref valuesRef, i) = this.Frequencies[node] << 8;
Unsafe.Add(ref heapRef, i) = i;
}
// Construct the Huffman tree by repeatedly combining the least two
// frequent nodes.
do
{
int first = Unsafe.Add(ref heapRef, 0);
int last = Unsafe.Add(ref heapRef, --heapLen);
// Propagate the hole to the leafs of the heap
int ppos = 0;
int path = 1;
while (path < heapLen)
{
if (path + 1 < heapLen && Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, path)) > Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, path + 1)))
{
path++;
}
Unsafe.Add(ref heapRef, ppos) = Unsafe.Add(ref heapRef, path);
ppos = path;
path = (path * 2) + 1;
}
// Now propagate the last element down along path. Normally
// it shouldn't go too deep.
int lastVal = Unsafe.Add(ref valuesRef, last);
while ((path = ppos) > 0
&& Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, ppos = (path - 1) >> 1)) > lastVal)
{
Unsafe.Add(ref heapRef, path) = Unsafe.Add(ref heapRef, ppos);
}
Unsafe.Add(ref heapRef, path) = last;
int second = Unsafe.Add(ref heapRef, 0);
// Create a new node father of first and second
last = numNodes++;
Unsafe.Add(ref childrenRef, 2 * last) = first;
Unsafe.Add(ref childrenRef, (2 * last) + 1) = second;
int mindepth = Math.Min(Unsafe.Add(ref valuesRef, first) & 0xFF, Unsafe.Add(ref valuesRef, second) & 0xFF);
Unsafe.Add(ref valuesRef, last) = lastVal = Unsafe.Add(ref valuesRef, first) + Unsafe.Add(ref valuesRef, second) - mindepth + 1;
// Again, propagate the hole to the leafs
ppos = 0;
path = 1;
while (path < heapLen)
{
if (path + 1 < heapLen
&& Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, path)) > Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, path + 1)))
{
path++;
}
Unsafe.Add(ref heapRef, ppos) = Unsafe.Add(ref heapRef, path);
ppos = path;
path = (ppos * 2) + 1;
}
// Now propagate the new element down along path
while ((path = ppos) > 0 && Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, ppos = (path - 1) >> 1)) > lastVal)
{
Unsafe.Add(ref heapRef, path) = Unsafe.Add(ref heapRef, ppos);
}
Unsafe.Add(ref heapRef, path) = last;
}
while (heapLen > 1);
if (Unsafe.Add(ref heapRef, 0) != (childrenLength >> 1) - 1)
{
DeflateThrowHelper.ThrowHeapViolated();
}
this.BuildLength(childrenMemoryOwner.Memory.Span);
}
}
}
/// <summary>
/// Get encoded length
/// </summary>
/// <returns>Encoded length, the sum of frequencies * lengths</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetEncodedLength()
{
int len = 0;
for (int i = 0; i < this.elementCount; i++)
{
len += this.Frequencies[i] * this.Length[i];
}
return len;
}
/// <summary>
/// Scan a literal or distance tree to determine the frequencies of the codes
/// in the bit length tree.
/// </summary>
public void CalcBLFreq(Tree blTree)
{
int maxCount; // max repeat count
int minCount; // min repeat count
int count; // repeat count of the current code
int curLen = -1; // length of current code
int i = 0;
while (i < this.NumCodes)
{
count = 1;
int nextlen = this.Length[i];
if (nextlen == 0)
{
maxCount = 138;
minCount = 3;
}
else
{
maxCount = 6;
minCount = 3;
if (curLen != nextlen)
{
blTree.Frequencies[nextlen]++;
count = 0;
}
}
curLen = nextlen;
i++;
while (i < this.NumCodes && curLen == this.Length[i])
{
i++;
if (++count >= maxCount)
{
break;
}
}
if (count < minCount)
{
blTree.Frequencies[curLen] += (short)count;
}
else if (curLen != 0)
{
blTree.Frequencies[Repeat3To6]++;
}
else if (count <= 10)
{
blTree.Frequencies[Repeat3To10]++;
}
else
{
blTree.Frequencies[Repeat11To138]++;
}
}
}
/// <summary>
/// Write the tree values.
/// </summary>
/// <param name="pendingBuffer">The pending buffer.</param>
/// <param name="bitLengthTree">The tree to write.</param>
public void WriteTree(DeflaterPendingBuffer pendingBuffer, Tree bitLengthTree)
{
int maxCount; // max repeat count
int minCount; // min repeat count
int count; // repeat count of the current code
int curLen = -1; // length of current code
int i = 0;
while (i < this.NumCodes)
{
count = 1;
int nextlen = this.Length[i];
if (nextlen == 0)
{
maxCount = 138;
minCount = 3;
}
else
{
maxCount = 6;
minCount = 3;
if (curLen != nextlen)
{
bitLengthTree.WriteSymbol(pendingBuffer, nextlen);
count = 0;
}
}
curLen = nextlen;
i++;
while (i < this.NumCodes && curLen == this.Length[i])
{
i++;
if (++count >= maxCount)
{
break;
}
}
if (count < minCount)
{
while (count-- > 0)
{
bitLengthTree.WriteSymbol(pendingBuffer, curLen);
}
}
else if (curLen != 0)
{
bitLengthTree.WriteSymbol(pendingBuffer, Repeat3To6);
pendingBuffer.WriteBits(count - 3, 2);
}
else if (count <= 10)
{
bitLengthTree.WriteSymbol(pendingBuffer, Repeat3To10);
pendingBuffer.WriteBits(count - 3, 3);
}
else
{
bitLengthTree.WriteSymbol(pendingBuffer, Repeat11To138);
pendingBuffer.WriteBits(count - 11, 7);
}
}
}
private void BuildLength(ReadOnlySpan<int> children)
{
byte* lengthPtr = this.Length;
ref int childrenRef = ref MemoryMarshal.GetReference(children);
ref int bitLengthCountsRef = ref MemoryMarshal.GetReference<int>(this.bitLengthCounts);
int maxLen = this.maxLength;
int numNodes = children.Length >> 1;
int numLeafs = (numNodes + 1) >> 1;
int overflow = 0;
Array.Clear(this.bitLengthCounts, 0, maxLen);
// First calculate optimal bit lengths
using (IMemoryOwner<int> lengthsMemoryOwner = this.memoryAllocator.Allocate<int>(numNodes, AllocationOptions.Clean))
{
ref int lengthsRef = ref MemoryMarshal.GetReference(lengthsMemoryOwner.Memory.Span);
for (int i = numNodes - 1; i >= 0; i--)
{
if (children[(2 * i) + 1] != -1)
{
int bitLength = Unsafe.Add(ref lengthsRef, i) + 1;
if (bitLength > maxLen)
{
bitLength = maxLen;
overflow++;
}
Unsafe.Add(ref lengthsRef, Unsafe.Add(ref childrenRef, 2 * i)) = Unsafe.Add(ref lengthsRef, Unsafe.Add(ref childrenRef, (2 * i) + 1)) = bitLength;
}
else
{
// A leaf node
int bitLength = Unsafe.Add(ref lengthsRef, i);
Unsafe.Add(ref bitLengthCountsRef, bitLength - 1)++;
lengthPtr[Unsafe.Add(ref childrenRef, 2 * i)] = (byte)Unsafe.Add(ref lengthsRef, i);
}
}
}
if (overflow == 0)
{
return;
}
int incrBitLen = maxLen - 1;
do
{
// Find the first bit length which could increase:
while (Unsafe.Add(ref bitLengthCountsRef, --incrBitLen) == 0)
{
}
// Move this node one down and remove a corresponding
// number of overflow nodes.
do
{
Unsafe.Add(ref bitLengthCountsRef, incrBitLen)--;
Unsafe.Add(ref bitLengthCountsRef, ++incrBitLen)++;
overflow -= 1 << (maxLen - 1 - incrBitLen);
}
while (overflow > 0 && incrBitLen < maxLen - 1);
}
while (overflow > 0);
// We may have overshot above. Move some nodes from maxLength to
// maxLength-1 in that case.
Unsafe.Add(ref bitLengthCountsRef, maxLen - 1) += overflow;
Unsafe.Add(ref bitLengthCountsRef, maxLen - 2) -= overflow;
// Now recompute all bit lengths, scanning in increasing
// frequency. It is simpler to reconstruct all lengths instead of
// fixing only the wrong ones. This idea is taken from 'ar'
// written by Haruhiko Okumura.
//
// The nodes were inserted with decreasing frequency into the childs
// array.
int nodeIndex = 2 * numLeafs;
for (int bits = maxLen; bits != 0; bits--)
{
int n = Unsafe.Add(ref bitLengthCountsRef, bits - 1);
while (n > 0)
{
int childIndex = 2 * Unsafe.Add(ref childrenRef, nodeIndex++);
if (Unsafe.Add(ref childrenRef, childIndex + 1) == -1)
{
// We found another leaf
lengthPtr[Unsafe.Add(ref childrenRef, childIndex)] = (byte)bits;
n--;
}
}
}
}
public void Dispose()
{
if (!this.isDisposed)
{
this.frequenciesMemoryHandle.Dispose();
this.frequenciesMemoryOwner.Dispose();
this.lengthsMemoryHandle.Dispose();
this.lengthsMemoryOwner.Dispose();
this.codesMemoryHandle.Dispose();
this.codesMemoryOwner.Dispose();
this.frequenciesMemoryOwner = null;
this.lengthsMemoryOwner = null;
this.codesMemoryOwner = null;
this.isDisposed = true;
}
GC.SuppressFinalize(this);
}
}
}
}

153
src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs

@ -0,0 +1,153 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
/// <summary>
/// A special stream deflating or compressing the bytes that are
/// written to it. It uses a Deflater to perform actual deflating.
/// </summary>
internal sealed class DeflaterOutputStream : Stream
{
private const int BufferLength = 512;
private IManagedByteBuffer memoryOwner;
private readonly byte[] buffer;
private Deflater deflater;
private readonly Stream rawStream;
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="DeflaterOutputStream"/> class.
/// </summary>
/// <param name="memoryAllocator">The memory allocator to use for buffer allocations.</param>
/// <param name="rawStream">The output stream where deflated output is written.</param>
/// <param name="compressionLevel">The compression level.</param>
public DeflaterOutputStream(MemoryAllocator memoryAllocator, Stream rawStream, int compressionLevel)
{
this.rawStream = rawStream;
this.memoryOwner = memoryAllocator.AllocateManagedByteBuffer(BufferLength);
this.buffer = this.memoryOwner.Array;
this.deflater = new Deflater(memoryAllocator, compressionLevel);
}
/// <inheritdoc/>
public override bool CanRead => false;
/// <inheritdoc/>
public override bool CanSeek => false;
/// <inheritdoc/>
public override bool CanWrite => this.rawStream.CanWrite;
/// <inheritdoc/>
public override long Length => this.rawStream.Length;
/// <inheritdoc/>
public override long Position
{
get
{
return this.rawStream.Position;
}
set
{
throw new NotSupportedException();
}
}
/// <inheritdoc/>
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
/// <inheritdoc/>
public override void SetLength(long value) => throw new NotSupportedException();
/// <inheritdoc/>
public override int ReadByte() => throw new NotSupportedException();
/// <inheritdoc/>
public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException();
/// <inheritdoc/>
public override void Flush()
{
this.deflater.Flush();
this.Deflate(true);
this.rawStream.Flush();
}
/// <inheritdoc/>
public override void Write(byte[] buffer, int offset, int count)
{
this.deflater.SetInput(buffer, offset, count);
this.Deflate();
}
private void Deflate() => this.Deflate(false);
private void Deflate(bool flushing)
{
while (flushing || !this.deflater.IsNeedingInput)
{
int deflateCount = this.deflater.Deflate(this.buffer, 0, BufferLength);
if (deflateCount <= 0)
{
break;
}
this.rawStream.Write(this.buffer, 0, deflateCount);
}
if (!this.deflater.IsNeedingInput)
{
DeflateThrowHelper.ThrowNoDeflate();
}
}
private void Finish()
{
this.deflater.Finish();
while (!this.deflater.IsFinished)
{
int len = this.deflater.Deflate(this.buffer, 0, BufferLength);
if (len <= 0)
{
break;
}
this.rawStream.Write(this.buffer, 0, len);
}
if (!this.deflater.IsFinished)
{
DeflateThrowHelper.ThrowNoDeflate();
}
this.rawStream.Flush();
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (!this.isDisposed)
{
if (disposing)
{
this.Finish();
this.deflater.Dispose();
this.memoryOwner.Dispose();
}
this.deflater = null;
this.memoryOwner = null;
this.isDisposed = true;
base.Dispose(disposing);
}
}
}
}

179
src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs

@ -0,0 +1,179 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
/// <summary>
/// Stores pending data for writing data to the Deflater.
/// </summary>
internal sealed unsafe class DeflaterPendingBuffer : IDisposable
{
private readonly byte[] buffer;
private readonly byte* pinnedBuffer;
private IManagedByteBuffer bufferMemoryOwner;
private MemoryHandle bufferMemoryHandle;
private int start;
private int end;
private uint bits;
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="DeflaterPendingBuffer"/> class.
/// </summary>
/// <param name="memoryAllocator">The memory allocator to use for buffer allocations.</param>
public DeflaterPendingBuffer(MemoryAllocator memoryAllocator)
{
this.bufferMemoryOwner = memoryAllocator.AllocateManagedByteBuffer(DeflaterConstants.PENDING_BUF_SIZE);
this.buffer = this.bufferMemoryOwner.Array;
this.bufferMemoryHandle = this.bufferMemoryOwner.Memory.Pin();
this.pinnedBuffer = (byte*)this.bufferMemoryHandle.Pointer;
}
/// <summary>
/// Gets the number of bits written to the buffer.
/// </summary>
public int BitCount { get; private set; }
/// <summary>
/// Gets a value indicating whether indicates the buffer has been flushed.
/// </summary>
public bool IsFlushed => this.end == 0;
/// <summary>
/// Clear internal state/buffers.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void Reset() => this.start = this.end = this.BitCount = 0;
/// <summary>
/// Write a short value to buffer LSB first.
/// </summary>
/// <param name="value">The value to write.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public void WriteShort(int value)
{
byte* pinned = this.pinnedBuffer;
pinned[this.end++] = unchecked((byte)value);
pinned[this.end++] = unchecked((byte)(value >> 8));
}
/// <summary>
/// Write a block of data to the internal buffer.
/// </summary>
/// <param name="block">The data to write.</param>
/// <param name="offset">The offset of first byte to write.</param>
/// <param name="length">The number of bytes to write.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public void WriteBlock(byte[] block, int offset, int length)
{
Unsafe.CopyBlockUnaligned(ref this.buffer[this.end], ref block[offset], unchecked((uint)length));
this.end += length;
}
/// <summary>
/// Aligns internal buffer on a byte boundary.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void AlignToByte()
{
if (this.BitCount > 0)
{
byte* pinned = this.pinnedBuffer;
pinned[this.end++] = unchecked((byte)this.bits);
if (this.BitCount > 8)
{
pinned[this.end++] = unchecked((byte)(this.bits >> 8));
}
}
this.bits = 0;
this.BitCount = 0;
}
/// <summary>
/// Write bits to internal buffer
/// </summary>
/// <param name="b">source of bits</param>
/// <param name="count">number of bits to write</param>
[MethodImpl(InliningOptions.ShortMethod)]
public void WriteBits(int b, int count)
{
this.bits |= (uint)(b << this.BitCount);
this.BitCount += count;
if (this.BitCount >= 16)
{
byte* pinned = this.pinnedBuffer;
pinned[this.end++] = unchecked((byte)this.bits);
pinned[this.end++] = unchecked((byte)(this.bits >> 8));
this.bits >>= 16;
this.BitCount -= 16;
}
}
/// <summary>
/// Write a short value to internal buffer most significant byte first
/// </summary>
/// <param name="value">The value to write</param>
[MethodImpl(InliningOptions.ShortMethod)]
public void WriteShortMSB(int value)
{
byte* pinned = this.pinnedBuffer;
pinned[this.end++] = unchecked((byte)(value >> 8));
pinned[this.end++] = unchecked((byte)value);
}
/// <summary>
/// Flushes the pending buffer into the given output array.
/// If the output array is to small, only a partial flush is done.
/// </summary>
/// <param name="output">The output array.</param>
/// <param name="offset">The offset into output array.</param>
/// <param name="length">The maximum number of bytes to store.</param>
/// <returns>The number of bytes flushed.</returns>
public int Flush(byte[] output, int offset, int length)
{
if (this.BitCount >= 8)
{
this.pinnedBuffer[this.end++] = unchecked((byte)this.bits);
this.bits >>= 8;
this.BitCount -= 8;
}
if (length > this.end - this.start)
{
length = this.end - this.start;
Unsafe.CopyBlockUnaligned(ref output[offset], ref this.buffer[this.start], unchecked((uint)length));
this.start = 0;
this.end = 0;
}
else
{
Unsafe.CopyBlockUnaligned(ref output[offset], ref this.buffer[this.start], unchecked((uint)length));
this.start += length;
}
return length;
}
/// <inheritdoc/>
public void Dispose()
{
if (!this.isDisposed)
{
this.bufferMemoryHandle.Dispose();
this.bufferMemoryOwner.Dispose();
this.bufferMemoryOwner = null;
this.isDisposed = true;
}
GC.SuppressFinalize(this);
}
}
}

7
src/ImageSharp/Formats/Png/Zlib/README.md

@ -1,2 +1,5 @@
Adler32.cs and Crc32.cs have been copied from
https://github.com/ygrenier/SharpZipLib.Portable
Deflatestream implementation adapted from
https://github.com/icsharpcode/SharpZipLib
LIcensed under MIT

83
src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs

@ -1,9 +1,9 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using System.IO.Compression;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
@ -38,14 +38,16 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <summary>
/// The stream responsible for compressing the input stream.
/// </summary>
private System.IO.Compression.DeflateStream deflateStream;
// private DeflateStream deflateStream;
private DeflaterOutputStream deflateStream;
/// <summary>
/// Initializes a new instance of the <see cref="ZlibDeflateStream"/> class.
/// </summary>
/// <param name="memoryAllocator">The memory allocator to use for buffer allocations.</param>
/// <param name="stream">The stream to compress.</param>
/// <param name="compressionLevel">The compression level.</param>
public ZlibDeflateStream(Stream stream, int compressionLevel)
public ZlibDeflateStream(MemoryAllocator memoryAllocator, Stream stream, int compressionLevel)
{
this.rawStream = stream;
@ -60,7 +62,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
// +---+---+
// |CMF|FLG|
// +---+---+
int cmf = 0x78;
const int Cmf = 0x78;
int flg = 218;
// http://stackoverflow.com/a/2331025/277304
@ -78,29 +80,17 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
}
// Just in case
flg -= ((cmf * 256) + flg) % 31;
flg -= ((Cmf * 256) + flg) % 31;
if (flg < 0)
{
flg += 31;
}
this.rawStream.WriteByte((byte)cmf);
this.rawStream.WriteByte(Cmf);
this.rawStream.WriteByte((byte)flg);
// Initialize the deflate Stream.
CompressionLevel level = CompressionLevel.Optimal;
if (compressionLevel >= 1 && compressionLevel <= 5)
{
level = CompressionLevel.Fastest;
}
else if (compressionLevel == 0)
{
level = CompressionLevel.NoCompression;
}
this.deflateStream = new System.IO.Compression.DeflateStream(this.rawStream, level, true);
this.deflateStream = new DeflaterOutputStream(memoryAllocator, this.rawStream, compressionLevel);
}
/// <inheritdoc/>
@ -110,41 +100,36 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
public override bool CanSeek => false;
/// <inheritdoc/>
public override bool CanWrite => true;
public override bool CanWrite => this.rawStream.CanWrite;
/// <inheritdoc/>
public override long Length => throw new NotSupportedException();
public override long Length => this.rawStream.Length;
/// <inheritdoc/>
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
get
{
return this.rawStream.Position;
}
set
{
throw new NotSupportedException();
}
}
/// <inheritdoc/>
public override void Flush()
{
this.deflateStream?.Flush();
}
public override void Flush() => this.deflateStream.Flush();
/// <inheritdoc/>
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException();
/// <inheritdoc/>
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
/// <inheritdoc/>
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override void SetLength(long value) => throw new NotSupportedException();
/// <inheritdoc/>
public override void Write(byte[] buffer, int offset, int count)
@ -164,17 +149,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
if (disposing)
{
// dispose managed resources
if (this.deflateStream != null)
{
this.deflateStream.Dispose();
this.deflateStream = null;
}
else
{
// Hack: empty input?
this.rawStream.WriteByte(3);
this.rawStream.WriteByte(0);
}
this.deflateStream.Dispose();
// Add the crc
uint crc = (uint)this.adler32.Value;
@ -184,11 +159,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.rawStream.WriteByte((byte)(crc & 0xFF));
}
base.Dispose(disposing);
this.deflateStream = null;
// Call the appropriate methods to clean up
// unmanaged resources here.
// Note disposing is done.
base.Dispose(disposing);
this.isDisposed = true;
}
}

6
src/ImageSharp/Formats/README.md

@ -0,0 +1,6 @@
# Encoder/Decoder for true vision targa files
Useful links for reference:
- [FileFront](https://www.fileformat.info/format/tga/egff.htm)
- [Tga Specification](http://www.dca.fee.unicamp.br/~martino/disciplinas/ea978/tgaffs.pdf)

12
src/ImageSharp/Formats/Tga/ITgaDecoderOptions.cs

@ -0,0 +1,12 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tga
{
/// <summary>
/// The options for decoding tga images. Currently empty, but this may change in the future.
/// </summary>
internal interface ITgaDecoderOptions
{
}
}

21
src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs

@ -0,0 +1,21 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tga
{
/// <summary>
/// Configuration options for use during tga encoding.
/// </summary>
internal interface ITgaEncoderOptions
{
/// <summary>
/// Gets the number of bits per pixel.
/// </summary>
TgaBitsPerPixel? BitsPerPixel { get; }
/// <summary>
/// Gets a value indicating whether run length compression should be used.
/// </summary>
TgaCompression Compression { get; }
}
}

BIN
src/ImageSharp/Formats/Tga/TGA_Specification.pdf

Binary file not shown.

31
src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs

@ -0,0 +1,31 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tga
{
/// <summary>
/// Enumerates the available bits per pixel the tga encoder supports.
/// </summary>
public enum TgaBitsPerPixel : byte
{
/// <summary>
/// 8 bits per pixel. Each pixel consists of 1 byte.
/// </summary>
Pixel8 = 8,
/// <summary>
/// 16 bits per pixel. Each pixel consists of 2 bytes.
/// </summary>
Pixel16 = 16,
/// <summary>
/// 24 bits per pixel. Each pixel consists of 3 bytes.
/// </summary>
Pixel24 = 24,
/// <summary>
/// 32 bits per pixel. Each pixel consists of 4 bytes.
/// </summary>
Pixel32 = 32
}
}

21
src/ImageSharp/Formats/Tga/TgaCompression.cs

@ -0,0 +1,21 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tga
{
/// <summary>
/// Indicates if compression is used.
/// </summary>
public enum TgaCompression
{
/// <summary>
/// No compression is used.
/// </summary>
None,
/// <summary>
/// Run length encoding is used.
/// </summary>
RunLength,
}
}

19
src/ImageSharp/Formats/Tga/TgaConfigurationModule.cs

@ -0,0 +1,19 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tga
{
/// <summary>
/// Registers the image encoders, decoders and mime type detectors for the tga format.
/// </summary>
public sealed class TgaConfigurationModule : IConfigurationModule
{
/// <inheritdoc/>
public void Configure(Configuration configuration)
{
configuration.ImageFormatsManager.SetEncoder(TgaFormat.Instance, new TgaEncoder());
configuration.ImageFormatsManager.SetDecoder(TgaFormat.Instance, new TgaDecoder());
configuration.ImageFormatsManager.AddImageFormatDetector(new TgaImageFormatDetector());
}
}
}

25
src/ImageSharp/Formats/Tga/TgaConstants.cs

@ -0,0 +1,25 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
namespace SixLabors.ImageSharp.Formats.Tga
{
internal static class TgaConstants
{
/// <summary>
/// The list of mimetypes that equate to a targa file.
/// </summary>
public static readonly IEnumerable<string> MimeTypes = new[] { "image/x-tga", "image/x-targa" };
/// <summary>
/// The list of file extensions that equate to a targa file.
/// </summary>
public static readonly IEnumerable<string> FileExtensions = new[] { "tga", "vda", "icb", "vst" };
/// <summary>
/// The file header length of a tga image in bytes.
/// </summary>
public const int FileHeaderLength = 18;
}
}

34
src/ImageSharp/Formats/Tga/TgaDecoder.cs

@ -0,0 +1,34 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tga
{
/// <summary>
/// Image decoder for Truevision TGA images.
/// </summary>
public sealed class TgaDecoder : IImageDecoder, ITgaDecoderOptions, IImageInfoDetector
{
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
return new TgaDecoderCore(configuration, this).Decode<TPixel>(stream);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
return new TgaDecoderCore(configuration, this).Identify(stream);
}
}
}

588
src/ImageSharp/Formats/Tga/TgaDecoderCore.cs

@ -0,0 +1,588 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Tga
{
internal sealed class TgaDecoderCore
{
/// <summary>
/// The metadata.
/// </summary>
private ImageMetadata metadata;
/// <summary>
/// The tga specific metadata.
/// </summary>
private TgaMetadata tgaMetadata;
/// <summary>
/// The file header containing general information about the image.
/// </summary>
private TgaFileHeader fileHeader;
/// <summary>
/// The global configuration.
/// </summary>
private readonly Configuration configuration;
/// <summary>
/// Used for allocating memory during processing operations.
/// </summary>
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// The stream to decode from.
/// </summary>
private Stream currentStream;
/// <summary>
/// The bitmap decoder options.
/// </summary>
private readonly ITgaDecoderOptions options;
/// <summary>
/// Initializes a new instance of the <see cref="TgaDecoderCore"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The options.</param>
public TgaDecoderCore(Configuration configuration, ITgaDecoderOptions options)
{
this.configuration = configuration;
this.memoryAllocator = configuration.MemoryAllocator;
this.options = options;
}
/// <summary>
/// Decodes the image from the specified stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The stream, where the image should be decoded from. Cannot be null.</param>
/// <exception cref="System.ArgumentNullException">
/// <para><paramref name="stream"/> is null.</para>
/// </exception>
/// <returns>The decoded image.</returns>
public Image<TPixel> Decode<TPixel>(Stream stream)
where TPixel : struct, IPixel<TPixel>
{
try
{
bool inverted = this.ReadFileHeader(stream);
this.currentStream.Skip(this.fileHeader.IdLength);
// Parse the color map, if present.
if (this.fileHeader.ColorMapType != 0 && this.fileHeader.ColorMapType != 1)
{
TgaThrowHelper.ThrowNotSupportedException($"Unknown tga colormap type {this.fileHeader.ColorMapType} found");
}
if (this.fileHeader.Width == 0 || this.fileHeader.Height == 0)
{
throw new UnknownImageFormatException("Width or height cannot be 0");
}
var image = new Image<TPixel>(this.configuration, this.fileHeader.Width, this.fileHeader.Height, this.metadata);
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
if (this.fileHeader.ColorMapType is 1)
{
if (this.fileHeader.CMapLength <= 0)
{
TgaThrowHelper.ThrowImageFormatException("Missing tga color map length");
}
if (this.fileHeader.CMapDepth <= 0)
{
TgaThrowHelper.ThrowImageFormatException("Missing tga color map depth");
}
int colorMapPixelSizeInBytes = this.fileHeader.CMapDepth / 8;
int colorMapSizeInBytes = this.fileHeader.CMapLength * colorMapPixelSizeInBytes;
using (IManagedByteBuffer palette = this.memoryAllocator.AllocateManagedByteBuffer(colorMapSizeInBytes, AllocationOptions.Clean))
{
this.currentStream.Read(palette.Array, this.fileHeader.CMapStart, colorMapSizeInBytes);
if (this.fileHeader.ImageType is TgaImageType.RleColorMapped)
{
this.ReadPalettedRle(
this.fileHeader.Width,
this.fileHeader.Height,
pixels,
palette.Array,
colorMapPixelSizeInBytes,
inverted);
}
else
{
this.ReadPaletted(
this.fileHeader.Width,
this.fileHeader.Height,
pixels,
palette.Array,
colorMapPixelSizeInBytes,
inverted);
}
}
return image;
}
// Even if the image type indicates it is not a paletted image, it can still contain a palette. Skip those bytes.
if (this.fileHeader.CMapLength > 0)
{
int colorMapPixelSizeInBytes = this.fileHeader.CMapDepth / 8;
this.currentStream.Skip(this.fileHeader.CMapLength * colorMapPixelSizeInBytes);
}
switch (this.fileHeader.PixelDepth)
{
case 8:
if (this.fileHeader.ImageType.IsRunLengthEncoded())
{
this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 1, inverted);
}
else
{
this.ReadMonoChrome(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted);
}
break;
case 15:
case 16:
if (this.fileHeader.ImageType.IsRunLengthEncoded())
{
this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 2, inverted);
}
else
{
this.ReadBgra16(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted);
}
break;
case 24:
if (this.fileHeader.ImageType.IsRunLengthEncoded())
{
this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 3, inverted);
}
else
{
this.ReadBgr24(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted);
}
break;
case 32:
if (this.fileHeader.ImageType.IsRunLengthEncoded())
{
this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 4, inverted);
}
else
{
this.ReadBgra32(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted);
}
break;
default:
TgaThrowHelper.ThrowNotSupportedException("Does not support this kind of tga files.");
break;
}
return image;
}
catch (IndexOutOfRangeException e)
{
throw new ImageFormatException("TGA image does not have a valid format.", e);
}
}
/// <summary>
/// Reads a uncompressed TGA image with a palette.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="palette">The color palette.</param>
/// <param name="colorMapPixelSizeInBytes">Color map size of one entry in bytes.</param>
/// <param name="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
private void ReadPaletted<TPixel>(int width, int height, Buffer2D<TPixel> pixels, byte[] palette, int colorMapPixelSizeInBytes, bool inverted)
where TPixel : struct, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.memoryAllocator.AllocateManagedByteBuffer(width, AllocationOptions.Clean))
{
TPixel color = default;
Span<byte> rowSpan = row.GetSpan();
for (int y = 0; y < height; y++)
{
this.currentStream.Read(row);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
switch (colorMapPixelSizeInBytes)
{
case 2:
for (int x = 0; x < width; x++)
{
int colorIndex = rowSpan[x];
// Set alpha value to 1, to treat it as opaque for Bgra5551.
Bgra5551 bgra = Unsafe.As<byte, Bgra5551>(ref palette[colorIndex * colorMapPixelSizeInBytes]);
bgra.PackedValue = (ushort)(bgra.PackedValue | 0x8000);
color.FromBgra5551(bgra);
pixelRow[x] = color;
}
break;
case 3:
for (int x = 0; x < width; x++)
{
int colorIndex = rowSpan[x];
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref palette[colorIndex * colorMapPixelSizeInBytes]));
pixelRow[x] = color;
}
break;
case 4:
for (int x = 0; x < width; x++)
{
int colorIndex = rowSpan[x];
color.FromBgra32(Unsafe.As<byte, Bgra32>(ref palette[colorIndex * colorMapPixelSizeInBytes]));
pixelRow[x] = color;
}
break;
}
}
}
}
/// <summary>
/// Reads a run length encoded TGA image with a palette.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="palette">The color palette.</param>
/// <param name="colorMapPixelSizeInBytes">Color map size of one entry in bytes.</param>
/// <param name="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
private void ReadPalettedRle<TPixel>(int width, int height, Buffer2D<TPixel> pixels, byte[] palette, int colorMapPixelSizeInBytes, bool inverted)
where TPixel : struct, IPixel<TPixel>
{
int bytesPerPixel = 1;
using (IMemoryOwner<byte> buffer = this.memoryAllocator.Allocate<byte>(width * height * bytesPerPixel, AllocationOptions.Clean))
{
TPixel color = default;
Span<byte> bufferSpan = buffer.GetSpan();
this.UncompressRle(width, height, bufferSpan, bytesPerPixel: 1);
for (int y = 0; y < height; y++)
{
int newY = Invert(y, height, inverted);
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
int rowStartIdx = y * width * bytesPerPixel;
for (int x = 0; x < width; x++)
{
int idx = rowStartIdx + x;
switch (colorMapPixelSizeInBytes)
{
case 1:
color.FromGray8(Unsafe.As<byte, Gray8>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]));
break;
case 2:
// Set alpha value to 1, to treat it as opaque for Bgra5551.
Bgra5551 bgra = Unsafe.As<byte, Bgra5551>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]);
bgra.PackedValue = (ushort)(bgra.PackedValue | 0x8000);
color.FromBgra5551(bgra);
break;
case 3:
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]));
break;
case 4:
color.FromBgra32(Unsafe.As<byte, Bgra32>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]));
break;
}
pixelRow[x] = color;
}
}
}
}
/// <summary>
/// Reads a uncompressed monochrome TGA image.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
private void ReadMonoChrome<TPixel>(int width, int height, Buffer2D<TPixel> pixels, bool inverted)
where TPixel : struct, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 1, 0))
{
for (int y = 0; y < height; y++)
{
this.currentStream.Read(row);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromGray8Bytes(
this.configuration,
row.GetSpan(),
pixelSpan,
width);
}
}
}
/// <summary>
/// Reads a uncompressed TGA image where each pixels has 16 bit.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
private void ReadBgra16<TPixel>(int width, int height, Buffer2D<TPixel> pixels, bool inverted)
where TPixel : struct, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0))
{
for (int y = 0; y < height; y++)
{
this.currentStream.Read(row);
Span<byte> rowSpan = row.GetSpan();
// We need to set each alpha component value to fully opaque.
for (int x = 1; x < rowSpan.Length; x += 2)
{
rowSpan[x] = (byte)(rowSpan[x] | (1 << 7));
}
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgra5551Bytes(
this.configuration,
rowSpan,
pixelSpan,
width);
}
}
}
/// <summary>
/// Reads a uncompressed TGA image where each pixels has 24 bit.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
private void ReadBgr24<TPixel>(int width, int height, Buffer2D<TPixel> pixels, bool inverted)
where TPixel : struct, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, 0))
{
for (int y = 0; y < height; y++)
{
this.currentStream.Read(row);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgr24Bytes(
this.configuration,
row.GetSpan(),
pixelSpan,
width);
}
}
}
/// <summary>
/// Reads a uncompressed TGA image where each pixels has 32 bit.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
private void ReadBgra32<TPixel>(int width, int height, Buffer2D<TPixel> pixels, bool inverted)
where TPixel : struct, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, 0))
{
for (int y = 0; y < height; y++)
{
this.currentStream.Read(row);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgra32Bytes(
this.configuration,
row.GetSpan(),
pixelSpan,
width);
}
}
}
/// <summary>
/// Reads a run length encoded TGA image.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <param name="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
private void ReadRle<TPixel>(int width, int height, Buffer2D<TPixel> pixels, int bytesPerPixel, bool inverted)
where TPixel : struct, IPixel<TPixel>
{
TPixel color = default;
using (IMemoryOwner<byte> buffer = this.memoryAllocator.Allocate<byte>(width * height * bytesPerPixel, AllocationOptions.Clean))
{
Span<byte> bufferSpan = buffer.GetSpan();
this.UncompressRle(width, height, bufferSpan, bytesPerPixel);
for (int y = 0; y < height; y++)
{
int newY = Invert(y, height, inverted);
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
int rowStartIdx = y * width * bytesPerPixel;
for (int x = 0; x < width; x++)
{
int idx = rowStartIdx + (x * bytesPerPixel);
switch (bytesPerPixel)
{
case 1:
color.FromGray8(Unsafe.As<byte, Gray8>(ref bufferSpan[idx]));
break;
case 2:
// Set alpha value to 1, to treat it as opaque for Bgra5551.
bufferSpan[idx + 1] = (byte)(bufferSpan[idx + 1] | 128);
color.FromBgra5551(Unsafe.As<byte, Bgra5551>(ref bufferSpan[idx]));
break;
case 3:
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref bufferSpan[idx]));
break;
case 4:
color.FromBgra32(Unsafe.As<byte, Bgra32>(ref bufferSpan[idx]));
break;
}
pixelRow[x] = color;
}
}
}
}
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
public IImageInfo Identify(Stream stream)
{
this.ReadFileHeader(stream);
return new ImageInfo(
new PixelTypeInfo(this.fileHeader.PixelDepth),
this.fileHeader.Width,
this.fileHeader.Height,
this.metadata);
}
/// <summary>
/// Produce uncompressed tga data from a run length encoded stream.
/// </summary>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="buffer">Buffer for uncompressed data.</param>
/// <param name="bytesPerPixel">The bytes used per pixel.</param>
private void UncompressRle(int width, int height, Span<byte> buffer, int bytesPerPixel)
{
int uncompressedPixels = 0;
var pixel = new byte[bytesPerPixel];
int totalPixels = width * height;
while (uncompressedPixels < totalPixels)
{
byte runLengthByte = (byte)this.currentStream.ReadByte();
// The high bit of a run length packet is set to 1.
int highBit = runLengthByte >> 7;
if (highBit == 1)
{
int runLength = runLengthByte & 127;
this.currentStream.Read(pixel, 0, bytesPerPixel);
int bufferIdx = uncompressedPixels * bytesPerPixel;
for (int i = 0; i < runLength + 1; i++, uncompressedPixels++)
{
pixel.AsSpan().CopyTo(buffer.Slice(bufferIdx));
bufferIdx += bytesPerPixel;
}
}
else
{
// Non-run-length encoded packet.
int runLength = runLengthByte;
int bufferIdx = uncompressedPixels * bytesPerPixel;
for (int i = 0; i < runLength + 1; i++, uncompressedPixels++)
{
this.currentStream.Read(pixel, 0, bytesPerPixel);
pixel.AsSpan().CopyTo(buffer.Slice(bufferIdx));
bufferIdx += bytesPerPixel;
}
}
}
}
/// <summary>
/// Returns the y- value based on the given height.
/// </summary>
/// <param name="y">The y- value representing the current row.</param>
/// <param name="height">The height of the bitmap.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
/// <returns>The <see cref="int"/> representing the inverted value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Invert(int y, int height, bool inverted) => (!inverted) ? height - y - 1 : y;
/// <summary>
/// Reads the tga file header from the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>true, if the image origin is top left.</returns>
private bool ReadFileHeader(Stream stream)
{
this.currentStream = stream;
#if NETCOREAPP2_1
Span<byte> buffer = stackalloc byte[TgaFileHeader.Size];
#else
var buffer = new byte[TgaFileHeader.Size];
#endif
this.currentStream.Read(buffer, 0, TgaFileHeader.Size);
this.fileHeader = TgaFileHeader.Parse(buffer);
this.metadata = new ImageMetadata();
this.tgaMetadata = this.metadata.GetFormatMetadata(TgaFormat.Instance);
this.tgaMetadata.BitsPerPixel = (TgaBitsPerPixel)this.fileHeader.PixelDepth;
// Bit at position 5 of the descriptor indicates, that the origin is top left instead of bottom right.
if ((this.fileHeader.ImageDescriptor & (1 << 5)) != 0)
{
return true;
}
return false;
}
}
}

34
src/ImageSharp/Formats/Tga/TgaEncoder.cs

@ -0,0 +1,34 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tga
{
/// <summary>
/// Image encoder for writing an image to a stream as a targa truevision image.
/// </summary>
public sealed class TgaEncoder : IImageEncoder, ITgaEncoderOptions
{
/// <summary>
/// Gets or sets the number of bits per pixel.
/// </summary>
public TgaBitsPerPixel? BitsPerPixel { get; set; }
/// <summary>
/// Gets or sets a value indicating whether no compression or run length compression should be used.
/// </summary>
public TgaCompression Compression { get; set; } = TgaCompression.RunLength;
/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
var encoder = new TgaEncoderCore(this, image.GetMemoryAllocator());
encoder.Encode(image, stream);
}
}
}

348
src/ImageSharp/Formats/Tga/TgaEncoderCore.cs

@ -0,0 +1,348 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers.Binary;
using System.IO;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Tga
{
/// <summary>
/// Image encoder for writing an image to a stream as a truevision targa image.
/// </summary>
internal sealed class TgaEncoderCore
{
/// <summary>
/// Used for allocating memory during processing operations.
/// </summary>
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// The global configuration.
/// </summary>
private Configuration configuration;
/// <summary>
/// Reusable buffer for writing data.
/// </summary>
private readonly byte[] buffer = new byte[2];
/// <summary>
/// The color depth, in number of bits per pixel.
/// </summary>
private TgaBitsPerPixel? bitsPerPixel;
/// <summary>
/// Indicates if run length compression should be used.
/// </summary>
private readonly TgaCompression compression;
/// <summary>
/// Initializes a new instance of the <see cref="TgaEncoderCore"/> class.
/// </summary>
/// <param name="options">The encoder options.</param>
/// <param name="memoryAllocator">The memory manager.</param>
public TgaEncoderCore(ITgaEncoderOptions options, MemoryAllocator memoryAllocator)
{
this.memoryAllocator = memoryAllocator;
this.bitsPerPixel = options.BitsPerPixel;
this.compression = options.Compression;
}
/// <summary>
/// Encodes the image to the specified stream from the <see cref="ImageFrame{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
this.configuration = image.GetConfiguration();
ImageMetadata metadata = image.Metadata;
TgaMetadata tgaMetadata = metadata.GetFormatMetadata(TgaFormat.Instance);
this.bitsPerPixel = this.bitsPerPixel ?? tgaMetadata.BitsPerPixel;
TgaImageType imageType = this.compression is TgaCompression.RunLength ? TgaImageType.RleTrueColor : TgaImageType.TrueColor;
if (this.bitsPerPixel == TgaBitsPerPixel.Pixel8)
{
imageType = this.compression is TgaCompression.RunLength ? TgaImageType.RleBlackAndWhite : TgaImageType.BlackAndWhite;
}
// If compression is used, set bit 5 of the image descriptor to indicate an left top origin.
byte imageDescriptor = (byte)(this.compression is TgaCompression.RunLength ? 32 : 0);
var fileHeader = new TgaFileHeader(
idLength: 0,
colorMapType: 0,
imageType: imageType,
cMapStart: 0,
cMapLength: 0,
cMapDepth: 0,
xOffset: 0,
yOffset: this.compression is TgaCompression.RunLength ? (short)image.Height : (short)0, // When run length encoding is used, the origin should be top left instead of the default bottom left.
width: (short)image.Width,
height: (short)image.Height,
pixelDepth: (byte)this.bitsPerPixel.Value,
imageDescriptor: imageDescriptor);
#if NETCOREAPP2_1
Span<byte> buffer = stackalloc byte[TgaFileHeader.Size];
#else
byte[] buffer = new byte[TgaFileHeader.Size];
#endif
fileHeader.WriteTo(buffer);
stream.Write(buffer, 0, TgaFileHeader.Size);
if (this.compression is TgaCompression.RunLength)
{
this.WriteRunLengthEndcodedImage(stream, image.Frames.RootFrame);
}
else
{
this.WriteImage(stream, image.Frames.RootFrame);
}
stream.Flush();
}
/// <summary>
/// Writes the pixel data to the binary stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image">
/// The <see cref="ImageFrame{TPixel}"/> containing pixel data.
/// </param>
private void WriteImage<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : struct, IPixel<TPixel>
{
Buffer2D<TPixel> pixels = image.PixelBuffer;
switch (this.bitsPerPixel)
{
case TgaBitsPerPixel.Pixel8:
this.Write8Bit(stream, pixels);
break;
case TgaBitsPerPixel.Pixel16:
this.Write16Bit(stream, pixels);
break;
case TgaBitsPerPixel.Pixel24:
this.Write24Bit(stream, pixels);
break;
case TgaBitsPerPixel.Pixel32:
this.Write32Bit(stream, pixels);
break;
}
}
/// <summary>
/// Writes a run length encoded tga image to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="stream">The stream to write the image to.</param>
/// <param name="image">The image to encode.</param>
private void WriteRunLengthEndcodedImage<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : struct, IPixel<TPixel>
{
Rgba32 color = default;
Buffer2D<TPixel> pixels = image.PixelBuffer;
Span<TPixel> pixelSpan = pixels.GetSpan();
int totalPixels = image.Width * image.Height;
int encodedPixels = 0;
while (encodedPixels < totalPixels)
{
TPixel currentPixel = pixelSpan[encodedPixels];
currentPixel.ToRgba32(ref color);
byte equalPixelCount = this.FindEqualPixels(pixelSpan.Slice(encodedPixels));
// Write the number of equal pixels, with the high bit set, indicating ist a compressed pixel run.
stream.WriteByte((byte)(equalPixelCount | 128));
switch (this.bitsPerPixel)
{
case TgaBitsPerPixel.Pixel8:
int luminance = GetLuminance(currentPixel);
stream.WriteByte((byte)luminance);
break;
case TgaBitsPerPixel.Pixel16:
var bgra5551 = new Bgra5551(color.ToVector4());
BinaryPrimitives.TryWriteInt16LittleEndian(this.buffer, (short)bgra5551.PackedValue);
stream.WriteByte(this.buffer[0]);
stream.WriteByte(this.buffer[1]);
break;
case TgaBitsPerPixel.Pixel24:
stream.WriteByte(color.B);
stream.WriteByte(color.G);
stream.WriteByte(color.R);
break;
case TgaBitsPerPixel.Pixel32:
stream.WriteByte(color.B);
stream.WriteByte(color.G);
stream.WriteByte(color.R);
stream.WriteByte(color.A);
break;
}
encodedPixels += equalPixelCount + 1;
}
}
/// <summary>
/// Finds consecutive pixels, which have the same value starting from the pixel span offset 0.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="pixelSpan">The pixel span to search in.</param>
/// <returns>The number of equal pixels.</returns>
private byte FindEqualPixels<TPixel>(Span<TPixel> pixelSpan)
where TPixel : struct, IPixel<TPixel>
{
int idx = 0;
byte equalPixelCount = 0;
while (equalPixelCount < 127 && idx < pixelSpan.Length - 1)
{
TPixel currentPixel = pixelSpan[idx];
TPixel nextPixel = pixelSpan[idx + 1];
if (currentPixel.Equals(nextPixel))
{
equalPixelCount++;
}
else
{
return equalPixelCount;
}
idx++;
}
return equalPixelCount;
}
private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel) => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, 0);
/// <summary>
/// Writes the 8bit pixels uncompressed to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write8Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : struct, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 1))
{
for (int y = pixels.Height - 1; y >= 0; y--)
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToGray8Bytes(
this.configuration,
pixelSpan,
row.GetSpan(),
pixelSpan.Length);
stream.Write(row.Array, 0, row.Length());
}
}
}
/// <summary>
/// Writes the 16bit pixels uncompressed to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write16Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : struct, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 2))
{
for (int y = pixels.Height - 1; y >= 0; y--)
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra5551Bytes(
this.configuration,
pixelSpan,
row.GetSpan(),
pixelSpan.Length);
stream.Write(row.Array, 0, row.Length());
}
}
}
/// <summary>
/// Writes the 24bit pixels uncompressed to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write24Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : struct, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 3))
{
for (int y = pixels.Height - 1; y >= 0; y--)
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgr24Bytes(
this.configuration,
pixelSpan,
row.GetSpan(),
pixelSpan.Length);
stream.Write(row.Array, 0, row.Length());
}
}
}
/// <summary>
/// Writes the 32bit pixels uncompressed to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write32Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : struct, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 4))
{
for (int y = pixels.Height - 1; y >= 0; y--)
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra32Bytes(
this.configuration,
pixelSpan,
row.GetSpan(),
pixelSpan.Length);
stream.Write(row.Array, 0, row.Length());
}
}
}
/// <summary>
/// Convert the pixel values to grayscale using ITU-R Recommendation BT.709.
/// </summary>
/// <param name="sourcePixel">The pixel to get the luminance from.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static int GetLuminance<TPixel>(TPixel sourcePixel)
where TPixel : struct, IPixel<TPixel>
{
var vector = sourcePixel.ToVector4();
return ImageMaths.GetBT709Luminance(ref vector, 256);
}
}
}

147
src/ImageSharp/Formats/Tga/TgaFileHeader.cs

@ -0,0 +1,147 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Tga
{
/// <summary>
/// This block of bytes tells the application detailed information about the targa image.
/// <see href="https://www.fileformat.info/format/tga/egff.htm"/>
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal readonly struct TgaFileHeader
{
/// <summary>
/// Defines the size of the data structure in the targa file.
/// </summary>
public const int Size = TgaConstants.FileHeaderLength;
public TgaFileHeader(
byte idLength,
byte colorMapType,
TgaImageType imageType,
short cMapStart,
short cMapLength,
byte cMapDepth,
short xOffset,
short yOffset,
short width,
short height,
byte pixelDepth,
byte imageDescriptor)
{
this.IdLength = idLength;
this.ColorMapType = colorMapType;
this.ImageType = imageType;
this.CMapStart = cMapStart;
this.CMapLength = cMapLength;
this.CMapDepth = cMapDepth;
this.XOffset = xOffset;
this.YOffset = yOffset;
this.Width = width;
this.Height = height;
this.PixelDepth = pixelDepth;
this.ImageDescriptor = imageDescriptor;
}
/// <summary>
/// Gets the id length.
/// This field identifies the number of bytes contained in Field 6, the Image ID Field. The maximum number
/// of characters is 255. A value of zero indicates that no Image ID field is included with the image.
/// </summary>
public byte IdLength { get; }
/// <summary>
/// Gets the color map type.
/// This field indicates the type of color map (if any) included with the image. There are currently 2 defined
/// values for this field:
/// 0 - indicates that no color-map data is included with this image.
/// 1 - indicates that a color-map is included with this image.
/// </summary>
public byte ColorMapType { get; }
/// <summary>
/// Gets the image type.
/// The TGA File Format can be used to store Pseudo-Color, True-Color and Direct-Color images of various
/// pixel depths.
/// </summary>
public TgaImageType ImageType { get; }
/// <summary>
/// Gets the start of the color map.
/// This field and its sub-fields describe the color map (if any) used for the image. If the Color Map Type field
/// is set to zero, indicating that no color map exists, then these 5 bytes should be set to zero.
/// </summary>
public short CMapStart { get; }
/// <summary>
/// Gets the total number of color map entries included.
/// </summary>
public short CMapLength { get; }
/// <summary>
/// Gets the number of bits per entry. Typically 15, 16, 24 or 32-bit values are used.
/// </summary>
public byte CMapDepth { get; }
/// <summary>
/// Gets the XOffset.
/// These bytes specify the absolute horizontal coordinate for the lower left
/// corner of the image as it is positioned on a display device having an
/// origin at the lower left of the screen.
/// </summary>
public short XOffset { get; }
/// <summary>
/// Gets the YOffset.
/// These bytes specify the absolute vertical coordinate for the lower left
/// corner of the image as it is positioned on a display device having an
/// origin at the lower left of the screen.
/// </summary>
public short YOffset { get; }
/// <summary>
/// Gets the width of the image in pixels.
/// </summary>
public short Width { get; }
/// <summary>
/// Gets the height of the image in pixels.
/// </summary>
public short Height { get; }
/// <summary>
/// Gets the number of bits per pixel. This number includes
/// the Attribute or Alpha channel bits. Common values are 8, 16, 24 and
/// 32 but other pixel depths could be used.
/// </summary>
public byte PixelDepth { get; }
/// <summary>
/// Gets the ImageDescriptor.
/// ImageDescriptor contains two pieces of information.
/// Bits 0 through 3 contain the number of attribute bits per pixel.
/// Attribute bits are found only in pixels for the 16- and 32-bit flavors of the TGA format and are called alpha channel,
/// overlay, or interrupt bits. Bits 4 and 5 contain the image origin location (coordinate 0,0) of the image.
/// This position may be any of the four corners of the display screen.
/// When both of these bits are set to zero, the image origin is the lower-left corner of the screen.
/// Bits 6 and 7 of the ImageDescriptor field are unused and should be set to 0.
/// </summary>
public byte ImageDescriptor { get; }
public static TgaFileHeader Parse(Span<byte> data)
{
return MemoryMarshal.Cast<byte, TgaFileHeader>(data)[0];
}
public void WriteTo(Span<byte> buffer)
{
ref TgaFileHeader dest = ref Unsafe.As<byte, TgaFileHeader>(ref MemoryMarshal.GetReference(buffer));
dest = this;
}
}
}

33
src/ImageSharp/Formats/Tga/TgaFormat.cs

@ -0,0 +1,33 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
namespace SixLabors.ImageSharp.Formats.Tga
{
/// <summary>
/// Registers the image encoders, decoders and mime type detectors for the tga format.
/// </summary>
public sealed class TgaFormat : IImageFormat<TgaMetadata>
{
/// <summary>
/// Gets the current instance.
/// </summary>
public static TgaFormat Instance { get; } = new TgaFormat();
/// <inheritdoc/>
public string Name => "TGA";
/// <inheritdoc/>
public string DefaultMimeType => "image/tga";
/// <inheritdoc/>
public IEnumerable<string> MimeTypes => TgaConstants.MimeTypes;
/// <inheritdoc/>
public IEnumerable<string> FileExtensions => TgaConstants.FileExtensions;
/// <inheritdoc/>
public TgaMetadata CreateDefaultFormatMetadata() => new TgaMetadata();
}
}

40
src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs

@ -0,0 +1,40 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.Tga
{
/// <summary>
/// Detects tga file headers.
/// </summary>
public sealed class TgaImageFormatDetector : IImageFormatDetector
{
/// <inheritdoc/>
public int HeaderSize => TgaConstants.FileHeaderLength;
/// <inheritdoc/>
public IImageFormat DetectFormat(ReadOnlySpan<byte> header)
{
return this.IsSupportedFileFormat(header) ? TgaFormat.Instance : null;
}
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
{
if (header.Length >= this.HeaderSize)
{
// There is no magick bytes in a tga file, so at least the image type
// and the colormap type in the header will be checked for a valid value.
if (header[1] != 0 && header[1] != 1)
{
return false;
}
var imageType = (TgaImageType)header[2];
return imageType.IsValid();
}
return true;
}
}
}

48
src/ImageSharp/Formats/Tga/TgaImageType.cs

@ -0,0 +1,48 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.
ImageSharp.Formats.Tga
{
/// <summary>
/// Defines the tga image type. The TGA File Format can be used to store Pseudo-Color,
/// True-Color and Direct-Color images of various pixel depths.
/// </summary>
public enum TgaImageType : byte
{
/// <summary>
/// No image data included.
/// </summary>
NoImageData = 0,
/// <summary>
/// Uncompressed, color mapped image.
/// </summary>
ColorMapped = 1,
/// <summary>
/// Uncompressed true color image.
/// </summary>
TrueColor = 2,
/// <summary>
/// Uncompressed Black and white (grayscale) image.
/// </summary>
BlackAndWhite = 3,
/// <summary>
/// Run length encoded, color mapped image.
/// </summary>
RleColorMapped = 9,
/// <summary>
/// Run length encoded, true color image.
/// </summary>
RleTrueColor = 10,
/// <summary>
/// Run length encoded, black and white (grayscale) image.
/// </summary>
RleBlackAndWhite = 11,
}
}

49
src/ImageSharp/Formats/Tga/TgaImageTypeExtensions.cs

@ -0,0 +1,49 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tga
{
/// <summary>
/// Extension methods for TgaImageType enum.
/// </summary>
public static class TgaImageTypeExtensions
{
/// <summary>
/// Checks if this tga image type is run length encoded.
/// </summary>
/// <param name="imageType">The tga image type.</param>
/// <returns>True, if this image type is run length encoded, otherwise false.</returns>
public static bool IsRunLengthEncoded(this TgaImageType imageType)
{
if (imageType is TgaImageType.RleColorMapped || imageType is TgaImageType.RleBlackAndWhite || imageType is TgaImageType.RleTrueColor)
{
return true;
}
return false;
}
/// <summary>
/// Checks, if the image type has valid value.
/// </summary>
/// <param name="imageType">The image type.</param>
/// <returns>true, if its a valid tga image type.</returns>
public static bool IsValid(this TgaImageType imageType)
{
switch (imageType)
{
case TgaImageType.NoImageData:
case TgaImageType.ColorMapped:
case TgaImageType.TrueColor:
case TgaImageType.BlackAndWhite:
case TgaImageType.RleColorMapped:
case TgaImageType.RleTrueColor:
case TgaImageType.RleBlackAndWhite:
return true;
default:
return false;
}
}
}
}

35
src/ImageSharp/Formats/Tga/TgaMetadata.cs

@ -0,0 +1,35 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tga
{
/// <summary>
/// Provides TGA specific metadata information for the image.
/// </summary>
public class TgaMetadata : IDeepCloneable
{
/// <summary>
/// Initializes a new instance of the <see cref="TgaMetadata"/> class.
/// </summary>
public TgaMetadata()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="TgaMetadata"/> class.
/// </summary>
/// <param name="other">The metadata to create an instance from.</param>
private TgaMetadata(TgaMetadata other)
{
this.BitsPerPixel = other.BitsPerPixel;
}
/// <summary>
/// Gets or sets the number of bits per pixel.
/// </summary>
public TgaBitsPerPixel BitsPerPixel { get; set; } = TgaBitsPerPixel.Pixel24;
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new TgaMetadata(this);
}
}

31
src/ImageSharp/Formats/Tga/TgaThrowHelper.cs

@ -0,0 +1,31 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Tga
{
internal static class TgaThrowHelper
{
/// <summary>
/// Cold path optimization for throwing <see cref="ImageFormatException"/>-s
/// </summary>
/// <param name="errorMessage">The error message for the exception.</param>
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowImageFormatException(string errorMessage)
{
throw new ImageFormatException(errorMessage);
}
/// <summary>
/// Cold path optimization for throwing <see cref="NotSupportedException"/>-s
/// </summary>
/// <param name="errorMessage">The error message for the exception.</param>
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowNotSupportedException(string errorMessage)
{
throw new NotSupportedException(errorMessage);
}
}
}

178
src/ImageSharp/GraphicsOptions.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
@ -8,170 +8,82 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Options for influencing the drawing functions.
/// </summary>
public struct GraphicsOptions
public class GraphicsOptions : IDeepCloneable<GraphicsOptions>
{
/// <summary>
/// Represents the default <see cref="GraphicsOptions"/>.
/// </summary>
public static readonly GraphicsOptions Default = new GraphicsOptions(true);
private float? blendPercentage;
private int? antialiasSubpixelDepth;
private bool? antialias;
private PixelColorBlendingMode colorBlendingMode;
private PixelAlphaCompositionMode alphaCompositionMode;
/// <summary>
/// Initializes a new instance of the <see cref="GraphicsOptions"/> struct.
/// </summary>
/// <param name="enableAntialiasing">If set to <c>true</c> [enable antialiasing].</param>
public GraphicsOptions(bool enableAntialiasing)
{
this.colorBlendingMode = PixelColorBlendingMode.Normal;
this.alphaCompositionMode = PixelAlphaCompositionMode.SrcOver;
this.blendPercentage = 1;
this.antialiasSubpixelDepth = 16;
this.antialias = enableAntialiasing;
}
/// <summary>
/// Initializes a new instance of the <see cref="GraphicsOptions"/> struct.
/// </summary>
/// <param name="enableAntialiasing">If set to <c>true</c> [enable antialiasing].</param>
/// <param name="opacity">blending percentage to apply to the drawing operation</param>
public GraphicsOptions(bool enableAntialiasing, float opacity)
{
Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity));
this.colorBlendingMode = PixelColorBlendingMode.Normal;
this.alphaCompositionMode = PixelAlphaCompositionMode.SrcOver;
this.blendPercentage = opacity;
this.antialiasSubpixelDepth = 16;
this.antialias = enableAntialiasing;
}
private int antialiasSubpixelDepth = 16;
private float blendPercentage = 1F;
/// <summary>
/// Initializes a new instance of the <see cref="GraphicsOptions"/> struct.
/// Initializes a new instance of the <see cref="GraphicsOptions"/> class.
/// </summary>
/// <param name="enableAntialiasing">If set to <c>true</c> [enable antialiasing].</param>
/// <param name="opacity">blending percentage to apply to the drawing operation</param>
/// <param name="blending">color blending mode to apply to the drawing operation</param>
public GraphicsOptions(bool enableAntialiasing, PixelColorBlendingMode blending, float opacity)
public GraphicsOptions()
{
Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity));
this.colorBlendingMode = blending;
this.alphaCompositionMode = PixelAlphaCompositionMode.SrcOver;
this.blendPercentage = opacity;
this.antialiasSubpixelDepth = 16;
this.antialias = enableAntialiasing;
}
/// <summary>
/// Initializes a new instance of the <see cref="GraphicsOptions"/> struct.
/// </summary>
/// <param name="enableAntialiasing">If set to <c>true</c> [enable antialiasing].</param>
/// <param name="opacity">blending percentage to apply to the drawing operation</param>
/// <param name="blending">color blending mode to apply to the drawing operation</param>
/// <param name="composition">alpha composition mode to apply to the drawing operation</param>
public GraphicsOptions(bool enableAntialiasing, PixelColorBlendingMode blending, PixelAlphaCompositionMode composition, float opacity)
private GraphicsOptions(GraphicsOptions source)
{
Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity));
this.colorBlendingMode = blending;
this.alphaCompositionMode = composition;
this.blendPercentage = opacity;
this.antialiasSubpixelDepth = 16;
this.antialias = enableAntialiasing;
this.AlphaCompositionMode = source.AlphaCompositionMode;
this.Antialias = source.Antialias;
this.AntialiasSubpixelDepth = source.AntialiasSubpixelDepth;
this.BlendPercentage = source.BlendPercentage;
this.ColorBlendingMode = source.ColorBlendingMode;
}
/// <summary>
/// Gets or sets a value indicating whether antialiasing should be applied.
/// Defaults to true.
/// </summary>
public bool Antialias
{
get => this.antialias ?? true;
set => this.antialias = value;
}
public bool Antialias { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating the number of subpixels to use while rendering with antialiasing enabled.
/// Defaults to 16.
/// </summary>
public int AntialiasSubpixelDepth
{
get => this.antialiasSubpixelDepth ?? 16;
set => this.antialiasSubpixelDepth = value;
get
{
return this.antialiasSubpixelDepth;
}
set
{
Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.AntialiasSubpixelDepth));
this.antialiasSubpixelDepth = value;
}
}
/// <summary>
/// Gets or sets a value indicating the blending percentage to apply to the drawing operation
/// Gets or sets a value between indicating the blending percentage to apply to the drawing operation.
/// Range 0..1; Defaults to 1.
/// </summary>
public float BlendPercentage
{
get => (this.blendPercentage ?? 1).Clamp(0, 1);
set => this.blendPercentage = value;
}
get
{
return this.blendPercentage;
}
// In the future we could expose a PixelBlender<TPixel> directly on here
// or some forms of PixelBlender factory for each pixel type. Will need
// some API thought post V1.
set
{
Guard.MustBeBetweenOrEqualTo(value, 0, 1F, nameof(this.BlendPercentage));
this.blendPercentage = value;
}
}
/// <summary>
/// Gets or sets a value indicating the color blending mode to apply to the drawing operation
/// Gets or sets a value indicating the color blending mode to apply to the drawing operation.
/// Defaults to <see cref="PixelColorBlendingMode.Normal"/>.
/// </summary>
public PixelColorBlendingMode ColorBlendingMode
{
get => this.colorBlendingMode;
set => this.colorBlendingMode = value;
}
public PixelColorBlendingMode ColorBlendingMode { get; set; } = PixelColorBlendingMode.Normal;
/// <summary>
/// Gets or sets a value indicating the alpha composition mode to apply to the drawing operation
/// Defaults to <see cref="PixelAlphaCompositionMode.SrcOver"/>.
/// </summary>
public PixelAlphaCompositionMode AlphaCompositionMode
{
get => this.alphaCompositionMode;
set => this.alphaCompositionMode = value;
}
public PixelAlphaCompositionMode AlphaCompositionMode { get; set; } = PixelAlphaCompositionMode.SrcOver;
/// <summary>
/// Evaluates if a given SOURCE color can completely replace a BACKDROP color given the current blending and composition settings.
/// </summary>
/// <param name="color">the color</param>
/// <returns>true if the color can be considered opaque</returns>
/// <remarks>
/// Blending and composition is an expensive operation, in some cases, like
/// filling with a solid color, the blending can be avoided by a plain color replacement.
/// This method can be useful for such processors to select the fast path.
/// </remarks>
internal bool IsOpaqueColorWithoutBlending(Color color)
{
if (this.ColorBlendingMode != PixelColorBlendingMode.Normal)
{
return false;
}
if (this.AlphaCompositionMode != PixelAlphaCompositionMode.SrcOver &&
this.AlphaCompositionMode != PixelAlphaCompositionMode.Src)
{
return false;
}
if (this.BlendPercentage != 1f)
{
return false;
}
if (color.ToVector4().W != 1f)
{
return false;
}
return true;
}
/// <inheritdoc/>
public GraphicsOptions DeepClone() => new GraphicsOptions(this);
}
}
}

27
src/ImageSharp/Memory/MemoryAllocatorExtensions.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Buffers;
@ -11,8 +11,18 @@ namespace SixLabors.ImageSharp.Memory
/// <summary>
/// Extension methods for <see cref="MemoryAllocator"/>.
/// </summary>
internal static class MemoryAllocatorExtensions
public static class MemoryAllocatorExtensions
{
/// <summary>
/// Allocates a buffer of value type objects interpreted as a 2D region
/// of <paramref name="width"/> x <paramref name="height"/> elements.
/// </summary>
/// <typeparam name="T">The type of buffer items to allocate.</typeparam>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="width">The buffer width.</param>
/// <param name="height">The buffer heght.</param>
/// <param name="options">The allocation options.</param>
/// <returns>The <see cref="Buffer2D{T}"/>.</returns>
public static Buffer2D<T> Allocate2D<T>(
this MemoryAllocator memoryAllocator,
int width,
@ -26,6 +36,15 @@ namespace SixLabors.ImageSharp.Memory
return new Buffer2D<T>(memorySource, width, height);
}
/// <summary>
/// Allocates a buffer of value type objects interpreted as a 2D region
/// of <paramref name="size"/> width x <paramref name="size"/> height elements.
/// </summary>
/// <typeparam name="T">The type of buffer items to allocate.</typeparam>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="size">The buffer size.</param>
/// <param name="options">The allocation options.</param>
/// <returns>The <see cref="Buffer2D{T}"/>.</returns>
public static Buffer2D<T> Allocate2D<T>(
this MemoryAllocator memoryAllocator,
Size size,
@ -41,7 +60,7 @@ namespace SixLabors.ImageSharp.Memory
/// <param name="pixelSizeInBytes">The pixel size in bytes, eg. 3 for RGB</param>
/// <param name="paddingInBytes">The padding</param>
/// <returns>A <see cref="IManagedByteBuffer"/></returns>
public static IManagedByteBuffer AllocatePaddedPixelRowBuffer(
internal static IManagedByteBuffer AllocatePaddedPixelRowBuffer(
this MemoryAllocator memoryAllocator,
int width,
int pixelSizeInBytes,
@ -51,4 +70,4 @@ namespace SixLabors.ImageSharp.Memory
return memoryAllocator.AllocateManagedByteBuffer(length);
}
}
}
}

4
src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt

@ -18,7 +18,7 @@
/// <summary>
/// Converts all pixels in 'source` span of <see cref="<#=pixelType#>"/> into a span of <typeparamref name="TPixel"/>-s.
/// </summary>
/// <param name="configuration">A <see cref="Configuration"/> to configure internal operations</param>
/// <param name="configuration">A <see cref="Configuration"/> to configure internal operations.</param>
/// <param name="source">The source <see cref="Span{T}"/> of <see cref="<#=pixelType#>"/> data.</param>
/// <param name="destPixels">The <see cref="Span{T}"/> to the destination pixels.</param>
internal virtual void From<#=pixelType#>(Configuration configuration, ReadOnlySpan<<#=pixelType#>> source, Span<TPixel> destPixels)
@ -41,7 +41,7 @@
/// A helper for <see cref="From<#=pixelType#>(Configuration, ReadOnlySpan{<#=pixelType#>}, Span{TPixel})"/> that expects a byte span.
/// The layout of the data in 'sourceBytes' must be compatible with <see cref="<#=pixelType#>"/> layout.
/// </summary>
/// <param name="configuration">A <see cref="Configuration"/> to configure internal operations</param>
/// <param name="configuration">A <see cref="Configuration"/> to configure internal operations.</param>
/// <param name="sourceBytes">The <see cref="ReadOnlySpan{T}"/> to the source bytes.</param>
/// <param name="destPixels">The <see cref="Span{T}"/> to the destination pixels.</param>
/// <param name="count">The number of pixels to convert.</param>

8
src/ImageSharp/Primitives/DenseMatrix{T}.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@ -98,9 +98,9 @@ namespace SixLabors.ImageSharp.Primitives
}
/// <summary>
/// Gets a Span wrapping the Data.
/// Gets a span wrapping the <see cref="Data"/>.
/// </summary>
internal Span<T> Span => new Span<T>(this.Data);
public Span<T> Span => new Span<T>(this.Data);
/// <summary>
/// Gets or sets the item at the specified position.
@ -222,4 +222,4 @@ namespace SixLabors.ImageSharp.Primitives
/// <inheritdoc/>
public override int GetHashCode() => this.Data.GetHashCode();
}
}
}

12
src/ImageSharp/Processing/Extensions/BackgroundColorExtensions.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing.Processors.Overlays;
@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="color">The color to set as the background.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext BackgroundColor(this IImageProcessingContext source, Color color) =>
BackgroundColor(source, GraphicsOptions.Default, color);
BackgroundColor(source, new GraphicsOptions(), color);
/// <summary>
/// Replaces the background color of image with the given one.
@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Processing
this IImageProcessingContext source,
Color color,
Rectangle rectangle) =>
BackgroundColor(source, GraphicsOptions.Default, color, rectangle);
BackgroundColor(source, new GraphicsOptions(), color, rectangle);
/// <summary>
/// Replaces the background color of image with the given one.
@ -47,7 +47,7 @@ namespace SixLabors.ImageSharp.Processing
this IImageProcessingContext source,
GraphicsOptions options,
Color color) =>
source.ApplyProcessor(new BackgroundColorProcessor(color, options));
source.ApplyProcessor(new BackgroundColorProcessor(options, color));
/// <summary>
/// Replaces the background color of image with the given one.
@ -64,6 +64,6 @@ namespace SixLabors.ImageSharp.Processing
GraphicsOptions options,
Color color,
Rectangle rectangle) =>
source.ApplyProcessor(new BackgroundColorProcessor(color, options), rectangle);
source.ApplyProcessor(new BackgroundColorProcessor(options, color), rectangle);
}
}
}

18
src/ImageSharp/Processing/Extensions/GlowExtensions.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Primitives;
@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="source">The image this method extends.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Glow(this IImageProcessingContext source) =>
Glow(source, GraphicsOptions.Default);
Glow(source, new GraphicsOptions());
/// <summary>
/// Applies a radial glow effect to an image.
@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Processing
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Glow(this IImageProcessingContext source, Color color)
{
return Glow(source, GraphicsOptions.Default, color);
return Glow(source, new GraphicsOptions(), color);
}
/// <summary>
@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="radius">The the radius.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Glow(this IImageProcessingContext source, float radius) =>
Glow(source, GraphicsOptions.Default, radius);
Glow(source, new GraphicsOptions(), radius);
/// <summary>
/// Applies a radial glow effect to an image.
@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Processing
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Glow(this IImageProcessingContext source, Rectangle rectangle) =>
source.Glow(GraphicsOptions.Default, rectangle);
source.Glow(new GraphicsOptions(), rectangle);
/// <summary>
/// Applies a radial glow effect to an image.
@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Processing
Color color,
float radius,
Rectangle rectangle) =>
source.Glow(GraphicsOptions.Default, color, ValueSize.Absolute(radius), rectangle);
source.Glow(new GraphicsOptions(), color, ValueSize.Absolute(radius), rectangle);
/// <summary>
/// Applies a radial glow effect to an image.
@ -155,7 +155,7 @@ namespace SixLabors.ImageSharp.Processing
Color color,
ValueSize radius,
Rectangle rectangle) =>
source.ApplyProcessor(new GlowProcessor(color, radius, options), rectangle);
source.ApplyProcessor(new GlowProcessor(options, color, radius), rectangle);
/// <summary>
/// Applies a radial glow effect to an image.
@ -170,6 +170,6 @@ namespace SixLabors.ImageSharp.Processing
GraphicsOptions options,
Color color,
ValueSize radius) =>
source.ApplyProcessor(new GlowProcessor(color, radius, options));
source.ApplyProcessor(new GlowProcessor(options, color, radius));
}
}
}

18
src/ImageSharp/Processing/Extensions/VignetteExtensions.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Primitives;
@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="source">The image this method extends.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Vignette(this IImageProcessingContext source) =>
Vignette(source, GraphicsOptions.Default);
Vignette(source, new GraphicsOptions());
/// <summary>
/// Applies a radial vignette effect to an image.
@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="color">The color to set as the vignette.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Vignette(this IImageProcessingContext source, Color color) =>
Vignette(source, GraphicsOptions.Default, color);
Vignette(source, new GraphicsOptions(), color);
/// <summary>
/// Applies a radial vignette effect to an image.
@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Processing
this IImageProcessingContext source,
float radiusX,
float radiusY) =>
Vignette(source, GraphicsOptions.Default, radiusX, radiusY);
Vignette(source, new GraphicsOptions(), radiusX, radiusY);
/// <summary>
/// Applies a radial vignette effect to an image.
@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Processing
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Vignette(this IImageProcessingContext source, Rectangle rectangle) =>
Vignette(source, GraphicsOptions.Default, rectangle);
Vignette(source, new GraphicsOptions(), rectangle);
/// <summary>
/// Applies a radial vignette effect to an image.
@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing
float radiusX,
float radiusY,
Rectangle rectangle) =>
source.Vignette(GraphicsOptions.Default, color, radiusX, radiusY, rectangle);
source.Vignette(new GraphicsOptions(), color, radiusX, radiusY, rectangle);
/// <summary>
/// Applies a radial vignette effect to an image.
@ -166,7 +166,7 @@ namespace SixLabors.ImageSharp.Processing
ValueSize radiusX,
ValueSize radiusY,
Rectangle rectangle) =>
source.ApplyProcessor(new VignetteProcessor(color, radiusX, radiusY, options), rectangle);
source.ApplyProcessor(new VignetteProcessor(options, color, radiusX, radiusY), rectangle);
private static IImageProcessingContext VignetteInternal(
this IImageProcessingContext source,
@ -174,6 +174,6 @@ namespace SixLabors.ImageSharp.Processing
Color color,
ValueSize radiusX,
ValueSize radiusY) =>
source.ApplyProcessor(new VignetteProcessor(color, radiusX, radiusY, options));
source.ApplyProcessor(new VignetteProcessor(options, color, radiusX, radiusY));
}
}
}

4
src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs

@ -349,7 +349,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
{
for (int idx = 0; idx < length; idx++)
{
int luminance = GetLuminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels);
int luminance = ImageMaths.GetBT709Luminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels);
Unsafe.Add(ref histogramBase, luminance)++;
}
}
@ -366,7 +366,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
{
for (int idx = 0; idx < length; idx++)
{
int luminance = GetLuminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels);
int luminance = ImageMaths.GetBT709Luminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels);
Unsafe.Add(ref histogramBase, luminance)--;
}
}

11
src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs

@ -143,16 +143,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
public static int GetLuminance(TPixel sourcePixel, int luminanceLevels)
{
var vector = sourcePixel.ToVector4();
return GetLuminance(ref vector, luminanceLevels);
return ImageMaths.GetBT709Luminance(ref vector, luminanceLevels);
}
/// <summary>
/// Convert the pixel values to grayscale using ITU-R Recommendation BT.709.
/// </summary>
/// <param name="vector">The vector to get the luminance from</param>
/// <param name="luminanceLevels">The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images)</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static int GetLuminance(ref Vector4 vector, int luminanceLevels)
=> (int)MathF.Round(((.2126F * vector.X) + (.7152F * vector.Y) + (.0722F * vector.Y)) * (luminanceLevels - 1));
}
}

4
src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs

@ -14,9 +14,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
/// <summary>
/// Initializes a new instance of the <see cref="BackgroundColorProcessor"/> class.
/// </summary>
/// <param name="color">The <see cref="Color"/> to set the background color to.</param>
/// <param name="options">The options defining blending algorithm and amount.</param>
public BackgroundColorProcessor(Color color, GraphicsOptions options)
/// <param name="color">The <see cref="Color"/> to set the background color to.</param>
public BackgroundColorProcessor(GraphicsOptions options, Color color)
{
this.Color = color;
this.GraphicsOptions = options;

14
src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs

@ -24,10 +24,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
/// <summary>
/// Initializes a new instance of the <see cref="GlowProcessor" /> class.
/// </summary>
/// <param name="color">The color or the glow.</param>
/// <param name="options">The options effecting blending and composition.</param>
public GlowProcessor(Color color, GraphicsOptions options)
: this(color, 0, options)
/// <param name="color">The color or the glow.</param>
public GlowProcessor(GraphicsOptions options, Color color)
: this(options, color, 0)
{
}
@ -37,17 +37,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
/// <param name="color">The color or the glow.</param>
/// <param name="radius">The radius of the glow.</param>
internal GlowProcessor(Color color, ValueSize radius)
: this(color, radius, GraphicsOptions.Default)
: this(new GraphicsOptions(), color, radius)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GlowProcessor" /> class.
/// </summary>
/// <param name="options">The options effecting blending and composition.</param>
/// <param name="color">The color or the glow.</param>
/// <param name="radius">The radius of the glow.</param>
/// <param name="options">The options effecting blending and composition.</param>
internal GlowProcessor(Color color, ValueSize radius, GraphicsOptions options)
internal GlowProcessor(GraphicsOptions options, Color color, ValueSize radius)
{
this.GlowColor = color;
this.Radius = radius;
@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
/// <summary>
/// Gets the the radius.
/// </summary>
internal ValueSize Radius { get; }
internal ValueSize Radius { get; }
/// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)

10
src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs

@ -17,16 +17,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
/// </summary>
/// <param name="color">The color of the vignette.</param>
public VignetteProcessor(Color color)
: this(color, GraphicsOptions.Default)
: this(new GraphicsOptions(), color)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="VignetteProcessor" /> class.
/// </summary>
/// <param name="color">The color of the vignette.</param>
/// <param name="options">The options effecting blending and composition.</param>
public VignetteProcessor(Color color, GraphicsOptions options)
/// <param name="color">The color of the vignette.</param>
public VignetteProcessor(GraphicsOptions options, Color color)
{
this.VignetteColor = color;
this.GraphicsOptions = options;
@ -35,11 +35,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
/// <summary>
/// Initializes a new instance of the <see cref="VignetteProcessor" /> class.
/// </summary>
/// <param name="options">The options effecting blending and composition.</param>
/// <param name="color">The color of the vignette.</param>
/// <param name="radiusX">The x-radius.</param>
/// <param name="radiusY">The y-radius.</param>
/// <param name="options">The options effecting blending and composition.</param>
internal VignetteProcessor(Color color, ValueSize radiusX, ValueSize radiusY, GraphicsOptions options)
internal VignetteProcessor(GraphicsOptions options, Color color, ValueSize radiusX, ValueSize radiusY)
{
this.VignetteColor = color;
this.RadiusX = radiusX;

42
tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs

@ -0,0 +1,42 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using BenchmarkDotNet.Attributes;
using ImageMagick;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
[Config(typeof(Config.ShortClr))]
public class DecodeTga : BenchmarkBase
{
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
[Params(TestImages.Tga.Bit24)]
public string TestImage { get; set; }
[Benchmark(Baseline = true, Description = "ImageMagick Tga")]
public Size TgaImageMagick()
{
using (var magickImage = new MagickImage(this.TestImageFullPath))
{
return new Size(magickImage.Width, magickImage.Height);
}
}
[Benchmark(Description = "ImageSharp Tga")]
public Size TgaCore()
{
using (var image = Image.Load<Rgba32>(this.TestImageFullPath))
{
return new Size(image.Width, image.Height);
}
}
}
}

8
tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs

@ -1,9 +1,10 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Drawing.Imaging;
using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests;
using SDImage = System.Drawing.Image;
@ -56,8 +57,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
using (var memoryStream = new MemoryStream())
{
this.bmpCore.SaveAsPng(memoryStream);
var encoder = new PngEncoder { FilterMethod = PngFilterMethod.None };
this.bmpCore.SaveAsPng(memoryStream, encoder);
}
}
}
}
}

54
tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs

@ -0,0 +1,54 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using BenchmarkDotNet.Attributes;
using ImageMagick;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests;
namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
[Config(typeof(Config.ShortClr))]
public class EncodeTga : BenchmarkBase
{
private MagickImage tgaMagick;
private Image<Rgba32> tgaCore;
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
[Params(TestImages.Tga.Bit24)]
public string TestImage { get; set; }
[GlobalSetup]
public void ReadImages()
{
if (this.tgaCore == null)
{
this.tgaCore = Image.Load<Rgba32>(TestImageFullPath);
this.tgaMagick = new MagickImage(this.TestImageFullPath);
}
}
[Benchmark(Baseline = true, Description = "Magick Tga")]
public void BmpSystemDrawing()
{
using (var memoryStream = new MemoryStream())
{
this.tgaMagick.Write(memoryStream, MagickFormat.Tga);
}
}
[Benchmark(Description = "ImageSharp Tga")]
public void BmpCore()
{
using (var memoryStream = new MemoryStream())
{
this.tgaCore.SaveAsBmp(memoryStream);
}
}
}
}

10
tests/ImageSharp.Benchmarks/Drawing/DrawText.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Drawing;
@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Benchmarks
graphics.SmoothingMode = SmoothingMode.AntiAlias;
using (var font = new Font("Arial", 12, GraphicsUnit.Point))
{
graphics.DrawString(TextToRender, font, System.Drawing.Brushes.HotPink, new RectangleF(10, 10, 780, 780));
graphics.DrawString(this.TextToRender, font, System.Drawing.Brushes.HotPink, new RectangleF(10, 10, 780, 780));
}
}
}
@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Benchmarks
using (var image = new Image<Rgba32>(800, 800))
{
var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12);
image.Mutate(x => x.ApplyProcessor(new DrawTextProcessor(new TextGraphicsOptions(true) { WrapTextWidth = 780 }, TextToRender, font, Processing.Brushes.Solid(Rgba32.HotPink), null, new SixLabors.Primitives.PointF(10, 10))));
image.Mutate(x => x.ApplyProcessor(new DrawTextProcessor(new TextGraphicsOptions { Antialias = true, WrapTextWidth = 780 }, this.TextToRender, font, Processing.Brushes.Solid(Rgba32.HotPink), null, new SixLabors.Primitives.PointF(10, 10))));
}
}
@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Benchmarks
using (var image = new Image<Rgba32>(800, 800))
{
var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12);
image.Mutate(x => DrawTextOldVersion(x, new TextGraphicsOptions(true) { WrapTextWidth = 780 }, TextToRender, font, Processing.Brushes.Solid(Rgba32.HotPink), null, new SixLabors.Primitives.PointF(10, 10)));
image.Mutate(x => DrawTextOldVersion(x, new TextGraphicsOptions { Antialias = true, WrapTextWidth = 780 }, this.TextToRender, font, Processing.Brushes.Solid(Rgba32.HotPink), null, new SixLabors.Primitives.PointF(10, 10)));
}
IImageProcessingContext DrawTextOldVersion(
@ -93,4 +93,4 @@ namespace SixLabors.ImageSharp.Benchmarks
}
}
}
}
}

14
tests/ImageSharp.Benchmarks/Drawing/DrawTextOutline.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Drawing;
@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Benchmarks
[Params(10, 100)]
public int TextIterations { get; set; }
public string TextPhrase { get; set; } = "Hello World";
public string TextToRender => string.Join(" ", Enumerable.Repeat(TextPhrase, TextIterations));
public string TextToRender => string.Join(" ", Enumerable.Repeat(this.TextPhrase, this.TextIterations));
[Benchmark(Baseline = true, Description = "System.Drawing Draw Text Outline")]
public void DrawTextSystemDrawing()
@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Benchmarks
using (var font = new Font("Arial", 12, GraphicsUnit.Point))
using (var gp = new GraphicsPath())
{
gp.AddString(TextToRender, font.FontFamily, (int)font.Style, font.Size, new RectangleF(10, 10, 780, 780), new StringFormat());
gp.AddString(this.TextToRender, font.FontFamily, (int)font.Style, font.Size, new RectangleF(10, 10, 780, 780), new StringFormat());
graphics.DrawPath(pen, gp);
}
}
@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Benchmarks
using (var image = new Image<Rgba32>(800, 800))
{
var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12);
image.Mutate(x => x.ApplyProcessor(new DrawTextProcessor(new TextGraphicsOptions(true) { WrapTextWidth = 780 }, TextToRender, font, null, Processing.Pens.Solid(Rgba32.HotPink, 10), new SixLabors.Primitives.PointF(10, 10))));
image.Mutate(x => x.ApplyProcessor(new DrawTextProcessor(new TextGraphicsOptions { Antialias = true, WrapTextWidth = 780 }, this.TextToRender, font, null, Processing.Pens.Solid(Rgba32.HotPink, 10), new SixLabors.Primitives.PointF(10, 10))));
}
}
@ -56,8 +56,8 @@ namespace SixLabors.ImageSharp.Benchmarks
image.Mutate(
x => DrawTextOldVersion(
x,
new TextGraphicsOptions(true) { WrapTextWidth = 780 },
TextToRender,
new TextGraphicsOptions { Antialias = true, WrapTextWidth = 780 },
this.TextToRender,
font,
null,
Processing.Pens.Solid(Rgba32.HotPink, 10),
@ -99,4 +99,4 @@ namespace SixLabors.ImageSharp.Benchmarks
}
}
}
}
}

1
tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj

@ -16,6 +16,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Magick.NET-Q16-AnyCPU" />
<PackageReference Include="BenchmarkDotNet" />
<PackageReference Include="Colourful" />
<PackageReference Include="SixLabors.Shapes.Text" />

11
tests/ImageSharp.Tests/ConfigurationTests.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@ -20,6 +20,8 @@ namespace SixLabors.ImageSharp.Tests
public Configuration ConfigurationEmpty { get; }
public Configuration DefaultConfiguration { get; }
private readonly int expectedDefaultConfigurationCount = 5;
public ConfigurationTests()
{
// the shallow copy of configuration should behave exactly like the default configuration,
@ -108,14 +110,13 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void ConfigurationCannotAddDuplicates()
{
const int count = 4;
Configuration config = this.DefaultConfiguration;
Assert.Equal(count, config.ImageFormats.Count());
Assert.Equal(expectedDefaultConfigurationCount, config.ImageFormats.Count());
config.ImageFormatsManager.AddImageFormat(BmpFormat.Instance);
Assert.Equal(count, config.ImageFormats.Count());
Assert.Equal(expectedDefaultConfigurationCount, config.ImageFormats.Count());
}
[Fact]
@ -123,7 +124,7 @@ namespace SixLabors.ImageSharp.Tests
{
Configuration config = Configuration.CreateDefaultInstance();
Assert.Equal(4, config.ImageFormats.Count());
Assert.Equal(expectedDefaultConfigurationCount, config.ImageFormats.Count());
}
[Fact]

2
tests/ImageSharp.Tests/Drawing/DrawImageTests.cs

@ -190,7 +190,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
void Test()
{
background.Mutate(context => context.DrawImage(overlay, new Point(x, y), GraphicsOptions.Default));
background.Mutate(context => context.DrawImage(overlay, new Point(x, y), new GraphicsOptions()));
}
}
}

22
tests/ImageSharp.Tests/Drawing/DrawLinesTests.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@ -24,10 +24,10 @@ namespace SixLabors.ImageSharp.Tests.Drawing
{
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha);
Pen pen = new Pen(color, thickness);
DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen);
}
[Theory]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, 5, false)]
public void DrawLines_Dash<TPixel>(TestImageProvider<TPixel> provider, string colorName, float alpha, float thickness, bool antialias)
@ -35,10 +35,10 @@ namespace SixLabors.ImageSharp.Tests.Drawing
{
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha);
Pen pen = Pens.Dash(color, thickness);
DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen);
}
[Theory]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "LightGreen", 1f, 5, false)]
public void DrawLines_Dot<TPixel>(TestImageProvider<TPixel> provider, string colorName, float alpha, float thickness, bool antialias)
@ -46,10 +46,10 @@ namespace SixLabors.ImageSharp.Tests.Drawing
{
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha);
Pen pen = Pens.Dot(color, thickness);
DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen);
}
[Theory]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "Yellow", 1f, 5, false)]
public void DrawLines_DashDot<TPixel>(TestImageProvider<TPixel> provider, string colorName, float alpha, float thickness, bool antialias)
@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
{
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha);
Pen pen = Pens.DashDot(color, thickness);
DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen);
}
@ -68,11 +68,11 @@ namespace SixLabors.ImageSharp.Tests.Drawing
{
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha);
Pen pen = Pens.DashDotDot(color, thickness);
DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen);
}
private static void DrawLinesImpl<TPixel>(
TestImageProvider<TPixel> provider,
string colorName,
@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
{
SixLabors.Primitives.PointF[] simplePath = { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) };
GraphicsOptions options = new GraphicsOptions(antialias);
GraphicsOptions options = new GraphicsOptions { Antialias = antialias };
string aa = antialias ? "" : "_NoAntialias";
FormattableString outputDetails = $"{colorName}_A({alpha})_T({thickness}){aa}";

2
tests/ImageSharp.Tests/Drawing/DrawPolygonTests.cs

@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
};
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha);
GraphicsOptions options = new GraphicsOptions(antialias);
GraphicsOptions options = new GraphicsOptions { Antialias = antialias };
string aa = antialias ? "" : "_NoAntialias";
FormattableString outputDetails = $"{colorName}_A({alpha})_T({thickness}){aa}";

45
tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@ -16,6 +16,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing
{
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.Primitives;
using SixLabors.Shapes;
[GroupOutput("Drawing/GradientBrushes")]
public class FillLinearGradientBrushTests
@ -392,5 +394,44 @@ namespace SixLabors.ImageSharp.Tests.Drawing
false,
false);
}
[Theory]
[WithBlankImages(200, 200, PixelTypes.Rgba32)]
public void GradientsWithTransparencyOnExistingBackground<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
provider.VerifyOperation(
image =>
{
image.Mutate(i => i.Fill(Color.Red));
image.Mutate(ApplyGloss);
});
void ApplyGloss(IImageProcessingContext ctx)
{
Size size = ctx.GetCurrentSize();
IPathCollection glossPath = BuildGloss(size.Width, size.Height);
var graphicsOptions = new GraphicsOptions
{
Antialias = true,
ColorBlendingMode = PixelColorBlendingMode.Normal,
AlphaCompositionMode = PixelAlphaCompositionMode.SrcAtop
};
var linearGradientBrush = new LinearGradientBrush(new Point(0, 0), new Point(0, size.Height / 2), GradientRepetitionMode.Repeat, new ColorStop(0, Color.White.WithAlpha(0.5f)), new ColorStop(1, Color.White.WithAlpha(0.25f)));
ctx.Fill(graphicsOptions, linearGradientBrush, glossPath);
}
IPathCollection BuildGloss(int imageWidth, int imageHeight)
{
var pathBuilder = new PathBuilder();
pathBuilder.AddLine(new PointF(0, 0), new PointF(imageWidth, 0));
pathBuilder.AddLine(new PointF(imageWidth, 0), new PointF(imageWidth, imageHeight * 0.4f));
pathBuilder.AddBezier(new PointF(imageWidth, imageHeight * 0.4f), new PointF(imageWidth / 2, imageHeight * 0.6f), new PointF(0, imageHeight * 0.4f));
pathBuilder.CloseFigure();
return new PathCollection(pathBuilder.Build());
}
}
}
}
}

2
tests/ImageSharp.Tests/Drawing/FillPolygonTests.cs

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
};
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha);
var options = new GraphicsOptions(antialias);
var options = new GraphicsOptions { Antialias = antialias };
string aa = antialias ? "" : "_NoAntialias";
FormattableString outputDetails = $"{colorName}_A{alpha}{aa}";

22
tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
@ -16,8 +16,6 @@ using SixLabors.Shapes;
namespace SixLabors.ImageSharp.Tests.Drawing
{
public class FillRegionProcessorTests
{
@ -35,11 +33,12 @@ namespace SixLabors.ImageSharp.Tests.Drawing
var brush = new Mock<IBrush>();
var region = new MockRegion2(bounds);
var options = new GraphicsOptions(antialias)
var options = new GraphicsOptions
{
Antialias = antialias,
AntialiasSubpixelDepth = 1
};
var processor = new FillRegionProcessor(brush.Object, region, options);
var processor = new FillRegionProcessor(options, brush.Object, region);
var img = new Image<Rgba32>(1, 1);
processor.Execute(img, bounds);
@ -51,8 +50,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing
{
var bounds = new Rectangle(-100, -10, 10, 10);
var brush = new Mock<IBrush>();
var options = new GraphicsOptions(true);
var processor = new FillRegionProcessor(brush.Object, new MockRegion1(), options);
var options = new GraphicsOptions { Antialias = true };
var processor = new FillRegionProcessor(options, brush.Object, new MockRegion1());
var img = new Image<Rgba32>(10, 10);
processor.Execute(img, bounds);
}
@ -73,11 +72,12 @@ namespace SixLabors.ImageSharp.Tests.Drawing
public void DoesNotThrowForIssue928()
{
var rectText = new RectangleF(0, 0, 2000, 2000);
using (Image<Rgba32> img = new Image<Rgba32>((int)rectText.Width, (int)rectText.Height))
using (var img = new Image<Rgba32>((int)rectText.Width, (int)rectText.Height))
{
img.Mutate(x => x.Fill(Rgba32.Transparent));
img.Mutate(ctx => {
img.Mutate(ctx =>
{
ctx.DrawLines(
Rgba32.Red,
0.984252f,
@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
new PointF(104.782608f, 1075.13245f),
new PointF(104.782608f, 1075.13245f)
);
}
}
);
}
}
@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
[Fact]
public void DoesNotThrowFillingTriangle()
{
using(var image = new Image<Rgba32>(28, 28))
using (var image = new Image<Rgba32>(28, 28))
{
var path = new Polygon(
new LinearLineSegment(new PointF(17.11f, 13.99659f), new PointF(14.01433f, 27.06201f)),

24
tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs

@ -156,10 +156,12 @@ namespace SixLabors.ImageSharp.Tests.Drawing
{
TPixel bgColor = image[0, 0];
var options = new GraphicsOptions(false)
{
ColorBlendingMode = blenderMode, BlendPercentage = blendPercentage
};
var options = new GraphicsOptions
{
Antialias = false,
ColorBlendingMode = blenderMode,
BlendPercentage = blendPercentage
};
if (triggerFillRegion)
{
@ -173,13 +175,13 @@ namespace SixLabors.ImageSharp.Tests.Drawing
}
var testOutputDetails = new
{
triggerFillRegion = triggerFillRegion,
newColorName = newColorName,
alpha = alpha,
blenderMode = blenderMode,
blendPercentage = blendPercentage
};
{
triggerFillRegion = triggerFillRegion,
newColorName = newColorName,
alpha = alpha,
blenderMode = blenderMode,
blendPercentage = blendPercentage
};
image.DebugSave(
provider,

17
tests/ImageSharp.Tests/Drawing/Paths/DrawPathCollection.cs

@ -7,6 +7,7 @@ using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Tests.Processing;
using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.Shapes;
using Xunit;
@ -14,7 +15,9 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
{
public class DrawPathCollection : BaseImageOperationsExtensionTest
{
GraphicsOptions noneDefault = new GraphicsOptions();
private static readonly GraphicsOptionsComparer graphicsOptionsComparer = new GraphicsOptionsComparer();
GraphicsOptions nonDefault = new GraphicsOptions { Antialias = false };
Color color = Color.HotPink;
Pen pen = Pens.Solid(Rgba32.HotPink, 1);
IPath path1 = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] {
@ -46,7 +49,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
{
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(i);
Assert.Equal(GraphicsOptions.Default, processor.Options);
Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer);
ShapePath region = Assert.IsType<ShapePath>(processor.Region);
@ -60,13 +63,13 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
[Fact]
public void CorrectlySetsBrushPathOptions()
{
this.operations.Draw(this.noneDefault, this.pen, this.pathCollection);
this.operations.Draw(this.nonDefault, this.pen, this.pathCollection);
for (int i = 0; i < 2; i++)
{
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(i);
Assert.Equal(this.noneDefault, processor.Options);
Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer);
ShapePath region = Assert.IsType<ShapePath>(processor.Region);
Assert.IsType<ComplexPolygon>(region.Shape);
@ -84,7 +87,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
{
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(i);
Assert.Equal(GraphicsOptions.Default, processor.Options);
Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer);
ShapePath region = Assert.IsType<ShapePath>(processor.Region);
Assert.IsType<ComplexPolygon>(region.Shape);
@ -97,13 +100,13 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
[Fact]
public void CorrectlySetsColorPathAndOptions()
{
this.operations.Draw(this.noneDefault, this.color, 1, this.pathCollection);
this.operations.Draw(this.nonDefault, this.color, 1, this.pathCollection);
for (int i = 0; i < 2; i++)
{
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(i);
Assert.Equal(this.noneDefault, processor.Options);
Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer);
ShapePath region = Assert.IsType<ShapePath>(processor.Region);
Assert.IsType<ComplexPolygon>(region.Shape);

17
tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs

@ -7,6 +7,7 @@ using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Tests.Processing;
using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.Shapes;
using Xunit;
@ -14,7 +15,9 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
{
public class FillPath : BaseImageOperationsExtensionTest
{
GraphicsOptions noneDefault = new GraphicsOptions();
private static readonly GraphicsOptionsComparer graphicsOptionsComparer = new GraphicsOptionsComparer();
GraphicsOptions nonDefault = new GraphicsOptions { Antialias = false };
Color color = Color.HotPink;
SolidBrush brush = Brushes.Solid(Rgba32.HotPink);
IPath path = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] {
@ -30,7 +33,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
this.operations.Fill(this.brush, this.path);
var processor = this.Verify<FillRegionProcessor>();
Assert.Equal(GraphicsOptions.Default, processor.Options);
Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
@ -44,10 +47,10 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
[Fact]
public void CorrectlySetsBrushPathOptions()
{
this.operations.Fill(this.noneDefault, this.brush, this.path);
this.operations.Fill(this.nonDefault, this.brush, this.path);
var processor = this.Verify<FillRegionProcessor>();
Assert.Equal(this.noneDefault, processor.Options);
Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
Polygon polygon = Assert.IsType<Polygon>(region.Shape);
@ -62,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
this.operations.Fill(this.color, this.path);
var processor = this.Verify<FillRegionProcessor>();
Assert.Equal(GraphicsOptions.Default, processor.Options);
Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
Polygon polygon = Assert.IsType<Polygon>(region.Shape);
@ -75,10 +78,10 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
[Fact]
public void CorrectlySetsColorPathAndOptions()
{
this.operations.Fill(this.noneDefault, this.color, this.path);
this.operations.Fill(this.nonDefault, this.color, this.path);
var processor = this.Verify<FillRegionProcessor>();
Assert.Equal(this.noneDefault, processor.Options);
Assert.Equal(this.nonDefault, processor.Options);
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
Polygon polygon = Assert.IsType<Polygon>(region.Shape);

17
tests/ImageSharp.Tests/Drawing/Paths/FillPathCollection.cs

@ -7,6 +7,7 @@ using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Tests.Processing;
using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.Shapes;
using Xunit;
@ -14,7 +15,9 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
{
public class FillPathCollection : BaseImageOperationsExtensionTest
{
GraphicsOptions noneDefault = new GraphicsOptions();
private static readonly GraphicsOptionsComparer graphicsOptionsComparer = new GraphicsOptionsComparer();
GraphicsOptions nonDefault = new GraphicsOptions { Antialias = false };
Color color = Color.HotPink;
SolidBrush brush = Brushes.Solid(Rgba32.HotPink);
IPath path1 = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] {
@ -46,7 +49,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
{
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(i);
Assert.Equal(GraphicsOptions.Default, processor.Options);
Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
@ -61,13 +64,13 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
[Fact]
public void CorrectlySetsBrushPathOptions()
{
this.operations.Fill(this.noneDefault, this.brush, this.pathCollection);
this.operations.Fill(this.nonDefault, this.brush, this.pathCollection);
for (int i = 0; i < 2; i++)
{
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(i);
Assert.Equal(this.noneDefault, processor.Options);
Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
Polygon polygon = Assert.IsType<Polygon>(region.Shape);
@ -86,7 +89,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
{
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(i);
Assert.Equal(GraphicsOptions.Default, processor.Options);
Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
Polygon polygon = Assert.IsType<Polygon>(region.Shape);
@ -100,13 +103,13 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
[Fact]
public void CorrectlySetsColorPathAndOptions()
{
this.operations.Fill(this.noneDefault, this.color, this.pathCollection);
this.operations.Fill(this.nonDefault, this.color, this.pathCollection);
for (int i = 0; i < 2; i++)
{
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(i);
Assert.Equal(this.noneDefault, processor.Options);
Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
Polygon polygon = Assert.IsType<Polygon>(region.Shape);

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save