Properly implement color spaces

This commit is contained in:
BobLD 2023-04-06 22:34:32 +01:00 committed by BobLd
parent db0583edea
commit b8a98fbed2
41 changed files with 824 additions and 631 deletions

View File

@ -26,15 +26,15 @@
public PdfPath CurrentPath { get; set; }
public IColorSpaceContext ColorSpaceContext { get; }
public PdfPoint CurrentPosition { get; set; }
public TestOperationContext()
{
StateStack.Push(new CurrentGraphicsState());
StateStack.Push(new CurrentGraphicsState()
{
ColorSpaceContext = new ColorSpaceContext(GetCurrentState, new ResourceStore(new TestPdfTokenScanner(), new TestFontFactory(), new TestFilterProvider()))
});
CurrentSubpath = new PdfSubpath();
ColorSpaceContext = new ColorSpaceContext(GetCurrentState, new ResourceStore(new TestPdfTokenScanner(), new TestFontFactory()));
}
public CurrentGraphicsState GetCurrentState()

View File

@ -0,0 +1,89 @@
namespace UglyToad.PdfPig.Tests.Integration
{
using System.Linq;
using UglyToad.PdfPig.Content;
using UglyToad.PdfPig.DocumentLayoutAnalysis.WordExtractor;
using Xunit;
public class ColorSpaceTests
{
[Fact]
public void CanGetAllPagesImages()
{
var path = IntegrationHelpers.GetDocumentPath("Pig Production Handbook.pdf");
using (var document = PdfDocument.Open(path))
{
for (int p = 0; p < document.NumberOfPages; p++)
{
var page = document.GetPage(p + 1);
var images = page.GetImages().ToArray();
foreach (var image in images)
{
if (image.TryGetPng(out var png))
{
}
}
}
}
}
[Fact]
public void SeparationIccColorSpacesWithForm()
{
var path = IntegrationHelpers.GetDocumentPath("68-1990-01_A.pdf");
using (var document = PdfDocument.Open(path))
{
Page page1 = document.GetPage(1);
var paths1 = page1.ExperimentalAccess.Paths.Where(p => p.IsFilled).ToArray();
Assert.Equal((0.930496m, 0.111542m, 0.142197m), paths1[0].FillColor.ToRGBValues()); // 'Reflex Red' Separation color space
Page page2 = document.GetPage(2);
var words = page2.GetWords(NearestNeighbourWordExtractor.Instance).ToArray();
var urlWord = words.First(w => w.ToString().Contains("www.extron.com"));
var firstLetter = urlWord.Letters[0];
Assert.Equal("w", firstLetter.Value);
Assert.Equal((0, 0, 1), firstLetter.Color.ToRGBValues()); // Blue
var paths2 = page2.ExperimentalAccess.Paths;
var filledPath = paths2.Where(p => p.IsFilled).ToArray();
var filledRects = filledPath.Where(p => p.Count == 1 && p[0].IsDrawnAsRectangle).ToArray();
// Colors picked from Acrobat reader
(decimal r, decimal g, decimal b) lightRed = (0.985m, 0.942m, 0.921m);
(decimal r, decimal g, decimal b) lightRed2 = (1m, 0.95m, 0.95m);
(decimal r, decimal g, decimal b) lightOrange = (0.993m, 0.964m, 0.929m);
var filledColors = filledRects
.OrderBy(x => x.GetBoundingRectangle().Value.Left)
.ThenByDescending(x => x.GetBoundingRectangle().Value.Top)
.Select(x => x.FillColor).ToArray();
for (int r = 0; r < filledColors.Length; r++)
{
var color = filledColors[r];
Assert.Equal(PdfPig.Graphics.Colors.ColorSpace.DeviceRGB, color.ColorSpace);
if (r % 2 == 0)
{
if (r == 2)
{
Assert.Equal(lightRed2, color.ToRGBValues());
}
else
{
Assert.Equal(lightRed, color.ToRGBValues());
}
}
else
{
Assert.Equal(lightOrange, color.ToRGBValues());
}
}
}
}
}
}

View File

@ -16,8 +16,6 @@
public int HeightInSamples { get; set; }
public ColorSpace? ColorSpace => IsImageMask ? default(ColorSpace?) : ColorSpaceDetails.Type;
public int BitsPerComponent { get; set; } = 8;
public IReadOnlyList<byte> RawBytes { get; }

View File

