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 6a06452103
6 changed files with 31 additions and 43 deletions

View File

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

View File

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

View File

@ -15,12 +15,6 @@
/// </summary>
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>
/// The type of the ColorSpace.
/// </summary>
@ -45,11 +39,10 @@
/// <summary>
/// Create a new <see cref="ColorSpaceDetails"/>.
/// </summary>
protected internal ColorSpaceDetails(ColorSpace type, bool isStencil = false)
protected internal ColorSpaceDetails(ColorSpace type)
{
Type = type;
BaseType = type;
IsStencil = isStencil;
}
/// <summary>
@ -278,15 +271,12 @@
private readonly ConcurrentDictionary<double, IColor> cache = new ConcurrentDictionary<double, IColor>();
/// <summary>
/// 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
/// [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.
/// 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).
/// </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, blackIsOne ? [255, 0] : [0, 255], true);
return new IndexedColorSpaceDetails(colorSpaceDetails, 1, [0, 255]);
}
/// <inheritdoc/>
@ -321,14 +311,7 @@
/// Create a new <see cref="IndexedColorSpaceDetails"/>.
/// </summary>
public IndexedColorSpaceDetails(ColorSpaceDetails baseColorSpaceDetails, byte hiVal, byte[] colorTable)
: this(baseColorSpaceDetails, hiVal, colorTable, false)
{ }
/// <summary>
/// Create a new <see cref="IndexedColorSpaceDetails"/>.
/// </summary>
private IndexedColorSpaceDetails(ColorSpaceDetails baseColorSpaceDetails, byte hiVal, byte[] colorTable, bool isStencil)
: base(ColorSpace.Indexed, isStencil)
: base(ColorSpace.Indexed)
{
BaseColorSpace = baseColorSpaceDetails ?? throw new ArgumentNullException(nameof(baseColorSpaceDetails));
HiVal = hiVal;

View File

@ -159,13 +159,28 @@
else
{
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);
byte pixel = bytesPure[i++];
builder.SetPixel(new Pixel(pixel, pixel, pixel, a, false), row, col);
for (int row = 0; row < image.WidthInSamples; row++)
{
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 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);
return IndexedColorSpaceDetails.Stencil(colorSpaceDetails);
}
if (!colorSpace.HasValue)

View File

@ -92,7 +92,6 @@
null);
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);