Add (limited) support for the ICCBased color space

This commit is contained in:
Kasper Frank
2021-05-11 20:08:49 +02:00
parent a56be02cfd
commit 2fa003730a
4 changed files with 137 additions and 9 deletions

View File

@@ -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",

View File

@@ -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;
/// <summary>
/// Contains more document-specific information about the <see cref="ColorSpace"/>.
/// </summary>
@@ -187,6 +190,77 @@
}
}
/// <summary>
/// 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 <see cref="AlternateColorSpaceDetails"/>.
/// </summary>
public class ICCBasedColorSpaceDetails : ColorSpaceDetails
{
/// <summary>
/// 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.
/// </summary>
public int NumberOfColorComponents { get; }
/// <summary>
/// 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 <see cref="NumberOfColorComponents"/> 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.
/// </summary>
[NotNull]
public ColorSpaceDetails AlternateColorSpaceDetails { get; }
/// <summary>
/// A list of 2 x <see cref="NumberOfColorComponents"/> 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 ...].
/// </summary>
[NotNull]
public IReadOnlyList<decimal> Range { get; }
/// <summary>
/// An optional metadata stream that contains metadata for the color space.
/// </summary>
[CanBeNull]
public XmpMetadata Metadata { get; }
/// <summary>
/// Create a new <see cref="ICCBasedColorSpaceDetails"/>.
/// </summary>
internal ICCBasedColorSpaceDetails(int numberOfColorComponents, [CanBeNull] ColorSpaceDetails alternateColorSpaceDetails,
[CanBeNull] IReadOnlyList<decimal> 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;
}
}
/// <summary>
/// A ColorSpace which the PdfPig library does not currently support. Please raise a PR if you need support for this ColorSpace.
/// </summary>

View File

@@ -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<byte, IEnumerable<byte>> transformer = null;
switch (indexed.BaseColorSpaceDetails.Type)
switch (indexed.BaseType)
{
case ColorSpace.DeviceRGB:
transformer = x =>

View File

@@ -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<decimal> range = null;
if (streamToken.StreamDictionary.TryGet(NameToken.Range, out ArrayToken arrayToken))
{
range = arrayToken.Data.OfType<NumericToken>().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)