[PATCH 0/2] MR10162: d2d1: Clamp opacity value for d2d_device_context_draw_bitmap().
Some applications call DrawBitmap() with the opacity value set to 255.0f and end up showing white in the end result. Tests show that the opacity value should be clamped to legitimate values. Command lists store the original opacity value according to tests. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10162
From: Zhiyi Zhang <zzhang@codeweavers.com> --- dlls/d2d1/tests/d2d1.c | 313 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 313 insertions(+) diff --git a/dlls/d2d1/tests/d2d1.c b/dlls/d2d1/tests/d2d1.c index 012f3005d09..f85c65ad9c3 100644 --- a/dlls/d2d1/tests/d2d1.c +++ b/dlls/d2d1/tests/d2d1.c @@ -2516,17 +2516,244 @@ static void test_color_brush(BOOL d3d11) release_test_context(&ctx); } +struct opacity_command_sink +{ + ID2D1CommandSink ID2D1CommandSink_iface; + unsigned int draw_bitmap_count; +}; + +static inline struct opacity_command_sink *impl_from_ID2D1CommandSink(ID2D1CommandSink *iface) +{ + return CONTAINING_RECORD(iface, struct opacity_command_sink, ID2D1CommandSink_iface); +} + +static HRESULT STDMETHODCALLTYPE opacity_command_sink_QueryInterface(ID2D1CommandSink *iface, + REFIID iid, void **out) +{ + if (IsEqualGUID(iid, &IID_ID2D1CommandSink) || IsEqualGUID(iid, &IID_IUnknown)) + { + ID2D1CommandSink_AddRef(iface); + *out = iface; + return S_OK; + } + + *out = NULL; + return E_NOINTERFACE; +} + +static ULONG STDMETHODCALLTYPE opacity_command_sink_AddRef(ID2D1CommandSink *iface) +{ + return 2; +} + +static ULONG STDMETHODCALLTYPE opacity_command_sink_Release(ID2D1CommandSink *iface) +{ + return 1; +} + +static HRESULT STDMETHODCALLTYPE opacity_command_sink_BeginDraw(ID2D1CommandSink *iface) +{ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE opacity_command_sink_EndDraw(ID2D1CommandSink *iface) +{ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE opacity_command_sink_SetAntialiasMode(ID2D1CommandSink *iface, + D2D1_ANTIALIAS_MODE antialias_mode) +{ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE opacity_command_sink_SetTags(ID2D1CommandSink *iface, + D2D1_TAG tag1, D2D1_TAG tag2) +{ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE opacity_command_sink_SetTextAntialiasMode(ID2D1CommandSink *iface, + D2D1_TEXT_ANTIALIAS_MODE antialias_mode) +{ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE opacity_command_sink_SetTextRenderingParams(ID2D1CommandSink *iface, + IDWriteRenderingParams *text_rendering_params) +{ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE opacity_command_sink_SetTransform(ID2D1CommandSink *iface, + const D2D1_MATRIX_3X2_F *transform) +{ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE opacity_command_sink_SetPrimitiveBlend(ID2D1CommandSink *iface, + D2D1_PRIMITIVE_BLEND primitive_blend) +{ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE opacity_command_sink_SetUnitMode(ID2D1CommandSink *iface, + D2D1_UNIT_MODE unit_mode) +{ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE opacity_command_sink_Clear(ID2D1CommandSink *iface, + const D2D1_COLOR_F *color) +{ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE opacity_command_sink_DrawGlyphRun(ID2D1CommandSink *iface, + D2D1_POINT_2F baseline_origin, const DWRITE_GLYPH_RUN *glyph_run, + const DWRITE_GLYPH_RUN_DESCRIPTION *glyph_run_desc, ID2D1Brush *brush, + DWRITE_MEASURING_MODE measuring_mode) +{ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE opacity_command_sink_DrawLine(ID2D1CommandSink *iface, + D2D1_POINT_2F p0, D2D1_POINT_2F p1, ID2D1Brush *brush, float stroke_width, + ID2D1StrokeStyle *stroke_style) +{ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE opacity_command_sink_DrawGeometry(ID2D1CommandSink *iface, + ID2D1Geometry *geometry, ID2D1Brush *brush, float stroke_width, + ID2D1StrokeStyle *stroke_style) +{ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE opacity_command_sink_DrawRectangle(ID2D1CommandSink *iface, + const D2D1_RECT_F *rect, ID2D1Brush *brush, float stroke_width, + ID2D1StrokeStyle *stroke_style) +{ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE opacity_command_sink_DrawBitmap(ID2D1CommandSink *iface, ID2D1Bitmap *bitmap, + const D2D1_RECT_F *dst_rect, float opacity, D2D1_INTERPOLATION_MODE interpolation_mode, + const D2D1_RECT_F *src_rect, const D2D1_MATRIX_4X4_F *perspective_transform) +{ + struct opacity_command_sink *sink = impl_from_ID2D1CommandSink(iface); + ok(opacity == 255.0f || opacity == -255.0f, "Got unexpected opacity %.8e.\n", opacity); + sink->draw_bitmap_count++; + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE opacity_command_sink_DrawImage(ID2D1CommandSink *iface, + ID2D1Image *image, const D2D1_POINT_2F *target_offset, const D2D1_RECT_F *image_rect, + D2D1_INTERPOLATION_MODE interpolation_mode, D2D1_COMPOSITE_MODE composite_mode) +{ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE opacity_command_sink_DrawGdiMetafile(ID2D1CommandSink *iface, + ID2D1GdiMetafile *metafile, const D2D1_POINT_2F *target_offset) +{ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE opacity_command_sink_FillMesh(ID2D1CommandSink *iface, + ID2D1Mesh *mesh, ID2D1Brush *brush) +{ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE opacity_command_sink_FillOpacityMask(ID2D1CommandSink *iface, + ID2D1Bitmap *opacity_mask, ID2D1Brush *brush, const D2D1_RECT_F *dst_rect, + const D2D1_RECT_F *src_rect) +{ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE opacity_command_sink_FillGeometry(ID2D1CommandSink *iface, + ID2D1Geometry *geometry, ID2D1Brush *brush, ID2D1Brush *opacity_brush) +{ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE opacity_command_sink_FillRectangle(ID2D1CommandSink *iface, + const D2D1_RECT_F *rect, ID2D1Brush *brush) +{ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE opacity_command_sink_PushAxisAlignedClip(ID2D1CommandSink *iface, + const D2D1_RECT_F *clip_rect, D2D1_ANTIALIAS_MODE antialias_mode) +{ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE opacity_command_sink_PushLayer(ID2D1CommandSink *iface, + const D2D1_LAYER_PARAMETERS1 *layer_parameters, ID2D1Layer *layer) +{ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE opacity_command_sink_PopAxisAlignedClip(ID2D1CommandSink *iface) +{ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE opacity_command_sink_PopLayer(ID2D1CommandSink *iface) +{ + return S_OK; +} + +static const ID2D1CommandSinkVtbl opacity_command_sink_vtbl = +{ + opacity_command_sink_QueryInterface, + opacity_command_sink_AddRef, + opacity_command_sink_Release, + opacity_command_sink_BeginDraw, + opacity_command_sink_EndDraw, + opacity_command_sink_SetAntialiasMode, + opacity_command_sink_SetTags, + opacity_command_sink_SetTextAntialiasMode, + opacity_command_sink_SetTextRenderingParams, + opacity_command_sink_SetTransform, + opacity_command_sink_SetPrimitiveBlend, + opacity_command_sink_SetUnitMode, + opacity_command_sink_Clear, + opacity_command_sink_DrawGlyphRun, + opacity_command_sink_DrawLine, + opacity_command_sink_DrawGeometry, + opacity_command_sink_DrawRectangle, + opacity_command_sink_DrawBitmap, + opacity_command_sink_DrawImage, + opacity_command_sink_DrawGdiMetafile, + opacity_command_sink_FillMesh, + opacity_command_sink_FillOpacityMask, + opacity_command_sink_FillGeometry, + opacity_command_sink_FillRectangle, + opacity_command_sink_PushAxisAlignedClip, + opacity_command_sink_PushLayer, + opacity_command_sink_PopAxisAlignedClip, + opacity_command_sink_PopLayer, +}; + static void test_bitmap_brush(BOOL d3d11) { D2D1_BITMAP_INTERPOLATION_MODE interpolation_mode; + struct opacity_command_sink opacity_command_sink; ID2D1TransformedGeometry *transformed_geometry; ID2D1RectangleGeometry *rectangle_geometry; D2D1_MATRIX_3X2_F matrix, tmp_matrix; D2D1_BITMAP_PROPERTIES bitmap_desc; ID2D1Bitmap *bitmap, *tmp_bitmap; D2D1_RECT_F src_rect, dst_rect; + ID2D1CommandList *command_list; struct d2d1_test_context ctx; D2D1_EXTEND_MODE extend_mode; + struct resource_readback rb; ID2D1BitmapBrush1 *brush1; ID2D1BitmapBrush *brush; D2D1_SIZE_F image_size; @@ -2538,6 +2765,7 @@ static void test_bitmap_brush(BOOL d3d11) unsigned int i; ULONG refcount; float opacity; + DWORD colour; HRESULT hr; BOOL match; @@ -2568,6 +2796,10 @@ static void test_bitmap_brush(BOOL d3d11) 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, }; + static const DWORD opacity_test_bitmap_data[] = + { + 0xff7f0000 + }; if (!init_test_context(&ctx, d3d11)) return; @@ -2900,6 +3132,87 @@ static void test_bitmap_brush(BOOL d3d11) ID2D1BitmapBrush_Release(brush); refcount = ID2D1Bitmap_Release(bitmap); ok(!refcount, "Bitmap has %lu references left.\n", refcount); + + /* Test ID2D1RenderTarget_DrawBitmap() with out of range opacity values */ + set_size_u(&size, 1, 1); + bitmap_desc.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM; + bitmap_desc.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; + bitmap_desc.dpiX = 96.0f; + bitmap_desc.dpiY = 96.0f; + hr = ID2D1RenderTarget_CreateBitmap(rt, size, opacity_test_bitmap_data, + sizeof(*bitmap_data), &bitmap_desc, &bitmap); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + + ID2D1RenderTarget_SetDpi(rt, 96.0f, 96.0f); + ID2D1RenderTarget_SetAntialiasMode(rt, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); + ID2D1RenderTarget_BeginDraw(rt); + set_color(&color, 0.0f, 0.0f, 0.0f, 1.0f); + ID2D1RenderTarget_Clear(rt, &color); + set_rect(&dst_rect, 0.0f, 0.0f, 1.0f, 1.0f); + ID2D1RenderTarget_DrawBitmap(rt, bitmap, &dst_rect, 255.0f, + D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, NULL); + set_rect(&dst_rect, 1.0f, 0.0f, 2.0f, 1.0f); + ID2D1RenderTarget_DrawBitmap(rt, bitmap, &dst_rect, -255.0f, + D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, NULL); + hr = ID2D1RenderTarget_EndDraw(rt, NULL, NULL); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + + get_surface_readback(&ctx, &rb); + colour = get_readback_colour(&rb, 0, 0); + todo_wine + ok(compare_colour(colour, 0xff7f0000, 1), "Got unexpected colour 0x%08lx.\n", colour); + colour = get_readback_colour(&rb, 1, 0); + ok(compare_colour(colour, 0xff010000, 1), "Got unexpected colour 0x%08lx.\n", colour); + release_resource_readback(&rb); + + /* Test ID2D1DeviceContext_DrawBitmap() with out of range opacity values */ + ID2D1DeviceContext_BeginDraw(ctx.context); + set_color(&color, 0.0f, 0.0f, 0.0f, 1.0f); + ID2D1DeviceContext_Clear(ctx.context, &color); + set_rect(&dst_rect, 0.0f, 0.0f, 1.0f, 1.0f); + ID2D1DeviceContext_DrawBitmap(ctx.context, bitmap, &dst_rect, 255.0f, + D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, NULL, NULL); + set_rect(&dst_rect, 1.0f, 0.0f, 2.0f, 1.0f); + ID2D1DeviceContext_DrawBitmap(ctx.context, bitmap, &dst_rect, -255.0f, + D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, NULL, NULL); + hr = ID2D1DeviceContext_EndDraw(ctx.context, NULL, NULL); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + + get_surface_readback(&ctx, &rb); + colour = get_readback_colour(&rb, 0, 0); + todo_wine + ok(compare_colour(colour, 0xff7f0000, 1), "Got unexpected colour 0x%08lx.\n", colour); + colour = get_readback_colour(&rb, 1, 0); + ok(compare_colour(colour, 0xff010000, 1), "Got unexpected colour 0x%08lx.\n", colour); + release_resource_readback(&rb); + + /* Test ID2D1DeviceContext_DrawBitmap() with a command list and out of range opacity values */ + hr = ID2D1DeviceContext_CreateCommandList(ctx.context, &command_list); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ID2D1DeviceContext_SetTarget(ctx.context, (ID2D1Image *)command_list); + ID2D1DeviceContext_BeginDraw(ctx.context); + ID2D1DeviceContext_DrawBitmap(ctx.context, bitmap, NULL, 255.0f, + D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, NULL, NULL); + ID2D1DeviceContext_DrawBitmap(ctx.context, bitmap, NULL, -255.0f, + D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, NULL, NULL); + hr = ID2D1DeviceContext_EndDraw(ctx.context, NULL, NULL); + todo_wine + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + + hr = ID2D1CommandList_Close(command_list); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + + opacity_command_sink.ID2D1CommandSink_iface.lpVtbl = &opacity_command_sink_vtbl; + opacity_command_sink.draw_bitmap_count = 0; + + hr = ID2D1CommandList_Stream(command_list, &opacity_command_sink.ID2D1CommandSink_iface); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ok(opacity_command_sink.draw_bitmap_count == 2, "Got unexpected count %u.\n", opacity_command_sink.draw_bitmap_count); + + ID2D1DeviceContext_SetTarget(ctx.context, NULL); + ID2D1CommandList_Release(command_list); + + ID2D1Bitmap_Release(bitmap); release_test_context(&ctx); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10162
From: Zhiyi Zhang <zzhang@codeweavers.com> Some applications call DrawBitmap() with the opacity value set to 255.0f and end up showing white in the end result. Tests show that the opacity value should be clamped to legitimate values. Command lists store the original opacity value according to tests. --- dlls/d2d1/device.c | 2 +- dlls/d2d1/tests/d2d1.c | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/dlls/d2d1/device.c b/dlls/d2d1/device.c index 74607dc8ea4..9476341f061 100644 --- a/dlls/d2d1/device.c +++ b/dlls/d2d1/device.c @@ -1226,7 +1226,7 @@ static void d2d_device_context_draw_bitmap(struct d2d_device_context *context, I bitmap_brush_desc.extendModeY = D2D1_EXTEND_MODE_CLAMP; bitmap_brush_desc.interpolationMode = interpolation_mode; - brush_desc.opacity = opacity; + brush_desc.opacity = max(0.0f, min(opacity, 1.0f)); brush_desc.transform._11 = fabsf((d.right - d.left) / (s.right - s.left)); brush_desc.transform._21 = 0.0f; brush_desc.transform._31 = min(d.left, d.right) - min(s.left, s.right) * brush_desc.transform._11; diff --git a/dlls/d2d1/tests/d2d1.c b/dlls/d2d1/tests/d2d1.c index f85c65ad9c3..aabfdc7d7dd 100644 --- a/dlls/d2d1/tests/d2d1.c +++ b/dlls/d2d1/tests/d2d1.c @@ -3159,7 +3159,6 @@ static void test_bitmap_brush(BOOL d3d11) get_surface_readback(&ctx, &rb); colour = get_readback_colour(&rb, 0, 0); - todo_wine ok(compare_colour(colour, 0xff7f0000, 1), "Got unexpected colour 0x%08lx.\n", colour); colour = get_readback_colour(&rb, 1, 0); ok(compare_colour(colour, 0xff010000, 1), "Got unexpected colour 0x%08lx.\n", colour); @@ -3180,7 +3179,6 @@ static void test_bitmap_brush(BOOL d3d11) get_surface_readback(&ctx, &rb); colour = get_readback_colour(&rb, 0, 0); - todo_wine ok(compare_colour(colour, 0xff7f0000, 1), "Got unexpected colour 0x%08lx.\n", colour); colour = get_readback_colour(&rb, 1, 0); ok(compare_colour(colour, 0xff010000, 1), "Got unexpected colour 0x%08lx.\n", colour); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10162
Could you please add similar tests for bitmap brushes? Maybe we should be fixing that instead. Also, I don't think we need command list tests like that in tree, it's good to confirm that original value is retained, but if we wanted to automate this it should instead collect all the data, and then explore that, rather than having test-specific checks in sink methods. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10162#note_130231
participants (3)
-
Nikolay Sivov (@nsivov) -
Zhiyi Zhang -
Zhiyi Zhang (@zhiyi)