Skip to content

TIFF without a BitsPerSample entry fails to decode or reports 0 bits per pixel #3242

Open

Description

@jafin

Prerequisites

  • I have written a descriptive issue title
  • I have verified that I am running the latest version of ImageSharp
  • I have verified if the problem exist in both DEBUG and RELEASE mode
  • I have searched open and closed issues to ensure it has not already been reported

ImageSharp version

main @ a73c43d (also present in v4.1.3, same code)

Other ImageSharp packages and versions

None

Environment (Operating system, version and so on)

Windows 11 Pro 10.0.22631, x64

.NET Framework version

.NET 10 (net10.0)

Description

TIFF 6.0 gives BitsPerSample a default of 1 (one bit for each sample) when the entry is absent. Bilevel fax and scanner files often leave the entry out. ImageSharp does not use this default:

  • An uncompressed bilevel TIFF without BitsPerSample fails to decode with NotSupportedException: The number of samples in the TIFF BitsPerSample entry is not supported.
  • A CCITT Group 4 bilevel TIFF without BitsPerSample decodes, but its frame metadata reports BitsPerPixel 0 and BitsPerSample (0, 0, 0).
  • Image.Identify reports PixelType.BitsPerPixel 0 for both files.

Expected: the decoder uses 1 bit for each sample (SamplesPerPixel itself defaults to 1). Both files then decode to the same pixels as the same file with an explicit BitsPerSample = 1, and the metadata and Identify report 1 bit per pixel.

Cause: TiffFrameMetadata.Parse sets BitsPerSample only when the entry is present and valid. When the entry is absent, the property keeps its initializer value TiffConstants.DefaultBitsPerSample. That value is meant to be 8-bit RGB. It is actually (0, 0, 0) at run time, because DefaultBitsPerSample = BitsPerSampleRgb8Bit is declared before BitsPerSampleRgb8Bit, and static fields initialize in declaration order. The decoder then rejects the zero-sample value, or reports 0 bits per pixel. For CCITT compression, the decoder options use 1 bit per sample, so those pixels decode correctly, but the frame metadata is not updated.

A fix is proposed in #3230.

Related: #2587 (closed) reported the same exception for CCITT G4 files. That fix forced 1 bit per sample for CCITT compression only, so uncompressed files without BitsPerSample still fail, and the metadata and Identify still report 0 bits per pixel.

Steps to Reproduce

The snippet encodes a 16x8 bilevel TIFF, then renames the BitsPerSample entry (tag 258) to an unused private tag (65000). The decoder then does not find the entry.

using System.Buffers.Binary;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.PixelFormats;

foreach (TiffCompression compression in new[] { TiffCompression.None, TiffCompression.CcittGroup4Fax })
{
    using Image<L8> source = new(16, 8);
    for (int y = 0; y < source.Height; y++)
    {
        for (int x = 0; x < source.Width; x++)
        {
            source[x, y] = new L8((byte)((x + y) % 3 == 0 ? 255 : 0));
        }
    }

    using MemoryStream ms = new();
    source.Save(ms, new TiffEncoder { BitsPerPixel = TiffBitsPerPixel.Bit1, Compression = compression });
    byte[] tiff = ms.ToArray();

    // Rename the BitsPerSample entry (tag 258) of the first IFD to the unused tag 65000.
    int ifd = (int)BinaryPrimitives.ReadUInt32LittleEndian(tiff.AsSpan(4));
    int count = BinaryPrimitives.ReadUInt16LittleEndian(tiff.AsSpan(ifd));
    for (int i = 0; i < count; i++)
    {
        Span<byte> entry = tiff.AsSpan(ifd + 2 + (i * 12));
        if (BinaryPrimitives.ReadUInt16LittleEndian(entry) == 258)
        {
            BinaryPrimitives.WriteUInt16LittleEndian(entry, 65000);
        }
    }

    Console.WriteLine($"{compression}: Identify BitsPerPixel = {Image.Identify(tiff).PixelType.BitsPerPixel}");
    try
    {
        using Image<L8> image = Image.Load<L8>(tiff);
        TiffFrameMetadata meta = image.Frames.RootFrame.Metadata.GetTiffMetadata();
        Console.WriteLine($"{compression}: decoded, BitsPerPixel = {meta.BitsPerPixel}, BitsPerSample = {meta.BitsPerSample}");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"{compression}: {ex.GetType().FullName}: {ex.Message}");
    }
}

Actual output:

None: Identify BitsPerPixel = 0
None: System.NotSupportedException: The number of samples in the TIFF BitsPerSample entry is not supported.
CcittGroup4Fax: Identify BitsPerPixel = 0
CcittGroup4Fax: decoded, BitsPerPixel = 0, BitsPerSample = TiffBitsPerSample(0, 0, 0)

Stack trace for the uncompressed file:

System.NotSupportedException: The number of samples in the TIFF BitsPerSample entry is not supported.
   at SixLabors.ImageSharp.Formats.Tiff.TiffThrowHelper.ThrowNotSupported(String message)
   at SixLabors.ImageSharp.Formats.Tiff.TiffDecoderOptionsParser.ParseColorType(TiffDecoderCore options, ExifProfile exifProfile)
   at SixLabors.ImageSharp.Formats.Tiff.TiffDecoderOptionsParser.VerifyAndParse(TiffDecoderCore options, ExifProfile exifProfile, TiffFrameMetadata frameMetadata)
   at SixLabors.ImageSharp.Formats.Tiff.TiffDecoderCore.DecodeFrame[TPixel](ExifProfile tags, Nullable`1 size, CancellationToken cancellationToken)
   at SixLabors.ImageSharp.Formats.Tiff.TiffDecoderCore.Decode[TPixel](BufferedReadStream stream, CancellationToken cancellationToken)
   at SixLabors.ImageSharp.Formats.ImageDecoderCore.Decode[TPixel](Configuration configuration, Stream stream, CancellationToken cancellationToken)

Expected: both files decode to the same pixels as the unmodified file. Identify and the frame metadata report 1 bit per pixel (TiffBitsPerPixel.Bit1).

Images

No images needed; the repro builds the input in memory.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions