[WINDOWSCODECS_WINETEST] Sync with Wine Staging 2.16. CORE-13762
authorAmine Khaldi <amine.khaldi@reactos.org>
Sun, 24 Sep 2017 11:19:20 +0000 (11:19 +0000)
committerAmine Khaldi <amine.khaldi@reactos.org>
Sun, 24 Sep 2017 11:19:20 +0000 (11:19 +0000)
svn path=/trunk/; revision=75954

rostests/winetests/windowscodecs/CMakeLists.txt
rostests/winetests/windowscodecs/converter.c
rostests/winetests/windowscodecs/metadata.c

index f84674f..c9a8518 100644 (file)
@@ -18,7 +18,7 @@ list(APPEND SOURCE
 
 add_executable(windowscodecs_winetest ${SOURCE})
 set_module_type(windowscodecs_winetest win32cui)
-add_importlibs(windowscodecs_winetest windowscodecs oleaut32 ole32 user32 gdi32 shlwapi msvcrt kernel32)
+add_importlibs(windowscodecs_winetest windowscodecs oleaut32 ole32 propsys user32 gdi32 shlwapi msvcrt kernel32)
 
 if(MSVC)
     add_importlibs(windowscodecs_winetest ntdll)
index b947b48..1f44513 100644 (file)
@@ -54,6 +54,9 @@ typedef struct BitmapTestSrc {
     const bitmap_data *data;
 } BitmapTestSrc;
 
+extern HRESULT STDMETHODCALLTYPE IWICBitmapFrameEncode_WriteSource_Proxy(IWICBitmapFrameEncode* This,
+    IWICBitmapSource *pIBitmapSource, WICRect *prc);
+
 static BOOL near_equal(float a, float b)
 {
     return fabsf(a - b) < 0.001;
@@ -631,6 +634,12 @@ static const WCHAR wszTiffCompressionMethod[] = {'T','i','f','f','C','o','m','p'
 static const WCHAR wszCompressionQuality[] = {'C','o','m','p','r','e','s','s','i','o','n','Q','u','a','l','i','t','y',0};
 static const WCHAR wszInterlaceOption[] = {'I','n','t','e','r','l','a','c','e','O','p','t','i','o','n',0};
 static const WCHAR wszFilterOption[] = {'F','i','l','t','e','r','O','p','t','i','o','n',0};
+static const WCHAR wszImageQuality[] = {'I','m','a','g','e','Q','u','a','l','i','t','y',0};
+static const WCHAR wszBitmapTransform[] = {'B','i','t','m','a','p','T','r','a','n','s','f','o','r','m',0};
+static const WCHAR wszLuminance[] = {'L','u','m','i','n','a','n','c','e',0};
+static const WCHAR wszChrominance[] = {'C','h','r','o','m','i','n','a','n','c','e',0};
+static const WCHAR wszJpegYCrCbSubsampling[] = {'J','p','e','g','Y','C','r','C','b','S','u','b','s','a','m','p','l','i','n','g',0};
+static const WCHAR wszSuppressApp0[] = {'S','u','p','p','r','e','s','s','A','p','p','0',0};
 
 static const struct property_opt_test_data testdata_tiff_props[] = {
     { wszTiffCompressionMethod, VT_UI1,         VT_UI1,  WICTiffCompressionDontCare },
@@ -644,6 +653,16 @@ static const struct property_opt_test_data testdata_png_props[] = {
     { NULL }
 };
 
+static const struct property_opt_test_data testdata_jpeg_props[] = {
+    { wszImageQuality,         VT_R4,           VT_EMPTY },
+    { wszBitmapTransform,      VT_UI1,          VT_UI1, WICBitmapTransformRotate0 },
+    { wszLuminance,            VT_I4|VT_ARRAY,  VT_EMPTY },
+    { wszChrominance,          VT_I4|VT_ARRAY,  VT_EMPTY },
+    { wszJpegYCrCbSubsampling, VT_UI1,          VT_UI1, WICJpegYCrCbSubsamplingDefault, 0.0f, TRUE }, /* not supported on XP/2k3 */
+    { wszSuppressApp0,         VT_BOOL,         VT_BOOL, FALSE },
+    { NULL }
+};
+
 static int find_property_index(const WCHAR* name, PROPBAG2* all_props, int all_prop_cnt)
 {
     int i;
@@ -768,6 +787,8 @@ static void test_encoder_properties(const CLSID* clsid_encoder, IPropertyBag2 *o
         test_specific_encoder_properties(options, testdata_tiff_props, all_props, cProperties2);
     else if (IsEqualCLSID(clsid_encoder, &CLSID_WICPngEncoder))
         test_specific_encoder_properties(options, testdata_png_props, all_props, cProperties2);
+    else if (IsEqualCLSID(clsid_encoder, &CLSID_WICJpegEncoder))
+        test_specific_encoder_properties(options, testdata_jpeg_props, all_props, cProperties2);
 
     for (i=0; i < cProperties2; i++)
     {
@@ -1369,6 +1390,13 @@ static void test_multi_encoder(const struct bitmap_data **srcs, const CLSID* cls
                 i++;
             }
 
+            if (clsid_decoder == NULL)
+            {
+                IStream_Release(stream);
+                IWICBitmapEncoder_Release(encoder);
+                return;
+            }
+
             if (SUCCEEDED(hr))
             {
                 hr = IWICBitmapEncoder_Commit(encoder);
@@ -1869,6 +1897,9 @@ if (!strcmp(winetest_platform, "windows")) /* FIXME: enable once implemented in
     test_encoder(&testdata_24bppBGR, &CLSID_WICTiffEncoder,
                  &testdata_24bppBGR, &CLSID_WICTiffDecoder, "TIFF encoder 24bppBGR");
 
+    test_encoder(&testdata_24bppBGR, &CLSID_WICJpegEncoder,
+                 &testdata_24bppBGR, NULL, "JPEG encoder 24bppBGR");
+
     test_multi_encoder(multiple_frames, &CLSID_WICTiffEncoder,
                        multiple_frames, &CLSID_WICTiffDecoder, NULL, NULL, "TIFF encoder multi-frame", NULL);
 
index 29a20dc..f470361 100644 (file)
 
 #include <windef.h>
 #include <winbase.h>
+#include <winnls.h>
+#include <winreg.h>
 #include <ole2.h>
 //#include "wincodec.h"
 #include <wincodecsdk.h>
+#include <propvarutil.h>
 #include <wine/test.h>
 
+#include <initguid.h>
+DEFINE_GUID(IID_MdbrUnknown, 0x00240e6f,0x3f23,0x4432,0xb0,0xcc,0x48,0xd5,0xbb,0xff,0x6c,0x36);
+
 #define expect_blob(propvar, data, length) do { \
     ok((propvar).vt == VT_BLOB, "unexpected vt: %i\n", (propvar).vt); \
     if ((propvar).vt == VT_BLOB) { \
@@ -1552,13 +1558,207 @@ static void test_metadata_gif(void)
         IWICMetadataBlockReader_Release(blockreader);
     }
 
-    hr = IWICBitmapFrameDecode_GetMetadataQueryReader(frame, &queryreader);
-    ok(hr == S_OK ||
-            broken(hr == WINCODEC_ERR_UNSUPPORTEDOPERATION) /* before Vista */,
-            "GetMetadataQueryReader failed: %08x\n", hr);
+    hr = IWICBitmapDecoder_GetMetadataQueryReader(decoder, &queryreader);
+    ok(hr == S_OK || broken(hr == WINCODEC_ERR_UNSUPPORTEDOPERATION) /* before Vista */,
+       "GetMetadataQueryReader error %#x\n", hr);
+    if (SUCCEEDED(hr))
+    {
+        static const struct
+        {
+            const char *query;
+            HRESULT hr;
+            UINT vt;
+        } decoder_data[] =
+        {
+            { "/logscrdesc/Signature", S_OK, VT_UI1 | VT_VECTOR },
+            { "/[0]logscrdesc/Signature", S_OK, VT_UI1 | VT_VECTOR },
+            { "/logscrdesc/\\Signature", S_OK, VT_UI1 | VT_VECTOR },
+            { "/Logscrdesc/\\signature", S_OK, VT_UI1 | VT_VECTOR },
+            { "/logscrdesc/{str=signature}", S_OK, VT_UI1 | VT_VECTOR },
+            { "/[0]logscrdesc/{str=signature}", S_OK, VT_UI1 | VT_VECTOR },
+            { "/logscrdesc/{wstr=signature}", S_OK, VT_UI1 | VT_VECTOR },
+            { "/[0]logscrdesc/{wstr=signature}", S_OK, VT_UI1 | VT_VECTOR },
+            { "/appext/Application", S_OK, VT_UI1 | VT_VECTOR },
+            { "/appext/{STR=APPlication}", S_OK, VT_UI1 | VT_VECTOR },
+            { "/appext/{WSTR=APPlication}", S_OK, VT_UI1 | VT_VECTOR },
+            { "/LogSCRdesC", S_OK, VT_UNKNOWN },
+            { "/[0]LogSCRdesC", S_OK, VT_UNKNOWN },
+            { "/appEXT", S_OK, VT_UNKNOWN },
+            { "/[0]appEXT", S_OK, VT_UNKNOWN },
+            { "grctlext", WINCODEC_ERR_PROPERTYNOTSUPPORTED, 0 },
+            { "/imgdesc", WINCODEC_ERR_PROPERTYNOTFOUND, 0 },
+        };
+        static const WCHAR rootW[] = {'/',0};
+        WCHAR name[256];
+        UINT len, i, j;
+        PROPVARIANT value;
+        IWICMetadataQueryReader *meta_reader;
+
+        hr = IWICMetadataQueryReader_GetContainerFormat(queryreader, &format);
+        ok(hr == S_OK, "GetContainerFormat error %#x\n", hr);
+        ok(IsEqualGUID(&format, &GUID_ContainerFormatGif),
+           "wrong container format %s\n", wine_dbgstr_guid(&format));
+
+        name[0] = 0;
+        len = 0xdeadbeef;
+        hr = IWICMetadataQueryReader_GetLocation(queryreader, 256, name, &len);
+        ok(hr == S_OK, "GetLocation error %#x\n", hr);
+        ok(len == 2, "expected 2, got %u\n", len);
+        ok(!lstrcmpW(name, rootW), "expected '/', got %s\n", wine_dbgstr_w(name));
+
+        for (i = 0; i < sizeof(decoder_data)/sizeof(decoder_data[0]); i++)
+        {
+            WCHAR queryW[256];
+
+            if (winetest_debug > 1)
+                trace("query: %s\n", decoder_data[i].query);
+            MultiByteToWideChar(CP_ACP, 0, decoder_data[i].query, -1, queryW, 256);
+            PropVariantInit(&value);
+            hr = IWICMetadataQueryReader_GetMetadataByName(queryreader, queryW, &value);
+            ok(hr == decoder_data[i].hr, "GetMetadataByName(%s) returned %#x, expected %#x\n", wine_dbgstr_w(queryW), hr, decoder_data[i].hr);
+            ok(value.vt == decoder_data[i].vt, "expected %#x, got %#x\n", decoder_data[i].vt, value.vt);
+            if (hr == S_OK && value.vt == VT_UNKNOWN)
+            {
+                hr = IUnknown_QueryInterface(value.punkVal, &IID_IWICMetadataQueryReader, (void **)&meta_reader);
+                ok(hr == S_OK, "QueryInterface error %#x\n", hr);
+
+                name[0] = 0;
+                len = 0xdeadbeef;
+                hr = IWICMetadataQueryReader_GetLocation(meta_reader, 256, name, &len);
+                ok(hr == S_OK, "GetLocation error %#x\n", hr);
+                ok(len == lstrlenW(queryW) + 1, "expected %u, got %u\n", lstrlenW(queryW) + 1, len);
+                ok(!lstrcmpW(name, queryW), "expected %s, got %s\n", wine_dbgstr_w(queryW), wine_dbgstr_w(name));
+
+                for (j = 0; j < sizeof(decoder_data)/sizeof(decoder_data[0]); j++)
+                {
+                    MultiByteToWideChar(CP_ACP, 0, decoder_data[j].query, -1, queryW, 256);
+
+                    if (CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, queryW, len-1, name, len-1) == CSTR_EQUAL && decoder_data[j].query[len - 1] != 0)
+                    {
+                        if (winetest_debug > 1)
+                            trace("query: %s\n", wine_dbgstr_w(queryW + len - 1));
+                        PropVariantClear(&value);
+                        hr = IWICMetadataQueryReader_GetMetadataByName(meta_reader, queryW + len - 1, &value);
+                        ok(hr == decoder_data[j].hr, "GetMetadataByName(%s) returned %#x, expected %#x\n", wine_dbgstr_w(queryW + len - 1), hr, decoder_data[j].hr);
+                        ok(value.vt == decoder_data[j].vt, "expected %#x, got %#x\n", decoder_data[j].vt, value.vt);
+                    }
+                }
+
+                IWICMetadataQueryReader_Release(meta_reader);
+            }
+
+            PropVariantClear(&value);
+        }
+
+        IWICMetadataQueryReader_Release(queryreader);
+    }
 
+    hr = IWICBitmapFrameDecode_GetMetadataQueryReader(frame, &queryreader);
+    ok(hr == S_OK || broken(hr == WINCODEC_ERR_UNSUPPORTEDOPERATION) /* before Vista */,
+       "GetMetadataQueryReader error %#x\n", hr);
     if (SUCCEEDED(hr))
     {
+        static const struct
+        {
+            const char *query;
+            HRESULT hr;
+            UINT vt;
+        } frame_data[] =
+        {
+            { "/grctlext/Delay", S_OK, VT_UI2 },
+            { "/[0]grctlext/Delay", S_OK, VT_UI2 },
+            { "/grctlext/{str=delay}", S_OK, VT_UI2 },
+            { "/[0]grctlext/{str=delay}", S_OK, VT_UI2 },
+            { "/grctlext/{wstr=delay}", S_OK, VT_UI2 },
+            { "/[0]grctlext/{wstr=delay}", S_OK, VT_UI2 },
+            { "/imgdesc/InterlaceFlag", S_OK, VT_BOOL },
+            { "/imgdesc/{STR=interlaceFLAG}", S_OK, VT_BOOL },
+            { "/imgdesc/{WSTR=interlaceFLAG}", S_OK, VT_BOOL },
+            { "/grctlext", S_OK, VT_UNKNOWN },
+            { "/[0]grctlext", S_OK, VT_UNKNOWN },
+            { "/imgdesc", S_OK, VT_UNKNOWN },
+            { "/[0]imgdesc", S_OK, VT_UNKNOWN },
+            { "/LogSCRdesC", WINCODEC_ERR_PROPERTYNOTFOUND, 0 },
+            { "/appEXT", WINCODEC_ERR_PROPERTYNOTFOUND, 0 },
+            { "/grctlext/{\\str=delay}", WINCODEC_ERR_WRONGSTATE, 0 },
+            { "/grctlext/{str=\\delay}", S_OK, VT_UI2 },
+            { "grctlext/Delay", WINCODEC_ERR_PROPERTYNOTSUPPORTED, 0 },
+        };
+        static const WCHAR rootW[] = {'/',0};
+        static const WCHAR guidW[] = {'/','{','g','u','i','d','=','\\',0};
+        static const WCHAR imgdescW[] = {'i','m','g','d','e','s','c',0};
+        static const WCHAR ImgDescW[] = {'I','m','g','D','e','s','c',0};
+        WCHAR name[256], queryW[256];
+        UINT len, i;
+        PROPVARIANT value;
+        IWICMetadataQueryReader *meta_reader;
+
+        hr = IWICMetadataQueryReader_GetContainerFormat(queryreader, &format);
+        ok(hr == S_OK, "GetContainerFormat error %#x\n", hr);
+        ok(IsEqualGUID(&format, &GUID_ContainerFormatGif),
+           "wrong container format %s\n", wine_dbgstr_guid(&format));
+
+        name[0] = 0;
+        len = 0xdeadbeef;
+        hr = IWICMetadataQueryReader_GetLocation(queryreader, 256, name, &len);
+        ok(hr == S_OK, "GetLocation error %#x\n", hr);
+        ok(len == 2, "expected 2, got %u\n", len);
+        ok(!lstrcmpW(name, rootW), "expected '/', got %s\n", wine_dbgstr_w(name));
+
+        for (i = 0; i < sizeof(frame_data)/sizeof(frame_data[0]); i++)
+        {
+            if (winetest_debug > 1)
+                trace("query: %s\n", frame_data[i].query);
+            MultiByteToWideChar(CP_ACP, 0, frame_data[i].query, -1, queryW, 256);
+            PropVariantInit(&value);
+            hr = IWICMetadataQueryReader_GetMetadataByName(queryreader, queryW, &value);
+            ok(hr == frame_data[i].hr, "GetMetadataByName(%s) returned %#x, expected %#x\n", wine_dbgstr_w(queryW), hr, frame_data[i].hr);
+            ok(value.vt == frame_data[i].vt, "expected %#x, got %#x\n", frame_data[i].vt, value.vt);
+            if (hr == S_OK && value.vt == VT_UNKNOWN)
+            {
+                hr = IUnknown_QueryInterface(value.punkVal, &IID_IWICMetadataQueryReader, (void **)&meta_reader);
+                ok(hr == S_OK, "QueryInterface error %#x\n", hr);
+
+                name[0] = 0;
+                len = 0xdeadbeef;
+                hr = IWICMetadataQueryReader_GetLocation(meta_reader, 256, name, &len);
+                ok(hr == S_OK, "GetLocation error %#x\n", hr);
+                ok(len == lstrlenW(queryW) + 1, "expected %u, got %u\n", lstrlenW(queryW) + 1, len);
+                ok(!lstrcmpW(name, queryW), "expected %s, got %s\n", wine_dbgstr_w(queryW), wine_dbgstr_w(name));
+
+                IWICMetadataQueryReader_Release(meta_reader);
+            }
+
+            PropVariantClear(&value);
+        }
+
+        name[0] = 0;
+        len = 0xdeadbeef;
+        hr = WICMapGuidToShortName(&GUID_MetadataFormatIMD, 256, name, &len);
+        ok(hr == S_OK, "WICMapGuidToShortName error %#x\n", hr);
+        ok(!lstrcmpW(name, imgdescW), "wrong short name %s\n", wine_dbgstr_w(name));
+
+        format = GUID_NULL;
+        hr = WICMapShortNameToGuid(imgdescW, &format);
+        ok(hr == S_OK, "WICMapGuidToShortName error %#x\n", hr);
+        ok(IsEqualGUID(&format, &GUID_MetadataFormatIMD), "wrong guid %s\n", wine_dbgstr_guid(&format));
+
+        format = GUID_NULL;
+        hr = WICMapShortNameToGuid(ImgDescW, &format);
+        ok(hr == S_OK, "WICMapGuidToShortName error %#x\n", hr);
+        ok(IsEqualGUID(&format, &GUID_MetadataFormatIMD), "wrong guid %s\n", wine_dbgstr_guid(&format));
+
+        lstrcpyW(queryW, guidW);
+        StringFromGUID2(&GUID_MetadataFormatIMD, queryW + lstrlenW(queryW) - 1, 39);
+        memcpy(queryW, guidW, sizeof(guidW) - 2);
+        if (winetest_debug > 1)
+            trace("query: %s\n", wine_dbgstr_w(queryW));
+        PropVariantInit(&value);
+        hr = IWICMetadataQueryReader_GetMetadataByName(queryreader, queryW, &value);
+        ok(hr == S_OK, "GetMetadataByName(%s) error %#x\n", wine_dbgstr_w(queryW), hr);
+        ok(value.vt == VT_UNKNOWN, "expected VT_UNKNOWN, got %#x\n", value.vt);
+        PropVariantClear(&value);
+
         IWICMetadataQueryReader_Release(queryreader);
     }
 
@@ -2252,10 +2452,610 @@ static void test_WICMapSchemaToName(void)
     }
 }
 
+struct metadata_item
+{
+    const char *schema, *id_str;
+    UINT id, type, value;
+};
+
+struct metadata_block
+{
+    const GUID *metadata_format;
+    UINT count;
+    const struct metadata_item *item;
+};
+
+struct metadata
+{
+    const GUID *container_format;
+    UINT count;
+    const struct metadata_block *block;
+};
+
+static const struct metadata *current_metadata;
+static const struct metadata_block *current_metadata_block;
+
+static char the_best[] = "The Best";
+static char the_worst[] = "The Worst";
+
+static HRESULT WINAPI mdr_QueryInterface(IWICMetadataReader *iface, REFIID iid, void **out)
+{
+    trace("%p,%s,%p\n", iface, wine_dbgstr_guid(iid), out);
+
+    if (IsEqualIID(iid, &IID_IUnknown) ||
+        IsEqualIID(iid, &IID_IWICMetadataReader))
+    {
+        *out = iface;
+        return S_OK;
+    }
+
+    ok(0, "unknown iid %s\n", wine_dbgstr_guid(iid));
+
+    *out = NULL;
+    return E_NOINTERFACE;
+}
+
+static ULONG WINAPI mdr_AddRef(IWICMetadataReader *iface)
+{
+    return 2;
+}
+
+static ULONG WINAPI mdr_Release(IWICMetadataReader *iface)
+{
+    return 1;
+}
+
+static HRESULT WINAPI mdr_GetMetadataFormat(IWICMetadataReader *iface, GUID *format)
+{
+    trace("%p,%p\n", iface, format);
+
+    ok(current_metadata_block != NULL, "current_metadata_block can't be NULL\n");
+    if (!current_metadata_block) return E_POINTER;
+
+    *format = *current_metadata_block->metadata_format;
+    return S_OK;
+}
+
+static HRESULT WINAPI mdr_GetMetadataHandlerInfo(IWICMetadataReader *iface, IWICMetadataHandlerInfo **handler)
+{
+    ok(0, "not implemented\n");
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI mdr_GetCount(IWICMetadataReader *iface, UINT *count)
+{
+    trace("%p,%p\n", iface, count);
+
+    ok(current_metadata_block != NULL, "current_metadata_block can't be NULL\n");
+    if (!current_metadata_block) return E_POINTER;
+
+    *count = current_metadata_block->count;
+    return S_OK;
+}
+
+static HRESULT WINAPI mdr_GetValueByIndex(IWICMetadataReader *iface, UINT index, PROPVARIANT *schema, PROPVARIANT *id, PROPVARIANT *value)
+{
+    ok(0, "not implemented\n");
+    return E_NOTIMPL;
+}
+
+static char *get_temp_buffer(int size)
+{
+    static char buf[16][256];
+    static int idx;
+    char *p;
+
+    assert(size < 256);
+
+    p = buf[idx & 0x0f];
+    idx++;
+    return p;
+}
+
+static const char *wine_dbgstr_propvariant(const PROPVARIANT *var)
+{
+    char *ret;
+
+    if (!var) return "(null)";
+
+    switch (var->vt)
+    {
+    case VT_LPWSTR:
+        ret = get_temp_buffer(lstrlenW(U(*var).pwszVal) + 16);
+        sprintf(ret, "(VT_LPWSTR:%s)", wine_dbgstr_w(U(*var).pwszVal));
+        break;
+
+    case VT_LPSTR:
+        ret = get_temp_buffer(lstrlenA(U(*var).pszVal) + 16);
+        sprintf(ret, "(VT_LPSTR:%s)", U(*var).pszVal);
+        break;
+
+    default:
+        ret = get_temp_buffer(16);
+        sprintf(ret, "(vt:%u)", var->vt);
+        break;
+    }
+
+    return ret;
+}
+
+static int propvar_cmp(const PROPVARIANT *v1, LONGLONG value2)
+{
+    LONGLONG value1;
+
+    if (PropVariantToInt64(v1, &value1) != S_OK) return -1;
+
+    value1 -= value2;
+    if (value1) return value1 < 0 ? -1 : 1;
+    return 0;
+}
+
+static HRESULT WINAPI mdr_GetValue(IWICMetadataReader *iface, const PROPVARIANT *schema, const PROPVARIANT *id, PROPVARIANT *value)
+{
+    UINT i;
+
+    trace("%p,%s,%s,%s\n", iface, wine_dbgstr_propvariant(schema), wine_dbgstr_propvariant(id), wine_dbgstr_propvariant(value));
+
+    ok(current_metadata_block != NULL, "current_metadata_block can't be NULL\n");
+    if (!current_metadata_block) return E_POINTER;
+
+    ok(schema != NULL && id != NULL && value != NULL, "%p, %p, %p should not be NULL\n", schema, id, value);
+
+    for (i = 0; i < current_metadata_block->count; i++)
+    {
+        if (schema->vt != VT_EMPTY)
+        {
+            if (!current_metadata_block->item[i].schema)
+                continue;
+
+            switch (schema->vt)
+            {
+            case VT_LPSTR:
+                if (lstrcmpA(U(*schema).pszVal, current_metadata_block->item[i].schema) != 0)
+                    continue;
+                break;
+
+            case VT_LPWSTR:
+            {
+                char schemaA[256];
+                WideCharToMultiByte(CP_ACP, 0, U(*schema).pwszVal, -1, schemaA, sizeof(schemaA), NULL, NULL);
+                if (lstrcmpA(schemaA, current_metadata_block->item[i].schema) != 0)
+                    continue;
+                break;
+            }
+
+            default:
+                ok(0, "unsupported schema vt %u\n", schema->vt);
+                continue;
+            }
+        }
+        else if (current_metadata_block->item[i].schema)
+            continue;
+
+        switch (id->vt)
+        {
+        case VT_LPSTR:
+            if (current_metadata_block->item[i].id_str)
+            {
+                if (!lstrcmpA(U(*id).pszVal, current_metadata_block->item[i].id_str))
+                {
+                    value->vt = VT_LPSTR;
+                    U(*value).pszVal = the_best;
+                    return S_OK;
+                }
+                break;
+            }
+            break;
+
+        case VT_LPWSTR:
+            if (current_metadata_block->item[i].id_str)
+            {
+                char idA[256];
+                WideCharToMultiByte(CP_ACP, 0, U(*id).pwszVal, -1, idA, sizeof(idA), NULL, NULL);
+                if (!lstrcmpA(idA, current_metadata_block->item[i].id_str))
+                {
+                    value->vt = VT_LPSTR;
+                    U(*value).pszVal = the_worst;
+                    return S_OK;
+                }
+                break;
+            }
+            break;
+
+        case VT_CLSID:
+            if (IsEqualGUID(U(*id).puuid, &GUID_MetadataFormatXMP) ||
+                IsEqualGUID(U(*id).puuid, &GUID_ContainerFormatTiff))
+            {
+                value->vt = VT_UNKNOWN;
+                value->punkVal = (IUnknown *)iface;
+                return S_OK;
+            }
+            break;
+
+        default:
+            if (!propvar_cmp(id, current_metadata_block->item[i].id))
+            {
+                value->vt = current_metadata_block->item[i].type;
+                U(*value).uiVal = current_metadata_block->item[i].value;
+                return S_OK;
+            }
+            break;
+        }
+    }
+
+    return 0xdeadbeef;
+}
+
+static HRESULT WINAPI mdr_GetEnumerator(IWICMetadataReader *iface, IWICEnumMetadataItem **enumerator)
+{
+    ok(0, "not implemented\n");
+    return E_NOTIMPL;
+}
+
+static /* const */ IWICMetadataReaderVtbl mdr_vtbl =
+{
+    mdr_QueryInterface,
+    mdr_AddRef,
+    mdr_Release,
+    mdr_GetMetadataFormat,
+    mdr_GetMetadataHandlerInfo,
+    mdr_GetCount,
+    mdr_GetValueByIndex,
+    mdr_GetValue,
+    mdr_GetEnumerator
+};
+
+static IWICMetadataReader mdr = { &mdr_vtbl };
+
+static HRESULT WINAPI mdbr_QueryInterface(IWICMetadataBlockReader *iface, REFIID iid, void **out)
+{
+    if (IsEqualIID(iid, &IID_IUnknown) ||
+        IsEqualIID(iid, &IID_IWICMetadataBlockReader))
+    {
+        *out = iface;
+        return S_OK;
+    }
+
+    /* Windows 8/10 query for some undocumented IID */
+    if (!IsEqualIID(iid, &IID_MdbrUnknown))
+        ok(0, "unknown iid %s\n", wine_dbgstr_guid(iid));
+
+    *out = NULL;
+    return E_NOINTERFACE;
+}
+
+static ULONG WINAPI mdbr_AddRef(IWICMetadataBlockReader *iface)
+{
+    return 2;
+}
+
+static ULONG WINAPI mdbr_Release(IWICMetadataBlockReader *iface)
+{
+    return 1;
+}
+
+static HRESULT WINAPI mdbr_GetContainerFormat(IWICMetadataBlockReader *iface, GUID *format)
+{
+    trace("%p,%p\n", iface, format);
+
+    ok(current_metadata != NULL, "current_metadata can't be NULL\n");
+    if (!current_metadata) return E_POINTER;
+
+    *format = *current_metadata->container_format;
+    return S_OK;
+}
+
+static HRESULT WINAPI mdbr_GetCount(IWICMetadataBlockReader *iface, UINT *count)
+{
+    trace("%p,%p\n", iface, count);
+
+    ok(current_metadata != NULL, "current_metadata can't be NULL\n");
+    if (!current_metadata) return E_POINTER;
+
+    *count = current_metadata->count;
+    return S_OK;
+}
+
+static HRESULT WINAPI mdbr_GetReaderByIndex(IWICMetadataBlockReader *iface, UINT index, IWICMetadataReader **out)
+{
+    trace("%p,%u,%p\n", iface, index, out);
+
+    *out = NULL;
+
+    ok(current_metadata != NULL, "current_metadata can't be NULL\n");
+    if (!current_metadata) return E_POINTER;
+
+    if (index < current_metadata->count)
+    {
+        current_metadata_block = &current_metadata->block[index];
+        *out = &mdr;
+        return S_OK;
+    }
+
+    current_metadata_block = NULL;
+    return E_INVALIDARG;
+}
+
+static HRESULT WINAPI mdbr_GetEnumerator(IWICMetadataBlockReader *iface, IEnumUnknown **enumerator)
+{
+    ok(0, "not implemented\n");
+    return E_NOTIMPL;
+}
+
+static /* const */ IWICMetadataBlockReaderVtbl mdbr_vtbl =
+{
+    mdbr_QueryInterface,
+    mdbr_AddRef,
+    mdbr_Release,
+    mdbr_GetContainerFormat,
+    mdbr_GetCount,
+    mdbr_GetReaderByIndex,
+    mdbr_GetEnumerator
+};
+
+static IWICMetadataBlockReader mdbr = { &mdbr_vtbl };
+
+static const char xmp[] = "http://ns.adobe.com/xap/1.0/";
+static const char dc[] = "http://purl.org/dc/elements/1.1/";
+static const char tiff[] = "http://ns.adobe.com/tiff/1.0/";
+
+static const struct metadata_item item1[] =
+{
+    { NULL, NULL, 1, 2, 3 }
+};
+
+static const struct metadata_item item2[] =
+{
+    { NULL, NULL, 1, 2, 3 },
+    { "xmp", "Rating", 4, 5, 6 },
+    { NULL, "Rating", 7, 8, 9 }
+};
+
+static const struct metadata_item item3[] =
+{
+    { NULL, NULL, 1, 2, 3 },
+    { NULL, NULL, 4, 5, 6 },
+    { NULL, NULL, 7, 8, 9 },
+    { NULL, NULL, 10, 11, 12 }
+};
+
+static const struct metadata_item item4[] =
+{
+    { NULL, NULL, 1, 2, 3 },
+    { xmp, "Rating", 4, 5, 6 },
+    { dc, NULL, 7, 8, 9 },
+    { tiff, NULL, 10, 11, 12 },
+    { NULL, "RATING", 13, 14, 15 },
+    { NULL, "R}ATING", 16, 17, 18 },
+    { NULL, "xmp", 19, 20, 21 }
+};
+
+static const struct metadata_block block1[] =
+{
+    { &GUID_MetadataFormatIfd, 1, item1 }
+};
+
+static const struct metadata_block block2[] =
+{
+    { &GUID_MetadataFormatXMP, 1, item1 },
+    { &GUID_MetadataFormatIfd, 3, item2 }
+};
+
+static const struct metadata_block block3[] =
+{
+    { &GUID_MetadataFormatXMP, 1, item1 },
+    { &GUID_MetadataFormatIfd, 3, item2 },
+    { &GUID_MetadataFormatXMP, 4, item3 },
+    { &GUID_MetadataFormatXMP, 7, item4 },
+    { &GUID_MetadataFormatIfd, 7, item4 }
+};
+
+static const struct metadata data1 =
+{
+    &GUID_ContainerFormatGif,
+    1, block1
+};
+
+static const struct metadata data2 =
+{
+    &GUID_ContainerFormatTiff,
+    2, block2
+};
+
+static const struct metadata data3 =
+{
+    &GUID_ContainerFormatPng,
+    5, block3
+};
+
+static void test_queryreader(void)
+{
+    static const char q1[] = "/ifd/{uchar=1}";
+    static const char q2[] = "/ifd/xmp:{long=4}";
+    static const char q3[] = "/ifd/{str=xmp}:{uint=4}";
+    static const char q4[] = "/xmp/{char=7}";
+    static const char q5[] = "/[1]xmp/{short=7}";
+    static const char q6[] = "/[1]ifd/{str=dc}:{uint=7}";
+    static const char q7[] = "/[1]ifd/{str=http://purl.org/dc/elements/1.1/}:{longlong=7}";
+    static const char q8[] = "/[1]ifd/{str=http://ns.adobe.com/tiff/1.0/}:{int=10}";
+    static const char q9[] = "/[2]xmp/xmp:{ulong=4}";
+    static const char q10[] = "/[2]xmp/{str=xmp}:{ulong=4}";
+    static const char q11[] = "/xmp";
+    static const char q12[] = "/ifd/xmp";
+    static const char q13[] = "/ifd/xmp/tiff";
+    static const char q14[] = "/[0]ifd/[0]xmp/[0]tiff";
+    static const char q15[] = "/[*]xmp";
+
+    static const char q20[] = "/ifd/\\Rating";
+    static const char q21[] = "/[0]ifd/Rating";
+    static const char q22[] = "/[2]xmp/xmp:{str=Rating}";
+    static const char q23[] = "/[2]xmp/xmp:Rating";
+
+    static const char q24[] = "/[1]ifd/{str=http://ns.adobe.com/xap/1.0/}:Rating";
+    static const char q25[] = "/[1]ifd/{str=http://ns.adobe.com/xap/1.0/}:{str=Rating}";
+    static const char q26[] = "/[1]ifd/{wstr=\\RATING}";
+    static const char q27[] = "/[1]ifd/{str=R\\ATING}";
+    static const char q28[] = "/[1]ifd/{str=R\\}ATING}";
+
+    static const char q40[] = "[0]/ifd/Rating";
+    static const char q41[] = "/[+1]ifd/Rating";
+    static const char q42[] = "/[-1]ifd/Rating";
+    static const char q43[] = "/ifd/{\\str=Rating}";
+    static const char q44[] = "/ifd/{badtype=0}";
+    static const char q45[] = "/ifd/{uint=0x1234}";
+    static const char q46[] = "/ifd/[0]Rating";
+    static const char q47[] = "/ifd/[*]Rating";
+    static const struct
+    {
+        BOOL todo;
+        const struct metadata *data;
+        const char *query;
+        HRESULT hr;
+        UINT vt, value;
+        const char *str_value;
+    } test_data[] =
+    {
+        { FALSE, &data1, q1, S_OK, 2, 3, NULL },
+        { FALSE, &data2, q2, S_OK, 5, 6, NULL },
+        { FALSE, &data2, q3, S_OK, 5, 6, NULL },
+        { FALSE, &data3, q4, 0xdeadbeef },
+        { FALSE, &data3, q5, S_OK, 8, 9, NULL },
+        { FALSE, &data3, q6, 0xdeadbeef },
+        { FALSE, &data3, q7, S_OK, 8, 9, NULL },
+        { FALSE, &data3, q8, S_OK, 11, 12, NULL },
+        { FALSE, &data3, q9, S_OK, 5, 6, NULL },
+        { FALSE, &data3, q10, 0xdeadbeef },
+
+        { FALSE, &data3, q11, S_OK, VT_UNKNOWN, 0, NULL },
+        { FALSE, &data3, q12, S_OK, VT_UNKNOWN, 0, NULL },
+        { FALSE, &data3, q13, S_OK, VT_UNKNOWN, 0, NULL },
+        { FALSE, &data3, q14, S_OK, VT_UNKNOWN, 0, NULL },
+        { TRUE, &data3, q15, S_OK, VT_LPSTR, 0, the_worst },
+
+        { FALSE, &data3, q20, S_OK, VT_LPSTR, 0, the_worst },
+        { FALSE, &data3, q21, S_OK, VT_LPSTR, 0, the_worst },
+        { FALSE, &data3, q22, S_OK, VT_LPSTR, 0, the_best },
+        { FALSE, &data3, q23, S_OK, VT_LPSTR, 0, the_worst },
+        { FALSE, &data3, q24, S_OK, VT_LPSTR, 0, the_worst },
+        { FALSE, &data3, q25, S_OK, VT_LPSTR, 0, the_best },
+        { FALSE, &data3, q26, S_OK, VT_LPSTR, 0, the_worst },
+        { FALSE, &data3, q27, S_OK, VT_LPSTR, 0, the_best },
+        { FALSE, &data3, q28, S_OK, VT_LPSTR, 0, the_best },
+
+        { FALSE, &data1, q40, WINCODEC_ERR_PROPERTYNOTSUPPORTED },
+        { TRUE, &data1, q41, WINCODEC_ERR_INVALIDQUERYCHARACTER },
+        { TRUE, &data1, q42, WINCODEC_ERR_INVALIDQUERYCHARACTER },
+        { FALSE, &data1, q43, WINCODEC_ERR_WRONGSTATE },
+        { FALSE, &data1, q44, WINCODEC_ERR_WRONGSTATE },
+        { TRUE, &data1, q45, DISP_E_TYPEMISMATCH },
+        { TRUE, &data1, q46, E_INVALIDARG },
+        { TRUE, &data1, q47, WINCODEC_ERR_REQUESTONLYVALIDATMETADATAROOT },
+    };
+    WCHAR queryW[256];
+    HRESULT hr;
+    IWICComponentFactory *factory;
+    IWICMetadataQueryReader *reader;
+    GUID format;
+    PROPVARIANT value;
+    UINT i;
+
+    hr = CoCreateInstance(&CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER,
+            &IID_IWICComponentFactory, (void **)&factory);
+    ok(hr == S_OK, "CoCreateInstance error %#x\n", hr);
+
+    hr = IWICComponentFactory_CreateQueryReaderFromBlockReader(factory, &mdbr, &reader);
+    ok(hr == S_OK, "CreateQueryReaderFromBlockReader error %#x\n", hr);
+
+    for (i = 0; i < sizeof(test_data)/sizeof(test_data[0]); i++)
+    {
+        current_metadata = test_data[i].data;
+
+        hr = IWICMetadataQueryReader_GetContainerFormat(reader, &format);
+        ok(hr == S_OK, "%u: GetContainerFormat error %#x\n", i, hr);
+        ok(IsEqualGUID(&format, test_data[i].data->container_format), "%u: expected %s, got %s\n",
+           i, wine_dbgstr_guid(test_data[i].data->container_format), wine_dbgstr_guid(&format));
+
+        MultiByteToWideChar(CP_ACP, 0, test_data[i].query, -1, queryW, 256);
+        PropVariantInit(&value);
+        hr = IWICMetadataQueryReader_GetMetadataByName(reader, queryW, &value);
+        todo_wine_if(test_data[i].todo)
+        ok(hr == test_data[i].hr, "%u: expected %#x, got %#x\n", i, test_data[i].hr, hr);
+        if (hr == S_OK)
+        {
+            ok(value.vt == test_data[i].vt, "%u: expected %u, got %u\n", i, test_data[i].vt, value.vt);
+            if (test_data[i].vt == value.vt)
+            {
+                if (value.vt == VT_UNKNOWN)
+                {
+                    IWICMetadataQueryReader *new_reader;
+                    WCHAR location[256];
+                    UINT len;
+
+                    hr = IUnknown_QueryInterface(value.punkVal, &IID_IWICMetadataQueryReader, (void **)&new_reader);
+                    ok(hr == S_OK, "QueryInterface error %#x\n", hr);
+
+                    location[0] = 0;
+                    len = 0xdeadbeef;
+                    hr = IWICMetadataQueryReader_GetLocation(new_reader, 256, location, &len);
+                    ok(hr == S_OK, "GetLocation error %#x\n", hr);
+                    ok(len == lstrlenW(queryW) + 1, "expected %u, got %u\n", lstrlenW(queryW) + 1, len);
+                    ok(!lstrcmpW(location, queryW), "expected %s, got %s\n", wine_dbgstr_w(queryW), wine_dbgstr_w(location));
+
+                    hr = IWICMetadataQueryReader_GetLocation(new_reader, 256, location, NULL);
+                    ok(hr == E_INVALIDARG, "got %#x\n", hr);
+
+                    location[0] = 0;
+                    len = 0xdeadbeef;
+                    hr = IWICMetadataQueryReader_GetLocation(new_reader, 3, location, &len);
+                    ok(hr == WINCODEC_ERR_INSUFFICIENTBUFFER, "got %#x\n", hr);
+                    ok(len == 0xdeadbeef, "got %u\n", len);
+                    ok(!location[0], "got %s\n", wine_dbgstr_w(location));
+
+                    location[0] = 0;
+                    len = 0xdeadbeef;
+                    hr = IWICMetadataQueryReader_GetLocation(new_reader, 0, location, &len);
+                    ok(hr == WINCODEC_ERR_INSUFFICIENTBUFFER, "got %#x\n", hr);
+                    ok(len == 0xdeadbeef, "got %u\n", len);
+                    ok(!location[0], "got %s\n", wine_dbgstr_w(location));
+
+                    len = 0xdeadbeef;
+                    hr = IWICMetadataQueryReader_GetLocation(new_reader, 0, NULL, &len);
+                    ok(hr == S_OK, "GetLocation error %#x\n", hr);
+                    ok(len == lstrlenW(queryW) + 1, "expected %u, got %u\n", lstrlenW(queryW) + 1, len);
+
+                    len = 0xdeadbeef;
+                    hr = IWICMetadataQueryReader_GetLocation(new_reader, 3, NULL, &len);
+                    ok(hr == S_OK, "GetLocation error %#x\n", hr);
+                    ok(len == lstrlenW(queryW) + 1, "expected %u, got %u\n", lstrlenW(queryW) + 1, len);
+
+                    hr = IWICMetadataQueryReader_GetLocation(new_reader, 0, NULL, NULL);
+                    ok(hr == E_INVALIDARG, "got %#x\n", hr);
+
+                    IWICMetadataQueryReader_Release(new_reader);
+                }
+                else if (value.vt == VT_LPSTR)
+                    ok(!lstrcmpA(U(value).pszVal, test_data[i].str_value), "%u: expected %s, got %s\n",
+                       i, test_data[i].str_value, U(value).pszVal);
+                else
+                    ok(U(value).uiVal == test_data[i].value, "%u: expected %u, got %u\n",
+                       i, test_data[i].value, U(value).uiVal);
+            }
+
+            /*
+             * Do NOT call PropVariantClear(&value) for fake value types.
+             */
+        }
+    }
+
+    IWICMetadataQueryReader_Release(reader);
+    IWICComponentFactory_Release(factory);
+}
+
 START_TEST(metadata)
 {
     CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
 
+    test_queryreader();
     test_WICMapGuidToShortName();
     test_WICMapShortNameToGuid();
     test_WICMapSchemaToName();