mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-10-08 00:14:35 +08:00
add full color space details and transformation into full bytes for indexed
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARGUMENTS_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_PARAMETERS_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
|
||||||
<s:Boolean x:Key="/Default/CodeStyle/CSharpUsing/AddImportsToDeepestScope/@EntryValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeStyle/CSharpUsing/AddImportsToDeepestScope/@EntryValue">True</s:Boolean>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BE/@EntryIndexedValue">BE</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BE/@EntryIndexedValue">BE</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CIE/@EntryIndexedValue">CIE</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CIE/@EntryIndexedValue">CIE</s:String>
|
||||||
|
@@ -84,6 +84,11 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
bool IsInlineImage { get; }
|
bool IsInlineImage { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Full details for the <see cref="ColorSpace"/> with any associated data.
|
||||||
|
/// </summary>
|
||||||
|
ColorSpaceDetails ColorSpaceDetails { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the decoded bytes of the image if applicable. For JPEG images and some other types the
|
/// Get the decoded bytes of the image if applicable. For JPEG images and some other types the
|
||||||
/// <see cref="RawBytes"/> should be used directly.
|
/// <see cref="RawBytes"/> should be used directly.
|
||||||
|
@@ -7,6 +7,7 @@
|
|||||||
using Filters;
|
using Filters;
|
||||||
using Graphics.Colors;
|
using Graphics.Colors;
|
||||||
using Graphics.Core;
|
using Graphics.Core;
|
||||||
|
using Images;
|
||||||
using Tokens;
|
using Tokens;
|
||||||
using Images.Png;
|
using Images.Png;
|
||||||
|
|
||||||
@@ -51,6 +52,9 @@
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IReadOnlyList<byte> RawBytes { get; }
|
public IReadOnlyList<byte> RawBytes { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ColorSpaceDetails ColorSpaceDetails { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new <see cref="InlineImage"/>.
|
/// Create a new <see cref="InlineImage"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -61,7 +65,8 @@
|
|||||||
IReadOnlyList<decimal> decode,
|
IReadOnlyList<decimal> decode,
|
||||||
IReadOnlyList<byte> bytes,
|
IReadOnlyList<byte> bytes,
|
||||||
IReadOnlyList<IFilter> filters,
|
IReadOnlyList<IFilter> filters,
|
||||||
DictionaryToken streamDictionary)
|
DictionaryToken streamDictionary,
|
||||||
|
ColorSpaceDetails colorSpaceDetails)
|
||||||
{
|
{
|
||||||
Bounds = bounds;
|
Bounds = bounds;
|
||||||
WidthInSamples = widthInSamples;
|
WidthInSamples = widthInSamples;
|
||||||
@@ -74,6 +79,7 @@
|
|||||||
Interpolate = interpolate;
|
Interpolate = interpolate;
|
||||||
|
|
||||||
RawBytes = bytes;
|
RawBytes = bytes;
|
||||||
|
ColorSpaceDetails = colorSpaceDetails;
|
||||||
|
|
||||||
var supportsFilters = true;
|
var supportsFilters = true;
|
||||||
foreach (var filter in filters)
|
foreach (var filter in filters)
|
||||||
@@ -107,8 +113,6 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bytes = bytesFactory.Value;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
129
src/UglyToad.PdfPig/Graphics/Colors/ColorSpaceDetails.cs
Normal file
129
src/UglyToad.PdfPig/Graphics/Colors/ColorSpaceDetails.cs
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
namespace UglyToad.PdfPig.Graphics.Colors
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains more document-specific information about the <see cref="ColorSpace"/>.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class ColorSpaceDetails
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The type of the ColorSpace.
|
||||||
|
/// </summary>
|
||||||
|
public ColorSpace Type { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The underlying type of ColorSpace, usually equal to <see cref="Type"/>
|
||||||
|
/// unless <see cref="ColorSpace.Indexed"/>.
|
||||||
|
/// </summary>
|
||||||
|
public ColorSpace BaseType { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="ColorSpaceDetails"/>.
|
||||||
|
/// </summary>
|
||||||
|
protected ColorSpaceDetails(ColorSpace type)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
BaseType = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A grayscale value is represented by a single number in the range 0.0 to 1.0,
|
||||||
|
/// where 0.0 corresponds to black, 1.0 to white, and intermediate values to different gray levels.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class DeviceGrayColorSpaceDetails : ColorSpaceDetails
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The single instance of the <see cref="DeviceGrayColorSpaceDetails"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DeviceGrayColorSpaceDetails Instance = new DeviceGrayColorSpaceDetails();
|
||||||
|
|
||||||
|
private DeviceGrayColorSpaceDetails() : base(ColorSpace.DeviceGray)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Color values are defined by three components representing the intensities of the additive primary colorants red, green and blue.
|
||||||
|
/// Each component is specified by a number in the range 0.0 to 1.0, where 0.0 denotes the complete absence of a primary component and 1.0 denotes maximum intensity.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class DeviceRgbColorSpaceDetails : ColorSpaceDetails
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The single instance of the <see cref="DeviceRgbColorSpaceDetails"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DeviceRgbColorSpaceDetails Instance = new DeviceRgbColorSpaceDetails();
|
||||||
|
|
||||||
|
private DeviceRgbColorSpaceDetails() : base(ColorSpace.DeviceRGB)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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();
|
||||||
|
|
||||||
|
private DeviceCmykColorSpaceDetails() : base(ColorSpace.DeviceCMYK)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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.
|
||||||
|
/// 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
|
||||||
|
{
|
||||||
|
/// <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,
|
||||||
|
/// but not a Pattern space or another Indexed space.
|
||||||
|
/// </summary>
|
||||||
|
public ColorSpaceDetails BaseColorSpaceDetails { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An integer that specifies the maximum valid index value. Can be no greater than 255.
|
||||||
|
/// </summary>
|
||||||
|
public byte HiVal { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides the mapping between index values and the corresponding colors in the base color space.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<byte> ColorTable { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="IndexedColorSpaceDetails"/>.
|
||||||
|
/// </summary>
|
||||||
|
public IndexedColorSpaceDetails(ColorSpaceDetails baseColorSpaceDetails, byte hiVal, IReadOnlyList<byte> colorTable)
|
||||||
|
: base(ColorSpace.Indexed)
|
||||||
|
{
|
||||||
|
BaseColorSpaceDetails = baseColorSpaceDetails ?? throw new ArgumentNullException(nameof(baseColorSpaceDetails));
|
||||||
|
HiVal = hiVal;
|
||||||
|
ColorTable = colorTable;
|
||||||
|
BaseType = baseColorSpaceDetails.BaseType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A ColorSpace which the PdfPig library does not currently support. Please raise a PR if you need support for this ColorSpace.
|
||||||
|
/// </summary>
|
||||||
|
public class UnsupportedColorSpaceDetails : ColorSpaceDetails
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The single instance of the <see cref="UnsupportedColorSpaceDetails"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly UnsupportedColorSpaceDetails Instance = new UnsupportedColorSpaceDetails();
|
||||||
|
|
||||||
|
private UnsupportedColorSpaceDetails() : base(ColorSpace.DeviceGray)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -10,6 +10,7 @@
|
|||||||
using PdfPig.Core;
|
using PdfPig.Core;
|
||||||
using Tokenization.Scanner;
|
using Tokenization.Scanner;
|
||||||
using Tokens;
|
using Tokens;
|
||||||
|
using Util;
|
||||||
|
|
||||||
internal class InlineImageBuilder
|
internal class InlineImageBuilder
|
||||||
{
|
{
|
||||||
@@ -27,35 +28,7 @@
|
|||||||
throw new InvalidOperationException($"Inline image builder not completely defined before calling {nameof(CreateInlineImage)}.");
|
throw new InvalidOperationException($"Inline image builder not completely defined before calling {nameof(CreateInlineImage)}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TryMapColorSpace(NameToken name, out ColorSpace colorSpaceResult)
|
|
||||||
{
|
|
||||||
if (name.TryMapToColorSpace(out colorSpaceResult))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryExtendedColorSpaceNameMapping(name, out colorSpaceResult))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!resourceStore.TryGetNamedColorSpace(name, out var colorSpaceNamedToken))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (colorSpaceNamedToken.Name.TryMapToColorSpace(out colorSpaceResult))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryExtendedColorSpaceNameMapping(colorSpaceNamedToken.Name, out colorSpaceResult))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var bounds = transformationMatrix.Transform(new PdfRectangle(new PdfPoint(1, 1),
|
var bounds = transformationMatrix.Transform(new PdfRectangle(new PdfPoint(1, 1),
|
||||||
new PdfPoint(0, 0)));
|
new PdfPoint(0, 0)));
|
||||||
@@ -90,7 +63,7 @@
|
|||||||
throw new PdfDocumentFormatException($"Invalid ColorSpace array defined for inline image: {colorSpaceArray}.");
|
throw new PdfDocumentFormatException($"Invalid ColorSpace array defined for inline image: {colorSpaceArray}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryMapColorSpace(firstColorSpaceName, out var colorSpaceMapped))
|
if (!ColorSpaceMapper.TryMap(firstColorSpaceName, resourceStore, out var colorSpaceMapped))
|
||||||
{
|
{
|
||||||
throw new PdfDocumentFormatException($"Invalid ColorSpace defined for inline image: {firstColorSpaceName}.");
|
throw new PdfDocumentFormatException($"Invalid ColorSpace defined for inline image: {firstColorSpaceName}.");
|
||||||
}
|
}
|
||||||
@@ -99,7 +72,7 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!TryMapColorSpace(colorSpaceName, out var colorSpaceMapped))
|
if (!ColorSpaceMapper.TryMap(colorSpaceName, resourceStore, out var colorSpaceMapped))
|
||||||
{
|
{
|
||||||
throw new PdfDocumentFormatException($"Invalid ColorSpace defined for inline image: {colorSpaceName}.");
|
throw new PdfDocumentFormatException($"Invalid ColorSpace defined for inline image: {colorSpaceName}.");
|
||||||
}
|
}
|
||||||
@@ -108,6 +81,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var imgDic = new DictionaryToken(Properties ?? new Dictionary<NameToken, IToken>());
|
||||||
|
|
||||||
|
var details = ColorSpaceDetailsParser.GetColorSpaceDetails(
|
||||||
|
colorSpace,
|
||||||
|
imgDic,
|
||||||
|
tokenScanner,
|
||||||
|
resourceStore,
|
||||||
|
filterProvider);
|
||||||
|
|
||||||
var renderingIntent = GetByKeys<NameToken>(NameToken.Intent, null, false)?.Data?.ToRenderingIntent() ?? defaultRenderingIntent;
|
var renderingIntent = GetByKeys<NameToken>(NameToken.Intent, null, false)?.Data?.ToRenderingIntent() ?? defaultRenderingIntent;
|
||||||
|
|
||||||
var filterNames = new List<NameToken>();
|
var filterNames = new List<NameToken>();
|
||||||
@@ -157,30 +139,8 @@
|
|||||||
|
|
||||||
return new InlineImage(bounds, width, height, bitsPerComponent, isMask, renderingIntent, interpolate, colorSpace, decode, Bytes,
|
return new InlineImage(bounds, width, height, bitsPerComponent, isMask, renderingIntent, interpolate, colorSpace, decode, Bytes,
|
||||||
filters,
|
filters,
|
||||||
streamDictionary);
|
streamDictionary,
|
||||||
}
|
details);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
|
// ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
|
||||||
|
78
src/UglyToad.PdfPig/Images/ColorSpaceDetailsByteConverter.cs
Normal file
78
src/UglyToad.PdfPig/Images/ColorSpaceDetailsByteConverter.cs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
namespace UglyToad.PdfPig.Images
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Graphics.Colors;
|
||||||
|
|
||||||
|
internal static class ColorSpaceDetailsByteConverter
|
||||||
|
{
|
||||||
|
public static byte[] Convert(ColorSpaceDetails details, IReadOnlyList<byte> decoded)
|
||||||
|
{
|
||||||
|
switch (details)
|
||||||
|
{
|
||||||
|
case IndexedColorSpaceDetails indexed:
|
||||||
|
return UnwrapIndexedColorSpaceBytes(indexed, decoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
return decoded.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] UnwrapIndexedColorSpaceBytes(IndexedColorSpaceDetails indexed, IReadOnlyList<byte> input)
|
||||||
|
{
|
||||||
|
var multiplier = 1;
|
||||||
|
Func<byte, IEnumerable<byte>> transformer = null;
|
||||||
|
switch (indexed.BaseColorSpaceDetails.Type)
|
||||||
|
{
|
||||||
|
case ColorSpace.DeviceRGB:
|
||||||
|
transformer = x =>
|
||||||
|
{
|
||||||
|
var r = new byte[3];
|
||||||
|
for (var i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
r[i] = indexed.ColorTable[x + i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
};
|
||||||
|
multiplier = 3;
|
||||||
|
break;
|
||||||
|
case ColorSpace.DeviceCMYK:
|
||||||
|
transformer = x =>
|
||||||
|
{
|
||||||
|
var r = new byte[4];
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
r[i] = indexed.ColorTable[x + i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
};
|
||||||
|
|
||||||
|
multiplier = 4;
|
||||||
|
break;
|
||||||
|
case ColorSpace.DeviceGray:
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -9,15 +9,23 @@
|
|||||||
{
|
{
|
||||||
bytes = null;
|
bytes = null;
|
||||||
|
|
||||||
var isColorSpaceSupported = image.ColorSpace == ColorSpace.DeviceGray || image.ColorSpace == ColorSpace.DeviceRGB;
|
var hasValidDetails = image.ColorSpaceDetails != null &&
|
||||||
|
!(image.ColorSpaceDetails is UnsupportedColorSpaceDetails);
|
||||||
|
var actualColorSpace = hasValidDetails ? image.ColorSpaceDetails.BaseType : image.ColorSpace;
|
||||||
|
|
||||||
|
var isColorSpaceSupported =
|
||||||
|
actualColorSpace == ColorSpace.DeviceGray || actualColorSpace == ColorSpace.DeviceRGB;
|
||||||
|
|
||||||
if (!isColorSpaceSupported || !image.TryGetBytes(out var bytesPure))
|
if (!isColorSpaceSupported || !image.TryGetBytes(out var bytesPure))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bytesPure = ColorSpaceDetailsByteConverter.Convert(image.ColorSpaceDetails, bytesPure);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var is3Byte = image.ColorSpace == ColorSpace.DeviceRGB;
|
var is3Byte = actualColorSpace == ColorSpace.DeviceRGB;
|
||||||
var multiplier = is3Byte ? 3 : 1;
|
var multiplier = is3Byte ? 3 : 1;
|
||||||
|
|
||||||
var builder = PngBuilder.Create(image.WidthInSamples, image.HeightInSamples, false);
|
var builder = PngBuilder.Create(image.WidthInSamples, image.HeightInSamples, false);
|
||||||
|
200
src/UglyToad.PdfPig/Util/ColorSpaceDetailsParser.cs
Normal file
200
src/UglyToad.PdfPig/Util/ColorSpaceDetailsParser.cs
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
namespace UglyToad.PdfPig.Util
|
||||||
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Content;
|
||||||
|
using Filters;
|
||||||
|
using Graphics.Colors;
|
||||||
|
using Parser.Parts;
|
||||||
|
using Tokenization.Scanner;
|
||||||
|
using Tokens;
|
||||||
|
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryExtendedColorSpaceNameMapping(name, out colorSpaceResult))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resourceStore.TryGetNamedColorSpace(name, out var colorSpaceNamedToken))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (colorSpaceNamedToken.Name.TryMapToColorSpace(out colorSpaceResult))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryExtendedColorSpaceNameMapping(colorSpaceNamedToken.Name, out colorSpaceResult))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class ColorSpaceDetailsParser
|
||||||
|
{
|
||||||
|
public static ColorSpaceDetails GetColorSpaceDetails(ColorSpace? colorSpace,
|
||||||
|
DictionaryToken imageDictionary,
|
||||||
|
IPdfTokenScanner scanner,
|
||||||
|
IResourceStore resourceStore,
|
||||||
|
IFilterProvider filterProvider,
|
||||||
|
bool cannotRecurse = false)
|
||||||
|
{
|
||||||
|
if (!colorSpace.HasValue)
|
||||||
|
{
|
||||||
|
return UnsupportedColorSpaceDetails.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (colorSpace.Value)
|
||||||
|
{
|
||||||
|
case ColorSpace.DeviceGray:
|
||||||
|
return DeviceGrayColorSpaceDetails.Instance;
|
||||||
|
case ColorSpace.DeviceRGB:
|
||||||
|
return DeviceRgbColorSpaceDetails.Instance;
|
||||||
|
case ColorSpace.DeviceCMYK:
|
||||||
|
return DeviceCmykColorSpaceDetails.Instance;
|
||||||
|
case ColorSpace.CalGray:
|
||||||
|
return UnsupportedColorSpaceDetails.Instance;
|
||||||
|
case ColorSpace.CalRGB:
|
||||||
|
return UnsupportedColorSpaceDetails.Instance;
|
||||||
|
case ColorSpace.Lab:
|
||||||
|
return UnsupportedColorSpaceDetails.Instance;
|
||||||
|
case ColorSpace.ICCBased:
|
||||||
|
return UnsupportedColorSpaceDetails.Instance;
|
||||||
|
case ColorSpace.Indexed:
|
||||||
|
{
|
||||||
|
if (cannotRecurse)
|
||||||
|
{
|
||||||
|
return UnsupportedColorSpaceDetails.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!imageDictionary.TryGet(NameToken.ColorSpace, scanner, out ArrayToken colorSpaceArray)
|
||||||
|
|| colorSpaceArray.Length != 4)
|
||||||
|
{
|
||||||
|
// Error instead?
|
||||||
|
return UnsupportedColorSpaceDetails.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
var first = colorSpaceArray[0] as NameToken;
|
||||||
|
|
||||||
|
if (first == null || !ColorSpaceMapper.TryMap(first, resourceStore, out var innerColorSpace)
|
||||||
|
|| innerColorSpace != ColorSpace.Indexed)
|
||||||
|
{
|
||||||
|
return UnsupportedColorSpaceDetails.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
var second = colorSpaceArray[1];
|
||||||
|
|
||||||
|
ColorSpaceDetails baseDetails;
|
||||||
|
|
||||||
|
if (DirectObjectFinder.TryGet(second, scanner, out NameToken baseColorSpaceNameToken)
|
||||||
|
&& ColorSpaceMapper.TryMap(baseColorSpaceNameToken, resourceStore, out var baseColorSpaceName))
|
||||||
|
{
|
||||||
|
baseDetails = GetColorSpaceDetails(
|
||||||
|
baseColorSpaceName,
|
||||||
|
imageDictionary,
|
||||||
|
scanner,
|
||||||
|
resourceStore,
|
||||||
|
filterProvider,
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
else if (DirectObjectFinder.TryGet(second, scanner, out ArrayToken baseColorSpaceArrayToken)
|
||||||
|
&& baseColorSpaceArrayToken.Length > 0 && baseColorSpaceArrayToken[0] is NameToken baseColorSpaceArrayNameToken
|
||||||
|
&& ColorSpaceMapper.TryMap(baseColorSpaceArrayNameToken, resourceStore, out var baseColorSpaceArrayColorSpace))
|
||||||
|
{
|
||||||
|
var pseudoImageDictionary = new DictionaryToken(new Dictionary<NameToken, IToken>
|
||||||
|
{
|
||||||
|
{NameToken.ColorSpace, baseColorSpaceArrayToken}
|
||||||
|
});
|
||||||
|
|
||||||
|
baseDetails = GetColorSpaceDetails(
|
||||||
|
baseColorSpaceArrayColorSpace,
|
||||||
|
pseudoImageDictionary,
|
||||||
|
scanner,
|
||||||
|
resourceStore,
|
||||||
|
filterProvider,
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return UnsupportedColorSpaceDetails.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (baseDetails is UnsupportedColorSpaceDetails)
|
||||||
|
{
|
||||||
|
return UnsupportedColorSpaceDetails.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
var third = colorSpaceArray[2];
|
||||||
|
|
||||||
|
if (!DirectObjectFinder.TryGet(third, scanner, out NumericToken hiValNumericToken))
|
||||||
|
{
|
||||||
|
return UnsupportedColorSpaceDetails.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hival = hiValNumericToken.Int;
|
||||||
|
|
||||||
|
var fourth = colorSpaceArray[3];
|
||||||
|
|
||||||
|
IReadOnlyList<byte> tableBytes;
|
||||||
|
|
||||||
|
if (DirectObjectFinder.TryGet(fourth, scanner, out HexToken tableHexToken))
|
||||||
|
{
|
||||||
|
tableBytes = tableHexToken.Bytes;
|
||||||
|
}
|
||||||
|
else if (DirectObjectFinder.TryGet(fourth, scanner, out StreamToken tableStreamToken))
|
||||||
|
{
|
||||||
|
tableBytes = tableStreamToken.Decode(filterProvider);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return UnsupportedColorSpaceDetails.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new IndexedColorSpaceDetails(baseDetails, (byte)hival, tableBytes);
|
||||||
|
}
|
||||||
|
case ColorSpace.Pattern:
|
||||||
|
return UnsupportedColorSpaceDetails.Instance;
|
||||||
|
case ColorSpace.Separation:
|
||||||
|
return UnsupportedColorSpaceDetails.Instance;
|
||||||
|
case ColorSpace.DeviceN:
|
||||||
|
return UnsupportedColorSpaceDetails.Instance;
|
||||||
|
default:
|
||||||
|
return UnsupportedColorSpaceDetails.Instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,4 @@
|
|||||||
using UglyToad.PdfPig.Parser.Parts;
|
namespace UglyToad.PdfPig.XObjects
|
||||||
|
|
||||||
namespace UglyToad.PdfPig.XObjects
|
|
||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -11,8 +9,10 @@ namespace UglyToad.PdfPig.XObjects
|
|||||||
using Graphics;
|
using Graphics;
|
||||||
using Graphics.Colors;
|
using Graphics.Colors;
|
||||||
using Graphics.Core;
|
using Graphics.Core;
|
||||||
|
using Parser.Parts;
|
||||||
using Tokenization.Scanner;
|
using Tokenization.Scanner;
|
||||||
using Tokens;
|
using Tokens;
|
||||||
|
using Util;
|
||||||
|
|
||||||
internal static class XObjectFactory
|
internal static class XObjectFactory
|
||||||
{
|
{
|
||||||
@@ -137,10 +137,27 @@ namespace UglyToad.PdfPig.XObjects
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new XObjectImage(bounds, width, height, bitsPerComponent, colorSpace, isJpxDecode, isImageMask, intent, interpolate, decode,
|
var details = ColorSpaceDetailsParser.GetColorSpaceDetails(colorSpace, dictionary, pdfScanner, resourceStore, filterProvider);
|
||||||
dictionary, xObject.Stream.Data, decodedBytes);
|
|
||||||
|
return new XObjectImage(
|
||||||
|
bounds,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
bitsPerComponent,
|
||||||
|
colorSpace,
|
||||||
|
isJpxDecode,
|
||||||
|
isImageMask,
|
||||||
|
intent,
|
||||||
|
interpolate,
|
||||||
|
decode,
|
||||||
|
dictionary,
|
||||||
|
xObject.Stream.Data,
|
||||||
|
decodedBytes,
|
||||||
|
details);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static bool TryMapColorSpace(NameToken name, IResourceStore resourceStore, out ColorSpace colorSpaceResult)
|
private static bool TryMapColorSpace(NameToken name, IResourceStore resourceStore, out ColorSpace colorSpaceResult)
|
||||||
{
|
{
|
||||||
if (name.TryMapToColorSpace(out colorSpaceResult))
|
if (name.TryMapToColorSpace(out colorSpaceResult))
|
||||||
|
@@ -6,6 +6,7 @@
|
|||||||
using Core;
|
using Core;
|
||||||
using Graphics.Colors;
|
using Graphics.Colors;
|
||||||
using Graphics.Core;
|
using Graphics.Core;
|
||||||
|
using Images;
|
||||||
using Images.Png;
|
using Images.Png;
|
||||||
using Tokens;
|
using Tokens;
|
||||||
using Util.JetBrains.Annotations;
|
using Util.JetBrains.Annotations;
|
||||||
@@ -64,11 +65,17 @@
|
|||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IReadOnlyList<byte> RawBytes { get; }
|
public IReadOnlyList<byte> RawBytes { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ColorSpaceDetails ColorSpaceDetails { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="XObjectImage"/>.
|
/// Creates a new <see cref="XObjectImage"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal XObjectImage(PdfRectangle bounds, int widthInSamples, int heightInSamples, int bitsPerComponent,
|
internal XObjectImage(PdfRectangle bounds,
|
||||||
|
int widthInSamples,
|
||||||
|
int heightInSamples,
|
||||||
|
int bitsPerComponent,
|
||||||
ColorSpace? colorSpace,
|
ColorSpace? colorSpace,
|
||||||
bool isJpxEncoded,
|
bool isJpxEncoded,
|
||||||
bool isImageMask,
|
bool isImageMask,
|
||||||
@@ -76,8 +83,9 @@
|
|||||||
bool interpolate,
|
bool interpolate,
|
||||||
IReadOnlyList<decimal> decode,
|
IReadOnlyList<decimal> decode,
|
||||||
DictionaryToken imageDictionary,
|
DictionaryToken imageDictionary,
|
||||||
IReadOnlyList<byte> rawBytes,
|
IReadOnlyList<byte> rawBytes,
|
||||||
Lazy<IReadOnlyList<byte>> bytes)
|
Lazy<IReadOnlyList<byte>> bytes,
|
||||||
|
ColorSpaceDetails colorSpaceDetails)
|
||||||
{
|
{
|
||||||
Bounds = bounds;
|
Bounds = bounds;
|
||||||
WidthInSamples = widthInSamples;
|
WidthInSamples = widthInSamples;
|
||||||
@@ -91,6 +99,7 @@
|
|||||||
Decode = decode;
|
Decode = decode;
|
||||||
ImageDictionary = imageDictionary ?? throw new ArgumentNullException(nameof(imageDictionary));
|
ImageDictionary = imageDictionary ?? throw new ArgumentNullException(nameof(imageDictionary));
|
||||||
RawBytes = rawBytes;
|
RawBytes = rawBytes;
|
||||||
|
ColorSpaceDetails = colorSpaceDetails;
|
||||||
bytesFactory = bytes;
|
bytesFactory = bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user