@ -1100,7 +1100,6 @@
}
}
(int, double) GetCounts(PdfDocument toCount)
{
int letters = 0;
@ -1109,7 +1108,6 @@
{
foreach (var letter in page.Letters)
{
unchecked { letters += 1; }
unchecked
{

View File

@ -27,17 +27,6 @@
/// </summary>
int HeightInSamples { get; }
/// <summary>
/// The <see cref="ColorSpace"/> used to interpret the image.
/// This defines the number of color components per sample, e.g.
/// 1 component for <see cref="Graphics.Colors.ColorSpace.DeviceGray"/>,
/// 3 components for <see cref="Graphics.Colors.ColorSpace.DeviceRGB"/>,
/// 4 components for <see cref="Graphics.Colors.ColorSpace.DeviceCMYK"/>,
/// etc.
/// This is not defined where <see cref="IsImageMask"/> is <see langword="true"/> and is optional where the image is JPXEncoded for <see cref="XObjectImage"/>.
/// </summary>
ColorSpace? ColorSpace { get; }
/// <summary>
/// The number of bits used to represent each color component.
/// </summary>
@ -88,10 +77,13 @@
/// <summary>
/// The full dictionary for this image object.
/// </summary>
DictionaryToken ImageDictionary { get; }
DictionaryToken ImageDictionary { get; }
/// <summary>
/// Full details for the <see cref="ColorSpace"/> with any associated data.
/// The <see cref="ColorSpaceDetails"/> used to interpret the image.
/// <para>
/// This is not defined where <see cref="IsImageMask"/> is <see langword="true"/> and is optional where the image is JPXEncoded for <see cref="XObjectImage"/>.
/// </para>
/// </summary>
ColorSpaceDetails ColorSpaceDetails { get; }

View File

@ -24,6 +24,8 @@
bool TryGetNamedColorSpace(NameToken name, out ResourceColorSpace namedColorSpace);
ColorSpaceDetails GetColorSpaceDetails(NameToken name, DictionaryToken dictionary);
DictionaryToken GetMarkedContentPropertiesDictionary(NameToken name);
}
}

View File

@ -28,9 +28,6 @@
/// <inheritdoc />
public int HeightInSamples { get; }
/// <inheritdoc />
public ColorSpace? ColorSpace { get; }
/// <inheritdoc />
public int BitsPerComponent { get; }
@ -65,7 +62,6 @@
internal InlineImage(PdfRectangle bounds, int widthInSamples, int heightInSamples, int bitsPerComponent, bool isImageMask,
RenderingIntent renderingIntent,
bool interpolate,
ColorSpace? colorSpace,
IReadOnlyList<decimal> decode,
IReadOnlyList<byte> bytes,
IReadOnlyList<IFilter> filters,
@ -75,7 +71,6 @@
Bounds = bounds;
WidthInSamples = widthInSamples;
HeightInSamples = heightInSamples;
ColorSpace = colorSpace;
Decode = decode;
BitsPerComponent = bitsPerComponent;
IsImageMask = isImageMask;

View File

@ -8,12 +8,14 @@
using PdfFonts;
using Tokenization.Scanner;
using Tokens;
using UglyToad.PdfPig.Filters;
using Util;
internal class ResourceStore : IResourceStore
{
private readonly IPdfTokenScanner scanner;
private readonly IFontFactory fontFactory;
private readonly ILookupFilterProvider filterProvider;
private readonly Dictionary<IndirectReference, IFont> loadedFonts = new Dictionary<IndirectReference, IFont>();
private readonly Dictionary<NameToken, IFont> loadedDirectFonts = new Dictionary<NameToken, IFont>();
@ -21,22 +23,26 @@
private readonly Dictionary<NameToken, DictionaryToken> extendedGraphicsStates = new Dictionary<NameToken, DictionaryToken>();
private readonly Dictionary<NameToken, ResourceColorSpace> namedColorSpaces = new Dictionary<NameToken, ResourceColorSpace>();
private readonly StackDictionary<NameToken, ResourceColorSpace> namedColorSpaces = new StackDictionary<NameToken, ResourceColorSpace>();
private readonly Dictionary<NameToken, ColorSpaceDetails> loadedNamedColorSpaceDetails = new Dictionary<NameToken, ColorSpaceDetails>();
private readonly Dictionary<NameToken, DictionaryToken> markedContentProperties = new Dictionary<NameToken, DictionaryToken>();
private (NameToken name, IFont font) lastLoadedFont;
public ResourceStore(IPdfTokenScanner scanner, IFontFactory fontFactory)
public ResourceStore(IPdfTokenScanner scanner, IFontFactory fontFactory, ILookupFilterProvider filterProvider)
{
this.scanner = scanner;
this.fontFactory = fontFactory;
this.filterProvider = filterProvider;
}
public void LoadResourceDictionary(DictionaryToken resourceDictionary, InternalParsingOptions parsingOptions)
{
lastLoadedFont = (null, null);
loadedNamedColorSpaceDetails.Clear();
namedColorSpaces.Push();
currentResourceState.Push();
if (resourceDictionary.TryGet(NameToken.Font, out var fontBase))
@ -129,7 +135,9 @@
public void UnloadResourceDictionary()
{
lastLoadedFont = (null, null);
loadedNamedColorSpaceDetails.Clear();
currentResourceState.Pop();
namedColorSpaces.Pop();
}
private void LoadFontDictionary(DictionaryToken fontDictionary, InternalParsingOptions parsingOptions)
@ -235,13 +243,50 @@
return true;
}
public ColorSpaceDetails GetColorSpaceDetails(NameToken name, DictionaryToken dictionary)
{
dictionary ??= new DictionaryToken(new Dictionary<NameToken, IToken>());
// Null color space for images
if (name is null)
{
return ColorSpaceDetailsParser.GetColorSpaceDetails(null, dictionary, scanner, this, filterProvider);
}
if (name.TryMapToColorSpace(out ColorSpace colorspaceActual))
{
// TODO - We need to find a way to store profile that have an actual dictionnary, e.g. ICC profiles - without parsing them again
return ColorSpaceDetailsParser.GetColorSpaceDetails(colorspaceActual, dictionary, scanner, this, filterProvider);
}
// Named color spaces
if (loadedNamedColorSpaceDetails.TryGetValue(name, out ColorSpaceDetails csdLoaded))
{
return csdLoaded;
}
if (TryGetNamedColorSpace(name, out ResourceColorSpace namedColorSpace) &&
namedColorSpace.Name.TryMapToColorSpace(out ColorSpace mapped))
{
if (namedColorSpace.Data is null)
{
return ColorSpaceDetailsParser.GetColorSpaceDetails(mapped, dictionary, scanner, this, filterProvider);
}
else if (namedColorSpace.Data is ArrayToken array)
{
var csd = ColorSpaceDetailsParser.GetColorSpaceDetails(mapped, dictionary.With(NameToken.ColorSpace, array), scanner, this, filterProvider);
loadedNamedColorSpaceDetails[name] = csd;
return csd;
}
}
throw new InvalidOperationException($"Could not find color space for token '{name}'.");
}
public StreamToken GetXObject(NameToken name)
{
var reference = currentResourceState[name];
var stream = DirectObjectFinder.Get<StreamToken>(new IndirectReferenceToken(reference), scanner);
return stream;
return DirectObjectFinder.Get<StreamToken>(new IndirectReferenceToken(reference), scanner);
}
public DictionaryToken GetExtendedGraphicsStateDictionary(NameToken name)

View File

@ -147,7 +147,7 @@
/// In many cases will be an array of a single value, but not always.</param>
/// <returns>The of outputs the function returns based on those inputs.
/// In many cases will be an array of a single value, but not always.</returns>
public abstract double[] Eval(double[] input);
public abstract double[] Eval(params double[] input);
/// <summary>
/// Returns all ranges for the output values as <see cref="ArrayToken"/>. Required for type 0 and type 4 functions.
@ -216,7 +216,7 @@
/// <param name="rangeMin">the min value of the range</param>
/// <param name="rangeMax">the max value of the range</param>
/// <returns>the clipped value</returns>
protected static double ClipToRange(double x, double rangeMin, double rangeMax)
public static double ClipToRange(double x, double rangeMin, double rangeMax)
{
if (x < rangeMin)
{

View File

@ -369,7 +369,7 @@
return samples;
}
public override double[] Eval(double[] input)
public override double[] Eval(params double[] input)
{
//This involves linear interpolation based on a set of sample points.
//Theoretically it's not that difficult ... see section 3.9.1 of the PDF Reference.

View File

@ -96,7 +96,7 @@
}
}
public override double[] Eval(double[] input)
public override double[] Eval(params double[] input)
{
// exponential interpolation
double xToN = Math.Pow(input[0], N); // x^exponent

View File

@ -51,7 +51,7 @@
}
}
public override double[] Eval(double[] input)
public override double[] Eval(params double[] input)
{
// This function is known as a "stitching" function. Based on the input, it decides which child function to call.
// All functions in the array are 1-value-input functions

View File

@ -32,7 +32,7 @@
}
}
public override double[] Eval(double[] input)
public override double[] Eval(params double[] input)
{
//Setup the input values
ExecutionContext context = new ExecutionContext(operators);

View File

@ -1,6 +1,8 @@
namespace UglyToad.PdfPig.Graphics
{
using System;
using System.Collections.Generic;
using System.Linq;
using Colors;
using Content;
using Tokens;
@ -10,13 +12,9 @@
private readonly Func<CurrentGraphicsState> currentStateFunc;
private readonly IResourceStore resourceStore;
public ColorSpace CurrentStrokingColorSpace { get; private set; } = ColorSpace.DeviceGray;
public ColorSpaceDetails CurrentStrokingColorSpace { get; private set; } = DeviceGrayColorSpaceDetails.Instance;
public ColorSpace CurrentNonStrokingColorSpace { get; private set; } = ColorSpace.DeviceGray;
public NameToken AdvancedStrokingColorSpace { get; private set; }
public NameToken AdvancedNonStrokingColorSpace { get; private set; }
public ColorSpaceDetails CurrentNonStrokingColorSpace { get; set; } = DeviceGrayColorSpaceDetails.Instance;
public ColorSpaceContext(Func<CurrentGraphicsState> currentStateFunc, IResourceStore resourceStore)
{
@ -26,200 +24,89 @@
public void SetStrokingColorspace(NameToken colorspace)
{
void DefaultColorSpace(ColorSpace? colorSpaceActual = null)
CurrentStrokingColorSpace = resourceStore.GetColorSpaceDetails(colorspace, null);
if (CurrentStrokingColorSpace is UnsupportedColorSpaceDetails)
{
if (colorSpaceActual.HasValue)
{
switch (colorSpaceActual)
{
case ColorSpace.DeviceGray:
currentStateFunc().CurrentStrokingColor = GrayColor.Black;
break;
case ColorSpace.DeviceRGB:
currentStateFunc().CurrentStrokingColor = RGBColor.Black;
break;
case ColorSpace.DeviceCMYK:
currentStateFunc().CurrentStrokingColor = CMYKColor.Black;
break;
default:
currentStateFunc().CurrentStrokingColor = GrayColor.Black;
break;
}
}
else
{
CurrentStrokingColorSpace = ColorSpace.DeviceGray;
currentStateFunc().CurrentStrokingColor = GrayColor.Black;
}
return;
}
AdvancedStrokingColorSpace = null;
if (colorspace.TryMapToColorSpace(out var colorspaceActual))
{
CurrentStrokingColorSpace = colorspaceActual;
}
else if (resourceStore.TryGetNamedColorSpace(colorspace, out var namedColorSpace))
{
if (namedColorSpace.Name == NameToken.Separation && namedColorSpace.Data is ArrayToken separationArray
&& separationArray.Length == 4
&& separationArray[2] is NameToken alternativeColorSpaceName
&& alternativeColorSpaceName.TryMapToColorSpace(out colorspaceActual))
{
AdvancedStrokingColorSpace = namedColorSpace.Name;
CurrentStrokingColorSpace = colorspaceActual;
DefaultColorSpace(colorspaceActual);
}
else
{
DefaultColorSpace();
}
}
else
{
DefaultColorSpace();
}
currentStateFunc().CurrentStrokingColor = CurrentStrokingColorSpace.GetInitializeColor();
}
public void SetNonStrokingColorspace(NameToken colorspace)
public void SetStrokingColor(IReadOnlyList<decimal> operands, NameToken patternName)
{
void DefaultColorSpace(ColorSpace? colorSpaceActual = null)
if (CurrentStrokingColorSpace is UnsupportedColorSpaceDetails)
{
if (colorSpaceActual.HasValue)
{
switch (colorSpaceActual)
{
case ColorSpace.DeviceGray:
currentStateFunc().CurrentNonStrokingColor = GrayColor.Black;
break;
case ColorSpace.DeviceRGB:
currentStateFunc().CurrentNonStrokingColor = RGBColor.Black;
break;
case ColorSpace.DeviceCMYK:
currentStateFunc().CurrentNonStrokingColor = CMYKColor.Black;
break;
default:
currentStateFunc().CurrentNonStrokingColor = GrayColor.Black;
break;
}
}
else
{
CurrentNonStrokingColorSpace = ColorSpace.DeviceGray;
currentStateFunc().CurrentNonStrokingColor = GrayColor.Black;
}
return;
}
AdvancedNonStrokingColorSpace = null;
if (colorspace.TryMapToColorSpace(out var colorspaceActual))
{
CurrentNonStrokingColorSpace = colorspaceActual;
}
else if (resourceStore.TryGetNamedColorSpace(colorspace, out var namedColorSpace))
{
if (namedColorSpace.Name == NameToken.Separation && namedColorSpace.Data is ArrayToken separationArray
&& separationArray.Length == 4
&& separationArray[2] is NameToken alternativeColorSpaceName
&& alternativeColorSpaceName.TryMapToColorSpace(out colorspaceActual))
{
AdvancedNonStrokingColorSpace = namedColorSpace.Name;
CurrentNonStrokingColorSpace = colorspaceActual;
DefaultColorSpace(colorspaceActual);
}
else
{
DefaultColorSpace();
}
}
else
{
DefaultColorSpace();
}
currentStateFunc().CurrentStrokingColor = CurrentStrokingColorSpace.GetColor(operands.Select(v => (double)v).ToArray());
}
public void SetStrokingColorGray(decimal gray)
{
CurrentStrokingColorSpace = ColorSpace.DeviceGray;
if (gray == 0)
{
currentStateFunc().CurrentStrokingColor = GrayColor.Black;
}
else if (gray == 1)
{
currentStateFunc().CurrentStrokingColor = GrayColor.White;
}
else
{
currentStateFunc().CurrentStrokingColor = new GrayColor(gray);
}
CurrentStrokingColorSpace = DeviceGrayColorSpaceDetails.Instance;
currentStateFunc().CurrentStrokingColor = CurrentStrokingColorSpace.GetColor((double)gray);
}
public void SetStrokingColorRgb(decimal r, decimal g, decimal b)
{
CurrentStrokingColorSpace = ColorSpace.DeviceRGB;
if (r == 0 && g == 0 && b == 0)
{
currentStateFunc().CurrentStrokingColor = RGBColor.Black;
}
else if (r == 1 && g == 1 && b == 1)
{
currentStateFunc().CurrentStrokingColor = RGBColor.White;
}
else
{
currentStateFunc().CurrentStrokingColor = new RGBColor(r, g, b);
}
CurrentStrokingColorSpace = DeviceRgbColorSpaceDetails.Instance;
currentStateFunc().CurrentStrokingColor = CurrentStrokingColorSpace.GetColor((double)r, (double)g, (double)b);
}
public void SetStrokingColorCmyk(decimal c, decimal m, decimal y, decimal k)
{
CurrentStrokingColorSpace = ColorSpace.DeviceCMYK;
currentStateFunc().CurrentStrokingColor = new CMYKColor(c, m, y, k);
CurrentStrokingColorSpace = DeviceCmykColorSpaceDetails.Instance;
currentStateFunc().CurrentStrokingColor = CurrentStrokingColorSpace.GetColor((double)c, (double)m, (double)y, (double)k);
}
public void SetNonStrokingColorspace(NameToken colorspace)
{
CurrentNonStrokingColorSpace = resourceStore.GetColorSpaceDetails(colorspace, null);
if (CurrentNonStrokingColorSpace is UnsupportedColorSpaceDetails)
{
return;
}
currentStateFunc().CurrentNonStrokingColor = CurrentNonStrokingColorSpace.GetInitializeColor();
}
public void SetNonStrokingColor(IReadOnlyList<decimal> operands, NameToken patternName)
{
if (CurrentNonStrokingColorSpace is UnsupportedColorSpaceDetails)
{
return;
}
currentStateFunc().CurrentNonStrokingColor = CurrentNonStrokingColorSpace.GetColor(operands.Select(v => (double)v).ToArray());
}
public void SetNonStrokingColorGray(decimal gray)
{
CurrentNonStrokingColorSpace = ColorSpace.DeviceGray;
if (gray == 0)
{
currentStateFunc().CurrentNonStrokingColor = GrayColor.Black;
}
else if (gray == 1)
{
currentStateFunc().CurrentNonStrokingColor = GrayColor.White;
}
else
{
currentStateFunc().CurrentNonStrokingColor = new GrayColor(gray);
}
CurrentNonStrokingColorSpace = DeviceGrayColorSpaceDetails.Instance;
currentStateFunc().CurrentNonStrokingColor = CurrentNonStrokingColorSpace.GetColor((double)gray);
}
public void SetNonStrokingColorRgb(decimal r, decimal g, decimal b)
{
CurrentNonStrokingColorSpace = ColorSpace.DeviceRGB;
if (r == 0 && g == 0 && b == 0)
{
currentStateFunc().CurrentNonStrokingColor = RGBColor.Black;
}
else if (r == 1 && g == 1 && b == 1)
{
currentStateFunc().CurrentNonStrokingColor = RGBColor.White;
}
else
{
currentStateFunc().CurrentNonStrokingColor = new RGBColor(r, g, b);
}
CurrentNonStrokingColorSpace = DeviceRgbColorSpaceDetails.Instance;
currentStateFunc().CurrentNonStrokingColor = CurrentNonStrokingColorSpace.GetColor((double)r, (double)g, (double)b);
}
public void SetNonStrokingColorCmyk(decimal c, decimal m, decimal y, decimal k)
{
CurrentNonStrokingColorSpace = ColorSpace.DeviceCMYK;
currentStateFunc().CurrentNonStrokingColor = new CMYKColor(c, m, y, k);
CurrentNonStrokingColorSpace = DeviceCmykColorSpaceDetails.Instance;
currentStateFunc().CurrentNonStrokingColor = CurrentNonStrokingColorSpace.GetColor((double)c, (double)m, (double)y, (double)k);
}
public IColorSpaceContext DeepClone()
{
return new ColorSpaceContext(currentStateFunc, resourceStore)
{
CurrentStrokingColorSpace = CurrentStrokingColorSpace,
CurrentNonStrokingColorSpace = CurrentNonStrokingColorSpace
};
}
}
}

View File

@ -1,6 +1,7 @@
namespace UglyToad.PdfPig.Graphics.Colors
{
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Tokens;
@ -17,7 +18,12 @@
/// <summary>
/// The type of the ColorSpace.
/// </summary>
public ColorSpace Type { get; }
public ColorSpace Type { get; }
/// <summary>
/// The number of components for the color space.
/// </summary>
public abstract int NumberOfColorComponents { get; }
/// <summary>
/// The underlying type of ColorSpace, usually equal to <see cref="Type"/>
@ -32,7 +38,26 @@
{
Type = type;
BaseType = type;
}
}
/// <summary>
/// Get the color.
/// </summary>
public abstract IColor GetColor(params double[] values);
/// <summary>
/// Get the color that initialize the current stroking or nonstroking colour.
/// </summary>
public abstract IColor GetInitializeColor();
/// <summary>
/// Convert to byte.
/// </summary>
protected static byte ConvertToByte(decimal componentValue)
{
var rounded = Math.Round(componentValue * 255, MidpointRounding.AwayFromZero);
return (byte)rounded;
}
}
/// <summary>
@ -44,11 +69,42 @@
/// <summary>
/// The single instance of the <see cref="DeviceGrayColorSpaceDetails"/>.
/// </summary>
public static readonly DeviceGrayColorSpaceDetails Instance = new DeviceGrayColorSpaceDetails();
public static readonly DeviceGrayColorSpaceDetails Instance = new DeviceGrayColorSpaceDetails();
/// <inheritdoc/>
public override int NumberOfColorComponents => 1;
private DeviceGrayColorSpaceDetails() : base(ColorSpace.DeviceGray)
{
}
{ }
/// <inheritdoc/>
public override IColor GetColor(params double[] values)
{
if (values == null || values.Length != NumberOfColorComponents)
{
throw new ArgumentException($"Invalid number of imputs, expecting {NumberOfColorComponents} but got {values.Length}", nameof(values));
}
double gray = values[0];
if (gray == 0)
{
return GrayColor.Black;
}
else if (gray == 1)
{
return GrayColor.White;
}
else
{
return new GrayColor((decimal)gray);
}
}
/// <inheritdoc/>
public override IColor GetInitializeColor()
{
return GrayColor.Black;
}
}
/// <summary>
@ -60,25 +116,89 @@
/// <summary>
/// The single instance of the <see cref="DeviceRgbColorSpaceDetails"/>.
/// </summary>
public static readonly DeviceRgbColorSpaceDetails Instance = new DeviceRgbColorSpaceDetails();
public static readonly DeviceRgbColorSpaceDetails Instance = new DeviceRgbColorSpaceDetails();
/// <inheritdoc/>
public override int NumberOfColorComponents => 3;
private DeviceRgbColorSpaceDetails() : base(ColorSpace.DeviceRGB)
{
{ }
/// <inheritdoc/>
public override IColor GetColor(params double[] values)
{
if (values == null || values.Length != NumberOfColorComponents)
{
throw new ArgumentException($"Invalid number of imputs, expecting {NumberOfColorComponents} but got {values.Length}", nameof(values));
}
double r = values[0];
double g = values[1];
double b = values[2];
if (r == 0 && g == 0 && b == 0)
{
return RGBColor.Black;
}
else if (r == 1 && g == 1 && b == 1)
{
return RGBColor.White;
}
return new RGBColor((decimal)r, (decimal)g, (decimal)b);
}
/// <inheritdoc/>
public override IColor GetInitializeColor()
{
return RGBColor.Black;
}
}
/// <summary>
/// Color values are defined by four components cyan, magenta, yellow and black
/// Color values are defined by four components cyan, magenta, yellow and black.
/// </summary>
public sealed class DeviceCmykColorSpaceDetails : ColorSpaceDetails
{
/// <summary>
/// The single instance of the <see cref="DeviceCmykColorSpaceDetails"/>.
/// </summary>
public static readonly DeviceCmykColorSpaceDetails Instance = new DeviceCmykColorSpaceDetails();
public static readonly DeviceCmykColorSpaceDetails Instance = new DeviceCmykColorSpaceDetails();
/// <inheritdoc/>
public override int NumberOfColorComponents => 4;
private DeviceCmykColorSpaceDetails() : base(ColorSpace.DeviceCMYK)
{
}
/// <inheritdoc/>
public override IColor GetColor(params double[] values)
{
if (values == null || values.Length != NumberOfColorComponents)
{
throw new ArgumentException($"Invalid number of imputs, expecting {NumberOfColorComponents} but got {values.Length}", nameof(values));
}
double c = values[0];
double m = values[1];
double y = values[2];
double k = values[3];
if (c == 0 && m == 0 && y == 0 && k == 1)
{
return CMYKColor.Black;
}
else if (c == 0 && m == 0 && y == 0 && k == 0)
{
return CMYKColor.White;
}
return new CMYKColor((decimal)c, (decimal)m, (decimal)y, (decimal)k);
}
/// <inheritdoc/>
public override IColor GetInitializeColor()
{
return CMYKColor.Black;
}
}
@ -87,7 +207,9 @@
/// A PDF consumer treats each sample value as an index into the color table and uses the color value it finds there.
/// </summary>
public class IndexedColorSpaceDetails : ColorSpaceDetails
{
{
private readonly ConcurrentDictionary<double, IColor> cache = new ConcurrentDictionary<double, IColor>();
/// <summary>
/// Creates a indexed color space useful for exracting stencil masks as black-and-white images,
/// i.e. with a color palette of two colors (black and white). If the decode parameter array is
@ -98,11 +220,14 @@
{
var blackIsOne = decode.Length >= 2 && decode[0] == 1 && decode[1] == 0;
return new IndexedColorSpaceDetails(colorSpaceDetails, 1, blackIsOne ? new byte[] { 255, 0 } : new byte[] { 0, 255 });
}
}
/// <inheritdoc/>
public override int NumberOfColorComponents => 1;
/// <summary>
/// The base color space in which the values in the color table are to be interpreted.
/// It can be any device or CIE-based color space or(in PDF 1.3) a Separation or DeviceN space,
/// It can be any device or CIE-based color space or (in PDF 1.3) a Separation or DeviceN space,
/// but not a Pattern space or another Indexed space.
/// </summary>
public ColorSpaceDetails BaseColorSpaceDetails { get; }
@ -128,6 +253,88 @@
ColorTable = colorTable;
BaseType = baseColorSpaceDetails.BaseType;
}
/// <inheritdoc/>
public override IColor GetColor(params double[] values)
{
if (values == null || values.Length != NumberOfColorComponents)
{
throw new ArgumentException($"Invalid number of imputs, expecting {NumberOfColorComponents} but got {values.Length}", nameof(values));
}
return cache.GetOrAdd(values[0], v =>
{
var csBytes = UnwrapIndexedColorSpaceBytes(new[] { (byte)v });
return BaseColorSpaceDetails.GetColor(csBytes.Select(b => b / 255.0).ToArray());
});
}
internal byte[] UnwrapIndexedColorSpaceBytes(IReadOnlyList<byte> input)
{
var multiplier = 1;
Func<byte, IEnumerable<byte>> transformer = null;
switch (BaseType)
{
case ColorSpace.DeviceRGB:
case ColorSpace.CalRGB:
transformer = x =>
{
var r = new byte[3];
for (var i = 0; i < 3; i++)
{
r[i] = ColorTable[x * 3 + i];
}
return r;
};
multiplier = 3;
break;
case ColorSpace.DeviceCMYK:
transformer = x =>
{
var r = new byte[4];
for (var i = 0; i < 4; i++)
{
r[i] = ColorTable[x * 4 + i];
}
return r;
};
multiplier = 4;
break;
case ColorSpace.DeviceGray:
case ColorSpace.CalGray:
transformer = x => new[] { ColorTable[x] };
multiplier = 1;
break;
}
if (transformer != null)
{
var result = new byte[input.Count * multiplier];
var i = 0;
foreach (var b in input)
{
foreach (var newByte in transformer(b))
{
result[i++] = newByte;
}
}
return result;
}
return input.ToArray();
}
/// <inheritdoc/>
public override IColor GetInitializeColor()
{
// Setting the current stroking or nonstroking colour space to an Indexed colour space shall
// initialize the corresponding current colour to 0.
return GetColor(0);
}
}
/// <summary>
@ -137,7 +344,12 @@
/// that controls the application of the given colorant or color components only.
/// </summary>
public class SeparationColorSpaceDetails : ColorSpaceDetails
{
{
private readonly ConcurrentDictionary<double, IColor> cache = new ConcurrentDictionary<double, IColor>();
/// <inheritdoc/>
public override int NumberOfColorComponents => 1;
/// <summary>
/// Specifies the name of the colorant that this Separation color space is intended to represent.
/// </summary>
@ -180,6 +392,44 @@
Name = name;
AlternateColorSpaceDetails = alternateColorSpaceDetails;
TintFunction = tintFunction;
}
/// <inheritdoc/>
public override IColor GetColor(params double[] values)
{
if (values == null || values.Length != NumberOfColorComponents)
{
throw new ArgumentException($"Invalid number of imputs, expecting {NumberOfColorComponents} but got {values.Length}", nameof(values));
}
// TODO - we ignore the name for now
return cache.GetOrAdd(values[0], v =>
{
var evaled = TintFunction.Eval(v);
return AlternateColorSpaceDetails.GetColor(evaled);
});
}
internal IReadOnlyList<byte> TransformToRGB(IReadOnlyList<byte> values)
{
var transformed = new List<byte>();
for (var i = 0; i < values.Count; i += 3)
{
var (r, g, b) = GetColor(values[i++] / 255.0).ToRGBValues();
transformed.Add(ConvertToByte(r));
transformed.Add(ConvertToByte(g));
transformed.Add(ConvertToByte(b));
}
return transformed;
}
/// <inheritdoc/>
public override IColor GetInitializeColor()
{
// The initial value for both the stroking and nonstroking colour in the graphics state shall be 1.0.
return GetColor(1.0);
}
}
@ -191,7 +441,11 @@
/// </summary>
public class CalGrayColorSpaceDetails : ColorSpaceDetails
{
/// <inheritdoc/>
public override int NumberOfColorComponents => 1;
private readonly CIEBasedColorSpaceTransformer colorSpaceTransformer;
/// <summary>
/// An array of three numbers [XW YW ZW] specifying the tristimulus value, in the CIE 1931 XYZ space of the
/// diffuse white point. The numbers XW and ZW shall be positive, and YW shall be equal to 1.0.
@ -205,7 +459,7 @@
public IReadOnlyList<decimal> BlackPoint { get; }
/// <summary>
/// A number defining defining the gamma for the gray (A) component. Gamma must be positive and is generally
/// A number defining the gamma for the gray (A) component. Gamma must be positive and is generally
/// greater than or equal to 1. Default value: 1.
/// </summary>
public decimal Gamma { get; }
@ -248,23 +502,61 @@
/// <summary>
/// Transforms the supplied A color to grayscale RGB (sRGB) using the propties of this
/// <see cref="CalGrayColorSpaceDetails"/> in the transformation process.
/// A represents the gray component of a calibrated gray space. The component must be in the range 0.0 to 1.0.
/// A represents the gray component of a calibrated gray space. The component must be in the range 0.0 to 1.0.
/// </summary>
internal RGBColor TransformToRGB(decimal colorA)
private RGBColor TransformToRGB(double colorA)
{
var colorRgb = colorSpaceTransformer.TransformToRGB(((double)colorA, (double)colorA, (double)colorA));
var colorRgb = colorSpaceTransformer.TransformToRGB((colorA, colorA, colorA));
return new RGBColor((decimal)colorRgb.R, (decimal)colorRgb.G, (decimal)colorRgb.B);
}
internal IReadOnlyList<byte> TransformToRGB(IReadOnlyList<byte> decoded)
{
var transformed = new List<byte>();
for (var i = 0; i < decoded.Count; i++)
{
var component = decoded[i] / 255.0;
var rgbPixel = TransformToRGB(component);
// We only need one component here
transformed.Add(ConvertToByte(rgbPixel.R));
}
return transformed;
}
/// <inheritdoc/>
public override IColor GetColor(params double[] values)
{
if (values == null || values.Length != NumberOfColorComponents)
{
throw new ArgumentException($"Invalid number of imputs, expecting {NumberOfColorComponents} but got {values.Length}", nameof(values));
}
return TransformToRGB(values[0]);
}
/// <inheritdoc/>
public override IColor GetInitializeColor()
{
// Setting the current stroking or nonstroking colour space to any CIE-based colour space shall
// initialize all components of the corresponding current colour to 0.0 (unless the range of valid
// values for a given component does not include 0.0, in which case the nearest valid value shall
// be substituted.)
return GetColor(0);
}
}
/// <summary>
/// CIE (Commission Internationale de l'Éclairage) colorspace.
/// Specifies color related to human visual perception with the aim of producing consistent color on different output devices.
/// CalRGB - A CIE ABC color space with a single transformation.
/// A, B and C represent red, green and blue color values in the range 0.0 to 1.0.
/// A, B and C represent red, green and blue color values in the range 0.0 to 1.0.
/// </summary>
public class CalRGBColorSpaceDetails : ColorSpaceDetails
{
/// <inheritdoc/>
public override int NumberOfColorComponents => 3;
private readonly CIEBasedColorSpaceTransformer colorSpaceTransformer;
/// <summary>
@ -321,7 +613,7 @@
{
throw new ArgumentOutOfRangeException(nameof(matrix), matrix, $"Must consist of exactly nine numbers, but was passed {matrix.Count}.");
}
colorSpaceTransformer =
new CIEBasedColorSpaceTransformer(((double)WhitePoint[0], (double)WhitePoint[1], (double)WhitePoint[2]), RGBWorkingSpace.sRGB)
{
@ -340,12 +632,48 @@
/// <summary>
/// Transforms the supplied ABC color to RGB (sRGB) using the propties of this <see cref="CalRGBColorSpaceDetails"/>
/// in the transformation process.
/// A, B and C represent red, green and blue calibrated color values in the range 0.0 to 1.0.
/// A, B and C represent red, green and blue calibrated color values in the range 0.0 to 1.0.
/// </summary>
internal RGBColor TransformToRGB((decimal A, decimal B, decimal C) colorAbc)
private RGBColor TransformToRGB((double A, double B, double C) colorAbc)
{
var colorRgb = colorSpaceTransformer.TransformToRGB(((double)colorAbc.A, (double)colorAbc.B, (double)colorAbc.C));
return new RGBColor((decimal)colorRgb.R, (decimal) colorRgb.G, (decimal) colorRgb.B);
var colorRgb = colorSpaceTransformer.TransformToRGB((colorAbc.A, colorAbc.B, colorAbc.C));
return new RGBColor((decimal)colorRgb.R, (decimal)colorRgb.G, (decimal)colorRgb.B);
}
internal IReadOnlyList<byte> TransformToRGB(IReadOnlyList<byte> decoded)
{
var transformed = new List<byte>();
for (var i = 0; i < decoded.Count; i += 3)
{
var rgbPixel = TransformToRGB((decoded[i] / 255.0, decoded[i + 1] / 255.0, decoded[i + 2] / 255.0));
transformed.Add(ConvertToByte(rgbPixel.R));
transformed.Add(ConvertToByte(rgbPixel.G));
transformed.Add(ConvertToByte(rgbPixel.B));
}
return transformed;
}
/// <inheritdoc/>
public override IColor GetColor(params double[] values)
{
if (values == null || values.Length != NumberOfColorComponents)
{
throw new ArgumentException($"Invalid number of imputs, expecting {NumberOfColorComponents} but got {values.Length}", nameof(values));
}
return TransformToRGB((values[0], values[1], values[2]));
}
/// <inheritdoc/>
public override IColor GetInitializeColor()
{
// Setting the current stroking or nonstroking colour space to any CIE-based colour space shall
// initialize all components of the corresponding current colour to 0.0 (unless the range of valid
// values for a given component does not include 0.0, in which case the nearest valid value shall
// be substituted.)
return TransformToRGB((0, 0, 0));
}
}
@ -365,7 +693,7 @@
/// This numbers shall match the number of components actually in the ICC profile.
/// Valid values are 1, 3 and 4.
/// </summary>
public int NumberOfColorComponents { get; }
public override int NumberOfColorComponents { get; }
/// <summary>
/// An alternate color space that can be used in case the one specified in the stream data is not
@ -373,11 +701,12 @@
/// valid color space (except a Pattern color space). If this property isn't explicitly set during
/// construction, it will assume one of the color spaces, DeviceGray, DeviceRGB or DeviceCMYK depending
/// on whether the value of <see cref="NumberOfColorComponents"/> is 1, 3 or respectively.
///
/// <para>
/// Conversion of the source color values should not be performed when using the alternate color space.
/// Color values within the range of the ICCBased color space might not be within the range of the
/// alternate color space. In this case, the nearest values within the range of the alternate space
/// must be substituted.
/// </para>
/// </summary>
[NotNull]
public ColorSpaceDetails AlternateColorSpaceDetails { get; }
@ -410,8 +739,8 @@
NumberOfColorComponents = numberOfColorComponents;
AlternateColorSpaceDetails = alternateColorSpaceDetails ??
(NumberOfColorComponents == 1 ? (ColorSpaceDetails) DeviceGrayColorSpaceDetails.Instance :
NumberOfColorComponents == 3 ? (ColorSpaceDetails) DeviceRgbColorSpaceDetails.Instance : (ColorSpaceDetails) DeviceCmykColorSpaceDetails.Instance);
(NumberOfColorComponents == 1 ? (ColorSpaceDetails)DeviceGrayColorSpaceDetails.Instance :
NumberOfColorComponents == 3 ? (ColorSpaceDetails)DeviceRgbColorSpaceDetails.Instance : (ColorSpaceDetails)DeviceCmykColorSpaceDetails.Instance);
BaseType = AlternateColorSpaceDetails.BaseType;
Range = range ??
@ -419,9 +748,34 @@
if (Range.Count != 2 * numberOfColorComponents)
{
throw new ArgumentOutOfRangeException(nameof(range), range,
$"Must consist of exactly {2 * numberOfColorComponents } (2 x NumberOfColorComponents), but was passed {range.Count }");
$"Must consist of exactly {2 * numberOfColorComponents} (2 x NumberOfColorComponents), but was passed {range.Count}");
}
Metadata = metadata;
}
/// <inheritdoc/>
public override IColor GetColor(params double[] values)
{
if (values == null || values.Length != NumberOfColorComponents)
{
throw new ArgumentException($"Invalid number of imputs, expecting {NumberOfColorComponents} but got {values.Length}", nameof(values));
}
// TODO - use ICC profile
return AlternateColorSpaceDetails.GetColor(values);
}
/// <inheritdoc/>
public override IColor GetInitializeColor()
{
// Setting the current stroking or nonstroking colour space to any CIE-based colour space shall
// initialize all components of the corresponding current colour to 0.0 (unless the range of valid
// values for a given component does not include 0.0, in which case the nearest valid value shall
// be substituted.)
double v = PdfFunction.ClipToRange(0.0, (double)Range[0], (double)Range[1]);
double[] init = Enumerable.Repeat(v, NumberOfColorComponents).ToArray();
return GetColor(init);
}
}
@ -433,10 +787,34 @@
/// <summary>
/// The single instance of the <see cref="UnsupportedColorSpaceDetails"/>.
/// </summary>
public static readonly UnsupportedColorSpaceDetails Instance = new UnsupportedColorSpaceDetails();
public static readonly UnsupportedColorSpaceDetails Instance = new UnsupportedColorSpaceDetails();
/// <summary>
/// <inheritdoc/>
/// <para>
/// Cannot be called for <see cref="UnsupportedColorSpaceDetails"/>, will throw a <see cref="InvalidOperationException"/>.
/// </para>
/// </summary>
public override int NumberOfColorComponents => throw new InvalidOperationException("UnsupportedColorSpaceDetails");
//private readonly IColor debugColor = new RGBColor(255m / 255m, 20m / 255m, 147m / 255m);
private UnsupportedColorSpaceDetails() : base(ColorSpace.DeviceGray)
{
}
/// <inheritdoc/>
public override IColor GetColor(params double[] values)
{
//return debugColor;
throw new InvalidOperationException("UnsupportedColorSpaceDetails");
}
/// <inheritdoc/>
public override IColor GetInitializeColor()
{
//return debugColor;
throw new InvalidOperationException("UnsupportedColorSpaceDetails");
}
}
}
}

