Browse Source

Merge branch 'master' into feature/fix-576

af/merge-core
James Jackson-South 8 years ago
committed by GitHub
parent
commit
bfc37981a7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      appveyor.yml
  2. 47
      run-tests.ps1
  3. 2
      src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
  4. 4
      src/ImageSharp.Drawing/Processing/Drawing/Brushes/BrushApplicator.cs
  5. 36
      src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/ColorStop{TPixel}.cs
  6. 167
      src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush{TPixel}.cs
  7. 174
      src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/GradientBrushBase{TPixel}.cs
  8. 34
      src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/GradientRepetitionMode.cs
  9. 152
      src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/LinearGradientBrush{TPixel}.cs
  10. 102
      src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/RadialGradientBrush{TPixel}.cs
  11. 2
      src/ImageSharp.Drawing/Processing/Drawing/FillPathExtensions.cs
  12. 2
      src/ImageSharp/Advanced/IConfigurable.cs
  13. 55
      src/ImageSharp/Common/Extensions/ByteExtensions.cs
  14. 87
      src/ImageSharp/Common/Helpers/Guard.cs
  15. 24
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  16. 7
      src/ImageSharp/Formats/ImageFormatManager.cs
  17. 2
      src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs
  18. 3
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs
  19. 2
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs
  20. 2
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt
  21. 10
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
  22. 4
      src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs
  23. 6
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs
  24. 6
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs
  25. 6
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs
  26. 6
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs
  27. 7
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs
  28. 7
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs
  29. 4
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccK.cs
  30. 7
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs
  31. 5
      src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegComponent.cs
  32. 7
      src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs
  33. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs
  34. 3
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs
  35. 5
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs
  36. 6
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
  37. 18
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs
  38. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs
  39. 8
      src/ImageSharp/Formats/Jpeg/Components/Encoder/BlockQuad.cs
  40. 2
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffIndex.cs
  41. 2
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs
  42. 2
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs
  43. 2
      src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs
  44. 12
      src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs
  45. 4
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
  46. 2
      src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs
  47. 5
      src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.cs
  48. 5
      src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.tt
  49. 9
      src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs
  50. 3
      src/ImageSharp/Formats/Jpeg/Components/SizeExtensions.cs
  51. 3
      src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs
  52. 36
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bits.cs
  53. 62
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bytes.cs
  54. 28
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/DecoderThrowHelper.cs
  55. 27
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangComponent.cs
  56. 2
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangComponentScan.cs
  57. 2
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangDecoderErrorCode.cs
  58. 12
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangHuffmanTree.cs
  59. 11
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangJpegScanDecoder.ComputationData.cs
  60. 8
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangJpegScanDecoder.DataPointers.cs
  61. 67
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangJpegScanDecoder.cs
  62. 80
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/InputProcessor.cs
  63. 6
      src/ImageSharp/Formats/Jpeg/GolangPort/GolangJpegDecoder.cs
  64. 153
      src/ImageSharp/Formats/Jpeg/GolangPort/GolangJpegDecoderCore.cs
  65. 189
      src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegConstants.cs
  66. 125
      src/ImageSharp/Formats/Jpeg/JpegConstants.cs
  67. 6
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  68. 1
      src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
  69. 74
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  70. 5
      src/ImageSharp/Formats/Jpeg/JpegFormat.cs
  71. 238
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/DoubleBufferedStreamReader.cs
  72. 24
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFileMarker.cs
  73. 9
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs
  74. 460
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs
  75. 329
      src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs
  76. 7
      src/ImageSharp/Formats/Png/Filters/NoneFilter.cs
  77. 16
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  78. 12
      src/ImageSharp/Image.LoadPixelData.cs
  79. 90
      src/ImageSharp/ImageExtensions.cs
  80. 4
      src/ImageSharp/ImageFrame.LoadPixelData.cs
  81. 10
      src/ImageSharp/ImageFrameCollection.cs
  82. 15
      src/ImageSharp/ImageSharp.csproj
  83. 48
      src/ImageSharp/Memory/SpanHelper.cs
  84. 2
      src/ImageSharp/MetaData/ImageProperty.cs
  85. 2
      src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs
  86. 28
      src/ImageSharp/PixelFormats/ColorBuilder{TPixel}.cs
  87. 2
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs
  88. 2
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegMultiple.cs
  89. 114
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs
  90. 52
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs
  91. 12
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs
  92. 17
      tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs
  93. 4
      tests/ImageSharp.Benchmarks/General/Block8x8F_DivideRound.cs
  94. 3
      tests/ImageSharp.Benchmarks/General/Block8x8F_Round.cs
  95. 4
      tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
  96. 4
      tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj
  97. 188
      tests/ImageSharp.Tests/ConfigurationTests.cs
  98. 149
      tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs
  99. 351
      tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs
  100. 76
      tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs

2
appveyor.yml

@ -1,5 +1,5 @@
version: 1.0.0.{build}
image: Visual Studio 2017
image: Visual Studio 2017 Preview
# prevent the double build when a branch has an active PR
skip_branch_with_pr: true

47
run-tests.ps1

@ -15,6 +15,32 @@ function VerifyPath($path, $errorMessage) {
}
}
function CheckSubmoduleStatus() {
$submoduleStatus = (git submodule status) | Out-String
# if the result string is empty, the command failed to run (we didn't capture the error stream)
if ($submoduleStatus) {
# git has been called successfully, what about the status?
if (($submoduleStatus -match "\-") -or ($submoduleStatus -match "\(\(null\)\)"))
{
# submodule has not been initialized!
return 2;
}
elseif ($submoduleStatus -match "\+")
{
# submodule is not synced:
return 1;
}
else {
# everything fine:
return 0;
}
} else {
# git call failed, so we should warn
return 3;
}
}
if ( ($targetFramework -eq "netcoreapp2.0") -and ($env:CI -eq "True") -and ($is32Bit -ne "True")) {
# We execute CodeCoverage.cmd only for one specific job on CI (netcoreapp2.0 + 64bit )
$testRunnerCmd = ".\tests\CodeCoverage\CodeCoverage.cmd"
@ -64,4 +90,23 @@ Invoke-Expression $testRunnerCmd
cd $PSScriptRoot
exit $LASTEXITCODE
$exitCodeOfTests = $LASTEXITCODE;
if (0 -ne ([int]$exitCodeOfTests)) {
# check submodule status
$submoduleStatus = CheckSubmoduleStatus
if ([int]$submoduleStatus -eq 1) {
# not synced
Write-Host -ForegroundColor Yellow "Check if submodules are up to date. You can use 'git submodule update' to fix this";
} elseif ($submoduleStatus -eq 2) {
# not initialized
Write-Host -ForegroundColor Yellow "Check if submodules are initialized. You can run 'git submodule init' to initialize them."
} elseif ($submoduleStatus -eq 3) {
# git not found, maybe submodules not synced?
Write-Host -ForegroundColor Yellow "Could not check if submodules are initialized correctly. Maybe git is not installed?"
} else {
#Write-Host "Submodules are up to date";
}
}
exit $exitCodeOfTests

2
src/ImageSharp.Drawing/ImageSharp.Drawing.csproj

@ -38,7 +38,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="SixLabors.Core" Version="1.0.0-beta0005" />
<PackageReference Include="SixLabors.Shapes" Version="1.0.0-beta0004" />
<PackageReference Include="SixLabors.Shapes" Version="1.0.0-ci0018" />
<PackageReference Include="SixLabors.Shapes.Text" Version="1.0.0-beta0004" />
<AdditionalFiles Include="..\..\stylecop.json" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta004">

4
src/ImageSharp.Drawing/Processing/Drawing/Brushes/BrushApplicator.cs

@ -79,6 +79,10 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes
{
amountSpan[i] = scanline[i] * this.Options.BlendPercentage;
}
else
{
amountSpan[i] = scanline[i];
}
overlaySpan[i] = this[x + i, y];
}

36
src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/ColorStop{TPixel}.cs

@ -0,0 +1,36 @@
using System.Diagnostics;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes
{
/// <summary>
/// A struct that defines a single color stop.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
[DebuggerDisplay("ColorStop({Ratio} -> {Color}")]
public struct ColorStop<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="ColorStop{TPixel}" /> struct.
/// </summary>
/// <param name="ratio">Where should it be? 0 is at the start, 1 at the end of the Gradient.</param>
/// <param name="color">What color should be used at that point?</param>
public ColorStop(float ratio, TPixel color)
{
this.Ratio = ratio;
this.Color = color;
}
/// <summary>
/// Gets the point along the defined gradient axis.
/// </summary>
public float Ratio { get; }
/// <summary>
/// Gets the color to be used.
/// </summary>
public TPixel Color { get; }
}
}

167
src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush{TPixel}.cs

@ -0,0 +1,167 @@
using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes
{
/// <summary>
/// Gradient Brush with elliptic shape.
/// The ellipse is defined by a center point,
/// a point on the longest extension of the ellipse and
/// the ratio between longest and shortest extension.
/// </summary>
/// <typeparam name="TPixel">The Pixel format that is used.</typeparam>
public sealed class EllipticGradientBrush<TPixel> : GradientBrushBase<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly Point center;
private readonly Point referenceAxisEnd;
private readonly float axisRatio;
/// <inheritdoc cref="GradientBrushBase{TPixel}" />
/// <param name="center">The center of the elliptical gradient and 0 for the color stops.</param>
/// <param name="referenceAxisEnd">The end point of the reference axis of the ellipse.</param>
/// <param name="axisRatio">
/// The ratio of the axis widths.
/// The second axis' is perpendicular to the reference axis and
/// it's length is the reference axis' length multiplied by this factor.
/// </param>
/// <param name="repetitionMode">Defines how the colors of the gradients are repeated.</param>
/// <param name="colorStops">the color stops as defined in base class.</param>
public EllipticGradientBrush(
Point center,
Point referenceAxisEnd,
float axisRatio,
GradientRepetitionMode repetitionMode,
params ColorStop<TPixel>[] colorStops)
: base(repetitionMode, colorStops)
{
this.center = center;
this.referenceAxisEnd = referenceAxisEnd;
this.axisRatio = axisRatio;
}
/// <inheritdoc cref="CreateApplicator" />
public override BrushApplicator<TPixel> CreateApplicator(
ImageFrame<TPixel> source,
RectangleF region,
GraphicsOptions options) =>
new RadialGradientBrushApplicator(
source,
options,
this.center,
this.referenceAxisEnd,
this.axisRatio,
this.ColorStops,
this.RepetitionMode);
/// <inheritdoc />
private sealed class RadialGradientBrushApplicator : GradientBrushApplicatorBase
{
private readonly Point center;
private readonly Point referenceAxisEnd;
private readonly float axisRatio;
private readonly double rotation;
private readonly float referenceRadius;
private readonly float secondRadius;
private readonly float cosRotation;
private readonly float sinRotation;
private readonly float secondRadiusSquared;
private readonly float referenceRadiusSquared;
/// <summary>
/// Initializes a new instance of the <see cref="RadialGradientBrushApplicator" /> 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="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="repetitionMode">Defines how the gradient colors are repeated.</param>
public RadialGradientBrushApplicator(
ImageFrame<TPixel> target,
GraphicsOptions options,
Point center,
Point referenceAxisEnd,
float axisRatio,
ColorStop<TPixel>[] colorStops,
GradientRepetitionMode repetitionMode)
: base(target, options, colorStops, repetitionMode)
{
this.center = center;
this.referenceAxisEnd = referenceAxisEnd;
this.axisRatio = axisRatio;
this.rotation = this.AngleBetween(
this.center,
new PointF(this.center.X + 1, this.center.Y),
this.referenceAxisEnd);
this.referenceRadius = this.DistanceBetween(this.center, this.referenceAxisEnd);
this.secondRadius = this.referenceRadius * this.axisRatio;
this.referenceRadiusSquared = this.referenceRadius * this.referenceRadius;
this.secondRadiusSquared = this.secondRadius * this.secondRadius;
this.sinRotation = (float)Math.Sin(this.rotation);
this.cosRotation = (float)Math.Cos(this.rotation);
}
/// <inheritdoc />
public override void Dispose()
{
}
/// <inheritdoc />
protected override float PositionOnGradient(int xt, int yt)
{
float x0 = xt - this.center.X;
float y0 = yt - this.center.Y;
float x = (x0 * this.cosRotation) - (y0 * this.sinRotation);
float y = (x0 * this.sinRotation) + (y0 * this.cosRotation);
float xSquared = x * x;
float ySquared = y * y;
var inBoundaryChecker = (xSquared / this.referenceRadiusSquared)
+ (ySquared / this.secondRadiusSquared);
return inBoundaryChecker;
}
private float AngleBetween(PointF junction, PointF a, PointF b)
{
var vA = a - junction;
var vB = b - junction;
return (float)(Math.Atan2(vB.Y, vB.X)
- Math.Atan2(vA.Y, vA.X));
}
private float DistanceBetween(
PointF p1,
PointF p2)
{
float dX = p1.X - p2.X;
float dXsquared = dX * dX;
float dY = p1.Y - p2.Y;
float dYsquared = dY * dY;
return (float)Math.Sqrt(dXsquared + dYsquared);
}
}
}
}

174
src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/GradientBrushBase{TPixel}.cs

@ -0,0 +1,174 @@
using System;
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.PixelFormats.PixelBlenders;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes
{
/// <summary>
/// Base class for Gradient brushes
/// </summary>
/// <typeparam name="TPixel">The pixel format</typeparam>
public abstract class GradientBrushBase<TPixel> : IBrush<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <inheritdoc cref="IBrush{TPixel}"/>
/// <param name="repetitionMode">Defines how the colors are repeated beyond the interval [0..1]</param>
/// <param name="colorStops">The gradient colors.</param>
protected GradientBrushBase(
GradientRepetitionMode repetitionMode,
params ColorStop<TPixel>[] colorStops)
{
this.RepetitionMode = repetitionMode;
this.ColorStops = colorStops;
}
/// <summary>
/// Gets how the colors are repeated beyond the interval [0..1].
/// </summary>
protected GradientRepetitionMode RepetitionMode { get; }
/// <summary>
/// Gets the list of color stops for this gradient.
/// </summary>
protected ColorStop<TPixel>[] ColorStops { get; }
/// <inheritdoc cref="IBrush{TPixel}" />
public abstract BrushApplicator<TPixel> CreateApplicator(
ImageFrame<TPixel> source,
RectangleF region,
GraphicsOptions options);
/// <summary>
/// Base class for gradient brush applicators
/// </summary>
protected abstract class GradientBrushApplicatorBase : BrushApplicator<TPixel>
{
private readonly ColorStop<TPixel>[] colorStops;
private readonly GradientRepetitionMode repetitionMode;
/// <summary>
/// Initializes a new instance of the <see cref="GradientBrushApplicatorBase"/> class.
/// </summary>
/// <param name="target">The target.</param>
/// <param name="options">The options.</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 GradientBrushApplicatorBase(
ImageFrame<TPixel> target,
GraphicsOptions options,
ColorStop<TPixel>[] colorStops,
GradientRepetitionMode repetitionMode)
: base(target, options)
{
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>
internal override TPixel this[int x, int y]
{
get
{
float positionOnCompleteGradient = this.PositionOnGradient(x, y);
switch (this.repetitionMode)
{
case GradientRepetitionMode.None:
// do nothing. The following could be done, but is not necessary:
// onLocalGradient = Math.Min(0, Math.Max(1, onLocalGradient));
break;
case GradientRepetitionMode.Repeat:
positionOnCompleteGradient = positionOnCompleteGradient % 1;
break;
case GradientRepetitionMode.Reflect:
positionOnCompleteGradient = positionOnCompleteGradient % 2;
if (positionOnCompleteGradient > 1)
{
positionOnCompleteGradient = 2 - positionOnCompleteGradient;
}
break;
case GradientRepetitionMode.DontFill:
if (positionOnCompleteGradient > 1 || positionOnCompleteGradient < 0)
{
return NamedColors<TPixel>.Transparent;
}
break;
default:
throw new ArgumentOutOfRangeException();
}
var (from, to) = this.GetGradientSegment(positionOnCompleteGradient);
if (from.Color.Equals(to.Color))
{
return from.Color;
}
else
{
var fromAsVector = from.Color.ToVector4();
var toAsVector = to.Color.ToVector4();
float onLocalGradient = (positionOnCompleteGradient - from.Ratio) / to.Ratio;
// TODO: this should be changeble for different gradienting functions
Vector4 result = PorterDuffFunctions.Normal(
fromAsVector,
toAsVector,
onLocalGradient);
TPixel resultColor = default;
resultColor.PackFromVector4(result);
return resultColor;
}
}
}
/// <summary>
/// calculates the position on the gradient for a given pixel.
/// This method is abstract as it's content depends on the shape of the gradient.
/// </summary>
/// <param name="x">The x coordinate of the pixel</param>
/// <param name="y">The y coordinate of the pixel</param>
/// <returns>
/// The position the given pixel has on the gradient.
/// The position is not bound to the [0..1] interval.
/// Values outside of that interval may be treated differently,
/// e.g. for the <see cref="GradientRepetitionMode" /> enum.
/// </returns>
protected abstract float PositionOnGradient(int x, int y);
private (ColorStop<TPixel> from, ColorStop<TPixel> to) GetGradientSegment(
float positionOnCompleteGradient)
{
var localGradientFrom = this.colorStops[0];
ColorStop<TPixel> localGradientTo = default;
// TODO: ensure colorStops has at least 2 items (technically 1 would be okay, but that's no gradient)
foreach (var colorStop in this.colorStops)
{
localGradientTo = colorStop;
if (colorStop.Ratio > positionOnCompleteGradient)
{
// we're done here, so break it!
break;
}
localGradientFrom = localGradientTo;
}
return (localGradientFrom, localGradientTo);
}
}
}
}

34
src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/GradientRepetitionMode.cs

@ -0,0 +1,34 @@
namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes
{
/// <summary>
/// Modes to repeat a gradient.
/// </summary>
public enum GradientRepetitionMode
{
/// <summary>
/// don't repeat, keep the color of start and end beyond those points stable.
/// </summary>
None,
/// <summary>
/// Repeat the gradient.
/// If it's a black-white gradient, with Repeat it will be Black->{gray}->White|Black->{gray}->White|...
/// </summary>
Repeat,
/// <summary>
/// Reflect the gradient.
/// Similar to <see cref="Repeat"/>, but each other repetition uses inverse order of <see cref="ColorStop{TPixel}"/>s.
/// Used on a Black-White gradient, Reflect leads to Black->{gray}->White->{gray}->White...
/// </summary>
Reflect,
/// <summary>
/// With DontFill a gradient does not touch any pixel beyond it's borders.
/// For the <see cref="LinearGradientBrush{TPixel}" /> this is beyond the orthogonal through start and end,
/// TODO For the cref="PolygonalGradientBrush" it's outside the polygon,
/// For <see cref="RadialGradientBrush{TPixel}" /> and <see cref="EllipticGradientBrush{TPixel}" /> it's beyond 1.0.
/// </summary>
DontFill
}
}

152
src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/LinearGradientBrush{TPixel}.cs

@ -0,0 +1,152 @@
using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes
{
/// <summary>
/// Provides an implementation of a brush for painting linear gradients within areas.
/// Supported right now:
/// - a set of colors in relative distances to each other.
/// </summary>
/// <typeparam name="TPixel">The pixel format</typeparam>
public sealed class LinearGradientBrush<TPixel> : GradientBrushBase<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly Point p1;
private readonly Point p2;
/// <summary>
/// Initializes a new instance of the <see cref="LinearGradientBrush{TPixel}"/> class.
/// </summary>
/// <param name="p1">Start point</param>
/// <param name="p2">End point</param>
/// <param name="repetitionMode">defines how colors are repeated.</param>
/// <param name="colorStops"><inheritdoc /></param>
public LinearGradientBrush(
Point p1,
Point p2,
GradientRepetitionMode repetitionMode,
params ColorStop<TPixel>[] colorStops)
: base(repetitionMode, colorStops)
{
this.p1 = p1;
this.p2 = p2;
}
/// <inheritdoc />
public override BrushApplicator<TPixel> CreateApplicator(ImageFrame<TPixel> source, RectangleF region, GraphicsOptions options)
=> new LinearGradientBrushApplicator(source, this.p1, this.p2, this.ColorStops, this.RepetitionMode, options);
/// <summary>
/// The linear gradient brush applicator.
/// </summary>
private sealed class LinearGradientBrushApplicator : GradientBrushApplicatorBase
{
private readonly Point start;
private readonly Point end;
/// <summary>
/// the vector along the gradient, x component
/// </summary>
private readonly float alongX;
/// <summary>
/// the vector along the gradient, y component
/// </summary>
private readonly float alongY;
/// <summary>
/// the vector perpendicular to the gradient, y component
/// </summary>
private readonly float acrossY;
/// <summary>
/// the vector perpendicular to the gradient, x component
/// </summary>
private readonly float acrossX;
/// <summary>
/// the result of <see cref="alongX"/>^2 + <see cref="alongY"/>^2
/// </summary>
private readonly float alongsSquared;
/// <summary>
/// the length of the defined gradient (between source and end)
/// </summary>
private readonly float length;
/// <summary>
/// Initializes a new instance of the <see cref="LinearGradientBrushApplicator" /> 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>
public LinearGradientBrushApplicator(
ImageFrame<TPixel> source,
Point start,
Point end,
ColorStop<TPixel>[] colorStops,
GradientRepetitionMode repetitionMode,
GraphicsOptions options)
: base(source, options, colorStops, repetitionMode)
{
this.start = start;
this.end = end;
// the along vector:
this.alongX = this.end.X - this.start.X;
this.alongY = this.end.Y - this.start.Y;
// the cross vector:
this.acrossX = this.alongY;
this.acrossY = -this.alongX;
// some helpers:
this.alongsSquared = (this.alongX * this.alongX) + (this.alongY * this.alongY);
this.length = (float)Math.Sqrt(this.alongsSquared);
}
protected override float PositionOnGradient(int x, int y)
{
if (this.acrossX == 0)
{
return (x - this.start.X) / (float)(this.end.X - this.start.X);
}
else if (this.acrossY == 0)
{
return (y - this.start.Y) / (float)(this.end.Y - this.start.Y);
}
else
{
float deltaX = x - this.start.X;
float deltaY = y - this.start.Y;
float k = ((this.alongY * deltaX) - (this.alongX * deltaY)) / this.alongsSquared;
// point on the line:
float x4 = x - (k * this.alongY);
float y4 = y + (k * this.alongX);
// get distance from (x4,y4) to start
float distance = (float)Math.Sqrt(
Math.Pow(x4 - this.start.X, 2)
+ Math.Pow(y4 - this.start.Y, 2));
// get and return ratio
float ratio = distance / this.length;
return ratio;
}
}
public override void Dispose()
{
}
}
}
}

102
src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/RadialGradientBrush{TPixel}.cs

@ -0,0 +1,102 @@
using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes
{
/// <summary>
/// A Circular Gradient Brush, defined by center point and radius.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public sealed class RadialGradientBrush<TPixel> : GradientBrushBase<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly Point center;
private readonly float radius;
/// <inheritdoc cref="GradientBrushBase{TPixel}" />
/// <param name="center">The center of the circular gradient and 0 for the color stops.</param>
/// <param name="radius">The radius of the circular gradient and 1 for the color stops.</param>
/// <param name="repetitionMode">Defines how the colors in the gradient are repeated.</param>
/// <param name="colorStops">the color stops as defined in base class.</param>
public RadialGradientBrush(
Point center,
float radius,
GradientRepetitionMode repetitionMode,
params ColorStop<TPixel>[] colorStops)
: base(repetitionMode, colorStops)
{
this.center = center;
this.radius = radius;
}
/// <inheritdoc cref="CreateApplicator" />
public override BrushApplicator<TPixel> CreateApplicator(
ImageFrame<TPixel> source,
RectangleF region,
GraphicsOptions options) =>
new RadialGradientBrushApplicator(
source,
options,
this.center,
this.radius,
this.ColorStops,
this.RepetitionMode);
/// <inheritdoc />
private sealed class RadialGradientBrushApplicator : GradientBrushApplicatorBase
{
private readonly Point center;
private readonly float radius;
/// <summary>
/// Initializes a new instance of the <see cref="RadialGradientBrushApplicator" /> class.
/// </summary>
/// <param name="target">The target image</param>
/// <param name="options">The options.</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,
GraphicsOptions options,
Point center,
float radius,
ColorStop<TPixel>[] colorStops,
GradientRepetitionMode repetitionMode)
: base(target, options, 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.
/// </summary>
/// <param name="x">The X coordinate of the target pixel.</param>
/// <param name="y">The Y coordinate of the target pixel.</param>
/// <returns>the position on the color gradient.</returns>
protected override float PositionOnGradient(int x, int y)
{
float distance = (float)Math.Sqrt(Math.Pow(this.center.X - x, 2) + Math.Pow(this.center.Y - y, 2));
return distance / this.radius;
}
internal override void Apply(Span<float> scanline, int x, int y)
{
// TODO: each row is symmetric across center, so we can calculate half of it and mirror it to improve performance.
base.Apply(scanline, x, y);
}
}
}
}

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

