Handle many functions in Shading

This commit is contained in:
BobLd 2023-05-19 18:31:28 +01:00
parent 0b8252e930
commit 9acfac4fdf
4 changed files with 114 additions and 39 deletions

View File

@ -30,5 +30,15 @@
}
}
}
[Fact]
public void AxialRadialTensorProductManyFunctions2()
{
// We just check pages can be parsed correctly for now
using (var document = PdfDocument.Open(IntegrationHelpers.GetDocumentPath("iron-ore-q2-q3-2013.pdf")))
{
var page = document.GetPage(8); // Should not throw
}
}
}
}

View File

@ -71,6 +71,53 @@
BBox = bbox;
Background = background;
}
/// <summary>
/// The shading's function(s), if any.
/// </summary>
public abstract PdfFunction[] Functions { get; }
/// <summary>
/// Convert the input values using the functions of the shading.
/// </summary>
public double[] Eval(params double[] input)
{
if (Functions == null || Functions.Length == 0)
{
return input;
}
else if (Functions.Length == 1)
{
return Clamp(Functions[0].Eval(input));
}
double[] returnValues = new double[Functions.Length];
for (int i = 0; i < Functions.Length; i++)
{
double[] newValue = Functions[i].Eval(input);
returnValues[i] = newValue[0]; // 1-out functions
}
return Clamp(returnValues);
}
private static double[] Clamp(double[] input)
{
// From the PDF spec:
// "If the value returned by the function for a given colour component
// is out of range, it shall be adjusted to the nearest valid value."
for (int i = 0; i < input.Length; ++i)
{
if (input[i] < 0)
{
input[i] = 0;
}
else if (input[i] > 1)
{
input[i] = 1;
}
}
return input;
}
}
/// <summary>
@ -104,19 +151,19 @@
/// If the value returned by the function for a given colour component is out of
/// range, it shall be adjusted to the nearest valid value.
/// </summary>
public PdfFunction Function { get; }
public override PdfFunction[] Functions { get; }
/// <summary>
/// Create a new <see cref="FunctionBasedShading"/>.
/// </summary>
public FunctionBasedShading(bool antiAlias, DictionaryToken shadingDictionary,
ColorSpaceDetails colorSpace, PdfRectangle? bbox, double[] background, double[] domain,
TransformationMatrix matrix, PdfFunction function)
TransformationMatrix matrix, PdfFunction[] functions)
: base(ShadingType.FunctionBased, antiAlias, shadingDictionary, colorSpace, bbox, background)
{
Domain = domain;
Matrix = matrix;
Function = function;
Functions = functions;
}
}
@ -148,7 +195,7 @@
/// returned by the function for a given colour component is out of range, it shall be adjusted
/// to the nearest valid value.
/// </summary>
public PdfFunction Function { get; }
public override PdfFunction[] Functions { get; }
/// <summary>
/// (Optional) An array of two boolean values specifying whether to extend the shading beyond the starting
@ -161,12 +208,12 @@
/// </summary>
public AxialShading(bool antiAlias, DictionaryToken shadingDictionary,
ColorSpaceDetails colorSpace, PdfRectangle? bbox, double[] background,
double[] coords, double[] domain, PdfFunction function, bool[] extend)
double[] coords, double[] domain, PdfFunction[] functions, bool[] extend)
: base(ShadingType.Axial, antiAlias, shadingDictionary, colorSpace, bbox, background)
{
Coords = coords;
Domain = domain;
Function = function;
Functions = functions;
Extend = extend;
}
}
@ -205,7 +252,7 @@
/// shading dictionary. If the value returned by the function for a given colour component
/// is out of range, it shall be adjusted to the nearest valid value.
/// </summary>
public PdfFunction Function { get; }
public override PdfFunction[] Functions { get; }
/// <summary>
/// (Optional) An array of two boolean values specifying whether to extend the shading beyond the starting
@ -221,12 +268,12 @@
/// </summary>
public RadialShading(bool antiAlias, DictionaryToken shadingDictionary,
ColorSpaceDetails colorSpace, PdfRectangle? bbox, double[] background,
double[] coords, double[] domain, PdfFunction function, bool[] extend)
double[] coords, double[] domain, PdfFunction[] functions, bool[] extend)
: base(ShadingType.Radial, antiAlias, shadingDictionary, colorSpace, bbox, background)
{
Coords = coords;
Domain = domain;
Function = function;
Functions = functions;
Extend = extend;
}
}
@ -280,21 +327,21 @@
/// to the nearest valid value.
/// This entry shall not be used with an Indexed colour space.
/// </summary>
public PdfFunction Function { get; }
public override PdfFunction[] Functions { get; }
/// <summary>
/// Create a new <see cref="FreeFormGouraudShading"/>.
/// </summary>
public FreeFormGouraudShading(bool antiAlias, StreamToken shadingStream,
ColorSpaceDetails colorSpace, PdfRectangle? bbox, double[] background,
int bitsPerCoordinate, int bitsPerComponent, int bitsPerFlag, double[] decode, PdfFunction function)
int bitsPerCoordinate, int bitsPerComponent, int bitsPerFlag, double[] decode, PdfFunction[] functions)
: base(ShadingType.FreeFormGouraud, antiAlias, shadingStream.StreamDictionary, colorSpace, bbox, background)
{
BitsPerCoordinate = bitsPerCoordinate;
BitsPerComponent = bitsPerComponent;
BitsPerFlag = bitsPerFlag;
Decode = decode;
Function = function;
Functions = functions;
}
}
@ -343,21 +390,21 @@
/// component is out of range, it shall be adjusted to the nearest valid value.
/// This entry shall not be used with an Indexed colour space.
/// </summary>
public PdfFunction Function { get; }
public override PdfFunction[] Functions { get; }
/// <summary>
/// Create a new <see cref="LatticeFormGouraudShading"/>.
/// </summary>
public LatticeFormGouraudShading(bool antiAlias, StreamToken shadingStream,
ColorSpaceDetails colorSpace, PdfRectangle? bbox, double[] background,
int bitsPerCoordinate, int bitsPerComponent, int verticesPerRow, double[] decode, PdfFunction function)
int bitsPerCoordinate, int bitsPerComponent, int verticesPerRow, double[] decode, PdfFunction[] functions)
: base(ShadingType.LatticeFormGouraud, antiAlias, shadingStream.StreamDictionary, colorSpace, bbox, background)
{
BitsPerCoordinate = bitsPerCoordinate;
BitsPerComponent = bitsPerComponent;
VerticesPerRow = verticesPerRow;
Decode = decode;
Function = function;
Functions = functions;
}
}
@ -408,21 +455,21 @@
/// shall be adjusted to the nearest valid value.
/// This entry shall not be used with an Indexed colour space.
/// </summary>
public PdfFunction Function { get; }
public override PdfFunction[] Functions { get; }
/// <summary>
/// Create a new <see cref="CoonsPatchMeshesShading"/>.
/// </summary>
public CoonsPatchMeshesShading(bool antiAlias, StreamToken shadingStream,
ColorSpaceDetails colorSpace, PdfRectangle? bbox, double[] background,
int bitsPerCoordinate, int bitsPerComponent, int bitsPerFlag, double[] decode, PdfFunction function)
int bitsPerCoordinate, int bitsPerComponent, int bitsPerFlag, double[] decode, PdfFunction[] functions)
: base(ShadingType.CoonsPatch, antiAlias, shadingStream.StreamDictionary, colorSpace, bbox, background)
{
BitsPerCoordinate = bitsPerCoordinate;
BitsPerComponent = bitsPerComponent;
BitsPerFlag = bitsPerFlag;
Decode = decode;
Function = function;
Functions = functions;
}
}
@ -473,21 +520,21 @@
/// shall be adjusted to the nearest valid value.
/// This entry shall not be used with an Indexed colour space.
/// </summary>
public PdfFunction Function { get; }
public override PdfFunction[] Functions { get; }
/// <summary>
/// Create a new <see cref="TensorProductPatchMeshesShading"/>.
/// </summary>
public TensorProductPatchMeshesShading(bool antiAlias, StreamToken shadingStream,
ColorSpaceDetails colorSpace, PdfRectangle? bbox, double[] background,
int bitsPerCoordinate, int bitsPerComponent, int bitsPerFlag, double[] decode, PdfFunction function)
int bitsPerCoordinate, int bitsPerComponent, int bitsPerFlag, double[] decode, PdfFunction[] functions)
: base(ShadingType.TensorProductPatch, antiAlias, shadingStream.StreamDictionary, colorSpace, bbox, background)
{
BitsPerCoordinate = bitsPerCoordinate;
BitsPerComponent = bitsPerComponent;
BitsPerFlag = bitsPerFlag;
Decode = decode;
Function = function;
Functions = functions;
}
}