View File

@ -36,20 +36,21 @@
/// <summary>
/// Maps from a <see cref="NameToken"/> to the corresponding <see cref="ColorSpace"/> if one exists.
/// <para>Includes extended color spaces.</para>
/// </summary>
public static bool TryMapToColorSpace(this NameToken name, out ColorSpace colorspace)
{
colorspace = ColorSpace.DeviceGray;
if (name.Data == NameToken.Devicegray.Data)
if (name.Data == NameToken.Devicegray.Data || name.Data == "G")
{
colorspace = ColorSpace.DeviceGray;
}
else if (name.Data == NameToken.Devicergb.Data)
else if (name.Data == NameToken.Devicergb.Data || name.Data == "RGB")
{
colorspace = ColorSpace.DeviceRGB;
}
else if (name.Data == NameToken.Devicecmyk.Data)
else if (name.Data == NameToken.Devicecmyk.Data || name.Data == "CMYK")
{
colorspace = ColorSpace.DeviceCMYK;
}
@ -69,7 +70,7 @@
{
colorspace = ColorSpace.ICCBased;
}
else if (name.Data == NameToken.Indexed.Data)
else if (name.Data == NameToken.Indexed.Data || name.Data == "I")
{
colorspace = ColorSpace.Indexed;
}

View File

@ -72,8 +72,6 @@
public PdfPath CurrentPath { get; private set; }
public IColorSpaceContext ColorSpaceContext { get; }
public PdfPoint CurrentPosition { get; set; }
public int StackSize => graphicsStack.Count;
@ -84,10 +82,10 @@
{XObjectType.PostScript, new List<XObjectContentRecord>()}
};
public ContentStreamProcessor(IResourceStore resourceStore,
UserSpaceUnit userSpaceUnit,
MediaBox mediaBox,
CropBox cropBox,
public ContentStreamProcessor(IResourceStore resourceStore,
UserSpaceUnit userSpaceUnit,
MediaBox mediaBox,
CropBox cropBox,
PageRotationDegrees rotation,
IPdfTokenScanner pdfScanner,
IPageContentParser pageContentParser,
@ -111,16 +109,15 @@
graphicsStack.Push(new CurrentGraphicsState()
{
CurrentTransformationMatrix = GetInitialMatrix(userSpaceUnit, mediaBox, cropBox, rotation, parsingOptions.Logger),
CurrentClippingPath = clippingPath
CurrentClippingPath = clippingPath,
ColorSpaceContext = new ColorSpaceContext(GetCurrentState, resourceStore)
});
ColorSpaceContext = new ColorSpaceContext(GetCurrentState, resourceStore);
}
[System.Diagnostics.Contracts.Pure]
internal static TransformationMatrix GetInitialMatrix(UserSpaceUnit userSpaceUnit,
internal static TransformationMatrix GetInitialMatrix(UserSpaceUnit userSpaceUnit,
MediaBox mediaBox,
CropBox cropBox,
CropBox cropBox,
PageRotationDegrees rotation,
ILog log)
{
@ -434,14 +431,14 @@
if (subType.Equals(NameToken.Ps))
{
var contentRecord = new XObjectContentRecord(XObjectType.PostScript, xObjectStream, matrix, state.RenderingIntent,
state.CurrentStrokingColor?.ColorSpace ?? ColorSpace.DeviceRGB);
state.ColorSpaceContext?.CurrentStrokingColorSpace ?? DeviceRgbColorSpaceDetails.Instance);
xObjects[XObjectType.PostScript].Add(contentRecord);
}
else if (subType.Equals(NameToken.Image))
{
var contentRecord = new XObjectContentRecord(XObjectType.Image, xObjectStream, matrix, state.RenderingIntent,
state.CurrentStrokingColor?.ColorSpace ?? ColorSpace.DeviceRGB);
state.ColorSpaceContext?.CurrentStrokingColorSpace ?? DeviceRgbColorSpaceDetails.Instance);
images.Add(Union<XObjectContentRecord, InlineImage>.One(contentRecord));
@ -480,6 +477,77 @@
var startState = GetCurrentState();
// Transparency Group XObjects
if (formStream.StreamDictionary.TryGet(NameToken.Group, pdfScanner, out DictionaryToken formGroupToken))
{
if (!formGroupToken.TryGet<NameToken>(NameToken.S, pdfScanner, out var sToken) || sToken != NameToken.Transparency)
{
throw new InvalidOperationException($"Invalid Transparency Group XObject, '{NameToken.S}' token is not set or not equal to '{NameToken.Transparency}'.");
}
/* blend mode
* A conforming reader shall implicitly reset this parameter to its initial value at the beginning of execution of a
* transparency group XObject (see 11.6.6, "Transparency Group XObjects"). Initial value: Normal.
*/
//startState.BlendMode = BlendMode.Normal;
/* soft mask
* A conforming reader shall implicitly reset this parameter implicitly reset to its initial value at the beginning
* of execution of a transparency group XObject (see 11.6.6, "Transparency Group XObjects"). Initial value: None.
*/
// TODO
/* alpha constant
* A conforming reader shall implicitly reset this parameter to its initial value at the beginning of execution of a
* transparency group XObject (see 11.6.6, "Transparency Group XObjects"). Initial value: 1.0.
*/
startState.AlphaConstantNonStroking = 1.0m;
startState.AlphaConstantStroking = 1.0m;
if (formGroupToken.TryGet(NameToken.Cs, pdfScanner, out NameToken csNameToken))
{
startState.ColorSpaceContext.SetNonStrokingColorspace(csNameToken);
}
else if (formGroupToken.TryGet(NameToken.Cs, pdfScanner, out ArrayToken csArrayToken)
&& csArrayToken.Length > 0)
{
if (csArrayToken.Data[0] is NameToken firstColorSpaceName)
{
startState.ColorSpaceContext.CurrentNonStrokingColorSpace = resourceStore.GetColorSpaceDetails(firstColorSpaceName, formGroupToken);
startState.CurrentNonStrokingColor = startState.ColorSpaceContext.CurrentNonStrokingColorSpace.GetInitializeColor();
}
else
{
throw new InvalidOperationException("Invalid color space in Transparency Group XObjects.");
}
}
bool isolated = false;
if (formGroupToken.TryGet(NameToken.I, pdfScanner, out BooleanToken isolatedToken))
{
/*
* (Optional) A flag specifying whether the transparency group is isolated (see Isolated Groups).
* If this flag is true, objects within the group shall be composited against a fully transparent
* initial backdrop; if false, they shall be composited against the groups backdrop.
* Default value: false.
*/
isolated = isolatedToken.Data;
}
bool knockout = false;
if (formGroupToken.TryGet(NameToken.K, pdfScanner, out BooleanToken knockoutToken))
{
/*
* (Optional) A flag specifying whether the transparency group is a knockout group (see Knockout Groups).
* If this flag is false, later objects within the group shall be composited with earlier ones with which
* they overlap; if true, they shall be composited with the groups initial backdrop and shall overwrite
* (knock out) any earlier overlapping objects.
* Default value: false.
*/
knockout = knockoutToken.Data;
}
}
var formMatrix = TransformationMatrix.Identity;
if (formStream.StreamDictionary.TryGet<ArrayToken>(NameToken.Matrix, pdfScanner, out var formMatrixToken))
{
@ -487,9 +555,7 @@
}
// 2. Update current transformation matrix.
var resultingTransformationMatrix = formMatrix.Multiply(startState.CurrentTransformationMatrix);
startState.CurrentTransformationMatrix = resultingTransformationMatrix;
startState.CurrentTransformationMatrix = formMatrix.Multiply(startState.CurrentTransformationMatrix);
var contentStream = formStream.Decode(filterProvider, pdfScanner);

View File

@ -78,6 +78,21 @@ namespace UglyToad.PdfPig.Graphics
/// </summary>
public TransformationMatrix CurrentTransformationMatrix { get; set; } = TransformationMatrix.Identity;
/// <summary>
/// The active colorspaces for this content stream.
/// </summary>
public IColorSpaceContext ColorSpaceContext { get; set; }
/// <summary>
/// The current active stroking color for paths.
/// </summary>
public IColor CurrentStrokingColor { get; set; }
/// <summary>
/// The current active non-stroking color for text and fill.
/// </summary>
public IColor CurrentNonStrokingColor { get; set; }
#region Device Dependent
/// <summary>
@ -106,17 +121,6 @@ namespace UglyToad.PdfPig.Graphics
/// The precision for rendering color gradients on the output device.
/// </summary>
public decimal Smoothness { get; set; } = 0;
/// <summary>
/// The current active stroking color for paths.
/// </summary>
public IColor CurrentStrokingColor { get; set; }
/// <summary>
/// The current active non-stroking color for text and fill.
/// </summary>
public IColor CurrentNonStrokingColor { get; set; }
#endregion
/// <inheritdoc />
@ -143,7 +147,8 @@ namespace UglyToad.PdfPig.Graphics
StrokeAdjustment = StrokeAdjustment,
CurrentStrokingColor = CurrentStrokingColor,
CurrentNonStrokingColor = CurrentNonStrokingColor,
CurrentClippingPath = CurrentClippingPath
CurrentClippingPath = CurrentClippingPath,
ColorSpaceContext = ColorSpaceContext?.DeepClone(),
};
}
}

