Signed-off-by: Nikolay Sivov nsivov@codeweavers.com
-- v3: windowscodecs/tiff: Skip frames marked with SUBFILETYPE(0x1). windowscodecs/tests: Add some tests for multi-frame tiffs vs SUBFILETYPE tag.
From: Nikolay Sivov nsivov@codeweavers.com
This is important for RAW formats that use TIFF container, but mark directories as 'reduced-size'. Those files should be processed with appropriate RAW decoder, instead of displaying auxiliary frame with the TIFF decoder.
Unfortunately libtiff skips old tag OSUBFILETYPE == 0xff, it's not available via TIFFGetField() and SUBFILETYPE is not synthesized from it. On Windows both tags have the same effect on frames being skipped by the decoder.
Signed-off-by: Nikolay Sivov nsivov@codeweavers.com --- dlls/windowscodecs/libtiff.c | 58 ++++++++++++++++++++++++++- dlls/windowscodecs/tests/tiffformat.c | 9 ----- 2 files changed, 56 insertions(+), 11 deletions(-)
diff --git a/dlls/windowscodecs/libtiff.c b/dlls/windowscodecs/libtiff.c index 60c5ffe3404..2e62e42c7bf 100644 --- a/dlls/windowscodecs/libtiff.c +++ b/dlls/windowscodecs/libtiff.c @@ -161,6 +161,7 @@ struct tiff_decoder IStream *stream; TIFF *tiff; DWORD frame_count; + unsigned int *frame_map; DWORD cached_frame; tiff_decode_info cached_decode_info; INT cached_tile_x, cached_tile_y; @@ -576,6 +577,56 @@ static HRESULT tiff_get_decode_info(TIFF *tiff, tiff_decode_info *decode_info) return S_OK; }
+static bool tiff_decoder_should_skip_directory(TIFF *tiff) +{ + unsigned int value; + + if (TIFFGetField(tiff, TIFFTAG_SUBFILETYPE, &value)) + return value & FILETYPE_REDUCEDIMAGE; + + return false; +} + +static HRESULT tiff_decoder_collect_frames(struct tiff_decoder *decoder) +{ + unsigned int dir_count = TIFFNumberOfDirectories(decoder->tiff), frame_count, dir; + unsigned int *map; + + if (!dir_count) + return S_OK; + + if (!(map = calloc(dir_count, sizeof(*map)))) + return E_OUTOFMEMORY; + + frame_count = 0; + for (dir = 0; dir < dir_count; ++dir) + { + if (!TIFFSetDirectory(decoder->tiff, dir)) + { + free(map); + return E_FAIL; + } + + if (tiff_decoder_should_skip_directory(decoder->tiff)) continue; + + map[frame_count++] = dir; + } + + if (!frame_count) + { + free(map); + return WINCODEC_ERR_BADIMAGE; + } + + /* Back to the first useful directory. */ + TIFFSetDirectory(decoder->tiff, map[0]); + + decoder->frame_count = frame_count; + decoder->frame_map = map; + + return S_OK; +} + static HRESULT CDECL tiff_decoder_initialize(struct decoder* iface, IStream *stream, struct decoder_stat *st) { struct tiff_decoder *This = impl_from_decoder(iface); @@ -585,7 +636,9 @@ static HRESULT CDECL tiff_decoder_initialize(struct decoder* iface, IStream *str if (!This->tiff) return E_FAIL;
- This->frame_count = TIFFNumberOfDirectories(This->tiff); + if (FAILED(hr = tiff_decoder_collect_frames(This))) + goto fail; + This->cached_frame = 0; hr = tiff_get_decode_info(This->tiff, &This->cached_decode_info); if (FAILED(hr)) @@ -617,7 +670,7 @@ static HRESULT tiff_decoder_select_frame(struct tiff_decoder* This, DWORD frame)
prev_tile_size = This->cached_tile ? This->cached_decode_info.tile_size : 0;
- res = TIFFSetDirectory(This->tiff, frame); + res = TIFFSetDirectory(This->tiff, This->frame_map[frame]); if (!res) return E_INVALIDARG;
@@ -1143,6 +1196,7 @@ static void CDECL tiff_decoder_destroy(struct decoder* iface) struct tiff_decoder *This = impl_from_decoder(iface); if (This->tiff) TIFFClose(This->tiff); free(This->cached_tile); + free(This->frame_map); free(This); }
diff --git a/dlls/windowscodecs/tests/tiffformat.c b/dlls/windowscodecs/tests/tiffformat.c index d2348bdc97d..a83cf4ed64e 100644 --- a/dlls/windowscodecs/tests/tiffformat.c +++ b/dlls/windowscodecs/tests/tiffformat.c @@ -791,13 +791,7 @@ static void test_tiff_1bpp_palette(void)
hr = IWICBitmapDecoder_GetFrameCount(decoder, &count); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(count == test_data[i].count, "Unexpected count %u.\n", count); - if (count != test_data[i].count) - { - IWICBitmapDecoder_Release(decoder); - continue; - }
for (int j = 0; j < count; ++j) { @@ -825,10 +819,7 @@ static void test_tiff_1bpp_palette(void) tiff_set_field(&tiff, 2, TIFF_SUBFILETYPE, 0x1);
hr = create_decoder(&tiff, sizeof(tiff), &decoder); - todo_wine ok(FAILED(hr), "Unexpected hr %#lx.\n", hr); - if (SUCCEEDED(hr)) - IWICBitmapDecoder_Release(decoder); }
static void test_QueryCapability(void)
From: Nikolay Sivov nsivov@codeweavers.com
Signed-off-by: Nikolay Sivov nsivov@codeweavers.com --- dlls/windowscodecs/tests/tiffformat.c | 254 ++++++++++++++++++++++---- 1 file changed, 218 insertions(+), 36 deletions(-)
diff --git a/dlls/windowscodecs/tests/tiffformat.c b/dlls/windowscodecs/tests/tiffformat.c index 49323028857..d2348bdc97d 100644 --- a/dlls/windowscodecs/tests/tiffformat.c +++ b/dlls/windowscodecs/tests/tiffformat.c @@ -37,18 +37,24 @@ static void _expect_ref(IUnknown* obj, ULONG ref, int line) ok_(__FILE__,line)(rc == ref, "expected refcount %ld, got %ld\n", ref, rc); }
-#define IFD_BYTE 1 -#define IFD_ASCII 2 -#define IFD_SHORT 3 -#define IFD_LONG 4 -#define IFD_RATIONAL 5 -#define IFD_SBYTE 6 -#define IFD_UNDEFINED 7 -#define IFD_SSHORT 8 -#define IFD_SLONG 9 -#define IFD_SRATIONAL 10 -#define IFD_FLOAT 11 -#define IFD_DOUBLE 12 +enum tiff_constants +{ + IFD_BYTE = 1, + IFD_ASCII = 2, + IFD_SHORT = 3, + IFD_LONG = 4, + IFD_RATIONAL = 5, + IFD_SBYTE = 6, + IFD_UNDEFINED = 7, + IFD_SSHORT = 8, + IFD_SLONG = 9, + IFD_SRATIONAL = 10, + IFD_FLOAT = 11, + IFD_DOUBLE = 12, + + TIFF_SUBFILETYPE = 0xfe, + TIFF_OSUBFILETYPE = 0xff, +};
#pragma pack(push,2) struct IFD_entry @@ -139,39 +145,118 @@ static char *get_tiff_be(const void *src, size_t size) return dst; }
+struct tiff_header +{ + USHORT byte_order; + USHORT version; + ULONG dir_offset; +}; + +static void tiff_set_field(void *data, int dir, short id, int value) +{ + struct tiff_header *tiff = (struct tiff_header *)data; + unsigned int offset = tiff->dir_offset; + struct IFD_entry *ifd; + uint16_t count; + BYTE *p = data; + + while (dir) + { + count = *(uint16_t *)(p + offset); + offset = *(uint32_t *)(p + offset + sizeof(count) + count * sizeof(struct IFD_entry)); + --dir; + } + count = *(uint16_t *)(p + offset); + ifd = (struct IFD_entry *)(p + offset + sizeof(count)); + + for (int i = 0; i < count; ++i) + { + if (ifd->id == id) + { + ifd->value = value; + break; + } + ifd++; + } +}
static const struct tiff_1bpp_data { USHORT byte_order; USHORT version; ULONG dir_offset; - USHORT number_of_entries; - struct IFD_entry entry[13]; - ULONG next_IFD; + struct + { + USHORT number_of_entries; + struct IFD_entry entries[13]; + ULONG next_IFD; + } frames[3]; struct IFD_rational res; BYTE pixel_data[4]; } tiff_1bpp_data = { 'I' | 'I' << 8, 42, - FIELD_OFFSET(struct tiff_1bpp_data, number_of_entries), - 13, + FIELD_OFFSET(struct tiff_1bpp_data, frames), { - { 0xff, IFD_SHORT, 1, 0 }, /* SUBFILETYPE */ - { 0x100, IFD_LONG, 1, 1 }, /* IMAGEWIDTH */ - { 0x101, IFD_LONG, 1, 1 }, /* IMAGELENGTH */ - { 0x102, IFD_SHORT, 1, 1 }, /* BITSPERSAMPLE */ - { 0x103, IFD_SHORT, 1, 1 }, /* COMPRESSION: XP doesn't accept IFD_LONG here */ - { 0x106, IFD_SHORT, 1, 1 }, /* PHOTOMETRIC */ - { 0x111, IFD_LONG, 1, FIELD_OFFSET(struct tiff_1bpp_data, pixel_data) }, /* STRIPOFFSETS */ - { 0x115, IFD_SHORT, 1, 1 }, /* SAMPLESPERPIXEL */ - { 0x116, IFD_LONG, 1, 1 }, /* ROWSPERSTRIP */ - { 0x117, IFD_LONG, 1, 1 }, /* STRIPBYTECOUNT */ - { 0x11a, IFD_RATIONAL, 1, FIELD_OFFSET(struct tiff_1bpp_data, res) }, /* XRESOLUTION */ - { 0x11b, IFD_RATIONAL, 1, FIELD_OFFSET(struct tiff_1bpp_data, res) }, /* YRESOLUTION */ - { 0x128, IFD_SHORT, 1, 2 }, /* RESOLUTIONUNIT */ + { + ARRAY_SIZE(tiff_1bpp_data.frames[0].entries), + { + { 0xfe, IFD_LONG, 1, 0 }, /* SUBFILETYPE */ + { 0x100, IFD_LONG, 1, 1 }, /* IMAGEWIDTH */ + { 0x101, IFD_LONG, 1, 1 }, /* IMAGELENGTH */ + { 0x102, IFD_SHORT, 1, 1 }, /* BITSPERSAMPLE */ + { 0x103, IFD_SHORT, 1, 1 }, /* COMPRESSION: XP doesn't accept IFD_LONG here */ + { 0x106, IFD_SHORT, 1, 1 }, /* PHOTOMETRIC */ + { 0x111, IFD_LONG, 1, FIELD_OFFSET(struct tiff_1bpp_data, pixel_data) }, /* STRIPOFFSETS */ + { 0x115, IFD_SHORT, 1, 1 }, /* SAMPLESPERPIXEL */ + { 0x116, IFD_LONG, 1, 1 }, /* ROWSPERSTRIP */ + { 0x117, IFD_LONG, 1, 1 }, /* STRIPBYTECOUNT */ + { 0x11a, IFD_RATIONAL, 1, FIELD_OFFSET(struct tiff_1bpp_data, res) }, /* XRESOLUTION */ + { 0x11b, IFD_RATIONAL, 1, FIELD_OFFSET(struct tiff_1bpp_data, res) }, /* YRESOLUTION */ + { 0x128, IFD_SHORT, 1, 2 }, /* RESOLUTIONUNIT */ + }, + FIELD_OFFSET(struct tiff_1bpp_data, frames[1]), + }, + { + ARRAY_SIZE(tiff_1bpp_data.frames[1].entries), + { + { 0xfe, IFD_LONG, 1, 0 }, /* SUBFILETYPE */ + { 0x100, IFD_LONG, 1, 1 }, /* IMAGEWIDTH */ + { 0x101, IFD_LONG, 1, 1 }, /* IMAGELENGTH */ + { 0x102, IFD_SHORT, 1, 1 }, /* BITSPERSAMPLE */ + { 0x103, IFD_SHORT, 1, 1 }, /* COMPRESSION: XP doesn't accept IFD_LONG here */ + { 0x106, IFD_SHORT, 1, 1 }, /* PHOTOMETRIC */ + { 0x111, IFD_LONG, 1, FIELD_OFFSET(struct tiff_1bpp_data, pixel_data) + 1 }, /* STRIPOFFSETS */ + { 0x115, IFD_SHORT, 1, 1 }, /* SAMPLESPERPIXEL */ + { 0x116, IFD_LONG, 1, 1 }, /* ROWSPERSTRIP */ + { 0x117, IFD_LONG, 1, 1 }, /* STRIPBYTECOUNT */ + { 0x11a, IFD_RATIONAL, 1, FIELD_OFFSET(struct tiff_1bpp_data, res) }, /* XRESOLUTION */ + { 0x11b, IFD_RATIONAL, 1, FIELD_OFFSET(struct tiff_1bpp_data, res) }, /* YRESOLUTION */ + { 0x128, IFD_SHORT, 1, 2 }, /* RESOLUTIONUNIT */ + }, + FIELD_OFFSET(struct tiff_1bpp_data, frames[2]), + }, + { + ARRAY_SIZE(tiff_1bpp_data.frames[2].entries), + { + { 0xfe, IFD_LONG, 1, 0 }, /* SUBFILETYPE */ + { 0x100, IFD_LONG, 1, 1 }, /* IMAGEWIDTH */ + { 0x101, IFD_LONG, 1, 1 }, /* IMAGELENGTH */ + { 0x102, IFD_SHORT, 1, 1 }, /* BITSPERSAMPLE */ + { 0x103, IFD_SHORT, 1, 1 }, /* COMPRESSION: XP doesn't accept IFD_LONG here */ + { 0x106, IFD_SHORT, 1, 1 }, /* PHOTOMETRIC */ + { 0x111, IFD_LONG, 1, FIELD_OFFSET(struct tiff_1bpp_data, pixel_data) + 2 }, /* STRIPOFFSETS */ + { 0x115, IFD_SHORT, 1, 1 }, /* SAMPLESPERPIXEL */ + { 0x116, IFD_LONG, 1, 1 }, /* ROWSPERSTRIP */ + { 0x117, IFD_LONG, 1, 1 }, /* STRIPBYTECOUNT */ + { 0x11a, IFD_RATIONAL, 1, FIELD_OFFSET(struct tiff_1bpp_data, res) }, /* XRESOLUTION */ + { 0x11b, IFD_RATIONAL, 1, FIELD_OFFSET(struct tiff_1bpp_data, res) }, /* YRESOLUTION */ + { 0x128, IFD_SHORT, 1, 2 }, /* RESOLUTIONUNIT */ + }, + 0, + }, }, - 0, { 900, 3 }, { 0x11, 0x22, 0x33, 0 } }; @@ -338,7 +423,7 @@ static const struct tiff_24bpp_data { 'I' | 'I' << 8, 42, - FIELD_OFFSET(struct tiff_1bpp_data, number_of_entries), + FIELD_OFFSET(struct tiff_24bpp_data, number_of_entries), 13, { { 0xff, IFD_SHORT, 1, 0 }, /* SUBFILETYPE */ @@ -597,7 +682,7 @@ static HRESULT get_pixelformat_info(const GUID *format, UINT *bpp, UINT *channel static void dump_tiff(void *buf) { UINT count, i; - struct tiff_1bpp_data *tiff; + struct tiff_header *tiff; struct IFD_entry *tag;
tiff = buf; @@ -620,16 +705,36 @@ static void dump_tiff(void *buf)
static void test_tiff_1bpp_palette(void) { + static const struct + { + unsigned int dir_mask; + unsigned int count; + unsigned int colors; + } test_data[] = + { + { 0x1, 2, 0x3322 }, + { 0x2, 2, 0x3311 }, + { 0x4, 2, 0x2211 }, + { 0x3, 1, 0x33 }, + { 0x5, 1, 0x22 }, + }; HRESULT hr; IWICBitmapDecoder *decoder; IWICBitmapFrameDecode *frame; + struct tiff_1bpp_data tiff; IWICPalette *palette; GUID format; + UINT count; + DWORD data;
hr = create_decoder(&tiff_1bpp_data, sizeof(tiff_1bpp_data), &decoder); ok(hr == S_OK, "Failed to load TIFF image data %#lx\n", hr); if (hr != S_OK) return;
+ hr = IWICBitmapDecoder_GetFrameCount(decoder, &count); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(count == 3, "Unexpected count %u.\n", count); + hr = IWICBitmapDecoder_GetFrame(decoder, 0, &frame); ok(hr == S_OK, "GetFrame error %#lx\n", hr);
@@ -643,10 +748,87 @@ static void test_tiff_1bpp_palette(void) hr = IWICBitmapFrameDecode_CopyPalette(frame, palette); ok(hr == WINCODEC_ERR_PALETTEUNAVAILABLE, "expected WINCODEC_ERR_PALETTEUNAVAILABLE, got %#lx\n", hr); - IWICPalette_Release(palette); + + data = 0xfeffaadd; + hr = IWICBitmapFrameDecode_CopyPixels(frame, NULL, 4, 4, (BYTE *)&data); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(data == 0xfeffaa11, "Unexpected data %#lx.\n", data); + IWICBitmapFrameDecode_Release(frame); + + hr = IWICBitmapDecoder_GetFrame(decoder, 1, &frame); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + data = 0xfeffaadd; + hr = IWICBitmapFrameDecode_CopyPixels(frame, NULL, 4, 4, (BYTE *)&data); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(data == 0xfeffaa22, "Unexpected data %#lx.\n", data); IWICBitmapFrameDecode_Release(frame); + + hr = IWICBitmapDecoder_GetFrame(decoder, 2, &frame); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + data = 0xfeffaadd; + hr = IWICBitmapFrameDecode_CopyPixels(frame, NULL, 4, 4, (BYTE *)&data); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(data == 0xfeffaa33, "Unexpected data %#lx.\n", data); + IWICBitmapFrameDecode_Release(frame); + IWICBitmapDecoder_Release(decoder); + + /* Frames marked with SUBFILETYPE(0x1) are ignored. */ + + for (int i = 0; i < ARRAY_SIZE(test_data); ++i) + { + tiff = tiff_1bpp_data; + + for (int j = 0; j < 3; ++j) + { + if (test_data[i].dir_mask & (1 << j)) + tiff_set_field(&tiff, j, TIFF_SUBFILETYPE, 0x1); + } + + hr = create_decoder(&tiff, sizeof(tiff), &decoder); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IWICBitmapDecoder_GetFrameCount(decoder, &count); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine + ok(count == test_data[i].count, "Unexpected count %u.\n", count); + if (count != test_data[i].count) + { + IWICBitmapDecoder_Release(decoder); + continue; + } + + for (int j = 0; j < count; ++j) + { + BYTE color; + + hr = IWICBitmapDecoder_GetFrame(decoder, j, &frame); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + data = 0xfeffaadd; + hr = IWICBitmapFrameDecode_CopyPixels(frame, NULL, 4, 4, (BYTE *)&data); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + color = (test_data[i].colors >> 8 * j) & 0xff; + ok(data == (0xfeffaa00 | color), "Unexpected data %#lx.\n", data); + IWICBitmapFrameDecode_Release(frame); + } + + IWICBitmapDecoder_Release(decoder); + } + + /* Mark all the frames as reduced-size - image is bad. */ + tiff = tiff_1bpp_data; + + tiff_set_field(&tiff, 0, TIFF_SUBFILETYPE, 0x1); + tiff_set_field(&tiff, 1, TIFF_SUBFILETYPE, 0x1); + tiff_set_field(&tiff, 2, TIFF_SUBFILETYPE, 0x1); + + hr = create_decoder(&tiff, sizeof(tiff), &decoder); + todo_wine + ok(FAILED(hr), "Unexpected hr %#lx.\n", hr); + if (SUCCEEDED(hr)) + IWICBitmapDecoder_Release(decoder); }
static void test_QueryCapability(void) @@ -692,7 +874,7 @@ static void test_QueryCapability(void) frame_count = 0xdeadbeef; hr = IWICBitmapDecoder_GetFrameCount(decoder, &frame_count); ok(hr == S_OK, "GetFrameCount error %#lx\n", hr); - ok(frame_count == 1, "expected 1, got %u\n", frame_count); + ok(frame_count == 3, "expected 1, got %u\n", frame_count);
hr = IWICBitmapDecoder_GetFrame(decoder, 0, &frame); ok(hr == S_OK, "GetFrame error %#lx\n", hr); @@ -728,7 +910,7 @@ static void test_QueryCapability(void) frame_count = 0xdeadbeef; hr = IWICBitmapDecoder_GetFrameCount(decoder, &frame_count); ok(hr == S_OK, "GetFrameCount error %#lx\n", hr); - ok(frame_count == 1, "expected 1, got %u\n", frame_count); + ok(frame_count == 3, "expected 1, got %u\n", frame_count);
hr = IWICBitmapDecoder_GetFrame(decoder, 0, &frame); ok(hr == S_OK, "GetFrame error %#lx\n", hr);
v2: changed to non-specific FAILED() check in the tests, win10 is using COMPONENTNOTFOUND, win11 - BAD_IMAGE.
This merge request was approved by Esme Povirk.