Skip to content

Commit

Permalink
LibGfx/TIFF: Support samples with a floating point value
Browse files Browse the repository at this point in the history
The test case was generated using this Python code:

import tifffile
import numpy as np
image = np.array(
                [[[0, 0, 0], [255, 0, 0]],
                [[0, 127.5, 127.5], [255, 255, 255]]]
            ).astype(np.float32)
tifffile.imwrite('float32.tiff', image / 255, extratags=[
        (340, 11, 3, [0, 0, 0], True),
        (341, 11, 3, [1, 1, 1], True)])
  • Loading branch information
LucasChollet committed May 6, 2024
1 parent 8cd6446 commit 55c7d00
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 15 deletions.
14 changes: 14 additions & 0 deletions Tests/LibGfx/TestImageDecoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1045,6 +1045,20 @@ TEST_CASE(test_tiff_tiled)
EXPECT_EQ(frame.image->get_pixel(60, 75), Gfx::Color::NamedColor::Red);
}

TEST_CASE(test_tiff_float32)
{
auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/float32.tiff"sv)));
EXPECT(Gfx::TIFFImageDecoderPlugin::sniff(file->bytes()));
auto plugin_decoder = TRY_OR_FAIL(Gfx::TIFFImageDecoderPlugin::create(file->bytes()));

auto frame = TRY_OR_FAIL(expect_single_frame_of_size(*plugin_decoder, { 2, 2 }));

EXPECT_EQ(frame.image->get_pixel(0, 0), Gfx::Color::NamedColor::Black);
EXPECT_EQ(frame.image->get_pixel(1, 0), Gfx::Color::NamedColor::Red);
EXPECT_EQ(frame.image->get_pixel(0, 1), Gfx::Color(0, 128, 128));
EXPECT_EQ(frame.image->get_pixel(1, 1), Gfx::Color::NamedColor::White);
}

TEST_CASE(test_tiff_invalid_tag)
{
auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("tiff/invalid_tag.tiff"sv)));
Expand Down
Binary file added Tests/LibGfx/test-inputs/tiff/float32.tiff
Binary file not shown.
57 changes: 42 additions & 15 deletions Userland/Libraries/LibGfx/ImageFormats/TIFFLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,16 @@ class TIFFLoadingContext {
m_image_width = m_metadata.image_width().value();
if (m_metadata.predictor().has_value())
m_predictor = m_metadata.predictor().value();

// FIXME: Default-initialize fields with multiple values
for (u32 i = 0; i < m_bits_per_sample.size(); ++i)
m_sample_format.append(SampleFormat::Unsigned);
if (m_metadata.sample_format().has_value()) {
auto sample_format = *m_metadata.sample_format();
for (u32 i = 0; i < min(sample_format.size(), m_sample_format.size()); ++i)
m_sample_format[i] = sample_format[i];
}

m_alpha_channel_index = alpha_channel_index();
}

Expand Down Expand Up @@ -175,14 +185,30 @@ class TIFFLoadingContext {
BigEndian,
};

static ErrorOr<u8> read_component(BigEndianInputBitStream& stream, u8 bits)
ErrorOr<u8> read_component(BigEndianInputBitStream& stream, u8 component_index) const
{
// FIXME: This function truncates everything to 8-bits
auto const value = TRY(stream.read_bits<u32>(bits));

if (bits > 8)
return value >> (bits - 8);
return NumericLimits<u8>::max() * value / ((1 << bits) - 1);
auto const bits = (*m_metadata.bits_per_sample())[component_index];
auto value = TRY(stream.read_bits<u32>(bits));

switch (m_sample_format[component_index]) {
case SampleFormat::Unsigned:
if (bits > 8)
return value >> (bits - 8);
return NumericLimits<u8>::max() * value / ((1 << bits) - 1);
case SampleFormat::Float:
if (bits == 32) {
if (m_byte_order == ByteOrder::LittleEndian)
value = __builtin_bswap32(value);
auto const float_value = bit_cast<float>(value);
auto const to_u8_range = 255 * (*m_metadata.s_max_sample_value())[component_index] * (float_value - (*m_metadata.s_min_sample_value())[component_index]);
return round_to<u8>(to_u8_range);
}
return Error::from_string_literal("TIFFImageDecoderPlugin: Invalid bit depth for floating point value");
default:
return Error::from_string_literal("TIFFImageDecoderPlugin: Value of SampleFormat is not supported");
}
}

u8 samples_for_photometric_interpretation() const
Expand Down Expand Up @@ -228,9 +254,9 @@ class TIFFLoadingContext {

for (u8 i = number_base_channels; i < m_bits_per_sample.size(); ++i) {
if (m_alpha_channel_index == i)
alpha = TRY(read_component(stream, m_bits_per_sample[i]));
alpha = TRY(read_component(stream, i));
else
TRY(read_component(stream, m_bits_per_sample[i]));
TRY(read_component(stream, i));
}

return alpha.value_or(NumericLimits<u8>::max());
Expand All @@ -239,9 +265,9 @@ class TIFFLoadingContext {
ErrorOr<Color> read_color(BigEndianInputBitStream& stream)
{
if (m_photometric_interpretation == PhotometricInterpretation::RGB) {
auto const first_component = TRY(read_component(stream, m_bits_per_sample[0]));
auto const second_component = TRY(read_component(stream, m_bits_per_sample[1]));
auto const third_component = TRY(read_component(stream, m_bits_per_sample[2]));
auto const first_component = TRY(read_component(stream, 0));
auto const second_component = TRY(read_component(stream, 1));
auto const third_component = TRY(read_component(stream, 2));

auto const alpha = TRY(manage_extra_channels(stream));
return Color(first_component, second_component, third_component, alpha);
Expand Down Expand Up @@ -275,7 +301,7 @@ class TIFFLoadingContext {

if (m_photometric_interpretation == PhotometricInterpretation::WhiteIsZero
|| m_photometric_interpretation == PhotometricInterpretation::BlackIsZero) {
auto luminosity = TRY(read_component(stream, m_bits_per_sample[0]));
auto luminosity = TRY(read_component(stream, 0));

if (m_photometric_interpretation == PhotometricInterpretation::WhiteIsZero)
luminosity = ~luminosity;
Expand All @@ -291,10 +317,10 @@ class TIFFLoadingContext {
{
VERIFY(m_photometric_interpretation == PhotometricInterpretation::CMYK);

auto const first_component = TRY(read_component(stream, m_bits_per_sample[0]));
auto const second_component = TRY(read_component(stream, m_bits_per_sample[1]));
auto const third_component = TRY(read_component(stream, m_bits_per_sample[2]));
auto const fourth_component = TRY(read_component(stream, m_bits_per_sample[3]));
auto const first_component = TRY(read_component(stream, 0));
auto const second_component = TRY(read_component(stream, 1));
auto const third_component = TRY(read_component(stream, 2));
auto const fourth_component = TRY(read_component(stream, 3));

// FIXME: We probably won't encounter CMYK images with an alpha channel, but if
// we do: the first step to support them is not dropping the value here!
Expand Down Expand Up @@ -722,6 +748,7 @@ class TIFFLoadingContext {
Vector<u32, 4> m_bits_per_sample {};
u32 m_image_width {};
Predictor m_predictor {};
Vector<SampleFormat, 4> m_sample_format {};

Optional<u8> m_alpha_channel_index {};
};
Expand Down

0 comments on commit 55c7d00

Please sign in to comment.