View File

@ -1,43 +1,40 @@
namespace UglyToad.PdfPig.Graphics
{
using Colors;
using System.Collections.Generic;
using Tokens;
using UglyToad.PdfPig.Core;
/// <summary>
/// Methods for manipulating and retrieving the current color state for a PDF content stream.
/// </summary>
public interface IColorSpaceContext
public interface IColorSpaceContext : IDeepCloneable<IColorSpaceContext>
{
/// <summary>
/// The <see cref="ColorSpace"/> used for stroking operations.
/// The <see cref="ColorSpaceDetails"/> used for stroking operations.
/// </summary>
ColorSpace CurrentStrokingColorSpace { get; }
ColorSpaceDetails CurrentStrokingColorSpace { get; }
/// <summary>
/// The <see cref="ColorSpace"/> used for non-stroking operations.
/// The <see cref="ColorSpaceDetails"/> used for non-stroking operations.
/// </summary>
ColorSpace CurrentNonStrokingColorSpace { get; }
ColorSpaceDetails CurrentNonStrokingColorSpace { get; internal set; }
/// <summary>
/// The name of the advanced ColorSpace active for stroking operations, if any.
/// </summary>
NameToken AdvancedStrokingColorSpace { get; }
/// <summary>
/// The name of the advanced ColorSpace active for non-stroking operations, if any.
/// </summary>
NameToken AdvancedNonStrokingColorSpace { get; }
/// <summary>
/// Set the current color space to use for stroking operations.
/// Set the current color space to use for stroking operations.
/// </summary>
void SetStrokingColorspace(NameToken colorspace);
/// <summary>
/// Set the current color space to use for nonstroking operations.
/// Set the current color space to use for nonstroking operations.
/// </summary>
void SetNonStrokingColorspace(NameToken colorspace);
/// <summary>
/// Set the color to use for stroking operations using the current color space.
/// </summary>
void SetStrokingColor(IReadOnlyList<decimal> operands, NameToken patternName = null);
/// <summary>
/// Set the stroking color space to DeviceGray and set the gray level to use for stroking operations.
/// </summary>
@ -61,6 +58,11 @@
/// <param name="k">Black - A number between 0 (minimum concentration) and 1 (maximum concentration).</param>
void SetStrokingColorCmyk(decimal c, decimal m, decimal y, decimal k);
/// <summary>
/// Set the color to use for nonstroking operations using the current color space.
/// </summary>
void SetNonStrokingColor(IReadOnlyList<decimal> operands, NameToken patternName = null);
/// <summary>
/// Set the nonstroking color space to DeviceGray and set the gray level to use for nonstroking operations.
/// </summary>
@ -76,7 +78,7 @@
void SetNonStrokingColorRgb(decimal r, decimal g, decimal b);
/// <summary>
/// Set the nonstroking color space to DeviceCMYK and set the color to use for nonstroking operations.
/// Set the nonstroking color space to DeviceCMYK and set the color to use for nonstroking operations.
/// </summary>
/// <param name="c">Cyan - A number between 0 (minimum concentration) and 1 (maximum concentration).</param>
/// <param name="m">Magenta - A number between 0 (minimum concentration) and 1 (maximum concentration).</param>
@ -84,4 +86,4 @@
/// <param name="k">Black - A number between 0 (minimum concentration) and 1 (maximum concentration).</param>
void SetNonStrokingColorCmyk(decimal c, decimal m, decimal y, decimal k);
}
}
}

