From dcc736c2c8765cf545cad05717d7d7021735e815 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 2 Feb 2026 11:20:09 +1000 Subject: [PATCH 1/3] Capture palette from Tiff when available. --- src/ImageSharp/Color/Color.cs | 15 +++ .../Tiff/Constants/TiffExtraSamples.cs | 25 ----- .../PaletteTiffColor{TPixel}.cs | 92 +++++++++++++++++-- .../TiffColorDecoderFactory{TPixel}.cs | 2 +- .../Formats/Tiff/TiffDecoderCore.cs | 14 +++ .../Formats/Tiff/TiffFrameMetadata.cs | 8 ++ .../PaletteTiffColorTests.cs | 6 +- .../Formats/Tiff/TiffMetadataTests.cs | 22 ++++- 8 files changed, 145 insertions(+), 39 deletions(-) delete mode 100644 src/ImageSharp/Formats/Tiff/Constants/TiffExtraSamples.cs diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs index bb78dcbbc..59bce5876 100644 --- a/src/ImageSharp/Color/Color.cs +++ b/src/ImageSharp/Color/Color.cs @@ -96,6 +96,21 @@ public readonly partial struct Color : IEquatable [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Color FromScaledVector(Vector4 source) => new(source); + /// + /// Bulk converts a span of generic scaled to a span of . + /// + /// The source vector span. + /// The destination color span. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void FromScaledVector(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = FromScaledVector(source[i]); + } + } + /// /// Bulk converts a span of a specified type to a span of . /// diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffExtraSamples.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffExtraSamples.cs deleted file mode 100644 index 10323304f..000000000 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffExtraSamples.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tiff.Constants; - -/// -/// Enumeration representing the possible uses of extra components in TIFF format files. -/// -internal enum TiffExtraSamples -{ - /// - /// Unspecified data. - /// - Unspecified = 0, - - /// - /// Associated alpha data (with pre-multiplied color). - /// - AssociatedAlpha = 1, - - /// - /// Unassociated alpha data. - /// - UnassociatedAlpha = 2 -} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs index 69113cf93..5e113ddbb 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs @@ -16,8 +16,13 @@ internal class PaletteTiffColor : TiffBaseColorDecoder where TPixel : unmanaged, IPixel { private readonly ushort bitsPerSample0; + private readonly ushort bitsPerSample1; + private readonly TiffExtraSampleType? extraSamplesType; - private readonly TPixel[] palette; + private readonly Vector4[] paletteVectors; + private readonly float alphaScale; + private readonly bool hasAlpha; + private Color[]? paletteColors; private const float InvMax = 1f / 65535f; @@ -26,34 +31,100 @@ internal class PaletteTiffColor : TiffBaseColorDecoder /// /// The number of bits per sample for each pixel. /// The RGB color lookup table to use for decoding the image. - public PaletteTiffColor(TiffBitsPerSample bitsPerSample, ushort[] colorMap) + /// The type of extra samples. + public PaletteTiffColor(TiffBitsPerSample bitsPerSample, ushort[] colorMap, TiffExtraSampleType? extraSamplesType) { this.bitsPerSample0 = bitsPerSample.Channel0; + this.bitsPerSample1 = bitsPerSample.Channel1; + this.extraSamplesType = extraSamplesType; + int colorCount = 1 << this.bitsPerSample0; - this.palette = GeneratePalette(colorMap, colorCount); + + // TIFF PaletteColor uses ColorMap (tag 320 / 0x0140) which is RGB-only (no alpha). + this.paletteVectors = GeneratePaletteVectors(colorMap, colorCount); + + // ExtraSamples (tag 338 / 0x0152) describes extra per-pixel samples stored in the image data stream. + // For PaletteColor, any alpha is per pixel (stored alongside the index), not per palette entry. + this.hasAlpha = + this.bitsPerSample1 > 0 + && this.extraSamplesType.HasValue + && this.extraSamplesType != TiffExtraSampleType.UnspecifiedData; + + if (this.hasAlpha) + { + ulong alphaMax = (1UL << this.bitsPerSample1) - 1; + this.alphaScale = alphaMax > 0 ? 1f / alphaMax : 1f; + } } + public Color[] PaletteColors => this.paletteColors ??= GenerateColorPalette(this.paletteVectors); + /// public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) { BitReader bitReader = new(data); + if (this.hasAlpha) + { + Color[] colors = this.paletteColors ??= GenerateColorPalette(this.paletteVectors); + + // NOTE: ExtraSamples may report "AssociatedAlphaData". For PaletteColor, the stored color sample is the + // palette index, not per-pixel RGB components, so the premultiplication concept is not representable + // in the encoded stream. We therefore treat the alpha sample as a per-pixel alpha value applied after + // palette expansion. + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + for (int x = 0; x < pixelRow.Length; x++) + { + int index = bitReader.ReadBits(this.bitsPerSample0); + float alpha = bitReader.ReadBits(this.bitsPerSample1) * this.alphaScale; + + // Defensive guard against malformed streams. + if ((uint)index >= (uint)this.paletteVectors.Length) + { + index = 0; + } + + Vector4 color = this.paletteVectors[index]; + color.W = alpha; + + pixelRow[x] = TPixel.FromScaledVector4(color); + + // Best-effort palette update for downstream conversions. + // This is intentionally "last writer wins" with no per-pixel branch. + colors[index] = Color.FromScaledVector(color); + } + + bitReader.NextRow(); + } + + return; + } + for (int y = top; y < top + height; y++) { Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); for (int x = 0; x < pixelRow.Length; x++) { int index = bitReader.ReadBits(this.bitsPerSample0); - pixelRow[x] = this.palette[index]; + + // Defensive guard against malformed streams. + if ((uint)index >= (uint)this.paletteVectors.Length) + { + index = 0; + } + + pixelRow[x] = TPixel.FromScaledVector4(this.paletteVectors[index]); } bitReader.NextRow(); } } - private static TPixel[] GeneratePalette(ushort[] colorMap, int colorCount) + private static Vector4[] GeneratePaletteVectors(ushort[] colorMap, int colorCount) { - TPixel[] palette = new TPixel[colorCount]; + Vector4[] palette = new Vector4[colorCount]; const int rOffset = 0; int gOffset = colorCount; @@ -64,9 +135,16 @@ internal class PaletteTiffColor : TiffBaseColorDecoder float r = colorMap[rOffset + i] * InvMax; float g = colorMap[gOffset + i] * InvMax; float b = colorMap[bOffset + i] * InvMax; - palette[i] = TPixel.FromScaledVector4(new Vector4(r, g, b, 1f)); + palette[i] = new Vector4(r, g, b, 1f); } return palette; } + + private static Color[] GenerateColorPalette(Vector4[] palette) + { + Color[] colors = new Color[palette.Length]; + Color.FromScaledVector(palette, colors); + return colors; + } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs index 270c7f356..dd36e912c 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -387,7 +387,7 @@ internal static class TiffColorDecoderFactory case TiffColorType.PaletteColor: DebugGuard.NotNull(colorMap, "colorMap"); - return new PaletteTiffColor(bitsPerSample, colorMap); + return new PaletteTiffColor(bitsPerSample, colorMap, extraSampleType); case TiffColorType.YCbCr: DebugGuard.IsTrue( diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index e3a51aa8d..5705e2a54 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -620,6 +620,13 @@ internal class TiffDecoderCore : ImageDecoderCore colorDecoder.Decode(stripBufferSpan, pixels, 0, top, width, stripHeight); } + + // If the color decoder is the palette decoder we need to capture its palette. + if (colorDecoder is PaletteTiffColor paletteDecoder) + { + TiffFrameMetadata tiffFrameMetadata = frame.Metadata.GetTiffMetadata(); + tiffFrameMetadata.LocalColorTable = paletteDecoder.PaletteColors; + } } /// @@ -804,6 +811,13 @@ internal class TiffDecoderCore : ImageDecoderCore tileIndex++; } } + + // If the color decoder is the palette decoder we need to capture its palette. + if (colorDecoder is PaletteTiffColor paletteDecoder) + { + TiffFrameMetadata tiffFrameMetadata = frame.Metadata.GetTiffMetadata(); + tiffFrameMetadata.LocalColorTable = paletteDecoder.PaletteColors; + } } private TiffBaseColorDecoder CreateChunkyColorDecoder(ImageFrameMetadata metadata) diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs index d9dfafbcc..09a9e8cf9 100644 --- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs +++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs @@ -33,6 +33,7 @@ public class TiffFrameMetadata : IFormatFrameMetadata this.InkSet = other.InkSet; this.EncodingWidth = other.EncodingWidth; this.EncodingHeight = other.EncodingHeight; + this.LocalColorTable = other.LocalColorTable; } /// @@ -75,6 +76,11 @@ public class TiffFrameMetadata : IFormatFrameMetadata /// public int EncodingHeight { get; set; } + /// + /// Gets or sets the local color table, if any. + /// + public ReadOnlyMemory? LocalColorTable { get; set; } + /// public static TiffFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata) { @@ -100,6 +106,8 @@ public class TiffFrameMetadata : IFormatFrameMetadata public void AfterFrameApply(ImageFrame source, ImageFrame destination, Matrix4x4 matrix) where TPixel : unmanaged, IPixel { + this.LocalColorTable = null; + float ratioX = destination.Width / (float)source.Width; float ratioY = destination.Height / (float)source.Height; this.EncodingWidth = Scale(this.EncodingWidth, destination.Width, ratioX); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs index a645f2edd..dbfa00950 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs @@ -89,7 +89,11 @@ public class PaletteTiffColorTests : PhotometricInterpretationTestBase public void Decode_WritesPixelData(byte[] inputData, ushort bitsPerSample, ushort[] colorMap, int left, int top, int width, int height, Rgba32[][] expectedResult) => AssertDecode(expectedResult, pixels => { - new PaletteTiffColor(new TiffBitsPerSample(bitsPerSample, 0, 0), colorMap).Decode(inputData, pixels, left, top, width, height); + new PaletteTiffColor( + new TiffBitsPerSample(bitsPerSample, 0, 0), + colorMap, + TiffExtraSampleType.UnspecifiedData) + .Decode(inputData, pixels, left, top, width, height); }); private static uint[][] GeneratePalette(int count) diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index f92d8ed59..8d75cd9bc 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -61,16 +61,15 @@ public class TiffMetadataTests clone.PhotometricInterpretation = TiffPhotometricInterpretation.CieLab; clone.Predictor = TiffPredictor.Horizontal; - Assert.False(meta.BitsPerPixel == clone.BitsPerPixel); - Assert.False(meta.Compression == clone.Compression); - Assert.False(meta.PhotometricInterpretation == clone.PhotometricInterpretation); - Assert.False(meta.Predictor == clone.Predictor); + Assert.NotEqual(meta.BitsPerPixel, clone.BitsPerPixel); + Assert.NotEqual(meta.Compression, clone.Compression); + Assert.NotEqual(meta.PhotometricInterpretation, clone.PhotometricInterpretation); + Assert.NotEqual(meta.Predictor, clone.Predictor); } private static void VerifyExpectedTiffFrameMetaDataIsPresent(TiffFrameMetadata frameMetaData) { Assert.NotNull(frameMetaData); - Assert.NotNull(frameMetaData.BitsPerPixel); Assert.Equal(TiffBitsPerPixel.Bit4, frameMetaData.BitsPerPixel); Assert.Equal(TiffCompression.Lzw, frameMetaData.Compression); Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frameMetaData.PhotometricInterpretation); @@ -409,4 +408,17 @@ public class TiffMetadataTests // Adding the IPTC and ICC profiles dynamically increments the number of values in the original EXIF profile by 2 Assert.Equal(exifProfileInput.Values.Count + 2, encodedImageExifProfile.Values.Count); } + + [Theory] + [WithFile(PaletteDeflateMultistrip, PixelTypes.Rgba32)] + [WithFile(PaletteUncompressed, PixelTypes.Rgba32)] + public void TiffDecoder_CanAssign_ColorPalette(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(TiffDecoder.Instance); + ImageFrame frame = image.Frames.RootFrame; + TiffFrameMetadata tiffMeta = frame.Metadata.GetTiffMetadata(); + Assert.Equal(TiffPhotometricInterpretation.PaletteColor, tiffMeta.PhotometricInterpretation); + Assert.NotNull(tiffMeta.LocalColorTable); + } } From a4c1a155e5f3455df0c5e7b623b29f609a311a96 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 2 Feb 2026 12:42:51 +1000 Subject: [PATCH 2/3] Fix reviewed issues --- src/ImageSharp/Color/Color.cs | 6 +-- .../PaletteTiffColor{TPixel}.cs | 46 +++++++++++++++---- .../Formats/Tiff/TiffDecoderOptionsParser.cs | 2 +- .../Formats/Tiff/TiffFrameMetadata.cs | 6 ++- 4 files changed, 46 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs index 59bce5876..dd248d488 100644 --- a/src/ImageSharp/Color/Color.cs +++ b/src/ImageSharp/Color/Color.cs @@ -105,7 +105,7 @@ public readonly partial struct Color : IEquatable public static void FromScaledVector(ReadOnlySpan source, Span destination) { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - for (int i = 0; i < destination.Length; i++) + for (int i = 0; i < source.Length; i++) { destination[i] = FromScaledVector(source[i]); } @@ -127,14 +127,14 @@ public readonly partial struct Color : IEquatable PixelTypeInfo info = TPixel.GetPixelTypeInfo(); if (info.ComponentInfo.HasValue && info.ComponentInfo.Value.GetMaximumComponentPrecision() <= (int)PixelComponentBitDepth.Bit32) { - for (int i = 0; i < destination.Length; i++) + for (int i = 0; i < source.Length; i++) { destination[i] = FromScaledVector(source[i].ToScaledVector4()); } } else { - for (int i = 0; i < destination.Length; i++) + for (int i = 0; i < source.Length; i++) { destination[i] = new Color(source[i]); } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs index 5e113ddbb..d1b519bdd 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs @@ -19,7 +19,9 @@ internal class PaletteTiffColor : TiffBaseColorDecoder private readonly ushort bitsPerSample1; private readonly TiffExtraSampleType? extraSamplesType; - private readonly Vector4[] paletteVectors; + private readonly Vector4[] vectorPallete; + private readonly TPixel[] pixelPalette; + private readonly float alphaScale; private readonly bool hasAlpha; private Color[]? paletteColors; @@ -41,7 +43,7 @@ internal class PaletteTiffColor : TiffBaseColorDecoder int colorCount = 1 << this.bitsPerSample0; // TIFF PaletteColor uses ColorMap (tag 320 / 0x0140) which is RGB-only (no alpha). - this.paletteVectors = GeneratePaletteVectors(colorMap, colorCount); + this.vectorPallete = GenerateVectorPalette(colorMap, colorCount); // ExtraSamples (tag 338 / 0x0152) describes extra per-pixel samples stored in the image data stream. // For PaletteColor, any alpha is per pixel (stored alongside the index), not per palette entry. @@ -54,10 +56,16 @@ internal class PaletteTiffColor : TiffBaseColorDecoder { ulong alphaMax = (1UL << this.bitsPerSample1) - 1; this.alphaScale = alphaMax > 0 ? 1f / alphaMax : 1f; + this.pixelPalette = []; + } + else + { + // Pre-generate pixel palette for non-alpha case for performance. + this.pixelPalette = GeneratePixelPalette(colorMap, colorCount); } } - public Color[] PaletteColors => this.paletteColors ??= GenerateColorPalette(this.paletteVectors); + public Color[] PaletteColors => this.paletteColors ??= GenerateColorPalette(this.vectorPallete); /// public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) @@ -66,7 +74,7 @@ internal class PaletteTiffColor : TiffBaseColorDecoder if (this.hasAlpha) { - Color[] colors = this.paletteColors ??= GenerateColorPalette(this.paletteVectors); + Color[] colors = this.paletteColors ??= GenerateColorPalette(this.vectorPallete); // NOTE: ExtraSamples may report "AssociatedAlphaData". For PaletteColor, the stored color sample is the // palette index, not per-pixel RGB components, so the premultiplication concept is not representable @@ -81,18 +89,19 @@ internal class PaletteTiffColor : TiffBaseColorDecoder float alpha = bitReader.ReadBits(this.bitsPerSample1) * this.alphaScale; // Defensive guard against malformed streams. - if ((uint)index >= (uint)this.paletteVectors.Length) + if ((uint)index >= (uint)this.vectorPallete.Length) { index = 0; } - Vector4 color = this.paletteVectors[index]; + Vector4 color = this.vectorPallete[index]; color.W = alpha; pixelRow[x] = TPixel.FromScaledVector4(color); // Best-effort palette update for downstream conversions. // This is intentionally "last writer wins" with no per-pixel branch. + // Performance is not an issue here since the constructor performs no actual transformations. colors[index] = Color.FromScaledVector(color); } @@ -110,19 +119,19 @@ internal class PaletteTiffColor : TiffBaseColorDecoder int index = bitReader.ReadBits(this.bitsPerSample0); // Defensive guard against malformed streams. - if ((uint)index >= (uint)this.paletteVectors.Length) + if ((uint)index >= (uint)this.pixelPalette.Length) { index = 0; } - pixelRow[x] = TPixel.FromScaledVector4(this.paletteVectors[index]); + pixelRow[x] = this.pixelPalette[index]; } bitReader.NextRow(); } } - private static Vector4[] GeneratePaletteVectors(ushort[] colorMap, int colorCount) + private static Vector4[] GenerateVectorPalette(ushort[] colorMap, int colorCount) { Vector4[] palette = new Vector4[colorCount]; @@ -141,6 +150,25 @@ internal class PaletteTiffColor : TiffBaseColorDecoder return palette; } + private static TPixel[] GeneratePixelPalette(ushort[] colorMap, int colorCount) + { + TPixel[] palette = new TPixel[colorCount]; + + const int rOffset = 0; + int gOffset = colorCount; + int bOffset = colorCount * 2; + + for (int i = 0; i < palette.Length; i++) + { + float r = colorMap[rOffset + i] * InvMax; + float g = colorMap[gOffset + i] * InvMax; + float b = colorMap[bOffset + i] * InvMax; + palette[i] = TPixel.FromScaledVector4(new Vector4(r, g, b, 1f)); + } + + return palette; + } + private static Color[] GenerateColorPalette(Vector4[] palette) { Color[] colors = new Color[palette.Length]; diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index 9445f468c..983b08c71 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -407,7 +407,7 @@ internal static class TiffDecoderOptionsParser if (exifProfile.TryGetValue(ExifTag.ColorMap, out IExifValue value)) { options.ColorMap = value.Value; - if (options.BitsPerSample.Channels != 1) + if (options.BitsPerSample.Channels is not 1 and not 2) { TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); } diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs index 09a9e8cf9..d4815ebdd 100644 --- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs +++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs @@ -33,7 +33,11 @@ public class TiffFrameMetadata : IFormatFrameMetadata this.InkSet = other.InkSet; this.EncodingWidth = other.EncodingWidth; this.EncodingHeight = other.EncodingHeight; - this.LocalColorTable = other.LocalColorTable; + + if (other.LocalColorTable?.Length > 0) + { + this.LocalColorTable = other.LocalColorTable.Value.ToArray(); + } } /// From c43b6365d057c6e3302778ebd9ee9c21f49452b9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 2 Feb 2026 12:46:03 +1000 Subject: [PATCH 3/3] Ensure palette is always captured. --- .../Formats/Tiff/TiffDecoderCore.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 5705e2a54..1822cb8d1 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -584,6 +584,15 @@ internal class TiffDecoderCore : ImageDecoderCore } } + { + // If the color decoder is the palette decoder we need to capture its palette. + if (colorDecoder is PaletteTiffColor paletteDecoder) + { + TiffFrameMetadata tiffFrameMetadata = frame.Metadata.GetTiffMetadata(); + tiffFrameMetadata.LocalColorTable = paletteDecoder.PaletteColors; + } + } + return; } @@ -621,11 +630,13 @@ internal class TiffDecoderCore : ImageDecoderCore colorDecoder.Decode(stripBufferSpan, pixels, 0, top, width, stripHeight); } - // If the color decoder is the palette decoder we need to capture its palette. - if (colorDecoder is PaletteTiffColor paletteDecoder) { - TiffFrameMetadata tiffFrameMetadata = frame.Metadata.GetTiffMetadata(); - tiffFrameMetadata.LocalColorTable = paletteDecoder.PaletteColors; + // If the color decoder is the palette decoder we need to capture its palette. + if (colorDecoder is PaletteTiffColor paletteDecoder) + { + TiffFrameMetadata tiffFrameMetadata = frame.Metadata.GetTiffMetadata(); + tiffFrameMetadata.LocalColorTable = paletteDecoder.PaletteColors; + } } }