mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-12-21 19:29:51 +08:00
Implement Lab and DeviceN color spaces and fix bug in SetNonStrokingColorspace() for Transparency Group XObjects
This commit is contained in:
@@ -1,12 +1,254 @@
|
|||||||
namespace UglyToad.PdfPig.Tests.Integration
|
namespace UglyToad.PdfPig.Tests.Integration
|
||||||
{
|
{
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using UglyToad.PdfPig.Content;
|
using UglyToad.PdfPig.Content;
|
||||||
using UglyToad.PdfPig.DocumentLayoutAnalysis.WordExtractor;
|
using UglyToad.PdfPig.DocumentLayoutAnalysis.WordExtractor;
|
||||||
|
using UglyToad.PdfPig.Graphics.Colors;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
public class ColorSpaceTests
|
public class ColorSpaceTests
|
||||||
{
|
{
|
||||||
|
private const string OutputFolder = "ColorSpaceTests";
|
||||||
|
|
||||||
|
public ColorSpaceTests()
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(OutputFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IndexedDeviceNColorSpaceImages()
|
||||||
|
{
|
||||||
|
var path = IntegrationHelpers.GetDocumentPath("MOZILLA-3136-0.pdf");
|
||||||
|
|
||||||
|
using (var document = PdfDocument.Open(path))
|
||||||
|
{
|
||||||
|
// page 1
|
||||||
|
var page1 = document.GetPage(1);
|
||||||
|
var images1 = page1.GetImages().ToArray();
|
||||||
|
|
||||||
|
// image 12
|
||||||
|
var image12 = images1[12];
|
||||||
|
Assert.Equal(ColorSpace.Indexed, image12.ColorSpaceDetails.Type);
|
||||||
|
Assert.Equal(ColorSpace.DeviceN, image12.ColorSpaceDetails.BaseType);
|
||||||
|
Assert.True(image12.TryGetPng(out byte[] bytes1_12)); // Cyan square
|
||||||
|
File.WriteAllBytes(Path.Combine(OutputFolder, "MOZILLA-3136-0_1_12.png"), bytes1_12);
|
||||||
|
|
||||||
|
// image 13
|
||||||
|
var image13 = images1[13];
|
||||||
|
Assert.Equal(ColorSpace.Indexed, image13.ColorSpaceDetails.Type);
|
||||||
|
Assert.Equal(ColorSpace.DeviceN, image13.ColorSpaceDetails.BaseType);
|
||||||
|
Assert.True(image13.TryGetPng(out byte[] bytes1_13)); // Cyan square
|
||||||
|
File.WriteAllBytes(Path.Combine(OutputFolder, "MOZILLA-3136-0_1_13.png"), bytes1_13);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DeviceNColorSpaceImages()
|
||||||
|
{
|
||||||
|
var path = IntegrationHelpers.GetDocumentPath("DeviceN_CS_test.pdf");
|
||||||
|
|
||||||
|
using (var document = PdfDocument.Open(path))
|
||||||
|
{
|
||||||
|
// page 3
|
||||||
|
var page3 = document.GetPage(3);
|
||||||
|
var images3 = page3.GetImages().ToArray();
|
||||||
|
|
||||||
|
var image3_0 = images3[0];
|
||||||
|
var deviceNCs = image3_0.ColorSpaceDetails as DeviceNColorSpaceDetails;
|
||||||
|
Assert.NotNull(deviceNCs);
|
||||||
|
Assert.True(deviceNCs.AlternateColorSpaceDetails is ICCBasedColorSpaceDetails);
|
||||||
|
Assert.True(image3_0.TryGetPng(out byte[] bytes3_0));
|
||||||
|
File.WriteAllBytes(Path.Combine(OutputFolder, "DeviceN_CS_test_3_0.png"), bytes3_0);
|
||||||
|
|
||||||
|
var image3_2 = images3[2];
|
||||||
|
deviceNCs = image3_2.ColorSpaceDetails as DeviceNColorSpaceDetails;
|
||||||
|
Assert.NotNull(deviceNCs);
|
||||||
|
Assert.True(deviceNCs.AlternateColorSpaceDetails is ICCBasedColorSpaceDetails);
|
||||||
|
Assert.True(image3_2.TryGetPng(out byte[] bytes3_2));
|
||||||
|
File.WriteAllBytes(Path.Combine(OutputFolder, "DeviceN_CS_test_3_2.png"), bytes3_2);
|
||||||
|
|
||||||
|
// page 6
|
||||||
|
var page6 = document.GetPage(6);
|
||||||
|
var images6 = page6.GetImages().ToArray();
|
||||||
|
|
||||||
|
var image6_0 = images6[0];
|
||||||
|
deviceNCs = image6_0.ColorSpaceDetails as DeviceNColorSpaceDetails;
|
||||||
|
Assert.NotNull(deviceNCs);
|
||||||
|
Assert.True(deviceNCs.AlternateColorSpaceDetails is ICCBasedColorSpaceDetails);
|
||||||
|
Assert.True(image6_0.TryGetPng(out byte[] bytes6_0));
|
||||||
|
File.WriteAllBytes(Path.Combine(OutputFolder, "DeviceN_CS_test_6_0.png"), bytes6_0);
|
||||||
|
|
||||||
|
var image6_1 = images6[1];
|
||||||
|
deviceNCs = image6_0.ColorSpaceDetails as DeviceNColorSpaceDetails;
|
||||||
|
Assert.NotNull(deviceNCs);
|
||||||
|
Assert.True(deviceNCs.AlternateColorSpaceDetails is ICCBasedColorSpaceDetails);
|
||||||
|
Assert.True(image6_1.TryGetPng(out byte[] bytes6_1));
|
||||||
|
File.WriteAllBytes(Path.Combine(OutputFolder, "DeviceN_CS_test_6_1.png"), bytes6_1);
|
||||||
|
|
||||||
|
var image6_2 = images6[2];
|
||||||
|
deviceNCs = image6_2.ColorSpaceDetails as DeviceNColorSpaceDetails;
|
||||||
|
Assert.NotNull(deviceNCs);
|
||||||
|
Assert.True(deviceNCs.AlternateColorSpaceDetails is ICCBasedColorSpaceDetails);
|
||||||
|
Assert.True(image6_2.TryGetPng(out byte[] bytes6_2));
|
||||||
|
File.WriteAllBytes(Path.Combine(OutputFolder, "DeviceN_CS_test_6_2.png"), bytes6_2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SeparationColorSpaceImages()
|
||||||
|
{
|
||||||
|
var path = IntegrationHelpers.GetDocumentPath("MOZILLA-7375-0.pdf");
|
||||||
|
|
||||||
|
using (var document = PdfDocument.Open(path))
|
||||||
|
{
|
||||||
|
var page1 = document.GetPage(1);
|
||||||
|
var images = page1.GetImages();
|
||||||
|
var image1page1 = images.ElementAt(0);
|
||||||
|
var separationCs = image1page1.ColorSpaceDetails as SeparationColorSpaceDetails;
|
||||||
|
Assert.NotNull(separationCs);
|
||||||
|
Assert.True(separationCs.AlternateColorSpaceDetails is DeviceCmykColorSpaceDetails);
|
||||||
|
|
||||||
|
foreach (var image in images)
|
||||||
|
{
|
||||||
|
if (image.TryGetPng(out byte[] bytes))
|
||||||
|
{
|
||||||
|
// Can't check actual image processing yet because encoded not supported
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IndexedCalRgbColorSpaceImages()
|
||||||
|
{
|
||||||
|
var path = IntegrationHelpers.GetDocumentPath("MOZILLA-10084-0.pdf");
|
||||||
|
|
||||||
|
using (var document = PdfDocument.Open(path))
|
||||||
|
{
|
||||||
|
var page1 = document.GetPage(1);
|
||||||
|
var images1 = page1.GetImages().ToArray();
|
||||||
|
|
||||||
|
var image0 = images1[0];
|
||||||
|
Assert.Equal(ColorSpace.Indexed, image0.ColorSpaceDetails.Type);
|
||||||
|
|
||||||
|
var indexedCs = image0.ColorSpaceDetails as IndexedColorSpaceDetails;
|
||||||
|
Assert.NotNull(indexedCs);
|
||||||
|
Assert.Equal(ColorSpace.CalRGB, indexedCs.BaseColorSpaceDetails.Type);
|
||||||
|
Assert.True(image0.TryGetPng(out byte[] bytes0));
|
||||||
|
File.WriteAllBytes(Path.Combine(OutputFolder, "MOZILLA-10084-0_1_0.png"), bytes0);
|
||||||
|
|
||||||
|
var image1 = images1[1];
|
||||||
|
Assert.Equal(ColorSpace.Indexed, image1.ColorSpaceDetails.Type);
|
||||||
|
indexedCs = image1.ColorSpaceDetails as IndexedColorSpaceDetails;
|
||||||
|
Assert.NotNull(indexedCs);
|
||||||
|
Assert.Equal(ColorSpace.CalRGB, indexedCs.BaseColorSpaceDetails.Type);
|
||||||
|
Assert.True(image1.TryGetPng(out byte[] bytes1));
|
||||||
|
File.WriteAllBytes(Path.Combine(OutputFolder, "MOZILLA-10084-0_1_1.png"), bytes1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void StencilIndexedIccColorSpaceImages()
|
||||||
|
{
|
||||||
|
var path = IntegrationHelpers.GetDocumentPath("MOZILLA-10225-0.pdf");
|
||||||
|
|
||||||
|
using (var document = PdfDocument.Open(path))
|
||||||
|
{
|
||||||
|
// page 1
|
||||||
|
var page1 = document.GetPage(2);
|
||||||
|
var images1 = page1.GetImages().ToArray();
|
||||||
|
|
||||||
|
var image0 = images1[0];
|
||||||
|
Assert.Equal(ColorSpace.Indexed, image0.ColorSpaceDetails.Type); // Icc
|
||||||
|
Assert.True(image0.TryGetPng(out byte[] bytes0));
|
||||||
|
File.WriteAllBytes(Path.Combine(OutputFolder, "MOZILLA-10225-0_1_0.png"), bytes0);
|
||||||
|
|
||||||
|
var image1 = images1[1];
|
||||||
|
Assert.Equal(ColorSpace.Indexed, image1.ColorSpaceDetails.Type); // stencil
|
||||||
|
Assert.True(image1.TryGetPng(out byte[] bytes1));
|
||||||
|
File.WriteAllBytes(Path.Combine(OutputFolder, "MOZILLA-10225-0_1_1.png"), bytes1);
|
||||||
|
|
||||||
|
// page 23
|
||||||
|
var page23 = document.GetPage(23);
|
||||||
|
var images23 = page23.GetImages().ToArray();
|
||||||
|
|
||||||
|
var image23_0 = images23[0];
|
||||||
|
Assert.Equal(ColorSpace.Indexed, image23_0.ColorSpaceDetails.Type);
|
||||||
|
Assert.True(image23_0.TryGetPng(out byte[] bytes23_0));
|
||||||
|
File.WriteAllBytes(Path.Combine(OutputFolder, "MOZILLA-10225-0_23_0.png"), bytes23_0);
|
||||||
|
|
||||||
|
// page 332
|
||||||
|
var page332 = document.GetPage(332);
|
||||||
|
var images332 = page332.GetImages().ToArray();
|
||||||
|
|
||||||
|
var image332_0 = images332[0];
|
||||||
|
Assert.Equal(ColorSpace.ICCBased, image332_0.ColorSpaceDetails.Type);
|
||||||
|
Assert.True(image332_0.TryGetPng(out byte[] bytes332_0));
|
||||||
|
File.WriteAllBytes(Path.Combine(OutputFolder, "MOZILLA-10225-0_332_0.png"), bytes332_0);
|
||||||
|
|
||||||
|
// page 338
|
||||||
|
var page338 = document.GetPage(338);
|
||||||
|
var images338 = page338.GetImages().ToArray();
|
||||||
|
|
||||||
|
var image338_1 = images338[1];
|
||||||
|
Assert.Equal(ColorSpace.Indexed, image338_1.ColorSpaceDetails.Type);
|
||||||
|
Assert.True(image338_1.TryGetPng(out byte[] bytes338_1));
|
||||||
|
File.WriteAllBytes(Path.Combine(OutputFolder, "MOZILLA-10225-0_338_1.png"), bytes338_1);
|
||||||
|
|
||||||
|
// page 339
|
||||||
|
var page339 = document.GetPage(339);
|
||||||
|
var images339 = page339.GetImages().ToArray();
|
||||||
|
|
||||||
|
var image339_0 = images339[0];
|
||||||
|
Assert.Equal(ColorSpace.Indexed, image339_0.ColorSpaceDetails.Type);
|
||||||
|
Assert.True(image339_0.TryGetPng(out byte[] bytes339_0));
|
||||||
|
File.WriteAllBytes(Path.Combine(OutputFolder, "MOZILLA-10225-0_339_0.png"), bytes339_0);
|
||||||
|
|
||||||
|
var image339_1 = images339[1];
|
||||||
|
Assert.Equal(ColorSpace.Indexed, image339_1.ColorSpaceDetails.Type);
|
||||||
|
Assert.True(image339_1.TryGetPng(out byte[] bytes339_1));
|
||||||
|
File.WriteAllBytes(Path.Combine(OutputFolder, "MOZILLA-10225-0_339_1.png"), bytes339_1);
|
||||||
|
|
||||||
|
// page 341
|
||||||
|
var page341 = document.GetPage(341);
|
||||||
|
var images341 = page341.GetImages().ToArray();
|
||||||
|
|
||||||
|
var image341_0 = images341[0];
|
||||||
|
Assert.Equal(ColorSpace.Indexed, image341_0.ColorSpaceDetails.Type);
|
||||||
|
Assert.True(image341_0.TryGetPng(out byte[] bytes341_0));
|
||||||
|
File.WriteAllBytes(Path.Combine(OutputFolder, "MOZILLA-10225-0_341_0.png"), bytes341_0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SeparationLabColorSpace()
|
||||||
|
{
|
||||||
|
// Test with TIKA_1552_0.pdf
|
||||||
|
// https://icolorpalette.com/color/pantone-289-c
|
||||||
|
// Pantone 289 C Color | #0C2340
|
||||||
|
// Rgb : rgb(12,35,64)
|
||||||
|
// CIE L*a*b* : 13.53, 2.89, -21.08
|
||||||
|
|
||||||
|
var path = IntegrationHelpers.GetDocumentPath("TIKA-1552-0.pdf");
|
||||||
|
|
||||||
|
using (var document = PdfDocument.Open(path))
|
||||||
|
{
|
||||||
|
var page1 = document.GetPage(1);
|
||||||
|
|
||||||
|
var background = page1.ExperimentalAccess.Paths[0];
|
||||||
|
Assert.True(background.IsFilled);
|
||||||
|
|
||||||
|
var (r, g, b) = background.FillColor.ToRGBValues();
|
||||||
|
|
||||||
|
// Colors picked from Acrobat reader: rgb(11, 34, 64)
|
||||||
|
Assert.Equal(10, ConvertToByte(r)); // Should be 11, but close enough
|
||||||
|
Assert.Equal(34, ConvertToByte(g));
|
||||||
|
Assert.Equal(64, ConvertToByte(b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanGetAllPagesImages()
|
public void CanGetAllPagesImages()
|
||||||
{
|
{
|
||||||
@@ -17,12 +259,11 @@
|
|||||||
for (int p = 0; p < document.NumberOfPages; p++)
|
for (int p = 0; p < document.NumberOfPages; p++)
|
||||||
{
|
{
|
||||||
var page = document.GetPage(p + 1);
|
var page = document.GetPage(p + 1);
|
||||||
var images = page.GetImages().ToArray();
|
foreach (var image in page.GetImages())
|
||||||
foreach (var image in images)
|
|
||||||
{
|
{
|
||||||
if (image.TryGetPng(out var png))
|
if (image.TryGetPng(out var png))
|
||||||
{
|
{
|
||||||
|
// TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,7 +306,7 @@
|
|||||||
for (int r = 0; r < filledColors.Length; r++)
|
for (int r = 0; r < filledColors.Length; r++)
|
||||||
{
|
{
|
||||||
var color = filledColors[r];
|
var color = filledColors[r];
|
||||||
Assert.Equal(PdfPig.Graphics.Colors.ColorSpace.DeviceRGB, color.ColorSpace);
|
Assert.Equal(ColorSpace.DeviceRGB, color.ColorSpace);
|
||||||
|
|
||||||
if (r % 2 == 0)
|
if (r % 2 == 0)
|
||||||
{
|
{
|
||||||
@@ -85,5 +326,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static byte ConvertToByte(decimal componentValue)
|
||||||
|
{
|
||||||
|
var rounded = Math.Round(componentValue * 255, MidpointRounding.AwayFromZero);
|
||||||
|
return (byte)rounded;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Integration/Documents/TIKA-1552-0.pdf
Normal file
BIN
src/UglyToad.PdfPig.Tests/Integration/Documents/TIKA-1552-0.pdf
Normal file
Binary file not shown.
@@ -122,8 +122,10 @@
|
|||||||
"UglyToad.PdfPig.Graphics.Colors.DeviceGrayColorSpaceDetails",
|
"UglyToad.PdfPig.Graphics.Colors.DeviceGrayColorSpaceDetails",
|
||||||
"UglyToad.PdfPig.Graphics.Colors.DeviceRgbColorSpaceDetails",
|
"UglyToad.PdfPig.Graphics.Colors.DeviceRgbColorSpaceDetails",
|
||||||
"UglyToad.PdfPig.Graphics.Colors.DeviceCmykColorSpaceDetails",
|
"UglyToad.PdfPig.Graphics.Colors.DeviceCmykColorSpaceDetails",
|
||||||
|
"UglyToad.PdfPig.Graphics.Colors.DeviceNColorSpaceDetails",
|
||||||
"UglyToad.PdfPig.Graphics.Colors.ICCBasedColorSpaceDetails",
|
"UglyToad.PdfPig.Graphics.Colors.ICCBasedColorSpaceDetails",
|
||||||
"UglyToad.PdfPig.Graphics.Colors.IndexedColorSpaceDetails",
|
"UglyToad.PdfPig.Graphics.Colors.IndexedColorSpaceDetails",
|
||||||
|
"UglyToad.PdfPig.Graphics.Colors.LabColorSpaceDetails",
|
||||||
"UglyToad.PdfPig.Graphics.Colors.SeparationColorSpaceDetails",
|
"UglyToad.PdfPig.Graphics.Colors.SeparationColorSpaceDetails",
|
||||||
"UglyToad.PdfPig.Graphics.Colors.UnsupportedColorSpaceDetails",
|
"UglyToad.PdfPig.Graphics.Colors.UnsupportedColorSpaceDetails",
|
||||||
"UglyToad.PdfPig.Graphics.Core.LineCapStyle",
|
"UglyToad.PdfPig.Graphics.Core.LineCapStyle",
|
||||||
|
|||||||
@@ -349,6 +349,7 @@
|
|||||||
public static readonly NameToken Metadata = new NameToken("Metadata");
|
public static readonly NameToken Metadata = new NameToken("Metadata");
|
||||||
public static readonly NameToken MissingWidth = new NameToken("MissingWidth");
|
public static readonly NameToken MissingWidth = new NameToken("MissingWidth");
|
||||||
public static readonly NameToken Mix = new NameToken("Mix");
|
public static readonly NameToken Mix = new NameToken("Mix");
|
||||||
|
public static readonly NameToken MixingHints = new NameToken("MixingHints");
|
||||||
public static readonly NameToken Mk = new NameToken("MK");
|
public static readonly NameToken Mk = new NameToken("MK");
|
||||||
public static readonly NameToken Ml = new NameToken("ML");
|
public static readonly NameToken Ml = new NameToken("ML");
|
||||||
public static readonly NameToken MmType1 = new NameToken("MMType1");
|
public static readonly NameToken MmType1 = new NameToken("MMType1");
|
||||||
|
|||||||
@@ -5,10 +5,11 @@
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Transformer for CIEBased color spaces.
|
/// Transformer for CIEBased color spaces.
|
||||||
///
|
/// <para>
|
||||||
/// In addition to the PDF spec itself, the transformation implementation is based on the descriptions in:
|
/// In addition to the PDF spec itself, the transformation implementation is based on the descriptions in:
|
||||||
/// https://en.wikipedia.org/wiki/SRGB#The_forward_transformation_(CIE_XYZ_to_sRGB) and
|
/// https://en.wikipedia.org/wiki/SRGB#The_forward_transformation_(CIE_XYZ_to_sRGB) and
|
||||||
/// http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html
|
/// http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html
|
||||||
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class CIEBasedColorSpaceTransformer
|
internal class CIEBasedColorSpaceTransformer
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -31,6 +31,11 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ColorSpace BaseType { get; protected set; }
|
public ColorSpace BaseType { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of components for the underlying color space.
|
||||||
|
/// </summary>
|
||||||
|
public abstract int BaseNumberOfColorComponents { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new <see cref="ColorSpaceDetails"/>.
|
/// Create a new <see cref="ColorSpaceDetails"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -45,15 +50,25 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract IColor GetColor(params double[] values);
|
public abstract IColor GetColor(params double[] values);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the color, without check and caching.
|
||||||
|
/// </summary>
|
||||||
|
internal abstract double[] Process(params double[] values);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the color that initialize the current stroking or nonstroking colour.
|
/// Get the color that initialize the current stroking or nonstroking colour.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract IColor GetInitializeColor();
|
public abstract IColor GetInitializeColor();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transform image bytes.
|
||||||
|
/// </summary>
|
||||||
|
internal abstract IReadOnlyList<byte> Transform(IReadOnlyList<byte> decoded);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Convert to byte.
|
/// Convert to byte.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected static byte ConvertToByte(decimal componentValue)
|
protected static byte ConvertToByte(double componentValue)
|
||||||
{
|
{
|
||||||
var rounded = Math.Round(componentValue * 255, MidpointRounding.AwayFromZero);
|
var rounded = Math.Round(componentValue * 255, MidpointRounding.AwayFromZero);
|
||||||
return (byte)rounded;
|
return (byte)rounded;
|
||||||
@@ -74,9 +89,18 @@
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override int NumberOfColorComponents => 1;
|
public override int NumberOfColorComponents => 1;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int BaseNumberOfColorComponents => NumberOfColorComponents;
|
||||||
|
|
||||||
private DeviceGrayColorSpaceDetails() : base(ColorSpace.DeviceGray)
|
private DeviceGrayColorSpaceDetails() : base(ColorSpace.DeviceGray)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
internal override double[] Process(params double[] values)
|
||||||
|
{
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override IColor GetColor(params double[] values)
|
public override IColor GetColor(params double[] values)
|
||||||
{
|
{
|
||||||
@@ -105,6 +129,12 @@
|
|||||||
{
|
{
|
||||||
return GrayColor.Black;
|
return GrayColor.Black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
internal override IReadOnlyList<byte> Transform(IReadOnlyList<byte> decoded)
|
||||||
|
{
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -121,9 +151,18 @@
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override int NumberOfColorComponents => 3;
|
public override int NumberOfColorComponents => 3;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int BaseNumberOfColorComponents => NumberOfColorComponents;
|
||||||
|
|
||||||
private DeviceRgbColorSpaceDetails() : base(ColorSpace.DeviceRGB)
|
private DeviceRgbColorSpaceDetails() : base(ColorSpace.DeviceRGB)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
internal override double[] Process(params double[] values)
|
||||||
|
{
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override IColor GetColor(params double[] values)
|
public override IColor GetColor(params double[] values)
|
||||||
{
|
{
|
||||||
@@ -152,6 +191,12 @@
|
|||||||
{
|
{
|
||||||
return RGBColor.Black;
|
return RGBColor.Black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
internal override IReadOnlyList<byte> Transform(IReadOnlyList<byte> decoded)
|
||||||
|
{
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -167,10 +212,19 @@
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override int NumberOfColorComponents => 4;
|
public override int NumberOfColorComponents => 4;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int BaseNumberOfColorComponents => NumberOfColorComponents;
|
||||||
|
|
||||||
private DeviceCmykColorSpaceDetails() : base(ColorSpace.DeviceCMYK)
|
private DeviceCmykColorSpaceDetails() : base(ColorSpace.DeviceCMYK)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
internal override double[] Process(params double[] values)
|
||||||
|
{
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override IColor GetColor(params double[] values)
|
public override IColor GetColor(params double[] values)
|
||||||
{
|
{
|
||||||
@@ -200,18 +254,24 @@
|
|||||||
{
|
{
|
||||||
return CMYKColor.Black;
|
return CMYKColor.Black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
internal override IReadOnlyList<byte> Transform(IReadOnlyList<byte> decoded)
|
||||||
|
{
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An Indexed color space allows a PDF content stream to use small integers as indices into a color map or color table of arbitrary colors in some other space.
|
/// An Indexed color space allows a PDF content stream to use small integers as indices into a color map or color table of arbitrary colors in some other space.
|
||||||
/// A PDF consumer treats each sample value as an index into the color table and uses the color value it finds there.
|
/// A PDF consumer treats each sample value as an index into the color table and uses the color value it finds there.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class IndexedColorSpaceDetails : ColorSpaceDetails
|
public sealed class IndexedColorSpaceDetails : ColorSpaceDetails
|
||||||
{
|
{
|
||||||
private readonly ConcurrentDictionary<double, IColor> cache = new ConcurrentDictionary<double, IColor>();
|
private readonly ConcurrentDictionary<double, IColor> cache = new ConcurrentDictionary<double, IColor>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a indexed color space useful for exracting stencil masks as black-and-white images,
|
/// Creates a indexed color space useful for extracting 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
|
/// i.e. with a color palette of two colors (black and white). If the decode parameter array is
|
||||||
/// [0, 1] it indicates that black is at index 0 in the color palette, whereas [1, 0] indicates
|
/// [0, 1] it indicates that black is at index 0 in the color palette, whereas [1, 0] indicates
|
||||||
/// that the black color is at index 1.
|
/// that the black color is at index 1.
|
||||||
@@ -225,6 +285,12 @@
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override int NumberOfColorComponents => 1;
|
public override int NumberOfColorComponents => 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <inheritdoc/>
|
||||||
|
/// <para>In the case of <see cref="IndexedColorSpaceDetails"/>, gets the <see cref="IndexedColorSpaceDetails.BaseColorSpaceDetails"/>' <c>BaseNumberOfColorComponents</c>.</para>
|
||||||
|
/// </summary>
|
||||||
|
public override int BaseNumberOfColorComponents => BaseColorSpaceDetails.BaseNumberOfColorComponents;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The base color space in which the values in the color table are to be interpreted.
|
/// 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,
|
||||||
@@ -254,6 +320,13 @@
|
|||||||
BaseType = baseColorSpaceDetails.BaseType;
|
BaseType = baseColorSpaceDetails.BaseType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
internal override double[] Process(params double[] values)
|
||||||
|
{
|
||||||
|
var csBytes = UnwrapIndexedColorSpaceBytes(new[] { (byte)values[0] });
|
||||||
|
return BaseColorSpaceDetails.Process(csBytes.Select(b => b / 255.0).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override IColor GetColor(params double[] values)
|
public override IColor GetColor(params double[] values)
|
||||||
{
|
{
|
||||||
@@ -277,6 +350,7 @@
|
|||||||
{
|
{
|
||||||
case ColorSpace.DeviceRGB:
|
case ColorSpace.DeviceRGB:
|
||||||
case ColorSpace.CalRGB:
|
case ColorSpace.CalRGB:
|
||||||
|
case ColorSpace.Lab:
|
||||||
transformer = x =>
|
transformer = x =>
|
||||||
{
|
{
|
||||||
var r = new byte[3];
|
var r = new byte[3];
|
||||||
@@ -289,6 +363,7 @@
|
|||||||
};
|
};
|
||||||
multiplier = 3;
|
multiplier = 3;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ColorSpace.DeviceCMYK:
|
case ColorSpace.DeviceCMYK:
|
||||||
transformer = x =>
|
transformer = x =>
|
||||||
{
|
{
|
||||||
@@ -303,11 +378,28 @@
|
|||||||
|
|
||||||
multiplier = 4;
|
multiplier = 4;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ColorSpace.DeviceGray:
|
case ColorSpace.DeviceGray:
|
||||||
case ColorSpace.CalGray:
|
case ColorSpace.CalGray:
|
||||||
|
case ColorSpace.Separation:
|
||||||
transformer = x => new[] { ColorTable[x] };
|
transformer = x => new[] { ColorTable[x] };
|
||||||
multiplier = 1;
|
multiplier = 1;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ColorSpace.DeviceN:
|
||||||
|
transformer = x =>
|
||||||
|
{
|
||||||
|
var r = new byte[BaseColorSpaceDetails.NumberOfColorComponents];
|
||||||
|
for (var i = 0; i < BaseColorSpaceDetails.NumberOfColorComponents; i++)
|
||||||
|
{
|
||||||
|
r[i] = ColorTable[x * BaseColorSpaceDetails.NumberOfColorComponents + i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
};
|
||||||
|
|
||||||
|
multiplier = BaseColorSpaceDetails.NumberOfColorComponents;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transformer != null)
|
if (transformer != null)
|
||||||
@@ -335,6 +427,185 @@
|
|||||||
// initialize the corresponding current colour to 0.
|
// initialize the corresponding current colour to 0.
|
||||||
return GetColor(0);
|
return GetColor(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <inheritdoc/>
|
||||||
|
/// <para>
|
||||||
|
/// Unwrap then transform using base color space details.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
internal override IReadOnlyList<byte> Transform(IReadOnlyList<byte> decoded)
|
||||||
|
{
|
||||||
|
var unwraped = UnwrapIndexedColorSpaceBytes(decoded);
|
||||||
|
return BaseColorSpaceDetails.Transform(unwraped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DeviceN colour spaces may contain an arbitrary number of colour components. They provide greater flexibility than
|
||||||
|
/// is possible with standard device colour spaces such as DeviceCMYK or with individual Separation colour spaces.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class DeviceNColorSpaceDetails : ColorSpaceDetails
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// <inheritdoc/>
|
||||||
|
/// <para>The 'N' in DeviceN.</para>
|
||||||
|
/// </summary>
|
||||||
|
public override int NumberOfColorComponents { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int BaseNumberOfColorComponents => AlternateColorSpaceDetails.NumberOfColorComponents;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies name objects specifying the individual colour components. The length of the array shall
|
||||||
|
/// determine the number of components in the DeviceN colour space.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The component names shall all be different from one another, except for the name None, which may be repeated.
|
||||||
|
/// <para>
|
||||||
|
/// The special name All, used by Separation colour spaces, shall not be used.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public IReadOnlyList<NameToken> Names { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If the colorant name associated with a DeviceN color space does not correspond to a colorant available on the device,
|
||||||
|
/// the application arranges for subsequent painting operations to be performed in an alternate color space.
|
||||||
|
/// The intended colors can be approximated by colors in a device or CIE-based color space
|
||||||
|
/// which are then rendered with the usual primary or process colorants.
|
||||||
|
/// </summary>
|
||||||
|
public ColorSpaceDetails AlternateColorSpaceDetails { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The optional attributes parameter shall be a dictionary containing additional information about the components of
|
||||||
|
/// colour space that conforming readers may use. Conforming readers need not use the alternateSpace and tintTransform
|
||||||
|
/// parameters, and may instead use custom blending algorithms, along with other information provided in the attributes
|
||||||
|
/// dictionary if present.
|
||||||
|
/// </summary>
|
||||||
|
public DeviceNColorSpaceAttributes? Attributes { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// During subsequent painting operations, an application calls this function to transform a tint value into
|
||||||
|
/// color component values in the alternate color space.
|
||||||
|
/// The function is called with the tint value and must return the corresponding color component values.
|
||||||
|
/// That is, the number of components and the interpretation of their values depend on the <see cref="AlternateColorSpaceDetails"/>.
|
||||||
|
/// </summary>
|
||||||
|
public PdfFunction TintFunction { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="DeviceNColorSpaceDetails"/>.
|
||||||
|
/// </summary>
|
||||||
|
public DeviceNColorSpaceDetails(IReadOnlyList<NameToken> names, ColorSpaceDetails alternateColorSpaceDetails,
|
||||||
|
PdfFunction tintFunction, DeviceNColorSpaceAttributes? attributes = null)
|
||||||
|
: base(ColorSpace.DeviceN)
|
||||||
|
{
|
||||||
|
Names = names;
|
||||||
|
NumberOfColorComponents = Names.Count;
|
||||||
|
AlternateColorSpaceDetails = alternateColorSpaceDetails;
|
||||||
|
Attributes = attributes;
|
||||||
|
TintFunction = tintFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
internal override double[] Process(params double[] values)
|
||||||
|
{
|
||||||
|
var evaled = TintFunction.Eval(values[0]);
|
||||||
|
return AlternateColorSpaceDetails.Process(evaled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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 attributes
|
||||||
|
|
||||||
|
// TODO - caching
|
||||||
|
var evaled = TintFunction.Eval(values);
|
||||||
|
return AlternateColorSpaceDetails.GetColor(evaled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
internal override IReadOnlyList<byte> Transform(IReadOnlyList<byte> decoded)
|
||||||
|
{
|
||||||
|
var transformed = new List<byte>();
|
||||||
|
for (var i = 0; i < decoded.Count; i += NumberOfColorComponents)
|
||||||
|
{
|
||||||
|
double[] comps = new double[NumberOfColorComponents];
|
||||||
|
for (int n = 0; n < NumberOfColorComponents; n++)
|
||||||
|
{
|
||||||
|
comps[n] = decoded[i + n] / 255.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var colors = Process(comps);
|
||||||
|
for (int c = 0; c < colors.Length; c++)
|
||||||
|
{
|
||||||
|
transformed.Add(ConvertToByte(colors[c]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return transformed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override IColor GetInitializeColor()
|
||||||
|
{
|
||||||
|
// When this space is set to the current colour space (using the CS or cs operators), each component
|
||||||
|
// shall be given an initial value of 1.0. The SCN and scn operators respectively shall set the current
|
||||||
|
// stroking and nonstroking colour.
|
||||||
|
return GetColor(Enumerable.Repeat(1.0, NumberOfColorComponents).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DeviceN Color Space Attributes.
|
||||||
|
/// </summary>
|
||||||
|
public struct DeviceNColorSpaceAttributes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A name specifying the preferred treatment for the colour space. Values shall be <c>DeviceN</c> or <c>NChannel</c>. Default value: <c>DeviceN</c>.
|
||||||
|
/// </summary>
|
||||||
|
public NameToken Subtype { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Colorants - dictionary - Required if Subtype is NChannel and the colour space includes spot colorants; otherwise optional.
|
||||||
|
/// </summary>
|
||||||
|
public DictionaryToken Colorants { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Process - dictionary - Required if Subtype is NChannel and the colour space includes components of a process colour space, otherwise optional.
|
||||||
|
/// </summary>
|
||||||
|
public DictionaryToken Process { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MixingHints - dictionary - Optional
|
||||||
|
/// </summary>
|
||||||
|
public DictionaryToken MixingHints { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// TODO
|
||||||
|
/// </summary>
|
||||||
|
public DeviceNColorSpaceAttributes()
|
||||||
|
{
|
||||||
|
Subtype = NameToken.Devicen;
|
||||||
|
Colorants = null;
|
||||||
|
Process = null;
|
||||||
|
MixingHints = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// TODO
|
||||||
|
/// </summary>
|
||||||
|
public DeviceNColorSpaceAttributes(NameToken subtype, DictionaryToken colorants, DictionaryToken process, DictionaryToken mixingHints)
|
||||||
|
{
|
||||||
|
Subtype = subtype;
|
||||||
|
Colorants = colorants;
|
||||||
|
Process = process;
|
||||||
|
MixingHints = mixingHints;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -343,13 +614,16 @@
|
|||||||
/// When such a space is the current color space, the current color is a single-component value, called a tint,
|
/// When such a space is the current color space, the current color is a single-component value, called a tint,
|
||||||
/// that controls the application of the given colorant or color components only.
|
/// that controls the application of the given colorant or color components only.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SeparationColorSpaceDetails : ColorSpaceDetails
|
public sealed class SeparationColorSpaceDetails : ColorSpaceDetails
|
||||||
{
|
{
|
||||||
private readonly ConcurrentDictionary<double, IColor> cache = new ConcurrentDictionary<double, IColor>();
|
private readonly ConcurrentDictionary<double, IColor> cache = new ConcurrentDictionary<double, IColor>();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override int NumberOfColorComponents => 1;
|
public override int NumberOfColorComponents => 1;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int BaseNumberOfColorComponents => AlternateColorSpaceDetails.NumberOfColorComponents;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Specifies the name of the colorant that this Separation color space is intended to represent.
|
/// Specifies the name of the colorant that this Separation color space is intended to represent.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -394,6 +668,13 @@
|
|||||||
TintFunction = tintFunction;
|
TintFunction = tintFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
internal override double[] Process(params double[] values)
|
||||||
|
{
|
||||||
|
var evaled = TintFunction.Eval(values[0]);
|
||||||
|
return AlternateColorSpaceDetails.Process(evaled);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override IColor GetColor(params double[] values)
|
public override IColor GetColor(params double[] values)
|
||||||
{
|
{
|
||||||
@@ -411,15 +692,17 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
internal IReadOnlyList<byte> TransformToRGB(IReadOnlyList<byte> values)
|
/// <inheritdoc/>
|
||||||
|
internal override IReadOnlyList<byte> Transform(IReadOnlyList<byte> values)
|
||||||
{
|
{
|
||||||
var transformed = new List<byte>();
|
var transformed = new List<byte>();
|
||||||
for (var i = 0; i < values.Count; i += 3)
|
for (var i = 0; i < values.Count; i += 3)
|
||||||
{
|
{
|
||||||
var (r, g, b) = GetColor(values[i++] / 255.0).ToRGBValues();
|
var colors = Process(values[i++] / 255.0);
|
||||||
transformed.Add(ConvertToByte(r));
|
for (int c = 0; c < colors.Length; c++)
|
||||||
transformed.Add(ConvertToByte(g));
|
{
|
||||||
transformed.Add(ConvertToByte(b));
|
transformed.Add(ConvertToByte(colors[c]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return transformed;
|
return transformed;
|
||||||
@@ -439,11 +722,14 @@
|
|||||||
/// CalGray - A CIE A color space with a single transformation.
|
/// CalGray - A CIE A color space with a single transformation.
|
||||||
/// 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>
|
/// </summary>
|
||||||
public class CalGrayColorSpaceDetails : ColorSpaceDetails
|
public sealed class CalGrayColorSpaceDetails : ColorSpaceDetails
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override int NumberOfColorComponents => 1;
|
public override int NumberOfColorComponents => 1;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int BaseNumberOfColorComponents => NumberOfColorComponents;
|
||||||
|
|
||||||
private readonly CIEBasedColorSpaceTransformer colorSpaceTransformer;
|
private readonly CIEBasedColorSpaceTransformer colorSpaceTransformer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -506,24 +792,32 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private RGBColor TransformToRGB(double colorA)
|
private RGBColor TransformToRGB(double colorA)
|
||||||
{
|
{
|
||||||
var colorRgb = colorSpaceTransformer.TransformToRGB((colorA, colorA, colorA));
|
var (R, G, B) = colorSpaceTransformer.TransformToRGB((colorA, colorA, colorA));
|
||||||
return new RGBColor((decimal)colorRgb.R, (decimal)colorRgb.G, (decimal)colorRgb.B);
|
return new RGBColor((decimal)R, (decimal)G, (decimal)B);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal IReadOnlyList<byte> TransformToRGB(IReadOnlyList<byte> decoded)
|
/// <inheritdoc/>
|
||||||
|
internal override IReadOnlyList<byte> Transform(IReadOnlyList<byte> decoded)
|
||||||
{
|
{
|
||||||
var transformed = new List<byte>();
|
var transformed = new List<byte>();
|
||||||
for (var i = 0; i < decoded.Count; i++)
|
for (var i = 0; i < decoded.Count; i++)
|
||||||
{
|
{
|
||||||
var component = decoded[i] / 255.0;
|
var component = decoded[i] / 255.0;
|
||||||
var rgbPixel = TransformToRGB(component);
|
var rgbPixel = Process(component);
|
||||||
// We only need one component here
|
// We only need one component here
|
||||||
transformed.Add(ConvertToByte(rgbPixel.R));
|
transformed.Add(ConvertToByte(rgbPixel[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
return transformed;
|
return transformed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
internal override double[] Process(params double[] values)
|
||||||
|
{
|
||||||
|
var (R, _, _) = colorSpaceTransformer.TransformToRGB((values[0], values[0], values[0]));
|
||||||
|
return new double[] { R };
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override IColor GetColor(params double[] values)
|
public override IColor GetColor(params double[] values)
|
||||||
{
|
{
|
||||||
@@ -552,11 +846,14 @@
|
|||||||
/// CalRGB - A CIE ABC color space with a single transformation.
|
/// 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>
|
/// </summary>
|
||||||
public class CalRGBColorSpaceDetails : ColorSpaceDetails
|
public sealed class CalRGBColorSpaceDetails : ColorSpaceDetails
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override int NumberOfColorComponents => 3;
|
public override int NumberOfColorComponents => 3;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int BaseNumberOfColorComponents => NumberOfColorComponents;
|
||||||
|
|
||||||
private readonly CIEBasedColorSpaceTransformer colorSpaceTransformer;
|
private readonly CIEBasedColorSpaceTransformer colorSpaceTransformer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -636,24 +933,31 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private RGBColor TransformToRGB((double A, double B, double C) colorAbc)
|
private RGBColor TransformToRGB((double A, double B, double C) colorAbc)
|
||||||
{
|
{
|
||||||
var colorRgb = colorSpaceTransformer.TransformToRGB((colorAbc.A, colorAbc.B, colorAbc.C));
|
var (R, G, B) = colorSpaceTransformer.TransformToRGB((colorAbc.A, colorAbc.B, colorAbc.C));
|
||||||
return new RGBColor((decimal)colorRgb.R, (decimal)colorRgb.G, (decimal)colorRgb.B);
|
return new RGBColor((decimal)R, (decimal)G, (decimal)B);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal IReadOnlyList<byte> TransformToRGB(IReadOnlyList<byte> decoded)
|
/// <inheritdoc/>
|
||||||
|
internal override IReadOnlyList<byte> Transform(IReadOnlyList<byte> decoded)
|
||||||
{
|
{
|
||||||
var transformed = new List<byte>();
|
var transformed = new List<byte>();
|
||||||
for (var i = 0; i < decoded.Count; i += 3)
|
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));
|
var rgbPixel = Process(decoded[i] / 255.0, decoded[i + 1] / 255.0, decoded[i + 2] / 255.0);
|
||||||
transformed.Add(ConvertToByte(rgbPixel.R));
|
transformed.Add(ConvertToByte(rgbPixel[0]));
|
||||||
transformed.Add(ConvertToByte(rgbPixel.G));
|
transformed.Add(ConvertToByte(rgbPixel[1]));
|
||||||
transformed.Add(ConvertToByte(rgbPixel.B));
|
transformed.Add(ConvertToByte(rgbPixel[2]));
|
||||||
}
|
}
|
||||||
|
|
||||||
return transformed;
|
return transformed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
internal override double[] Process(params double[] values)
|
||||||
|
{
|
||||||
|
var (R, G, B) = colorSpaceTransformer.TransformToRGB((values[0], values[1], values[2]));
|
||||||
|
return new double[] { R, G, B };
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override IColor GetColor(params double[] values)
|
public override IColor GetColor(params double[] values)
|
||||||
@@ -677,16 +981,160 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class LabColorSpaceDetails : ColorSpaceDetails
|
||||||
|
{
|
||||||
|
private readonly CIEBasedColorSpaceTransformer colorSpaceTransformer;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int NumberOfColorComponents => 3;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int BaseNumberOfColorComponents => NumberOfColorComponents;
|
||||||
|
|
||||||
|
/// <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.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<double> WhitePoint { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An array of three numbers [XB YB ZB] specifying the tristimulus value, in the CIE 1931 XYZ space of the
|
||||||
|
/// diffuse black point. All three numbers must be non-negative. Default value: [0.0 0.0 0.0].
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<double> BlackPoint { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An array of four numbers [a_min a_max b_min b_max] that shall specify the range of valid values for the a* and b* (B and C)
|
||||||
|
/// components of the colour space — that is, a_min ≤ a* ≤ a_max and b_min ≤ b* ≤ b_max
|
||||||
|
/// <para>Component values falling outside the specified range shall be adjusted to the nearest valid value without error indication.</para>
|
||||||
|
/// Default value: [−100 100 −100 100].
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<double> Matrix { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="LabColorSpaceDetails"/>.
|
||||||
|
/// </summary>
|
||||||
|
public LabColorSpaceDetails([NotNull] IReadOnlyList<decimal> whitePoint, [CanBeNull] IReadOnlyList<decimal> blackPoint, [CanBeNull] IReadOnlyList<decimal> matrix)
|
||||||
|
: base(ColorSpace.Lab)
|
||||||
|
{
|
||||||
|
WhitePoint = whitePoint?.Select(v => (double)v).ToArray() ?? throw new ArgumentNullException(nameof(whitePoint));
|
||||||
|
if (WhitePoint.Count != 3)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(whitePoint), whitePoint, $"Must consist of exactly three numbers, but was passed {whitePoint.Count}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
BlackPoint = blackPoint?.Select(v => (double)v).ToArray() ?? new[] { 0.0, 0.0, 0.0 };
|
||||||
|
if (BlackPoint.Count != 3)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(blackPoint), blackPoint, $"Must consist of exactly three numbers, but was passed {blackPoint.Count}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix = matrix?.Select(v => (double)v).ToArray() ?? new[] { -100.0, 100.0, -100.0, 100.0 };
|
||||||
|
if (Matrix.Count != 4)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(matrix), matrix, $"Must consist of exactly four numbers, but was passed {matrix.Count}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
colorSpaceTransformer = new CIEBasedColorSpaceTransformer((WhitePoint[0], WhitePoint[1], WhitePoint[2]), RGBWorkingSpace.sRGB);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transforms the supplied ABC color to RGB (sRGB) using the properties of this <see cref="LabColorSpaceDetails"/>
|
||||||
|
/// in the transformation process.
|
||||||
|
/// A, B and C represent the L*, a*, and b* components of a CIE 1976 L*a*b* space. The range of the first (L*)
|
||||||
|
/// component shall be 0 to 100; the ranges of the second and third (a* and b*) components shall be defined by
|
||||||
|
/// the Range entry in the colour space dictionary
|
||||||
|
/// </summary>
|
||||||
|
private RGBColor TransformToRGB((double A, double B, double C) colorAbc)
|
||||||
|
{
|
||||||
|
var rgb = Process(colorAbc.A, colorAbc.B, colorAbc.C);
|
||||||
|
return new RGBColor((decimal)rgb[0], (decimal)rgb[1], (decimal)rgb[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
internal override IReadOnlyList<byte> Transform(IReadOnlyList<byte> decoded)
|
||||||
|
{
|
||||||
|
var transformed = new List<byte>();
|
||||||
|
for (var i = 0; i < decoded.Count; i += 3)
|
||||||
|
{
|
||||||
|
var rgbPixel = Process(decoded[i] / 255.0, decoded[i + 1] / 255.0, decoded[i + 2] / 255.0);
|
||||||
|
transformed.Add(ConvertToByte(rgbPixel[0]));
|
||||||
|
transformed.Add(ConvertToByte(rgbPixel[1]));
|
||||||
|
transformed.Add(ConvertToByte(rgbPixel[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return transformed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double g(double x)
|
||||||
|
{
|
||||||
|
if (x > 6.0 / 29.0)
|
||||||
|
{
|
||||||
|
return x * x * x;
|
||||||
|
}
|
||||||
|
return 108.0 / 841.0 * (x - 4.0 / 29.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
internal override double[] Process(params double[] values)
|
||||||
|
{
|
||||||
|
// Component Ranges: L*: [0 100]; a* and b*: [−128 127]
|
||||||
|
double b = PdfFunction.ClipToRange(values[1], Matrix[0], Matrix[1]);
|
||||||
|
double c = PdfFunction.ClipToRange(values[2], Matrix[2], Matrix[3]);
|
||||||
|
|
||||||
|
double M = (values[0] + 16.0) / 116.0;
|
||||||
|
double L = M + (b / 500.0);
|
||||||
|
double N = M - (c / 200.0);
|
||||||
|
|
||||||
|
double X = WhitePoint[0] * g(L);
|
||||||
|
double Y = WhitePoint[1] * g(M);
|
||||||
|
double Z = WhitePoint[2] * g(N);
|
||||||
|
|
||||||
|
var (R, G, B) = colorSpaceTransformer.TransformToRGB((X, Y, Z));
|
||||||
|
return new double[] { R, G, B };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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.)
|
||||||
|
double b = PdfFunction.ClipToRange(0, Matrix[0], Matrix[1]);
|
||||||
|
double c = PdfFunction.ClipToRange(0, Matrix[2], Matrix[3]);
|
||||||
|
return TransformToRGB((0, b, c));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The ICCBased color space is one of the CIE-based color spaces supported in PDFs. These color spaces
|
/// The ICCBased color space is one of the CIE-based color spaces supported in PDFs. These color spaces
|
||||||
/// enable a page description to specify color values in a way that is related to human visual perception.
|
/// enable a page description to specify color values in a way that is related to human visual perception.
|
||||||
/// The goal is for the same color specification to produce consistent results on different output devices,
|
/// The goal is for the same color specification to produce consistent results on different output devices,
|
||||||
/// within the limitations of each device.
|
/// within the limitations of each device.
|
||||||
///
|
/// <para>
|
||||||
/// Currently support for this color space is limited in PdfPig. Calculations will only be based on
|
/// Currently support for this color space is limited in PdfPig. Calculations will only be based on
|
||||||
/// the color space of <see cref="AlternateColorSpaceDetails"/>.
|
/// the color space of <see cref="AlternateColorSpaceDetails"/>.
|
||||||
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ICCBasedColorSpaceDetails : ColorSpaceDetails
|
public sealed class ICCBasedColorSpaceDetails : ColorSpaceDetails
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The number of color components in the color space described by the ICC profile data.
|
/// The number of color components in the color space described by the ICC profile data.
|
||||||
@@ -695,6 +1143,9 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public override int NumberOfColorComponents { get; }
|
public override int NumberOfColorComponents { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int BaseNumberOfColorComponents => NumberOfColorComponents;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An alternate color space that can be used in case the one specified in the stream data is not
|
/// An alternate color space that can be used in case the one specified in the stream data is not
|
||||||
/// supported. Non-conforming readers may use this color space. The alternate color space may be any
|
/// supported. Non-conforming readers may use this color space. The alternate color space may be any
|
||||||
@@ -753,6 +1204,14 @@
|
|||||||
Metadata = metadata;
|
Metadata = metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
internal override double[] Process(params double[] values)
|
||||||
|
{
|
||||||
|
// TODO - use ICC profile
|
||||||
|
|
||||||
|
return AlternateColorSpaceDetails.Process(values);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override IColor GetColor(params double[] values)
|
public override IColor GetColor(params double[] values)
|
||||||
{
|
{
|
||||||
@@ -777,12 +1236,20 @@
|
|||||||
double[] init = Enumerable.Repeat(v, NumberOfColorComponents).ToArray();
|
double[] init = Enumerable.Repeat(v, NumberOfColorComponents).ToArray();
|
||||||
return GetColor(init);
|
return GetColor(init);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
internal override IReadOnlyList<byte> Transform(IReadOnlyList<byte> decoded)
|
||||||
|
{
|
||||||
|
// TODO - use ICC profile
|
||||||
|
|
||||||
|
return AlternateColorSpaceDetails.Transform(decoded);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A ColorSpace which the PdfPig library does not currently support. Please raise a PR if you need support for this ColorSpace.
|
/// A ColorSpace which the PdfPig library does not currently support. Please raise a PR if you need support for this ColorSpace.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class UnsupportedColorSpaceDetails : ColorSpaceDetails
|
public sealed class UnsupportedColorSpaceDetails : ColorSpaceDetails
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The single instance of the <see cref="UnsupportedColorSpaceDetails"/>.
|
/// The single instance of the <see cref="UnsupportedColorSpaceDetails"/>.
|
||||||
@@ -797,23 +1264,39 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public override int NumberOfColorComponents => throw new InvalidOperationException("UnsupportedColorSpaceDetails");
|
public override int NumberOfColorComponents => throw new InvalidOperationException("UnsupportedColorSpaceDetails");
|
||||||
|
|
||||||
//private readonly IColor debugColor = new RGBColor(255m / 255m, 20m / 255m, 147m / 255m);
|
/// <summary>
|
||||||
|
/// <inheritdoc/>
|
||||||
|
/// <para>
|
||||||
|
/// Cannot be called for <see cref="UnsupportedColorSpaceDetails"/>, will throw a <see cref="InvalidOperationException"/>.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
public override int BaseNumberOfColorComponents => NumberOfColorComponents;
|
||||||
|
|
||||||
private UnsupportedColorSpaceDetails() : base(ColorSpace.DeviceGray)
|
private UnsupportedColorSpaceDetails() : base(ColorSpace.DeviceGray)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
internal override double[] Process(params double[] values)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("UnsupportedColorSpaceDetails");
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override IColor GetColor(params double[] values)
|
public override IColor GetColor(params double[] values)
|
||||||
{
|
{
|
||||||
//return debugColor;
|
|
||||||
throw new InvalidOperationException("UnsupportedColorSpaceDetails");
|
throw new InvalidOperationException("UnsupportedColorSpaceDetails");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override IColor GetInitializeColor()
|
public override IColor GetInitializeColor()
|
||||||
{
|
{
|
||||||
//return debugColor;
|
throw new InvalidOperationException("UnsupportedColorSpaceDetails");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
internal override IReadOnlyList<byte> Transform(IReadOnlyList<byte> decoded)
|
||||||
|
{
|
||||||
throw new InvalidOperationException("UnsupportedColorSpaceDetails");
|
throw new InvalidOperationException("UnsupportedColorSpaceDetails");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -513,7 +513,7 @@
|
|||||||
{
|
{
|
||||||
if (csArrayToken.Data[0] is NameToken firstColorSpaceName)
|
if (csArrayToken.Data[0] is NameToken firstColorSpaceName)
|
||||||
{
|
{
|
||||||
startState.ColorSpaceContext.SetNonStrokingColorspace(csNameToken, formGroupToken);
|
startState.ColorSpaceContext.SetNonStrokingColorspace(firstColorSpaceName, formGroupToken);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -44,30 +44,7 @@
|
|||||||
decoded = RemoveStridePadding(decoded.ToArray(), strideWidth, imageWidth, imageHeight, bytesPerPixel);
|
decoded = RemoveStridePadding(decoded.ToArray(), strideWidth, imageWidth, imageHeight, bytesPerPixel);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details is SeparationColorSpaceDetails separation)
|
decoded = details.Transform(decoded);
|
||||||
{
|
|
||||||
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();
|
return decoded.ToArray();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,15 +10,9 @@
|
|||||||
{
|
{
|
||||||
bytes = null;
|
bytes = null;
|
||||||
|
|
||||||
var hasValidDetails = image.ColorSpaceDetails != null &&
|
var hasValidDetails = image.ColorSpaceDetails != null && !(image.ColorSpaceDetails is UnsupportedColorSpaceDetails);
|
||||||
!(image.ColorSpaceDetails is UnsupportedColorSpaceDetails);
|
|
||||||
|
|
||||||
var actualColorSpace = image.ColorSpaceDetails.BaseType;
|
var isColorSpaceSupported = hasValidDetails && image.ColorSpaceDetails.BaseType != ColorSpace.Pattern;
|
||||||
|
|
||||||
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))
|
if (!isColorSpaceSupported || !image.TryGetBytes(out var bytesPure))
|
||||||
{
|
{
|
||||||
@@ -30,10 +24,7 @@
|
|||||||
bytesPure = ColorSpaceDetailsByteConverter.Convert(image.ColorSpaceDetails, bytesPure,
|
bytesPure = ColorSpaceDetailsByteConverter.Convert(image.ColorSpaceDetails, bytesPure,
|
||||||
image.BitsPerComponent, image.WidthInSamples, image.HeightInSamples);
|
image.BitsPerComponent, image.WidthInSamples, image.HeightInSamples);
|
||||||
|
|
||||||
var numberOfComponents =
|
var numberOfComponents = image.ColorSpaceDetails.BaseNumberOfColorComponents;
|
||||||
actualColorSpace == ColorSpace.DeviceCMYK ? 4 :
|
|
||||||
actualColorSpace == ColorSpace.DeviceRGB ? 3 :
|
|
||||||
actualColorSpace == ColorSpace.CalRGB ? 3 : 1;
|
|
||||||
|
|
||||||
var is3Byte = numberOfComponents == 3;
|
var is3Byte = numberOfComponents == 3;
|
||||||
|
|
||||||
@@ -57,12 +48,12 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var i = 0;
|
if (image.ColorSpaceDetails.BaseType == ColorSpace.DeviceCMYK || numberOfComponents == 4)
|
||||||
for (var col = 0; col < image.HeightInSamples; col++)
|
|
||||||
{
|
{
|
||||||
for (var row = 0; row < image.WidthInSamples; row++)
|
int i = 0;
|
||||||
|
for (int col = 0; col < image.HeightInSamples; col++)
|
||||||
{
|
{
|
||||||
if (actualColorSpace == ColorSpace.DeviceCMYK)
|
for (int row = 0; row < image.WidthInSamples; row++)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Where CMYK in 0..1
|
* Where CMYK in 0..1
|
||||||
@@ -71,23 +62,37 @@
|
|||||||
* B = 255 × (1-Y) × (1-K)
|
* B = 255 × (1-Y) × (1-K)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var c = (bytesPure[i++]/255d);
|
double c = (bytesPure[i++] / 255d);
|
||||||
var m = (bytesPure[i++]/255d);
|
double m = (bytesPure[i++] / 255d);
|
||||||
var y = (bytesPure[i++]/255d);
|
double y = (bytesPure[i++] / 255d);
|
||||||
var k = (bytesPure[i++]/255d);
|
double k = (bytesPure[i++] / 255d);
|
||||||
var r = (byte)(255 * (1 - c) * (1 - k));
|
var r = (byte)(255 * (1 - c) * (1 - k));
|
||||||
var g = (byte)(255 * (1 - m) * (1 - k));
|
var g = (byte)(255 * (1 - m) * (1 - k));
|
||||||
var b = (byte)(255 * (1 - y) * (1 - k));
|
var b = (byte)(255 * (1 - y) * (1 - k));
|
||||||
|
|
||||||
builder.SetPixel(r, g, b, row, col);
|
builder.SetPixel(r, g, b, row, col);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (is3Byte)
|
else if (is3Byte)
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
for (int col = 0; col < image.HeightInSamples; col++)
|
||||||
|
{
|
||||||
|
for (int row = 0; row < image.WidthInSamples; row++)
|
||||||
{
|
{
|
||||||
builder.SetPixel(bytesPure[i++], bytesPure[i++], bytesPure[i++], row, col);
|
builder.SetPixel(bytesPure[i++], bytesPure[i++], bytesPure[i++], row, col);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var pixel = bytesPure[i++];
|
int i = 0;
|
||||||
|
for (int col = 0; col < image.HeightInSamples; col++)
|
||||||
|
{
|
||||||
|
for (int row = 0; row < image.WidthInSamples; row++)
|
||||||
|
{
|
||||||
|
byte pixel = bytesPure[i++];
|
||||||
builder.SetPixel(pixel, pixel, pixel, row, col);
|
builder.SetPixel(pixel, pixel, pixel, row, col);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,8 @@
|
|||||||
{
|
{
|
||||||
if (cannotRecurse)
|
if (cannotRecurse)
|
||||||
{
|
{
|
||||||
return UnsupportedColorSpaceDetails.Instance;
|
// Not sure if always Gray, it's just a single color.
|
||||||
|
return DeviceGrayColorSpaceDetails.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
var colorSpaceDetails = GetColorSpaceDetails(colorSpace, imageDictionary.Without(NameToken.Filter).Without(NameToken.F), scanner, resourceStore, filterProvider, true);
|
var colorSpaceDetails = GetColorSpaceDetails(colorSpace, imageDictionary.Without(NameToken.Filter).Without(NameToken.F), scanner, resourceStore, filterProvider, true);
|
||||||
@@ -167,7 +168,48 @@
|
|||||||
return new CalRGBColorSpaceDetails(whitePoint, blackPoint, gamma, matrix);
|
return new CalRGBColorSpaceDetails(whitePoint, blackPoint, gamma, matrix);
|
||||||
}
|
}
|
||||||
case ColorSpace.Lab:
|
case ColorSpace.Lab:
|
||||||
|
{
|
||||||
|
if (!TryGetColorSpaceArray(imageDictionary, resourceStore, scanner, out var colorSpaceArray)
|
||||||
|
|| colorSpaceArray.Length != 2)
|
||||||
|
{
|
||||||
return UnsupportedColorSpaceDetails.Instance;
|
return UnsupportedColorSpaceDetails.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
var first = colorSpaceArray[0] as NameToken;
|
||||||
|
|
||||||
|
if (first == null || !ColorSpaceMapper.TryMap(first, resourceStore, out var innerColorSpace)
|
||||||
|
|| innerColorSpace != ColorSpace.Lab)
|
||||||
|
{
|
||||||
|
return UnsupportedColorSpaceDetails.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
var second = colorSpaceArray[1];
|
||||||
|
|
||||||
|
// WhitePoint is required
|
||||||
|
if (!DirectObjectFinder.TryGet(second, scanner, out DictionaryToken dictionaryToken) ||
|
||||||
|
!dictionaryToken.TryGet(NameToken.WhitePoint, scanner, out ArrayToken whitePointToken))
|
||||||
|
{
|
||||||
|
return UnsupportedColorSpaceDetails.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
var whitePoint = whitePointToken.Data.OfType<NumericToken>().Select(x => x.Data).ToList();
|
||||||
|
|
||||||
|
// BlackPoint is optional
|
||||||
|
IReadOnlyList<decimal> blackPoint = null;
|
||||||
|
if (dictionaryToken.TryGet(NameToken.BlackPoint, scanner, out ArrayToken blackPointToken))
|
||||||
|
{
|
||||||
|
blackPoint = blackPointToken.Data.OfType<NumericToken>().Select(x => x.Data).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matrix is optional
|
||||||
|
IReadOnlyList<decimal> matrix = null;
|
||||||
|
if (dictionaryToken.TryGet(NameToken.Matrix, scanner, out ArrayToken matrixToken))
|
||||||
|
{
|
||||||
|
matrix = matrixToken.Data.OfType<NumericToken>().Select(x => x.Data).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new LabColorSpaceDetails(whitePoint, blackPoint, matrix);
|
||||||
|
}
|
||||||
case ColorSpace.ICCBased:
|
case ColorSpace.ICCBased:
|
||||||
{
|
{
|
||||||
if (!TryGetColorSpaceArray(imageDictionary, resourceStore, scanner, out var colorSpaceArray)
|
if (!TryGetColorSpaceArray(imageDictionary, resourceStore, scanner, out var colorSpaceArray)
|
||||||
@@ -392,7 +434,103 @@
|
|||||||
return new SeparationColorSpaceDetails(separationNameToken, alternateColorSpaceDetails, function);
|
return new SeparationColorSpaceDetails(separationNameToken, alternateColorSpaceDetails, function);
|
||||||
}
|
}
|
||||||
case ColorSpace.DeviceN:
|
case ColorSpace.DeviceN:
|
||||||
|
{
|
||||||
|
if (!TryGetColorSpaceArray(imageDictionary, resourceStore, scanner, out var colorSpaceArray)
|
||||||
|
|| (colorSpaceArray.Length != 4 && colorSpaceArray.Length != 5))
|
||||||
|
{
|
||||||
|
// Error instead?
|
||||||
return UnsupportedColorSpaceDetails.Instance;
|
return UnsupportedColorSpaceDetails.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!DirectObjectFinder.TryGet(colorSpaceArray[0], scanner, out NameToken deviceNColorSpaceNameToken)
|
||||||
|
|| !deviceNColorSpaceNameToken.Equals(NameToken.Devicen))
|
||||||
|
{
|
||||||
|
return UnsupportedColorSpaceDetails.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!DirectObjectFinder.TryGet(colorSpaceArray[1], scanner, out ArrayToken deviceNNamesToken))
|
||||||
|
{
|
||||||
|
return UnsupportedColorSpaceDetails.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorSpaceDetails alternateColorSpaceDetails;
|
||||||
|
if (DirectObjectFinder.TryGet(colorSpaceArray[2], scanner, out NameToken alternateNameToken)
|
||||||
|
&& ColorSpaceMapper.TryMap(alternateNameToken, resourceStore, out var baseColorSpaceName))
|
||||||
|
{
|
||||||
|
alternateColorSpaceDetails = GetColorSpaceDetails(
|
||||||
|
baseColorSpaceName,
|
||||||
|
imageDictionary,
|
||||||
|
scanner,
|
||||||
|
resourceStore,
|
||||||
|
filterProvider,
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
else if (DirectObjectFinder.TryGet(colorSpaceArray[2], scanner, out ArrayToken alternateArrayToken)
|
||||||
|
&& alternateArrayToken.Length > 0
|
||||||
|
&& alternateArrayToken[0] is NameToken alternateColorSpaceNameToken
|
||||||
|
&& ColorSpaceMapper.TryMap(alternateColorSpaceNameToken, resourceStore, out var alternateArrayColorSpace))
|
||||||
|
{
|
||||||
|
var pseudoImageDictionary = new DictionaryToken(
|
||||||
|
new Dictionary<NameToken, IToken>
|
||||||
|
{
|
||||||
|
{ NameToken.ColorSpace, alternateArrayToken }
|
||||||
|
});
|
||||||
|
|
||||||
|
alternateColorSpaceDetails = GetColorSpaceDetails(
|
||||||
|
alternateArrayColorSpace,
|
||||||
|
pseudoImageDictionary,
|
||||||
|
scanner,
|
||||||
|
resourceStore,
|
||||||
|
filterProvider,
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return UnsupportedColorSpaceDetails.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
var func = colorSpaceArray[3];
|
||||||
|
PdfFunction tintFunc = PdfFunctionParser.Create(func, scanner, filterProvider);
|
||||||
|
|
||||||
|
if (colorSpaceArray.Length > 4 && DirectObjectFinder.TryGet(colorSpaceArray[4], scanner, out DictionaryToken deviceNAttributesToken))
|
||||||
|
{
|
||||||
|
// Optionnal
|
||||||
|
|
||||||
|
// Subtype - NameToken - Optional - Default value: DeviceN.
|
||||||
|
NameToken subtype = NameToken.Devicen;
|
||||||
|
if (deviceNAttributesToken.ContainsKey(NameToken.Subtype))
|
||||||
|
{
|
||||||
|
subtype = deviceNAttributesToken.Get<NameToken>(NameToken.Subtype, scanner);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Colorants - dictionary - Required if Subtype is NChannel and the colour space includes spot colorants; otherwise optional
|
||||||
|
DictionaryToken colorants = null;
|
||||||
|
if (deviceNAttributesToken.ContainsKey(NameToken.Colorants))
|
||||||
|
{
|
||||||
|
colorants = deviceNAttributesToken.Get<DictionaryToken>(NameToken.Colorants, scanner);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process - dictionary - Required if Subtype is NChannel and the colour space includes components of a process colour space, otherwise optional; PDF 1.6
|
||||||
|
DictionaryToken process = null;
|
||||||
|
if (deviceNAttributesToken.ContainsKey(NameToken.Process))
|
||||||
|
{
|
||||||
|
process = deviceNAttributesToken.Get<DictionaryToken>(NameToken.Process, scanner);
|
||||||
|
}
|
||||||
|
|
||||||
|
// MixingHints - dictionary - Optional
|
||||||
|
DictionaryToken mixingHints = null;
|
||||||
|
if (deviceNAttributesToken.ContainsKey(NameToken.MixingHints))
|
||||||
|
{
|
||||||
|
mixingHints = deviceNAttributesToken.Get<DictionaryToken>(NameToken.MixingHints, scanner);
|
||||||
|
}
|
||||||
|
|
||||||
|
var attributes = new DeviceNColorSpaceDetails.DeviceNColorSpaceAttributes(subtype, colorants, process, mixingHints);
|
||||||
|
|
||||||
|
return new DeviceNColorSpaceDetails(deviceNNamesToken.Data.OfType<NameToken>().ToArray(), alternateColorSpaceDetails, tintFunc, attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DeviceNColorSpaceDetails(deviceNNamesToken.Data.OfType<NameToken>().ToArray(), alternateColorSpaceDetails, tintFunc);
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return UnsupportedColorSpaceDetails.Instance;
|
return UnsupportedColorSpaceDetails.Instance;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,12 @@
|
|||||||
DictionaryToken functionDictionary;
|
DictionaryToken functionDictionary;
|
||||||
StreamToken functionStream = null;
|
StreamToken functionStream = null;
|
||||||
|
|
||||||
if (function is StreamToken fs)
|
if (DirectObjectFinder.TryGet(function, scanner, out StreamToken fs))
|
||||||
{
|
{
|
||||||
functionDictionary = fs.StreamDictionary;
|
functionDictionary = fs.StreamDictionary;
|
||||||
functionStream = new StreamToken(fs.StreamDictionary, fs.Decode(filterProvider, scanner));
|
functionStream = new StreamToken(fs.StreamDictionary, fs.Decode(filterProvider, scanner));
|
||||||
}
|
}
|
||||||
else if (function is DictionaryToken fd)
|
else if (DirectObjectFinder.TryGet(function, scanner, out DictionaryToken fd))
|
||||||
{
|
{
|
||||||
functionDictionary = fd;
|
functionDictionary = fd;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user