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, &params, &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, &params,
+                                    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,