View File

@ -7,6 +7,7 @@
using UglyToad.PdfPig.Filters;
using UglyToad.PdfPig.Functions;
using UglyToad.PdfPig.Graphics.Colors;
using UglyToad.PdfPig.Parser.Parts;
using UglyToad.PdfPig.Tokenization.Scanner;
using UglyToad.PdfPig.Tokens;
@ -111,6 +112,23 @@
}
}
private static PdfFunction[] GetFunctions(IToken functionToken, IPdfTokenScanner scanner, ILookupFilterProvider filterProvider)
{
if (DirectObjectFinder.TryGet(functionToken, scanner, out ArrayToken fa))
{
var functionArray = new PdfFunction[fa.Length];
for (int i = 0; i < fa.Length; i++)
{
functionArray[i] = PdfFunctionParser.Create(fa[i], scanner, filterProvider);
}
return functionArray;
}
else
{
return new PdfFunction[] { PdfFunctionParser.Create(functionToken, scanner, filterProvider) };
}
}
private static FunctionBasedShading CreateFunctionBasedShading(DictionaryToken shadingDictionary, ColorSpaceDetails colorSpace,
double[] background, PdfRectangle? bbox, bool antiAlias, IPdfTokenScanner scanner, ILookupFilterProvider filterProvider)
{
@ -141,9 +159,9 @@
throw new ArgumentNullException($"'{NameToken.Function}' is required for shading type '{ShadingType.FunctionBased}'.");
}
PdfFunction function = PdfFunctionParser.Create(shadingDictionary.Data[NameToken.Function], scanner, filterProvider);
PdfFunction[] functions = GetFunctions(shadingDictionary.Data[NameToken.Function], scanner, filterProvider);
return new FunctionBasedShading(antiAlias, shadingDictionary, colorSpace, bbox, background, domain, matrix, function);
return new FunctionBasedShading(antiAlias, shadingDictionary, colorSpace, bbox, background, domain, matrix, functions);
}
private static AxialShading CreateAxialShading(DictionaryToken shadingDictionary, ColorSpaceDetails colorSpace,
@ -175,7 +193,7 @@
throw new ArgumentNullException($"{NameToken.Function} is required for shading type '{ShadingType.Axial}'.");
}
PdfFunction function = PdfFunctionParser.Create(shadingDictionary.Data[NameToken.Function], scanner, filterProvider);
PdfFunction[] functions = GetFunctions(shadingDictionary.Data[NameToken.Function], scanner, filterProvider);
bool[] extend = new bool[] { false, false }; // Default values
if (shadingDictionary.TryGet<ArrayToken>(NameToken.Extend, scanner, out var extendToken))
@ -183,7 +201,7 @@
extend = extendToken.Data.OfType<BooleanToken>().Select(v => v.Data).ToArray();
}
return new AxialShading(antiAlias, shadingDictionary, colorSpace, bbox, background, coords, domain, function, extend);
return new AxialShading(antiAlias, shadingDictionary, colorSpace, bbox, background, coords, domain, functions, extend);
}
private static RadialShading CreateRadialShading(DictionaryToken shadingDictionary, ColorSpaceDetails colorSpace,
@ -215,7 +233,7 @@
throw new ArgumentNullException($"{NameToken.Function} is required for shading type '{ShadingType.Radial}'.");
}
PdfFunction function = PdfFunctionParser.Create(shadingDictionary.Data[NameToken.Function], scanner, filterProvider);
PdfFunction[] functions = GetFunctions(shadingDictionary.Data[NameToken.Function], scanner, filterProvider);
bool[] extend = new bool[] { false, false }; // Default values
if (shadingDictionary.TryGet<ArrayToken>(NameToken.Extend, scanner, out var extendToken))
@ -223,7 +241,7 @@
extend = extendToken.Data.OfType<BooleanToken>().Select(v => v.Data).ToArray();
}
return new RadialShading(antiAlias, shadingDictionary, colorSpace, bbox, background, coords, domain, function, extend);
return new RadialShading(antiAlias, shadingDictionary, colorSpace, bbox, background, coords, domain, functions, extend);
}
private static FreeFormGouraudShading CreateFreeFormGouraudShadedTriangleMeshesShading(StreamToken shadingStream,
@ -269,14 +287,14 @@
throw new ArgumentNullException($"{NameToken.Decode} is required for shading type '{ShadingType.FreeFormGouraud}'.");
}
PdfFunction function = null; // Optional
PdfFunction[] functions = null; // Optional
if (shadingStream.StreamDictionary.ContainsKey(NameToken.Function))
{
function = PdfFunctionParser.Create(shadingStream.StreamDictionary.Data[NameToken.Function], scanner, filterProvider);
functions = GetFunctions(shadingStream.StreamDictionary.Data[NameToken.Function], scanner, filterProvider);
}
return new FreeFormGouraudShading(antiAlias, shadingStream, colorSpace, bbox, background,
bitsPerCoordinate, bitsPerComponent, bitsPerFlag, decode, function);
bitsPerCoordinate, bitsPerComponent, bitsPerFlag, decode, functions);
}
private static LatticeFormGouraudShading CreateLatticeFormGouraudShadedTriangleMeshesShading(StreamToken shadingStream,
@ -322,14 +340,14 @@
throw new ArgumentNullException($"{NameToken.Decode} is required for shading type '{ShadingType.LatticeFormGouraud}'.");
}
PdfFunction function = null; // Optional
PdfFunction[] functions = null; // Optional
if (shadingStream.StreamDictionary.ContainsKey(NameToken.Function))
{
function = PdfFunctionParser.Create(shadingStream.StreamDictionary.Data[NameToken.Function], scanner, filterProvider);
functions = GetFunctions(shadingStream.StreamDictionary.Data[NameToken.Function], scanner, filterProvider);
}
return new LatticeFormGouraudShading(antiAlias, shadingStream, colorSpace, bbox, background,
bitsPerCoordinate, bitsPerComponent, verticesPerRow, decode, function);
bitsPerCoordinate, bitsPerComponent, verticesPerRow, decode, functions);
}
private static CoonsPatchMeshesShading CreateCoonsPatchMeshesShading(StreamToken shadingStream,
@ -375,14 +393,14 @@
throw new ArgumentNullException($"{NameToken.Decode} is required for shading type '{ShadingType.CoonsPatch}'.");
}
PdfFunction function = null; // Optional
PdfFunction[] functions = null; // Optional
if (shadingStream.StreamDictionary.ContainsKey(NameToken.Function))
{
function = PdfFunctionParser.Create(shadingStream.StreamDictionary.Data[NameToken.Function], scanner, filterProvider);
functions = GetFunctions(shadingStream.StreamDictionary.Data[NameToken.Function], scanner, filterProvider);
}
return new CoonsPatchMeshesShading(antiAlias, shadingStream, colorSpace, bbox, background,
bitsPerCoordinate, bitsPerComponent, bitsPerFlag, decode, function);
bitsPerCoordinate, bitsPerComponent, bitsPerFlag, decode, functions);
}
private static TensorProductPatchMeshesShading CreateTensorProductPatchMeshesShading(StreamToken shadingStream,
@ -428,14 +446,14 @@
throw new ArgumentNullException($"{NameToken.Decode} is required for shading type '{ShadingType.TensorProductPatch}'.");
}
PdfFunction function = null; // Optional
PdfFunction[] functions = null; // Optional
if (shadingStream.StreamDictionary.ContainsKey(NameToken.Function))
{
function = PdfFunctionParser.Create(shadingStream.StreamDictionary.Data[NameToken.Function], scanner, filterProvider);
functions = GetFunctions(shadingStream.StreamDictionary.Data[NameToken.Function], scanner, filterProvider);
}
return new TensorProductPatchMeshesShading(antiAlias, shadingStream, colorSpace, bbox, background,
bitsPerCoordinate, bitsPerComponent, bitsPerFlag, decode, function);
bitsPerCoordinate, bitsPerComponent, bitsPerFlag, decode, functions);
}
}
}