mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-08-20 09:37:44 +08:00
Properly implement color spaces
This commit is contained in:
parent
db0583edea
commit
b8a98fbed2
@ -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()
|
||||
|
||||
89
src/UglyToad.PdfPig.Tests/Integration/ColorSpaceTests.cs
Normal file
89
src/UglyToad.PdfPig.Tests/Integration/ColorSpaceTests.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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; }
|
||||
|
||||
|
||||
@ -24,6 +24,8 @@
|
||||
|
||||
bool TryGetNamedColorSpace(NameToken name, out ResourceColorSpace namedColorSpace);
|
||||
|
||||
ColorSpaceDetails GetColorSpaceDetails(NameToken name, DictionaryToken dictionary);
|
||||
|
||||
DictionaryToken GetMarkedContentPropertiesDictionary(NameToken name);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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 group’s 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 group’s 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);
|
||||
|
||||
|
||||
@ -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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 />
|
||||
|
||||
@ -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 />
|
||||
|
||||
@ -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 />
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
operationContext.ColorSpaceContext.SetNonStrokingColorGray(Gray);
|
||||
operationContext.GetCurrentState().ColorSpaceContext.SetNonStrokingColorGray(Gray);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@ -47,7 +47,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
operationContext.ColorSpaceContext.SetNonStrokingColorRgb(R, G, B);
|
||||
operationContext.GetCurrentState().ColorSpaceContext.SetNonStrokingColorRgb(R, G, B);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
operationContext.ColorSpaceContext.SetNonStrokingColorspace(Name);
|
||||
operationContext.GetCurrentState().ColorSpaceContext.SetNonStrokingColorspace(Name);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@ -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 />
|
||||
|
||||
@ -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 />
|
||||
|
||||
@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
operationContext.ColorSpaceContext.SetStrokingColorspace(Name);
|
||||
operationContext.GetCurrentState().ColorSpaceContext.SetStrokingColorspace(Name);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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))
|
||||
{
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user