Continuation of https://gitlab.winehq.org/wine/wine/-/merge_requests/5593.
Removing HBITMAP objects revealed some bugs in the software path for linecaps. It also led to using the stride passed in (instead of using the stride a DIB would have), which is actually invalid in one of our tests and native ignores it.
From: Esme Povirk esme@codeweavers.com
--- dlls/gdiplus/image.c | 2 +- dlls/gdiplus/tests/graphics.c | 71 +++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-)
diff --git a/dlls/gdiplus/image.c b/dlls/gdiplus/image.c index d3d4ec7de42..3a5e338c4ac 100644 --- a/dlls/gdiplus/image.c +++ b/dlls/gdiplus/image.c @@ -1817,7 +1817,7 @@ GpStatus WINGDIPAPI GdipCreateBitmapFromScan0(INT width, INT height, INT stride, row_size = (width * PIXELFORMATBPP(format)+7) / 8; dib_stride = (row_size + 3) & ~3;
- if(stride == 0) + if(!scan0) stride = dib_stride;
if (format & PixelFormatGDI && !(format & (PixelFormatAlpha|PixelFormatIndexed)) && !scan0) diff --git a/dlls/gdiplus/tests/graphics.c b/dlls/gdiplus/tests/graphics.c index ff6a64e1fb5..73e2a9b2c3a 100644 --- a/dlls/gdiplus/tests/graphics.c +++ b/dlls/gdiplus/tests/graphics.c @@ -7406,6 +7406,76 @@ static void test_printer_dc(void) DeleteDC(hdc_printer); }
+void test_bitmap_stride(void) +{ + GpStatus status; + GpBitmap *bitmap = NULL; + BitmapData locked_data; + GpRect bounds = {0, 0, 10, 10}; + BYTE buffer[400]; + BYTE *scan0; + int i; + struct { + INT stride; + BOOL use_scan0; + INT expected_stride; + GpStatus status; + } test_data[] = { + { 12, FALSE, 20 }, + { 40, FALSE, 20 }, + { 0, FALSE, 20 }, + { 20, FALSE, 20 }, + { -12, FALSE, 20 }, + { -32, FALSE, 20 }, + { 30, TRUE, 0, InvalidParameter }, + { 12, TRUE, 12 }, + { 40, TRUE, 40 }, + { 0, TRUE, 0, InvalidParameter }, + { 32, TRUE, 32 }, + { -12, TRUE, -12 }, + { -32, TRUE, -32 }, + { -13, TRUE, 0, InvalidParameter }, + }; + + for (i=0; i < ARRAY_SIZE(test_data); i++) + { + if (test_data[i].use_scan0) + { + if (test_data[i].stride >= 0) + scan0 = buffer; + else + scan0 = buffer + sizeof(buffer) + test_data[i].stride; + } + else + scan0 = NULL; + + winetest_push_context("%i: %i %i", i, test_data[i].stride, test_data[i].use_scan0); + + status = GdipCreateBitmapFromScan0(10, 10, test_data[i].stride, PixelFormat16bppGrayScale, scan0, &bitmap); + expect(test_data[i].status, status); + + if (status == Ok) + { + status = GdipBitmapLockBits(bitmap, &bounds, ImageLockModeRead, PixelFormat16bppGrayScale, &locked_data); + expect(Ok, status); + + expect(10, locked_data.Width); + expect(10, locked_data.Height); + expect(test_data[i].expected_stride, locked_data.Stride); + expect(PixelFormat16bppGrayScale, locked_data.PixelFormat); + if (test_data[i].use_scan0) + ok(locked_data.Scan0 == scan0, "got %p, expected %p\n", locked_data.Scan0, scan0); + + status = GdipBitmapUnlockBits(bitmap, &locked_data); + expect(Ok, status); + + GdipDisposeImage((GpImage*)bitmap); + } + + winetest_pop_context(); + } +} + START_TEST(graphics) { struct GdiplusStartupInput gdiplusStartupInput; @@ -7504,6 +7574,7 @@ START_TEST(graphics) test_gdi_interop_bitmap(); test_gdi_interop_hdc(); test_printer_dc(); + test_bitmap_stride();
GdiplusShutdown(gdiplusToken); DestroyWindow( hwnd );
From: Esme Povirk esme@codeweavers.com
--- dlls/gdiplus/gdiplus_private.h | 2 + dlls/gdiplus/graphics.c | 80 +++++++++++++++++++++++++++++- dlls/gdiplus/graphicspath.c | 91 +++++++++++++++++++++++++--------- 3 files changed, 148 insertions(+), 25 deletions(-)
diff --git a/dlls/gdiplus/gdiplus_private.h b/dlls/gdiplus/gdiplus_private.h index c3b516a9a2b..c327d44af66 100644 --- a/dlls/gdiplus/gdiplus_private.h +++ b/dlls/gdiplus/gdiplus_private.h @@ -137,6 +137,8 @@ extern void free_installed_fonts(void);
extern BOOL lengthen_path(GpPath *path, INT len);
+extern GpStatus widen_flat_path_anchors(GpPath *flat_path, GpPen *pen, REAL pen_width, GpPath **anchors); + extern DWORD write_region_data(const GpRegion *region, void *data); extern DWORD write_path_data(GpPath *path, void *data);
diff --git a/dlls/gdiplus/graphics.c b/dlls/gdiplus/graphics.c index 86c156354ef..7eea3f58c77 100644 --- a/dlls/gdiplus/graphics.c +++ b/dlls/gdiplus/graphics.c @@ -3737,7 +3737,8 @@ end: static GpStatus SOFTWARE_GdipDrawThinPath(GpGraphics *graphics, GpPen *pen, GpPath *path) { GpStatus stat; - GpPath* flat_path; + GpPath *flat_path, *anchor_path; + GpRegion *anchor_region; GpMatrix* transform; GpRectF gp_bound_rect; GpRect gp_output_area; @@ -3765,6 +3766,9 @@ static GpStatus SOFTWARE_GdipDrawThinPath(GpGraphics *graphics, GpPen *pen, GpPa if (stat == Ok) stat = GdipFlattenPath(flat_path, transform, 1.0);
+ if (stat == Ok) + stat = widen_flat_path_anchors(flat_path, pen, 1.0, &anchor_path); + GdipDeleteMatrix(transform); }
@@ -3788,6 +3792,18 @@ static GpStatus SOFTWARE_GdipDrawThinPath(GpGraphics *graphics, GpPen *pen, GpPa if (ceilf(y) > output_area.bottom) output_area.bottom = ceilf(y); }
+ for (i=0; i<anchor_path->pathdata.Count; i++) + { + REAL x, y; + x = anchor_path->pathdata.Points[i].X; + y = anchor_path->pathdata.Points[i].Y; + + if (floorf(x) < output_area.left) output_area.left = floorf(x); + if (floorf(y) < output_area.top) output_area.top = floorf(y); + if (ceilf(x) > output_area.right) output_area.right = ceilf(x); + if (ceilf(y) > output_area.bottom) output_area.bottom = ceilf(y); + } + stat = get_graphics_device_bounds(graphics, &gp_bound_rect); }
@@ -3804,6 +3820,7 @@ static GpStatus SOFTWARE_GdipDrawThinPath(GpGraphics *graphics, GpPen *pen, GpPa if (output_width <= 0 || output_height <= 0) { GdipDeletePath(flat_path); + GdipDeletePath(anchor_path); return Ok; }
@@ -4015,6 +4032,66 @@ static GpStatus SOFTWARE_GdipDrawThinPath(GpGraphics *graphics, GpPen *pen, GpPa } } } + + /* draw anchors */ + stat = GdipCreateRegionPath(anchor_path, &anchor_region); + if (stat == Ok) + { + HRGN hrgn; + DWORD rgn_data_size; + RGNDATA *rgn_data; + RECT *rects; + INT x, y; + + stat = GdipCombineRegionRectI(anchor_region, &gp_output_area, CombineModeIntersect); + + if (stat == Ok) + stat = GdipGetRegionHRgn(anchor_region, NULL, &hrgn); + + if (stat == Ok) + { + rgn_data_size = GetRegionData(hrgn, 0, NULL); + + if (rgn_data_size) + { + rgn_data = malloc(rgn_data_size); + + if (rgn_data) + { + GetRegionData(hrgn, rgn_data_size, rgn_data); + + rects = (RECT*)&rgn_data->Buffer; + + for (i=0; i < rgn_data->rdh.nCount; i++) + { + RECT rc; + rc = rects[i]; + + OffsetRect(&rc, -output_area.left, -output_area.top); + + for (y = rc.top; y < rc.bottom; y++) + { + for (x = rc.left; x < rc.right; x++) + { + if (brush_bits) + output_bits[x + y*output_width] = brush_bits[x + y*output_width]; + else + output_bits[x + y*output_width] = ((GpSolidFill*)pen->brush)->color; + } + } + } + + free(rgn_data); + } + else + stat = OutOfMemory; + } + + DeleteObject(hrgn); + } + + GdipDeleteRegion(anchor_region); + } }
/* draw output image */ @@ -4035,6 +4112,7 @@ static GpStatus SOFTWARE_GdipDrawThinPath(GpGraphics *graphics, GpPen *pen, GpPa }
GdipDeletePath(flat_path); + GdipDeletePath(anchor_path);
return stat; } diff --git a/dlls/gdiplus/graphicspath.c b/dlls/gdiplus/graphicspath.c index 662e20071aa..105df4cacfd 100644 --- a/dlls/gdiplus/graphicspath.c +++ b/dlls/gdiplus/graphicspath.c @@ -2111,9 +2111,8 @@ static void widen_closed_figure(const GpPointF *points, int start, int end, GpPen *pen, REAL pen_width, path_list_node_t **last_point);
static void add_anchor(const GpPointF *endpoint, const GpPointF *nextpoint, - GpPen *pen, GpLineCap cap, GpCustomLineCap *custom, path_list_node_t **last_point) + GpPen *pen, REAL pen_width, GpLineCap cap, GpCustomLineCap *custom, path_list_node_t **last_point) { - REAL pen_width = max(pen->width, 2.0); switch (cap) { default: @@ -2520,6 +2519,71 @@ static void widen_dashed_figure(GpPath *path, int start, int end, int closed, free(tmp_points); }
+void widen_anchors(GpPath *flat_path, GpPen *pen, REAL pen_width, path_list_node_t** last_point) +{ + BYTE *types = flat_path->pathdata.Types; + int i, subpath_start=0; + + for (i=0; i < flat_path->pathdata.Count; i++) + { + if ((types[i]&PathPointTypeCloseSubpath) == PathPointTypeCloseSubpath) + continue; + + if ((types[i]&PathPointTypePathTypeMask) == PathPointTypeStart) + subpath_start = i; + + if (i == flat_path->pathdata.Count-1 || + (types[i+1]&PathPointTypePathTypeMask) == PathPointTypeStart) + { + if (pen->startcap & LineCapAnchorMask) + add_anchor(&flat_path->pathdata.Points[subpath_start], + &flat_path->pathdata.Points[subpath_start+1], + pen, pen_width, pen->startcap, pen->customstart, last_point); + + if (pen->endcap & LineCapAnchorMask) + add_anchor(&flat_path->pathdata.Points[i], + &flat_path->pathdata.Points[i-1], + pen, pen_width, pen->endcap, pen->customend, last_point); + } + } +} + +GpStatus widen_flat_path_anchors(GpPath *flat_path, GpPen *pen, REAL pen_width, GpPath **anchors) +{ + GpStatus stat; + path_list_node_t *points=NULL, *last_point=NULL; + + if (!flat_path || !pen) + return InvalidParameter; + + if (init_path_list(&points, 314.0, 22.0)) + { + last_point = points; + + stat = GdipCreatePath(flat_path->fill, anchors); + if (stat == Ok) + { + widen_anchors(flat_path, pen, pen_width, &last_point); + + if (!path_list_to_path(points->next, *anchors)) + stat = OutOfMemory; + + if (stat != Ok) + { + GdipDeletePath(*anchors); + *anchors = NULL; + } + } + free_path_list(points); + } + else + stat = OutOfMemory; + + /* FIXME: Apply insets to flat_path */ + + return stat; +} + GpStatus WINGDIPAPI GdipWidenPath(GpPath *path, GpPen *pen, GpMatrix *matrix, REAL flatness) { @@ -2586,28 +2650,7 @@ GpStatus WINGDIPAPI GdipWidenPath(GpPath *path, GpPen *pen, GpMatrix *matrix, } }
- for (i=0; i < flat_path->pathdata.Count; i++) - { - if ((types[i]&PathPointTypeCloseSubpath) == PathPointTypeCloseSubpath) - continue; - - if ((types[i]&PathPointTypePathTypeMask) == PathPointTypeStart) - subpath_start = i; - - if (i == flat_path->pathdata.Count-1 || - (types[i+1]&PathPointTypePathTypeMask) == PathPointTypeStart) - { - if (pen->startcap & LineCapAnchorMask) - add_anchor(&flat_path->pathdata.Points[subpath_start], - &flat_path->pathdata.Points[subpath_start+1], - pen, pen->startcap, pen->customstart, &last_point); - - if (pen->endcap & LineCapAnchorMask) - add_anchor(&flat_path->pathdata.Points[i], - &flat_path->pathdata.Points[i-1], - pen, pen->endcap, pen->customend, &last_point); - } - } + widen_anchors(flat_path, pen, fmax(pen->width, 2.0), &last_point);
if (!path_list_to_path(points->next, path)) status = OutOfMemory;
From: Esme Povirk esme@codeweavers.com
These additions and subtractions cancel each other out. --- dlls/gdiplus/graphicspath.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/dlls/gdiplus/graphicspath.c b/dlls/gdiplus/graphicspath.c index 105df4cacfd..94fae70ad88 100644 --- a/dlls/gdiplus/graphicspath.c +++ b/dlls/gdiplus/graphicspath.c @@ -2264,8 +2264,8 @@ static void add_anchor(const GpPointF *endpoint, const GpPointF *nextpoint, cosa = pen_width * custom->scale * segment_dy / segment_length;
/* Coordination where cap needs to be drawn */ - posx = endpoint->X + sina; - posy = endpoint->Y - cosa; + posx = endpoint->X; + posy = endpoint->Y;
if (!custom->fill) { @@ -2277,8 +2277,8 @@ static void add_anchor(const GpPointF *endpoint, const GpPointF *nextpoint,
for (INT i = 0; i < custom->pathdata.Count; i++) { - tmp_points[i].X = posx + custom->pathdata.Points[i].X * cosa + (custom->pathdata.Points[i].Y - 1.0) * sina; - tmp_points[i].Y = posy + custom->pathdata.Points[i].X * sina - (custom->pathdata.Points[i].Y - 1.0) * cosa; + tmp_points[i].X = posx + custom->pathdata.Points[i].X * cosa + custom->pathdata.Points[i].Y * sina; + tmp_points[i].Y = posy + custom->pathdata.Points[i].X * sina - custom->pathdata.Points[i].Y * cosa; } if ((custom->pathdata.Types[custom->pathdata.Count - 1] & PathPointTypeCloseSubpath) == PathPointTypeCloseSubpath) widen_closed_figure(tmp_points, 0, custom->pathdata.Count - 1, pen, pen_width, last_point); @@ -2291,8 +2291,8 @@ static void add_anchor(const GpPointF *endpoint, const GpPointF *nextpoint, for (INT i = 0; i < custom->pathdata.Count; i++) { /* rotation of CustomCap according to line */ - perp_dx = custom->pathdata.Points[i].X * cosa + (custom->pathdata.Points[i].Y - 1.0) * sina; - perp_dy = custom->pathdata.Points[i].X * sina - (custom->pathdata.Points[i].Y - 1.0) * cosa; + perp_dx = custom->pathdata.Points[i].X * cosa + custom->pathdata.Points[i].Y * sina; + perp_dy = custom->pathdata.Points[i].X * sina - custom->pathdata.Points[i].Y * cosa; *last_point = add_path_list_node(*last_point, posx + perp_dx, posy + perp_dy, custom->pathdata.Types[i]); }
From: Esme Povirk esme@codeweavers.com
--- dlls/gdiplus/graphicspath.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/dlls/gdiplus/graphicspath.c b/dlls/gdiplus/graphicspath.c index 94fae70ad88..44077950a87 100644 --- a/dlls/gdiplus/graphicspath.c +++ b/dlls/gdiplus/graphicspath.c @@ -2260,8 +2260,8 @@ static void add_anchor(const GpPointF *endpoint, const GpPointF *nextpoint, TRACE("GpCustomLineCap fill: %d basecap: %d inset: %f join: %d scale: %f pen_width:%f\n", custom->fill, custom->basecap, custom->inset, custom->join, custom->scale, pen_width);
- sina = -pen_width * custom->scale * segment_dx / segment_length; - cosa = pen_width * custom->scale * segment_dy / segment_length; + sina = pen_width * custom->scale * segment_dx / segment_length; + cosa = -pen_width * custom->scale * segment_dy / segment_length;
/* Coordination where cap needs to be drawn */ posx = endpoint->X; @@ -2277,8 +2277,8 @@ static void add_anchor(const GpPointF *endpoint, const GpPointF *nextpoint,
for (INT i = 0; i < custom->pathdata.Count; i++) { - tmp_points[i].X = posx + custom->pathdata.Points[i].X * cosa + custom->pathdata.Points[i].Y * sina; - tmp_points[i].Y = posy + custom->pathdata.Points[i].X * sina - custom->pathdata.Points[i].Y * cosa; + tmp_points[i].X = posx + custom->pathdata.Points[i].X * cosa - custom->pathdata.Points[i].Y * sina; + tmp_points[i].Y = posy + custom->pathdata.Points[i].X * sina + custom->pathdata.Points[i].Y * cosa; } if ((custom->pathdata.Types[custom->pathdata.Count - 1] & PathPointTypeCloseSubpath) == PathPointTypeCloseSubpath) widen_closed_figure(tmp_points, 0, custom->pathdata.Count - 1, pen, pen_width, last_point); @@ -2291,8 +2291,8 @@ static void add_anchor(const GpPointF *endpoint, const GpPointF *nextpoint, for (INT i = 0; i < custom->pathdata.Count; i++) { /* rotation of CustomCap according to line */ - perp_dx = custom->pathdata.Points[i].X * cosa + custom->pathdata.Points[i].Y * sina; - perp_dy = custom->pathdata.Points[i].X * sina - custom->pathdata.Points[i].Y * cosa; + perp_dx = custom->pathdata.Points[i].X * cosa - custom->pathdata.Points[i].Y * sina; + perp_dy = custom->pathdata.Points[i].X * sina + custom->pathdata.Points[i].Y * cosa; *last_point = add_path_list_node(*last_point, posx + perp_dx, posy + perp_dy, custom->pathdata.Types[i]); }
From: Esme Povirk esme@codeweavers.com
--- dlls/gdiplus/customlinecap.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/dlls/gdiplus/customlinecap.c b/dlls/gdiplus/customlinecap.c index b6dcf26fdd1..d493214904c 100644 --- a/dlls/gdiplus/customlinecap.c +++ b/dlls/gdiplus/customlinecap.c @@ -299,11 +299,11 @@ static void arrowcap_update_path(GpAdjustableArrowCap *cap) { memcpy(cap->cap.pathdata.Types, types_filled, sizeof(types_filled)); cap->cap.pathdata.Count = 4; - points[0].X = -cap->width / 2.0; + points[0].X = cap->width / 2.0; points[0].Y = -cap->height; points[1].X = 0.0; points[1].Y = 0.0; - points[2].X = cap->width / 2.0; + points[2].X = -cap->width / 2.0; points[2].Y = -cap->height; points[3].X = 0.0; points[3].Y = -cap->height + cap->middle_inset;