View File

@ -10,11 +10,6 @@
/// </summary>
public interface IOperationContext
{
/// <summary>
/// The active colorspaces for this content stream.
/// </summary>
IColorSpaceContext ColorSpaceContext { get; }
/// <summary>
/// The current position.
/// </summary>
@ -30,6 +25,11 @@
/// </summary>
int StackSize { get; }
/// <summary>
/// Gets the current graphic state.
/// </summary>
CurrentGraphicsState GetCurrentState();
/// <summary>
/// Sets the current graphics state to the state from the top of the stack.
/// </summary>

View File

@ -41,11 +41,11 @@
var bitsPerComponent = GetByKeys<NumericToken>(NameToken.BitsPerComponent, NameToken.Bpc, !isMask)?.Int ?? 1;
var colorSpace = default(ColorSpace?);
NameToken colorSpaceName = null;
if (!isMask)
{
var colorSpaceName = GetByKeys<NameToken>(NameToken.ColorSpace, NameToken.Cs, false);
colorSpaceName = GetByKeys<NameToken>(NameToken.ColorSpace, NameToken.Cs, false);
if (colorSpaceName == null)
{
@ -61,32 +61,13 @@
throw new PdfDocumentFormatException($"Invalid ColorSpace array defined for inline image: {colorSpaceArray}.");
}
if (!ColorSpaceMapper.TryMap(firstColorSpaceName, resourceStore, out var colorSpaceMapped))
{
throw new PdfDocumentFormatException($"Invalid ColorSpace defined for inline image: {firstColorSpaceName}.");
}
colorSpace = colorSpaceMapped;
}
else
{
if (!ColorSpaceMapper.TryMap(colorSpaceName, resourceStore, out var colorSpaceMapped))
{
throw new PdfDocumentFormatException($"Invalid ColorSpace defined for inline image: {colorSpaceName}.");
}
colorSpace = colorSpaceMapped;
colorSpaceName = firstColorSpaceName;
}
}
var imgDic = new DictionaryToken(Properties ?? new Dictionary<NameToken, IToken>());
var details = ColorSpaceDetailsParser.GetColorSpaceDetails(
colorSpace,
imgDic,
tokenScanner,
resourceStore,
filterProvider);
var details = resourceStore.GetColorSpaceDetails(colorSpaceName, imgDic);
var renderingIntent = GetByKeys<NameToken>(NameToken.Intent, null, false)?.Data?.ToRenderingIntent() ?? defaultRenderingIntent;
@ -116,7 +97,7 @@
var interpolate = GetByKeys<BooleanToken>(NameToken.Interpolate, NameToken.I, false)?.Data ?? false;
return new InlineImage(bounds, width, height, bitsPerComponent, isMask, renderingIntent, interpolate, colorSpace, decode, Bytes,
return new InlineImage(bounds, width, height, bitsPerComponent, isMask, renderingIntent, interpolate, decode, Bytes,
filters,
imgDic,
details);

View File

@ -35,20 +35,7 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
switch (Operands.Count)
{
case 1:
operationContext.ColorSpaceContext.SetNonStrokingColorGray(Operands[0]);
break;
case 3:
operationContext.ColorSpaceContext.SetNonStrokingColorRgb(Operands[0], Operands[1], Operands[2]);
break;
case 4:
operationContext.ColorSpaceContext.SetNonStrokingColorCmyk(Operands[0], Operands[1], Operands[2], Operands[3]);
break;
default:
return;
}
operationContext.GetCurrentState().ColorSpaceContext.SetNonStrokingColor(Operands);
}
/// <inheritdoc />