@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing
public static class FillPathExtensions
{
/// <summary>
/// Flood fills the image in the shape of the provided polygon with the specified brush..
/// Flood fills the image in the shape of the provided polygon with the specified brush.
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param>

2
src/ImageSharp/Advanced/IConfigurable.cs

@ -4,7 +4,7 @@
namespace SixLabors.ImageSharp.Advanced
{
/// <summary>
/// Encapsulates the properties for configuration
/// Encapsulates the properties for configuration.
/// </summary>
internal interface IConfigurable
{

55
src/ImageSharp/Common/Extensions/ByteExtensions.cs

@ -1,55 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Extension methods for the <see cref="byte"/> struct buffers.
/// </summary>
internal static class ByteExtensions
{
/// <summary>
/// Returns a reference to the given position of the array unsafe casted to <see cref="ImageSharp.PixelFormats.Rgb24"/>.
/// </summary>
/// <param name="bytes">The byte array.</param>
/// <param name="offset">The offset in bytes.</param>
/// <returns>The <see cref="ImageSharp.PixelFormats.Rgb24"/> reference at the given offset.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref Rgb24 GetRgb24(this byte[] bytes, int offset)
{
DebugGuard.MustBeLessThan(offset + 2, bytes.Length, nameof(offset));
return ref Unsafe.As<byte, Rgb24>(ref bytes[offset]);
}
/// <summary>
/// Returns a reference to the given position of the span unsafe casted to <see cref="ImageSharp.PixelFormats.Rgb24"/>.
/// </summary>
/// <param name="bytes">The byte span.</param>
/// <param name="offset">The offset in bytes.</param>
/// <returns>The <see cref="ImageSharp.PixelFormats.Rgb24"/> reference at the given offset.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref Rgb24 GetRgb24(this Span<byte> bytes, int offset)
{
DebugGuard.MustBeLessThan(offset + 2, bytes.Length, nameof(offset));
return ref Unsafe.As<byte, Rgb24>(ref bytes[offset]);
}
/// <summary>
/// Returns a reference to the given position of the buffer pointed by `baseRef` unsafe casted to <see cref="ImageSharp.PixelFormats.Rgb24"/>.
/// </summary>
/// <param name="baseRef">A reference to the beginning of the buffer</param>
/// <param name="offset">The offset in bytes.</param>
/// <returns>The <see cref="ImageSharp.PixelFormats.Rgb24"/> reference at the given offset.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref Rgb24 GetRgb24(ref byte baseRef, int offset)
{
return ref Unsafe.As<byte, Rgb24>(ref Unsafe.Add(ref baseRef, offset));
}
}
}

87
src/ImageSharp/Common/Helpers/Guard.cs

@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace SixLabors.ImageSharp
{
@ -15,78 +14,62 @@ namespace SixLabors.ImageSharp
internal static class Guard
{
/// <summary>
/// Verifies, that the method parameter with specified object value is not null
/// and throws an exception if it is found to be so.
/// Ensures that the value is not null.
/// </summary>
/// <param name="target">The target object, which cannot be null.</param>
/// <param name="value">The target object, which cannot be null.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <param name="message">The error message, if any to add to the exception.</param>
/// <exception cref="ArgumentNullException"><paramref name="target"/> is null</exception>
public static void NotNull(object target, string parameterName, string message = "")
/// <exception cref="ArgumentNullException"><paramref name="value"/> is null</exception>
public static void NotNull(object value, string parameterName)
{
if (target == null)
if (value == null)
{
if (!string.IsNullOrWhiteSpace(message))
{
throw new ArgumentNullException(parameterName, message);
}
throw new ArgumentNullException(parameterName);
}
}
/// <summary>
/// Verifies, that the string method parameter with specified object value and message
/// is not null, not empty and does not contain only blanks and throws an exception
/// if the object is null.
/// Ensures that the target value is not null, empty, or whitespace.
/// </summary>
/// <param name="target">The target string, which should be checked against being null or empty.</param>
/// <param name="value">The target string, which should be checked against being null or empty.</param>
/// <param name="parameterName">Name of the parameter.</param>
/// <param name="message">The error message, if any to add to the exception.</param>
/// <exception cref="ArgumentNullException"><paramref name="target"/> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="target"/> is empty or contains only blanks.</exception>
public static void NotNullOrEmpty(string target, string parameterName, string message = "")
/// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="value"/> is empty or contains only blanks.</exception>
public static void NotNullOrWhiteSpace(string value, string parameterName)
{
NotNull(target, parameterName, message);
if (string.IsNullOrWhiteSpace(target))
if (value == null)
{
if (!string.IsNullOrWhiteSpace(message))
{
throw new ArgumentException(message, parameterName);
}
throw new ArgumentNullException(parameterName);
}
throw new ArgumentException("Value cannot be null or empty and cannot contain only blanks.", parameterName);
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException("Must not be empty or whitespace.", parameterName);
}
}
/// <summary>
/// Verifies, that the enumeration is not null and not empty.
/// Ensures that the enumeration is not null or empty.
/// </summary>
/// <typeparam name="T">The type of objects in the <paramref name="target"/></typeparam>
/// <param name="target">The target enumeration, which should be checked against being null or empty.</param>
/// <typeparam name="T">The type of objects in the <paramref name="value"/></typeparam>
/// <param name="value">The target enumeration, which should be checked against being null or empty.</param>
/// <param name="parameterName">Name of the parameter.</param>
/// <param name="message">The error message, if any to add to the exception.</param>
/// <exception cref="ArgumentNullException"><paramref name="target"/> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="target"/> is empty.</exception>
public static void NotNullOrEmpty<T>(IEnumerable<T> target, string parameterName, string message = "")
/// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="value"/> is empty.</exception>
public static void NotNullOrEmpty<T>(ICollection<T> value, string parameterName)
{
NotNull(target, parameterName, message);
if (!target.Any())
if (value == null)
{
if (!string.IsNullOrWhiteSpace(message))
{
throw new ArgumentException(message, parameterName);
}
throw new ArgumentNullException(parameterName);
}
throw new ArgumentException("Value cannot be empty.", parameterName);
if (value.Count == 0)
{
throw new ArgumentException("Must not be empty.", parameterName);
}
}
/// <summary>
/// Verifies that the specified value is less than a maximum value
/// and throws an exception if it is not.
/// Ensures that the specified value is less than a maximum value.
/// </summary>
/// <param name="value">The target value, which should be validated.</param>
/// <param name="max">The maximum value.</param>
@ -191,15 +174,9 @@ namespace SixLabors.ImageSharp
/// Verifies, that the method parameter with specified target value is true
/// and throws an exception if it is found to be so.
/// </summary>
/// <param name="target">
/// The target value, which cannot be false.
/// </param>
/// <param name="parameterName">
/// The name of the parameter that is to be checked.
/// </param>
/// <param name="message">
/// The error message, if any to add to the exception.
/// </param>
/// <param name="target">The target value, which cannot be false.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <param name="message">The error message, if any to add to the exception.</param>
/// <exception cref="ArgumentException">
/// <paramref name="target"/> is false
/// </exception>

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

@ -39,11 +39,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
private IManagedByteBuffer globalColorTable;
/// <summary>
/// The global color table length
/// </summary>
private int globalColorTableLength;
/// <summary>
/// The area to restore.
/// </summary>
@ -333,8 +328,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
indices = this.configuration.MemoryManager.AllocateManagedByteBuffer(imageDescriptor.Width * imageDescriptor.Height, true);
this.ReadFrameIndices(imageDescriptor, indices.Span);
IManagedByteBuffer colorTable = localColorTable ?? this.globalColorTable;
this.ReadFrameColors(ref image, ref previousFrame, indices.Span, colorTable.Span, imageDescriptor);
ReadOnlySpan<Rgb24> colorTable = MemoryMarshal.Cast<byte, Rgb24>((localColorTable ?? this.globalColorTable).Span);
this.ReadFrameColors(ref image, ref previousFrame, indices.Span, colorTable, imageDescriptor);
// Skip any remaining blocks
this.Skip(0);
@ -370,7 +365,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <param name="indices">The indexed pixels.</param>
/// <param name="colorTable">The color table containing the available colors.</param>
/// <param name="descriptor">The <see cref="GifImageDescriptor"/></param>
private void ReadFrameColors<TPixel>(ref Image<TPixel> image, ref ImageFrame<TPixel> previousFrame, Span<byte> indices, Span<byte> colorTable, in GifImageDescriptor descriptor)
private void ReadFrameColors<TPixel>(ref Image<TPixel> image, ref ImageFrame<TPixel> previousFrame, Span<byte> indices, ReadOnlySpan<Rgb24> colorTable, in GifImageDescriptor descriptor)
where TPixel : struct, IPixel<TPixel>
{
ref byte indicesRef = ref MemoryMarshal.GetReference(indices);
@ -458,11 +453,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
if (this.graphicsControlExtension.TransparencyFlag == false ||
this.graphicsControlExtension.TransparencyIndex != index)
{
int indexOffset = index * 3;
ref TPixel pixel = ref Unsafe.Add(ref rowRef, x);
rgba.Rgb = colorTable.GetRgb24(indexOffset);
rgba.Rgb = colorTable[index];
pixel.PackFromRgba32(rgba);
}
@ -534,12 +526,12 @@ namespace SixLabors.ImageSharp.Formats.Gif
if (this.logicalScreenDescriptor.GlobalColorTableFlag)
{
this.globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3;
int globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3;
this.globalColorTable = this.MemoryManager.AllocateManagedByteBuffer(this.globalColorTableLength, true);
this.globalColorTable = this.MemoryManager.AllocateManagedByteBuffer(globalColorTableLength, true);
// Read the global color table from the stream
stream.Read(this.globalColorTable.Array, 0, this.globalColorTableLength);
// Read the global color table data from the stream
stream.Read(this.globalColorTable.Array, 0, globalColorTableLength);
}
}
}

7
src/ImageSharp/Formats/ImageFormatManager.cs

@ -85,6 +85,13 @@ namespace SixLabors.ImageSharp.Formats
/// <returns>The <see cref="IImageFormat"/> if found otherwise null</returns>
public IImageFormat FindFormatByFileExtension(string extension)
{
Guard.NotNullOrWhiteSpace(extension, nameof(extension));
if (extension[0] == '.')
{
extension = extension.Substring(1);
}
return this.imageFormats.FirstOrDefault(x => x.FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase));
}

2
src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs → src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs

@ -7,7 +7,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
/// <summary>
/// Represents a Jpeg block with <see cref="short"/> coefficiens.

3
src/ImageSharp/Formats/Jpeg/Common/Block8x8F.CopyTo.cs → src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs

@ -3,10 +3,11 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal partial struct Block8x8F
{

2
src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs → src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs

@ -5,7 +5,7 @@ using System.Numerics;
using System.Runtime.CompilerServices;
// <auto-generated />
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal partial struct Block8x8F
{

2
src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt → src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt

@ -18,7 +18,7 @@ using System.Runtime.CompilerServices;
<#
char[] coordz = {'X', 'Y', 'Z', 'W'};
#>
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal partial struct Block8x8F
{

10
src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs → src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs

@ -9,7 +9,7 @@ using System.Runtime.InteropServices;
using System.Text;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
/// <summary>
/// Represents a Jpeg block with <see cref="float"/> coefficients.
@ -100,7 +100,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
{
float val = result[i];
val /= value;
result[i] = (float)val;
result[i] = val;
}
return result;
@ -113,7 +113,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
{
float val = result[i];
val += value;
result[i] = (float)val;
result[i] = val;
}
return result;
@ -126,7 +126,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
{
float val = result[i];
val -= value;
result[i] = (float)val;
result[i] = val;
}
return result;
@ -153,7 +153,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
public void Clear()
{
// The cheapest way to do this in C#:
this = default(Block8x8F);
this = default;
}
/// <summary>

4
src/ImageSharp/Formats/Jpeg/Common/Decoder/AdobeMarker.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs

@ -4,7 +4,7 @@
using System;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Provides information about the Adobe marker segment.
@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
return true;
}
marker = default(AdobeMarker);
marker = default;
return false;
}

6
src/ImageSharp/Formats/Jpeg/Common/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs

@ -4,18 +4,18 @@
using System;
using System.Numerics;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder.ColorConverters
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal class FromCmyk : ColorConverters.JpegColorConverter
internal class FromCmyk : JpegColorConverter
{
public FromCmyk()
: base(JpegColorSpace.Cmyk)
{
}
public override void ConvertToRGBA(ComponentValues values, Span<Vector4> result)
public override void ConvertToRgba(ComponentValues values, Span<Vector4> result)
{
// TODO: We can optimize a lot here with Vector<float> and SRCS.Unsafe()!
ReadOnlySpan<float> cVals = values.Component0;

6
src/ImageSharp/Formats/Jpeg/Common/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs

@ -4,18 +4,18 @@
using System;
using System.Numerics;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder.ColorConverters
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal class FromGrayscale : ColorConverters.JpegColorConverter
internal class FromGrayscale : JpegColorConverter
{
public FromGrayscale()
: base(JpegColorSpace.Grayscale)
{
}
public override void ConvertToRGBA(ComponentValues values, Span<Vector4> result)
public override void ConvertToRgba(ComponentValues values, Span<Vector4> result)
{
// TODO: We can optimize a lot here with Vector<float> and SRCS.Unsafe()!
ReadOnlySpan<float> yVals = values.Component0;

6
src/ImageSharp/Formats/Jpeg/Common/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs

@ -4,18 +4,18 @@
using System;
using System.Numerics;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder.ColorConverters
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal class FromRgb : ColorConverters.JpegColorConverter
internal class FromRgb : JpegColorConverter
{
public FromRgb()
: base(JpegColorSpace.RGB)
{
}
public override void ConvertToRGBA(ComponentValues values, Span<Vector4> result)
public override void ConvertToRgba(ComponentValues values, Span<Vector4> result)
{
// TODO: We can optimize a lot here with Vector<float> and SRCS.Unsafe()!
ReadOnlySpan<float> rVals = values.Component0;

6
src/ImageSharp/Formats/Jpeg/Common/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs

@ -4,18 +4,18 @@
using System;
using System.Numerics;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder.ColorConverters
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal class FromYCbCrBasic : ColorConverters.JpegColorConverter
internal class FromYCbCrBasic : JpegColorConverter
{
public FromYCbCrBasic()
: base(JpegColorSpace.YCbCr)
{
}
public override void ConvertToRGBA(ComponentValues values, Span<Vector4> result)
public override void ConvertToRgba(ComponentValues values, Span<Vector4> result)
{
ConvertCore(values, result);
}

7
src/ImageSharp/Formats/Jpeg/Common/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs

@ -5,20 +5,21 @@ using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Common.Tuples;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder.ColorConverters
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal class FromYCbCrSimd : ColorConverters.JpegColorConverter
internal class FromYCbCrSimd : JpegColorConverter
{
public FromYCbCrSimd()
: base(JpegColorSpace.YCbCr)
{
}
public override void ConvertToRGBA(ComponentValues values, Span<Vector4> result)
public override void ConvertToRgba(ComponentValues values, Span<Vector4> result)
{
int remainder = result.Length % 8;
int simdCount = result.Length - remainder;

7
src/ImageSharp/Formats/Jpeg/Common/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs

@ -5,14 +5,15 @@ using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Common.Tuples;
// ReSharper disable ImpureMethodCallOnReadonlyValueField
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder.ColorConverters
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
internal class FromYCbCrSimdAvx2 : ColorConverters.JpegColorConverter
internal class FromYCbCrSimdAvx2 : JpegColorConverter
{
public FromYCbCrSimdAvx2()
: base(JpegColorSpace.YCbCr)
@ -21,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder.ColorConverters
public static bool IsAvailable => Vector.IsHardwareAccelerated && SimdUtils.IsAvx2CompatibleArchitecture;
public override void ConvertToRGBA(ComponentValues values, Span<Vector4> result)
public override void ConvertToRgba(ComponentValues values, Span<Vector4> result)
{
int remainder = result.Length % 8;
int simdCount = result.Length - remainder;

4
src/ImageSharp/Formats/Jpeg/Common/Decoder/ColorConverters/JpegColorConverter.FromYccK.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccK.cs

@ -4,7 +4,7 @@
using System;
using System.Numerics;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder.ColorConverters
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder.ColorConverters
{
}
public override void ConvertToRGBA(ComponentValues values, Span<Vector4> result)
public override void ConvertToRgba(ComponentValues values, Span<Vector4> result)
{
// TODO: We can optimize a lot here with Vector<float> and SRCS.Unsafe()!
ReadOnlySpan<float> yVals = values.Component0;

7
src/ImageSharp/Formats/Jpeg/Common/Decoder/ColorConverters/JpegColorConverter.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs

@ -5,10 +5,11 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using SixLabors.ImageSharp.Common.Tuples;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder.ColorConverters
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
/// <summary>
/// Encapsulates the conversion of Jpeg channels to RGBA values packed in <see cref="Vector4"/> buffer.
@ -55,13 +56,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder.ColorConverters
/// </summary>
/// <param name="values">The input as a stack-only <see cref="ComponentValues"/> struct</param>
/// <param name="result">The destination buffer of <see cref="Vector4"/> values</param>
public abstract void ConvertToRGBA(ComponentValues values, Span<Vector4> result);
public abstract void ConvertToRgba(ComponentValues values, Span<Vector4> result);
/// <summary>
/// Returns the <see cref="JpegColorConverter"/> for the YCbCr colorspace that matches the current CPU architecture.
/// </summary>
private static JpegColorConverter GetYCbCrConverter() =>
FromYCbCrSimdAvx2.IsAvailable ? (JpegColorConverter)new FromYCbCrSimdAvx2() : new FromYCbCrSimd();
JpegColorConverter.FromYCbCrSimdAvx2.IsAvailable ? (JpegColorConverter)new JpegColorConverter.FromYCbCrSimdAvx2() : new JpegColorConverter.FromYCbCrSimd();
/// <summary>
/// A stack-only struct to reference the input buffers using <see cref="ReadOnlySpan{T}"/>-s.

5
src/ImageSharp/Formats/Jpeg/Common/Decoder/IJpegComponent.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegComponent.cs

@ -1,7 +1,10 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Common interface to represent raw Jpeg components.

7
src/ImageSharp/Formats/Jpeg/Common/Decoder/IRawJpegData.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs

@ -1,13 +1,16 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <inheritdoc />
/// <summary>
/// Represents decompressed, unprocessed jpeg data with spectral space <see cref="T:SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder.IJpegComponent" />-s.
/// Represents decompressed, unprocessed jpeg data with spectral space <see cref="IJpegComponent" />-s.
/// </summary>
internal interface IRawJpegData : IDisposable
{

2
src/ImageSharp/Formats/Jpeg/Common/Decoder/JFifMarker.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs

@ -3,7 +3,7 @@
using System;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Provides information about the JFIF marker segment

3
src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs

@ -2,10 +2,11 @@
// Licensed under the Apache License, Version 2.0.
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Encapsulates the implementation of processing "raw" <see cref="IBuffer{T}"/>-s into Jpeg image channels.

5
src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorSpace.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs

@ -1,4 +1,7 @@
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Identifies the colorspace of a Jpeg image

6
src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs

@ -1,8 +1,12 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Encapsulates postprocessing data for one component for <see cref="JpegImagePostProcessor"/>.

18
src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegImagePostProcessor.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs

@ -1,12 +1,18 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Linq;
using System.Numerics;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
using JpegColorConverter = SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters.JpegColorConverter;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Encapsulates the execution od post-processing algorithms to be applied on a <see cref="IRawJpegData"/> to produce a valid <see cref="Image{TPixel}"/>: <br/>
@ -36,9 +42,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
private readonly IBuffer<Vector4> rgbaBuffer;
/// <summary>
/// The <see cref="ColorConverters.JpegColorConverter"/> corresponding to the current <see cref="JpegColorSpace"/> determined by <see cref="IRawJpegData.ColorSpace"/>.
/// The <see cref="JpegColorConverter"/> corresponding to the current <see cref="JpegColorSpace"/> determined by <see cref="IRawJpegData.ColorSpace"/>.
/// </summary>
private ColorConverters.JpegColorConverter colorConverter;
private readonly JpegColorConverter colorConverter;
/// <summary>
/// Initializes a new instance of the <see cref="JpegImagePostProcessor"/> class.
@ -54,7 +60,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
this.ComponentProcessors = rawJpeg.Components.Select(c => new JpegComponentPostProcessor(memoryManager, this, c)).ToArray();
this.rgbaBuffer = memoryManager.Allocate<Vector4>(rawJpeg.ImageSizeInPixels.Width);
this.colorConverter = ColorConverters.JpegColorConverter.GetConverter(rawJpeg.ColorSpace);
this.colorConverter = JpegColorConverter.GetConverter(rawJpeg.ColorSpace);
}
/// <summary>
@ -148,8 +154,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
{
int y = yy - this.PixelRowCounter;
var values = new ColorConverters.JpegColorConverter.ComponentValues(buffers, y);
this.colorConverter.ConvertToRGBA(values, this.rgbaBuffer.Span);
var values = new JpegColorConverter.ComponentValues(buffers, y);
this.colorConverter.ConvertToRgba(values, this.rgbaBuffer.Span);
Span<TPixel> destRow = destination.GetPixelRowSpan(yy);

2
src/ImageSharp/Formats/Jpeg/Common/Decoder/ProfileResolver.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs

@ -4,7 +4,7 @@
using System;
using System.Text;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Provides methods for identifying metadata and color profiles within jpeg images.

8
src/ImageSharp/Formats/Jpeg/GolangPort/Components/BlockQuad.cs → src/ImageSharp/Formats/Jpeg/Components/Encoder/BlockQuad.cs

@ -1,18 +1,16 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using Block8x8F = SixLabors.ImageSharp.Formats.Jpeg.Common.Block8x8F;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// Poor man's stackalloc: Contains a value-type <see cref="float"/> buffer sized for 4 <see cref="Common.Block8x8F"/> instances.
/// Poor man's stackalloc: Contains a value-type <see cref="float"/> buffer sized for 4 <see cref="Block8x8F"/> instances.
/// Useful for decoder/encoder operations allocating a block for each Jpeg component.
/// </summary>
internal unsafe struct BlockQuad
{
/// <summary>
/// The value-type <see cref="float"/> buffer sized for 4 <see cref="Common.Block8x8F"/> instances.
/// The value-type <see cref="float"/> buffer sized for 4 <see cref="Block8x8F"/> instances.
/// </summary>
public fixed float Data[4 * Block8x8F.Size];
}

2
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/HuffIndex.cs → src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffIndex.cs

@ -1,7 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// Enumerates the Huffman tables

2
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/HuffmanLut.cs → src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs

@ -1,7 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// A compiled look-up table representation of a huffmanSpec.

2
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/HuffmanSpec.cs → src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs

@ -1,7 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// The Huffman encoding specifications.

2
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/QuantIndex.cs → src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs

@ -1,7 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// Enumerates the quantization tables

12
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/RgbToYCbCrTables.cs → src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs

@ -3,9 +3,7 @@
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// Provides 8-bit lookup tables for converting from Rgb to YCbCr colorspace.
@ -68,7 +66,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder
/// <returns>The intialized <see cref="RgbToYCbCrTables"/></returns>
public static RgbToYCbCrTables Create()
{
RgbToYCbCrTables tables = default(RgbToYCbCrTables);
RgbToYCbCrTables tables = default;
for (int i = 0; i <= 255; i++)
{
@ -123,11 +121,5 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder
{
return (int)((x * (1L << ScaleBits)) + 0.5F);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int RightShift(int x)
{
return x >> ScaleBits;
}
}
}

4
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/YCbCrForwardConverter{TPixel}.cs → src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs

@ -1,10 +1,10 @@
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// On-stack worker struct to efficiently encapsulate the TPixel -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks.

2
src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs → src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs

@ -5,7 +5,7 @@ using System.Numerics;
using System.Runtime.CompilerServices;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
/// <summary>
/// Contains inaccurate, but fast forward and inverse DCT implementations.

5
src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.Generated.cs → src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.cs

@ -1,11 +1,8 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using System.Runtime.CompilerServices;
// <auto-generated />
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal unsafe partial struct GenericBlock8x8<T>
{

5
src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.Generated.tt → src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.tt

@ -11,11 +11,8 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using System.Runtime.CompilerServices;
// <auto-generated />
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal unsafe partial struct GenericBlock8x8<T>
{

9
src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.cs → src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs

@ -1,13 +1,16 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
/// <summary>
/// A generic 8x8 block implementation, useful for manipulating custom 8x8 pixel data.
@ -18,8 +21,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
{
public const int Size = 64;
public const int SizeInBytes = Size * 3;
/// <summary>
/// FOR TESTING ONLY!
/// Gets or sets a <see cref="Rgb24"/> value at the given index

3
src/ImageSharp/Formats/Jpeg/Common/SizeExtensions.cs → src/ImageSharp/Formats/Jpeg/Components/SizeExtensions.cs

@ -3,9 +3,10 @@
using System;
using System.Numerics;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
/// <summary>
/// Extension methods for <see cref="Size"/>

3
src/ImageSharp/Formats/Jpeg/Common/ZigZag.cs → src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs

@ -1,10 +1,11 @@
// 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.Jpeg.Common
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
/// <summary>
/// Holds the Jpeg UnZig array in a value/stack type.

36
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bits.cs

@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void EnsureNBits(int n, ref InputProcessor inputProcessor)
{
OrigDecoderErrorCode errorCode = this.EnsureNBitsUnsafe(n, ref inputProcessor);
GolangDecoderErrorCode errorCode = this.EnsureNBitsUnsafe(n, ref inputProcessor);
errorCode.EnsureNoError();
}
@ -46,17 +46,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// Reads bytes from the byte buffer to ensure that bits.UnreadBits is at
/// least n. For best performance (avoiding function calls inside hot loops),
/// the caller is the one responsible for first checking that bits.UnreadBits &lt; n.
/// This method does not throw. Returns <see cref="OrigDecoderErrorCode"/> instead.
/// This method does not throw. Returns <see cref="GolangDecoderErrorCode"/> instead.
/// </summary>
/// <param name="n">The number of bits to ensure.</param>
/// <param name="inputProcessor">The <see cref="InputProcessor"/></param>
/// <returns>Error code</returns>
public OrigDecoderErrorCode EnsureNBitsUnsafe(int n, ref InputProcessor inputProcessor)
public GolangDecoderErrorCode EnsureNBitsUnsafe(int n, ref InputProcessor inputProcessor)
{
while (true)
{
OrigDecoderErrorCode errorCode = this.EnsureBitsStepImpl(ref inputProcessor);
if (errorCode != OrigDecoderErrorCode.NoError || this.UnreadBits >= n)
GolangDecoderErrorCode errorCode = this.EnsureBitsStepImpl(ref inputProcessor);
if (errorCode != GolangDecoderErrorCode.NoError || this.UnreadBits >= n)
{
return errorCode;
}
@ -67,8 +67,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// Unrolled version of <see cref="EnsureNBitsUnsafe"/> for n==8
/// </summary>
/// <param name="inputProcessor">The <see cref="InputProcessor"/></param>
/// <returns>A <see cref="OrigDecoderErrorCode"/></returns>
public OrigDecoderErrorCode Ensure8BitsUnsafe(ref InputProcessor inputProcessor)
/// <returns>A <see cref="GolangDecoderErrorCode"/></returns>
public GolangDecoderErrorCode Ensure8BitsUnsafe(ref InputProcessor inputProcessor)
{
return this.EnsureBitsStepImpl(ref inputProcessor);
}
@ -77,8 +77,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// Unrolled version of <see cref="EnsureNBitsUnsafe"/> for n==1
/// </summary>
/// <param name="inputProcessor">The <see cref="InputProcessor"/></param>
/// <returns>A <see cref="OrigDecoderErrorCode"/></returns>
public OrigDecoderErrorCode Ensure1BitUnsafe(ref InputProcessor inputProcessor)
/// <returns>A <see cref="GolangDecoderErrorCode"/></returns>
public GolangDecoderErrorCode Ensure1BitUnsafe(ref InputProcessor inputProcessor)
{
return this.EnsureBitsStepImpl(ref inputProcessor);
}
@ -92,7 +92,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReceiveExtend(int t, ref InputProcessor inputProcessor)
{
OrigDecoderErrorCode errorCode = this.ReceiveExtendUnsafe(t, ref inputProcessor, out int x);
GolangDecoderErrorCode errorCode = this.ReceiveExtendUnsafe(t, ref inputProcessor, out int x);
errorCode.EnsureNoError();
return x;
}
@ -103,13 +103,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <param name="t">Byte</param>
/// <param name="inputProcessor">The <see cref="InputProcessor"/></param>
/// <param name="x">Read bits value</param>
/// <returns>The <see cref="OrigDecoderErrorCode"/></returns>
public OrigDecoderErrorCode ReceiveExtendUnsafe(int t, ref InputProcessor inputProcessor, out int x)
/// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
public GolangDecoderErrorCode ReceiveExtendUnsafe(int t, ref InputProcessor inputProcessor, out int x)
{
if (this.UnreadBits < t)
{
OrigDecoderErrorCode errorCode = this.EnsureNBitsUnsafe(t, ref inputProcessor);
if (errorCode != OrigDecoderErrorCode.NoError)
GolangDecoderErrorCode errorCode = this.EnsureNBitsUnsafe(t, ref inputProcessor);
if (errorCode != GolangDecoderErrorCode.NoError)
{
x = int.MaxValue;
return errorCode;
@ -126,14 +126,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
x += ((-1) << t) + 1;
}
return OrigDecoderErrorCode.NoError;
return GolangDecoderErrorCode.NoError;
}
private OrigDecoderErrorCode EnsureBitsStepImpl(ref InputProcessor inputProcessor)
private GolangDecoderErrorCode EnsureBitsStepImpl(ref InputProcessor inputProcessor)
{
OrigDecoderErrorCode errorCode = inputProcessor.Bytes.ReadByteStuffedByteUnsafe(inputProcessor.InputStream, out int c);
GolangDecoderErrorCode errorCode = inputProcessor.Bytes.ReadByteStuffedByteUnsafe(inputProcessor.InputStream, out int c);
if (errorCode != OrigDecoderErrorCode.NoError)
if (errorCode != GolangDecoderErrorCode.NoError)
{
return errorCode;
}

62
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bytes.cs

@ -77,8 +77,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// </summary>
/// <param name="inputStream">Input stream</param>
/// <param name="x">The result byte as <see cref="int"/></param>
/// <returns>The <see cref="OrigDecoderErrorCode"/></returns>
public OrigDecoderErrorCode ReadByteStuffedByteUnsafe(Stream inputStream, out int x)
/// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
public GolangDecoderErrorCode ReadByteStuffedByteUnsafe(Stream inputStream, out int x)
{
// Take the fast path if bytes.buf contains at least two bytes.
if (this.I + 2 <= this.J)
@ -86,50 +86,50 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
x = this.BufferAsInt[this.I];
this.I++;
this.UnreadableBytes = 1;
if (x != OrigJpegConstants.Markers.XFFInt)
if (x != JpegConstants.Markers.XFFInt)
{
return OrigDecoderErrorCode.NoError;
return GolangDecoderErrorCode.NoError;
}
if (this.BufferAsInt[this.I] != 0x00)
{
return OrigDecoderErrorCode.MissingFF00;
return GolangDecoderErrorCode.MissingFF00;
}
this.I++;
this.UnreadableBytes = 2;
x = OrigJpegConstants.Markers.XFF;
return OrigDecoderErrorCode.NoError;
x = JpegConstants.Markers.XFF;
return GolangDecoderErrorCode.NoError;
}
this.UnreadableBytes = 0;
OrigDecoderErrorCode errorCode = this.ReadByteAsIntUnsafe(inputStream, out x);
GolangDecoderErrorCode errorCode = this.ReadByteAsIntUnsafe(inputStream, out x);
this.UnreadableBytes = 1;
if (errorCode != OrigDecoderErrorCode.NoError)
if (errorCode != GolangDecoderErrorCode.NoError)
{
return errorCode;
}
if (x != OrigJpegConstants.Markers.XFF)
if (x != JpegConstants.Markers.XFF)
{
return OrigDecoderErrorCode.NoError;
return GolangDecoderErrorCode.NoError;
}
errorCode = this.ReadByteAsIntUnsafe(inputStream, out x);
this.UnreadableBytes = 2;
if (errorCode != OrigDecoderErrorCode.NoError)
if (errorCode != GolangDecoderErrorCode.NoError)
{
return errorCode;
}
if (x != 0x00)
{
return OrigDecoderErrorCode.MissingFF00;
return GolangDecoderErrorCode.MissingFF00;
}
x = OrigJpegConstants.Markers.XFF;
return OrigDecoderErrorCode.NoError;
x = JpegConstants.Markers.XFF;
return GolangDecoderErrorCode.NoError;
}
/// <summary>
@ -140,25 +140,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte ReadByte(Stream inputStream)
{
OrigDecoderErrorCode errorCode = this.ReadByteUnsafe(inputStream, out byte result);
GolangDecoderErrorCode errorCode = this.ReadByteUnsafe(inputStream, out byte result);
errorCode.EnsureNoError();
return result;
}
/// <summary>
/// Extracts the next byte, whether buffered or not buffered into the result out parameter. It does not care about byte stuffing.
/// This method does not throw on format error, it returns a <see cref="OrigDecoderErrorCode"/> instead.
/// This method does not throw on format error, it returns a <see cref="GolangDecoderErrorCode"/> instead.
/// </summary>
/// <param name="inputStream">Input stream</param>
/// <param name="result">The result <see cref="byte"/> as out parameter</param>
/// <returns>The <see cref="OrigDecoderErrorCode"/></returns>
public OrigDecoderErrorCode ReadByteUnsafe(Stream inputStream, out byte result)
/// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
public GolangDecoderErrorCode ReadByteUnsafe(Stream inputStream, out byte result)
{
OrigDecoderErrorCode errorCode = OrigDecoderErrorCode.NoError;
GolangDecoderErrorCode errorCode = GolangDecoderErrorCode.NoError;
while (this.I == this.J)
{
errorCode = this.FillUnsafe(inputStream);
if (errorCode != OrigDecoderErrorCode.NoError)
if (errorCode != GolangDecoderErrorCode.NoError)
{
result = 0;
return errorCode;
@ -176,15 +176,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// </summary>
/// <param name="inputStream">The input stream</param>
/// <param name="result">The result <see cref="int"/></param>
/// <returns>A <see cref="OrigDecoderErrorCode"/></returns>
/// <returns>A <see cref="GolangDecoderErrorCode"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public OrigDecoderErrorCode ReadByteAsIntUnsafe(Stream inputStream, out int result)
public GolangDecoderErrorCode ReadByteAsIntUnsafe(Stream inputStream, out int result)
{
OrigDecoderErrorCode errorCode = OrigDecoderErrorCode.NoError;
GolangDecoderErrorCode errorCode = GolangDecoderErrorCode.NoError;
while (this.I == this.J)
{
errorCode = this.FillUnsafe(inputStream);
if (errorCode != OrigDecoderErrorCode.NoError)
if (errorCode != GolangDecoderErrorCode.NoError)
{
result = 0;
return errorCode;
@ -206,18 +206,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Fill(Stream inputStream)
{
OrigDecoderErrorCode errorCode = this.FillUnsafe(inputStream);
GolangDecoderErrorCode errorCode = this.FillUnsafe(inputStream);
errorCode.EnsureNoError();
}
/// <summary>
/// Fills up the bytes buffer from the underlying stream.
/// It should only be called when there are no unread bytes in bytes.
/// This method does not throw <see cref="EOFException"/>, returns a <see cref="OrigDecoderErrorCode"/> instead!
/// This method does not throw <see cref="EOFException"/>, returns a <see cref="GolangDecoderErrorCode"/> instead!
/// </summary>
/// <param name="inputStream">Input stream</param>
/// <returns>The <see cref="OrigDecoderErrorCode"/></returns>
public OrigDecoderErrorCode FillUnsafe(Stream inputStream)
/// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
public GolangDecoderErrorCode FillUnsafe(Stream inputStream)
{
if (this.I != this.J)
{
@ -239,7 +239,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
int n = inputStream.Read(this.Buffer, this.J, this.Buffer.Length - this.J);
if (n == 0)
{
return OrigDecoderErrorCode.UnexpectedEndOfStream;
return GolangDecoderErrorCode.UnexpectedEndOfStream;
}
this.J += n;
@ -249,7 +249,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
this.BufferAsInt[i] = this.Buffer[i];
}
return OrigDecoderErrorCode.NoError;
return GolangDecoderErrorCode.NoError;
}
}
}

28
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/DecoderThrowHelper.cs

@ -12,22 +12,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
internal static class DecoderThrowHelper
{
/// <summary>
/// Throws an exception that belongs to the given <see cref="OrigDecoderErrorCode"/>
/// Throws an exception that belongs to the given <see cref="GolangDecoderErrorCode"/>
/// </summary>
/// <param name="errorCode">The <see cref="OrigDecoderErrorCode"/></param>
/// <param name="errorCode">The <see cref="GolangDecoderErrorCode"/></param>
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowExceptionForErrorCode(this OrigDecoderErrorCode errorCode)
public static void ThrowExceptionForErrorCode(this GolangDecoderErrorCode errorCode)
{
// REMARK: If this method throws for an image that is expected to be decodable,
// consider using the ***Unsafe variant of the parsing method that asks for ThrowExceptionForErrorCode()
// then verify the error code + implement fallback logic manually!
switch (errorCode)
{
case OrigDecoderErrorCode.NoError:
case GolangDecoderErrorCode.NoError:
throw new ArgumentException("ThrowExceptionForErrorCode() called with NoError!", nameof(errorCode));
case OrigDecoderErrorCode.MissingFF00:
case GolangDecoderErrorCode.MissingFF00:
throw new MissingFF00Exception();
case OrigDecoderErrorCode.UnexpectedEndOfStream:
case GolangDecoderErrorCode.UnexpectedEndOfStream:
throw new EOFException();
default:
throw new ArgumentOutOfRangeException(nameof(errorCode), errorCode, null);
@ -35,26 +35,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
}
/// <summary>
/// Throws an exception if the given <see cref="OrigDecoderErrorCode"/> defines an error.
/// Throws an exception if the given <see cref="GolangDecoderErrorCode"/> defines an error.
/// </summary>
/// <param name="errorCode">The <see cref="OrigDecoderErrorCode"/></param>
/// <param name="errorCode">The <see cref="GolangDecoderErrorCode"/></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void EnsureNoError(this OrigDecoderErrorCode errorCode)
public static void EnsureNoError(this GolangDecoderErrorCode errorCode)
{
if (errorCode != OrigDecoderErrorCode.NoError)
if (errorCode != GolangDecoderErrorCode.NoError)
{
ThrowExceptionForErrorCode(errorCode);
}
}
/// <summary>
/// Throws an exception if the given <see cref="OrigDecoderErrorCode"/> is <see cref="OrigDecoderErrorCode.UnexpectedEndOfStream"/>.
/// Throws an exception if the given <see cref="GolangDecoderErrorCode"/> is <see cref="GolangDecoderErrorCode.UnexpectedEndOfStream"/>.
/// </summary>
/// <param name="errorCode">The <see cref="OrigDecoderErrorCode"/></param>
/// <param name="errorCode">The <see cref="GolangDecoderErrorCode"/></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void EnsureNoEOF(this OrigDecoderErrorCode errorCode)
public static void EnsureNoEOF(this GolangDecoderErrorCode errorCode)
{
if (errorCode == OrigDecoderErrorCode.UnexpectedEndOfStream)
if (errorCode == GolangDecoderErrorCode.UnexpectedEndOfStream)
{
errorCode.ThrowExceptionForErrorCode();
}

27
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs → src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangComponent.cs

@ -3,8 +3,9 @@
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Memory;
using SixLabors.Primitives;
@ -14,9 +15,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <summary>
/// Represents a single color component
/// </summary>
internal class OrigComponent : IDisposable, IJpegComponent
internal class GolangComponent : IDisposable, IJpegComponent
{
public OrigComponent(byte identifier, int index)
public GolangComponent(byte identifier, int index)
{
this.Identifier = identifier;
this.Index = index;
@ -56,9 +57,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// Initializes <see cref="SpectralBlocks"/>
/// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <param name="decoder">The <see cref="OrigJpegDecoderCore"/> instance</param>
/// <param name="metadataOnly">Whether to decode metadata only. If this is true, memory allocation for SpectralBlocks will not be necessary</param>
public void InitializeDerivedData(MemoryManager memoryManager, OrigJpegDecoderCore decoder, bool metadataOnly)
/// <param name="decoder">The <see cref="GolangJpegDecoderCore"/> instance</param>
public void InitializeDerivedData(MemoryManager memoryManager, GolangJpegDecoderCore decoder)
{
// For 4-component images (either CMYK or YCbCrK), we only support two
// hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22].
@ -77,21 +77,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
}
else
{
OrigComponent c0 = decoder.Components[0];
GolangComponent c0 = decoder.Components[0];
this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors);
}
if (!metadataOnly)
{
this.SpectralBlocks = memoryManager.Allocate2D<Block8x8>(this.SizeInBlocks.Width, this.SizeInBlocks.Height, true);
}
this.SpectralBlocks = memoryManager.Allocate2D<Block8x8>(this.SizeInBlocks.Width, this.SizeInBlocks.Height, true);
}
/// <summary>
/// Initializes all component data except <see cref="SpectralBlocks"/>.
/// </summary>
/// <param name="decoder">The <see cref="OrigJpegDecoderCore"/> instance</param>
public void InitializeCoreData(OrigJpegDecoderCore decoder)
/// <param name="decoder">The <see cref="GolangJpegDecoderCore"/> instance</param>
public void InitializeCoreData(GolangJpegDecoderCore decoder)
{
// Section B.2.2 states that "the value of C_i shall be different from
// the values of C_1 through C_(i-1)".
@ -106,7 +103,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
}
this.QuantizationTableIndex = decoder.Temp[8 + (3 * i)];
if (this.QuantizationTableIndex > OrigJpegDecoderCore.MaxTq)
if (this.QuantizationTableIndex > GolangJpegDecoderCore.MaxTq)
{
throw new ImageFormatException("Bad Tq value");
}

2
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponentScan.cs → src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangComponentScan.cs

@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// Represents a component scan
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct OrigComponentScan
internal struct GolangComponentScan
{
/// <summary>
/// Gets or sets the component index.

2
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigDecoderErrorCode.cs → src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangDecoderErrorCode.cs

@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <summary>
/// Represents "recoverable" decoder errors.
/// </summary>
internal enum OrigDecoderErrorCode
internal enum GolangDecoderErrorCode
{
/// <summary>
/// NoError

12
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigHuffmanTree.cs → src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangHuffmanTree.cs

@ -1,8 +1,6 @@
// 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;
@ -12,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// Represents a Huffman tree
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct OrigHuffmanTree
internal unsafe struct GolangHuffmanTree
{
/// <summary>
/// The index of the AC table row
@ -95,12 +93,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
public FixedInt32Buffer16 Indices;
/// <summary>
/// Creates and initializes an array of <see cref="OrigHuffmanTree" /> instances of size <see cref="NumberOfTrees" />
/// Creates and initializes an array of <see cref="GolangHuffmanTree" /> instances of size <see cref="NumberOfTrees" />
/// </summary>
/// <returns>An array of <see cref="OrigHuffmanTree" /> instances representing the Huffman tables</returns>
public static OrigHuffmanTree[] CreateHuffmanTrees()
/// <returns>An array of <see cref="GolangHuffmanTree" /> instances representing the Huffman tables</returns>
public static GolangHuffmanTree[] CreateHuffmanTrees()
{
return new OrigHuffmanTree[NumberOfTrees];
return new GolangHuffmanTree[NumberOfTrees];
}
/// <summary>

11
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.ComputationData.cs → src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangJpegScanDecoder.ComputationData.cs

@ -2,14 +2,15 @@
// Licensed under the Apache License, Version 2.0.
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{
/// <content>
/// Conains the definition of <see cref="ComputationData"/>
/// </content>
internal unsafe partial struct OrigJpegScanDecoder
internal unsafe partial struct GolangJpegScanDecoder
{
/// <summary>
/// Holds the "large" data blocks needed for computations.
@ -28,14 +29,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
public ZigZag Unzig;
/// <summary>
/// The buffer storing the <see cref="OrigComponentScan"/>-s for each component
/// The buffer storing the <see cref="GolangComponentScan"/>-s for each component
/// </summary>
public fixed byte ScanData[3 * OrigJpegDecoderCore.MaxComponents];
public fixed byte ScanData[3 * GolangJpegDecoderCore.MaxComponents];
/// <summary>
/// The DC values for each component
/// </summary>
public fixed int Dc[OrigJpegDecoderCore.MaxComponents];
public fixed int Dc[GolangJpegDecoderCore.MaxComponents];
/// <summary>
/// Creates and initializes a new <see cref="ComputationData"/> instance

8
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.DataPointers.cs → src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangJpegScanDecoder.DataPointers.cs

@ -1,14 +1,14 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{
/// <content>
/// Conains the definition of <see cref="DataPointers"/>
/// </content>
internal unsafe partial struct OrigJpegScanDecoder
internal unsafe partial struct GolangJpegScanDecoder
{
/// <summary>
/// Contains pointers to the memory regions of <see cref="ComputationData"/> so they can be easily passed around to pointer based utility methods of <see cref="Block8x8F"/>
@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <summary>
/// Pointer to <see cref="ComputationData.ScanData"/> as Scan*
/// </summary>
public OrigComponentScan* ComponentScan;
public GolangComponentScan* ComponentScan;
/// <summary>
/// Pointer to <see cref="ComputationData.Dc"/>
@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{
this.Block = &basePtr->Block;
this.Unzig = basePtr->Unzig.Data;
this.ComponentScan = (OrigComponentScan*)basePtr->ScanData;
this.ComponentScan = (GolangComponentScan*)basePtr->ScanData;
this.Dc = basePtr->Dc;
}
}

67
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.cs → src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangJpegScanDecoder.cs

@ -3,9 +3,8 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
@ -29,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// For baseline JPEGs, these parameters are hard-coded to 0/63/0/0.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct OrigJpegScanDecoder
internal unsafe partial struct GolangJpegScanDecoder
{
// The JpegScanDecoder members should be ordered in a way that results in optimal memory layout.
#pragma warning disable SA1202 // ElementsMustBeOrderedByAccess
@ -110,12 +109,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
private byte expectedRst;
/// <summary>
/// Initializes a default-constructed <see cref="OrigJpegScanDecoder"/> instance for reading data from <see cref="OrigJpegDecoderCore"/>-s stream.
/// Initializes a default-constructed <see cref="GolangJpegScanDecoder"/> instance for reading data from <see cref="GolangJpegDecoderCore"/>-s stream.
/// </summary>
/// <param name="p">Pointer to <see cref="OrigJpegScanDecoder"/> on the stack</param>
/// <param name="decoder">The <see cref="OrigJpegDecoderCore"/> instance</param>
/// <param name="p">Pointer to <see cref="GolangJpegScanDecoder"/> on the stack</param>
/// <param name="decoder">The <see cref="GolangJpegDecoderCore"/> instance</param>
/// <param name="remaining">The remaining bytes in the segment block.</param>
public static void InitStreamReading(OrigJpegScanDecoder* p, OrigJpegDecoderCore decoder, int remaining)
public static void InitStreamReading(GolangJpegScanDecoder* p, GolangJpegDecoderCore decoder, int remaining)
{
p->data = ComputationData.Create();
p->pointers = new DataPointers(&p->data);
@ -123,8 +122,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
}
/// <summary>
/// Read Huffman data from Jpeg scans in <see cref="OrigJpegDecoderCore.InputStream"/>,
/// and decode it as <see cref="Block8x8"/> into <see cref="OrigComponent.SpectralBlocks"/>.
/// Read Huffman data from Jpeg scans in <see cref="GolangJpegDecoderCore.InputStream"/>,
/// and decode it as <see cref="Block8x8"/> into <see cref="GolangComponent.SpectralBlocks"/>.
///
/// The blocks are traversed one MCU at a time. For 4:2:0 chroma
/// subsampling, there are four Y 8x8 blocks in every 16x16 MCU.
@ -149,14 +148,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// 0 1 2
/// 3 4 5
/// </summary>
/// <param name="decoder">The <see cref="OrigJpegDecoderCore"/> instance</param>
public void DecodeBlocks(OrigJpegDecoderCore decoder)
/// <param name="decoder">The <see cref="GolangJpegDecoderCore"/> instance</param>
public void DecodeBlocks(GolangJpegDecoderCore decoder)
{
decoder.InputProcessor.ResetErrorState();
this.blockCounter = 0;
this.mcuCounter = 0;
this.expectedRst = OrigJpegConstants.Markers.RST0;
this.expectedRst = JpegConstants.Markers.RST0;
for (int my = 0; my < decoder.MCUCountY; my++)
{
@ -177,12 +176,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
}
}
private void DecodeBlocksAtMcuIndex(OrigJpegDecoderCore decoder, int mx, int my)
private void DecodeBlocksAtMcuIndex(GolangJpegDecoderCore decoder, int mx, int my)
{
for (int scanIndex = 0; scanIndex < this.componentScanCount; scanIndex++)
{
this.ComponentIndex = this.pointers.ComponentScan[scanIndex].ComponentIndex;
OrigComponent component = decoder.Components[this.ComponentIndex];
GolangComponent component = decoder.Components[this.ComponentIndex];
this.hi = component.HorizontalSamplingFactor;
int vi = component.VerticalSamplingFactor;
@ -223,7 +222,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
}
}
private void ProcessRSTMarker(OrigJpegDecoderCore decoder)
private void ProcessRSTMarker(GolangJpegDecoderCore decoder)
{
// Attempt to look for RST[0-7] markers to resynchronize from corrupt input.
if (!decoder.InputProcessor.ReachedEOF)
@ -262,15 +261,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
}
this.expectedRst++;
if (this.expectedRst == OrigJpegConstants.Markers.RST7 + 1)
if (this.expectedRst == JpegConstants.Markers.RST7 + 1)
{
this.expectedRst = OrigJpegConstants.Markers.RST0;
this.expectedRst = JpegConstants.Markers.RST0;
}
}
}
}
private void Reset(OrigJpegDecoderCore decoder)
private void Reset(GolangJpegDecoderCore decoder)
{
decoder.InputProcessor.ResetHuffmanDecoder();
@ -285,15 +284,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// </summary>
private void ResetDcValues()
{
Unsafe.InitBlock(this.pointers.Dc, default(byte), sizeof(int) * OrigJpegDecoderCore.MaxComponents);
Unsafe.InitBlock(this.pointers.Dc, default, sizeof(int) * GolangJpegDecoderCore.MaxComponents);
}
/// <summary>
/// The implementation part of <see cref="InitStreamReading"/> as an instance method.
/// </summary>
/// <param name="decoder">The <see cref="OrigJpegDecoderCore"/></param>
/// <param name="decoder">The <see cref="GolangJpegDecoderCore"/></param>
/// <param name="remaining">The remaining bytes</param>
private void InitStreamReadingImpl(OrigJpegDecoderCore decoder, int remaining)
private void InitStreamReadingImpl(GolangJpegDecoderCore decoder, int remaining)
{
if (decoder.ComponentCount == 0)
{
@ -360,10 +359,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// </summary>
/// <param name="decoder">The decoder</param>
/// <param name="scanIndex">The index of the scan</param>
private void DecodeBlock(OrigJpegDecoderCore decoder, int scanIndex)
private void DecodeBlock(GolangJpegDecoderCore decoder, int scanIndex)
{
Block8x8* b = this.pointers.Block;
int huffmannIdx = (OrigHuffmanTree.AcTableIndex * OrigHuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].AcTableSelector;
int huffmannIdx = (GolangHuffmanTree.AcTableIndex * GolangHuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].AcTableSelector;
if (this.ah != 0)
{
this.Refine(ref decoder.InputProcessor, ref decoder.HuffmanTrees[huffmannIdx], 1 << this.al);
@ -377,7 +376,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
zig++;
// Decode the DC coefficient, as specified in section F.2.2.1.
int huffmanIndex = (OrigHuffmanTree.DcTableIndex * OrigHuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].DcTableSelector;
int huffmanIndex = (GolangHuffmanTree.DcTableIndex * GolangHuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].DcTableSelector;
decoder.InputProcessor.DecodeHuffmanUnsafe(
ref decoder.HuffmanTrees[huffmanIndex],
out int value);
@ -467,7 +466,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
private void DecodeEobRun(int count, ref InputProcessor processor)
{
processor.DecodeBitsUnsafe(count, out int bitsResult);
if (processor.LastErrorCode != OrigDecoderErrorCode.NoError)
if (processor.LastErrorCode != GolangDecoderErrorCode.NoError)
{
return;
}
@ -475,7 +474,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
this.eobRun |= bitsResult;
}
private void InitComponentScan(OrigJpegDecoderCore decoder, int i, ref OrigComponentScan currentComponentScan, ref int totalHv)
private void InitComponentScan(GolangJpegDecoderCore decoder, int i, ref GolangComponentScan currentComponentScan, ref int totalHv)
{
// Component selector.
int cs = decoder.Temp[1 + (2 * i)];
@ -500,11 +499,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
}
private void ProcessComponentImpl(
OrigJpegDecoderCore decoder,
GolangJpegDecoderCore decoder,
int i,
ref OrigComponentScan currentComponentScan,
ref GolangComponentScan currentComponentScan,
ref int totalHv,
OrigComponent currentComponent)
GolangComponent currentComponent)
{
// Section B.2.3 states that "the value of Cs_j shall be different from
// the values of Cs_1 through Cs_(j-1)". Since we have previously
@ -522,13 +521,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
totalHv += currentComponent.HorizontalSamplingFactor * currentComponent.VerticalSamplingFactor;
currentComponentScan.DcTableSelector = (byte)(decoder.Temp[2 + (2 * i)] >> 4);
if (currentComponentScan.DcTableSelector > OrigHuffmanTree.MaxTh)
if (currentComponentScan.DcTableSelector > GolangHuffmanTree.MaxTh)
{
throw new ImageFormatException("Bad DC table selector value");
}
currentComponentScan.AcTableSelector = (byte)(decoder.Temp[2 + (2 * i)] & 0x0f);
if (currentComponentScan.AcTableSelector > OrigHuffmanTree.MaxTh)
if (currentComponentScan.AcTableSelector > GolangHuffmanTree.MaxTh)
{
throw new ImageFormatException("Bad AC table selector value");
}
@ -540,7 +539,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <param name="bp">The <see cref="InputProcessor"/> instance</param>
/// <param name="h">The Huffman tree</param>
/// <param name="delta">The low transform offset</param>
private void Refine(ref InputProcessor bp, ref OrigHuffmanTree h, int delta)
private void Refine(ref InputProcessor bp, ref GolangHuffmanTree h, int delta)
{
Block8x8* b = this.pointers.Block;
@ -560,7 +559,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
if (bit)
{
int stuff = (int)Block8x8.GetScalarAt(b, 0);
int stuff = Block8x8.GetScalarAt(b, 0);
// int stuff = (int)b[0];
stuff |= delta;

80
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/InputProcessor.cs

@ -8,7 +8,7 @@ using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{
/// <summary>
/// Encapsulates stream reading and processing data and operations for <see cref="OrigJpegDecoderCore"/>.
/// Encapsulates stream reading and processing data and operations for <see cref="GolangJpegDecoderCore"/>.
/// It's a value type for imporved data locality, and reduced number of CALLVIRT-s
/// </summary>
internal struct InputProcessor : IDisposable
@ -27,14 +27,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// Initializes a new instance of the <see cref="InputProcessor"/> struct.
/// </summary>
/// <param name="inputStream">The input <see cref="Stream"/></param>
/// <param name="temp">Temporal buffer, same as <see cref="OrigJpegDecoderCore.Temp"/></param>
/// <param name="temp">Temporal buffer, same as <see cref="GolangJpegDecoderCore.Temp"/></param>
public InputProcessor(Stream inputStream, byte[] temp)
{
this.Bits = default(Bits);
this.Bits = default;
this.Bytes = Bytes.Create();
this.InputStream = inputStream;
this.Temp = temp;
this.LastErrorCode = OrigDecoderErrorCode.NoError;
this.LastErrorCode = GolangDecoderErrorCode.NoError;
}
/// <summary>
@ -43,20 +43,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
public Stream InputStream { get; }
/// <summary>
/// Gets the temporary buffer, same instance as <see cref="OrigJpegDecoderCore.Temp"/>
/// Gets the temporary buffer, same instance as <see cref="GolangJpegDecoderCore.Temp"/>
/// </summary>
public byte[] Temp { get; }
/// <summary>
/// Gets a value indicating whether an unexpected EOF reached in <see cref="InputStream"/>.
/// </summary>
public bool ReachedEOF => this.LastErrorCode == OrigDecoderErrorCode.UnexpectedEndOfStream;
public bool ReachedEOF => this.LastErrorCode == GolangDecoderErrorCode.UnexpectedEndOfStream;
public bool HasError => this.LastErrorCode != OrigDecoderErrorCode.NoError;
public bool HasError => this.LastErrorCode != GolangDecoderErrorCode.NoError;
public OrigDecoderErrorCode LastErrorCode { get; private set; }
public GolangDecoderErrorCode LastErrorCode { get; private set; }
public void ResetErrorState() => this.LastErrorCode = OrigDecoderErrorCode.NoError;
public void ResetErrorState() => this.LastErrorCode = GolangDecoderErrorCode.NoError;
/// <summary>
/// If errorCode indicates unexpected EOF, sets <see cref="ReachedEOF"/> to true and returns false.
@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <returns>A <see cref="bool"/> indicating whether EOF reached</returns>
public bool CheckEOFEnsureNoError()
{
if (this.LastErrorCode == OrigDecoderErrorCode.UnexpectedEndOfStream)
if (this.LastErrorCode == GolangDecoderErrorCode.UnexpectedEndOfStream)
{
return false;
}
@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <returns>A <see cref="bool"/> indicating whether EOF reached</returns>
public bool CheckEOF()
{
if (this.LastErrorCode == OrigDecoderErrorCode.UnexpectedEndOfStream)
if (this.LastErrorCode == GolangDecoderErrorCode.UnexpectedEndOfStream)
{
return false;
}
@ -106,7 +106,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public OrigDecoderErrorCode ReadByteUnsafe(out byte result)
public GolangDecoderErrorCode ReadByteUnsafe(out byte result)
{
this.LastErrorCode = this.Bytes.ReadByteUnsafe(this.InputStream, out result);
return this.LastErrorCode;
@ -117,13 +117,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// TODO: This method (and also the usages) could be optimized by batching!
/// </summary>
/// <param name="result">The decoded bit as a <see cref="bool"/></param>
/// <returns>The <see cref="OrigDecoderErrorCode" /></returns>
public OrigDecoderErrorCode DecodeBitUnsafe(out bool result)
/// <returns>The <see cref="GolangDecoderErrorCode" /></returns>
public GolangDecoderErrorCode DecodeBitUnsafe(out bool result)
{
if (this.Bits.UnreadBits == 0)
{
this.LastErrorCode = this.Bits.Ensure1BitUnsafe(ref this);
if (this.LastErrorCode != OrigDecoderErrorCode.NoError)
if (this.LastErrorCode != GolangDecoderErrorCode.NoError)
{
result = false;
return this.LastErrorCode;
@ -133,18 +133,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
result = (this.Bits.Accumulator & this.Bits.Mask) != 0;
this.Bits.UnreadBits--;
this.Bits.Mask >>= 1;
return this.LastErrorCode = OrigDecoderErrorCode.NoError;
return this.LastErrorCode = GolangDecoderErrorCode.NoError;
}
/// <summary>
/// Reads exactly length bytes into data. It does not care about byte stuffing.
/// Does not throw on errors, returns <see cref="OrigJpegDecoderCore"/> instead!
/// Does not throw on errors, returns <see cref="GolangJpegDecoderCore"/> instead!
/// </summary>
/// <param name="data">The data to write to.</param>
/// <param name="offset">The offset in the source buffer</param>
/// <param name="length">The number of bytes to read</param>
/// <returns>The <see cref="OrigDecoderErrorCode"/></returns>
public OrigDecoderErrorCode ReadFullUnsafe(byte[] data, int offset, int length)
/// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
public GolangDecoderErrorCode ReadFullUnsafe(byte[] data, int offset, int length)
{
// Unread the overshot bytes, if any.
if (this.Bytes.UnreadableBytes != 0)
@ -157,8 +157,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
this.Bytes.UnreadableBytes = 0;
}
this.LastErrorCode = OrigDecoderErrorCode.NoError;
while (length > 0 && this.LastErrorCode == OrigDecoderErrorCode.NoError)
this.LastErrorCode = GolangDecoderErrorCode.NoError;
while (length > 0 && this.LastErrorCode == GolangDecoderErrorCode.NoError)
{
if (this.Bytes.J - this.Bytes.I >= length)
{
@ -185,13 +185,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// </summary>
/// <param name="count">The number of bits to decode.</param>
/// <param name="result">The <see cref="uint" /> result</param>
/// <returns>The <see cref="OrigDecoderErrorCode"/></returns>
public OrigDecoderErrorCode DecodeBitsUnsafe(int count, out int result)
/// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
public GolangDecoderErrorCode DecodeBitsUnsafe(int count, out int result)
{
if (this.Bits.UnreadBits < count)
{
this.LastErrorCode = this.Bits.EnsureNBitsUnsafe(count, ref this);
if (this.LastErrorCode != OrigDecoderErrorCode.NoError)
if (this.LastErrorCode != GolangDecoderErrorCode.NoError)
{
result = 0;
return this.LastErrorCode;
@ -202,7 +202,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
result = result & ((1 << count) - 1);
this.Bits.UnreadBits -= count;
this.Bits.Mask >>= count;
return this.LastErrorCode = OrigDecoderErrorCode.NoError;
return this.LastErrorCode = GolangDecoderErrorCode.NoError;
}
/// <summary>
@ -210,8 +210,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// </summary>
/// <param name="huffmanTree">The huffman value</param>
/// <param name="result">The decoded <see cref="byte" /></param>
/// <returns>The <see cref="OrigDecoderErrorCode"/></returns>
public OrigDecoderErrorCode DecodeHuffmanUnsafe(ref OrigHuffmanTree huffmanTree, out int result)
/// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
public GolangDecoderErrorCode DecodeHuffmanUnsafe(ref GolangHuffmanTree huffmanTree, out int result)
{
result = 0;
@ -224,9 +224,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{
this.LastErrorCode = this.Bits.Ensure8BitsUnsafe(ref this);
if (this.LastErrorCode == OrigDecoderErrorCode.NoError)
if (this.LastErrorCode == GolangDecoderErrorCode.NoError)
{
int lutIndex = (this.Bits.Accumulator >> (this.Bits.UnreadBits - OrigHuffmanTree.LutSizeLog2)) & 0xFF;
int lutIndex = (this.Bits.Accumulator >> (this.Bits.UnreadBits - GolangHuffmanTree.LutSizeLog2)) & 0xFF;
int v = huffmanTree.Lut[lutIndex];
if (v != 0)
@ -246,7 +246,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
}
int code = 0;
for (int i = 0; i < OrigHuffmanTree.MaxCodeLength; i++)
for (int i = 0; i < GolangHuffmanTree.MaxCodeLength; i++)
{
if (this.Bits.UnreadBits == 0)
{
@ -269,7 +269,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
if (code <= huffmanTree.MaxCodes[i])
{
result = huffmanTree.GetValue(code, i);
return this.LastErrorCode = OrigDecoderErrorCode.NoError;
return this.LastErrorCode = GolangDecoderErrorCode.NoError;
}
code <<= 1;
@ -279,7 +279,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
DecoderThrowHelper.ThrowImageFormatException.BadHuffmanCode();
// DUMMY RETURN! C# doesn't know we have thrown an exception!
return OrigDecoderErrorCode.NoError;
return GolangDecoderErrorCode.NoError;
}
/// <summary>
@ -295,11 +295,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <summary>
/// Skips the next n bytes.
/// Does not throw, returns <see cref="OrigDecoderErrorCode"/> instead!
/// Does not throw, returns <see cref="GolangDecoderErrorCode"/> instead!
/// </summary>
/// <param name="count">The number of bytes to ignore.</param>
/// <returns>The <see cref="OrigDecoderErrorCode"/></returns>
public OrigDecoderErrorCode SkipUnsafe(int count)
/// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
public GolangDecoderErrorCode SkipUnsafe(int count)
{
// Unread the overshot bytes, if any.
if (this.Bytes.UnreadableBytes != 0)
@ -328,13 +328,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
}
this.LastErrorCode = this.Bytes.FillUnsafe(this.InputStream);
if (this.LastErrorCode != OrigDecoderErrorCode.NoError)
if (this.LastErrorCode != GolangDecoderErrorCode.NoError)
{
return this.LastErrorCode;
}
}
return this.LastErrorCode = OrigDecoderErrorCode.NoError;
return this.LastErrorCode = GolangDecoderErrorCode.NoError;
}
/// <summary>
@ -374,8 +374,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// </summary>
/// <param name="t">Byte</param>
/// <param name="x">Read bits value</param>
/// <returns>The <see cref="OrigDecoderErrorCode"/></returns>
public OrigDecoderErrorCode ReceiveExtendUnsafe(int t, out int x)
/// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
public GolangDecoderErrorCode ReceiveExtendUnsafe(int t, out int x)
{
this.LastErrorCode = this.Bits.ReceiveExtendUnsafe(t, ref this, out x);
return this.LastErrorCode;
@ -386,7 +386,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// </summary>
public void ResetHuffmanDecoder()
{
this.Bits = default(Bits);
this.Bits = default;
}
}
}

6
src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoder.cs → src/ImageSharp/Formats/Jpeg/GolangPort/GolangJpegDecoder.cs

@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// <summary>
/// Image decoder for generating an image out of a jpg stream.
/// </summary>
internal sealed class OrigJpegDecoder : IImageDecoder, IJpegDecoderOptions, IImageInfoDetector
internal sealed class GolangJpegDecoder : IImageDecoder, IJpegDecoderOptions, IImageInfoDetector
{
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
{
Guard.NotNull(stream, nameof(stream));
using (var decoder = new OrigJpegDecoderCore(configuration, this))
using (var decoder = new GolangJpegDecoderCore(configuration, this))
{
return decoder.Decode<TPixel>(stream);
}
@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
{
Guard.NotNull(stream, nameof(stream));
using (var decoder = new OrigJpegDecoderCore(configuration, this))
using (var decoder = new GolangJpegDecoderCore(configuration, this))
{
return decoder.Identify(stream);
}

153
src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs → src/ImageSharp/Formats/Jpeg/GolangPort/GolangJpegDecoderCore.cs

@ -3,8 +3,9 @@
using System.Collections.Generic;
using System.IO;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
@ -19,7 +20,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// <summary>
/// Performs the jpeg decoding operation.
/// </summary>
internal sealed unsafe class OrigJpegDecoderCore : IRawJpegData
internal sealed unsafe class GolangJpegDecoderCore : IRawJpegData
{
/// <summary>
/// The maximum number of color components
@ -40,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
#pragma warning disable SA1401 // FieldsMustBePrivate
/// <summary>
/// Encapsulates stream reading and processing data and operations for <see cref="OrigJpegDecoderCore"/>.
/// Encapsulates stream reading and processing data and operations for <see cref="GolangJpegDecoderCore"/>.
/// It's a value type for improved data locality, and reduced number of CALLVIRT-s
/// </summary>
public InputProcessor InputProcessor;
@ -79,16 +80,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
private AdobeMarker adobe;
/// <summary>
/// Initializes a new instance of the <see cref="OrigJpegDecoderCore" /> class.
/// Initializes a new instance of the <see cref="GolangJpegDecoderCore" /> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The options.</param>
public OrigJpegDecoderCore(Configuration configuration, IJpegDecoderOptions options)
public GolangJpegDecoderCore(Configuration configuration, IJpegDecoderOptions options)
{
this.IgnoreMetadata = options.IgnoreMetadata;
this.configuration = configuration ?? Configuration.Default;
this.HuffmanTrees = OrigHuffmanTree.CreateHuffmanTrees();
this.QuantizationTables = new Block8x8F[MaxTq + 1];
this.Temp = new byte[2 * Block8x8F.Size];
}
@ -98,15 +97,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// <summary>
/// Gets the component array
/// </summary>
public OrigComponent[] Components { get; private set; }
public GolangComponent[] Components { get; private set; }
/// <summary>
/// Gets the huffman trees
/// </summary>
public OrigHuffmanTree[] HuffmanTrees { get; }
public GolangHuffmanTree[] HuffmanTrees { get; private set; }
/// <inheritdoc />
public Block8x8F[] QuantizationTables { get; }
public Block8x8F[] QuantizationTables { get; private set; }
/// <summary>
/// Gets the temporary buffer used to store bytes read from the stream.
@ -193,7 +192,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
where TPixel : struct, IPixel<TPixel>
{
this.ParseStream(stream);
return this.PostProcessIntoImage<TPixel>();
}
@ -204,7 +202,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
public IImageInfo Identify(Stream stream)
{
this.ParseStream(stream, true);
return new ImageInfo(new PixelTypeInfo(this.BitsPerPixel), this.ImageWidth, this.ImageHeight, this.MetaData);
}
@ -213,9 +210,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
{
if (this.Components != null)
{
foreach (OrigComponent component in this.Components)
foreach (GolangComponent component in this.Components)
{
component.Dispose();
component?.Dispose();
}
}
@ -223,7 +220,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
}
/// <summary>
/// Read metadata from stream and read the blocks in the scans into <see cref="OrigComponent.SpectralBlocks"/>.
/// Read metadata from stream and read the blocks in the scans into <see cref="GolangComponent.SpectralBlocks"/>.
/// </summary>
/// <param name="stream">The stream</param>
/// <param name="metadataOnly">Whether to decode metadata only.</param>
@ -233,10 +230,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
this.InputStream = stream;
this.InputProcessor = new InputProcessor(stream, this.Temp);
if (!metadataOnly)
{
this.HuffmanTrees = GolangHuffmanTree.CreateHuffmanTrees();
this.QuantizationTables = new Block8x8F[MaxTq + 1];
}
// Check for the Start Of Image marker.
this.InputProcessor.ReadFull(this.Temp, 0, 2);
if (this.Temp[0] != OrigJpegConstants.Markers.XFF || this.Temp[1] != OrigJpegConstants.Markers.SOI)
if (this.Temp[0] != JpegConstants.Markers.XFF || this.Temp[1] != JpegConstants.Markers.SOI)
{
throw new ImageFormatException("Missing SOI marker.");
}
@ -300,12 +303,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
}
// End Of Image.
if (marker == OrigJpegConstants.Markers.EOI)
if (marker == JpegConstants.Markers.EOI)
{
break;
}
if (marker >= OrigJpegConstants.Markers.RST0 && marker <= OrigJpegConstants.Markers.RST7)
if (marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7)
{
// Figures B.2 and B.16 of the specification suggest that restart markers should
// only occur between Entropy Coded Segments and not after the final ECS.
@ -327,18 +330,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
switch (marker)
{
case OrigJpegConstants.Markers.SOF0:
case OrigJpegConstants.Markers.SOF1:
case OrigJpegConstants.Markers.SOF2:
this.IsProgressive = marker == OrigJpegConstants.Markers.SOF2;
case JpegConstants.Markers.SOF0:
case JpegConstants.Markers.SOF1:
case JpegConstants.Markers.SOF2:
this.IsProgressive = marker == JpegConstants.Markers.SOF2;
this.ProcessStartOfFrameMarker(remaining, metadataOnly);
if (metadataOnly && this.isJFif)
{
return;
}
break;
case OrigJpegConstants.Markers.DHT:
case JpegConstants.Markers.DHT:
if (metadataOnly)
{
this.InputProcessor.Skip(remaining);
@ -349,7 +348,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
}
break;
case OrigJpegConstants.Markers.DQT:
case JpegConstants.Markers.DQT:
if (metadataOnly)
{
this.InputProcessor.Skip(remaining);
@ -360,21 +359,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
}
break;
case OrigJpegConstants.Markers.SOS:
if (metadataOnly)
case JpegConstants.Markers.SOS:
if (!metadataOnly)
{
return;
this.ProcessStartOfScanMarker(remaining);
if (this.InputProcessor.ReachedEOF)
{
// If unexpected EOF reached. We can stop processing bytes as we now have the image data.
processBytes = false;
}
}
this.ProcessStartOfScanMarker(remaining);
if (this.InputProcessor.ReachedEOF)
else
{
// If unexpected EOF reached. We can stop processing bytes as we now have the image data.
// It's highly unlikely that APPn related data will be found after the SOS marker
// We should have gathered everything we need by now.
processBytes = false;
}
break;
case OrigJpegConstants.Markers.DRI:
case JpegConstants.Markers.DRI:
if (metadataOnly)
{
this.InputProcessor.Skip(remaining);
@ -385,21 +389,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
}
break;
case OrigJpegConstants.Markers.APP0:
case JpegConstants.Markers.APP0:
this.ProcessApplicationHeaderMarker(remaining);
break;
case OrigJpegConstants.Markers.APP1:
case JpegConstants.Markers.APP1:
this.ProcessApp1Marker(remaining);
break;
case OrigJpegConstants.Markers.APP2:
case JpegConstants.Markers.APP2:
this.ProcessApp2Marker(remaining);
break;
case OrigJpegConstants.Markers.APP14:
case JpegConstants.Markers.APP14:
this.ProcessApp14Marker(remaining);
break;
default:
if ((marker >= OrigJpegConstants.Markers.APP0 && marker <= OrigJpegConstants.Markers.APP15)
|| marker == OrigJpegConstants.Markers.COM)
if ((marker >= JpegConstants.Markers.APP0 && marker <= JpegConstants.Markers.APP15)
|| marker == JpegConstants.Markers.COM)
{
this.InputProcessor.Skip(remaining);
}
@ -425,7 +429,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// </summary>
private void InitDerivedMetaDataProperties()
{
if (this.isExif)
if (this.isJFif)
{
this.MetaData.HorizontalResolution = this.jFif.XDensity;
this.MetaData.VerticalResolution = this.jFif.YDensity;
}
else if (this.isExif)
{
double horizontalValue = this.MetaData.ExifProfile.TryGetValue(ExifTag.XResolution, out ExifValue horizonalTag)
? ((Rational)horizonalTag.Value).ToDouble()
@ -441,11 +450,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
this.MetaData.VerticalResolution = verticalValue;
}
}
else if (this.isJFif)
{
this.MetaData.HorizontalResolution = this.jFif.XDensity;
this.MetaData.VerticalResolution = this.jFif.YDensity;
}
}
/// <summary>
@ -675,26 +679,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
throw new ImageFormatException("SOF has wrong length");
}
this.Components = new OrigComponent[this.ComponentCount];
for (int i = 0; i < this.ComponentCount; i++)
if (!metadataOnly)
{
byte componentIdentifier = this.Temp[6 + (3 * i)];
var component = new OrigComponent(componentIdentifier, i);
component.InitializeCoreData(this);
this.Components[i] = component;
}
this.Components = new GolangComponent[this.ComponentCount];
int h0 = this.Components[0].HorizontalSamplingFactor;
int v0 = this.Components[0].VerticalSamplingFactor;
for (int i = 0; i < this.ComponentCount; i++)
{
byte componentIdentifier = this.Temp[6 + (3 * i)];
var component = new GolangComponent(componentIdentifier, i);
component.InitializeCoreData(this);
this.Components[i] = component;
}
this.ImageSizeInMCU = this.ImageSizeInPixels.DivideRoundUp(8 * h0, 8 * v0);
int h0 = this.Components[0].HorizontalSamplingFactor;
int v0 = this.Components[0].VerticalSamplingFactor;
this.ColorSpace = this.DeduceJpegColorSpace();
this.ImageSizeInMCU = this.ImageSizeInPixels.DivideRoundUp(8 * h0, 8 * v0);
foreach (OrigComponent component in this.Components)
{
component.InitializeDerivedData(this.configuration.MemoryManager, this, metadataOnly);
this.ColorSpace = this.DeduceJpegColorSpace();
foreach (GolangComponent component in this.Components)
{
component.InitializeDerivedData(this.configuration.MemoryManager, this);
}
}
}
@ -715,18 +722,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
this.InputProcessor.ReadFull(this.Temp, 0, 17);
int tc = this.Temp[0] >> 4;
if (tc > OrigHuffmanTree.MaxTc)
if (tc > GolangHuffmanTree.MaxTc)
{
throw new ImageFormatException("Bad Tc value");
}
int th = this.Temp[0] & 0x0f;
if (th > OrigHuffmanTree.MaxTh)
if (th > GolangHuffmanTree.MaxTh)
{
throw new ImageFormatException("Bad Th value");
}
int huffTreeIndex = (tc * OrigHuffmanTree.ThRowSize) + th;
int huffTreeIndex = (tc * GolangHuffmanTree.ThRowSize) + th;
this.HuffmanTrees[huffTreeIndex].ProcessDefineHuffmanTablesMarkerLoop(
ref this.InputProcessor,
this.Temp,
@ -760,9 +767,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// </exception>
private void ProcessStartOfScanMarker(int remaining)
{
var scan = default(OrigJpegScanDecoder);
OrigJpegScanDecoder.InitStreamReading(&scan, this, remaining);
this.InputProcessor.Bits = default(Bits);
var scan = default(GolangJpegScanDecoder);
GolangJpegScanDecoder.InitStreamReading(&scan, this, remaining);
this.InputProcessor.Bits = default;
scan.DecodeBlocks(this);
}
@ -773,19 +780,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
case 1:
return JpegColorSpace.Grayscale;
case 3:
if (!this.isAdobe || this.adobe.ColorTransform == OrigJpegConstants.Adobe.ColorTransformYCbCr)
if (!this.isAdobe || this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYCbCr)
{
return JpegColorSpace.YCbCr;
}
if (this.adobe.ColorTransform == OrigJpegConstants.Adobe.ColorTransformUnknown)
if (this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown)
{
return JpegColorSpace.RGB;
}
break;
case 4:
if (this.adobe.ColorTransform == OrigJpegConstants.Adobe.ColorTransformYcck)
if (this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYcck)
{
return JpegColorSpace.Ycck;
}

189
src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegConstants.cs

@ -1,189 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
{
/// <summary>
/// Defines jpeg constants defined in the specification.
/// </summary>
internal static class OrigJpegConstants
{
/// <summary>
/// The maximum allowable length in each dimension of a jpeg image.
/// </summary>
public const ushort MaxLength = 65535;
/// <summary>
/// The list of mimetypes that equate to a jpeg.
/// </summary>
public static readonly IEnumerable<string> MimeTypes = new[] { "image/jpeg", "image/pjpeg" };
/// <summary>
/// The list of file extensions that equate to a jpeg.
/// </summary>
public static readonly IEnumerable<string> FileExtensions = new[] { "jpg", "jpeg", "jfif" };
/// <summary>
/// Describes common Jpeg markers
/// </summary>
internal static class Markers
{
/// <summary>
/// Marker prefix. Next byte is a marker.
/// </summary>
public const byte XFF = 0xff;
/// <summary>
/// Same as <see cref="XFF"/> but of type <see cref="int"/>
/// </summary>
public const int XFFInt = XFF;
/// <summary>
/// Start of Image
/// </summary>
public const byte SOI = 0xd8;
/// <summary>
/// Start of Frame (baseline DCT)
/// <remarks>
/// Indicates that this is a baseline DCT-based JPEG, and specifies the width, height, number of components,
/// and component subsampling (e.g., 4:2:0).
/// </remarks>
/// </summary>
public const byte SOF0 = 0xc0;
/// <summary>
/// Start Of Frame (Extended Sequential DCT)
/// <remarks>
/// Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components,
/// and component subsampling (e.g., 4:2:0).
/// </remarks>
/// </summary>
public const byte SOF1 = 0xc1;
/// <summary>
/// Start Of Frame (progressive DCT)
/// <remarks>
/// Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components,
/// and component subsampling (e.g., 4:2:0).
/// </remarks>
/// </summary>
public const byte SOF2 = 0xc2;
/// <summary>
/// Define Huffman Table(s)
/// <remarks>
/// Specifies one or more Huffman tables.
/// </remarks>
/// </summary>
public const byte DHT = 0xc4;
/// <summary>
/// Define Quantization Table(s)
/// <remarks>
/// Specifies one or more quantization tables.
/// </remarks>
/// </summary>
public const byte DQT = 0xdb;
/// <summary>
/// Define Restart Interval
/// <remarks>
/// Specifies the interval between RSTn markers, in macroblocks. This marker is followed by two bytes
/// indicating the fixed size so it can be treated like any other variable size segment.
/// </remarks>
/// </summary>
public const byte DRI = 0xdd;
/// <summary>
/// Define First Restart
/// <remarks>
/// Inserted every r macroblocks, where r is the restart interval set by a DRI marker.
/// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7.
/// </remarks>
/// </summary>
public const byte RST0 = 0xd0;
/// <summary>
/// Define Eigth Restart
/// <remarks>
/// Inserted every r macroblocks, where r is the restart interval set by a DRI marker.
/// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7.
/// </remarks>
/// </summary>
public const byte RST7 = 0xd7;
/// <summary>
/// Start of Scan
/// <remarks>
/// Begins a top-to-bottom scan of the image. In baseline DCT JPEG images, there is generally a single scan.
/// Progressive DCT JPEG images usually contain multiple scans. This marker specifies which slice of data it
/// will contain, and is immediately followed by entropy-coded data.
/// </remarks>
/// </summary>
public const byte SOS = 0xda;
/// <summary>
/// Comment
/// <remarks>
/// Contains a text comment.
/// </remarks>
/// </summary>
public const byte COM = 0xfe;
/// <summary>
/// End of Image
/// </summary>
public const byte EOI = 0xd9;
/// <summary>
/// Application specific marker for marking the jpeg format.
/// <see href="http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html"/>
/// </summary>
public const byte APP0 = 0xe0;
/// <summary>
/// Application specific marker for marking where to store metadata.
/// </summary>
public const byte APP1 = 0xe1;
/// <summary>
/// Application specific marker for marking where to store ICC profile information.
/// </summary>
public const byte APP2 = 0xe2;
/// <summary>
/// Application specific marker used by Adobe for storing encoding information for DCT filters.
/// </summary>
public const byte APP14 = 0xee;
/// <summary>
/// Application specific marker used by GraphicConverter to store JPEG quality.
/// </summary>
public const byte APP15 = 0xef;
}
/// <summary>
/// Describes Adobe specific markers <see href="http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe"/>
/// </summary>
internal static class Adobe
{
/// <summary>
/// The color transform is unknown.(RGB or CMYK)
/// </summary>
public const int ColorTransformUnknown = 0;
/// <summary>
/// The color transform is YCbCr (luminance, red chroma, blue chroma)
/// </summary>
public const int ColorTransformYCbCr = 1;
/// <summary>
/// The color transform is YCCK (luminance, red chroma, blue chroma, keyline)
/// </summary>
public const int ColorTransformYcck = 2;
}
}
}

125
src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegConstants.cs → src/ImageSharp/Formats/Jpeg/JpegConstants.cs

@ -1,119 +1,141 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
using System.Collections.Generic;
namespace SixLabors.ImageSharp.Formats.Jpeg
{
/// <summary>
/// Contains jpeg constant values
/// Contains jpeg constant values defined in the specification.
/// </summary>
internal static class PdfJsJpegConstants
internal static class JpegConstants
{
/// <summary>
/// The maximum allowable length in each dimension of a jpeg image.
/// </summary>
public const ushort MaxLength = 65535;
/// <summary>
/// The list of mimetypes that equate to a jpeg.
/// </summary>
public static readonly IEnumerable<string> MimeTypes = new[] { "image/jpeg", "image/pjpeg" };
/// <summary>
/// The list of file extensions that equate to a jpeg.
/// </summary>
public static readonly IEnumerable<string> FileExtensions = new[] { "jpg", "jpeg", "jfif" };
/// <summary>
/// Contains marker specific constants
/// </summary>
public static class Markers
// ReSharper disable InconsistentNaming
internal static class Markers
{
/// <summary>
/// The prefix used for all markers.
/// </summary>
public const byte Prefix = 0xFF;
public const byte XFF = 0xFF;
/// <summary>
/// Same as <see cref="XFF"/> but of type <see cref="int"/>
/// </summary>
public const int XFFInt = XFF;
/// <summary>
/// The Start of Image marker
/// </summary>
public const ushort SOI = 0xFFD8;
public const byte SOI = 0xD8;
/// <summary>
/// The End of Image marker
/// </summary>
public const ushort EOI = 0xFFD9;
public const byte EOI = 0xD9;
/// <summary>
/// Application specific marker for marking the jpeg format.
/// <see href="http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html"/>
/// </summary>
public const ushort APP0 = 0xFFE0;
public const byte APP0 = 0xE0;
/// <summary>
/// Application specific marker for marking where to store metadata.
/// </summary>
public const ushort APP1 = 0xFFE1;
public const byte APP1 = 0xE1;
/// <summary>
/// Application specific marker for marking where to store ICC profile information.
/// </summary>
public const ushort APP2 = 0xFFE2;
public const byte APP2 = 0xE2;
/// <summary>
/// Application specific marker.
/// </summary>
public const ushort APP3 = 0xFFE3;
public const byte APP3 = 0xE3;
/// <summary>
/// Application specific marker.
/// </summary>
public const ushort APP4 = 0xFFE4;
public const byte APP4 = 0xE4;
/// <summary>
/// Application specific marker.
/// </summary>
public const ushort APP5 = 0xFFE5;
public const byte APP5 = 0xE5;
/// <summary>
/// Application specific marker.
/// </summary>
public const ushort APP6 = 0xFFE6;
public const byte APP6 = 0xE6;
/// <summary>
/// Application specific marker.
/// </summary>
public const ushort APP7 = 0xFFE7;
public const byte APP7 = 0xE7;
/// <summary>
/// Application specific marker.
/// </summary>
public const ushort APP8 = 0xFFE8;
public const byte APP8 = 0xE8;
/// <summary>
/// Application specific marker.
/// </summary>
public const ushort APP9 = 0xFFE9;
public const byte APP9 = 0xE9;
/// <summary>
/// Application specific marker.
/// </summary>
public const ushort APP10 = 0xFFEA;
public const byte APP10 = 0xEA;
/// <summary>
/// Application specific marker.
/// </summary>
public const ushort APP11 = 0xFFEB;
public const byte APP11 = 0xEB;
/// <summary>
/// Application specific marker.
/// </summary>
public const ushort APP12 = 0xFFEC;
public const byte APP12 = 0xEC;
/// <summary>
/// Application specific marker.
/// </summary>
public const ushort APP13 = 0xFFED;
public const byte APP13 = 0xED;
/// <summary>
/// Application specific marker used by Adobe for storing encoding information for DCT filters.
/// </summary>
public const ushort APP14 = 0xFFEE;
public const byte APP14 = 0xEE;
/// <summary>
/// Application specific marker used by GraphicConverter to store JPEG quality.
/// </summary>
public const ushort APP15 = 0xFFEF;
public const byte APP15 = 0xEF;
/// <summary>
/// The text comment marker
/// </summary>
public const ushort COM = 0xFFFE;
public const byte COM = 0xFE;
/// <summary>
/// Define Quantization Table(s) marker
@ -121,7 +143,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// Specifies one or more quantization tables.
/// </remarks>
/// </summary>
public const ushort DQT = 0xFFDB;
public const byte DQT = 0xDB;
/// <summary>
/// Start of Frame (baseline DCT)
@ -130,7 +152,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// and component subsampling (e.g., 4:2:0).
/// </remarks>
/// </summary>
public const ushort SOF0 = 0xFFC0;
public const byte SOF0 = 0xC0;
/// <summary>
/// Start Of Frame (Extended Sequential DCT)
@ -139,7 +161,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// and component subsampling (e.g., 4:2:0).
/// </remarks>
/// </summary>
public const ushort SOF1 = 0xFFC1;
public const byte SOF1 = 0xC1;
/// <summary>
/// Start Of Frame (progressive DCT)
@ -148,7 +170,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// and component subsampling (e.g., 4:2:0).
/// </remarks>
/// </summary>
public const ushort SOF2 = 0xFFC2;
public const byte SOF2 = 0xC2;
/// <summary>
/// Define Huffman Table(s)
@ -156,15 +178,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// Specifies one or more Huffman tables.
/// </remarks>
/// </summary>
public const ushort DHT = 0xFFC4;
public const byte DHT = 0xC4;
/// <summary>
/// Define Restart Interval
/// <remarks>
/// Specifies the interval between RSTn markers, in macroblocks.This marker is followed by two bytes indicating the fixed size so it can be treated like any other variable size segment.
/// Specifies the interval between RSTn markers, in macroblocks.This marker is followed by two bytes indicating the fixed size so
/// it can be treated like any other variable size segment.
/// </remarks>
/// </summary>
public const ushort DRI = 0xFFDD;
public const byte DRI = 0xDD;
/// <summary>
/// Start of Scan
@ -174,7 +197,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// will contain, and is immediately followed by entropy-coded data.
/// </remarks>
/// </summary>
public const ushort SOS = 0xFFDA;
public const byte SOS = 0xDA;
/// <summary>
/// Define First Restart
@ -183,7 +206,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7.
/// </remarks>
/// </summary>
public const ushort RST0 = 0xFFD0;
public const byte RST0 = 0xD0;
/// <summary>
/// Define Eigth Restart
@ -192,28 +215,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7.
/// </remarks>
/// </summary>
public const ushort RST7 = 0xFFD7;
public const byte RST7 = 0xD7;
}
/// <summary>
/// Contains Adobe specific constants
/// </summary>
internal static class Adobe
{
/// <summary>
/// Contains Adobe specific markers
/// The color transform is unknown.(RGB or CMYK)
/// </summary>
public static class Adobe
{
/// <summary>
/// The color transform is unknown.(RGB or CMYK)
/// </summary>
public const byte ColorTransformUnknown = 0;
public const byte ColorTransformUnknown = 0;
/// <summary>
/// The color transform is YCbCr (luminance, red chroma, blue chroma)
/// </summary>
public const byte ColorTransformYCbCr = 1;
/// <summary>
/// The color transform is YCbCr (luminance, red chroma, blue chroma)
/// </summary>
public const byte ColorTransformYCbCr = 1;
/// <summary>
/// The color transform is YCCK (luminance, red chroma, blue chroma, keyline)
/// </summary>
public const byte ColorTransformYcck = 2;
}
/// <summary>
/// The color transform is YCCK (luminance, red chroma, blue chroma, keyline)
/// </summary>
public const byte ColorTransformYcck = 2;
}
}
}

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

@ -3,7 +3,7 @@
using System.IO;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg
@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
Guard.NotNull(stream, nameof(stream));
using (var decoder = new OrigJpegDecoderCore(configuration, this))
using (var decoder = new PdfJsJpegDecoderCore(configuration, this))
{
return decoder.Decode<TPixel>(stream);
}
@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
Guard.NotNull(stream, "stream");
using (var decoder = new OrigJpegDecoderCore(configuration, this))
using (var decoder = new PdfJsJpegDecoderCore(configuration, this))
{
return decoder.Identify(stream);
}

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

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg

74
src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs → src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

@ -1,19 +1,16 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.MetaData.Profiles.Icc;
using SixLabors.ImageSharp.PixelFormats;
using Block8x8F = SixLabors.ImageSharp.Formats.Jpeg.Common.Block8x8F;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
namespace SixLabors.ImageSharp.Formats.Jpeg
{
/// <summary>
/// Image encoder for writing an image to a stream as a jpeg.
@ -58,7 +55,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// </summary>
private static readonly byte[] SosHeaderYCbCr =
{
OrigJpegConstants.Markers.XFF, OrigJpegConstants.Markers.SOS,
JpegConstants.Markers.XFF, JpegConstants.Markers.SOS,
// Marker
0x00, 0x0c,
@ -104,11 +101,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
}
};
/// <summary>
/// Lookup tables for converting Rgb to YCbCr
/// </summary>
private static RgbToYCbCrTables rgbToYCbCrTables = RgbToYCbCrTables.Create();
/// <summary>
/// A scratch buffer to reduce allocations.
/// </summary>
@ -190,7 +182,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
ushort max = OrigJpegConstants.MaxLength;
ushort max = JpegConstants.MaxLength;
if (image.Width >= max || image.Height >= max)
{
throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}.");
@ -234,8 +226,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
this.WriteStartOfScan(image);
// Write the End Of Image marker.
this.buffer[0] = OrigJpegConstants.Markers.XFF;
this.buffer[1] = OrigJpegConstants.Markers.EOI;
this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = JpegConstants.Markers.EOI;
stream.Write(this.buffer, 0, 2);
stream.Flush();
}
@ -382,18 +374,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
{
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
// (Partially done with YCbCrForwardConverter<TPixel>)
Block8x8F temp1 = default(Block8x8F);
Block8x8F temp2 = default(Block8x8F);
Block8x8F temp1 = default;
Block8x8F temp2 = default;
Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable;
Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable;
ZigZag unzig = ZigZag.CreateUnzigTable();
var unzig = ZigZag.CreateUnzigTable();
// ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
YCbCrForwardConverter<TPixel> pixelConverter = YCbCrForwardConverter<TPixel>.Create();
var pixelConverter = YCbCrForwardConverter<TPixel>.Create();
for (int y = 0; y < pixels.Height; y += 8)
{
@ -437,12 +429,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
private void WriteApplicationHeader(short horizontalResolution, short verticalResolution)
{
// Write the start of image marker. Markers are always prefixed with with 0xff.
this.buffer[0] = OrigJpegConstants.Markers.XFF;
this.buffer[1] = OrigJpegConstants.Markers.SOI;
this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = JpegConstants.Markers.SOI;
// Write the JFIF headers
this.buffer[2] = OrigJpegConstants.Markers.XFF;
this.buffer[3] = OrigJpegConstants.Markers.APP0; // Application Marker
this.buffer[2] = JpegConstants.Markers.XFF;
this.buffer[3] = JpegConstants.Markers.APP0; // Application Marker
this.buffer[4] = 0x00;
this.buffer[5] = 0x10;
this.buffer[6] = 0x4a; // J
@ -502,7 +494,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
this.EmitHuffRLE((HuffIndex)((2 * (int)index) + 0), 0, dc - prevDC);
// Emit the AC components.
HuffIndex h = (HuffIndex)((2 * (int)index) + 1);
var h = (HuffIndex)((2 * (int)index) + 1);
int runLength = 0;
for (int zig = 1; zig < Block8x8F.Size; zig++)
@ -556,7 +548,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
markerlen += 1 + 16 + s.Values.Length;
}
this.WriteMarkerHeader(OrigJpegConstants.Markers.DHT, markerlen);
this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen);
for (int i = 0; i < specs.Length; i++)
{
HuffmanSpec spec = specs[i];
@ -590,7 +582,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
{
// Marker + quantization table lengths
int markerlen = 2 + (QuantizationTableCount * (1 + Block8x8F.Size));
this.WriteMarkerHeader(OrigJpegConstants.Markers.DQT, markerlen);
this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen);
// Loop through and collect the tables as one array.
// This allows us to reduce the number of writes to the stream.
@ -627,8 +619,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
int length = data.Length + 2;
this.buffer[0] = OrigJpegConstants.Markers.XFF;
this.buffer[1] = OrigJpegConstants.Markers.APP1; // Application Marker
this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = JpegConstants.Markers.APP1; // Application Marker
this.buffer[2] = (byte)((length >> 8) & 0xFF);
this.buffer[3] = (byte)(length & 0xFF);
@ -686,8 +678,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
dataLength -= length;
this.buffer[0] = OrigJpegConstants.Markers.XFF;
this.buffer[1] = OrigJpegConstants.Markers.APP2; // Application Marker
this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = JpegConstants.Markers.APP2; // Application Marker
int markerLength = length + 16;
this.buffer[2] = (byte)((markerLength >> 8) & 0xFF);
this.buffer[3] = (byte)(markerLength & 0xFF);
@ -759,7 +751,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
// Length (high byte, low byte), 8 + components * 3.
int markerlen = 8 + (3 * componentCount);
this.WriteMarkerHeader(OrigJpegConstants.Markers.SOF0, markerlen);
this.WriteMarkerHeader(JpegConstants.Markers.SOF0, markerlen);
this.buffer[0] = 8; // Data Precision. 8 for now, 12 and 16 bit jpegs not supported
this.buffer[1] = (byte)(height >> 8);
this.buffer[2] = (byte)(height & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported
@ -827,20 +819,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
where TPixel : struct, IPixel<TPixel>
{
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
Block8x8F b = default(Block8x8F);
Block8x8F b = default;
BlockQuad cb = default(BlockQuad);
BlockQuad cr = default(BlockQuad);
Block8x8F* cbPtr = (Block8x8F*)cb.Data;
Block8x8F* crPtr = (Block8x8F*)cr.Data;
BlockQuad cb = default;
BlockQuad cr = default;
var cbPtr = (Block8x8F*)cb.Data;
var crPtr = (Block8x8F*)cr.Data;
Block8x8F temp1 = default(Block8x8F);
Block8x8F temp2 = default(Block8x8F);
Block8x8F temp1 = default;
Block8x8F temp2 = default;
Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable;
Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable;
ZigZag unzig = ZigZag.CreateUnzigTable();
var unzig = ZigZag.CreateUnzigTable();
var pixelConverter = YCbCrForwardConverter<TPixel>.Create();
@ -902,7 +894,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
private void WriteMarkerHeader(byte marker, int length)
{
// Markers are always prefixed with with 0xff.
this.buffer[0] = OrigJpegConstants.Markers.XFF;
this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = marker;
this.buffer[2] = (byte)(length >> 8);
this.buffer[3] = (byte)(length & 0xff);

5
src/ImageSharp/Formats/Jpeg/JpegFormat.cs

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
namespace SixLabors.ImageSharp.Formats.Jpeg
{
@ -18,9 +17,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public string DefaultMimeType => "image/jpeg";
/// <inheritdoc/>
public IEnumerable<string> MimeTypes => OrigJpegConstants.MimeTypes;
public IEnumerable<string> MimeTypes => JpegConstants.MimeTypes;
/// <inheritdoc/>
public IEnumerable<string> FileExtensions => OrigJpegConstants.FileExtensions;
public IEnumerable<string> FileExtensions => JpegConstants.FileExtensions;
}
}

238
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/DoubleBufferedStreamReader.cs

@ -0,0 +1,238 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
// TODO: This could be useful elsewhere.
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{
/// <summary>
/// A stream reader that add a secondary level buffer in addition to native stream buffered reading
/// to reduce the overhead of small incremental reads.
/// </summary>
internal class DoubleBufferedStreamReader : IDisposable
{
/// <summary>
/// The length, in bytes, of the buffering chunk
/// </summary>
public const int ChunkLength = 4096;
private const int ChunkLengthMinusOne = ChunkLength - 1;
private readonly Stream stream;
private readonly IManagedByteBuffer managedBuffer;
private readonly byte[] bufferChunk;
private readonly int length;
private int bytesRead;
private int position;
/// <summary>
/// Initializes a new instance of the <see cref="DoubleBufferedStreamReader"/> class.
/// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <param name="stream">The input stream.</param>
public DoubleBufferedStreamReader(MemoryManager memoryManager, Stream stream)
{
this.stream = stream;
this.length = (int)stream.Length;
this.managedBuffer = memoryManager.AllocateCleanManagedByteBuffer(ChunkLength);
this.bufferChunk = this.managedBuffer.Array;
}
/// <summary>
/// Gets the length, in bytes, of the stream
/// </summary>
public long Length => this.length;
/// <summary>
/// Gets or sets the current position within the stream
/// </summary>
public long Position
{
get => this.position;
set
{
// Reset everything. It's easier than tracking.
this.position = (int)value;
this.stream.Seek(this.position, SeekOrigin.Begin);
this.bytesRead = ChunkLength;
}
}
/// <summary>
/// Reads a byte from the stream and advances the position within the stream by one
/// byte, or returns -1 if at the end of the stream.
/// </summary>
/// <returns>The unsigned byte cast to an <see cref="int"/>, or -1 if at the end of the stream.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadByte()
{
if (this.position >= this.length)
{
return -1;
}
if (this.position == 0 || this.bytesRead > ChunkLengthMinusOne)
{
return this.ReadByteSlow();
}
this.position++;
return this.bufferChunk[this.bytesRead++];
}
/// <summary>
/// Skips the number of bytes in the stream
/// </summary>
/// <param name="count">The number of bytes to skip</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Skip(int count)
{
this.Position += count;
}
/// <summary>
/// Reads a sequence of bytes from the current stream and advances the position within the stream
/// by the number of bytes read.
/// </summary>
/// <param name="buffer">
/// An array of bytes. When this method returns, the buffer contains the specified
/// byte array with the values between offset and (offset + count - 1) replaced by
/// the bytes read from the current source.
/// </param>
/// <param name="offset">
/// The zero-based byte offset in buffer at which to begin storing the data read
/// from the current stream.
/// </param>
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
/// <returns>
/// The total number of bytes read into the buffer. This can be less than the number
/// of bytes requested if that many bytes are not currently available, or zero (0)
/// if the end of the stream has been reached.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Read(byte[] buffer, int offset, int count)
{
if (buffer.Length > ChunkLength)
{
return this.ReadToBufferSlow(buffer, offset, count);
}
if (this.position == 0 || count + this.bytesRead > ChunkLength)
{
return this.ReadToChunkSlow(buffer, offset, count);
}
int n = this.GetCount(count);
this.CopyBytes(buffer, offset, n);
this.position += n;
this.bytesRead += n;
return n;
}
/// <inheritdoc/>
public void Dispose()
{
this.managedBuffer?.Dispose();
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int ReadByteSlow()
{
if (this.position != this.stream.Position)
{
this.stream.Seek(this.position, SeekOrigin.Begin);
}
this.stream.Read(this.bufferChunk, 0, ChunkLength);
this.bytesRead = 0;
this.position++;
return this.bufferChunk[this.bytesRead++];
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int ReadToChunkSlow(byte[] buffer, int offset, int count)
{
// Refill our buffer then copy.
if (this.position != this.stream.Position)
{
this.stream.Seek(this.position, SeekOrigin.Begin);
}
this.stream.Read(this.bufferChunk, 0, ChunkLength);
this.bytesRead = 0;
int n = this.GetCount(count);
this.CopyBytes(buffer, offset, n);
this.position += n;
this.bytesRead += n;
return n;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int ReadToBufferSlow(byte[] buffer, int offset, int count)
{
// Read to target but don't copy to our chunk.
if (this.position != this.stream.Position)
{
this.stream.Seek(this.position, SeekOrigin.Begin);
}
int n = this.stream.Read(buffer, offset, count);
this.Position += n;
return n;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int GetCount(int count)
{
int n = this.length - this.position;
if (n > count)
{
n = count;
}
if (n < 0)
{
n = 0;
}
return n;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CopyBytes(byte[] buffer, int offset, int count)
{
if (count < 9)
{
int byteCount = count;
int read = this.bytesRead;
byte[] chunk = this.bufferChunk;
while (--byteCount > -1)
{
buffer[offset + byteCount] = chunk[read + byteCount];
}
}
else
{
Buffer.BlockCopy(this.bufferChunk, this.bytesRead, buffer, offset, count);
}
}
}
}

24
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFileMarker.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{
/// <summary>
@ -13,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// </summary>
/// <param name="marker">The marker</param>
/// <param name="position">The position within the stream</param>
public PdfJsFileMarker(ushort marker, long position)
public PdfJsFileMarker(byte marker, long position)
{
this.Marker = marker;
this.Position = position;
@ -26,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// <param name="marker">The marker</param>
/// <param name="position">The position within the stream</param>
/// <param name="invalid">Whether the current marker is invalid</param>
public PdfJsFileMarker(ushort marker, long position, bool invalid)
public PdfJsFileMarker(byte marker, long position, bool invalid)
{
this.Marker = marker;
this.Position = position;
@ -36,17 +38,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// <summary>
/// Gets a value indicating whether the current marker is invalid
/// </summary>
public bool Invalid { get; }
public bool Invalid
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
}
/// <summary>
/// Gets the position of the marker within a stream
/// </summary>
public ushort Marker { get; }
public byte Marker
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
}
/// <summary>
/// Gets the position of the marker within a stream
/// </summary>
public long Position { get; }
public long Position
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
}
/// <inheritdoc/>
public override string ToString()

9
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs

@ -4,8 +4,9 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Memory;
using SixLabors.Primitives;
@ -36,9 +37,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
public byte Id { get; }
/// <summary>
/// Gets or sets Pred TODO: What does pred stand for?
/// Gets or sets DC coefficient predictor
/// </summary>
public int Pred { get; set; }
public int DcPredictor { get; set; }
/// <summary>
/// Gets the horizontal sampling factor.

460
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs

@ -5,10 +5,10 @@ using System;
#if DEBUG
using System.Diagnostics;
#endif
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{
@ -21,6 +21,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
private byte[] markerBuffer;
private int mcuToRead;
private int mcusPerLine;
private int mcu;
private int bitsData;
private int bitsCount;
@ -60,7 +66,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// <param name="successive">The successive approximation bit low end</param>
public void DecodeScan(
PdfJsFrame frame,
Stream stream,
DoubleBufferedStreamReader stream,
PdfJsHuffmanTables dcHuffmanTables,
PdfJsHuffmanTables acHuffmanTables,
PdfJsFrameComponent[] components,
@ -82,9 +88,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
this.unexpectedMarkerReached = false;
bool progressive = frame.Progressive;
int mcusPerLine = frame.McusPerLine;
this.mcusPerLine = frame.McusPerLine;
int mcu = 0;
this.mcu = 0;
int mcuExpected;
if (componentsLength == 1)
{
@ -92,51 +98,46 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
else
{
mcuExpected = mcusPerLine * frame.McusPerColumn;
mcuExpected = this.mcusPerLine * frame.McusPerColumn;
}
PdfJsFileMarker fileMarker;
while (mcu < mcuExpected)
while (this.mcu < mcuExpected)
{
// Reset interval stuff
int mcuToRead = resetInterval != 0 ? Math.Min(mcuExpected - mcu, resetInterval) : mcuExpected;
this.mcuToRead = resetInterval != 0 ? Math.Min(mcuExpected - this.mcu, resetInterval) : mcuExpected;
for (int i = 0; i < components.Length; i++)
{
PdfJsFrameComponent c = components[i];
c.Pred = 0;
c.DcPredictor = 0;
}
this.eobrun = 0;
if (!progressive)
{
this.DecodeScanBaseline(dcHuffmanTables, acHuffmanTables, components, componentsLength, mcusPerLine, mcuToRead, ref mcu, stream);
this.DecodeScanBaseline(dcHuffmanTables, acHuffmanTables, components, componentsLength, stream);
}
else
{
bool isAc = this.specStart != 0;
bool isFirst = successivePrev == 0;
PdfJsHuffmanTables huffmanTables = isAc ? acHuffmanTables : dcHuffmanTables;
this.DecodeScanProgressive(huffmanTables, isAc, isFirst, components, componentsLength, mcusPerLine, mcuToRead, ref mcu, stream);
this.DecodeScanProgressive(huffmanTables, isAc, isFirst, components, componentsLength, stream);
}
// Find marker
// Reset
// TODO: I do not understand why these values are reset? We should surely be tracking the bits across mcu's?
this.bitsCount = 0;
fileMarker = PdfJsJpegDecoderCore.FindNextFileMarker(this.markerBuffer, stream);
// Some bad images seem to pad Scan blocks with e.g. zero bytes, skip past
// those to attempt to find a valid marker (fixes issue4090.pdf) in original code.
if (fileMarker.Invalid)
{
#if DEBUG
Debug.WriteLine($"DecodeScan - Unexpected MCU data at {stream.Position}, next marker is: {fileMarker.Marker:X}");
#endif
}
this.bitsData = 0;
this.unexpectedMarkerReached = false;
ushort marker = fileMarker.Marker;
// Some images include more scan blocks than expected, skip past those and
// attempt to find the next valid marker
PdfJsFileMarker fileMarker = PdfJsJpegDecoderCore.FindNextFileMarker(this.markerBuffer, stream);
byte marker = fileMarker.Marker;
// RSTn - We've already read the bytes and altered the position so no need to skip
if (marker >= PdfJsJpegConstants.Markers.RST0 && marker <= PdfJsJpegConstants.Markers.RST7)
if (marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7)
{
continue;
}
@ -148,24 +149,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
stream.Position = fileMarker.Position;
break;
}
}
fileMarker = PdfJsJpegDecoderCore.FindNextFileMarker(this.markerBuffer, stream);
// Some images include more Scan blocks than expected, skip past those and
// attempt to find the next valid marker (fixes issue8182.pdf) ref original code.
if (fileMarker.Invalid)
{
#if DEBUG
Debug.WriteLine($"DecodeScan - Unexpected MCU data at {stream.Position}, next marker is: {fileMarker.Marker:X}");
#endif
}
else
{
// We've found a valid marker.
// Rewind the stream to the position of the marker
stream.Position = fileMarker.Position;
}
}
private void DecodeScanBaseline(
@ -173,10 +161,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
PdfJsHuffmanTables acHuffmanTables,
PdfJsFrameComponent[] components,
int componentsLength,
int mcusPerLine,
int mcuToRead,
ref int mcu,
Stream stream)
DoubleBufferedStreamReader stream)
{
if (componentsLength == 1)
{
@ -185,20 +170,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId];
ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId];
for (int n = 0; n < mcuToRead; n++)
for (int n = 0; n < this.mcuToRead; n++)
{
if (this.endOfStreamReached || this.unexpectedMarkerReached)
{
continue;
}
this.DecodeBlockBaseline(ref dcHuffmanTable, ref acHuffmanTable, component, ref blockDataRef, mcu, stream);
mcu++;
this.DecodeBlockBaseline(ref dcHuffmanTable, ref acHuffmanTable, component, ref blockDataRef, stream);
this.mcu++;
}
}
else
{
for (int n = 0; n < mcuToRead; n++)
for (int n = 0; n < this.mcuToRead; n++)
{
for (int i = 0; i < componentsLength; i++)
{
@ -218,12 +203,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
continue;
}
this.DecodeMcuBaseline(ref dcHuffmanTable, ref acHuffmanTable, component, ref blockDataRef, mcusPerLine, mcu, j, k, stream);
this.DecodeMcuBaseline(ref dcHuffmanTable, ref acHuffmanTable, component, ref blockDataRef, j, k, stream);
}
}
}
mcu++;
this.mcu++;
}
}
}
@ -234,10 +219,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
bool isFirst,
PdfJsFrameComponent[] components,
int componentsLength,
int mcusPerLine,
int mcuToRead,
ref int mcu,
Stream stream)
DoubleBufferedStreamReader stream)
{
if (componentsLength == 1)
{
@ -245,7 +227,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<Block8x8, short>(component.SpectralBlocks.Span));
ref PdfJsHuffmanTable huffmanTable = ref huffmanTables[isAC ? component.ACHuffmanTableId : component.DCHuffmanTableId];
for (int n = 0; n < mcuToRead; n++)
for (int n = 0; n < this.mcuToRead; n++)
{
if (this.endOfStreamReached || this.unexpectedMarkerReached)
{
@ -256,31 +238,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{
if (isFirst)
{
this.DecodeBlockACFirst(ref huffmanTable, component, ref blockDataRef, mcu, stream);
this.DecodeBlockACFirst(ref huffmanTable, component, ref blockDataRef, stream);
}
else
{
this.DecodeBlockACSuccessive(ref huffmanTable, component, ref blockDataRef, mcu, stream);
this.DecodeBlockACSuccessive(ref huffmanTable, component, ref blockDataRef, stream);
}
}
else
{
if (isFirst)
{
this.DecodeBlockDCFirst(ref huffmanTable, component, ref blockDataRef, mcu, stream);
this.DecodeBlockDCFirst(ref huffmanTable, component, ref blockDataRef, stream);
}
else
{
this.DecodeBlockDCSuccessive(component, ref blockDataRef, mcu, stream);
this.DecodeBlockDCSuccessive(component, ref blockDataRef, stream);
}
}
mcu++;
this.mcu++;
}
}
else
{
for (int n = 0; n < mcuToRead; n++)
for (int n = 0; n < this.mcuToRead; n++)
{
for (int i = 0; i < componentsLength; i++)
{
@ -294,56 +276,57 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{
for (int k = 0; k < h; k++)
{
// No need to continue here.
if (this.endOfStreamReached || this.unexpectedMarkerReached)
{
continue;
break;
}
if (isAC)
{
if (isFirst)
{
this.DecodeMcuACFirst(ref huffmanTable, component, ref blockDataRef, mcusPerLine, mcu, j, k, stream);
this.DecodeMcuACFirst(ref huffmanTable, component, ref blockDataRef, j, k, stream);
}
else
{
this.DecodeMcuACSuccessive(ref huffmanTable, component, ref blockDataRef, mcusPerLine, mcu, j, k, stream);
this.DecodeMcuACSuccessive(ref huffmanTable, component, ref blockDataRef, j, k, stream);
}
}
else
{
if (isFirst)
{
this.DecodeMcuDCFirst(ref huffmanTable, component, ref blockDataRef, mcusPerLine, mcu, j, k, stream);
this.DecodeMcuDCFirst(ref huffmanTable, component, ref blockDataRef, j, k, stream);
}
else
{
this.DecodeMcuDCSuccessive(component, ref blockDataRef, mcusPerLine, mcu, j, k, stream);
this.DecodeMcuDCSuccessive(component, ref blockDataRef, j, k, stream);
}
}
}
}
}
mcu++;
this.mcu++;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DecodeBlockBaseline(ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcu, Stream stream)
private void DecodeBlockBaseline(ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, DoubleBufferedStreamReader stream)
{
int blockRow = mcu / component.WidthInBlocks;
int blockCol = mcu % component.WidthInBlocks;
int blockRow = this.mcu / component.WidthInBlocks;
int blockCol = this.mcu % component.WidthInBlocks;
int offset = component.GetBlockBufferOffset(blockRow, blockCol);
this.DecodeBaseline(component, ref blockDataRef, offset, ref dcHuffmanTable, ref acHuffmanTable, stream);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DecodeMcuBaseline(ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcusPerLine, int mcu, int row, int col, Stream stream)
private void DecodeMcuBaseline(ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int row, int col, DoubleBufferedStreamReader stream)
{
int mcuRow = mcu / mcusPerLine;
int mcuCol = mcu % mcusPerLine;
int mcuRow = this.mcu / this.mcusPerLine;
int mcuCol = this.mcu % this.mcusPerLine;
int blockRow = (mcuRow * component.VerticalSamplingFactor) + row;
int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col;
int offset = component.GetBlockBufferOffset(blockRow, blockCol);
@ -351,19 +334,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DecodeBlockDCFirst(ref PdfJsHuffmanTable dcHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcu, Stream stream)
private void DecodeBlockDCFirst(ref PdfJsHuffmanTable dcHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, DoubleBufferedStreamReader stream)
{
int blockRow = mcu / component.WidthInBlocks;
int blockCol = mcu % component.WidthInBlocks;
int blockRow = this.mcu / component.WidthInBlocks;
int blockCol = this.mcu % component.WidthInBlocks;
int offset = component.GetBlockBufferOffset(blockRow, blockCol);
this.DecodeDCFirst(component, ref blockDataRef, offset, ref dcHuffmanTable, stream);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DecodeMcuDCFirst(ref PdfJsHuffmanTable dcHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcusPerLine, int mcu, int row, int col, Stream stream)
private void DecodeMcuDCFirst(ref PdfJsHuffmanTable dcHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int row, int col, DoubleBufferedStreamReader stream)
{
int mcuRow = mcu / mcusPerLine;
int mcuCol = mcu % mcusPerLine;
int mcuRow = this.mcu / this.mcusPerLine;
int mcuCol = this.mcu % this.mcusPerLine;
int blockRow = (mcuRow * component.VerticalSamplingFactor) + row;
int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col;
int offset = component.GetBlockBufferOffset(blockRow, blockCol);
@ -371,19 +354,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DecodeBlockDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int mcu, Stream stream)
private void DecodeBlockDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, DoubleBufferedStreamReader stream)
{
int blockRow = mcu / component.WidthInBlocks;
int blockCol = mcu % component.WidthInBlocks;
int blockRow = this.mcu / component.WidthInBlocks;
int blockCol = this.mcu % component.WidthInBlocks;
int offset = component.GetBlockBufferOffset(blockRow, blockCol);
this.DecodeDCSuccessive(component, ref blockDataRef, offset, stream);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DecodeMcuDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int mcusPerLine, int mcu, int row, int col, Stream stream)
private void DecodeMcuDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int row, int col, DoubleBufferedStreamReader stream)
{
int mcuRow = mcu / mcusPerLine;
int mcuCol = mcu % mcusPerLine;
int mcuRow = this.mcu / this.mcusPerLine;
int mcuCol = this.mcu % this.mcusPerLine;
int blockRow = (mcuRow * component.VerticalSamplingFactor) + row;
int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col;
int offset = component.GetBlockBufferOffset(blockRow, blockCol);
@ -391,169 +374,257 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DecodeBlockACFirst(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcu, Stream stream)
private void DecodeBlockACFirst(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, DoubleBufferedStreamReader stream)
{
int blockRow = mcu / component.WidthInBlocks;
int blockCol = mcu % component.WidthInBlocks;
int blockRow = this.mcu / component.WidthInBlocks;
int blockCol = this.mcu % component.WidthInBlocks;
int offset = component.GetBlockBufferOffset(blockRow, blockCol);
this.DecodeACFirst(component, ref blockDataRef, offset, ref acHuffmanTable, stream);
this.DecodeACFirst(ref blockDataRef, offset, ref acHuffmanTable, stream);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DecodeMcuACFirst(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcusPerLine, int mcu, int row, int col, Stream stream)
private void DecodeMcuACFirst(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int row, int col, DoubleBufferedStreamReader stream)
{
int mcuRow = mcu / mcusPerLine;
int mcuCol = mcu % mcusPerLine;
int mcuRow = this.mcu / this.mcusPerLine;
int mcuCol = this.mcu % this.mcusPerLine;
int blockRow = (mcuRow * component.VerticalSamplingFactor) + row;
int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col;
int offset = component.GetBlockBufferOffset(blockRow, blockCol);
this.DecodeACFirst(component, ref blockDataRef, offset, ref acHuffmanTable, stream);
this.DecodeACFirst(ref blockDataRef, offset, ref acHuffmanTable, stream);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DecodeBlockACSuccessive(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcu, Stream stream)
private void DecodeBlockACSuccessive(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, DoubleBufferedStreamReader stream)
{
int blockRow = mcu / component.WidthInBlocks;
int blockCol = mcu % component.WidthInBlocks;
int blockRow = this.mcu / component.WidthInBlocks;
int blockCol = this.mcu % component.WidthInBlocks;
int offset = component.GetBlockBufferOffset(blockRow, blockCol);
this.DecodeACSuccessive(component, ref blockDataRef, offset, ref acHuffmanTable, stream);
this.DecodeACSuccessive(ref blockDataRef, offset, ref acHuffmanTable, stream);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DecodeMcuACSuccessive(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int mcusPerLine, int mcu, int row, int col, Stream stream)
private void DecodeMcuACSuccessive(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int row, int col, DoubleBufferedStreamReader stream)
{
int mcuRow = mcu / mcusPerLine;
int mcuCol = mcu % mcusPerLine;
int mcuRow = this.mcu / this.mcusPerLine;
int mcuCol = this.mcu % this.mcusPerLine;
int blockRow = (mcuRow * component.VerticalSamplingFactor) + row;
int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col;
int offset = component.GetBlockBufferOffset(blockRow, blockCol);
this.DecodeACSuccessive(component, ref blockDataRef, offset, ref acHuffmanTable, stream);
this.DecodeACSuccessive(ref blockDataRef, offset, ref acHuffmanTable, stream);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int ReadBit(Stream stream)
private bool TryReadBit(DoubleBufferedStreamReader stream, out int bit)
{
// TODO: I wonder if we can do this two bytes at a time; libjpeg turbo seems to do that?
if (this.bitsCount > 0)
if (this.bitsCount == 0)
{
this.bitsCount--;
return (this.bitsData >> this.bitsCount) & 1;
if (!this.TryFillBits(stream))
{
bit = 0;
return false;
}
}
this.bitsData = stream.ReadByte();
this.bitsCount--;
bit = (this.bitsData >> this.bitsCount) & 1;
return true;
}
if (this.bitsData == -0x1)
{
// We've encountered the end of the file stream which means there's no EOI marker ref the image
this.endOfStreamReached = true;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private bool TryFillBits(DoubleBufferedStreamReader stream)
{
// TODO: Read more then 1 byte at a time.
// In LibJpegTurbo this is be 25 bits (32-7) but I cannot get this to work
// for some images, I'm assuming because I am crossing MCU boundaries and not maintining the correct buffer state.
const int MinGetBits = 7;
if (this.bitsData == PdfJsJpegConstants.Markers.Prefix)
if (!this.unexpectedMarkerReached)
{
int nextByte = stream.ReadByte();
if (nextByte != 0)
// Attempt to load to the minimum bit count.
while (this.bitsCount < MinGetBits)
{
int c = stream.ReadByte();
switch (c)
{
case -0x1:
// We've encountered the end of the file stream which means there's no EOI marker in the image.
this.endOfStreamReached = true;
return false;
case JpegConstants.Markers.XFF:
int nextByte = stream.ReadByte();
if (nextByte == -0x1)
{
this.endOfStreamReached = true;
return false;
}
if (nextByte != 0)
{
#if DEBUG
Debug.WriteLine($"DecodeScan - Unexpected marker {(this.bitsData << 8) | nextByte:X} at {stream.Position}");
Debug.WriteLine($"DecodeScan - Unexpected marker {(c << 8) | nextByte:X} at {stream.Position}");
#endif
// We've encountered an unexpected marker. Reverse the stream and exit.
this.unexpectedMarkerReached = true;
stream.Position -= 2;
}
// We've encountered an unexpected marker. Reverse the stream and exit.
this.unexpectedMarkerReached = true;
stream.Position -= 2;
// TODO: double check we need this.
// Fill buffer with zero bits.
if (this.bitsCount == 0)
{
this.bitsData <<= MinGetBits;
this.bitsCount = MinGetBits;
}
return true;
}
break;
}
// Unstuff 0
// OK, load the next byte into bitsData
this.bitsData = (this.bitsData << 8) | c;
this.bitsCount += 8;
}
}
this.bitsCount = 7;
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int PeekBits(int count)
{
return this.bitsData >> (this.bitsCount - count) & ((1 << count) - 1);
}
return this.bitsData >> 7;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DropBits(int count)
{
this.bitsCount -= count;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private short DecodeHuffman(ref PdfJsHuffmanTable tree, Stream stream)
private bool TryDecodeHuffman(ref PdfJsHuffmanTable tree, DoubleBufferedStreamReader stream, out short value)
{
value = -1;
// TODO: Implement fast Huffman decoding.
// NOTES # During investigation of the libjpeg implementation it appears that they pull 32bits at a time and operate on those bits
// using 3 methods: FillBits, PeekBits, and ReadBits. We should attempt to do the same.
short code = (short)this.ReadBit(stream);
if (this.endOfStreamReached || this.unexpectedMarkerReached)
// In LibJpegTurbo a minimum of 25 bits (32-7) is collected from the stream
// Then a LUT is used to avoid the loop when decoding the Huffman value.
// using 3 methods: FillBits, PeekBits, and DropBits.
// The LUT has been ported from LibJpegTurbo as has this code but it doesn't work.
// this.TryFillBits(stream);
//
// const int LookAhead = 8;
// int look = this.PeekBits(LookAhead);
// look = tree.Lookahead[look];
// int bits = look >> LookAhead;
//
// if (bits <= LookAhead)
// {
// this.DropBits(bits);
// value = (short)(look & ((1 << LookAhead) - 1));
// return true;
// }
if (!this.TryReadBit(stream, out int bit))
{
return -1;
return false;
}
short code = (short)bit;
// "DECODE", section F.2.2.3, figure F.16, page 109 of T.81
int i = 1;
while (code > tree.MaxCode[i])
{
code <<= 1;
code |= (short)this.ReadBit(stream);
if (this.endOfStreamReached || this.unexpectedMarkerReached)
if (!this.TryReadBit(stream, out bit))
{
return -1;
return false;
}
code <<= 1;
code |= (short)bit;
i++;
}
int j = tree.ValOffset[i];
return tree.HuffVal[(j + code) & 0xFF];
value = tree.HuffVal[(j + code) & 0xFF];
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int Receive(int length, Stream stream)
private bool TryReceive(int length, DoubleBufferedStreamReader stream, out int value)
{
int n = 0;
value = 0;
while (length > 0)
{
int bit = this.ReadBit(stream);
if (this.endOfStreamReached || this.unexpectedMarkerReached)
if (!this.TryReadBit(stream, out int bit))
{
return -1;
return false;
}
n = (n << 1) | bit;
value = (value << 1) | bit;
length--;
}
return n;
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int ReceiveAndExtend(int length, Stream stream)
private bool TryReceiveAndExtend(int length, DoubleBufferedStreamReader stream, out int value)
{
if (length == 1)
{
return this.ReadBit(stream) == 1 ? 1 : -1;
}
if (!this.TryReadBit(stream, out value))
{
return false;
}
int n = this.Receive(length, stream);
if (n >= 1 << (length - 1))
value = value == 1 ? 1 : -1;
}
else
{
return n;
if (!this.TryReceive(length, stream, out value))
{
return false;
}
if (value < 1 << (length - 1))
{
value += (-1 << length) + 1;
}
}
return n + (-1 << length) + 1;
return true;
}
private void DecodeBaseline(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, Stream stream)
private void DecodeBaseline(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, DoubleBufferedStreamReader stream)
{
short t = this.DecodeHuffman(ref dcHuffmanTable, stream);
if (this.endOfStreamReached || this.unexpectedMarkerReached)
if (!this.TryDecodeHuffman(ref dcHuffmanTable, stream, out short t))
{
return;
}
int diff = t == 0 ? 0 : this.ReceiveAndExtend(t, stream);
Unsafe.Add(ref blockDataRef, offset) = (short)(component.Pred += diff);
int diff = 0;
if (t != 0)
{
if (!this.TryReceiveAndExtend(t, stream, out diff))
{
return;
}
}
Unsafe.Add(ref blockDataRef, offset) = (short)(component.DcPredictor += diff);
int k = 1;
while (k < 64)
{
short rs = this.DecodeHuffman(ref acHuffmanTable, stream);
if (this.endOfStreamReached || this.unexpectedMarkerReached)
if (!this.TryDecodeHuffman(ref acHuffmanTable, stream, out short rs))
{
return;
}
@ -574,36 +645,42 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
k += r;
if (k > 63)
byte z = this.dctZigZag[k];
if (!this.TryReceiveAndExtend(s, stream, out int re))
{
break;
return;
}
byte z = this.dctZigZag[k];
short re = (short)this.ReceiveAndExtend(s, stream);
Unsafe.Add(ref blockDataRef, offset + z) = re;
Unsafe.Add(ref blockDataRef, offset + z) = (short)re;
k++;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DecodeDCFirst(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable dcHuffmanTable, Stream stream)
private void DecodeDCFirst(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable dcHuffmanTable, DoubleBufferedStreamReader stream)
{
short t = this.DecodeHuffman(ref dcHuffmanTable, stream);
if (this.endOfStreamReached || this.unexpectedMarkerReached)
if (!this.TryDecodeHuffman(ref dcHuffmanTable, stream, out short t))
{
return;
}
int diff = t == 0 ? 0 : this.ReceiveAndExtend(t, stream) << this.successiveState;
Unsafe.Add(ref blockDataRef, offset) = (short)(component.Pred += diff);
int diff = 0;
if (t != 0)
{
if (!this.TryReceiveAndExtend(t, stream, out diff))
{
return;
}
}
Unsafe.Add(ref blockDataRef, offset) = (short)(component.DcPredictor += diff << this.successiveState);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DecodeDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int offset, Stream stream)
private void DecodeDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int offset, DoubleBufferedStreamReader stream)
{
int bit = this.ReadBit(stream);
if (this.endOfStreamReached || this.unexpectedMarkerReached)
if (!this.TryReadBit(stream, out int bit))
{
return;
}
@ -611,7 +688,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
Unsafe.Add(ref blockDataRef, offset) |= (short)(bit << this.successiveState);
}
private void DecodeACFirst(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable acHuffmanTable, Stream stream)
private void DecodeACFirst(ref short blockDataRef, int offset, ref PdfJsHuffmanTable acHuffmanTable, DoubleBufferedStreamReader stream)
{
if (this.eobrun > 0)
{
@ -623,8 +700,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
int e = this.specEnd;
while (k <= e)
{
short rs = this.DecodeHuffman(ref acHuffmanTable, stream);
if (this.endOfStreamReached || this.unexpectedMarkerReached)
if (!this.TryDecodeHuffman(ref acHuffmanTable, stream, out short rs))
{
return;
}
@ -636,7 +712,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{
if (r < 15)
{
this.eobrun = this.Receive(r, stream) + (1 << r) - 1;
if (!this.TryReceive(r, stream, out int eob))
{
return;
}
this.eobrun = eob + (1 << r) - 1;
break;
}
@ -647,12 +728,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
k += r;
byte z = this.dctZigZag[k];
Unsafe.Add(ref blockDataRef, offset + z) = (short)(this.ReceiveAndExtend(s, stream) * (1 << this.successiveState));
if (!this.TryReceiveAndExtend(s, stream, out int v))
{
return;
}
Unsafe.Add(ref blockDataRef, offset + z) = (short)(v * (1 << this.successiveState));
k++;
}
}
private void DecodeACSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable acHuffmanTable, Stream stream)
private void DecodeACSuccessive(ref short blockDataRef, int offset, ref PdfJsHuffmanTable acHuffmanTable, DoubleBufferedStreamReader stream)
{
int k = this.specStart;
int e = this.specEnd;
@ -667,8 +754,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
switch (this.successiveACState)
{
case 0: // Initial state
short rs = this.DecodeHuffman(ref acHuffmanTable, stream);
if (this.endOfStreamReached || this.unexpectedMarkerReached)
if (!this.TryDecodeHuffman(ref acHuffmanTable, stream, out short rs))
{
return;
}
@ -679,7 +766,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{
if (r < 15)
{
this.eobrun = this.Receive(r, stream) + (1 << r);
if (!this.TryReceive(r, stream, out int eob))
{
return;
}
this.eobrun = eob + (1 << r);
this.successiveACState = 4;
}
else
@ -695,7 +787,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
throw new ImageFormatException("Invalid ACn encoding");
}
this.successiveACNextValue = this.ReceiveAndExtend(s, stream);
if (!this.TryReceiveAndExtend(s, stream, out int v))
{
return;
}
this.successiveACNextValue = v;
this.successiveACState = r > 0 ? 2 : 3;
}
@ -704,8 +801,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
case 2:
if (blockOffsetZRef != 0)
{
int bit = this.ReadBit(stream);
if (this.endOfStreamReached || this.unexpectedMarkerReached)
if (!this.TryReadBit(stream, out int bit))
{
return;
}
@ -725,8 +821,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
case 3: // Set value for a zero item
if (blockOffsetZRef != 0)
{
int bit = this.ReadBit(stream);
if (this.endOfStreamReached || this.unexpectedMarkerReached)
if (!this.TryReadBit(stream, out int bit))
{
return;
}
@ -743,8 +838,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
case 4: // Eob
if (blockOffsetZRef != 0)
{
int bit = this.ReadBit(stream);
if (this.endOfStreamReached || this.unexpectedMarkerReached)
if (!this.TryReadBit(stream, out int bit))
{
return;
}

329
src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs

@ -7,8 +7,9 @@ using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData;
@ -22,7 +23,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
{
/// <summary>
/// Performs the jpeg decoding operation.
/// Ported from <see href="https://github.com/mozilla/pdf.js/blob/master/src/core/jpg.js"/> with additional fixes to handle common encoding errors
/// Originally ported from <see href="https://github.com/mozilla/pdf.js/blob/master/src/core/jpg.js"/>
/// with additional fixes for both performance and common encoding errors.
/// </summary>
internal sealed class PdfJsJpegDecoderCore : IRawJpegData
{
@ -31,7 +33,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// </summary>
public const int SupportedPrecision = 8;
#pragma warning disable SA1401 // Fields should be private
/// <summary>
/// The global configuration
/// </summary>
@ -93,15 +94,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// </summary>
public PdfJsFrame Frame { get; private set; }
/// <inheritdoc/>
public Size ImageSizeInPixels { get; private set; }
/// <summary>
/// Gets the number of MCU blocks in the image as <see cref="Size"/>.
/// </summary>
public Size ImageSizeInMCU { get; private set; }
/// <summary>
/// Gets the image width
/// </summary>
public int ImageWidth { get; private set; }
public int ImageWidth => this.ImageSizeInPixels.Width;
/// <summary>
/// Gets the image height
/// </summary>
public int ImageHeight { get; private set; }
public int ImageHeight => this.ImageSizeInPixels.Height;
/// <summary>
/// Gets the color depth, in number of bits per pixel.
@ -111,7 +120,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// <summary>
/// Gets the input stream.
/// </summary>
public Stream InputStream { get; private set; }
public DoubleBufferedStreamReader InputStream { get; private set; }
/// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
@ -123,17 +132,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// </summary>
public ImageMetaData MetaData { get; private set; }
/// <inheritdoc/>
public Size ImageSizeInPixels => new Size(this.ImageWidth, this.ImageHeight);
/// <inheritdoc/>
public int ComponentCount { get; private set; }
/// <inheritdoc/>
public JpegColorSpace ColorSpace { get; private set; }
/// <summary>
/// Gets the components.
/// </summary>
public PdfJsFrameComponent[] Components => this.Frame.Components;
/// <inheritdoc/>
public IEnumerable<IJpegComponent> Components => this.Frame.Components;
IEnumerable<IJpegComponent> IRawJpegData.Components => this.Components;
/// <inheritdoc/>
public Block8x8F[] QuantizationTables { get; private set; }
@ -144,34 +155,35 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// <param name="marker">The buffer to read file markers to</param>
/// <param name="stream">The input stream</param>
/// <returns>The <see cref="PdfJsFileMarker"/></returns>
public static PdfJsFileMarker FindNextFileMarker(byte[] marker, Stream stream)
public static PdfJsFileMarker FindNextFileMarker(byte[] marker, DoubleBufferedStreamReader stream)
{
int value = stream.Read(marker, 0, 2);
if (value == 0)
{
return new PdfJsFileMarker(PdfJsJpegConstants.Markers.EOI, stream.Length - 2);
return new PdfJsFileMarker(JpegConstants.Markers.EOI, stream.Length - 2);
}
if (marker[0] == PdfJsJpegConstants.Markers.Prefix)
if (marker[0] == JpegConstants.Markers.XFF)
{
// According to Section B.1.1.2:
// "Any marker may optionally be preceded by any number of fill bytes, which are bytes assigned code 0xFF."
while (marker[1] == PdfJsJpegConstants.Markers.Prefix)
int m = marker[1];
while (m == JpegConstants.Markers.XFF)
{
int suffix = stream.ReadByte();
if (suffix == -1)
{
return new PdfJsFileMarker(PdfJsJpegConstants.Markers.EOI, stream.Length - 2);
return new PdfJsFileMarker(JpegConstants.Markers.EOI, stream.Length - 2);
}
marker[1] = (byte)suffix;
m = suffix;
}
return new PdfJsFileMarker(BinaryPrimitives.ReadUInt16BigEndian(marker), stream.Position - 2);
return new PdfJsFileMarker((byte)m, stream.Position - 2);
}
return new PdfJsFileMarker(BinaryPrimitives.ReadUInt16BigEndian(marker), stream.Position - 2, true);
return new PdfJsFileMarker(marker[1], stream.Position - 2, true);
}
/// <summary>
@ -184,6 +196,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
where TPixel : struct, IPixel<TPixel>
{
this.ParseStream(stream);
this.AssignResolution();
return this.PostProcessIntoImage<TPixel>();
}
@ -206,136 +219,142 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
public void ParseStream(Stream stream, bool metadataOnly = false)
{
this.MetaData = new ImageMetaData();
this.InputStream = stream;
this.InputStream = new DoubleBufferedStreamReader(this.configuration.MemoryManager, stream);
// Check for the Start Of Image marker.
var fileMarker = new PdfJsFileMarker(this.ReadUint16(), 0);
if (fileMarker.Marker != PdfJsJpegConstants.Markers.SOI)
this.InputStream.Read(this.markerBuffer, 0, 2);
var fileMarker = new PdfJsFileMarker(this.markerBuffer[1], 0);
if (fileMarker.Marker != JpegConstants.Markers.SOI)
{
throw new ImageFormatException("Missing SOI marker.");
}
ushort marker = this.ReadUint16();
this.InputStream.Read(this.markerBuffer, 0, 2);
byte marker = this.markerBuffer[1];
fileMarker = new PdfJsFileMarker(marker, (int)this.InputStream.Position - 2);
this.QuantizationTables = new Block8x8F[4];
// this.quantizationTables = new PdfJsQuantizationTables(this.configuration.MemoryManager);
this.dcHuffmanTables = new PdfJsHuffmanTables();
this.acHuffmanTables = new PdfJsHuffmanTables();
while (fileMarker.Marker != PdfJsJpegConstants.Markers.EOI)
// Only assign what we need
if (!metadataOnly)
{
// Get the marker length
int remaining = this.ReadUint16() - 2;
this.QuantizationTables = new Block8x8F[4];
this.dcHuffmanTables = new PdfJsHuffmanTables();
this.acHuffmanTables = new PdfJsHuffmanTables();
}
switch (fileMarker.Marker)
while (fileMarker.Marker != JpegConstants.Markers.EOI)
{
if (!fileMarker.Invalid)
{
case PdfJsJpegConstants.Markers.APP0:
this.ProcessApplicationHeaderMarker(remaining);
break;
case PdfJsJpegConstants.Markers.APP1:
this.ProcessApp1Marker(remaining);
break;
case PdfJsJpegConstants.Markers.APP2:
this.ProcessApp2Marker(remaining);
break;
case PdfJsJpegConstants.Markers.APP3:
case PdfJsJpegConstants.Markers.APP4:
case PdfJsJpegConstants.Markers.APP5:
case PdfJsJpegConstants.Markers.APP6:
case PdfJsJpegConstants.Markers.APP7:
case PdfJsJpegConstants.Markers.APP8:
case PdfJsJpegConstants.Markers.APP9:
case PdfJsJpegConstants.Markers.APP10:
case PdfJsJpegConstants.Markers.APP11:
case PdfJsJpegConstants.Markers.APP12:
case PdfJsJpegConstants.Markers.APP13:
this.InputStream.Skip(remaining);
break;
// Get the marker length
int remaining = this.ReadUint16() - 2;
case PdfJsJpegConstants.Markers.APP14:
this.ProcessApp14Marker(remaining);
break;
switch (fileMarker.Marker)
{
case JpegConstants.Markers.SOF0:
case JpegConstants.Markers.SOF1:
case JpegConstants.Markers.SOF2:
this.ProcessStartOfFrameMarker(remaining, fileMarker, metadataOnly);
break;
case JpegConstants.Markers.SOS:
if (!metadataOnly)
{
this.ProcessStartOfScanMarker();
break;
}
else
{
// It's highly unlikely that APPn related data will be found after the SOS marker
// We should have gathered everything we need by now.
return;
}
case PdfJsJpegConstants.Markers.APP15:
case PdfJsJpegConstants.Markers.COM:
this.InputStream.Skip(remaining);
break;
case JpegConstants.Markers.DHT:
if (metadataOnly)
{
this.InputStream.Skip(remaining);
}
else
{
this.ProcessDefineHuffmanTablesMarker(remaining);
}
case PdfJsJpegConstants.Markers.DQT:
if (metadataOnly)
{
this.InputStream.Skip(remaining);
}
else
{
this.ProcessDefineQuantizationTablesMarker(remaining);
}
break;
break;
case JpegConstants.Markers.DQT:
if (metadataOnly)
{
this.InputStream.Skip(remaining);
}
else
{
this.ProcessDefineQuantizationTablesMarker(remaining);
}
case PdfJsJpegConstants.Markers.SOF0:
case PdfJsJpegConstants.Markers.SOF1:
case PdfJsJpegConstants.Markers.SOF2:
this.ProcessStartOfFrameMarker(remaining, fileMarker);
if (metadataOnly && !this.jFif.Equals(default))
{
this.InputStream.Skip(remaining);
}
break;
break;
case JpegConstants.Markers.DRI:
if (metadataOnly)
{
this.InputStream.Skip(remaining);
}
else
{
this.ProcessDefineRestartIntervalMarker(remaining);
}
case PdfJsJpegConstants.Markers.DHT:
if (metadataOnly)
{
break;
case JpegConstants.Markers.APP0:
this.ProcessApplicationHeaderMarker(remaining);
break;
case JpegConstants.Markers.APP1:
this.ProcessApp1Marker(remaining);
break;
case JpegConstants.Markers.APP2:
this.ProcessApp2Marker(remaining);
break;
case JpegConstants.Markers.APP3:
case JpegConstants.Markers.APP4:
case JpegConstants.Markers.APP5:
case JpegConstants.Markers.APP6:
case JpegConstants.Markers.APP7:
case JpegConstants.Markers.APP8:
case JpegConstants.Markers.APP9:
case JpegConstants.Markers.APP10:
case JpegConstants.Markers.APP11:
case JpegConstants.Markers.APP12:
case JpegConstants.Markers.APP13:
this.InputStream.Skip(remaining);
}
else
{
this.ProcessDefineHuffmanTablesMarker(remaining);
}
break;
break;
case JpegConstants.Markers.APP14:
this.ProcessApp14Marker(remaining);
break;
case PdfJsJpegConstants.Markers.DRI:
if (metadataOnly)
{
case JpegConstants.Markers.APP15:
case JpegConstants.Markers.COM:
this.InputStream.Skip(remaining);
}
else
{
this.ProcessDefineRestartIntervalMarker(remaining);
}
break;
case PdfJsJpegConstants.Markers.SOS:
if (!metadataOnly)
{
this.ProcessStartOfScanMarker();
}
break;
break;
}
}
// Read on.
fileMarker = FindNextFileMarker(this.markerBuffer, this.InputStream);
}
this.ImageWidth = this.Frame.SamplesPerLine;
this.ImageHeight = this.Frame.Scanlines;
this.ComponentCount = this.Frame.ComponentCount;
}
/// <inheritdoc/>
public void Dispose()
{
this.InputStream?.Dispose();
this.Frame?.Dispose();
// Set large fields to null.
this.InputStream = null;
this.Frame = null;
this.dcHuffmanTables = null;
this.acHuffmanTables = null;
@ -354,11 +373,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
if (this.ComponentCount == 3)
{
if (this.adobe.Equals(default) || this.adobe.ColorTransform == PdfJsJpegConstants.Markers.Adobe.ColorTransformYCbCr)
if (this.adobe.Equals(default) || this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYCbCr)
{
return JpegColorSpace.YCbCr;
}
else if (this.adobe.ColorTransform == PdfJsJpegConstants.Markers.Adobe.ColorTransformUnknown)
if (this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown)
{
return JpegColorSpace.RGB;
}
@ -366,7 +386,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
if (this.ComponentCount == 4)
{
return this.adobe.ColorTransform == PdfJsJpegConstants.Markers.Adobe.ColorTransformYcck
return this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYcck
? JpegColorSpace.Ycck
: JpegColorSpace.Cmyk;
}
@ -379,7 +399,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// </summary>
private void AssignResolution()
{
if (this.isExif)
if (this.jFif.XDensity > 0 && this.jFif.YDensity > 0)
{
this.MetaData.HorizontalResolution = this.jFif.XDensity;
this.MetaData.VerticalResolution = this.jFif.YDensity;
}
else if (this.isExif)
{
double horizontalValue = this.MetaData.ExifProfile.TryGetValue(ExifTag.XResolution, out ExifValue horizontalTag)
? ((Rational)horizontalTag.Value).ToDouble()
@ -395,11 +420,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
this.MetaData.VerticalResolution = verticalValue;
}
}
else if (this.jFif.XDensity > 0 && this.jFif.YDensity > 0)
{
this.MetaData.HorizontalResolution = this.jFif.XDensity;
this.MetaData.VerticalResolution = this.jFif.YDensity;
}
}
/// <summary>
@ -593,7 +613,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
/// <param name="frameMarker">The current frame marker.</param>
private void ProcessStartOfFrameMarker(int remaining, PdfJsFileMarker frameMarker)
/// <param name="metadataOnly">Whether to parse metadata only</param>
private void ProcessStartOfFrameMarker(int remaining, PdfJsFileMarker frameMarker, bool metadataOnly)
{
if (this.Frame != null)
{
@ -610,49 +631,59 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
this.Frame = new PdfJsFrame
{
Extended = frameMarker.Marker == PdfJsJpegConstants.Markers.SOF1,
Progressive = frameMarker.Marker == PdfJsJpegConstants.Markers.SOF2,
Extended = frameMarker.Marker == JpegConstants.Markers.SOF1,
Progressive = frameMarker.Marker == JpegConstants.Markers.SOF2,
Precision = this.temp[0],
Scanlines = (short)((this.temp[1] << 8) | this.temp[2]),
SamplesPerLine = (short)((this.temp[3] << 8) | this.temp[4]),
ComponentCount = this.temp[5]
};
this.ImageSizeInPixels = new Size(this.Frame.SamplesPerLine, this.Frame.Scanlines);
int maxH = 0;
int maxV = 0;
int index = 6;
// No need to pool this. They max out at 4
this.Frame.ComponentIds = new byte[this.Frame.ComponentCount];
this.Frame.Components = new PdfJsFrameComponent[this.Frame.ComponentCount];
this.ComponentCount = this.Frame.ComponentCount;
for (int i = 0; i < this.Frame.Components.Length; i++)
if (!metadataOnly)
{
byte hv = this.temp[index + 1];
int h = hv >> 4;
int v = hv & 15;
// No need to pool this. They max out at 4
this.Frame.ComponentIds = new byte[this.Frame.ComponentCount];
this.Frame.Components = new PdfJsFrameComponent[this.Frame.ComponentCount];
this.ColorSpace = this.DeduceJpegColorSpace();
if (maxH < h)
for (int i = 0; i < this.Frame.ComponentCount; i++)
{
maxH = h;
}
byte hv = this.temp[index + 1];
int h = hv >> 4;
int v = hv & 15;
if (maxV < v)
{
maxV = v;
}
if (maxH < h)
{
maxH = h;
}
if (maxV < v)
{
maxV = v;
}
var component = new PdfJsFrameComponent(this.configuration.MemoryManager, this.Frame, this.temp[index], h, v, this.temp[index + 2], i);
var component = new PdfJsFrameComponent(this.configuration.MemoryManager, this.Frame, this.temp[index], h, v, this.temp[index + 2], i);
this.Frame.Components[i] = component;
this.Frame.ComponentIds[i] = component.Id;
this.Frame.Components[i] = component;
this.Frame.ComponentIds[i] = component.Id;
index += 3;
}
index += 3;
}
this.Frame.MaxHorizontalFactor = maxH;
this.Frame.MaxVerticalFactor = maxV;
this.Frame.InitComponents();
this.Frame.MaxHorizontalFactor = maxH;
this.Frame.MaxVerticalFactor = maxV;
this.ColorSpace = this.DeduceJpegColorSpace();
this.Frame.InitComponents();
this.ImageSizeInMCU = new Size(this.Frame.McusPerLine, this.Frame.McusPerColumn);
}
}
/// <summary>
@ -798,8 +829,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
private Image<TPixel> PostProcessIntoImage<TPixel>()
where TPixel : struct, IPixel<TPixel>
{
this.ColorSpace = this.DeduceJpegColorSpace();
this.AssignResolution();
using (var postProcessor = new JpegImagePostProcessor(this.configuration.MemoryManager, this))
{
var image = new Image<TPixel>(this.configuration, this.ImageWidth, this.ImageHeight, this.MetaData);

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

@ -3,7 +3,6 @@
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Png.Filters
{
@ -20,12 +19,12 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
/// <param name="scanline">The scanline to encode</param>
/// <param name="result">The filtered scanline result.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(Span<byte> scanline, Span<byte> result)
public static void Encode(ReadOnlySpan<byte> scanline, Span<byte> result)
{
// Insert a byte before the data.
result[0] = 0;
result = result.Slice(1);
SpanHelper.Copy(scanline, result);
scanline.Slice(0, Math.Min(scanline.Length, result.Length)).CopyTo(result);
}
}
}
}

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

@ -853,7 +853,7 @@ namespace SixLabors.ImageSharp.Formats.Png
where TPixel : struct, IPixel<TPixel>
{
ReadOnlySpan<byte> newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth);
byte[] pal = this.palette;
ReadOnlySpan<Rgb24> pal = MemoryMarshal.Cast<byte, Rgb24>(this.palette);
var color = default(TPixel);
var rgba = default(Rgba32);
@ -865,10 +865,9 @@ namespace SixLabors.ImageSharp.Formats.Png
for (int x = 0; x < this.header.Width; x++)
{
int index = newScanline[x];
int pixelOffset = index * 3;
rgba.A = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : (byte)255;
rgba.Rgb = pal.GetRgb24(pixelOffset);
rgba.Rgb = pal[index];
color.PackFromRgba32(rgba);
row[x] = color;
@ -881,9 +880,8 @@ namespace SixLabors.ImageSharp.Formats.Png
for (int x = 0; x < this.header.Width; x++)
{
int index = newScanline[x];
int pixelOffset = index * 3;
rgba.Rgb = pal.GetRgb24(pixelOffset);
rgba.Rgb = pal[index];
color.PackFromRgba32(rgba);
row[x] = color;
@ -946,6 +944,7 @@ namespace SixLabors.ImageSharp.Formats.Png
ReadOnlySpan<byte> newScanline = ToArrayByBitsLength(scanlineBuffer, this.bytesPerScanline, this.header.BitDepth);
var rgba = default(Rgba32);
Span<Rgb24> pal = MemoryMarshal.Cast<byte, Rgb24>(this.palette);
if (this.paletteAlpha != null && this.paletteAlpha.Length > 0)
{
@ -954,10 +953,9 @@ namespace SixLabors.ImageSharp.Formats.Png
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++)
{
int index = newScanline[o];
int offset = index * 3;
rgba.A = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : (byte)255;
rgba.Rgb = this.palette.GetRgb24(offset);
rgba.Rgb = pal[index];
color.PackFromRgba32(rgba);
rowSpan[x] = color;
@ -970,10 +968,8 @@ namespace SixLabors.ImageSharp.Formats.Png
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++)
{
int index = newScanline[o];
int offset = index * 3;
rgba.Rgb = this.palette.GetRgb24(offset);
rgba.Rgb = pal[index];
color.PackFromRgba32(rgba);
rowSpan[x] = color;
}

12
src/ImageSharp/Image.LoadPixelData.cs

@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
private static Image<TPixel> LoadPixelData<TPixel>(Span<TPixel> data, int width, int height)
public static Image<TPixel> LoadPixelData<TPixel>(ReadOnlySpan<TPixel> data, int width, int height)
where TPixel : struct, IPixel<TPixel>
=> LoadPixelData(Configuration.Default, data, width, height);
@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
private static Image<TPixel> LoadPixelData<TPixel>(Span<byte> data, int width, int height)
public static Image<TPixel> LoadPixelData<TPixel>(ReadOnlySpan<byte> data, int width, int height)
where TPixel : struct, IPixel<TPixel>
=> LoadPixelData<TPixel>(Configuration.Default, data, width, height);
@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(Configuration config, byte[] data, int width, int height)
where TPixel : struct, IPixel<TPixel>
=> LoadPixelData(config, MemoryMarshal.Cast<byte, TPixel>(data.AsSpan()), width, height);
=> LoadPixelData(config, MemoryMarshal.Cast<byte, TPixel>(new ReadOnlySpan<byte>(data)), width, height);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array in <typeparamref name="TPixel"/> format.
@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
private static Image<TPixel> LoadPixelData<TPixel>(Configuration config, Span<byte> data, int width, int height)
public static Image<TPixel> LoadPixelData<TPixel>(Configuration config, ReadOnlySpan<byte> data, int width, int height)
where TPixel : struct, IPixel<TPixel>
=> LoadPixelData(config, MemoryMarshal.Cast<byte, TPixel>(data), width, height);
@ -99,7 +99,7 @@ namespace SixLabors.ImageSharp
public static Image<TPixel> LoadPixelData<TPixel>(Configuration config, TPixel[] data, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
return LoadPixelData(config, data.AsSpan(), width, height);
return LoadPixelData(config, new ReadOnlySpan<TPixel>(data), width, height);
}
/// <summary>
@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
private static Image<TPixel> LoadPixelData<TPixel>(Configuration config, Span<TPixel> data, int width, int height)
public static Image<TPixel> LoadPixelData<TPixel>(Configuration config, ReadOnlySpan<TPixel> data, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
int count = width * height;

90
src/ImageSharp/ImageExtensions.cs

@ -4,13 +4,11 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp
{
@ -21,18 +19,18 @@ namespace SixLabors.ImageSharp
{
#if !NETSTANDARD1_1
/// <summary>
/// Saves the image to the given stream using the currently loaded image format.
/// Writes the image to the given stream using the currently loaded image format.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image.</param>
/// <param name="filePath">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void Save<TPixel>(this Image<TPixel> source, string filePath)
where TPixel : struct, IPixel<TPixel>
{
Guard.NotNullOrEmpty(filePath, nameof(filePath));
Guard.NotNullOrWhiteSpace(filePath, nameof(filePath));
string ext = Path.GetExtension(filePath).Trim('.');
string ext = Path.GetExtension(filePath);
IImageFormat format = source.GetConfiguration().ImageFormatsManager.FindFormatByFileExtension(ext);
if (format == null)
{
@ -64,13 +62,13 @@ namespace SixLabors.ImageSharp
}
/// <summary>
/// Saves the image to the given stream using the currently loaded image format.
/// Writes the image to the given stream using the currently loaded image format.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image.</param>
/// <param name="filePath">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the encoder is null.</exception>
/// <exception cref="ArgumentNullException">Thrown if the encoder is null.</exception>
public static void Save<TPixel>(this Image<TPixel> source, string filePath, IImageEncoder encoder)
where TPixel : struct, IPixel<TPixel>
{
@ -83,13 +81,13 @@ namespace SixLabors.ImageSharp
#endif
/// <summary>
/// Saves the image to the given stream using the currently loaded image format.
/// Writes the image to the given stream using the currently loaded image format.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param>
/// <param name="source">The source image.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="format">The format to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <param name="format">The format to save the image in.</param>
/// <exception cref="ArgumentNullException">Thrown if the stream is null.</exception>
public static void Save<TPixel>(this Image<TPixel> source, Stream stream, IImageFormat format)
where TPixel : struct, IPixel<TPixel>
{
@ -113,67 +111,67 @@ namespace SixLabors.ImageSharp
}
/// <summary>
/// Saves the raw image pixels to a byte array in row-major order.
/// Returns the a copy of the image pixels as a byte array in row-major order.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image</param>
/// <returns>A copy of the pixel data as bytes from this frame.</returns>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <exception cref="ArgumentNullException">Thrown if the stream is null.</exception>
public static byte[] SavePixelData<TPixel>(this ImageFrame<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> MemoryMarshal.AsBytes(source.GetPixelSpan()).ToArray();
/// <summary>
/// Saves the raw image pixels to the given byte array in row-major order.
/// Writes the raw image pixels to the given byte array in row-major order.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image.</param>
/// <param name="buffer">The buffer to save the raw pixel data to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <exception cref="ArgumentNullException">Thrown if the stream is null.</exception>
public static void SavePixelData<TPixel>(this ImageFrame<TPixel> source, byte[] buffer)
where TPixel : struct, IPixel<TPixel>
=> SavePixelData(source, MemoryMarshal.Cast<byte, TPixel>(buffer.AsSpan()));
/// <summary>
/// Saves the raw image pixels to the given TPixel array in row-major order.
/// Writes the raw image pixels to the given TPixel array in row-major order.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image</param>
/// <param name="buffer">The buffer to save the raw pixel data to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <exception cref="ArgumentNullException">Thrown if the stream is null.</exception>
public static void SavePixelData<TPixel>(this ImageFrame<TPixel> source, TPixel[] buffer)
where TPixel : struct, IPixel<TPixel>
=> SavePixelData(source, buffer.AsSpan());
/// <summary>
/// Saves the raw image pixels to a byte array in row-major order.
/// Returns a copy of the raw image pixels as a byte array in row-major order.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image.</param>
/// <returns>A copy of the pixel data from the first frame as bytes.</returns>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <exception cref="ArgumentNullException">Thrown if the stream is null.</exception>
public static byte[] SavePixelData<TPixel>(this Image<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.SavePixelData();
/// <summary>
/// Saves the raw image pixels to the given byte array in row-major order.
/// Writes the raw image pixels to the given byte array in row-major order.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image.</param>
/// <param name="buffer">The buffer to save the raw pixel data to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <exception cref="ArgumentNullException">Thrown if the stream is null.</exception>
public static void SavePixelData<TPixel>(this Image<TPixel> source, byte[] buffer)
where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.SavePixelData(buffer);
/// <summary>
/// Saves the raw image pixels to the given TPixel array in row-major order.
/// Writes the raw image pixels to the given TPixel array in row-major order.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image</param>
/// <param name="buffer">The buffer to save the raw pixel data to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <exception cref="ArgumentNullException">Thrown if the stream is null.</exception>
public static void SavePixelData<TPixel>(this Image<TPixel> source, TPixel[] buffer)
where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.SavePixelData(buffer);
@ -182,7 +180,7 @@ namespace SixLabors.ImageSharp
/// Returns a Base64 encoded string from the given image.
/// </summary>
/// <example><see href="data:image/gif;base64,R0lGODlhAQABAIABAEdJRgAAACwAAAAAAQABAAACAkQBAA=="/></example>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image</param>
/// <param name="format">The format.</param>
/// <returns>The <see cref="string"/></returns>
@ -198,24 +196,24 @@ namespace SixLabors.ImageSharp
}
/// <summary>
/// Saves the raw image to the given bytes.
/// Writes the raw image bytes to the given byte span.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image</param>
/// <param name="buffer">The buffer to save the raw pixel data to.</param>
/// <param name="buffer">The span to save the raw pixel data to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
internal static void SavePixelData<TPixel>(this Image<TPixel> source, Span<byte> buffer)
public static void SavePixelData<TPixel>(this Image<TPixel> source, Span<byte> buffer)
where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.SavePixelData(MemoryMarshal.Cast<byte, TPixel>(buffer));
/// <summary>
/// Saves the raw image to the given bytes.
/// Writes the raw image pixels to the given TPixel span.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image</param>
/// <param name="buffer">The buffer to save the raw pixel data to.</param>
/// <param name="buffer">The span to save the raw pixel data to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
internal static void SavePixelData<TPixel>(this ImageFrame<TPixel> source, Span<TPixel> buffer)
public static void SavePixelData<TPixel>(this ImageFrame<TPixel> source, Span<TPixel> buffer)
where TPixel : struct, IPixel<TPixel>
{
Span<TPixel> sourceBuffer = source.GetPixelSpan();
@ -224,4 +222,4 @@ namespace SixLabors.ImageSharp
sourceBuffer.CopyTo(buffer);
}
}
}
}

4
src/ImageSharp/ImageFrame.LoadPixelData.cs

@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static ImageFrame<TPixel> LoadPixelData<TPixel>(MemoryManager memoryManager, Span<byte> data, int width, int height)
public static ImageFrame<TPixel> LoadPixelData<TPixel>(MemoryManager memoryManager, ReadOnlySpan<byte> data, int width, int height)
where TPixel : struct, IPixel<TPixel>
=> LoadPixelData(memoryManager, MemoryMarshal.Cast<byte, TPixel>(data), width, height);
@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static ImageFrame<TPixel> LoadPixelData<TPixel>(MemoryManager memoryManager, Span<TPixel> data, int width, int height)
public static ImageFrame<TPixel> LoadPixelData<TPixel>(MemoryManager memoryManager, ReadOnlySpan<TPixel> data, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
int count = width * height;

10
src/ImageSharp/ImageFrameCollection.cs

@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp
internal ImageFrameCollection(Image<TPixel> parent, IEnumerable<ImageFrame<TPixel>> frames)
{
Guard.NotNull(parent, nameof(parent));
Guard.NotNullOrEmpty(frames, nameof(frames));
Guard.NotNull(frames, nameof(frames));
this.parent = parent;
@ -42,6 +42,12 @@ namespace SixLabors.ImageSharp
this.ValidateFrame(f);
this.frames.Add(f);
}
// Ensure at least 1 frame was added to the frames collection
if (this.frames.Count == 0)
{
throw new ArgumentException("Must not be empty.", nameof(frames));
}
}
/// <summary>
@ -111,7 +117,7 @@ namespace SixLabors.ImageSharp
var frame = ImageFrame.LoadPixelData(
this.parent.GetMemoryManager(),
new Span<TPixel>(source),
new ReadOnlySpan<TPixel>(source),
this.RootFrame.Width,
this.RootFrame.Height);
this.frames.Add(frame);

15
src/ImageSharp/ImageSharp.csproj

@ -41,8 +41,8 @@
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="System.Buffers" Version="4.4.0" />
<PackageReference Include="System.Memory" Version="4.5.0-preview2-26406-04" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.0-preview2-26406-04" />
<PackageReference Include="System.Memory" Version="4.5.0-rc1" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.0-rc1" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.1' OR '$(TargetFramework)' == 'netstandard1.3'">
<PackageReference Include="System.IO.Compression" Version="4.3.0" />
@ -57,11 +57,11 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<None Update="Formats\Jpeg\Common\Block8x8F.Generated.tt">
<None Update="Formats\Jpeg\Components\Block8x8F.Generated.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>Block8x8F.Generated.cs</LastGenOutput>
</None>
<None Update="Formats\Jpeg\Common\GenericBlock8x8.Generated.tt">
<None Update="Formats\Jpeg\Components\GenericBlock8x8.Generated.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>GenericBlock8x8.Generated.cs</LastGenOutput>
</None>
@ -87,12 +87,12 @@
</None>
</ItemGroup>
<ItemGroup>
<Compile Update="Formats\Jpeg\Common\Block8x8F.Generated.cs">
<Compile Update="Formats\Jpeg\Components\Block8x8F.Generated.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Block8x8F.Generated.tt</DependentUpon>
</Compile>
<Compile Update="Formats\Jpeg\Common\GenericBlock8x8.Generated.cs">
<Compile Update="Formats\Jpeg\Components\GenericBlock8x8.Generated.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>GenericBlock8x8.Generated.tt</DependentUpon>
@ -123,4 +123,7 @@
<DependentUpon>PorterDuffFunctions.Generated.tt</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>
</Project>

48
src/ImageSharp/Memory/SpanHelper.cs

@ -1,48 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// Utility methods for <see cref="Span{T}"/>
/// </summary>
internal static class SpanHelper
{
/// <summary>
/// Copy all elements of 'source' into 'destination'.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <param name="source">The <see cref="Span{T}"/> to copy elements from.</param>
/// <param name="destination">The destination <see cref="Span{T}"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Copy<T>(ReadOnlySpan<T> source, Span<T> destination)
where T : struct
{
source.Slice(0, Math.Min(source.Length, destination.Length)).CopyTo(destination);
}
/// <summary>
/// Gets the size of `count` elements in bytes.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <param name="count">The count of the elements</param>
/// <returns>The size in bytes as int</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int SizeOf<T>(int count)
where T : struct => Unsafe.SizeOf<T>() * count;
/// <summary>
/// Gets the size of `count` elements in bytes as UInt32
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <param name="count">The count of the elements</param>
/// <returns>The size in bytes as UInt32</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint USizeOf<T>(int count)
where T : struct
=> (uint)SizeOf<T>(count);
}
}

2
src/ImageSharp/MetaData/ImageProperty.cs

@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.MetaData
/// <param name="value">The value of the property.</param>
public ImageProperty(string name, string value)
{
Guard.NotNullOrEmpty(name, nameof(name));
Guard.NotNullOrWhiteSpace(name, nameof(name));
this.Name = name;
this.Value = value;

2
src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs

@ -387,7 +387,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
value = this.ConvertValue(dataType, offsetBuffer, numberOfComponents);
}
exifValue = new ExifValue(tag, dataType, value, isArray: value != null && numberOfComponents > 1);
exifValue = new ExifValue(tag, dataType, value, isArray: value != null && numberOfComponents != 1);
return true;
}

28
src/ImageSharp/PixelFormats/ColorBuilder{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers.Binary;
using System.Globalization;
namespace SixLabors.ImageSharp.PixelFormats
@ -23,21 +24,17 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <returns>Returns a <typeparamref name="TPixel"/> that represents the color defined by the provided RGBA heax string.</returns>
public static TPixel FromHex(string hex)
{
Guard.NotNullOrEmpty(hex, nameof(hex));
Guard.NotNullOrWhiteSpace(hex, nameof(hex));
hex = ToRgbaHex(hex);
uint packedValue;
if (hex == null || !uint.TryParse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out packedValue))
if (hex == null || !uint.TryParse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out uint packedValue))
{
throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex));
}
TPixel result = default;
var rgba = new Rgba32(
(byte)(packedValue >> 24),
(byte)(packedValue >> 16),
(byte)(packedValue >> 8),
(byte)(packedValue >> 0));
var rgba = new Rgba32(BinaryPrimitives.ReverseEndianness(packedValue));
result.PackFromRgba32(rgba);
return result;
@ -76,7 +73,10 @@ namespace SixLabors.ImageSharp.PixelFormats
/// </returns>
private static string ToRgbaHex(string hex)
{
hex = hex.StartsWith("#") ? hex.Substring(1) : hex;
if (hex[0] == '#')
{
hex = hex.Substring(1);
}
if (hex.Length == 8)
{
@ -93,12 +93,12 @@ namespace SixLabors.ImageSharp.PixelFormats
return null;
}
string red = char.ToString(hex[0]);
string green = char.ToString(hex[1]);
string blue = char.ToString(hex[2]);
string alpha = hex.Length == 3 ? "F" : char.ToString(hex[3]);
char r = hex[0];
char g = hex[1];
char b = hex[2];
char a = hex.Length == 3 ? 'F' : hex[3];
return string.Concat(red, red, green, green, blue, blue, alpha, alpha);
return new string(new[] { r, r, g, g, b, b, a, a });
}
}
}

2
tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs

@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
using (var memoryStream = new MemoryStream(this.jpegBytes))
{
using (var image = Image.Load<Rgba32>(memoryStream, new OrigJpegDecoder()))
using (var image = Image.Load<Rgba32>(memoryStream, new GolangJpegDecoder()))
{
return new CoreSize(image.Width, image.Height);
}

2
tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegMultiple.cs

@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark(Description = "DecodeJpegMultiple - ImageSharp")]
public void DecodeJpegImageSharpOrig()
{
this.ForEachStream(ms => Image.Load<Rgba32>(ms, new OrigJpegDecoder()));
this.ForEachStream(ms => Image.Load<Rgba32>(ms, new GolangJpegDecoder()));
}
[Benchmark(Description = "DecodeJpegMultiple - ImageSharp PDFJs")]

114
tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs

@ -0,0 +1,114 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
[Config(typeof(Config.ShortClr))]
public class DoubleBufferedStreams
{
private byte[] buffer = CreateTestBytes();
private byte[] chunk1 = new byte[2];
private byte[] chunk2 = new byte[2];
private MemoryStream stream1;
private MemoryStream stream2;
private MemoryStream stream3;
private MemoryStream stream4;
DoubleBufferedStreamReader reader1;
DoubleBufferedStreamReader reader2;
[GlobalSetup]
public void CreateStreams()
{
this.stream1 = new MemoryStream(this.buffer);
this.stream2 = new MemoryStream(this.buffer);
this.stream3 = new MemoryStream(this.buffer);
this.stream4 = new MemoryStream(this.buffer);
this.reader1 = new DoubleBufferedStreamReader(Configuration.Default.MemoryManager, this.stream2);
this.reader2 = new DoubleBufferedStreamReader(Configuration.Default.MemoryManager, this.stream2);
}
[GlobalCleanup]
public void DestroyStreams()
{
this.stream1?.Dispose();
this.stream2?.Dispose();
this.stream3?.Dispose();
this.stream4?.Dispose();
this.reader1?.Dispose();
this.reader2?.Dispose();
}
[Benchmark(Baseline = true)]
public int StandardStreamReadByte()
{
int r = 0;
Stream stream = this.stream1;
for (int i = 0; i < stream.Length; i++)
{
r += stream.ReadByte();
}
return r;
}
[Benchmark]
public int StandardStreamRead()
{
int r = 0;
Stream stream = this.stream1;
byte[] b = this.chunk1;
for (int i = 0; i < stream.Length / 2; i++)
{
r += stream.Read(b, 0, 2);
}
return r;
}
[Benchmark]
public int DoubleBufferedStreamReadByte()
{
int r = 0;
DoubleBufferedStreamReader reader = this.reader1;
for (int i = 0; i < reader.Length; i++)
{
r += reader.ReadByte();
}
return r;
}
[Benchmark]
public int DoubleBufferedStreamRead()
{
int r = 0;
DoubleBufferedStreamReader reader = this.reader2;
byte[] b = this.chunk2;
for (int i = 0; i < reader.Length / 2; i++)
{
r += reader.Read(b, 0, 2);
}
return r;
}
private static byte[] CreateTestBytes()
{
byte[] buffer = new byte[DoubleBufferedStreamReader.ChunkLength * 3];
var random = new Random();
random.NextBytes(buffer);
return buffer;
}
}
}

52
tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs

@ -0,0 +1,52 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort;
using SixLabors.ImageSharp.Tests;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
[Config(typeof(Config.ShortClr))]
public class IdentifyJpeg
{
private byte[] jpegBytes;
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
[Params(TestImages.Jpeg.Baseline.Jpeg420Exif, TestImages.Jpeg.Baseline.Calliphora)]
public string TestImage { get; set; }
[GlobalSetup]
public void ReadImages()
{
if (this.jpegBytes == null)
{
this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath);
}
}
[Benchmark]
public IImageInfo IdentifyGolang()
{
using (var memoryStream = new MemoryStream(this.jpegBytes))
{
var decoder = new GolangJpegDecoder();
return decoder.Identify(Configuration.Default, memoryStream);
}
}
[Benchmark]
public IImageInfo IdentifyPdfJs()
{
using (var memoryStream = new MemoryStream(this.jpegBytes))
{
var decoder = new PdfJsJpegDecoder();
return decoder.Identify(Configuration.Default, memoryStream);
}
}
}
}

12
tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs

@ -1,14 +1,14 @@
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
using System;
using System.Numerics;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder.ColorConverters;
using SixLabors.ImageSharp.Memory;
[Config(typeof(Config.ShortClr))]
public class YCbCrColorConversion
{
@ -57,7 +57,7 @@
JpegColorConverter.FromYCbCrSimdAvx2.ConvertCore(values, this.output);
}
private static Buffer2D<float>[] CreateRandomValues(
int componentCount,
int inputBufferLength,
@ -81,6 +81,6 @@
return buffers;
}
}
}

17
tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs

@ -1,13 +1,12 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
namespace SixLabors.ImageSharp.Benchmarks
{
@ -104,14 +103,14 @@ namespace SixLabors.ImageSharp.Benchmarks
}
}
}
public struct Result
{
internal Block8x8F Y;
internal Block8x8F Cb;
internal Block8x8F Cr;
}
// The operation is defined as "RGBA -> YCbCr Transform a stream of bytes into a stream of floats"
// We need to benchmark the whole operation, to get true results, not missing any side effects!
private byte[] inputSourceRGB = null;
@ -200,11 +199,11 @@ namespace SixLabors.ImageSharp.Benchmarks
float* cbPtr = (float*)&result.Cb;
float* crPtr = (float*)&result.Cr;
// end of code-bloat block :)
Vector<int> yCoeffs = new Vector<int>(ScaledCoeffs.Y);
Vector<int> cbCoeffs = new Vector<int>(ScaledCoeffs.Cb);
Vector<int> crCoeffs = new Vector<int>(ScaledCoeffs.Cr);
for (int i = 0; i < this.inputSourceRGB.Length; i++)
{
this.inputSourceRGBAsInteger[i] = this.inputSourceRGB[i];
@ -217,7 +216,7 @@ namespace SixLabors.ImageSharp.Benchmarks
Vector<int> y = yCoeffs * rgb;
Vector<int> cb = cbCoeffs * rgb;
Vector<int> cr = crCoeffs * rgb;
*yPtr++ = (y[0] + y[1] + y[2]) >> 10;
*cbPtr++ = 128 + ((cb[0] - cb[1] + cb[2]) >> 10);
*crPtr++ = 128 + ((cr[0] - cr[1] - cr[2]) >> 10);
@ -335,7 +334,7 @@ namespace SixLabors.ImageSharp.Benchmarks
*crPtr++ = 128 + ((cr0 - cr1 - cr2) >> 10);
}
}
[Benchmark(Description = "Scaled Integer LUT Conversion")]
public unsafe void RgbaToYcbCrScaledIntegerLut()
{

4
tests/ImageSharp.Benchmarks/General/Block8x8F_DivideRound.cs

@ -5,7 +5,9 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Benchmarks.General

3
tests/ImageSharp.Benchmarks/General/Block8x8F_Round.cs

@ -6,8 +6,7 @@ using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
namespace SixLabors.ImageSharp.Benchmarks.General
{

4
tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj

@ -18,9 +18,7 @@
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.10.12" />
<PackageReference Include="Colourful" Version="1.1.2" />
<PackageReference Include="System.Drawing.Common" Version="4.5.0-preview1-26216-02" />
<PackageReference Include="System.Memory" Version="4.5.0-preview2-26406-04" />
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0-preview2-26406-04" />
<PackageReference Include="System.Drawing.Common" Version="4.5.0-rc1" />
</ItemGroup>
<ItemGroup>

4
tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj

@ -21,7 +21,6 @@
<PackageReference Include="BitMiracle.LibJpeg.NET" Version="1.4.280" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="Moq" Version="4.8.1" />
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0-preview2-26406-04" />
<!--<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />-->
<!--<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />-->
</ItemGroup>
@ -33,7 +32,4 @@
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
</Project>

188
tests/ImageSharp.Tests/ConfigurationTests.cs

@ -1,95 +1,95 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.PixelFormats;
using Moq;
using Xunit;
namespace SixLabors.ImageSharp.Tests
{
/// <summary>
/// Tests the configuration class.
/// </summary>
public class ConfigurationTests
{
public Configuration ConfigurationEmpty { get; private set; }
public Configuration DefaultConfiguration { get; private set; }
public ConfigurationTests()
{
// the shallow copy of configuration should behave exactly like the default configuration,
// so by using the copy, we test both the default and the copy.
this.DefaultConfiguration = Configuration.CreateDefaultInstance().ShallowCopy();
this.ConfigurationEmpty = new Configuration();
}
[Fact]
public void DefaultsToLocalFileSystem()
{
Assert.IsType<LocalFileSystem>(this.DefaultConfiguration.FileSystem);
Assert.IsType<LocalFileSystem>(this.ConfigurationEmpty.FileSystem);
}
/// <summary>
/// Test that the default configuration is not null.
/// </summary>
[Fact]
public void TestDefultConfigurationIsNotNull()
{
Assert.True(Configuration.Default != null);
}
/// <summary>
/// Test that the default configuration parallel options is not null.
/// </summary>
[Fact]
public void TestDefultConfigurationParallelOptionsIsNotNull()
{
Assert.True(Configuration.Default.ParallelOptions != null);
}
/// <summary>
/// Test that the default configuration read origin options is set to begin.
/// </summary>
[Fact]
public void TestDefultConfigurationReadOriginIsCurrent()
{
Assert.True(Configuration.Default.ReadOrigin == ReadOrigin.Current);
}
/// <summary>
/// Test that the default configuration parallel options max degrees of parallelism matches the
/// environment processor count.
/// </summary>
[Fact]
public void TestDefultConfigurationMaxDegreeOfParallelism()
{
Assert.True(Configuration.Default.ParallelOptions.MaxDegreeOfParallelism == Environment.ProcessorCount);
}
[Fact]
public void ConstructorCallConfigureOnFormatProvider()
{
var provider = new Mock<IConfigurationModule>();
var config = new Configuration(provider.Object);
provider.Verify(x => x.Configure(config));
}
[Fact]
public void AddFormatCallsConfig()
{
var provider = new Mock<IConfigurationModule>();
var config = new Configuration();
config.Configure(provider.Object);
provider.Verify(x => x.Configure(config));
}
}
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.PixelFormats;
using Moq;
using Xunit;
namespace SixLabors.ImageSharp.Tests
{
/// <summary>
/// Tests the configuration class.
/// </summary>
public class ConfigurationTests
{
public Configuration ConfigurationEmpty { get; private set; }
public Configuration DefaultConfiguration { get; private set; }
public ConfigurationTests()
{
// the shallow copy of configuration should behave exactly like the default configuration,
// so by using the copy, we test both the default and the copy.
this.DefaultConfiguration = Configuration.CreateDefaultInstance().ShallowCopy();
this.ConfigurationEmpty = new Configuration();
}
[Fact]
public void DefaultsToLocalFileSystem()
{
Assert.IsType<LocalFileSystem>(this.DefaultConfiguration.FileSystem);
Assert.IsType<LocalFileSystem>(this.ConfigurationEmpty.FileSystem);
}
/// <summary>
/// Test that the default configuration is not null.
/// </summary>
[Fact]
public void TestDefaultConfigurationIsNotNull()
{
Assert.True(Configuration.Default != null);
}
/// <summary>
/// Test that the default configuration parallel options is not null.
/// </summary>
[Fact]
public void TestDefaultConfigurationParallelOptionsIsNotNull()
{
Assert.True(Configuration.Default.ParallelOptions != null);
}
/// <summary>
/// Test that the default configuration read origin options is set to begin.
/// </summary>
[Fact]
public void TestDefaultConfigurationReadOriginIsCurrent()
{
Assert.True(Configuration.Default.ReadOrigin == ReadOrigin.Current);
}
/// <summary>
/// Test that the default configuration parallel options max degrees of parallelism matches the
/// environment processor count.
/// </summary>
[Fact]
public void TestDefaultConfigurationMaxDegreeOfParallelism()
{
Assert.True(Configuration.Default.ParallelOptions.MaxDegreeOfParallelism == Environment.ProcessorCount);
}
[Fact]
public void ConstructorCallConfigureOnFormatProvider()
{
var provider = new Mock<IConfigurationModule>();
var config = new Configuration(provider.Object);
provider.Verify(x => x.Configure(config));
}
[Fact]
public void AddFormatCallsConfig()
{
var provider = new Mock<IConfigurationModule>();
var config = new Configuration();
config.Configure(provider.Object);
provider.Verify(x => x.Configure(config));
}
}
}

149
tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs

@ -0,0 +1,149 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Drawing;
using SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing
{
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
[GroupOutput("Drawing/GradientBrushes")]
public class FillEllipticGradientBrushTests
{
public static ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.01f);
[Theory]
[WithBlankImages(10, 10, PixelTypes.Rgba32)]
public void WithEqualColorsReturnsUnicolorImage<TPixel>(
TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
TPixel red = NamedColors<TPixel>.Red;
using (Image<TPixel> image = provider.GetImage())
{
var unicolorLinearGradientBrush =
new EllipticGradientBrush<TPixel>(
new SixLabors.Primitives.Point(0, 0),
new SixLabors.Primitives.Point(10, 0),
1.0f,
GradientRepetitionMode.None,
new ColorStop<TPixel>(0, red),
new ColorStop<TPixel>(1, red));
image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false);
// no need for reference image in this test:
image.ComparePixelBufferTo(red);
}
}
[Theory]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 1.2)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 1.6)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 2.0)]
public void AxisParallelEllipsesWithDifferentRatio<TPixel>(
TestImageProvider<TPixel> provider,
float ratio)
where TPixel : struct, IPixel<TPixel>
{
TPixel yellow = NamedColors<TPixel>.Yellow;
TPixel red = NamedColors<TPixel>.Red;
TPixel black = NamedColors<TPixel>.Black;
provider.VerifyOperation(
TolerantComparer,
image =>
{
var unicolorLinearGradientBrush = new EllipticGradientBrush<TPixel>(
new SixLabors.Primitives.Point(image.Width / 2, image.Height / 2),
new SixLabors.Primitives.Point(image.Width / 2, (image.Width * 2) / 3),
ratio,
GradientRepetitionMode.None,
new ColorStop<TPixel>(0, yellow),
new ColorStop<TPixel>(1, red),
new ColorStop<TPixel>(1, black));
image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
},
$"{ratio:F2}",
false,
false);
}
[Theory]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1, 0)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4, 0)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8, 0)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0, 0)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1, 45)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4, 45)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8, 45)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0, 45)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1, 90)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4, 90)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8, 90)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0, 90)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1, 30)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4, 30)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8, 30)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0, 30)]
public void RotatedEllipsesWithDifferentRatio<TPixel>(
TestImageProvider<TPixel> provider,
float ratio,
float rotationInDegree)
where TPixel: struct, IPixel<TPixel>
{
FormattableString variant = $"{ratio:F2}_AT_{rotationInDegree:00}deg";
provider.VerifyOperation(
TolerantComparer,
image =>
{
TPixel yellow = NamedColors<TPixel>.Yellow;
TPixel red = NamedColors<TPixel>.Red;
TPixel black = NamedColors<TPixel>.Black;
var center = new SixLabors.Primitives.Point(image.Width / 2, image.Height / 2);
double rotation = (Math.PI * rotationInDegree) / 180.0;
double cos = Math.Cos(rotation);
double sin = Math.Sin(rotation);
int offsetY = image.Height / 6;
int axisX = center.X + (int)-(offsetY * sin);
int axisY = center.Y + (int)(offsetY * cos);
var unicolorLinearGradientBrush = new EllipticGradientBrush<TPixel>(
center,
new SixLabors.Primitives.Point(axisX, axisY),
ratio,
GradientRepetitionMode.None,
new ColorStop<TPixel>(0, yellow),
new ColorStop<TPixel>(1, red),
new ColorStop<TPixel>(1, black));
image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
},
variant,
false,
false);
}
}
}

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

@ -0,0 +1,351 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Globalization;
using System.Linq;
using System.Text;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Drawing;
using SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes;
using Xunit;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Drawing
{
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
[GroupOutput("Drawing/GradientBrushes")]
public class FillLinearGradientBrushTests
{
public static ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.01f);
[Theory]
[WithBlankImages(10, 10, PixelTypes.Rgba32)]
public void WithEqualColorsReturnsUnicolorImage<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
TPixel red = NamedColors<TPixel>.Red;
var unicolorLinearGradientBrush = new LinearGradientBrush<TPixel>(
new SixLabors.Primitives.Point(0, 0),
new SixLabors.Primitives.Point(10, 0),
GradientRepetitionMode.None,
new ColorStop<TPixel>(0, red),
new ColorStop<TPixel>(1, red));
image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false);
// no need for reference image in this test:
image.ComparePixelBufferTo(red);
}
}
[Theory]
[WithBlankImages(20, 10, PixelTypes.Rgba32)]
[WithBlankImages(20, 10, PixelTypes.Argb32)]
[WithBlankImages(20, 10, PixelTypes.Rgb24)]
public void DoesNotDependOnSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
provider.VerifyOperation(
TolerantComparer,
image =>
{
var unicolorLinearGradientBrush = new LinearGradientBrush<TPixel>(
new SixLabors.Primitives.Point(0, 0),
new SixLabors.Primitives.Point(image.Width, 0),
GradientRepetitionMode.None,
new ColorStop<TPixel>(0, NamedColors<TPixel>.Blue),
new ColorStop<TPixel>(1, NamedColors<TPixel>.Yellow));
image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
},
appendSourceFileOrDescription: false);
}
[Theory]
[WithBlankImages(500, 10, PixelTypes.Rgba32)]
public void HorizontalReturnsUnicolorColumns<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
provider.VerifyOperation(
TolerantComparer,
image =>
{
TPixel red = NamedColors<TPixel>.Red;
TPixel yellow = NamedColors<TPixel>.Yellow;
var unicolorLinearGradientBrush = new LinearGradientBrush<TPixel>(
new SixLabors.Primitives.Point(0, 0),
new SixLabors.Primitives.Point(image.Width, 0),
GradientRepetitionMode.None,
new ColorStop<TPixel>(0, red),
new ColorStop<TPixel>(1, yellow));
image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
},
false,
false);
}
[Theory]
[WithBlankImages(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.DontFill)]
[WithBlankImages(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.None)]
[WithBlankImages(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.Repeat)]
[WithBlankImages(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.Reflect)]
public void HorizontalGradientWithRepMode<TPixel>(
TestImageProvider<TPixel> provider,
GradientRepetitionMode repetitionMode)
where TPixel : struct, IPixel<TPixel>
{
provider.VerifyOperation(
TolerantComparer,
image =>
{
TPixel red = NamedColors<TPixel>.Red;
TPixel yellow = NamedColors<TPixel>.Yellow;
var unicolorLinearGradientBrush = new LinearGradientBrush<TPixel>(
new SixLabors.Primitives.Point(0, 0),
new SixLabors.Primitives.Point(image.Width / 10, 0),
repetitionMode,
new ColorStop<TPixel>(0, red),
new ColorStop<TPixel>(1, yellow));
image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
},
$"{repetitionMode}",
false,
false);
}
[Theory]
[WithBlankImages(200, 100, PixelTypes.Rgba32, new[] { 0.5f })]
[WithBlankImages(200, 100, PixelTypes.Rgba32, new[] { 0.2f, 0.4f, 0.6f, 0.8f })]
[WithBlankImages(200, 100, PixelTypes.Rgba32, new[] { 0.1f, 0.3f, 0.6f })]
public void WithDoubledStopsProduceDashedPatterns<TPixel>(
TestImageProvider<TPixel> provider,
float[] pattern)
where TPixel : struct, IPixel<TPixel>
{
string variant = string.Join("_", pattern.Select(i => i.ToString(CultureInfo.InvariantCulture)));
// ensure the input data is valid
Assert.True(pattern.Length > 0);
TPixel black = NamedColors<TPixel>.Black;
TPixel white = NamedColors<TPixel>.White;
// create the input pattern: 0, followed by each of the arguments twice, followed by 1.0 - toggling black and white.
ColorStop<TPixel>[] colorStops =
Enumerable.Repeat(new ColorStop<TPixel>(0, black), 1)
.Concat(
pattern
.SelectMany((f, index) => new[]
{
new ColorStop<TPixel>(f, index % 2 == 0 ? black : white),
new ColorStop<TPixel>(f, index % 2 == 0 ? white : black)
}))
.Concat(Enumerable.Repeat(new ColorStop<TPixel>(1, pattern.Length % 2 == 0 ? black : white), 1))
.ToArray();
using (Image<TPixel> image = provider.GetImage())
{
var unicolorLinearGradientBrush =
new LinearGradientBrush<TPixel>(
new SixLabors.Primitives.Point(0, 0),
new SixLabors.Primitives.Point(image.Width, 0),
GradientRepetitionMode.None,
colorStops);
image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
image.DebugSave(
provider,
variant,
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
// the result must be a black and white pattern, no other color should occur:
Assert.All(
Enumerable.Range(0, image.Width).Select(i => image[i, 0]),
color => Assert.True(color.Equals(black) || color.Equals(white)));
image.CompareToReferenceOutput(
TolerantComparer,
provider,
variant,
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
}
}
[Theory]
[WithBlankImages(10, 500, PixelTypes.Rgba32)]
public void VerticalBrushReturnsUnicolorRows<TPixel>(
TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
provider.VerifyOperation(
image =>
{
TPixel red = NamedColors<TPixel>.Red;
TPixel yellow = NamedColors<TPixel>.Yellow;
var unicolorLinearGradientBrush = new LinearGradientBrush<TPixel>(
new SixLabors.Primitives.Point(0, 0),
new SixLabors.Primitives.Point(0, image.Height),
GradientRepetitionMode.None,
new ColorStop<TPixel>(0, red),
new ColorStop<TPixel>(1, yellow));
image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
VerifyAllRowsAreUnicolor(image);
},
false,
false);
void VerifyAllRowsAreUnicolor(Image<TPixel> image)
{
for (int y = 0; y < image.Height; y++)
{
Span<TPixel> row = image.GetPixelRowSpan(y);
TPixel firstColorOfRow = row[0];
foreach (TPixel p in row)
{
Assert.Equal(firstColorOfRow, p);
}
}
}
}
public enum ImageCorner
{
TopLeft = 0,
TopRight = 1,
BottomLeft = 2,
BottomRight = 3
}
[Theory]
[WithBlankImages(200, 200, PixelTypes.Rgba32, ImageCorner.TopLeft)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, ImageCorner.TopRight)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, ImageCorner.BottomLeft)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, ImageCorner.BottomRight)]
public void DiagonalReturnsCorrectImages<TPixel>(
TestImageProvider<TPixel> provider,
ImageCorner startCorner)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
Assert.True(image.Height == image.Width, "For the math check block at the end the image must be squared, but it is not.");
int startX = (int)startCorner % 2 == 0 ? 0 : image.Width - 1;
int startY = startCorner > ImageCorner.TopRight ? 0 : image.Height - 1;
int endX = image.Height - startX - 1;
int endY = image.Width - startY - 1;
TPixel red = NamedColors<TPixel>.Red;
TPixel yellow = NamedColors<TPixel>.Yellow;
var unicolorLinearGradientBrush =
new LinearGradientBrush<TPixel>(
new SixLabors.Primitives.Point(startX, startY),
new SixLabors.Primitives.Point(endX, endY),
GradientRepetitionMode.None,
new ColorStop<TPixel>(0, red),
new ColorStop<TPixel>(1, yellow));
image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
image.DebugSave(
provider,
startCorner,
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
int verticalSign = startY == 0 ? 1 : -1;
int horizontalSign = startX == 0 ? 1 : -1;
// check first and last pixel, these are known:
Assert.Equal(red, image[startX, startY]);
Assert.Equal(yellow, image[endX, endY]);
for (int i = 0; i < image.Height; i++)
{
// it's diagonal, so for any (a, a) on the gradient line, for all (a-x, b+x) - +/- depending on the diagonal direction - must be the same color)
TPixel colorOnDiagonal = image[i, i];
int orthoCount = 0;
for (int offset = -orthoCount; offset < orthoCount; offset++)
{
Assert.Equal(colorOnDiagonal, image[i + horizontalSign * offset, i + verticalSign * offset]);
}
}
image.CompareToReferenceOutput(
TolerantComparer,
provider,
startCorner,
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
}
}
[Theory]
[WithBlankImages(500, 500, PixelTypes.Rgba32, 0, 0, 499, 499, new[] { 0f, .2f, .5f, .9f }, new[] { 0, 0, 1, 1 })]
[WithBlankImages(500, 500, PixelTypes.Rgba32, 0, 499, 499, 0, new[] { 0f, 0.2f, 0.5f, 0.9f }, new[] { 0, 1, 2, 3 })]
[WithBlankImages(500, 500, PixelTypes.Rgba32, 499, 499, 0, 0, new[] { 0f, 0.7f, 0.8f, 0.9f}, new[] { 0, 1, 2, 0 })]
[WithBlankImages(500, 500, PixelTypes.Rgba32, 0, 0, 499, 499, new[] { 0f, .5f, 1f}, new[]{0, 1, 3})]
public void ArbitraryGradients<TPixel>(
TestImageProvider<TPixel> provider,
int startX, int startY,
int endX, int endY,
float[] stopPositions,
int[] stopColorCodes)
where TPixel : struct, IPixel<TPixel>
{
TPixel[] colors = {
NamedColors<TPixel>.Navy, NamedColors<TPixel>.LightGreen, NamedColors<TPixel>.Yellow,
NamedColors<TPixel>.Red
};
var coloringVariant = new StringBuilder();
ColorStop<TPixel>[] colorStops = new ColorStop<TPixel>[stopPositions.Length];
for (int i = 0; i < stopPositions.Length; i++)
{
TPixel color = colors[stopColorCodes[i % colors.Length]];
float position = stopPositions[i];
colorStops[i] = new ColorStop<TPixel>(position, color);
coloringVariant.AppendFormat(CultureInfo.InvariantCulture, "{0}@{1};", color, position);
}
FormattableString variant = $"({startX},{startY})_TO_({endX},{endY})__[{coloringVariant}]";
provider.VerifyOperation(
image =>
{
var unicolorLinearGradientBrush = new LinearGradientBrush<TPixel>(
new SixLabors.Primitives.Point(startX, startY),
new SixLabors.Primitives.Point(endX, endY),
GradientRepetitionMode.None,
colorStops);
image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
},
variant,
false,
false);
}
}
}

76
tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs

@ -0,0 +1,76 @@
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Drawing;
using SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing
{
using System;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
[GroupOutput("Drawing/GradientBrushes")]
public class FillRadialGradientBrushTests
{
public static ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.01f);
[Theory]
[WithBlankImages(200, 200, PixelTypes.Rgba32)]
public void WithEqualColorsReturnsUnicolorImage<TPixel>(
TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
TPixel red = NamedColors<TPixel>.Red;
var unicolorRadialGradientBrush =
new RadialGradientBrush<TPixel>(
new SixLabors.Primitives.Point(0, 0),
100,
GradientRepetitionMode.None,
new ColorStop<TPixel>(0, red),
new ColorStop<TPixel>(1, red));
image.Mutate(x => x.Fill(unicolorRadialGradientBrush));
image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false);
// no need for reference image in this test:
image.ComparePixelBufferTo(red);
}
}
[Theory]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 100, 100)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0, 0)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 100, 0)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0, 100)]
[WithBlankImages(200, 200, PixelTypes.Rgba32, -40, 100)]
public void WithDifferentCentersReturnsImage<TPixel>(
TestImageProvider<TPixel> provider,
int centerX,
int centerY)
where TPixel : struct, IPixel<TPixel>
{
provider.VerifyOperation(
TolerantComparer,
image =>
{
var brush = new RadialGradientBrush<TPixel>(
new SixLabors.Primitives.Point(centerX, centerY),
image.Width / 2f,
GradientRepetitionMode.None,
new ColorStop<TPixel>(0, NamedColors<TPixel>.Red),
new ColorStop<TPixel>(1, NamedColors<TPixel>.Yellow));
image.Mutate(x => x.Fill(brush));
},
$"center({centerX},{centerY})",
false,
false);
}
}
}

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

Loading…
Cancel
Save