From 2fa003730abb50a226d46268258c794cf0461829 Mon Sep 17 00:00:00 2001 From: Kasper Frank Date: Tue, 11 May 2021 20:08:49 +0200 Subject: [PATCH] Add (limited) support for the ICCBased color space --- .../PublicApiScannerTests.cs | 1 + .../Graphics/Colors/ColorSpaceDetails.cs | 80 ++++++++++++++++++- .../Images/ColorSpaceDetailsByteConverter.cs | 19 +++-- .../Util/ColorSpaceDetailsParser.cs | 46 ++++++++++- 4 files changed, 137 insertions(+), 9 deletions(-) diff --git a/src/UglyToad.PdfPig.Tests/PublicApiScannerTests.cs b/src/UglyToad.PdfPig.Tests/PublicApiScannerTests.cs index 33240ce0..60eebf12 100644 --- a/src/UglyToad.PdfPig.Tests/PublicApiScannerTests.cs +++ b/src/UglyToad.PdfPig.Tests/PublicApiScannerTests.cs @@ -108,6 +108,7 @@ "UglyToad.PdfPig.Graphics.Colors.DeviceGrayColorSpaceDetails", "UglyToad.PdfPig.Graphics.Colors.DeviceRgbColorSpaceDetails", "UglyToad.PdfPig.Graphics.Colors.DeviceCmykColorSpaceDetails", + "UglyToad.PdfPig.Graphics.Colors.ICCBasedColorSpaceDetails", "UglyToad.PdfPig.Graphics.Colors.IndexedColorSpaceDetails", "UglyToad.PdfPig.Graphics.Colors.SeparationColorSpaceDetails", "UglyToad.PdfPig.Graphics.Colors.UnsupportedColorSpaceDetails", diff --git a/src/UglyToad.PdfPig/Graphics/Colors/ColorSpaceDetails.cs b/src/UglyToad.PdfPig/Graphics/Colors/ColorSpaceDetails.cs index 8a81cdee..745151d1 100644 --- a/src/UglyToad.PdfPig/Graphics/Colors/ColorSpaceDetails.cs +++ b/src/UglyToad.PdfPig/Graphics/Colors/ColorSpaceDetails.cs @@ -1,10 +1,13 @@ namespace UglyToad.PdfPig.Graphics.Colors { using System; - using System.Collections.Generic; + using System.Collections.Generic; + using System.Linq; using PdfPig.Core; - using Tokens; - + using Tokens; + using UglyToad.PdfPig.Content; + using UglyToad.PdfPig.Util.JetBrains.Annotations; + /// /// Contains more document-specific information about the . /// @@ -187,6 +190,77 @@ } } + /// + /// 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. + /// The goal is for the same color specification to produce consistent results on different output devices, + /// within the limitations of each device. + /// + /// Currently support for this color space is limited in PdfPig, as calculations will only be based on + /// the color space of . + /// + public class ICCBasedColorSpaceDetails : ColorSpaceDetails + { + /// + /// The number of color components in the color space described by the ICC profile data. + /// This numbers shall match the number of components actually in the ICC profile. + /// Valid values are 1, 3 and 4. + /// + public int NumberOfColorComponents { get; } + + /// + /// 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 + /// 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 is 1, 3 or respectively. + /// + /// 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. + /// + [NotNull] + public ColorSpaceDetails AlternateColorSpaceDetails { get; } + + /// + /// A list of 2 x numbers [min0 max0 min1 max1 ...] that + /// specifies the minimum and maximum valid values of the corresponding color components. These + /// values must match the information in the ICC profile. Default value: [0.0 1.0 0.0 1.0 ...]. + /// + [NotNull] + public IReadOnlyList Range { get; } + + /// + /// An optional metadata stream that contains metadata for the color space. + /// + [CanBeNull] + public XmpMetadata Metadata { get; } + + /// + /// Create a new . + /// + internal ICCBasedColorSpaceDetails(int numberOfColorComponents, [CanBeNull] ColorSpaceDetails alternateColorSpaceDetails, + [CanBeNull] IReadOnlyList range, [CanBeNull] XmpMetadata metadata) + : base(ColorSpace.ICCBased) + { + if (numberOfColorComponents != 1 && numberOfColorComponents != 3 && numberOfColorComponents != 4) + { + throw new ArgumentOutOfRangeException(nameof(numberOfColorComponents), "must be 1, 3 or 4"); + } + + NumberOfColorComponents = numberOfColorComponents; + AlternateColorSpaceDetails = alternateColorSpaceDetails ?? + (NumberOfColorComponents == 1 ? DeviceGrayColorSpaceDetails.Instance : + NumberOfColorComponents == 3 ? DeviceRgbColorSpaceDetails.Instance : DeviceCmykColorSpaceDetails.Instance); + + BaseType = AlternateColorSpaceDetails.BaseType; + Range = range ?? + Enumerable.Range(0, numberOfColorComponents).Select(x => new[] { 0.0m, 1.0m }).SelectMany(x => x).ToList(); + Metadata = metadata; + } + } + /// /// A ColorSpace which the PdfPig library does not currently support. Please raise a PR if you need support for this ColorSpace. /// diff --git a/src/UglyToad.PdfPig/Images/ColorSpaceDetailsByteConverter.cs b/src/UglyToad.PdfPig/Images/ColorSpaceDetailsByteConverter.cs index 3a2393d6..5f541818 100644 --- a/src/UglyToad.PdfPig/Images/ColorSpaceDetailsByteConverter.cs +++ b/src/UglyToad.PdfPig/Images/ColorSpaceDetailsByteConverter.cs @@ -55,15 +55,24 @@ private static int GetBytesPerPixel(ColorSpaceDetails details) { - var colorSpace = (details is IndexedColorSpaceDetails indexed) ? indexed.BaseColorSpaceDetails.Type : details.Type; - switch (colorSpace) + switch (details) { - case ColorSpace.DeviceRGB: + case DeviceGrayColorSpaceDetails: + return 1; + + case DeviceRgbColorSpaceDetails: return 3; - case ColorSpace.DeviceCMYK: + case DeviceCmykColorSpaceDetails: 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; } @@ -100,7 +109,7 @@ { var multiplier = 1; Func> transformer = null; - switch (indexed.BaseColorSpaceDetails.Type) + switch (indexed.BaseType) { case ColorSpace.DeviceRGB: transformer = x => diff --git a/src/UglyToad.PdfPig/Util/ColorSpaceDetailsParser.cs b/src/UglyToad.PdfPig/Util/ColorSpaceDetailsParser.cs index 3805468a..ee578c78 100644 --- a/src/UglyToad.PdfPig/Util/ColorSpaceDetailsParser.cs +++ b/src/UglyToad.PdfPig/Util/ColorSpaceDetailsParser.cs @@ -105,7 +105,51 @@ case ColorSpace.Lab: return UnsupportedColorSpaceDetails.Instance; case ColorSpace.ICCBased: - return UnsupportedColorSpaceDetails.Instance; + { + if (!TryGetColorSpaceArray(imageDictionary, resourceStore, scanner, out var colorSpaceArray) + || colorSpaceArray.Length != 2) + { + return UnsupportedColorSpaceDetails.Instance; + } + + var first = colorSpaceArray[0] as NameToken; + + if (first == null || !ColorSpaceMapper.TryMap(first, resourceStore, out var innerColorSpace) + || innerColorSpace != ColorSpace.ICCBased) + { + return UnsupportedColorSpaceDetails.Instance; + } + + var second = colorSpaceArray[1]; + + if (!DirectObjectFinder.TryGet(second, scanner, out StreamToken streamToken) || + !streamToken.StreamDictionary.TryGet(NameToken.N, out NumericToken numeric)) + { + return UnsupportedColorSpaceDetails.Instance; + } + + ColorSpaceDetails alternateColorSpaceDetails = null; + if (streamToken.StreamDictionary.TryGet(NameToken.Alternate, out NameToken alternateColorSpaceNameToken) && + ColorSpaceMapper.TryMap(alternateColorSpaceNameToken, resourceStore, out var alternateColorSpace)) + { + alternateColorSpaceDetails = + GetColorSpaceDetails(alternateColorSpace, imageDictionary, scanner, resourceStore, filterProvider, true); + } + + IReadOnlyList range = null; + if (streamToken.StreamDictionary.TryGet(NameToken.Range, out ArrayToken arrayToken)) + { + range = arrayToken.Data.OfType().Select(x => x.Data).ToList(); + } + + XmpMetadata metadata = null; + if (streamToken.StreamDictionary.TryGet(NameToken.Metadata, out StreamToken metadataStream)) + { + metadata = new XmpMetadata(metadataStream, filterProvider, scanner); + } + + return new ICCBasedColorSpaceDetails(numeric.Int, alternateColorSpaceDetails, range, metadata); + } case ColorSpace.Indexed: { if (cannotRecurse)