View File

@ -3,7 +3,6 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Colors;
using Tokens;
using Writer;
@ -56,26 +55,7 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
if (operationContext.ColorSpaceContext.CurrentNonStrokingColorSpace.GetFamily() != ColorSpaceFamily.Device
|| operationContext.ColorSpaceContext.AdvancedNonStrokingColorSpace != null)
{
return;
}
switch (Operands.Count)
{
case 1:
operationContext.ColorSpaceContext.SetNonStrokingColorGray(Operands[0]);
break;
case 3:
operationContext.ColorSpaceContext.SetNonStrokingColorRgb(Operands[0], Operands[1], Operands[2]);
break;
case 4:
operationContext.ColorSpaceContext.SetNonStrokingColorCmyk(Operands[0], Operands[1], Operands[2], Operands[3]);
break;
default:
return;
}
operationContext.GetCurrentState().ColorSpaceContext.SetNonStrokingColor(Operands, PatternName);
}
/// <inheritdoc />

View File

@ -54,7 +54,7 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.ColorSpaceContext.SetNonStrokingColorCmyk(C, M, Y, K);
operationContext.GetCurrentState().ColorSpaceContext.SetNonStrokingColorCmyk(C, M, Y, K);
}
/// <inheritdoc />

View File

@ -33,7 +33,7 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.ColorSpaceContext.SetNonStrokingColorGray(Gray);
operationContext.GetCurrentState().ColorSpaceContext.SetNonStrokingColorGray(Gray);
}
/// <inheritdoc />

