Browse Source

Merge branch 'master' into feature/fix-576

pull/591/head
James Jackson-South 8 years ago
committed by GitHub
parent
commit
82328ea346
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} 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 # prevent the double build when a branch has an active PR
skip_branch_with_pr: true 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")) { 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 ) # We execute CodeCoverage.cmd only for one specific job on CI (netcoreapp2.0 + 64bit )
$testRunnerCmd = ".\tests\CodeCoverage\CodeCoverage.cmd" $testRunnerCmd = ".\tests\CodeCoverage\CodeCoverage.cmd"
@ -64,4 +90,23 @@ Invoke-Expression $testRunnerCmd
cd $PSScriptRoot 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>
<ItemGroup> <ItemGroup>
<PackageReference Include="SixLabors.Core" Version="1.0.0-beta0005" /> <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" /> <PackageReference Include="SixLabors.Shapes.Text" Version="1.0.0-beta0004" />
<AdditionalFiles Include="..\..\stylecop.json" /> <AdditionalFiles Include="..\..\stylecop.json" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta004"> <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; amountSpan[i] = scanline[i] * this.Options.BlendPercentage;
} }
else
{
amountSpan[i] = scanline[i];
}
overlaySpan[i] = this[x + i, y]; 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 public static class FillPathExtensions
{ {
/// <summary> /// <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> /// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam> /// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>

2
src/ImageSharp/Advanced/IConfigurable.cs

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

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

@ -39,11 +39,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary> /// </summary>
private IManagedByteBuffer globalColorTable; private IManagedByteBuffer globalColorTable;
/// <summary>
/// The global color table length
/// </summary>
private int globalColorTableLength;
/// <summary> /// <summary>
/// The area to restore. /// The area to restore.
/// </summary> /// </summary>
@ -333,8 +328,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
indices = this.configuration.MemoryManager.AllocateManagedByteBuffer(imageDescriptor.Width * imageDescriptor.Height, true); indices = this.configuration.MemoryManager.AllocateManagedByteBuffer(imageDescriptor.Width * imageDescriptor.Height, true);
this.ReadFrameIndices(imageDescriptor, indices.Span); this.ReadFrameIndices(imageDescriptor, indices.Span);
IManagedByteBuffer colorTable = localColorTable ?? this.globalColorTable; ReadOnlySpan<Rgb24> colorTable = MemoryMarshal.Cast<byte, Rgb24>((localColorTable ?? this.globalColorTable).Span);
this.ReadFrameColors(ref image, ref previousFrame, indices.Span, colorTable.Span, imageDescriptor); this.ReadFrameColors(ref image, ref previousFrame, indices.Span, colorTable, imageDescriptor);
// Skip any remaining blocks // Skip any remaining blocks
this.Skip(0); this.Skip(0);
@ -370,7 +365,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <param name="indices">The indexed pixels.</param> /// <param name="indices">The indexed pixels.</param>
/// <param name="colorTable">The color table containing the available colors.</param> /// <param name="colorTable">The color table containing the available colors.</param>
/// <param name="descriptor">The <see cref="GifImageDescriptor"/></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> where TPixel : struct, IPixel<TPixel>
{ {
ref byte indicesRef = ref MemoryMarshal.GetReference(indices); ref byte indicesRef = ref MemoryMarshal.GetReference(indices);
@ -458,11 +453,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
if (this.graphicsControlExtension.TransparencyFlag == false || if (this.graphicsControlExtension.TransparencyFlag == false ||
this.graphicsControlExtension.TransparencyIndex != index) this.graphicsControlExtension.TransparencyIndex != index)
{ {
int indexOffset = index * 3;
ref TPixel pixel = ref Unsafe.Add(ref rowRef, x); ref TPixel pixel = ref Unsafe.Add(ref rowRef, x);
rgba.Rgb = colorTable.GetRgb24(indexOffset); rgba.Rgb = colorTable[index];
pixel.PackFromRgba32(rgba); pixel.PackFromRgba32(rgba);
} }
@ -534,12 +526,12 @@ namespace SixLabors.ImageSharp.Formats.Gif
if (this.logicalScreenDescriptor.GlobalColorTableFlag) 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 // Read the global color table data from the stream
stream.Read(this.globalColorTable.Array, 0, this.globalColorTableLength); 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> /// <returns>The <see cref="IImageFormat"/> if found otherwise null</returns>
public IImageFormat FindFormatByFileExtension(string extension) 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)); 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.Runtime.InteropServices;
using System.Text; using System.Text;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
/// <summary> /// <summary>
/// Represents a Jpeg block with <see cref="short"/> coefficiens. /// 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.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Common namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
internal partial struct Block8x8F 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; using System.Runtime.CompilerServices;
// <auto-generated /> // <auto-generated />
namespace SixLabors.ImageSharp.Formats.Jpeg.Common namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
internal partial struct Block8x8F 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'}; char[] coordz = {'X', 'Y', 'Z', 'W'};
#> #>
namespace SixLabors.ImageSharp.Formats.Jpeg.Common namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
internal partial struct Block8x8F 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; using System.Text;
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Common namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
/// <summary> /// <summary>
/// Represents a Jpeg block with <see cref="float"/> coefficients. /// Represents a Jpeg block with <see cref="float"/> coefficients.
@ -100,7 +100,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
{ {
float val = result[i]; float val = result[i];
val /= value; val /= value;
result[i] = (float)val; result[i] = val;
} }
return result; return result;
@ -113,7 +113,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
{ {
float val = result[i]; float val = result[i];
val += value; val += value;
result[i] = (float)val; result[i] = val;
} }
return result; return result;
@ -126,7 +126,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
{ {
float val = result[i]; float val = result[i];
val -= value; val -= value;
result[i] = (float)val; result[i] = val;
} }
return result; return result;
@ -153,7 +153,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
public void Clear() public void Clear()
{ {
// The cheapest way to do this in C#: // The cheapest way to do this in C#:
this = default(Block8x8F); this = default;
} }
/// <summary> /// <summary>

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

@ -4,7 +4,7 @@
using System; using System;
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
/// <summary> /// <summary>
/// Provides information about the Adobe marker segment. /// Provides information about the Adobe marker segment.
@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
return true; return true;
} }
marker = default(AdobeMarker); marker = default;
return false; 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;
using System.Numerics; 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 abstract partial class JpegColorConverter
{ {
internal class FromCmyk : ColorConverters.JpegColorConverter internal class FromCmyk : JpegColorConverter
{ {
public FromCmyk() public FromCmyk()
: base(JpegColorSpace.Cmyk) : 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()! // TODO: We can optimize a lot here with Vector<float> and SRCS.Unsafe()!
ReadOnlySpan<float> cVals = values.Component0; 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;
using System.Numerics; 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 abstract partial class JpegColorConverter
{ {
internal class FromGrayscale : ColorConverters.JpegColorConverter internal class FromGrayscale : JpegColorConverter
{ {
public FromGrayscale() public FromGrayscale()
: base(JpegColorSpace.Grayscale) : 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()! // TODO: We can optimize a lot here with Vector<float> and SRCS.Unsafe()!
ReadOnlySpan<float> yVals = values.Component0; 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;
using System.Numerics; 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 abstract partial class JpegColorConverter
{ {
internal class FromRgb : ColorConverters.JpegColorConverter internal class FromRgb : JpegColorConverter
{ {
public FromRgb() public FromRgb()
: base(JpegColorSpace.RGB) : 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()! // TODO: We can optimize a lot here with Vector<float> and SRCS.Unsafe()!
ReadOnlySpan<float> rVals = values.Component0; 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;
using System.Numerics; 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 abstract partial class JpegColorConverter
{ {
internal class FromYCbCrBasic : ColorConverters.JpegColorConverter internal class FromYCbCrBasic : JpegColorConverter
{ {
public FromYCbCrBasic() public FromYCbCrBasic()
: base(JpegColorSpace.YCbCr) : base(JpegColorSpace.YCbCr)
{ {
} }
public override void ConvertToRGBA(ComponentValues values, Span<Vector4> result) public override void ConvertToRgba(ComponentValues values, Span<Vector4> result)
{ {
ConvertCore(values, 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.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Common.Tuples; 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 abstract partial class JpegColorConverter
{ {
internal class FromYCbCrSimd : ColorConverters.JpegColorConverter internal class FromYCbCrSimd : JpegColorConverter
{ {
public FromYCbCrSimd() public FromYCbCrSimd()
: base(JpegColorSpace.YCbCr) : 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 remainder = result.Length % 8;
int simdCount = result.Length - remainder; 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.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Common.Tuples; using SixLabors.ImageSharp.Common.Tuples;
// ReSharper disable ImpureMethodCallOnReadonlyValueField // 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 abstract partial class JpegColorConverter
{ {
internal class FromYCbCrSimdAvx2 : ColorConverters.JpegColorConverter internal class FromYCbCrSimdAvx2 : JpegColorConverter
{ {
public FromYCbCrSimdAvx2() public FromYCbCrSimdAvx2()
: base(JpegColorSpace.YCbCr) : base(JpegColorSpace.YCbCr)
@ -21,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder.ColorConverters
public static bool IsAvailable => Vector.IsHardwareAccelerated && SimdUtils.IsAvx2CompatibleArchitecture; 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 remainder = result.Length % 8;
int simdCount = result.Length - remainder; 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;
using System.Numerics; 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 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()! // TODO: We can optimize a lot here with Vector<float> and SRCS.Unsafe()!
ReadOnlySpan<float> yVals = values.Component0; 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.Collections.Generic;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using SixLabors.ImageSharp.Common.Tuples; using SixLabors.ImageSharp.Common.Tuples;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder.ColorConverters namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
/// <summary> /// <summary>
/// Encapsulates the conversion of Jpeg channels to RGBA values packed in <see cref="Vector4"/> buffer. /// 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> /// </summary>
/// <param name="values">The input as a stack-only <see cref="ComponentValues"/> struct</param> /// <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> /// <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> /// <summary>
/// Returns the <see cref="JpegColorConverter"/> for the YCbCr colorspace that matches the current CPU architecture. /// Returns the <see cref="JpegColorConverter"/> for the YCbCr colorspace that matches the current CPU architecture.
/// </summary> /// </summary>
private static JpegColorConverter GetYCbCrConverter() => private static JpegColorConverter GetYCbCrConverter() =>
FromYCbCrSimdAvx2.IsAvailable ? (JpegColorConverter)new FromYCbCrSimdAvx2() : new FromYCbCrSimd(); JpegColorConverter.FromYCbCrSimdAvx2.IsAvailable ? (JpegColorConverter)new JpegColorConverter.FromYCbCrSimdAvx2() : new JpegColorConverter.FromYCbCrSimd();
/// <summary> /// <summary>
/// A stack-only struct to reference the input buffers using <see cref="ReadOnlySpan{T}"/>-s. /// 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.ImageSharp.Memory;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
/// <summary> /// <summary>
/// Common interface to represent raw Jpeg components. /// 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;
using System.Collections.Generic; using System.Collections.Generic;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
/// <inheritdoc /> /// <inheritdoc />
/// <summary> /// <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> /// </summary>
internal interface IRawJpegData : IDisposable 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; using System;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
/// <summary> /// <summary>
/// Provides information about the JFIF marker segment /// 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. // Licensed under the Apache License, Version 2.0.
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
/// <summary> /// <summary>
/// Encapsulates the implementation of processing "raw" <see cref="IBuffer{T}"/>-s into Jpeg image channels. /// 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> /// <summary>
/// Identifies the colorspace of a Jpeg image /// 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 System;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
/// <summary> /// <summary>
/// Encapsulates postprocessing data for one component for <see cref="JpegImagePostProcessor"/>. /// 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;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives; 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> /// <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/> /// 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; private readonly IBuffer<Vector4> rgbaBuffer;
/// <summary> /// <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> /// </summary>
private ColorConverters.JpegColorConverter colorConverter; private readonly JpegColorConverter colorConverter;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JpegImagePostProcessor"/> class. /// 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.ComponentProcessors = rawJpeg.Components.Select(c => new JpegComponentPostProcessor(memoryManager, this, c)).ToArray();
this.rgbaBuffer = memoryManager.Allocate<Vector4>(rawJpeg.ImageSizeInPixels.Width); this.rgbaBuffer = memoryManager.Allocate<Vector4>(rawJpeg.ImageSizeInPixels.Width);
this.colorConverter = ColorConverters.JpegColorConverter.GetConverter(rawJpeg.ColorSpace); this.colorConverter = JpegColorConverter.GetConverter(rawJpeg.ColorSpace);
} }
/// <summary> /// <summary>
@ -148,8 +154,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
{ {
int y = yy - this.PixelRowCounter; int y = yy - this.PixelRowCounter;
var values = new ColorConverters.JpegColorConverter.ComponentValues(buffers, y); var values = new JpegColorConverter.ComponentValues(buffers, y);
this.colorConverter.ConvertToRGBA(values, this.rgbaBuffer.Span); this.colorConverter.ConvertToRgba(values, this.rgbaBuffer.Span);
Span<TPixel> destRow = destination.GetPixelRowSpan(yy); 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;
using System.Text; using System.Text;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
/// <summary> /// <summary>
/// Provides methods for identifying metadata and color profiles within jpeg images. /// 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. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using Block8x8F = SixLabors.ImageSharp.Formats.Jpeg.Common.Block8x8F; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components
{ {
/// <summary> /// <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. /// Useful for decoder/encoder operations allocating a block for each Jpeg component.
/// </summary> /// </summary>
internal unsafe struct BlockQuad internal unsafe struct BlockQuad
{ {
/// <summary> /// <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> /// </summary>
public fixed float Data[4 * Block8x8F.Size]; 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. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{ {
/// <summary> /// <summary>
/// Enumerates the Huffman tables /// 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. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{ {
/// <summary> /// <summary>
/// A compiled look-up table representation of a huffmanSpec. /// 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. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{ {
/// <summary> /// <summary>
/// The Huffman encoding specifications. /// 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. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{ {
/// <summary> /// <summary>
/// Enumerates the quantization tables /// 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 System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder
{ {
/// <summary> /// <summary>
/// Provides 8-bit lookup tables for converting from Rgb to YCbCr colorspace. /// 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> /// <returns>The intialized <see cref="RgbToYCbCrTables"/></returns>
public static RgbToYCbCrTables Create() public static RgbToYCbCrTables Create()
{ {
RgbToYCbCrTables tables = default(RgbToYCbCrTables); RgbToYCbCrTables tables = default;
for (int i = 0; i <= 255; i++) 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); 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;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{ {
/// <summary> /// <summary>
/// On-stack worker struct to efficiently encapsulate the TPixel -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks. /// 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; using System.Runtime.CompilerServices;
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Common namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
/// <summary> /// <summary>
/// Contains inaccurate, but fast forward and inverse DCT implementations. /// 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. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Numerics;
using System.Runtime.CompilerServices;
// <auto-generated /> // <auto-generated />
namespace SixLabors.ImageSharp.Formats.Jpeg.Common namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
internal unsafe partial struct GenericBlock8x8<T> 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. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Numerics;
using System.Runtime.CompilerServices;
// <auto-generated /> // <auto-generated />
namespace SixLabors.ImageSharp.Formats.Jpeg.Common namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
internal unsafe partial struct GenericBlock8x8<T> 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;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Common namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
/// <summary> /// <summary>
/// A generic 8x8 block implementation, useful for manipulating custom 8x8 pixel data. /// 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 Size = 64;
public const int SizeInBytes = Size * 3;
/// <summary> /// <summary>
/// FOR TESTING ONLY! /// FOR TESTING ONLY!
/// Gets or sets a <see cref="Rgb24"/> value at the given index /// 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;
using System.Numerics; using System.Numerics;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
/// <summary> /// <summary>
/// Extension methods for <see cref="Size"/> /// 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. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
/// <summary> /// <summary>
/// Holds the Jpeg UnZig array in a value/stack type. /// 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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void EnsureNBits(int n, ref InputProcessor inputProcessor) public void EnsureNBits(int n, ref InputProcessor inputProcessor)
{ {
OrigDecoderErrorCode errorCode = this.EnsureNBitsUnsafe(n, ref inputProcessor); GolangDecoderErrorCode errorCode = this.EnsureNBitsUnsafe(n, ref inputProcessor);
errorCode.EnsureNoError(); 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 /// Reads bytes from the byte buffer to ensure that bits.UnreadBits is at
/// least n. For best performance (avoiding function calls inside hot loops), /// 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. /// 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> /// </summary>
/// <param name="n">The number of bits to ensure.</param> /// <param name="n">The number of bits to ensure.</param>
/// <param name="inputProcessor">The <see cref="InputProcessor"/></param> /// <param name="inputProcessor">The <see cref="InputProcessor"/></param>
/// <returns>Error code</returns> /// <returns>Error code</returns>
public OrigDecoderErrorCode EnsureNBitsUnsafe(int n, ref InputProcessor inputProcessor) public GolangDecoderErrorCode EnsureNBitsUnsafe(int n, ref InputProcessor inputProcessor)
{ {
while (true) while (true)
{ {
OrigDecoderErrorCode errorCode = this.EnsureBitsStepImpl(ref inputProcessor); GolangDecoderErrorCode errorCode = this.EnsureBitsStepImpl(ref inputProcessor);
if (errorCode != OrigDecoderErrorCode.NoError || this.UnreadBits >= n) if (errorCode != GolangDecoderErrorCode.NoError || this.UnreadBits >= n)
{ {
return errorCode; return errorCode;
} }
@ -67,8 +67,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// Unrolled version of <see cref="EnsureNBitsUnsafe"/> for n==8 /// Unrolled version of <see cref="EnsureNBitsUnsafe"/> for n==8
/// </summary> /// </summary>
/// <param name="inputProcessor">The <see cref="InputProcessor"/></param> /// <param name="inputProcessor">The <see cref="InputProcessor"/></param>
/// <returns>A <see cref="OrigDecoderErrorCode"/></returns> /// <returns>A <see cref="GolangDecoderErrorCode"/></returns>
public OrigDecoderErrorCode Ensure8BitsUnsafe(ref InputProcessor inputProcessor) public GolangDecoderErrorCode Ensure8BitsUnsafe(ref InputProcessor inputProcessor)
{ {
return this.EnsureBitsStepImpl(ref 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 /// Unrolled version of <see cref="EnsureNBitsUnsafe"/> for n==1
/// </summary> /// </summary>
/// <param name="inputProcessor">The <see cref="InputProcessor"/></param> /// <param name="inputProcessor">The <see cref="InputProcessor"/></param>
/// <returns>A <see cref="OrigDecoderErrorCode"/></returns> /// <returns>A <see cref="GolangDecoderErrorCode"/></returns>
public OrigDecoderErrorCode Ensure1BitUnsafe(ref InputProcessor inputProcessor) public GolangDecoderErrorCode Ensure1BitUnsafe(ref InputProcessor inputProcessor)
{ {
return this.EnsureBitsStepImpl(ref inputProcessor); return this.EnsureBitsStepImpl(ref inputProcessor);
} }
@ -92,7 +92,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReceiveExtend(int t, ref InputProcessor inputProcessor) 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(); errorCode.EnsureNoError();
return x; return x;
} }
@ -103,13 +103,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <param name="t">Byte</param> /// <param name="t">Byte</param>
/// <param name="inputProcessor">The <see cref="InputProcessor"/></param> /// <param name="inputProcessor">The <see cref="InputProcessor"/></param>
/// <param name="x">Read bits value</param> /// <param name="x">Read bits value</param>
/// <returns>The <see cref="OrigDecoderErrorCode"/></returns> /// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
public OrigDecoderErrorCode ReceiveExtendUnsafe(int t, ref InputProcessor inputProcessor, out int x) public GolangDecoderErrorCode ReceiveExtendUnsafe(int t, ref InputProcessor inputProcessor, out int x)
{ {
if (this.UnreadBits < t) if (this.UnreadBits < t)
{ {
OrigDecoderErrorCode errorCode = this.EnsureNBitsUnsafe(t, ref inputProcessor); GolangDecoderErrorCode errorCode = this.EnsureNBitsUnsafe(t, ref inputProcessor);
if (errorCode != OrigDecoderErrorCode.NoError) if (errorCode != GolangDecoderErrorCode.NoError)
{ {
x = int.MaxValue; x = int.MaxValue;
return errorCode; return errorCode;
@ -126,14 +126,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
x += ((-1) << t) + 1; 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; 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> /// </summary>
/// <param name="inputStream">Input stream</param> /// <param name="inputStream">Input stream</param>
/// <param name="x">The result byte as <see cref="int"/></param> /// <param name="x">The result byte as <see cref="int"/></param>
/// <returns>The <see cref="OrigDecoderErrorCode"/></returns> /// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
public OrigDecoderErrorCode ReadByteStuffedByteUnsafe(Stream inputStream, out int x) public GolangDecoderErrorCode ReadByteStuffedByteUnsafe(Stream inputStream, out int x)
{ {
// Take the fast path if bytes.buf contains at least two bytes. // Take the fast path if bytes.buf contains at least two bytes.
if (this.I + 2 <= this.J) if (this.I + 2 <= this.J)
@ -86,50 +86,50 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
x = this.BufferAsInt[this.I]; x = this.BufferAsInt[this.I];
this.I++; this.I++;
this.UnreadableBytes = 1; 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) if (this.BufferAsInt[this.I] != 0x00)
{ {
return OrigDecoderErrorCode.MissingFF00; return GolangDecoderErrorCode.MissingFF00;
} }
this.I++; this.I++;
this.UnreadableBytes = 2; this.UnreadableBytes = 2;
x = OrigJpegConstants.Markers.XFF; x = JpegConstants.Markers.XFF;
return OrigDecoderErrorCode.NoError; return GolangDecoderErrorCode.NoError;
} }
this.UnreadableBytes = 0; this.UnreadableBytes = 0;
OrigDecoderErrorCode errorCode = this.ReadByteAsIntUnsafe(inputStream, out x); GolangDecoderErrorCode errorCode = this.ReadByteAsIntUnsafe(inputStream, out x);
this.UnreadableBytes = 1; this.UnreadableBytes = 1;
if (errorCode != OrigDecoderErrorCode.NoError) if (errorCode != GolangDecoderErrorCode.NoError)
{ {
return errorCode; return errorCode;
} }
if (x != OrigJpegConstants.Markers.XFF) if (x != JpegConstants.Markers.XFF)
{ {
return OrigDecoderErrorCode.NoError; return GolangDecoderErrorCode.NoError;
} }
errorCode = this.ReadByteAsIntUnsafe(inputStream, out x); errorCode = this.ReadByteAsIntUnsafe(inputStream, out x);
this.UnreadableBytes = 2; this.UnreadableBytes = 2;
if (errorCode != OrigDecoderErrorCode.NoError) if (errorCode != GolangDecoderErrorCode.NoError)
{ {
return errorCode; return errorCode;
} }
if (x != 0x00) if (x != 0x00)
{ {
return OrigDecoderErrorCode.MissingFF00; return GolangDecoderErrorCode.MissingFF00;
} }
x = OrigJpegConstants.Markers.XFF; x = JpegConstants.Markers.XFF;
return OrigDecoderErrorCode.NoError; return GolangDecoderErrorCode.NoError;
} }
/// <summary> /// <summary>
@ -140,25 +140,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte ReadByte(Stream inputStream) public byte ReadByte(Stream inputStream)
{ {
OrigDecoderErrorCode errorCode = this.ReadByteUnsafe(inputStream, out byte result); GolangDecoderErrorCode errorCode = this.ReadByteUnsafe(inputStream, out byte result);
errorCode.EnsureNoError(); errorCode.EnsureNoError();
return result; return result;
} }
/// <summary> /// <summary>
/// Extracts the next byte, whether buffered or not buffered into the result out parameter. It does not care about byte stuffing. /// 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> /// </summary>
/// <param name="inputStream">Input stream</param> /// <param name="inputStream">Input stream</param>
/// <param name="result">The result <see cref="byte"/> as out parameter</param> /// <param name="result">The result <see cref="byte"/> as out parameter</param>
/// <returns>The <see cref="OrigDecoderErrorCode"/></returns> /// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
public OrigDecoderErrorCode ReadByteUnsafe(Stream inputStream, out byte result) public GolangDecoderErrorCode ReadByteUnsafe(Stream inputStream, out byte result)
{ {
OrigDecoderErrorCode errorCode = OrigDecoderErrorCode.NoError; GolangDecoderErrorCode errorCode = GolangDecoderErrorCode.NoError;
while (this.I == this.J) while (this.I == this.J)
{ {
errorCode = this.FillUnsafe(inputStream); errorCode = this.FillUnsafe(inputStream);
if (errorCode != OrigDecoderErrorCode.NoError) if (errorCode != GolangDecoderErrorCode.NoError)
{ {
result = 0; result = 0;
return errorCode; return errorCode;
@ -176,15 +176,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// </summary> /// </summary>
/// <param name="inputStream">The input stream</param> /// <param name="inputStream">The input stream</param>
/// <param name="result">The result <see cref="int"/></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)] [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) while (this.I == this.J)
{ {
errorCode = this.FillUnsafe(inputStream); errorCode = this.FillUnsafe(inputStream);
if (errorCode != OrigDecoderErrorCode.NoError) if (errorCode != GolangDecoderErrorCode.NoError)
{ {
result = 0; result = 0;
return errorCode; return errorCode;
@ -206,18 +206,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Fill(Stream inputStream) public void Fill(Stream inputStream)
{ {
OrigDecoderErrorCode errorCode = this.FillUnsafe(inputStream); GolangDecoderErrorCode errorCode = this.FillUnsafe(inputStream);
errorCode.EnsureNoError(); errorCode.EnsureNoError();
} }
/// <summary> /// <summary>
/// Fills up the bytes buffer from the underlying stream. /// Fills up the bytes buffer from the underlying stream.
/// It should only be called when there are no unread bytes in bytes. /// 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> /// </summary>
/// <param name="inputStream">Input stream</param> /// <param name="inputStream">Input stream</param>
/// <returns>The <see cref="OrigDecoderErrorCode"/></returns> /// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
public OrigDecoderErrorCode FillUnsafe(Stream inputStream) public GolangDecoderErrorCode FillUnsafe(Stream inputStream)
{ {
if (this.I != this.J) 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); int n = inputStream.Read(this.Buffer, this.J, this.Buffer.Length - this.J);
if (n == 0) if (n == 0)
{ {
return OrigDecoderErrorCode.UnexpectedEndOfStream; return GolangDecoderErrorCode.UnexpectedEndOfStream;
} }
this.J += n; this.J += n;
@ -249,7 +249,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
this.BufferAsInt[i] = this.Buffer[i]; 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 internal static class DecoderThrowHelper
{ {
/// <summary> /// <summary>
/// Throws an exception that belongs to the given <see cref="OrigDecoderErrorCode"/> /// Throws an exception that belongs to the given <see cref="GolangDecoderErrorCode"/>
/// </summary> /// </summary>
/// <param name="errorCode">The <see cref="OrigDecoderErrorCode"/></param> /// <param name="errorCode">The <see cref="GolangDecoderErrorCode"/></param>
[MethodImpl(MethodImplOptions.NoInlining)] [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, // 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() // consider using the ***Unsafe variant of the parsing method that asks for ThrowExceptionForErrorCode()
// then verify the error code + implement fallback logic manually! // then verify the error code + implement fallback logic manually!
switch (errorCode) switch (errorCode)
{ {
case OrigDecoderErrorCode.NoError: case GolangDecoderErrorCode.NoError:
throw new ArgumentException("ThrowExceptionForErrorCode() called with NoError!", nameof(errorCode)); throw new ArgumentException("ThrowExceptionForErrorCode() called with NoError!", nameof(errorCode));
case OrigDecoderErrorCode.MissingFF00: case GolangDecoderErrorCode.MissingFF00:
throw new MissingFF00Exception(); throw new MissingFF00Exception();
case OrigDecoderErrorCode.UnexpectedEndOfStream: case GolangDecoderErrorCode.UnexpectedEndOfStream:
throw new EOFException(); throw new EOFException();
default: default:
throw new ArgumentOutOfRangeException(nameof(errorCode), errorCode, null); throw new ArgumentOutOfRangeException(nameof(errorCode), errorCode, null);
@ -35,26 +35,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
} }
/// <summary> /// <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> /// </summary>
/// <param name="errorCode">The <see cref="OrigDecoderErrorCode"/></param> /// <param name="errorCode">The <see cref="GolangDecoderErrorCode"/></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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); ThrowExceptionForErrorCode(errorCode);
} }
} }
/// <summary> /// <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> /// </summary>
/// <param name="errorCode">The <see cref="OrigDecoderErrorCode"/></param> /// <param name="errorCode">The <see cref="GolangDecoderErrorCode"/></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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(); 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;
using System.Runtime.CompilerServices; 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.ImageSharp.Memory;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -14,9 +15,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <summary> /// <summary>
/// Represents a single color component /// Represents a single color component
/// </summary> /// </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.Identifier = identifier;
this.Index = index; this.Index = index;
@ -56,9 +57,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// Initializes <see cref="SpectralBlocks"/> /// Initializes <see cref="SpectralBlocks"/>
/// </summary> /// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param> /// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <param name="decoder">The <see cref="OrigJpegDecoderCore"/> instance</param> /// <param name="decoder">The <see cref="GolangJpegDecoderCore"/> 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, GolangJpegDecoderCore decoder)
public void InitializeDerivedData(MemoryManager memoryManager, OrigJpegDecoderCore decoder, bool metadataOnly)
{ {
// For 4-component images (either CMYK or YCbCrK), we only support two // For 4-component images (either CMYK or YCbCrK), we only support two
// hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22]. // hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22].
@ -77,21 +77,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
} }
else else
{ {
OrigComponent c0 = decoder.Components[0]; GolangComponent c0 = decoder.Components[0];
this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors); 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> /// <summary>
/// Initializes all component data except <see cref="SpectralBlocks"/>. /// Initializes all component data except <see cref="SpectralBlocks"/>.
/// </summary> /// </summary>
/// <param name="decoder">The <see cref="OrigJpegDecoderCore"/> instance</param> /// <param name="decoder">The <see cref="GolangJpegDecoderCore"/> instance</param>
public void InitializeCoreData(OrigJpegDecoderCore decoder) public void InitializeCoreData(GolangJpegDecoderCore decoder)
{ {
// Section B.2.2 states that "the value of C_i shall be different from // Section B.2.2 states that "the value of C_i shall be different from
// the values of C_1 through C_(i-1)". // 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)]; this.QuantizationTableIndex = decoder.Temp[8 + (3 * i)];
if (this.QuantizationTableIndex > OrigJpegDecoderCore.MaxTq) if (this.QuantizationTableIndex > GolangJpegDecoderCore.MaxTq)
{ {
throw new ImageFormatException("Bad Tq value"); 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 /// Represents a component scan
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal struct OrigComponentScan internal struct GolangComponentScan
{ {
/// <summary> /// <summary>
/// Gets or sets the component index. /// 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> /// <summary>
/// Represents "recoverable" decoder errors. /// Represents "recoverable" decoder errors.
/// </summary> /// </summary>
internal enum OrigDecoderErrorCode internal enum GolangDecoderErrorCode
{ {
/// <summary> /// <summary>
/// NoError /// 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. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -12,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// Represents a Huffman tree /// Represents a Huffman tree
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal unsafe struct OrigHuffmanTree internal unsafe struct GolangHuffmanTree
{ {
/// <summary> /// <summary>
/// The index of the AC table row /// The index of the AC table row
@ -95,12 +93,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
public FixedInt32Buffer16 Indices; public FixedInt32Buffer16 Indices;
/// <summary> /// <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> /// </summary>
/// <returns>An array of <see cref="OrigHuffmanTree" /> instances representing the Huffman tables</returns> /// <returns>An array of <see cref="GolangHuffmanTree" /> instances representing the Huffman tables</returns>
public static OrigHuffmanTree[] CreateHuffmanTrees() public static GolangHuffmanTree[] CreateHuffmanTrees()
{ {
return new OrigHuffmanTree[NumberOfTrees]; return new GolangHuffmanTree[NumberOfTrees];
} }
/// <summary> /// <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. // Licensed under the Apache License, Version 2.0.
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{ {
/// <content> /// <content>
/// Conains the definition of <see cref="ComputationData"/> /// Conains the definition of <see cref="ComputationData"/>
/// </content> /// </content>
internal unsafe partial struct OrigJpegScanDecoder internal unsafe partial struct GolangJpegScanDecoder
{ {
/// <summary> /// <summary>
/// Holds the "large" data blocks needed for computations. /// Holds the "large" data blocks needed for computations.
@ -28,14 +29,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
public ZigZag Unzig; public ZigZag Unzig;
/// <summary> /// <summary>
/// The buffer storing the <see cref="OrigComponentScan"/>-s for each component /// The buffer storing the <see cref="GolangComponentScan"/>-s for each component
/// </summary> /// </summary>
public fixed byte ScanData[3 * OrigJpegDecoderCore.MaxComponents]; public fixed byte ScanData[3 * GolangJpegDecoderCore.MaxComponents];
/// <summary> /// <summary>
/// The DC values for each component /// The DC values for each component
/// </summary> /// </summary>
public fixed int Dc[OrigJpegDecoderCore.MaxComponents]; public fixed int Dc[GolangJpegDecoderCore.MaxComponents];
/// <summary> /// <summary>
/// Creates and initializes a new <see cref="ComputationData"/> instance /// 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. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // 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 namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{ {
/// <content> /// <content>
/// Conains the definition of <see cref="DataPointers"/> /// Conains the definition of <see cref="DataPointers"/>
/// </content> /// </content>
internal unsafe partial struct OrigJpegScanDecoder internal unsafe partial struct GolangJpegScanDecoder
{ {
/// <summary> /// <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"/> /// 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> /// <summary>
/// Pointer to <see cref="ComputationData.ScanData"/> as Scan* /// Pointer to <see cref="ComputationData.ScanData"/> as Scan*
/// </summary> /// </summary>
public OrigComponentScan* ComponentScan; public GolangComponentScan* ComponentScan;
/// <summary> /// <summary>
/// Pointer to <see cref="ComputationData.Dc"/> /// Pointer to <see cref="ComputationData.Dc"/>
@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{ {
this.Block = &basePtr->Block; this.Block = &basePtr->Block;
this.Unzig = basePtr->Unzig.Data; this.Unzig = basePtr->Unzig.Data;
this.ComponentScan = (OrigComponentScan*)basePtr->ScanData; this.ComponentScan = (GolangComponentScan*)basePtr->ScanData;
this.Dc = basePtr->Dc; 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.CompilerServices;
using System.Runtime.InteropServices; 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.Memory;
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder 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. /// For baseline JPEGs, these parameters are hard-coded to 0/63/0/0.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential)] [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. // The JpegScanDecoder members should be ordered in a way that results in optimal memory layout.
#pragma warning disable SA1202 // ElementsMustBeOrderedByAccess #pragma warning disable SA1202 // ElementsMustBeOrderedByAccess
@ -110,12 +109,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
private byte expectedRst; private byte expectedRst;
/// <summary> /// <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> /// </summary>
/// <param name="p">Pointer to <see cref="OrigJpegScanDecoder"/> on the stack</param> /// <param name="p">Pointer to <see cref="GolangJpegScanDecoder"/> on the stack</param>
/// <param name="decoder">The <see cref="OrigJpegDecoderCore"/> instance</param> /// <param name="decoder">The <see cref="GolangJpegDecoderCore"/> instance</param>
/// <param name="remaining">The remaining bytes in the segment block.</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->data = ComputationData.Create();
p->pointers = new DataPointers(&p->data); p->pointers = new DataPointers(&p->data);
@ -123,8 +122,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
} }
/// <summary> /// <summary>
/// Read Huffman data from Jpeg scans in <see cref="OrigJpegDecoderCore.InputStream"/>, /// Read Huffman data from Jpeg scans in <see cref="GolangJpegDecoderCore.InputStream"/>,
/// and decode it as <see cref="Block8x8"/> into <see cref="OrigComponent.SpectralBlocks"/>. /// 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 /// 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. /// 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 /// 0 1 2
/// 3 4 5 /// 3 4 5
/// </summary> /// </summary>
/// <param name="decoder">The <see cref="OrigJpegDecoderCore"/> instance</param> /// <param name="decoder">The <see cref="GolangJpegDecoderCore"/> instance</param>
public void DecodeBlocks(OrigJpegDecoderCore decoder) public void DecodeBlocks(GolangJpegDecoderCore decoder)
{ {
decoder.InputProcessor.ResetErrorState(); decoder.InputProcessor.ResetErrorState();
this.blockCounter = 0; this.blockCounter = 0;
this.mcuCounter = 0; this.mcuCounter = 0;
this.expectedRst = OrigJpegConstants.Markers.RST0; this.expectedRst = JpegConstants.Markers.RST0;
for (int my = 0; my < decoder.MCUCountY; my++) 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++) for (int scanIndex = 0; scanIndex < this.componentScanCount; scanIndex++)
{ {
this.ComponentIndex = this.pointers.ComponentScan[scanIndex].ComponentIndex; this.ComponentIndex = this.pointers.ComponentScan[scanIndex].ComponentIndex;
OrigComponent component = decoder.Components[this.ComponentIndex]; GolangComponent component = decoder.Components[this.ComponentIndex];
this.hi = component.HorizontalSamplingFactor; this.hi = component.HorizontalSamplingFactor;
int vi = component.VerticalSamplingFactor; 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. // Attempt to look for RST[0-7] markers to resynchronize from corrupt input.
if (!decoder.InputProcessor.ReachedEOF) if (!decoder.InputProcessor.ReachedEOF)
@ -262,15 +261,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
} }
this.expectedRst++; 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(); decoder.InputProcessor.ResetHuffmanDecoder();
@ -285,15 +284,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// </summary> /// </summary>
private void ResetDcValues() 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> /// <summary>
/// The implementation part of <see cref="InitStreamReading"/> as an instance method. /// The implementation part of <see cref="InitStreamReading"/> as an instance method.
/// </summary> /// </summary>
/// <param name="decoder">The <see cref="OrigJpegDecoderCore"/></param> /// <param name="decoder">The <see cref="GolangJpegDecoderCore"/></param>
/// <param name="remaining">The remaining bytes</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) if (decoder.ComponentCount == 0)
{ {
@ -360,10 +359,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// </summary> /// </summary>
/// <param name="decoder">The decoder</param> /// <param name="decoder">The decoder</param>
/// <param name="scanIndex">The index of the scan</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; 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) if (this.ah != 0)
{ {
this.Refine(ref decoder.InputProcessor, ref decoder.HuffmanTrees[huffmannIdx], 1 << this.al); 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++; zig++;
// Decode the DC coefficient, as specified in section F.2.2.1. // 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( decoder.InputProcessor.DecodeHuffmanUnsafe(
ref decoder.HuffmanTrees[huffmanIndex], ref decoder.HuffmanTrees[huffmanIndex],
out int value); out int value);
@ -467,7 +466,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
private void DecodeEobRun(int count, ref InputProcessor processor) private void DecodeEobRun(int count, ref InputProcessor processor)
{ {
processor.DecodeBitsUnsafe(count, out int bitsResult); processor.DecodeBitsUnsafe(count, out int bitsResult);
if (processor.LastErrorCode != OrigDecoderErrorCode.NoError) if (processor.LastErrorCode != GolangDecoderErrorCode.NoError)
{ {
return; return;
} }
@ -475,7 +474,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
this.eobRun |= bitsResult; 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. // Component selector.
int cs = decoder.Temp[1 + (2 * i)]; int cs = decoder.Temp[1 + (2 * i)];
@ -500,11 +499,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
} }
private void ProcessComponentImpl( private void ProcessComponentImpl(
OrigJpegDecoderCore decoder, GolangJpegDecoderCore decoder,
int i, int i,
ref OrigComponentScan currentComponentScan, ref GolangComponentScan currentComponentScan,
ref int totalHv, ref int totalHv,
OrigComponent currentComponent) GolangComponent currentComponent)
{ {
// Section B.2.3 states that "the value of Cs_j shall be different from // 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 // 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; totalHv += currentComponent.HorizontalSamplingFactor * currentComponent.VerticalSamplingFactor;
currentComponentScan.DcTableSelector = (byte)(decoder.Temp[2 + (2 * i)] >> 4); 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"); throw new ImageFormatException("Bad DC table selector value");
} }
currentComponentScan.AcTableSelector = (byte)(decoder.Temp[2 + (2 * i)] & 0x0f); 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"); 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="bp">The <see cref="InputProcessor"/> instance</param>
/// <param name="h">The Huffman tree</param> /// <param name="h">The Huffman tree</param>
/// <param name="delta">The low transform offset</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; Block8x8* b = this.pointers.Block;
@ -560,7 +559,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
if (bit) if (bit)
{ {
int stuff = (int)Block8x8.GetScalarAt(b, 0); int stuff = Block8x8.GetScalarAt(b, 0);
// int stuff = (int)b[0]; // int stuff = (int)b[0];
stuff |= delta; 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 namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{ {
/// <summary> /// <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 /// It's a value type for imporved data locality, and reduced number of CALLVIRT-s
/// </summary> /// </summary>
internal struct InputProcessor : IDisposable 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. /// Initializes a new instance of the <see cref="InputProcessor"/> struct.
/// </summary> /// </summary>
/// <param name="inputStream">The input <see cref="Stream"/></param> /// <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) public InputProcessor(Stream inputStream, byte[] temp)
{ {
this.Bits = default(Bits); this.Bits = default;
this.Bytes = Bytes.Create(); this.Bytes = Bytes.Create();
this.InputStream = inputStream; this.InputStream = inputStream;
this.Temp = temp; this.Temp = temp;
this.LastErrorCode = OrigDecoderErrorCode.NoError; this.LastErrorCode = GolangDecoderErrorCode.NoError;
} }
/// <summary> /// <summary>
@ -43,20 +43,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
public Stream InputStream { get; } public Stream InputStream { get; }
/// <summary> /// <summary>
/// Gets the temporary buffer, same instance as <see cref="OrigJpegDecoderCore.Temp"/> /// Gets the temporary buffer, same instance as <see cref="GolangJpegDecoderCore.Temp"/>
/// </summary> /// </summary>
public byte[] Temp { get; } public byte[] Temp { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether an unexpected EOF reached in <see cref="InputStream"/>. /// Gets a value indicating whether an unexpected EOF reached in <see cref="InputStream"/>.
/// </summary> /// </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> /// <summary>
/// If errorCode indicates unexpected EOF, sets <see cref="ReachedEOF"/> to true and returns false. /// 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> /// <returns>A <see cref="bool"/> indicating whether EOF reached</returns>
public bool CheckEOFEnsureNoError() public bool CheckEOFEnsureNoError()
{ {
if (this.LastErrorCode == OrigDecoderErrorCode.UnexpectedEndOfStream) if (this.LastErrorCode == GolangDecoderErrorCode.UnexpectedEndOfStream)
{ {
return false; return false;
} }
@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <returns>A <see cref="bool"/> indicating whether EOF reached</returns> /// <returns>A <see cref="bool"/> indicating whether EOF reached</returns>
public bool CheckEOF() public bool CheckEOF()
{ {
if (this.LastErrorCode == OrigDecoderErrorCode.UnexpectedEndOfStream) if (this.LastErrorCode == GolangDecoderErrorCode.UnexpectedEndOfStream)
{ {
return false; return false;
} }
@ -106,7 +106,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public OrigDecoderErrorCode ReadByteUnsafe(out byte result) public GolangDecoderErrorCode ReadByteUnsafe(out byte result)
{ {
this.LastErrorCode = this.Bytes.ReadByteUnsafe(this.InputStream, out result); this.LastErrorCode = this.Bytes.ReadByteUnsafe(this.InputStream, out result);
return this.LastErrorCode; 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! /// TODO: This method (and also the usages) could be optimized by batching!
/// </summary> /// </summary>
/// <param name="result">The decoded bit as a <see cref="bool"/></param> /// <param name="result">The decoded bit as a <see cref="bool"/></param>
/// <returns>The <see cref="OrigDecoderErrorCode" /></returns> /// <returns>The <see cref="GolangDecoderErrorCode" /></returns>
public OrigDecoderErrorCode DecodeBitUnsafe(out bool result) public GolangDecoderErrorCode DecodeBitUnsafe(out bool result)
{ {
if (this.Bits.UnreadBits == 0) if (this.Bits.UnreadBits == 0)
{ {
this.LastErrorCode = this.Bits.Ensure1BitUnsafe(ref this); this.LastErrorCode = this.Bits.Ensure1BitUnsafe(ref this);
if (this.LastErrorCode != OrigDecoderErrorCode.NoError) if (this.LastErrorCode != GolangDecoderErrorCode.NoError)
{ {
result = false; result = false;
return this.LastErrorCode; return this.LastErrorCode;
@ -133,18 +133,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
result = (this.Bits.Accumulator & this.Bits.Mask) != 0; result = (this.Bits.Accumulator & this.Bits.Mask) != 0;
this.Bits.UnreadBits--; this.Bits.UnreadBits--;
this.Bits.Mask >>= 1; this.Bits.Mask >>= 1;
return this.LastErrorCode = OrigDecoderErrorCode.NoError; return this.LastErrorCode = GolangDecoderErrorCode.NoError;
} }
/// <summary> /// <summary>
/// Reads exactly length bytes into data. It does not care about byte stuffing. /// 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> /// </summary>
/// <param name="data">The data to write to.</param> /// <param name="data">The data to write to.</param>
/// <param name="offset">The offset in the source buffer</param> /// <param name="offset">The offset in the source buffer</param>
/// <param name="length">The number of bytes to read</param> /// <param name="length">The number of bytes to read</param>
/// <returns>The <see cref="OrigDecoderErrorCode"/></returns> /// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
public OrigDecoderErrorCode ReadFullUnsafe(byte[] data, int offset, int length) public GolangDecoderErrorCode ReadFullUnsafe(byte[] data, int offset, int length)
{ {
// Unread the overshot bytes, if any. // Unread the overshot bytes, if any.
if (this.Bytes.UnreadableBytes != 0) if (this.Bytes.UnreadableBytes != 0)
@ -157,8 +157,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
this.Bytes.UnreadableBytes = 0; this.Bytes.UnreadableBytes = 0;
} }
this.LastErrorCode = OrigDecoderErrorCode.NoError; this.LastErrorCode = GolangDecoderErrorCode.NoError;
while (length > 0 && this.LastErrorCode == OrigDecoderErrorCode.NoError) while (length > 0 && this.LastErrorCode == GolangDecoderErrorCode.NoError)
{ {
if (this.Bytes.J - this.Bytes.I >= length) if (this.Bytes.J - this.Bytes.I >= length)
{ {
@ -185,13 +185,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// </summary> /// </summary>
/// <param name="count">The number of bits to decode.</param> /// <param name="count">The number of bits to decode.</param>
/// <param name="result">The <see cref="uint" /> result</param> /// <param name="result">The <see cref="uint" /> result</param>
/// <returns>The <see cref="OrigDecoderErrorCode"/></returns> /// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
public OrigDecoderErrorCode DecodeBitsUnsafe(int count, out int result) public GolangDecoderErrorCode DecodeBitsUnsafe(int count, out int result)
{ {
if (this.Bits.UnreadBits < count) if (this.Bits.UnreadBits < count)
{ {
this.LastErrorCode = this.Bits.EnsureNBitsUnsafe(count, ref this); this.LastErrorCode = this.Bits.EnsureNBitsUnsafe(count, ref this);
if (this.LastErrorCode != OrigDecoderErrorCode.NoError) if (this.LastErrorCode != GolangDecoderErrorCode.NoError)
{ {
result = 0; result = 0;
return this.LastErrorCode; return this.LastErrorCode;
@ -202,7 +202,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
result = result & ((1 << count) - 1); result = result & ((1 << count) - 1);
this.Bits.UnreadBits -= count; this.Bits.UnreadBits -= count;
this.Bits.Mask >>= count; this.Bits.Mask >>= count;
return this.LastErrorCode = OrigDecoderErrorCode.NoError; return this.LastErrorCode = GolangDecoderErrorCode.NoError;
} }
/// <summary> /// <summary>
@ -210,8 +210,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// </summary> /// </summary>
/// <param name="huffmanTree">The huffman value</param> /// <param name="huffmanTree">The huffman value</param>
/// <param name="result">The decoded <see cref="byte" /></param> /// <param name="result">The decoded <see cref="byte" /></param>
/// <returns>The <see cref="OrigDecoderErrorCode"/></returns> /// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
public OrigDecoderErrorCode DecodeHuffmanUnsafe(ref OrigHuffmanTree huffmanTree, out int result) public GolangDecoderErrorCode DecodeHuffmanUnsafe(ref GolangHuffmanTree huffmanTree, out int result)
{ {
result = 0; result = 0;
@ -224,9 +224,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{ {
this.LastErrorCode = this.Bits.Ensure8BitsUnsafe(ref this); 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]; int v = huffmanTree.Lut[lutIndex];
if (v != 0) if (v != 0)
@ -246,7 +246,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
} }
int code = 0; int code = 0;
for (int i = 0; i < OrigHuffmanTree.MaxCodeLength; i++) for (int i = 0; i < GolangHuffmanTree.MaxCodeLength; i++)
{ {
if (this.Bits.UnreadBits == 0) if (this.Bits.UnreadBits == 0)
{ {
@ -269,7 +269,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
if (code <= huffmanTree.MaxCodes[i]) if (code <= huffmanTree.MaxCodes[i])
{ {
result = huffmanTree.GetValue(code, i); result = huffmanTree.GetValue(code, i);
return this.LastErrorCode = OrigDecoderErrorCode.NoError; return this.LastErrorCode = GolangDecoderErrorCode.NoError;
} }
code <<= 1; code <<= 1;
@ -279,7 +279,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
DecoderThrowHelper.ThrowImageFormatException.BadHuffmanCode(); DecoderThrowHelper.ThrowImageFormatException.BadHuffmanCode();
// DUMMY RETURN! C# doesn't know we have thrown an exception! // DUMMY RETURN! C# doesn't know we have thrown an exception!
return OrigDecoderErrorCode.NoError; return GolangDecoderErrorCode.NoError;
} }
/// <summary> /// <summary>
@ -295,11 +295,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <summary> /// <summary>
/// Skips the next n bytes. /// Skips the next n bytes.
/// Does not throw, returns <see cref="OrigDecoderErrorCode"/> instead! /// Does not throw, returns <see cref="GolangDecoderErrorCode"/> instead!
/// </summary> /// </summary>
/// <param name="count">The number of bytes to ignore.</param> /// <param name="count">The number of bytes to ignore.</param>
/// <returns>The <see cref="OrigDecoderErrorCode"/></returns> /// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
public OrigDecoderErrorCode SkipUnsafe(int count) public GolangDecoderErrorCode SkipUnsafe(int count)
{ {
// Unread the overshot bytes, if any. // Unread the overshot bytes, if any.
if (this.Bytes.UnreadableBytes != 0) if (this.Bytes.UnreadableBytes != 0)
@ -328,13 +328,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
} }
this.LastErrorCode = this.Bytes.FillUnsafe(this.InputStream); this.LastErrorCode = this.Bytes.FillUnsafe(this.InputStream);
if (this.LastErrorCode != OrigDecoderErrorCode.NoError) if (this.LastErrorCode != GolangDecoderErrorCode.NoError)
{ {
return this.LastErrorCode; return this.LastErrorCode;
} }
} }
return this.LastErrorCode = OrigDecoderErrorCode.NoError; return this.LastErrorCode = GolangDecoderErrorCode.NoError;
} }
/// <summary> /// <summary>
@ -374,8 +374,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// </summary> /// </summary>
/// <param name="t">Byte</param> /// <param name="t">Byte</param>
/// <param name="x">Read bits value</param> /// <param name="x">Read bits value</param>
/// <returns>The <see cref="OrigDecoderErrorCode"/></returns> /// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
public OrigDecoderErrorCode ReceiveExtendUnsafe(int t, out int x) public GolangDecoderErrorCode ReceiveExtendUnsafe(int t, out int x)
{ {
this.LastErrorCode = this.Bits.ReceiveExtendUnsafe(t, ref this, out x); this.LastErrorCode = this.Bits.ReceiveExtendUnsafe(t, ref this, out x);
return this.LastErrorCode; return this.LastErrorCode;
@ -386,7 +386,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// </summary> /// </summary>
public void ResetHuffmanDecoder() 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> /// <summary>
/// Image decoder for generating an image out of a jpg stream. /// Image decoder for generating an image out of a jpg stream.
/// </summary> /// </summary>
internal sealed class OrigJpegDecoder : IImageDecoder, IJpegDecoderOptions, IImageInfoDetector internal sealed class GolangJpegDecoder : IImageDecoder, IJpegDecoderOptions, IImageInfoDetector
{ {
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. /// 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)); Guard.NotNull(stream, nameof(stream));
using (var decoder = new OrigJpegDecoderCore(configuration, this)) using (var decoder = new GolangJpegDecoderCore(configuration, this))
{ {
return decoder.Decode<TPixel>(stream); return decoder.Decode<TPixel>(stream);
} }
@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
{ {
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
using (var decoder = new OrigJpegDecoderCore(configuration, this)) using (var decoder = new GolangJpegDecoderCore(configuration, this))
{ {
return decoder.Identify(stream); 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.Collections.Generic;
using System.IO; 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.Formats.Jpeg.GolangPort.Components.Decoder;
using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.MetaData.Profiles.Exif; using SixLabors.ImageSharp.MetaData.Profiles.Exif;
@ -19,7 +20,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// <summary> /// <summary>
/// Performs the jpeg decoding operation. /// Performs the jpeg decoding operation.
/// </summary> /// </summary>
internal sealed unsafe class OrigJpegDecoderCore : IRawJpegData internal sealed unsafe class GolangJpegDecoderCore : IRawJpegData
{ {
/// <summary> /// <summary>
/// The maximum number of color components /// The maximum number of color components
@ -40,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
#pragma warning disable SA1401 // FieldsMustBePrivate #pragma warning disable SA1401 // FieldsMustBePrivate
/// <summary> /// <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 /// It's a value type for improved data locality, and reduced number of CALLVIRT-s
/// </summary> /// </summary>
public InputProcessor InputProcessor; public InputProcessor InputProcessor;
@ -79,16 +80,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
private AdobeMarker adobe; private AdobeMarker adobe;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="OrigJpegDecoderCore" /> class. /// Initializes a new instance of the <see cref="GolangJpegDecoderCore" /> class.
/// </summary> /// </summary>
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
public OrigJpegDecoderCore(Configuration configuration, IJpegDecoderOptions options) public GolangJpegDecoderCore(Configuration configuration, IJpegDecoderOptions options)
{ {
this.IgnoreMetadata = options.IgnoreMetadata; this.IgnoreMetadata = options.IgnoreMetadata;
this.configuration = configuration ?? Configuration.Default; this.configuration = configuration ?? Configuration.Default;
this.HuffmanTrees = OrigHuffmanTree.CreateHuffmanTrees();
this.QuantizationTables = new Block8x8F[MaxTq + 1];
this.Temp = new byte[2 * Block8x8F.Size]; this.Temp = new byte[2 * Block8x8F.Size];
} }
@ -98,15 +97,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// <summary> /// <summary>
/// Gets the component array /// Gets the component array
/// </summary> /// </summary>
public OrigComponent[] Components { get; private set; } public GolangComponent[] Components { get; private set; }
/// <summary> /// <summary>
/// Gets the huffman trees /// Gets the huffman trees
/// </summary> /// </summary>
public OrigHuffmanTree[] HuffmanTrees { get; } public GolangHuffmanTree[] HuffmanTrees { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public Block8x8F[] QuantizationTables { get; } public Block8x8F[] QuantizationTables { get; private set; }
/// <summary> /// <summary>
/// Gets the temporary buffer used to store bytes read from the stream. /// 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> where TPixel : struct, IPixel<TPixel>
{ {
this.ParseStream(stream); this.ParseStream(stream);
return this.PostProcessIntoImage<TPixel>(); return this.PostProcessIntoImage<TPixel>();
} }
@ -204,7 +202,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
public IImageInfo Identify(Stream stream) public IImageInfo Identify(Stream stream)
{ {
this.ParseStream(stream, true); this.ParseStream(stream, true);
return new ImageInfo(new PixelTypeInfo(this.BitsPerPixel), this.ImageWidth, this.ImageHeight, this.MetaData); 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) 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> /// <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> /// </summary>
/// <param name="stream">The stream</param> /// <param name="stream">The stream</param>
/// <param name="metadataOnly">Whether to decode metadata only.</param> /// <param name="metadataOnly">Whether to decode metadata only.</param>
@ -233,10 +230,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
this.InputStream = stream; this.InputStream = stream;
this.InputProcessor = new InputProcessor(stream, this.Temp); 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. // Check for the Start Of Image marker.
this.InputProcessor.ReadFull(this.Temp, 0, 2); 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."); throw new ImageFormatException("Missing SOI marker.");
} }
@ -300,12 +303,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
} }
// End Of Image. // End Of Image.
if (marker == OrigJpegConstants.Markers.EOI) if (marker == JpegConstants.Markers.EOI)
{ {
break; 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 // 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. // only occur between Entropy Coded Segments and not after the final ECS.
@ -327,18 +330,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
switch (marker) switch (marker)
{ {
case OrigJpegConstants.Markers.SOF0: case JpegConstants.Markers.SOF0:
case OrigJpegConstants.Markers.SOF1: case JpegConstants.Markers.SOF1:
case OrigJpegConstants.Markers.SOF2: case JpegConstants.Markers.SOF2:
this.IsProgressive = marker == OrigJpegConstants.Markers.SOF2; this.IsProgressive = marker == JpegConstants.Markers.SOF2;
this.ProcessStartOfFrameMarker(remaining, metadataOnly); this.ProcessStartOfFrameMarker(remaining, metadataOnly);
if (metadataOnly && this.isJFif)
{
return;
}
break; break;
case OrigJpegConstants.Markers.DHT: case JpegConstants.Markers.DHT:
if (metadataOnly) if (metadataOnly)
{ {
this.InputProcessor.Skip(remaining); this.InputProcessor.Skip(remaining);
@ -349,7 +348,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
} }
break; break;
case OrigJpegConstants.Markers.DQT: case JpegConstants.Markers.DQT:
if (metadataOnly) if (metadataOnly)
{ {
this.InputProcessor.Skip(remaining); this.InputProcessor.Skip(remaining);
@ -360,21 +359,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
} }
break; break;
case OrigJpegConstants.Markers.SOS: case JpegConstants.Markers.SOS:
if (metadataOnly) 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;
}
} }
else
this.ProcessStartOfScanMarker(remaining);
if (this.InputProcessor.ReachedEOF)
{ {
// 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; processBytes = false;
} }
break; break;
case OrigJpegConstants.Markers.DRI:
case JpegConstants.Markers.DRI:
if (metadataOnly) if (metadataOnly)
{ {
this.InputProcessor.Skip(remaining); this.InputProcessor.Skip(remaining);
@ -385,21 +389,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
} }
break; break;
case OrigJpegConstants.Markers.APP0: case JpegConstants.Markers.APP0:
this.ProcessApplicationHeaderMarker(remaining); this.ProcessApplicationHeaderMarker(remaining);
break; break;
case OrigJpegConstants.Markers.APP1: case JpegConstants.Markers.APP1:
this.ProcessApp1Marker(remaining); this.ProcessApp1Marker(remaining);
break; break;
case OrigJpegConstants.Markers.APP2: case JpegConstants.Markers.APP2:
this.ProcessApp2Marker(remaining); this.ProcessApp2Marker(remaining);
break; break;
case OrigJpegConstants.Markers.APP14: case JpegConstants.Markers.APP14:
this.ProcessApp14Marker(remaining); this.ProcessApp14Marker(remaining);
break; break;
default: default:
if ((marker >= OrigJpegConstants.Markers.APP0 && marker <= OrigJpegConstants.Markers.APP15) if ((marker >= JpegConstants.Markers.APP0 && marker <= JpegConstants.Markers.APP15)
|| marker == OrigJpegConstants.Markers.COM) || marker == JpegConstants.Markers.COM)
{ {
this.InputProcessor.Skip(remaining); this.InputProcessor.Skip(remaining);
} }
@ -425,7 +429,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// </summary> /// </summary>
private void InitDerivedMetaDataProperties() 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) double horizontalValue = this.MetaData.ExifProfile.TryGetValue(ExifTag.XResolution, out ExifValue horizonalTag)
? ((Rational)horizonalTag.Value).ToDouble() ? ((Rational)horizonalTag.Value).ToDouble()
@ -441,11 +450,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
this.MetaData.VerticalResolution = verticalValue; this.MetaData.VerticalResolution = verticalValue;
} }
} }
else if (this.isJFif)
{
this.MetaData.HorizontalResolution = this.jFif.XDensity;
this.MetaData.VerticalResolution = this.jFif.YDensity;
}
} }
/// <summary> /// <summary>
@ -675,26 +679,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
throw new ImageFormatException("SOF has wrong length"); throw new ImageFormatException("SOF has wrong length");
} }
this.Components = new OrigComponent[this.ComponentCount]; if (!metadataOnly)
for (int i = 0; i < this.ComponentCount; i++)
{ {
byte componentIdentifier = this.Temp[6 + (3 * i)]; this.Components = new GolangComponent[this.ComponentCount];
var component = new OrigComponent(componentIdentifier, i);
component.InitializeCoreData(this);
this.Components[i] = component;
}
int h0 = this.Components[0].HorizontalSamplingFactor; for (int i = 0; i < this.ComponentCount; i++)
int v0 = this.Components[0].VerticalSamplingFactor; {
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) this.ColorSpace = this.DeduceJpegColorSpace();
{
component.InitializeDerivedData(this.configuration.MemoryManager, this, metadataOnly); 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); this.InputProcessor.ReadFull(this.Temp, 0, 17);
int tc = this.Temp[0] >> 4; int tc = this.Temp[0] >> 4;
if (tc > OrigHuffmanTree.MaxTc) if (tc > GolangHuffmanTree.MaxTc)
{ {
throw new ImageFormatException("Bad Tc value"); throw new ImageFormatException("Bad Tc value");
} }
int th = this.Temp[0] & 0x0f; int th = this.Temp[0] & 0x0f;
if (th > OrigHuffmanTree.MaxTh) if (th > GolangHuffmanTree.MaxTh)
{ {
throw new ImageFormatException("Bad Th value"); throw new ImageFormatException("Bad Th value");
} }
int huffTreeIndex = (tc * OrigHuffmanTree.ThRowSize) + th; int huffTreeIndex = (tc * GolangHuffmanTree.ThRowSize) + th;
this.HuffmanTrees[huffTreeIndex].ProcessDefineHuffmanTablesMarkerLoop( this.HuffmanTrees[huffTreeIndex].ProcessDefineHuffmanTablesMarkerLoop(
ref this.InputProcessor, ref this.InputProcessor,
this.Temp, this.Temp,
@ -760,9 +767,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// </exception> /// </exception>
private void ProcessStartOfScanMarker(int remaining) private void ProcessStartOfScanMarker(int remaining)
{ {
var scan = default(OrigJpegScanDecoder); var scan = default(GolangJpegScanDecoder);
OrigJpegScanDecoder.InitStreamReading(&scan, this, remaining); GolangJpegScanDecoder.InitStreamReading(&scan, this, remaining);
this.InputProcessor.Bits = default(Bits); this.InputProcessor.Bits = default;
scan.DecodeBlocks(this); scan.DecodeBlocks(this);
} }
@ -773,19 +780,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
case 1: case 1:
return JpegColorSpace.Grayscale; return JpegColorSpace.Grayscale;
case 3: case 3:
if (!this.isAdobe || this.adobe.ColorTransform == OrigJpegConstants.Adobe.ColorTransformYCbCr) if (!this.isAdobe || this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYCbCr)
{ {
return JpegColorSpace.YCbCr; return JpegColorSpace.YCbCr;
} }
if (this.adobe.ColorTransform == OrigJpegConstants.Adobe.ColorTransformUnknown) if (this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown)
{ {
return JpegColorSpace.RGB; return JpegColorSpace.RGB;
} }
break; break;
case 4: case 4:
if (this.adobe.ColorTransform == OrigJpegConstants.Adobe.ColorTransformYcck) if (this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYcck)
{ {
return JpegColorSpace.Ycck; 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. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// ReSharper disable InconsistentNaming using System.Collections.Generic;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
namespace SixLabors.ImageSharp.Formats.Jpeg
{ {
/// <summary> /// <summary>
/// Contains jpeg constant values /// Contains jpeg constant values defined in the specification.
/// </summary> /// </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> /// <summary>
/// Contains marker specific constants /// Contains marker specific constants
/// </summary> /// </summary>
public static class Markers // ReSharper disable InconsistentNaming
internal static class Markers
{ {
/// <summary> /// <summary>
/// The prefix used for all markers. /// The prefix used for all markers.
/// </summary> /// </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> /// <summary>
/// The Start of Image marker /// The Start of Image marker
/// </summary> /// </summary>
public const ushort SOI = 0xFFD8; public const byte SOI = 0xD8;
/// <summary> /// <summary>
/// The End of Image marker /// The End of Image marker
/// </summary> /// </summary>
public const ushort EOI = 0xFFD9; public const byte EOI = 0xD9;
/// <summary> /// <summary>
/// Application specific marker for marking the jpeg format. /// Application specific marker for marking the jpeg format.
/// <see href="http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html"/> /// <see href="http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html"/>
/// </summary> /// </summary>
public const ushort APP0 = 0xFFE0; public const byte APP0 = 0xE0;
/// <summary> /// <summary>
/// Application specific marker for marking where to store metadata. /// Application specific marker for marking where to store metadata.
/// </summary> /// </summary>
public const ushort APP1 = 0xFFE1; public const byte APP1 = 0xE1;
/// <summary> /// <summary>
/// Application specific marker for marking where to store ICC profile information. /// Application specific marker for marking where to store ICC profile information.
/// </summary> /// </summary>
public const ushort APP2 = 0xFFE2; public const byte APP2 = 0xE2;
/// <summary> /// <summary>
/// Application specific marker. /// Application specific marker.
/// </summary> /// </summary>
public const ushort APP3 = 0xFFE3; public const byte APP3 = 0xE3;
/// <summary> /// <summary>
/// Application specific marker. /// Application specific marker.
/// </summary> /// </summary>
public const ushort APP4 = 0xFFE4; public const byte APP4 = 0xE4;
/// <summary> /// <summary>
/// Application specific marker. /// Application specific marker.
/// </summary> /// </summary>
public const ushort APP5 = 0xFFE5; public const byte APP5 = 0xE5;
/// <summary> /// <summary>
/// Application specific marker. /// Application specific marker.
/// </summary> /// </summary>
public const ushort APP6 = 0xFFE6; public const byte APP6 = 0xE6;
/// <summary> /// <summary>
/// Application specific marker. /// Application specific marker.
/// </summary> /// </summary>
public const ushort APP7 = 0xFFE7; public const byte APP7 = 0xE7;
/// <summary> /// <summary>
/// Application specific marker. /// Application specific marker.
/// </summary> /// </summary>
public const ushort APP8 = 0xFFE8; public const byte APP8 = 0xE8;
/// <summary> /// <summary>
/// Application specific marker. /// Application specific marker.
/// </summary> /// </summary>
public const ushort APP9 = 0xFFE9; public const byte APP9 = 0xE9;
/// <summary> /// <summary>
/// Application specific marker. /// Application specific marker.
/// </summary> /// </summary>
public const ushort APP10 = 0xFFEA; public const byte APP10 = 0xEA;
/// <summary> /// <summary>
/// Application specific marker. /// Application specific marker.
/// </summary> /// </summary>
public const ushort APP11 = 0xFFEB; public const byte APP11 = 0xEB;
/// <summary> /// <summary>
/// Application specific marker. /// Application specific marker.
/// </summary> /// </summary>
public const ushort APP12 = 0xFFEC; public const byte APP12 = 0xEC;
/// <summary> /// <summary>
/// Application specific marker. /// Application specific marker.
/// </summary> /// </summary>
public const ushort APP13 = 0xFFED; public const byte APP13 = 0xED;
/// <summary> /// <summary>
/// Application specific marker used by Adobe for storing encoding information for DCT filters. /// Application specific marker used by Adobe for storing encoding information for DCT filters.
/// </summary> /// </summary>
public const ushort APP14 = 0xFFEE; public const byte APP14 = 0xEE;
/// <summary> /// <summary>
/// Application specific marker used by GraphicConverter to store JPEG quality. /// Application specific marker used by GraphicConverter to store JPEG quality.
/// </summary> /// </summary>
public const ushort APP15 = 0xFFEF; public const byte APP15 = 0xEF;
/// <summary> /// <summary>
/// The text comment marker /// The text comment marker
/// </summary> /// </summary>
public const ushort COM = 0xFFFE; public const byte COM = 0xFE;
/// <summary> /// <summary>
/// Define Quantization Table(s) marker /// Define Quantization Table(s) marker
@ -121,7 +143,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// Specifies one or more quantization tables. /// Specifies one or more quantization tables.
/// </remarks> /// </remarks>
/// </summary> /// </summary>
public const ushort DQT = 0xFFDB; public const byte DQT = 0xDB;
/// <summary> /// <summary>
/// Start of Frame (baseline DCT) /// Start of Frame (baseline DCT)
@ -130,7 +152,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// and component subsampling (e.g., 4:2:0). /// and component subsampling (e.g., 4:2:0).
/// </remarks> /// </remarks>
/// </summary> /// </summary>
public const ushort SOF0 = 0xFFC0; public const byte SOF0 = 0xC0;
/// <summary> /// <summary>
/// Start Of Frame (Extended Sequential DCT) /// Start Of Frame (Extended Sequential DCT)
@ -139,7 +161,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// and component subsampling (e.g., 4:2:0). /// and component subsampling (e.g., 4:2:0).
/// </remarks> /// </remarks>
/// </summary> /// </summary>
public const ushort SOF1 = 0xFFC1; public const byte SOF1 = 0xC1;
/// <summary> /// <summary>
/// Start Of Frame (progressive DCT) /// Start Of Frame (progressive DCT)
@ -148,7 +170,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// and component subsampling (e.g., 4:2:0). /// and component subsampling (e.g., 4:2:0).
/// </remarks> /// </remarks>
/// </summary> /// </summary>
public const ushort SOF2 = 0xFFC2; public const byte SOF2 = 0xC2;
/// <summary> /// <summary>
/// Define Huffman Table(s) /// Define Huffman Table(s)
@ -156,15 +178,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// Specifies one or more Huffman tables. /// Specifies one or more Huffman tables.
/// </remarks> /// </remarks>
/// </summary> /// </summary>
public const ushort DHT = 0xFFC4; public const byte DHT = 0xC4;
/// <summary> /// <summary>
/// Define Restart Interval /// Define Restart Interval
/// <remarks> /// <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> /// </remarks>
/// </summary> /// </summary>
public const ushort DRI = 0xFFDD; public const byte DRI = 0xDD;
/// <summary> /// <summary>
/// Start of Scan /// Start of Scan
@ -174,7 +197,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// will contain, and is immediately followed by entropy-coded data. /// will contain, and is immediately followed by entropy-coded data.
/// </remarks> /// </remarks>
/// </summary> /// </summary>
public const ushort SOS = 0xFFDA; public const byte SOS = 0xDA;
/// <summary> /// <summary>
/// Define First Restart /// 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. /// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7.
/// </remarks> /// </remarks>
/// </summary> /// </summary>
public const ushort RST0 = 0xFFD0; public const byte RST0 = 0xD0;
/// <summary> /// <summary>
/// Define Eigth Restart /// 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. /// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7.
/// </remarks> /// </remarks>
/// </summary> /// </summary>
public const ushort RST7 = 0xFFD7; public const byte RST7 = 0xD7;
}
/// <summary>
/// Contains Adobe specific constants
/// </summary>
internal static class Adobe
{
/// <summary> /// <summary>
/// Contains Adobe specific markers /// The color transform is unknown.(RGB or CMYK)
/// </summary> /// </summary>
public static class Adobe public const byte ColorTransformUnknown = 0;
{
/// <summary>
/// The color transform is unknown.(RGB or CMYK)
/// </summary>
public const byte ColorTransformUnknown = 0;
/// <summary> /// <summary>
/// The color transform is YCbCr (luminance, red chroma, blue chroma) /// The color transform is YCbCr (luminance, red chroma, blue chroma)
/// </summary> /// </summary>
public const byte ColorTransformYCbCr = 1; public const byte ColorTransformYCbCr = 1;
/// <summary> /// <summary>
/// The color transform is YCCK (luminance, red chroma, blue chroma, keyline) /// The color transform is YCCK (luminance, red chroma, blue chroma, keyline)
/// </summary> /// </summary>
public const byte ColorTransformYcck = 2; public const byte ColorTransformYcck = 2;
}
} }
} }
} }

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

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

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

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.IO; using System.IO;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg 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. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Buffers;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder; using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData.Profiles.Exif; using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.MetaData.Profiles.Icc; using SixLabors.ImageSharp.MetaData.Profiles.Icc;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using Block8x8F = SixLabors.ImageSharp.Formats.Jpeg.Common.Block8x8F;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort namespace SixLabors.ImageSharp.Formats.Jpeg
{ {
/// <summary> /// <summary>
/// Image encoder for writing an image to a stream as a jpeg. /// Image encoder for writing an image to a stream as a jpeg.
@ -58,7 +55,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// </summary> /// </summary>
private static readonly byte[] SosHeaderYCbCr = private static readonly byte[] SosHeaderYCbCr =
{ {
OrigJpegConstants.Markers.XFF, OrigJpegConstants.Markers.SOS, JpegConstants.Markers.XFF, JpegConstants.Markers.SOS,
// Marker // Marker
0x00, 0x0c, 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> /// <summary>
/// A scratch buffer to reduce allocations. /// A scratch buffer to reduce allocations.
/// </summary> /// </summary>
@ -190,7 +182,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
Guard.NotNull(image, nameof(image)); Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
ushort max = OrigJpegConstants.MaxLength; ushort max = JpegConstants.MaxLength;
if (image.Width >= max || image.Height >= max) if (image.Width >= max || image.Height >= max)
{ {
throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}."); throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}.");
@ -234,8 +226,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
this.WriteStartOfScan(image); this.WriteStartOfScan(image);
// Write the End Of Image marker. // Write the End Of Image marker.
this.buffer[0] = OrigJpegConstants.Markers.XFF; this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = OrigJpegConstants.Markers.EOI; this.buffer[1] = JpegConstants.Markers.EOI;
stream.Write(this.buffer, 0, 2); stream.Write(this.buffer, 0, 2);
stream.Flush(); 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.) // TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
// (Partially done with YCbCrForwardConverter<TPixel>) // (Partially done with YCbCrForwardConverter<TPixel>)
Block8x8F temp1 = default(Block8x8F); Block8x8F temp1 = default;
Block8x8F temp2 = default(Block8x8F); Block8x8F temp2 = default;
Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable; Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable;
Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable; Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable;
ZigZag unzig = ZigZag.CreateUnzigTable(); var unzig = ZigZag.CreateUnzigTable();
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; 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) 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) private void WriteApplicationHeader(short horizontalResolution, short verticalResolution)
{ {
// Write the start of image marker. Markers are always prefixed with with 0xff. // Write the start of image marker. Markers are always prefixed with with 0xff.
this.buffer[0] = OrigJpegConstants.Markers.XFF; this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = OrigJpegConstants.Markers.SOI; this.buffer[1] = JpegConstants.Markers.SOI;
// Write the JFIF headers // Write the JFIF headers
this.buffer[2] = OrigJpegConstants.Markers.XFF; this.buffer[2] = JpegConstants.Markers.XFF;
this.buffer[3] = OrigJpegConstants.Markers.APP0; // Application Marker this.buffer[3] = JpegConstants.Markers.APP0; // Application Marker
this.buffer[4] = 0x00; this.buffer[4] = 0x00;
this.buffer[5] = 0x10; this.buffer[5] = 0x10;
this.buffer[6] = 0x4a; // J 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); this.EmitHuffRLE((HuffIndex)((2 * (int)index) + 0), 0, dc - prevDC);
// Emit the AC components. // Emit the AC components.
HuffIndex h = (HuffIndex)((2 * (int)index) + 1); var h = (HuffIndex)((2 * (int)index) + 1);
int runLength = 0; int runLength = 0;
for (int zig = 1; zig < Block8x8F.Size; zig++) for (int zig = 1; zig < Block8x8F.Size; zig++)
@ -556,7 +548,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
markerlen += 1 + 16 + s.Values.Length; 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++) for (int i = 0; i < specs.Length; i++)
{ {
HuffmanSpec spec = specs[i]; HuffmanSpec spec = specs[i];
@ -590,7 +582,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
{ {
// Marker + quantization table lengths // Marker + quantization table lengths
int markerlen = 2 + (QuantizationTableCount * (1 + Block8x8F.Size)); 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. // Loop through and collect the tables as one array.
// This allows us to reduce the number of writes to the stream. // 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; int length = data.Length + 2;
this.buffer[0] = OrigJpegConstants.Markers.XFF; this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = OrigJpegConstants.Markers.APP1; // Application Marker this.buffer[1] = JpegConstants.Markers.APP1; // Application Marker
this.buffer[2] = (byte)((length >> 8) & 0xFF); this.buffer[2] = (byte)((length >> 8) & 0xFF);
this.buffer[3] = (byte)(length & 0xFF); this.buffer[3] = (byte)(length & 0xFF);
@ -686,8 +678,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
dataLength -= length; dataLength -= length;
this.buffer[0] = OrigJpegConstants.Markers.XFF; this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = OrigJpegConstants.Markers.APP2; // Application Marker this.buffer[1] = JpegConstants.Markers.APP2; // Application Marker
int markerLength = length + 16; int markerLength = length + 16;
this.buffer[2] = (byte)((markerLength >> 8) & 0xFF); this.buffer[2] = (byte)((markerLength >> 8) & 0xFF);
this.buffer[3] = (byte)(markerLength & 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. // Length (high byte, low byte), 8 + components * 3.
int markerlen = 8 + (3 * componentCount); 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[0] = 8; // Data Precision. 8 for now, 12 and 16 bit jpegs not supported
this.buffer[1] = (byte)(height >> 8); this.buffer[1] = (byte)(height >> 8);
this.buffer[2] = (byte)(height & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported 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> where TPixel : struct, IPixel<TPixel>
{ {
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) // 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 cb = default;
BlockQuad cr = default(BlockQuad); BlockQuad cr = default;
Block8x8F* cbPtr = (Block8x8F*)cb.Data; var cbPtr = (Block8x8F*)cb.Data;
Block8x8F* crPtr = (Block8x8F*)cr.Data; var crPtr = (Block8x8F*)cr.Data;
Block8x8F temp1 = default(Block8x8F); Block8x8F temp1 = default;
Block8x8F temp2 = default(Block8x8F); Block8x8F temp2 = default;
Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable; Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable;
Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable; Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable;
ZigZag unzig = ZigZag.CreateUnzigTable(); var unzig = ZigZag.CreateUnzigTable();
var pixelConverter = YCbCrForwardConverter<TPixel>.Create(); var pixelConverter = YCbCrForwardConverter<TPixel>.Create();
@ -902,7 +894,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
private void WriteMarkerHeader(byte marker, int length) private void WriteMarkerHeader(byte marker, int length)
{ {
// Markers are always prefixed with with 0xff. // 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[1] = marker;
this.buffer[2] = (byte)(length >> 8); this.buffer[2] = (byte)(length >> 8);
this.buffer[3] = (byte)(length & 0xff); 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. // Licensed under the Apache License, Version 2.0.
using System.Collections.Generic; using System.Collections.Generic;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
namespace SixLabors.ImageSharp.Formats.Jpeg namespace SixLabors.ImageSharp.Formats.Jpeg
{ {
@ -18,9 +17,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public string DefaultMimeType => "image/jpeg"; public string DefaultMimeType => "image/jpeg";
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerable<string> MimeTypes => OrigJpegConstants.MimeTypes; public IEnumerable<string> MimeTypes => JpegConstants.MimeTypes;
/// <inheritdoc/> /// <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. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{ {
/// <summary> /// <summary>
@ -13,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// </summary> /// </summary>
/// <param name="marker">The marker</param> /// <param name="marker">The marker</param>
/// <param name="position">The position within the stream</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.Marker = marker;
this.Position = position; this.Position = position;
@ -26,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// <param name="marker">The marker</param> /// <param name="marker">The marker</param>
/// <param name="position">The position within the stream</param> /// <param name="position">The position within the stream</param>
/// <param name="invalid">Whether the current marker is invalid</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.Marker = marker;
this.Position = position; this.Position = position;
@ -36,17 +38,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// <summary> /// <summary>
/// Gets a value indicating whether the current marker is invalid /// Gets a value indicating whether the current marker is invalid
/// </summary> /// </summary>
public bool Invalid { get; } public bool Invalid
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
}
/// <summary> /// <summary>
/// Gets the position of the marker within a stream /// Gets the position of the marker within a stream
/// </summary> /// </summary>
public ushort Marker { get; } public byte Marker
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
}
/// <summary> /// <summary>
/// Gets the position of the marker within a stream /// Gets the position of the marker within a stream
/// </summary> /// </summary>
public long Position { get; } public long Position
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
}
/// <inheritdoc/> /// <inheritdoc/>
public override string ToString() public override string ToString()

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

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

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

@ -5,10 +5,10 @@ using System;
#if DEBUG #if DEBUG
using System.Diagnostics; using System.Diagnostics;
#endif #endif
using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{ {
@ -21,6 +21,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
private byte[] markerBuffer; private byte[] markerBuffer;
private int mcuToRead;
private int mcusPerLine;
private int mcu;
private int bitsData; private int bitsData;
private int bitsCount; private int bitsCount;
@ -60,7 +66,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// <param name="successive">The successive approximation bit low end</param> /// <param name="successive">The successive approximation bit low end</param>
public void DecodeScan( public void DecodeScan(
PdfJsFrame frame, PdfJsFrame frame,
Stream stream, DoubleBufferedStreamReader stream,
PdfJsHuffmanTables dcHuffmanTables, PdfJsHuffmanTables dcHuffmanTables,
PdfJsHuffmanTables acHuffmanTables, PdfJsHuffmanTables acHuffmanTables,
PdfJsFrameComponent[] components, PdfJsFrameComponent[] components,
@ -82,9 +88,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
this.unexpectedMarkerReached = false; this.unexpectedMarkerReached = false;
bool progressive = frame.Progressive; bool progressive = frame.Progressive;
int mcusPerLine = frame.McusPerLine; this.mcusPerLine = frame.McusPerLine;
int mcu = 0; this.mcu = 0;
int mcuExpected; int mcuExpected;
if (componentsLength == 1) if (componentsLength == 1)
{ {
@ -92,51 +98,46 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
} }
else else
{ {
mcuExpected = mcusPerLine * frame.McusPerColumn; mcuExpected = this.mcusPerLine * frame.McusPerColumn;
} }
PdfJsFileMarker fileMarker; while (this.mcu < mcuExpected)
while (mcu < mcuExpected)
{ {
// Reset interval stuff // 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++) for (int i = 0; i < components.Length; i++)
{ {
PdfJsFrameComponent c = components[i]; PdfJsFrameComponent c = components[i];
c.Pred = 0; c.DcPredictor = 0;
} }
this.eobrun = 0; this.eobrun = 0;
if (!progressive) if (!progressive)
{ {
this.DecodeScanBaseline(dcHuffmanTables, acHuffmanTables, components, componentsLength, mcusPerLine, mcuToRead, ref mcu, stream); this.DecodeScanBaseline(dcHuffmanTables, acHuffmanTables, components, componentsLength, stream);
} }
else else
{ {
bool isAc = this.specStart != 0; bool isAc = this.specStart != 0;
bool isFirst = successivePrev == 0; bool isFirst = successivePrev == 0;
PdfJsHuffmanTables huffmanTables = isAc ? acHuffmanTables : dcHuffmanTables; 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; this.bitsCount = 0;
fileMarker = PdfJsJpegDecoderCore.FindNextFileMarker(this.markerBuffer, stream); this.bitsData = 0;
this.unexpectedMarkerReached = false;
// 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
}
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 // 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; continue;
} }
@ -148,24 +149,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
stream.Position = fileMarker.Position; stream.Position = fileMarker.Position;
break; 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 #if DEBUG
Debug.WriteLine($"DecodeScan - Unexpected MCU data at {stream.Position}, next marker is: {fileMarker.Marker:X}"); Debug.WriteLine($"DecodeScan - Unexpected MCU data at {stream.Position}, next marker is: {fileMarker.Marker:X}");
#endif #endif
} }
else
{
// We've found a valid marker.
// Rewind the stream to the position of the marker
stream.Position = fileMarker.Position;
}
} }
private void DecodeScanBaseline( private void DecodeScanBaseline(
@ -173,10 +161,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
PdfJsHuffmanTables acHuffmanTables, PdfJsHuffmanTables acHuffmanTables,
PdfJsFrameComponent[] components, PdfJsFrameComponent[] components,
int componentsLength, int componentsLength,
int mcusPerLine, DoubleBufferedStreamReader stream)
int mcuToRead,
ref int mcu,
Stream stream)
{ {
if (componentsLength == 1) if (componentsLength == 1)
{ {
@ -185,20 +170,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId];
ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; 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) if (this.endOfStreamReached || this.unexpectedMarkerReached)
{ {
continue; continue;
} }
this.DecodeBlockBaseline(ref dcHuffmanTable, ref acHuffmanTable, component, ref blockDataRef, mcu, stream); this.DecodeBlockBaseline(ref dcHuffmanTable, ref acHuffmanTable, component, ref blockDataRef, stream);
mcu++; this.mcu++;
} }
} }
else else
{ {
for (int n = 0; n < mcuToRead; n++) for (int n = 0; n < this.mcuToRead; n++)
{ {
for (int i = 0; i < componentsLength; i++) for (int i = 0; i < componentsLength; i++)
{ {
@ -218,12 +203,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
continue; 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, bool isFirst,
PdfJsFrameComponent[] components, PdfJsFrameComponent[] components,
int componentsLength, int componentsLength,
int mcusPerLine, DoubleBufferedStreamReader stream)
int mcuToRead,
ref int mcu,
Stream stream)
{ {
if (componentsLength == 1) 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 short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<Block8x8, short>(component.SpectralBlocks.Span));
ref PdfJsHuffmanTable huffmanTable = ref huffmanTables[isAC ? component.ACHuffmanTableId : component.DCHuffmanTableId]; 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) if (this.endOfStreamReached || this.unexpectedMarkerReached)
{ {
@ -256,31 +238,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{ {
if (isFirst) if (isFirst)
{ {
this.DecodeBlockACFirst(ref huffmanTable, component, ref blockDataRef, mcu, stream); this.DecodeBlockACFirst(ref huffmanTable, component, ref blockDataRef, stream);
} }
else else
{ {
this.DecodeBlockACSuccessive(ref huffmanTable, component, ref blockDataRef, mcu, stream); this.DecodeBlockACSuccessive(ref huffmanTable, component, ref blockDataRef, stream);
} }
} }
else else
{ {
if (isFirst) if (isFirst)
{ {
this.DecodeBlockDCFirst(ref huffmanTable, component, ref blockDataRef, mcu, stream); this.DecodeBlockDCFirst(ref huffmanTable, component, ref blockDataRef, stream);
} }
else else
{ {
this.DecodeBlockDCSuccessive(component, ref blockDataRef, mcu, stream); this.DecodeBlockDCSuccessive(component, ref blockDataRef, stream);
} }
} }
mcu++; this.mcu++;
} }
} }
else else
{ {
for (int n = 0; n < mcuToRead; n++) for (int n = 0; n < this.mcuToRead; n++)
{ {
for (int i = 0; i < componentsLength; i++) 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++) for (int k = 0; k < h; k++)
{ {
// No need to continue here.
if (this.endOfStreamReached || this.unexpectedMarkerReached) if (this.endOfStreamReached || this.unexpectedMarkerReached)
{ {
continue; break;
} }
if (isAC) if (isAC)
{ {
if (isFirst) 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 else
{ {
this.DecodeMcuACSuccessive(ref huffmanTable, component, ref blockDataRef, mcusPerLine, mcu, j, k, stream); this.DecodeMcuACSuccessive(ref huffmanTable, component, ref blockDataRef, j, k, stream);
} }
} }
else else
{ {
if (isFirst) 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 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)] [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 blockRow = this.mcu / component.WidthInBlocks;
int blockCol = mcu % component.WidthInBlocks; int blockCol = this.mcu % component.WidthInBlocks;
int offset = component.GetBlockBufferOffset(blockRow, blockCol); int offset = component.GetBlockBufferOffset(blockRow, blockCol);
this.DecodeBaseline(component, ref blockDataRef, offset, ref dcHuffmanTable, ref acHuffmanTable, stream); this.DecodeBaseline(component, ref blockDataRef, offset, ref dcHuffmanTable, ref acHuffmanTable, stream);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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 mcuRow = this.mcu / this.mcusPerLine;
int mcuCol = mcu % mcusPerLine; int mcuCol = this.mcu % this.mcusPerLine;
int blockRow = (mcuRow * component.VerticalSamplingFactor) + row; int blockRow = (mcuRow * component.VerticalSamplingFactor) + row;
int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col; int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col;
int offset = component.GetBlockBufferOffset(blockRow, blockCol); int offset = component.GetBlockBufferOffset(blockRow, blockCol);
@ -351,19 +334,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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 blockRow = this.mcu / component.WidthInBlocks;
int blockCol = mcu % component.WidthInBlocks; int blockCol = this.mcu % component.WidthInBlocks;
int offset = component.GetBlockBufferOffset(blockRow, blockCol); int offset = component.GetBlockBufferOffset(blockRow, blockCol);
this.DecodeDCFirst(component, ref blockDataRef, offset, ref dcHuffmanTable, stream); this.DecodeDCFirst(component, ref blockDataRef, offset, ref dcHuffmanTable, stream);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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 mcuRow = this.mcu / this.mcusPerLine;
int mcuCol = mcu % mcusPerLine; int mcuCol = this.mcu % this.mcusPerLine;
int blockRow = (mcuRow * component.VerticalSamplingFactor) + row; int blockRow = (mcuRow * component.VerticalSamplingFactor) + row;
int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col; int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col;
int offset = component.GetBlockBufferOffset(blockRow, blockCol); int offset = component.GetBlockBufferOffset(blockRow, blockCol);
@ -371,19 +354,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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 blockRow = this.mcu / component.WidthInBlocks;
int blockCol = mcu % component.WidthInBlocks; int blockCol = this.mcu % component.WidthInBlocks;
int offset = component.GetBlockBufferOffset(blockRow, blockCol); int offset = component.GetBlockBufferOffset(blockRow, blockCol);
this.DecodeDCSuccessive(component, ref blockDataRef, offset, stream); this.DecodeDCSuccessive(component, ref blockDataRef, offset, stream);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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 mcuRow = this.mcu / this.mcusPerLine;
int mcuCol = mcu % mcusPerLine; int mcuCol = this.mcu % this.mcusPerLine;
int blockRow = (mcuRow * component.VerticalSamplingFactor) + row; int blockRow = (mcuRow * component.VerticalSamplingFactor) + row;
int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col; int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col;
int offset = component.GetBlockBufferOffset(blockRow, blockCol); int offset = component.GetBlockBufferOffset(blockRow, blockCol);
@ -391,169 +374,257 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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 blockRow = this.mcu / component.WidthInBlocks;
int blockCol = mcu % component.WidthInBlocks; int blockCol = this.mcu % component.WidthInBlocks;
int offset = component.GetBlockBufferOffset(blockRow, blockCol); 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)] [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 mcuRow = this.mcu / this.mcusPerLine;
int mcuCol = mcu % mcusPerLine; int mcuCol = this.mcu % this.mcusPerLine;
int blockRow = (mcuRow * component.VerticalSamplingFactor) + row; int blockRow = (mcuRow * component.VerticalSamplingFactor) + row;
int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col; int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col;
int offset = component.GetBlockBufferOffset(blockRow, blockCol); 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)] [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 blockRow = this.mcu / component.WidthInBlocks;
int blockCol = mcu % component.WidthInBlocks; int blockCol = this.mcu % component.WidthInBlocks;
int offset = component.GetBlockBufferOffset(blockRow, blockCol); 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)] [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 mcuRow = this.mcu / this.mcusPerLine;
int mcuCol = mcu % mcusPerLine; int mcuCol = this.mcu % this.mcusPerLine;
int blockRow = (mcuRow * component.VerticalSamplingFactor) + row; int blockRow = (mcuRow * component.VerticalSamplingFactor) + row;
int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col; int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col;
int offset = component.GetBlockBufferOffset(blockRow, blockCol); 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)] [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--; if (!this.TryFillBits(stream))
return (this.bitsData >> this.bitsCount) & 1; {
bit = 0;
return false;
}
} }
this.bitsData = stream.ReadByte(); this.bitsCount--;
bit = (this.bitsData >> this.bitsCount) & 1;
return true;
}
if (this.bitsData == -0x1) [MethodImpl(MethodImplOptions.NoInlining)]
{ private bool TryFillBits(DoubleBufferedStreamReader stream)
// We've encountered the end of the file stream which means there's no EOI marker ref the image {
this.endOfStreamReached = true; // 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(); // Attempt to load to the minimum bit count.
if (nextByte != 0) 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 #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 #endif
// We've encountered an unexpected marker. Reverse the stream and exit. // We've encountered an unexpected marker. Reverse the stream and exit.
this.unexpectedMarkerReached = true; this.unexpectedMarkerReached = true;
stream.Position -= 2; 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)] [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. // 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 // In LibJpegTurbo a minimum of 25 bits (32-7) is collected from the stream
// using 3 methods: FillBits, PeekBits, and ReadBits. We should attempt to do the same. // Then a LUT is used to avoid the loop when decoding the Huffman value.
short code = (short)this.ReadBit(stream); // using 3 methods: FillBits, PeekBits, and DropBits.
if (this.endOfStreamReached || this.unexpectedMarkerReached) // 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 // "DECODE", section F.2.2.3, figure F.16, page 109 of T.81
int i = 1; int i = 1;
while (code > tree.MaxCode[i]) while (code > tree.MaxCode[i])
{ {
code <<= 1; if (!this.TryReadBit(stream, out bit))
code |= (short)this.ReadBit(stream);
if (this.endOfStreamReached || this.unexpectedMarkerReached)
{ {
return -1; return false;
} }
code <<= 1;
code |= (short)bit;
i++; i++;
} }
int j = tree.ValOffset[i]; int j = tree.ValOffset[i];
return tree.HuffVal[(j + code) & 0xFF]; value = tree.HuffVal[(j + code) & 0xFF];
return true;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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) while (length > 0)
{ {
int bit = this.ReadBit(stream); if (!this.TryReadBit(stream, out int bit))
if (this.endOfStreamReached || this.unexpectedMarkerReached)
{ {
return -1; return false;
} }
n = (n << 1) | bit; value = (value << 1) | bit;
length--; length--;
} }
return n; return true;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private int ReceiveAndExtend(int length, Stream stream) private bool TryReceiveAndExtend(int length, DoubleBufferedStreamReader stream, out int value)
{ {
if (length == 1) if (length == 1)
{ {
return this.ReadBit(stream) == 1 ? 1 : -1; if (!this.TryReadBit(stream, out value))
} {
return false;
}
int n = this.Receive(length, stream); value = value == 1 ? 1 : -1;
if (n >= 1 << (length - 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.TryDecodeHuffman(ref dcHuffmanTable, stream, out short t))
if (this.endOfStreamReached || this.unexpectedMarkerReached)
{ {
return; return;
} }
int diff = t == 0 ? 0 : this.ReceiveAndExtend(t, stream); int diff = 0;
Unsafe.Add(ref blockDataRef, offset) = (short)(component.Pred += diff); if (t != 0)
{
if (!this.TryReceiveAndExtend(t, stream, out diff))
{
return;
}
}
Unsafe.Add(ref blockDataRef, offset) = (short)(component.DcPredictor += diff);
int k = 1; int k = 1;
while (k < 64) while (k < 64)
{ {
short rs = this.DecodeHuffman(ref acHuffmanTable, stream); if (!this.TryDecodeHuffman(ref acHuffmanTable, stream, out short rs))
if (this.endOfStreamReached || this.unexpectedMarkerReached)
{ {
return; return;
} }
@ -574,36 +645,42 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
k += r; 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]; Unsafe.Add(ref blockDataRef, offset + z) = (short)re;
short re = (short)this.ReceiveAndExtend(s, stream);
Unsafe.Add(ref blockDataRef, offset + z) = re;
k++; k++;
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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.TryDecodeHuffman(ref dcHuffmanTable, stream, out short t))
if (this.endOfStreamReached || this.unexpectedMarkerReached)
{ {
return; return;
} }
int diff = t == 0 ? 0 : this.ReceiveAndExtend(t, stream) << this.successiveState; int diff = 0;
Unsafe.Add(ref blockDataRef, offset) = (short)(component.Pred += diff); 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)] [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.TryReadBit(stream, out int bit))
if (this.endOfStreamReached || this.unexpectedMarkerReached)
{ {
return; return;
} }
@ -611,7 +688,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
Unsafe.Add(ref blockDataRef, offset) |= (short)(bit << this.successiveState); 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) if (this.eobrun > 0)
{ {
@ -623,8 +700,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
int e = this.specEnd; int e = this.specEnd;
while (k <= e) while (k <= e)
{ {
short rs = this.DecodeHuffman(ref acHuffmanTable, stream); if (!this.TryDecodeHuffman(ref acHuffmanTable, stream, out short rs))
if (this.endOfStreamReached || this.unexpectedMarkerReached)
{ {
return; return;
} }
@ -636,7 +712,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{ {
if (r < 15) 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; break;
} }
@ -647,12 +728,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
k += r; k += r;
byte z = this.dctZigZag[k]; 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++; 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 k = this.specStart;
int e = this.specEnd; int e = this.specEnd;
@ -667,8 +754,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
switch (this.successiveACState) switch (this.successiveACState)
{ {
case 0: // Initial state 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; return;
} }
@ -679,7 +766,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{ {
if (r < 15) 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; this.successiveACState = 4;
} }
else else
@ -695,7 +787,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
throw new ImageFormatException("Invalid ACn encoding"); 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; this.successiveACState = r > 0 ? 2 : 3;
} }
@ -704,8 +801,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
case 2: case 2:
if (blockOffsetZRef != 0) if (blockOffsetZRef != 0)
{ {
int bit = this.ReadBit(stream); if (!this.TryReadBit(stream, out int bit))
if (this.endOfStreamReached || this.unexpectedMarkerReached)
{ {
return; return;
} }
@ -725,8 +821,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
case 3: // Set value for a zero item case 3: // Set value for a zero item
if (blockOffsetZRef != 0) if (blockOffsetZRef != 0)
{ {
int bit = this.ReadBit(stream); if (!this.TryReadBit(stream, out int bit))
if (this.endOfStreamReached || this.unexpectedMarkerReached)
{ {
return; return;
} }
@ -743,8 +838,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
case 4: // Eob case 4: // Eob
if (blockOffsetZRef != 0) if (blockOffsetZRef != 0)
{ {
int bit = this.ReadBit(stream); if (!this.TryReadBit(stream, out int bit))
if (this.endOfStreamReached || this.unexpectedMarkerReached)
{ {
return; return;
} }

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

@ -7,8 +7,9 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; 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.Formats.Jpeg.PdfJsPort.Components;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData;
@ -22,7 +23,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
{ {
/// <summary> /// <summary>
/// Performs the jpeg decoding operation. /// 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> /// </summary>
internal sealed class PdfJsJpegDecoderCore : IRawJpegData internal sealed class PdfJsJpegDecoderCore : IRawJpegData
{ {
@ -31,7 +33,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// </summary> /// </summary>
public const int SupportedPrecision = 8; public const int SupportedPrecision = 8;
#pragma warning disable SA1401 // Fields should be private
/// <summary> /// <summary>
/// The global configuration /// The global configuration
/// </summary> /// </summary>
@ -93,15 +94,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// </summary> /// </summary>
public PdfJsFrame Frame { get; private set; } 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> /// <summary>
/// Gets the image width /// Gets the image width
/// </summary> /// </summary>
public int ImageWidth { get; private set; } public int ImageWidth => this.ImageSizeInPixels.Width;
/// <summary> /// <summary>
/// Gets the image height /// Gets the image height
/// </summary> /// </summary>
public int ImageHeight { get; private set; } public int ImageHeight => this.ImageSizeInPixels.Height;
/// <summary> /// <summary>
/// Gets the color depth, in number of bits per pixel. /// Gets the color depth, in number of bits per pixel.
@ -111,7 +120,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// <summary> /// <summary>
/// Gets the input stream. /// Gets the input stream.
/// </summary> /// </summary>
public Stream InputStream { get; private set; } public DoubleBufferedStreamReader InputStream { get; private set; }
/// <summary> /// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded. /// 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> /// </summary>
public ImageMetaData MetaData { get; private set; } public ImageMetaData MetaData { get; private set; }
/// <inheritdoc/>
public Size ImageSizeInPixels => new Size(this.ImageWidth, this.ImageHeight);
/// <inheritdoc/> /// <inheritdoc/>
public int ComponentCount { get; private set; } public int ComponentCount { get; private set; }
/// <inheritdoc/> /// <inheritdoc/>
public JpegColorSpace ColorSpace { get; private set; } public JpegColorSpace ColorSpace { get; private set; }
/// <summary>
/// Gets the components.
/// </summary>
public PdfJsFrameComponent[] Components => this.Frame.Components;
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerable<IJpegComponent> Components => this.Frame.Components; IEnumerable<IJpegComponent> IRawJpegData.Components => this.Components;
/// <inheritdoc/> /// <inheritdoc/>
public Block8x8F[] QuantizationTables { get; private set; } 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="marker">The buffer to read file markers to</param>
/// <param name="stream">The input stream</param> /// <param name="stream">The input stream</param>
/// <returns>The <see cref="PdfJsFileMarker"/></returns> /// <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); int value = stream.Read(marker, 0, 2);
if (value == 0) 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: // 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." // "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(); int suffix = stream.ReadByte();
if (suffix == -1) 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> /// <summary>
@ -184,6 +196,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
this.ParseStream(stream); this.ParseStream(stream);
this.AssignResolution();
return this.PostProcessIntoImage<TPixel>(); return this.PostProcessIntoImage<TPixel>();
} }
@ -206,136 +219,142 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
public void ParseStream(Stream stream, bool metadataOnly = false) public void ParseStream(Stream stream, bool metadataOnly = false)
{ {
this.MetaData = new ImageMetaData(); this.MetaData = new ImageMetaData();
this.InputStream = stream; this.InputStream = new DoubleBufferedStreamReader(this.configuration.MemoryManager, stream);
// Check for the Start Of Image marker. // Check for the Start Of Image marker.
var fileMarker = new PdfJsFileMarker(this.ReadUint16(), 0); this.InputStream.Read(this.markerBuffer, 0, 2);
if (fileMarker.Marker != PdfJsJpegConstants.Markers.SOI) var fileMarker = new PdfJsFileMarker(this.markerBuffer[1], 0);
if (fileMarker.Marker != JpegConstants.Markers.SOI)
{ {
throw new ImageFormatException("Missing SOI marker."); 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); fileMarker = new PdfJsFileMarker(marker, (int)this.InputStream.Position - 2);
this.QuantizationTables = new Block8x8F[4]; // Only assign what we need
if (!metadataOnly)
// this.quantizationTables = new PdfJsQuantizationTables(this.configuration.MemoryManager);
this.dcHuffmanTables = new PdfJsHuffmanTables();
this.acHuffmanTables = new PdfJsHuffmanTables();
while (fileMarker.Marker != PdfJsJpegConstants.Markers.EOI)
{ {
// Get the marker length this.QuantizationTables = new Block8x8F[4];
int remaining = this.ReadUint16() - 2; this.dcHuffmanTables = new PdfJsHuffmanTables();
this.acHuffmanTables = new PdfJsHuffmanTables();
}
switch (fileMarker.Marker) while (fileMarker.Marker != JpegConstants.Markers.EOI)
{
if (!fileMarker.Invalid)
{ {
case PdfJsJpegConstants.Markers.APP0: // Get the marker length
this.ProcessApplicationHeaderMarker(remaining); int remaining = this.ReadUint16() - 2;
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;
case PdfJsJpegConstants.Markers.APP14: switch (fileMarker.Marker)
this.ProcessApp14Marker(remaining); {
break; 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 JpegConstants.Markers.DHT:
case PdfJsJpegConstants.Markers.COM: if (metadataOnly)
this.InputStream.Skip(remaining); {
break; this.InputStream.Skip(remaining);
}
else
{
this.ProcessDefineHuffmanTablesMarker(remaining);
}
case PdfJsJpegConstants.Markers.DQT: break;
if (metadataOnly)
{
this.InputStream.Skip(remaining);
}
else
{
this.ProcessDefineQuantizationTablesMarker(remaining);
}
break; case JpegConstants.Markers.DQT:
if (metadataOnly)
{
this.InputStream.Skip(remaining);
}
else
{
this.ProcessDefineQuantizationTablesMarker(remaining);
}
case PdfJsJpegConstants.Markers.SOF0: break;
case PdfJsJpegConstants.Markers.SOF1:
case PdfJsJpegConstants.Markers.SOF2:
this.ProcessStartOfFrameMarker(remaining, fileMarker);
if (metadataOnly && !this.jFif.Equals(default))
{
this.InputStream.Skip(remaining);
}
break; case JpegConstants.Markers.DRI:
if (metadataOnly)
{
this.InputStream.Skip(remaining);
}
else
{
this.ProcessDefineRestartIntervalMarker(remaining);
}
case PdfJsJpegConstants.Markers.DHT: break;
if (metadataOnly)
{ 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); this.InputStream.Skip(remaining);
} break;
else
{
this.ProcessDefineHuffmanTablesMarker(remaining);
}
break; case JpegConstants.Markers.APP14:
this.ProcessApp14Marker(remaining);
break;
case PdfJsJpegConstants.Markers.DRI: case JpegConstants.Markers.APP15:
if (metadataOnly) case JpegConstants.Markers.COM:
{
this.InputStream.Skip(remaining); this.InputStream.Skip(remaining);
} break;
else }
{
this.ProcessDefineRestartIntervalMarker(remaining);
}
break;
case PdfJsJpegConstants.Markers.SOS:
if (!metadataOnly)
{
this.ProcessStartOfScanMarker();
}
break;
} }
// Read on. // Read on.
fileMarker = FindNextFileMarker(this.markerBuffer, this.InputStream); fileMarker = FindNextFileMarker(this.markerBuffer, this.InputStream);
} }
this.ImageWidth = this.Frame.SamplesPerLine;
this.ImageHeight = this.Frame.Scanlines;
this.ComponentCount = this.Frame.ComponentCount;
} }
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()
{ {
this.InputStream?.Dispose();
this.Frame?.Dispose(); this.Frame?.Dispose();
// Set large fields to null. // Set large fields to null.
this.InputStream = null;
this.Frame = null; this.Frame = null;
this.dcHuffmanTables = null; this.dcHuffmanTables = null;
this.acHuffmanTables = null; this.acHuffmanTables = null;
@ -354,11 +373,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
if (this.ComponentCount == 3) 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; return JpegColorSpace.YCbCr;
} }
else if (this.adobe.ColorTransform == PdfJsJpegConstants.Markers.Adobe.ColorTransformUnknown)
if (this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown)
{ {
return JpegColorSpace.RGB; return JpegColorSpace.RGB;
} }
@ -366,7 +386,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
if (this.ComponentCount == 4) if (this.ComponentCount == 4)
{ {
return this.adobe.ColorTransform == PdfJsJpegConstants.Markers.Adobe.ColorTransformYcck return this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYcck
? JpegColorSpace.Ycck ? JpegColorSpace.Ycck
: JpegColorSpace.Cmyk; : JpegColorSpace.Cmyk;
} }
@ -379,7 +399,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// </summary> /// </summary>
private void AssignResolution() 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) double horizontalValue = this.MetaData.ExifProfile.TryGetValue(ExifTag.XResolution, out ExifValue horizontalTag)
? ((Rational)horizontalTag.Value).ToDouble() ? ((Rational)horizontalTag.Value).ToDouble()
@ -395,11 +420,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
this.MetaData.VerticalResolution = verticalValue; 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> /// <summary>
@ -593,7 +613,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// </summary> /// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param> /// <param name="remaining">The remaining bytes in the segment block.</param>
/// <param name="frameMarker">The current frame marker.</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) if (this.Frame != null)
{ {
@ -610,49 +631,59 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
this.Frame = new PdfJsFrame this.Frame = new PdfJsFrame
{ {
Extended = frameMarker.Marker == PdfJsJpegConstants.Markers.SOF1, Extended = frameMarker.Marker == JpegConstants.Markers.SOF1,
Progressive = frameMarker.Marker == PdfJsJpegConstants.Markers.SOF2, Progressive = frameMarker.Marker == JpegConstants.Markers.SOF2,
Precision = this.temp[0], Precision = this.temp[0],
Scanlines = (short)((this.temp[1] << 8) | this.temp[2]), Scanlines = (short)((this.temp[1] << 8) | this.temp[2]),
SamplesPerLine = (short)((this.temp[3] << 8) | this.temp[4]), SamplesPerLine = (short)((this.temp[3] << 8) | this.temp[4]),
ComponentCount = this.temp[5] ComponentCount = this.temp[5]
}; };
this.ImageSizeInPixels = new Size(this.Frame.SamplesPerLine, this.Frame.Scanlines);
int maxH = 0; int maxH = 0;
int maxV = 0; int maxV = 0;
int index = 6; int index = 6;
// No need to pool this. They max out at 4 this.ComponentCount = this.Frame.ComponentCount;
this.Frame.ComponentIds = new byte[this.Frame.ComponentCount];
this.Frame.Components = new PdfJsFrameComponent[this.Frame.ComponentCount];
for (int i = 0; i < this.Frame.Components.Length; i++) if (!metadataOnly)
{ {
byte hv = this.temp[index + 1]; // No need to pool this. They max out at 4
int h = hv >> 4; this.Frame.ComponentIds = new byte[this.Frame.ComponentCount];
int v = hv & 15; 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) if (maxH < h)
{ {
maxV = v; 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.Components[i] = component;
this.Frame.ComponentIds[i] = component.Id; this.Frame.ComponentIds[i] = component.Id;
index += 3; index += 3;
} }
this.Frame.MaxHorizontalFactor = maxH; this.Frame.MaxHorizontalFactor = maxH;
this.Frame.MaxVerticalFactor = maxV; this.Frame.MaxVerticalFactor = maxV;
this.Frame.InitComponents(); this.ColorSpace = this.DeduceJpegColorSpace();
this.Frame.InitComponents();
this.ImageSizeInMCU = new Size(this.Frame.McusPerLine, this.Frame.McusPerColumn);
}
} }
/// <summary> /// <summary>
@ -798,8 +829,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
private Image<TPixel> PostProcessIntoImage<TPixel>() private Image<TPixel> PostProcessIntoImage<TPixel>()
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
this.ColorSpace = this.DeduceJpegColorSpace();
this.AssignResolution();
using (var postProcessor = new JpegImagePostProcessor(this.configuration.MemoryManager, this)) using (var postProcessor = new JpegImagePostProcessor(this.configuration.MemoryManager, this))
{ {
var image = new Image<TPixel>(this.configuration, this.ImageWidth, this.ImageHeight, this.MetaData); 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;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Png.Filters 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="scanline">The scanline to encode</param>
/// <param name="result">The filtered scanline result.</param> /// <param name="result">The filtered scanline result.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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. // Insert a byte before the data.
result[0] = 0; result[0] = 0;
result = result.Slice(1); 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> where TPixel : struct, IPixel<TPixel>
{ {
ReadOnlySpan<byte> newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); 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 color = default(TPixel);
var rgba = default(Rgba32); var rgba = default(Rgba32);
@ -865,10 +865,9 @@ namespace SixLabors.ImageSharp.Formats.Png
for (int x = 0; x < this.header.Width; x++) for (int x = 0; x < this.header.Width; x++)
{ {
int index = newScanline[x]; int index = newScanline[x];
int pixelOffset = index * 3;
rgba.A = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : (byte)255; rgba.A = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : (byte)255;
rgba.Rgb = pal.GetRgb24(pixelOffset); rgba.Rgb = pal[index];
color.PackFromRgba32(rgba); color.PackFromRgba32(rgba);
row[x] = color; row[x] = color;
@ -881,9 +880,8 @@ namespace SixLabors.ImageSharp.Formats.Png
for (int x = 0; x < this.header.Width; x++) for (int x = 0; x < this.header.Width; x++)
{ {
int index = newScanline[x]; int index = newScanline[x];
int pixelOffset = index * 3;
rgba.Rgb = pal.GetRgb24(pixelOffset); rgba.Rgb = pal[index];
color.PackFromRgba32(rgba); color.PackFromRgba32(rgba);
row[x] = color; row[x] = color;
@ -946,6 +944,7 @@ namespace SixLabors.ImageSharp.Formats.Png
ReadOnlySpan<byte> newScanline = ToArrayByBitsLength(scanlineBuffer, this.bytesPerScanline, this.header.BitDepth); ReadOnlySpan<byte> newScanline = ToArrayByBitsLength(scanlineBuffer, this.bytesPerScanline, this.header.BitDepth);
var rgba = default(Rgba32); var rgba = default(Rgba32);
Span<Rgb24> pal = MemoryMarshal.Cast<byte, Rgb24>(this.palette);
if (this.paletteAlpha != null && this.paletteAlpha.Length > 0) 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++) for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++)
{ {
int index = newScanline[o]; int index = newScanline[o];
int offset = index * 3;
rgba.A = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : (byte)255; rgba.A = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : (byte)255;
rgba.Rgb = this.palette.GetRgb24(offset); rgba.Rgb = pal[index];
color.PackFromRgba32(rgba); color.PackFromRgba32(rgba);
rowSpan[x] = color; 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++) for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++)
{ {
int index = newScanline[o]; int index = newScanline[o];
int offset = index * 3;
rgba.Rgb = this.palette.GetRgb24(offset);
rgba.Rgb = pal[index];
color.PackFromRgba32(rgba); color.PackFromRgba32(rgba);
rowSpan[x] = color; 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> /// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <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> where TPixel : struct, IPixel<TPixel>
=> LoadPixelData(Configuration.Default, data, width, height); => LoadPixelData(Configuration.Default, data, width, height);
@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp
/// <param name="height">The height of the final image.</param> /// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <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> where TPixel : struct, IPixel<TPixel>
=> LoadPixelData<TPixel>(Configuration.Default, data, width, height); => LoadPixelData<TPixel>(Configuration.Default, data, width, height);
@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(Configuration config, byte[] data, int width, int height) public static Image<TPixel> LoadPixelData<TPixel>(Configuration config, byte[] data, int width, int height)
where TPixel : struct, IPixel<TPixel> 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> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array in <typeparamref name="TPixel"/> format. /// 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> /// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <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> where TPixel : struct, IPixel<TPixel>
=> LoadPixelData(config, MemoryMarshal.Cast<byte, TPixel>(data), width, height); => 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) public static Image<TPixel> LoadPixelData<TPixel>(Configuration config, TPixel[] data, int width, int height)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return LoadPixelData(config, data.AsSpan(), width, height); return LoadPixelData(config, new ReadOnlySpan<TPixel>(data), width, height);
} }
/// <summary> /// <summary>
@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp
/// <param name="height">The height of the final image.</param> /// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <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> where TPixel : struct, IPixel<TPixel>
{ {
int count = width * height; int count = width * height;

90
src/ImageSharp/ImageExtensions.cs

@ -4,13 +4,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp namespace SixLabors.ImageSharp
{ {
@ -21,18 +19,18 @@ namespace SixLabors.ImageSharp
{ {
#if !NETSTANDARD1_1 #if !NETSTANDARD1_1
/// <summary> /// <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> /// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image</param> /// <param name="source">The source image.</param>
/// <param name="filePath">The file path to save the image to.</param> /// <param name="filePath">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception> /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void Save<TPixel>(this Image<TPixel> source, string filePath) public static void Save<TPixel>(this Image<TPixel> source, string filePath)
where TPixel : struct, IPixel<TPixel> 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); IImageFormat format = source.GetConfiguration().ImageFormatsManager.FindFormatByFileExtension(ext);
if (format == null) if (format == null)
{ {
@ -64,13 +62,13 @@ namespace SixLabors.ImageSharp
} }
/// <summary> /// <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> /// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image</param> /// <param name="source">The source image.</param>
/// <param name="filePath">The file path to save the image to.</param> /// <param name="filePath">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param> /// <param name="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) public static void Save<TPixel>(this Image<TPixel> source, string filePath, IImageEncoder encoder)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
@ -83,13 +81,13 @@ namespace SixLabors.ImageSharp
#endif #endif
/// <summary> /// <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> /// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam> /// <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="stream">The stream to save the image to.</param>
/// <param name="format">The format to save the image to.</param> /// <param name="format">The format to save the image in.</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 Save<TPixel>(this Image<TPixel> source, Stream stream, IImageFormat format) public static void Save<TPixel>(this Image<TPixel> source, Stream stream, IImageFormat format)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
@ -113,67 +111,67 @@ namespace SixLabors.ImageSharp
} }
/// <summary> /// <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> /// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image</param> /// <param name="source">The source image</param>
/// <returns>A copy of the pixel data as bytes from this frame.</returns> /// <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) public static byte[] SavePixelData<TPixel>(this ImageFrame<TPixel> source)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
=> MemoryMarshal.AsBytes(source.GetPixelSpan()).ToArray(); => MemoryMarshal.AsBytes(source.GetPixelSpan()).ToArray();
/// <summary> /// <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> /// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image</param> /// <param name="source">The source image.</param>
/// <param name="buffer">The buffer to save the raw pixel data to.</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) public static void SavePixelData<TPixel>(this ImageFrame<TPixel> source, byte[] buffer)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
=> SavePixelData(source, MemoryMarshal.Cast<byte, TPixel>(buffer.AsSpan())); => SavePixelData(source, MemoryMarshal.Cast<byte, TPixel>(buffer.AsSpan()));
/// <summary> /// <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> /// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image</param> /// <param name="source">The source image</param>
/// <param name="buffer">The buffer to save the raw pixel data to.</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) public static void SavePixelData<TPixel>(this ImageFrame<TPixel> source, TPixel[] buffer)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
=> SavePixelData(source, buffer.AsSpan()); => SavePixelData(source, buffer.AsSpan());
/// <summary> /// <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> /// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image</param> /// <param name="source">The source image.</param>
/// <returns>A copy of the pixel data from the first frame as bytes.</returns> /// <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) public static byte[] SavePixelData<TPixel>(this Image<TPixel> source)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.SavePixelData(); => source.Frames.RootFrame.SavePixelData();
/// <summary> /// <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> /// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image</param> /// <param name="source">The source image.</param>
/// <param name="buffer">The buffer to save the raw pixel data to.</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) public static void SavePixelData<TPixel>(this Image<TPixel> source, byte[] buffer)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.SavePixelData(buffer); => source.Frames.RootFrame.SavePixelData(buffer);
/// <summary> /// <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> /// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image</param> /// <param name="source">The source image</param>
/// <param name="buffer">The buffer to save the raw pixel data to.</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) public static void SavePixelData<TPixel>(this Image<TPixel> source, TPixel[] buffer)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.SavePixelData(buffer); => source.Frames.RootFrame.SavePixelData(buffer);
@ -182,7 +180,7 @@ namespace SixLabors.ImageSharp
/// Returns a Base64 encoded string from the given image. /// Returns a Base64 encoded string from the given image.
/// </summary> /// </summary>
/// <example><see href="data:image/gif;base64,R0lGODlhAQABAIABAEdJRgAAACwAAAAAAQABAAACAkQBAA=="/></example> /// <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="source">The source image</param>
/// <param name="format">The format.</param> /// <param name="format">The format.</param>
/// <returns>The <see cref="string"/></returns> /// <returns>The <see cref="string"/></returns>
@ -198,24 +196,24 @@ namespace SixLabors.ImageSharp
} }
/// <summary> /// <summary>
/// Saves the raw image to the given bytes. /// Writes the raw image bytes to the given byte span.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image</param> /// <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> /// <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> where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.SavePixelData(MemoryMarshal.Cast<byte, TPixel>(buffer)); => source.Frames.RootFrame.SavePixelData(MemoryMarshal.Cast<byte, TPixel>(buffer));
/// <summary> /// <summary>
/// Saves the raw image to the given bytes. /// Writes the raw image pixels to the given TPixel span.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image</param> /// <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> /// <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> where TPixel : struct, IPixel<TPixel>
{ {
Span<TPixel> sourceBuffer = source.GetPixelSpan(); Span<TPixel> sourceBuffer = source.GetPixelSpan();
@ -224,4 +222,4 @@ namespace SixLabors.ImageSharp
sourceBuffer.CopyTo(buffer); 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> /// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <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> where TPixel : struct, IPixel<TPixel>
=> LoadPixelData(memoryManager, MemoryMarshal.Cast<byte, TPixel>(data), width, height); => 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> /// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <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> where TPixel : struct, IPixel<TPixel>
{ {
int count = width * height; 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) internal ImageFrameCollection(Image<TPixel> parent, IEnumerable<ImageFrame<TPixel>> frames)
{ {
Guard.NotNull(parent, nameof(parent)); Guard.NotNull(parent, nameof(parent));
Guard.NotNullOrEmpty(frames, nameof(frames)); Guard.NotNull(frames, nameof(frames));
this.parent = parent; this.parent = parent;
@ -42,6 +42,12 @@ namespace SixLabors.ImageSharp
this.ValidateFrame(f); this.ValidateFrame(f);
this.frames.Add(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> /// <summary>
@ -111,7 +117,7 @@ namespace SixLabors.ImageSharp
var frame = ImageFrame.LoadPixelData( var frame = ImageFrame.LoadPixelData(
this.parent.GetMemoryManager(), this.parent.GetMemoryManager(),
new Span<TPixel>(source), new ReadOnlySpan<TPixel>(source),
this.RootFrame.Width, this.RootFrame.Width,
this.RootFrame.Height); this.RootFrame.Height);
this.frames.Add(frame); this.frames.Add(frame);

15
src/ImageSharp/ImageSharp.csproj

@ -41,8 +41,8 @@
<PrivateAssets>All</PrivateAssets> <PrivateAssets>All</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="System.Buffers" Version="4.4.0" /> <PackageReference Include="System.Buffers" Version="4.4.0" />
<PackageReference Include="System.Memory" 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-preview2-26406-04" /> <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.0-rc1" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.1' OR '$(TargetFramework)' == 'netstandard1.3'"> <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.1' OR '$(TargetFramework)' == 'netstandard1.3'">
<PackageReference Include="System.IO.Compression" Version="4.3.0" /> <PackageReference Include="System.IO.Compression" Version="4.3.0" />
@ -57,11 +57,11 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Update="Formats\Jpeg\Common\Block8x8F.Generated.tt"> <None Update="Formats\Jpeg\Components\Block8x8F.Generated.tt">
<Generator>TextTemplatingFileGenerator</Generator> <Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>Block8x8F.Generated.cs</LastGenOutput> <LastGenOutput>Block8x8F.Generated.cs</LastGenOutput>
</None> </None>
<None Update="Formats\Jpeg\Common\GenericBlock8x8.Generated.tt"> <None Update="Formats\Jpeg\Components\GenericBlock8x8.Generated.tt">
<Generator>TextTemplatingFileGenerator</Generator> <Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>GenericBlock8x8.Generated.cs</LastGenOutput> <LastGenOutput>GenericBlock8x8.Generated.cs</LastGenOutput>
</None> </None>
@ -87,12 +87,12 @@
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Update="Formats\Jpeg\Common\Block8x8F.Generated.cs"> <Compile Update="Formats\Jpeg\Components\Block8x8F.Generated.cs">
<DesignTime>True</DesignTime> <DesignTime>True</DesignTime>
<AutoGen>True</AutoGen> <AutoGen>True</AutoGen>
<DependentUpon>Block8x8F.Generated.tt</DependentUpon> <DependentUpon>Block8x8F.Generated.tt</DependentUpon>
</Compile> </Compile>
<Compile Update="Formats\Jpeg\Common\GenericBlock8x8.Generated.cs"> <Compile Update="Formats\Jpeg\Components\GenericBlock8x8.Generated.cs">
<DesignTime>True</DesignTime> <DesignTime>True</DesignTime>
<AutoGen>True</AutoGen> <AutoGen>True</AutoGen>
<DependentUpon>GenericBlock8x8.Generated.tt</DependentUpon> <DependentUpon>GenericBlock8x8.Generated.tt</DependentUpon>
@ -123,4 +123,7 @@
<DependentUpon>PorterDuffFunctions.Generated.tt</DependentUpon> <DependentUpon>PorterDuffFunctions.Generated.tt</DependentUpon>
</Compile> </Compile>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>
</Project> </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> /// <param name="value">The value of the property.</param>
public ImageProperty(string name, string value) public ImageProperty(string name, string value)
{ {
Guard.NotNullOrEmpty(name, nameof(name)); Guard.NotNullOrWhiteSpace(name, nameof(name));
this.Name = name; this.Name = name;
this.Value = value; 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); 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; return true;
} }

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers.Binary;
using System.Globalization; using System.Globalization;
namespace SixLabors.ImageSharp.PixelFormats 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> /// <returns>Returns a <typeparamref name="TPixel"/> that represents the color defined by the provided RGBA heax string.</returns>
public static TPixel FromHex(string hex) public static TPixel FromHex(string hex)
{ {
Guard.NotNullOrEmpty(hex, nameof(hex)); Guard.NotNullOrWhiteSpace(hex, nameof(hex));
hex = ToRgbaHex(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)); throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex));
} }
TPixel result = default; TPixel result = default;
var rgba = new Rgba32( var rgba = new Rgba32(BinaryPrimitives.ReverseEndianness(packedValue));
(byte)(packedValue >> 24),
(byte)(packedValue >> 16),
(byte)(packedValue >> 8),
(byte)(packedValue >> 0));
result.PackFromRgba32(rgba); result.PackFromRgba32(rgba);
return result; return result;
@ -76,7 +73,10 @@ namespace SixLabors.ImageSharp.PixelFormats
/// </returns> /// </returns>
private static string ToRgbaHex(string hex) private static string ToRgbaHex(string hex)
{ {
hex = hex.StartsWith("#") ? hex.Substring(1) : hex; if (hex[0] == '#')
{
hex = hex.Substring(1);
}
if (hex.Length == 8) if (hex.Length == 8)
{ {
@ -93,12 +93,12 @@ namespace SixLabors.ImageSharp.PixelFormats
return null; return null;
} }
string red = char.ToString(hex[0]); char r = hex[0];
string green = char.ToString(hex[1]); char g = hex[1];
string blue = char.ToString(hex[2]); char b = hex[2];
string alpha = hex.Length == 3 ? "F" : char.ToString(hex[3]); 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 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); 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")] [Benchmark(Description = "DecodeJpegMultiple - ImageSharp")]
public void DecodeJpegImageSharpOrig() 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")] [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;
using System.Numerics; using System.Numerics;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder.ColorConverters;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortClr))]
public class YCbCrColorConversion public class YCbCrColorConversion
{ {
@ -57,7 +57,7 @@
JpegColorConverter.FromYCbCrSimdAvx2.ConvertCore(values, this.output); JpegColorConverter.FromYCbCrSimdAvx2.ConvertCore(values, this.output);
} }
private static Buffer2D<float>[] CreateRandomValues( private static Buffer2D<float>[] CreateRandomValues(
int componentCount, int componentCount,
int inputBufferLength, int inputBufferLength,
@ -81,6 +81,6 @@
return buffers; return buffers;
} }
} }
} }

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

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

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

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

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

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

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

@ -21,7 +21,6 @@
<PackageReference Include="BitMiracle.LibJpeg.NET" Version="1.4.280" /> <PackageReference Include="BitMiracle.LibJpeg.NET" Version="1.4.280" />
<PackageReference Include="xunit" Version="2.3.1" /> <PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="Moq" Version="4.8.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="Microsoft.NET.Test.Sdk" Version="15.0.0" />-->
<!--<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />--> <!--<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />-->
</ItemGroup> </ItemGroup>
@ -33,7 +32,4 @@
<ItemGroup> <ItemGroup>
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
</Project> </Project>

188
tests/ImageSharp.Tests/ConfigurationTests.cs

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