From a4c1a155e5f3455df0c5e7b623b29f609a311a96 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 2 Feb 2026 12:42:51 +1000 Subject: [PATCH] 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 59bce58769..dd248d488f 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 5e113ddbb3..d1b519bddc 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 9445f468cb..983b08c711 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 09a9e8cf9f..d4815ebdd4 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(); + } } ///