View File

@ -47,7 +47,7 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.ColorSpaceContext.SetNonStrokingColorRgb(R, G, B);
operationContext.GetCurrentState().ColorSpaceContext.SetNonStrokingColorRgb(R, G, B);
}
/// <inheritdoc />

View File

@ -38,7 +38,7 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.ColorSpaceContext.SetNonStrokingColorspace(Name);
operationContext.GetCurrentState().ColorSpaceContext.SetNonStrokingColorspace(Name);
}
/// <inheritdoc />

View File

@ -35,20 +35,7 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
switch (Operands.Count)
{
case 1:
operationContext.ColorSpaceContext.SetStrokingColorGray(Operands[0]);
break;
case 3:
operationContext.ColorSpaceContext.SetStrokingColorRgb(Operands[0], Operands[1], Operands[2]);
break;
case 4:
operationContext.ColorSpaceContext.SetStrokingColorCmyk(Operands[0], Operands[1], Operands[2], Operands[3]);
break;
default:
return;
}
operationContext.GetCurrentState().ColorSpaceContext.SetStrokingColor(Operands);
}
/// <inheritdoc />

View File

@ -3,7 +3,6 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Colors;
using Tokens;
using Writer;
@ -56,26 +55,7 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
if (operationContext.ColorSpaceContext.CurrentStrokingColorSpace.GetFamily() != ColorSpaceFamily.Device
|| operationContext.ColorSpaceContext.AdvancedStrokingColorSpace != null)
{
return;
}
switch (Operands.Count)
{
case 1:
operationContext.ColorSpaceContext.SetStrokingColorGray(Operands[0]);
break;
case 3:
operationContext.ColorSpaceContext.SetStrokingColorRgb(Operands[0], Operands[1], Operands[2]);
break;
case 4:
operationContext.ColorSpaceContext.SetStrokingColorCmyk(Operands[0], Operands[1], Operands[2], Operands[3]);
break;
default:
return;
}
operationContext.GetCurrentState().ColorSpaceContext.SetStrokingColor(Operands, PatternName);
}
/// <inheritdoc />

View File

@ -12,7 +12,7 @@
/// The symbol for this operation in a stream.
/// </summary>
public const string Symbol = "K";
/// <inheritdoc />
public string Operator => Symbol;
@ -54,7 +54,7 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.ColorSpaceContext.SetStrokingColorCmyk(C, M, Y, K);
operationContext.GetCurrentState().ColorSpaceContext.SetStrokingColorCmyk(C, M, Y, K);
}
/// <inheritdoc />
@ -78,4 +78,4 @@
return $"{C} {M} {Y} {K} {Symbol}";
}
}
}
}

View File

@ -33,9 +33,9 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.ColorSpaceContext.SetStrokingColorGray(Gray);
operationContext.GetCurrentState().ColorSpaceContext.SetStrokingColorGray(Gray);
}
/// <inheritdoc />
public void Write(Stream stream)
{
@ -48,4 +48,4 @@
return $"{Gray} {Symbol}";
}
}
}
}

View File

@ -47,7 +47,7 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.ColorSpaceContext.SetStrokingColorRgb(R, G, B);
operationContext.GetCurrentState().ColorSpaceContext.SetStrokingColorRgb(R, G, B);
}
/// <inheritdoc />
@ -69,4 +69,4 @@
return $"{R} {G} {B} {Symbol}";
}
}
}
}

View File

@ -38,7 +38,7 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.ColorSpaceContext.SetStrokingColorspace(Name);
operationContext.GetCurrentState().ColorSpaceContext.SetStrokingColorspace(Name);
}
/// <inheritdoc />

View File

@ -37,11 +37,11 @@
/// <summary>
/// The default color space.
/// </summary>
public ColorSpace DefaultColorSpace { get; }
public ColorSpaceDetails DefaultColorSpace { get; }
internal XObjectContentRecord(XObjectType type, StreamToken stream, TransformationMatrix appliedTransformation,
RenderingIntent defaultRenderingIntent,
ColorSpace defaultColorSpace)
ColorSpaceDetails defaultColorSpace)
{
Type = type;
Stream = stream ?? throw new ArgumentNullException(nameof(stream));

View File

@ -37,66 +37,41 @@
}
// Remove padding bytes when the stride width differs from the image width
var bytesPerPixel = details is IndexedColorSpaceDetails ? 1 : GetBytesPerPixel(details);
var bytesPerPixel = details.NumberOfColorComponents;
var strideWidth = decoded.Count / imageHeight / bytesPerPixel;
if (strideWidth != imageWidth)
{
decoded = RemoveStridePadding(decoded.ToArray(), strideWidth, imageWidth, imageHeight, bytesPerPixel);
}
// In case of indexed color space images, unwrap indices to actual pixel component values
if (details is IndexedColorSpaceDetails indexed)
{
decoded = UnwrapIndexedColorSpaceBytes(indexed, decoded);
// Use the base color space in potential further decoding
details = indexed.BaseColorSpaceDetails;
}
if (details is CalRGBColorSpaceDetails calRgb)
{
decoded = TransformToRGB(calRgb, decoded);
}
if (details is CalGrayColorSpaceDetails calGray)
{
decoded = TransformToRgbGrayScale(calGray, decoded);
if (details is SeparationColorSpaceDetails separation)
{
decoded = separation.TransformToRGB(decoded);
}
else
{
// In case of indexed color space images, unwrap indices to actual pixel component values
if (details is IndexedColorSpaceDetails indexed)
{
decoded = indexed.UnwrapIndexedColorSpaceBytes(decoded);
// Use the base color space in potential further decoding
details = indexed.BaseColorSpaceDetails;
}
if (details is CalRGBColorSpaceDetails calRgb)
{
decoded = calRgb.TransformToRGB(decoded);
}
else if (details is CalGrayColorSpaceDetails calGray)
{
decoded = calGray.TransformToRGB(decoded);
}
}
return decoded.ToArray();
}
private static int GetBytesPerPixel(ColorSpaceDetails details)
{
switch (details)
{
case DeviceGrayColorSpaceDetails deviceGray:
return 1;
case CalGrayColorSpaceDetails calGray:
return 1;
case DeviceRgbColorSpaceDetails deviceRgb:
return 3;
case CalRGBColorSpaceDetails calRgb:
return 3;
case DeviceCmykColorSpaceDetails deviceCmyk:
return 4;
case IndexedColorSpaceDetails indexed:
return GetBytesPerPixel(indexed.BaseColorSpaceDetails);
case ICCBasedColorSpaceDetails iccBased:
// Currently PdfPig only supports the 'Alternate' color space of ICCBasedColorSpaceDetails
return GetBytesPerPixel(iccBased.AlternateColorSpaceDetails);
default:
return 1;
}
}
private static byte[] UnpackComponents(IReadOnlyList<byte> input, int bitsPerComponent)
{
IEnumerable<byte> Unpack(byte b)
@ -108,7 +83,7 @@
}
}
return input.SelectMany(b => Unpack(b)).ToArray();
return input.SelectMany(Unpack).ToArray();
}
private static byte[] RemoveStridePadding(byte[] input, int strideWidth, int imageWidth, int imageHeight, int multiplier)
@ -123,98 +98,5 @@
return result;
}
private static IReadOnlyList<byte> TransformToRgbGrayScale(CalGrayColorSpaceDetails calGray, IReadOnlyList<byte> decoded)
{
var transformed = new List<byte>();
for (var i = 0; i < decoded.Count; i++)
{
var component = decoded[i] / 255m;
var rgbPixel = calGray.TransformToRGB(component);
// We only need one component here
transformed.Add(ConvertToByte(rgbPixel.R));
}
return transformed;
}
private static IReadOnlyList<byte> TransformToRGB(CalRGBColorSpaceDetails calRgb, IReadOnlyList<byte> decoded)
{
var transformed = new List<byte>();
for (var i = 0; i < decoded.Count; i += 3)
{
var rgbPixel = calRgb.TransformToRGB((decoded[i] / 255m, decoded[i + 1] / 255m, decoded[i + 2] / 255m));
transformed.Add(ConvertToByte(rgbPixel.R));
transformed.Add(ConvertToByte(rgbPixel.G));
transformed.Add(ConvertToByte(rgbPixel.B));
}
return transformed;
}
private static byte[] UnwrapIndexedColorSpaceBytes(IndexedColorSpaceDetails indexed, IReadOnlyList<byte> input)
{
var multiplier = 1;
Func<byte, IEnumerable<byte>> transformer = null;
switch (indexed.BaseType)
{
case ColorSpace.DeviceRGB:
case ColorSpace.CalRGB:
transformer = x =>
{
var r = new byte[3];
for (var i = 0; i < 3; i++)
{
r[i] = indexed.ColorTable[x * 3 + i];
}
return r;
};
multiplier = 3;
break;
case ColorSpace.DeviceCMYK:
transformer = x =>
{
var r = new byte[4];
for (var i = 0; i < 4; i++)
{
r[i] = indexed.ColorTable[x * 4 + i];
}
return r;
};
multiplier = 4;
break;
case ColorSpace.DeviceGray:
case ColorSpace.CalGray:
transformer = x => new[] { indexed.ColorTable[x] };
multiplier = 1;
break;
}
if (transformer != null)
{
var result = new byte[input.Count * multiplier];
var i = 0;
foreach (var b in input)
{
foreach (var newByte in transformer(b))
{
result[i++] = newByte;
}
}
return result;
}
return input.ToArray();
}
private static byte ConvertToByte(decimal componentValue)
{
var rounded = Math.Round(componentValue * 255, MidpointRounding.AwayFromZero);
return (byte)rounded;
}
}
}

