Remove decode parameter application from Stencil color space for consistency

This commit is contained in:
BobLd 2025-07-19 13:06:56 +01:00
parent a5e92cd11c
commit 692641671c
6 changed files with 31 additions and 43 deletions

View File

@ -172,11 +172,12 @@
var decodedBytes = ImageHelpers.LoadFileBytes("ccittfax-decoded.bin"); var decodedBytes = ImageHelpers.LoadFileBytes("ccittfax-decoded.bin");
var image = new TestPdfImage var image = new TestPdfImage
{ {
ColorSpaceDetails = IndexedColorSpaceDetails.Stencil(DeviceGrayColorSpaceDetails.Instance, new[] { 1.0, 0 }), ColorSpaceDetails = IndexedColorSpaceDetails.Stencil(DeviceGrayColorSpaceDetails.Instance),
DecodedBytes = decodedBytes, DecodedBytes = decodedBytes,
WidthInSamples = 1800, WidthInSamples = 1800,
HeightInSamples = 3113, HeightInSamples = 3113,
BitsPerComponent = 1 BitsPerComponent = 1,
Decode = [1.0, 0]
}; };
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes)); Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));

View File

@ -10,13 +10,7 @@
/// </summary> /// </summary>
public static bool NeedsReverseDecode(this IPdfImage pdfImage) public static bool NeedsReverseDecode(this IPdfImage pdfImage)
{ {
if (pdfImage.ColorSpaceDetails?.IsStencil == true) return pdfImage.Decode?.Count >= 2 && pdfImage.Decode[0] == 1 && pdfImage.Decode[1] == 0;
{
// Stencil color space already takes care of reversing.
return false;
}
return pdfImage.Decode.Count >= 2 && pdfImage.Decode[0] == 1 && pdfImage.Decode[1] == 0;
} }
} }
} }

View File

@ -15,12 +15,6 @@
/// </summary> /// </summary>
public abstract class ColorSpaceDetails public abstract class ColorSpaceDetails
{ {
/// <summary>
/// Is the color space a stencil indexed color space.
/// <para>Stencil color spaces take care of inverting colors based on the Decode array.</para>
/// </summary>
public bool IsStencil { get; }
/// <summary> /// <summary>
/// The type of the ColorSpace. /// The type of the ColorSpace.
/// </summary> /// </summary>
@ -45,11 +39,10 @@
/// <summary> /// <summary>
/// Create a new <see cref="ColorSpaceDetails"/>. /// Create a new <see cref="ColorSpaceDetails"/>.
/// </summary> /// </summary>
protected internal ColorSpaceDetails(ColorSpace type, bool isStencil = false) protected internal ColorSpaceDetails(ColorSpace type)
{ {
Type = type; Type = type;
BaseType = type; BaseType = type;
IsStencil = isStencil;
} }
/// <summary> /// <summary>
@ -278,15 +271,12 @@
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 extracting stencil masks as black-and-white images, /// Creates an 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).
/// [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.
/// </summary> /// </summary>
internal static ColorSpaceDetails Stencil(ColorSpaceDetails colorSpaceDetails, double[] decode) internal static ColorSpaceDetails Stencil(ColorSpaceDetails colorSpaceDetails)
{ {
var blackIsOne = decode.Length >= 2 && decode[0] == 1 && decode[1] == 0; return new IndexedColorSpaceDetails(colorSpaceDetails, 1, [0, 255]);
return new IndexedColorSpaceDetails(colorSpaceDetails, 1, blackIsOne ? [255, 0] : [0, 255], true);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -321,14 +311,7 @@
/// Create a new <see cref="IndexedColorSpaceDetails"/>. /// Create a new <see cref="IndexedColorSpaceDetails"/>.
/// </summary> /// </summary>
public IndexedColorSpaceDetails(ColorSpaceDetails baseColorSpaceDetails, byte hiVal, byte[] colorTable) public IndexedColorSpaceDetails(ColorSpaceDetails baseColorSpaceDetails, byte hiVal, byte[] colorTable)
: this(baseColorSpaceDetails, hiVal, colorTable, false) : base(ColorSpace.Indexed)
{ }
/// <summary>
/// Create a new <see cref="IndexedColorSpaceDetails"/>.
/// </summary>
private IndexedColorSpaceDetails(ColorSpaceDetails baseColorSpaceDetails, byte hiVal, byte[] colorTable, bool isStencil)
: base(ColorSpace.Indexed, isStencil)
{ {
BaseColorSpace = baseColorSpaceDetails ?? throw new ArgumentNullException(nameof(baseColorSpaceDetails)); BaseColorSpace = baseColorSpaceDetails ?? throw new ArgumentNullException(nameof(baseColorSpaceDetails));
HiVal = hiVal; HiVal = hiVal;

View File

@ -159,13 +159,28 @@
else else
{ {
int i = 0; int i = 0;
for (int col = 0; col < image.HeightInSamples; col++) if (!image.NeedsReverseDecode()) // TODO - Need to properly implement decode for other numberOfComponents
{ {
for (int row = 0; row < image.WidthInSamples; row++) for (int col = 0; col < image.HeightInSamples; col++)
{ {
byte a = getAlphaChannel(i); for (int row = 0; row < image.WidthInSamples; row++)
byte pixel = bytesPure[i++]; {
builder.SetPixel(new Pixel(pixel, pixel, pixel, a, false), row, col); byte a = getAlphaChannel(i);
byte pixel = bytesPure[i++];
builder.SetPixel(new Pixel(pixel, pixel, pixel, a, false), row, col);
}
}
}
else
{
for (int col = 0; col < image.HeightInSamples; col++)
{
for (int row = 0; row < image.WidthInSamples; row++)
{
byte a = getAlphaChannel(i);
byte pixel = (byte)(255 - bytesPure[i++]); // Inverse decode
builder.SetPixel(new Pixel(pixel, pixel, pixel, a, false), row, col);
}
} }
} }
} }

View File

@ -54,11 +54,7 @@
} }
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);
return IndexedColorSpaceDetails.Stencil(colorSpaceDetails);
var decodeRaw = imageDictionary.GetObjectOrDefault(NameToken.Decode, NameToken.D) as ArrayToken ?? new ArrayToken([]);
var decode = decodeRaw.Data.OfType<NumericToken>().Select(static x => x.Double).ToArray();
return IndexedColorSpaceDetails.Stencil(colorSpaceDetails, decode);
} }
if (!colorSpace.HasValue) if (!colorSpace.HasValue)

View File

@ -92,7 +92,6 @@
null); null);
softMaskImage = ReadImage(maskImageRecord, pdfScanner, filterProvider, resourceStore); softMaskImage = ReadImage(maskImageRecord, pdfScanner, filterProvider, resourceStore);
System.Diagnostics.Debug.Assert(softMaskImage.ColorSpaceDetails?.IsStencil == true);
} }
var isJpxDecode = dictionary.TryGet(NameToken.Filter, out NameToken filterName) && filterName.Equals(NameToken.JpxDecode); var isJpxDecode = dictionary.TryGet(NameToken.Filter, out NameToken filterName) && filterName.Equals(NameToken.JpxDecode);