diff --git a/dlls/gdi32/dibdrv/objects.c b/dlls/gdi32/dibdrv/objects.c index a2411c34b4e9a167b2ce5a1c4fc5d34e04006f3c..a35ba8856b99c2668a2b29b8b208cc43b7de67ad 100644 --- a/dlls/gdi32/dibdrv/objects.c +++ b/dlls/gdi32/dibdrv/objects.c @@ -18,6 +18,8 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ +#include <stdlib.h> + #include "gdi_private.h" #include "dibdrv.h" @@ -106,6 +108,31 @@ static inline BOOL pt_in_rect( const RECT *rect, const POINT *pt ) (pt->y >= rect->top) && (pt->y < rect->bottom)); } +#define Y_INCREASING_MASK 0x0f +#define X_INCREASING_MASK 0xc3 +#define X_MAJOR_MASK 0x99 +#define POS_SLOPE_MASK 0x33 + +static inline BOOL is_xmajor(DWORD octant) +{ + return octant & X_MAJOR_MASK; +} + +static inline BOOL is_pos_slope(DWORD octant) +{ + return octant & POS_SLOPE_MASK; +} + +static inline BOOL is_x_increasing(DWORD octant) +{ + return octant & X_INCREASING_MASK; +} + +static inline BOOL is_y_increasing(DWORD octant) +{ + return octant & Y_INCREASING_MASK; +} + /********************************************************************** * get_octant_number * @@ -143,65 +170,276 @@ static void solid_pen_line_callback(INT x, INT y, LPARAM lparam) return; } -static void bres_line_with_bias(INT x1, INT y1, INT x2, INT y2, void (* callback)(INT,INT,LPARAM), LPARAM lParam) +#define OUT_LEFT 1 +#define OUT_RIGHT 2 +#define OUT_TOP 4 +#define OUT_BOTTOM 8 + +static inline DWORD calc_outcode(const POINT *pt, const RECT *clip) { - INT xadd = 1, yadd = 1; - INT err, erradd; - INT cnt; - INT dx = x2 - x1; - INT dy = y2 - y1; - DWORD octant = get_octant_mask(dx, dy); - INT bias = 0; - - /* Octants 3, 5, 6 and 8 take a bias */ - if(octant & 0xb4) bias = 1; - - if (dx < 0) - { - dx = -dx; - xadd = -1; - } - if (dy < 0) + DWORD out = 0; + if(pt->x < clip->left) out |= OUT_LEFT; + else if(pt->x >= clip->right) out |= OUT_RIGHT; + if(pt->y < clip->top) out |= OUT_TOP; + else if(pt->y >= clip->bottom) out |= OUT_BOTTOM; + + return out; +} + +typedef struct +{ + unsigned int dx, dy; + int bias; + DWORD octant; +} bres_params; + +/****************************************************************************** + * clip_line + * + * Clips the start and end points to a rectangle. + * + * Note, this treats the end point like the start point. If the + * caller doesn't want it displayed, it should exclude it. If the end + * point is clipped out, then the likelihood is that the new end point + * should be displayed. + * + * Returns 0 if totally excluded, 1 if partially clipped and 2 if unclipped. + * + * This derivation is based on the comments in X.org's xserver/mi/mizerclip.c, + * however the Bresenham error term is defined differently so the equations + * will also differ. + * + * For x major lines we have 2dy >= err + bias > 2dy - 2dx + * 0 >= err + bias - 2dy > -2dx + * + * Note dx, dy, m and n are all +ve. + * + * Moving the start pt from x1 to x1 + m, we need to figure out y1 + n. + * err = 2dy - dx + 2mdy - 2ndx + * 0 >= 2dy - dx + 2mdy - 2ndx + bias - 2dy > -2dx + * 0 >= 2mdy - 2ndx + bias - dx > -2dx + * which of course will give exactly one solution for n, + * so looking at the >= inequality + * n >= (2mdy + bias - dx) / 2dx + * n = ceiling((2mdy + bias - dx) / 2dx) + * = (2mdy + bias + dx - 1) / 2dx (assuming division truncation) + * + * Moving start pt from y1 to y1 + n we need to figure out x1 + m - there may be several + * solutions we pick the one that minimizes m (ie that first unlipped pt). As above: + * 0 >= 2mdy - 2ndx + bias - dx > -2dx + * 2mdy > 2ndx - bias - dx + * m > (2ndx - bias - dx) / 2dy + * m = floor((2ndx - bias - dx) / 2dy) + 1 + * m = (2ndx - bias - dx) / 2dy + 1 + * + * Moving end pt from x2 to x2 - m, we need to figure out y2 - n + * err = 2dy - dx + 2(dx - m)dy - 2(dy - n)dx + * = 2dy - dx - 2mdy + 2ndx + * 0 >= 2dy - dx - 2mdy + 2ndx + bias - 2dy > -2dx + * 0 >= 2ndx - 2mdy + bias - dx > -2dx + * again exactly one solution. + * 2ndx <= 2mdy - bias + dx + * n = floor((2mdy - bias + dx) / 2dx) + * = (2mdy - bias + dx) / 2dx + * + * Moving end pt from y2 to y2 - n when need x2 - m this time maximizing x2 - m so + * mininizing m to include all of the points at y = y2 - n. As above: + * 0 >= 2ndx - 2mdy + bias - dx > -2dx + * 2mdy >= 2ndx + bias - dx + * m = ceiling((2ndx + bias - dx) / 2dy) + * = (2ndx + bias - dx - 1) / 2dy + 1 + * + * For y major lines, symmetry (dx <-> dy and swap the cases over) gives: + * + * Moving start point from y1 to y1 + n find x1 + m + * m = (2ndx + bias + dy - 1) / 2dy + * + * Moving start point from x1 to x1 + m find y1 + n + * n = (2mdy - bias - dy) / 2ndx + 1 + * + * Moving end point from y2 to y2 - n find x1 - m + * m = (2ndx - bias + dy) / 2dy + * + * Moving end point from x2 to x2 - m find y2 - n + * n = (2mdy + bias - dy - 1) / 2dx + 1 + */ +static int clip_line(const POINT *start, const POINT *end, const RECT *clip, + const bres_params *params, POINT *pt1, POINT *pt2) +{ + unsigned int n, m; + BOOL clipped = FALSE; + DWORD start_oc, end_oc; + const int bias = params->bias; + const unsigned int dx = params->dx; + const unsigned int dy = params->dy; + const unsigned int two_dx = params->dx * 2; + const unsigned int two_dy = params->dy * 2; + const BOOL xmajor = is_xmajor(params->octant); + const BOOL neg_slope = !is_pos_slope(params->octant); + + *pt1 = *start; + *pt2 = *end; + + start_oc = calc_outcode(start, clip); + end_oc = calc_outcode(end, clip); + + while(1) { - dy = -dy; - yadd = -1; + if(start_oc == 0 && end_oc == 0) return clipped ? 1 : 2; /* trivial accept */ + if(start_oc & end_oc) return 0; /* trivial reject */ + + clipped = TRUE; + if(start_oc & OUT_LEFT) + { + m = clip->left - start->x; + if(xmajor) + n = (m * two_dy + bias + dx - 1) / two_dx; + else + n = (m * two_dy - bias - dy) / two_dx + 1; + + pt1->x = clip->left; + if(neg_slope) n = -n; + pt1->y = start->y + n; + start_oc = calc_outcode(pt1, clip); + } + else if(start_oc & OUT_RIGHT) + { + m = start->x - clip->right + 1; + if(xmajor) + n = (m * two_dy + bias + dx - 1) / two_dx; + else + n = (m * two_dy - bias - dy) / two_dx + 1; + + pt1->x = clip->right - 1; + if(neg_slope) n = -n; + pt1->y = start->y - n; + start_oc = calc_outcode(pt1, clip); + } + else if(start_oc & OUT_TOP) + { + n = clip->top - start->y; + if(xmajor) + m = (n * two_dx - bias - dx) / two_dy + 1; + else + m = (n * two_dx + bias + dy - 1) / two_dy; + + pt1->y = clip->top; + if(neg_slope) m = -m; + pt1->x = start->x + m; + start_oc = calc_outcode(pt1, clip); + } + else if(start_oc & OUT_BOTTOM) + { + n = start->y - clip->bottom + 1; + if(xmajor) + m = (n * two_dx - bias - dx) / two_dy + 1; + else + m = (n * two_dx + bias + dy - 1) / two_dy; + + pt1->y = clip->bottom - 1; + if(neg_slope) m = -m; + pt1->x = start->x - m; + start_oc = calc_outcode(pt1, clip); + } + else if(end_oc & OUT_LEFT) + { + m = clip->left - end->x; + if(xmajor) + n = (m * two_dy - bias + dx) / two_dx; + else + n = (m * two_dy + bias - dy - 1) / two_dx + 1; + + pt2->x = clip->left; + if(neg_slope) n = -n; + pt2->y = end->y + n; + end_oc = calc_outcode(pt2, clip); + } + else if(end_oc & OUT_RIGHT) + { + m = end->x - clip->right + 1; + if(xmajor) + n = (m * two_dy - bias + dx) / two_dx; + else + n = (m * two_dy + bias - dy - 1) / two_dx + 1; + + pt2->x = clip->right - 1; + if(neg_slope) n = -n; + pt2->y = end->y - n; + end_oc = calc_outcode(pt2, clip); + } + else if(end_oc & OUT_TOP) + { + n = clip->top - end->y; + if(xmajor) + m = (n * two_dx + bias - dx - 1) / two_dy + 1; + else + m = (n * two_dx - bias + dy) / two_dy; + + pt2->y = clip->top; + if(neg_slope) m = -m; + pt2->x = end->x + m; + end_oc = calc_outcode(pt2, clip); + } + else if(end_oc & OUT_BOTTOM) + { + n = end->y - clip->bottom + 1; + if(xmajor) + m = (n * two_dx + bias - dx - 1) / two_dy + 1; + else + m = (n * two_dx - bias + dy) / two_dy; + + pt2->y = clip->bottom - 1; + if(neg_slope) m = -m; + pt2->x = end->x - m; + end_oc = calc_outcode(pt2, clip); + } } - if (dx > dy) /* line is "more horizontal" */ +} + +static void bres_line_with_bias(INT x1, INT y1, INT x2, INT y2, const bres_params *params, INT err, + BOOL last_pt, void (* callback)(INT,INT,LPARAM), LPARAM lParam) +{ + const int xadd = is_x_increasing(params->octant) ? 1 : -1; + const int yadd = is_y_increasing(params->octant) ? 1 : -1; + INT erradd; + + if (is_xmajor(params->octant)) /* line is "more horizontal" */ { - err = 2*dy - dx; erradd = 2*dy - 2*dx; - for(cnt = 0; cnt < dx; cnt++) + erradd = 2*params->dy - 2*params->dx; + while(x1 != x2) { callback(x1, y1, lParam); - if (err + bias > 0) + if (err + params->bias > 0) { y1 += yadd; err += erradd; } - else err += 2*dy; + else err += 2*params->dy; x1 += xadd; } + if(last_pt) callback(x1, y1, lParam); } else /* line is "more vertical" */ { - err = 2*dx - dy; erradd = 2*dx - 2*dy; - for(cnt = 0; cnt < dy; cnt++) + erradd = 2*params->dx - 2*params->dy; + while(y1 != y2) { callback(x1, y1, lParam); - if (err + bias > 0) + if (err + params->bias > 0) { x1 += xadd; err += erradd; } - else err += 2*dx; + else err += 2*params->dx; y1 += yadd; } + if(last_pt) callback(x1, y1, lParam); } } static BOOL solid_pen_line(dibdrv_physdev *pdev, POINT *start, POINT *end) { const WINEREGION *clip = get_wine_region(pdev->clip); - BOOL ret = TRUE; if(start->y == end->y) { @@ -264,15 +502,47 @@ static BOOL solid_pen_line(dibdrv_physdev *pdev, POINT *start, POINT *end) } else { - if(clip->numRects == 1 && pt_in_rect(&clip->extents, start) && pt_in_rect(&clip->extents, end)) - /* FIXME: Optimize by moving Bresenham algorithm to the primitive functions, - or at least cache adjacent points in the callback */ - bres_line_with_bias(start->x, start->y, end->x, end->y, solid_pen_line_callback, (LPARAM)pdev); - else if(clip->numRects >= 1) - ret = FALSE; + bres_params params; + INT dx = end->x - start->x; + INT dy = end->y - start->y; + INT i; + + params.dx = abs(dx); + params.dy = abs(dy); + params.octant = get_octant_mask(dx, dy); + /* Octants 3, 5, 6 and 8 take a bias */ + params.bias = (params.octant & 0xb4) ? 1 : 0; + + for(i = 0; i < clip->numRects; i++) + { + POINT clipped_start, clipped_end; + int clip_status; + clip_status = clip_line(start, end, clip->rects + i, ¶ms, &clipped_start, &clipped_end); + + if(clip_status) + { + int m = abs(clipped_start.x - start->x); + int n = abs(clipped_start.y - start->y); + int err; + BOOL last_pt = FALSE; + + if(is_xmajor(params.octant)) + err = 2 * params.dy - params.dx + m * 2 * params.dy - n * 2 * params.dx; + else + err = 2 * params.dx - params.dy + n * 2 * params.dx - m * 2 * params.dy; + + if(clip_status == 1 && (end->x != clipped_end.x || end->y != clipped_end.y)) last_pt = TRUE; + + bres_line_with_bias(clipped_start.x, clipped_start.y, clipped_end.x, clipped_end.y, ¶ms, + err, last_pt, solid_pen_line_callback, (LPARAM)pdev); + + if(clip_status == 2) break; /* completely unclipped, so we can finish */ + } + } + } release_wine_region(pdev->clip); - return ret; + return TRUE; } /*********************************************************************** diff --git a/dlls/gdi32/tests/dib.c b/dlls/gdi32/tests/dib.c index 3046799255e095bea9d7f7da78d188630ec273a5..7bc48f31761f1f3e1023e93e59de434d13925f20 100644 --- a/dlls/gdi32/tests/dib.c +++ b/dlls/gdi32/tests/dib.c @@ -82,6 +82,7 @@ static const char *sha1_graphics_a8r8g8b8[] = "17b2c177bdce5e94433574a928bda5c94a8cdfa5", "fe6cc678fb13a3ead67839481bf22348adc69f52", "d51bd330cec510cdccf5394328bd8e5411901e9e", + "df4aebf98d91f11be560dd232123b3ae327303d7", "f2af53dd073a09b1031d0032d28da35c82adc566", NULL }; @@ -179,7 +180,7 @@ static const RECT hline_clips[] = { 10, 134, 101, 134}, /* r end on l edgecase */ { 10, 136, 100, 136}, /* r end on l edgecase clipped */ {199, 138, 220, 138}, /* l end on r edgecase */ - {200, 140, 220, 200} /* l end on r edgecase clipped */ + {200, 140, 220, 140} /* l end on r edgecase clipped */ }; static const RECT vline_clips[] = @@ -201,6 +202,17 @@ static const RECT vline_clips[] = {140, 200, 140, 220} /* t end on b edgecase clipped */ }; +static const RECT line_clips[] = +{ + { 90, 110, 310, 120}, + { 90, 120, 295, 130}, + { 90, 190, 110, 240}, /* totally clipped, moving outcodes */ + { 90, 130, 100, 135}, /* totally clipped, end pt on l edge */ + { 90, 132, 101, 137}, /* end pt just inside l edge */ + {200, 140, 210, 141}, /* totally clipped, start pt on r edge */ + {199, 142, 210, 143} /* start pt just inside r edge */ +}; + static const RECT patblt_clips[] = { {120, 120, 140, 126}, /* unclipped */ @@ -252,6 +264,7 @@ static void draw_graphics(HDC hdc, BITMAPINFO *bmi, BYTE *bits, const char ***sh compare_hash(bmi, bits, sha1, "h and v solid lines"); memset(bits, 0xcc, dib_size); + /* diagonal lines */ SetROP2(hdc, R2_COPYPEN); for(i = 0; i < 16; i++) { @@ -320,6 +333,15 @@ static void draw_graphics(HDC hdc, BITMAPINFO *bmi, BYTE *bits, const char ***sh compare_hash(bmi, bits, sha1, "clipped solid vlines"); memset(bits, 0xcc, dib_size); + for(i = 0; i < sizeof(line_clips)/sizeof(line_clips[0]); i++) + { + MoveToEx(hdc, line_clips[i].left, line_clips[i].top, NULL); + LineTo(hdc, line_clips[i].right, line_clips[i].bottom); + } + compare_hash(bmi, bits, sha1, "clipped solid diagonal lines"); + memset(bits, 0xcc, dib_size); + + /* clipped PatBlt */ for(i = 0; i < sizeof(patblt_clips) / sizeof(patblt_clips[0]); i++) { PatBlt(hdc, patblt_clips[i].left, patblt_clips[i].top,