View File

@ -12,12 +12,13 @@
var hasValidDetails = image.ColorSpaceDetails != null &&
!(image.ColorSpaceDetails is UnsupportedColorSpaceDetails);
var actualColorSpace = hasValidDetails ? image.ColorSpaceDetails.BaseType : image.ColorSpace;
var actualColorSpace = image.ColorSpaceDetails.BaseType;
var isColorSpaceSupported =
actualColorSpace == ColorSpace.DeviceGray || actualColorSpace == ColorSpace.DeviceRGB
|| actualColorSpace == ColorSpace.DeviceCMYK || actualColorSpace == ColorSpace.CalGray
|| actualColorSpace == ColorSpace.CalRGB;
var isColorSpaceSupported = hasValidDetails &&
(actualColorSpace == ColorSpace.DeviceGray || actualColorSpace == ColorSpace.DeviceRGB
|| actualColorSpace == ColorSpace.DeviceCMYK || actualColorSpace == ColorSpace.CalGray
|| actualColorSpace == ColorSpace.CalRGB);
if (!isColorSpaceSupported || !image.TryGetBytes(out var bytesPure))
{

View File

@ -169,7 +169,7 @@
type1Handler,
new Type3FontHandler(pdfScanner, filterProvider, encodingReader));
var resourceContainer = new ResourceStore(pdfScanner, fontFactory);
var resourceContainer = new ResourceStore(pdfScanner, fontFactory, filterProvider);
var information = DocumentInformationFactory.Create(
pdfScanner,

View File

@ -13,29 +13,6 @@
internal static class ColorSpaceMapper
{
private static bool TryExtendedColorSpaceNameMapping(NameToken name, out ColorSpace result)
{
result = ColorSpace.DeviceGray;
switch (name.Data)
{
case "G":
result = ColorSpace.DeviceGray;
return true;
case "RGB":
result = ColorSpace.DeviceRGB;
return true;
case "CMYK":
result = ColorSpace.DeviceCMYK;
return true;
case "I":
result = ColorSpace.Indexed;
return true;
}
return false;
}
public static bool TryMap(NameToken name, IResourceStore resourceStore, out ColorSpace colorSpaceResult)
{
if (name.TryMapToColorSpace(out colorSpaceResult))
@ -43,11 +20,6 @@
return true;
}
if (TryExtendedColorSpaceNameMapping(name, out colorSpaceResult))
{
return true;
}
if (!resourceStore.TryGetNamedColorSpace(name, out var colorSpaceNamedToken))
{
return false;
@ -58,14 +30,8 @@
return true;
}
if (TryExtendedColorSpaceNameMapping(colorSpaceNamedToken.Name, out colorSpaceResult))
{
return true;
}
return false;
}
}
internal static class ColorSpaceDetailsParser
@ -86,7 +52,7 @@
}
var colorSpaceDetails = GetColorSpaceDetails(colorSpace, imageDictionary.Without(NameToken.Filter).Without(NameToken.F), scanner, resourceStore, filterProvider, true);
var decodeRaw = imageDictionary.GetObjectOrDefault(NameToken.Decode, NameToken.D) as ArrayToken
?? new ArrayToken(EmptyArray<IToken>.Instance);
var decode = decodeRaw.Data.OfType<NumericToken>().Select(x => x.Data).ToArray();

View File

@ -11,7 +11,6 @@
using Graphics.Core;
using Tokenization.Scanner;
using Tokens;
using UglyToad.PdfPig.Parser.Parts;
using Util;
internal static class XObjectFactory
@ -107,7 +106,7 @@
}
var streamToken = new StreamToken(dictionary, xObject.Stream.Data);
var decodedBytes = supportsFilters ? new Lazy<IReadOnlyList<byte>>(() => streamToken.Decode(filterProvider, pdfScanner))
: null;
@ -119,40 +118,34 @@
.Select(x => x.Data)
.ToArray();
}
var colorSpace = default(ColorSpace?);
ColorSpaceDetails details = null;
if (!isImageMask)
{
if (dictionary.TryGet(NameToken.ColorSpace, pdfScanner, out NameToken colorSpaceNameToken)
&& TryMapColorSpace(colorSpaceNameToken, resourceStore, out var colorSpaceResult))
{
colorSpace = colorSpaceResult;
if (dictionary.TryGet(NameToken.ColorSpace, pdfScanner, out NameToken colorSpaceNameToken))
{
details = resourceStore.GetColorSpaceDetails(colorSpaceNameToken, dictionary);
}
else if (dictionary.TryGet(NameToken.ColorSpace, pdfScanner, out ArrayToken colorSpaceArrayToken)
&& colorSpaceArrayToken.Length > 0)
{
var first = colorSpaceArrayToken.Data[0];
if ((first is NameToken firstColorSpaceName) && TryMapColorSpace(firstColorSpaceName, resourceStore, out colorSpaceResult))
{
colorSpace = colorSpaceResult;
}
else if (dictionary.TryGet(NameToken.ColorSpace, pdfScanner, out ArrayToken colorSpaceArrayToken)
&& colorSpaceArrayToken.Length > 0 && colorSpaceArrayToken.Data[0] is NameToken firstColorSpaceName)
{
details = resourceStore.GetColorSpaceDetails(firstColorSpaceName, dictionary);
}
else if (!isJpxDecode)
{
colorSpace = xObject.DefaultColorSpace;
details = xObject.DefaultColorSpace;
}
}
var details = ColorSpaceDetailsParser.GetColorSpaceDetails(colorSpace, dictionary, pdfScanner, resourceStore, filterProvider);
else
{
details = resourceStore.GetColorSpaceDetails(null, dictionary);
}
return new XObjectImage(
bounds,
width,
height,
bitsPerComponent,
colorSpace,
isJpxDecode,
isImageMask,
intent,
@ -163,22 +156,5 @@
decodedBytes,
details);
}
private static bool TryMapColorSpace(NameToken name, IResourceStore resourceStore, out ColorSpace colorSpaceResult)
{
if (name.TryMapToColorSpace(out colorSpaceResult))
{
return true;
}
if (!resourceStore.TryGetNamedColorSpace(name, out var colorSpaceNamedToken))
{
return false;
}
return colorSpaceNamedToken.Name.TryMapToColorSpace(out colorSpaceResult);
}
}
}

View File

@ -28,9 +28,6 @@
/// <inheritdoc />
public int HeightInSamples { get; }
/// <inheritdoc />
public ColorSpace? ColorSpace { get; }
/// <inheritdoc />
public int BitsPerComponent { get; }
@ -73,7 +70,6 @@
int widthInSamples,
int heightInSamples,
int bitsPerComponent,
ColorSpace? colorSpace,
bool isJpxEncoded,
bool isImageMask,
RenderingIntent renderingIntent,
@ -88,7 +84,6 @@
WidthInSamples = widthInSamples;
HeightInSamples = heightInSamples;
BitsPerComponent = bitsPerComponent;
ColorSpace = colorSpace;
IsJpxEncoded = isJpxEncoded;
IsImageMask = isImageMask;
RenderingIntent = renderingIntent;