Index: /libcaca/trunk/tests/dirty.cpp
===================================================================
--- /libcaca/trunk/tests/dirty.cpp	(revision 3469)
+++ /libcaca/trunk/tests/dirty.cpp	(revision 3470)
@@ -39,19 +39,22 @@
     {
         caca_canvas_t *cv;
-        int xmin, xmax, ymin, ymax;
+        int i, xmin, xmax, ymin, ymax;
 
         /* Check that the dirty rectangle contains the whole canvas
          * upon creation. */
         cv = caca_create_canvas(WIDTH, HEIGHT);
-        caca_get_dirty_rectangle(cv, &xmin, &ymin, &xmax, &ymax);
-        CPPUNIT_ASSERT(xmin <= 0);
-        CPPUNIT_ASSERT(ymin <= 0);
-        CPPUNIT_ASSERT(xmax >= caca_get_canvas_width(cv) - 1);
-        CPPUNIT_ASSERT(ymax >= caca_get_canvas_height(cv) - 1);
+        i = caca_get_dirty_rectangle_count(cv);
+        CPPUNIT_ASSERT_EQUAL(i, 1);
+
+        caca_get_dirty_rectangle(cv, 0, &xmin, &ymin, &xmax, &ymax);
+        CPPUNIT_ASSERT_EQUAL(xmin, 0);
+        CPPUNIT_ASSERT_EQUAL(ymin, 0);
+        CPPUNIT_ASSERT_EQUAL(xmax, caca_get_canvas_width(cv) - 1);
+        CPPUNIT_ASSERT_EQUAL(ymax, caca_get_canvas_height(cv) - 1);
 
         /* Invalidate the dirty rectangle and check that it stays so. */
-        caca_set_dirty_rectangle(cv, -1, -1, -1, -1);
-        caca_get_dirty_rectangle(cv, &xmin, &ymin, &xmax, &ymax);
-        CPPUNIT_ASSERT(xmin > xmax || ymin > ymax);
+        caca_clear_dirty_rectangle_list(cv);
+        i = caca_get_dirty_rectangle_count(cv);
+        CPPUNIT_ASSERT_EQUAL(i, 0);
 
         caca_free_canvas(cv);
@@ -61,64 +64,76 @@
     {
         caca_canvas_t *cv;
-        int xmin, xmax, ymin, ymax;
+        int i, xmin, xmax, ymin, ymax;
 
         cv = caca_create_canvas(WIDTH, HEIGHT);
 
-        caca_set_dirty_rectangle(cv, -1, -1, -1, -1);
+        caca_clear_dirty_rectangle_list(cv);
         caca_put_char(cv, 7, 3, 'x');
-        caca_get_dirty_rectangle(cv, &xmin, &ymin, &xmax, &ymax);
-        CPPUNIT_ASSERT(xmin <= 7);
-        CPPUNIT_ASSERT(ymin <= 3);
-        CPPUNIT_ASSERT(xmax >= 7);
-        CPPUNIT_ASSERT(ymax >= 3);
+        i = caca_get_dirty_rectangle_count(cv);
+        CPPUNIT_ASSERT_EQUAL(i, 1);
+        caca_get_dirty_rectangle(cv, 0, &xmin, &ymin, &xmax, &ymax);
+        CPPUNIT_ASSERT_EQUAL(xmin, 7);
+        CPPUNIT_ASSERT_EQUAL(ymin, 3);
+        CPPUNIT_ASSERT_EQUAL(xmax, 7);
+        CPPUNIT_ASSERT_EQUAL(ymax, 3);
 
         caca_clear_canvas(cv);
-        caca_set_dirty_rectangle(cv, -1, -1, -1, -1);
+        caca_clear_dirty_rectangle_list(cv);
         caca_put_char(cv, 7, 3, 0x2f06 /* ⼆ */);
-        caca_get_dirty_rectangle(cv, &xmin, &ymin, &xmax, &ymax);
-        CPPUNIT_ASSERT(xmin <= 7);
-        CPPUNIT_ASSERT(ymin <= 3);
-        CPPUNIT_ASSERT(xmax >= 8);
-        CPPUNIT_ASSERT(ymax >= 3);
+        i = caca_get_dirty_rectangle_count(cv);
+        CPPUNIT_ASSERT_EQUAL(i, 1);
+        caca_get_dirty_rectangle(cv, 0, &xmin, &ymin, &xmax, &ymax);
+        CPPUNIT_ASSERT_EQUAL(xmin, 7);
+        CPPUNIT_ASSERT_EQUAL(ymin, 3);
+        CPPUNIT_ASSERT_EQUAL(xmax, 8);
+        CPPUNIT_ASSERT_EQUAL(ymax, 3);
 
         caca_clear_canvas(cv);
         caca_put_char(cv, 7, 3, 0x2f06 /* ⼆ */);
-        caca_set_dirty_rectangle(cv, -1, -1, -1, -1);
+        caca_clear_dirty_rectangle_list(cv);
         caca_put_char(cv, 7, 3, 'x');
-        caca_get_dirty_rectangle(cv, &xmin, &ymin, &xmax, &ymax);
-        CPPUNIT_ASSERT(xmin <= 7);
-        CPPUNIT_ASSERT(ymin <= 3);
-        CPPUNIT_ASSERT(xmax >= 8);
-        CPPUNIT_ASSERT(ymax >= 3);
+        i = caca_get_dirty_rectangle_count(cv);
+        CPPUNIT_ASSERT_EQUAL(i, 1);
+        caca_get_dirty_rectangle(cv, 0, &xmin, &ymin, &xmax, &ymax);
+        CPPUNIT_ASSERT_EQUAL(xmin, 7);
+        CPPUNIT_ASSERT_EQUAL(ymin, 3);
+        CPPUNIT_ASSERT_EQUAL(xmax, 8);
+        CPPUNIT_ASSERT_EQUAL(ymax, 3);
 
         caca_clear_canvas(cv);
         caca_put_char(cv, 7, 3, 0x2f06 /* ⼆ */);
-        caca_set_dirty_rectangle(cv, -1, -1, -1, -1);
+        caca_clear_dirty_rectangle_list(cv);
         caca_put_char(cv, 8, 3, 'x');
-        caca_get_dirty_rectangle(cv, &xmin, &ymin, &xmax, &ymax);
-        CPPUNIT_ASSERT(xmin <= 7);
-        CPPUNIT_ASSERT(ymin <= 3);
-        CPPUNIT_ASSERT(xmax >= 8);
-        CPPUNIT_ASSERT(ymax >= 3);
+        i = caca_get_dirty_rectangle_count(cv);
+        CPPUNIT_ASSERT_EQUAL(i, 1);
+        caca_get_dirty_rectangle(cv, 0, &xmin, &ymin, &xmax, &ymax);
+        CPPUNIT_ASSERT_EQUAL(xmin, 7);
+        CPPUNIT_ASSERT_EQUAL(ymin, 3);
+        CPPUNIT_ASSERT_EQUAL(xmax, 8);
+        CPPUNIT_ASSERT_EQUAL(ymax, 3);
 
         caca_clear_canvas(cv);
         caca_put_char(cv, 7, 3, 0x2f06 /* ⼆ */);
-        caca_set_dirty_rectangle(cv, -1, -1, -1, -1);
+        caca_clear_dirty_rectangle_list(cv);
         caca_put_char(cv, 6, 3, 0x2f06 /* ⼆ */);
-        caca_get_dirty_rectangle(cv, &xmin, &ymin, &xmax, &ymax);
-        CPPUNIT_ASSERT(xmin <= 6);
-        CPPUNIT_ASSERT(ymin <= 3);
-        CPPUNIT_ASSERT(xmax >= 8);
-        CPPUNIT_ASSERT(ymax >= 3);
+        i = caca_get_dirty_rectangle_count(cv);
+        CPPUNIT_ASSERT_EQUAL(i, 1);
+        caca_get_dirty_rectangle(cv, 0, &xmin, &ymin, &xmax, &ymax);
+        CPPUNIT_ASSERT_EQUAL(xmin, 6);
+        CPPUNIT_ASSERT_EQUAL(ymin, 3);
+        CPPUNIT_ASSERT_EQUAL(xmax, 8);
+        CPPUNIT_ASSERT_EQUAL(ymax, 3);
 
         caca_clear_canvas(cv);
         caca_put_char(cv, 7, 3, 0x2f06 /* ⼆ */);
-        caca_set_dirty_rectangle(cv, -1, -1, -1, -1);
+        caca_clear_dirty_rectangle_list(cv);
         caca_put_char(cv, 8, 3, 0x2f06 /* ⼆ */);
-        caca_get_dirty_rectangle(cv, &xmin, &ymin, &xmax, &ymax);
-        CPPUNIT_ASSERT(xmin <= 7);
-        CPPUNIT_ASSERT(ymin <= 3);
-        CPPUNIT_ASSERT(xmax >= 9);
-        CPPUNIT_ASSERT(ymax >= 3);
+        i = caca_get_dirty_rectangle_count(cv);
+        CPPUNIT_ASSERT_EQUAL(i, 1);
+        caca_get_dirty_rectangle(cv, 0, &xmin, &ymin, &xmax, &ymax);
+        CPPUNIT_ASSERT_EQUAL(xmin, 7);
+        CPPUNIT_ASSERT_EQUAL(ymin, 3);
+        CPPUNIT_ASSERT_EQUAL(xmax, 9);
+        CPPUNIT_ASSERT_EQUAL(ymax, 3);
     }
 
Index: /libcaca/trunk/caca/dirty.c
===================================================================
--- /libcaca/trunk/caca/dirty.c	(revision 3470)
+++ /libcaca/trunk/caca/dirty.c	(revision 3470)
@@ -0,0 +1,215 @@
+/*
+ *  libcaca       Colour ASCII-Art library
+ *  Copyright (c) 2002-2009 Sam Hocevar <sam@hocevar.net>
+ *                All Rights Reserved
+ *
+ *  $Id$
+ *
+ *  This library is free software. It comes without any warranty, to
+ *  the extent permitted by applicable law. You can redistribute it
+ *  and/or modify it under the terms of the Do What The Fuck You Want
+ *  To Public License, Version 2, as published by Sam Hocevar. See
+ *  http://sam.zoy.org/wtfpl/COPYING for more details.
+ */
+
+/*
+ *  This file contains the dirty rectangle handling functions.
+ */
+
+#include "config.h"
+
+#if !defined(__KERNEL__)
+#   include <stdio.h>
+#endif
+
+#include "caca.h"
+#include "caca_internals.h"
+
+/** \brief Get the number of dirty rectangles in the canvas.
+ *
+ *  Get the number of dirty rectangles in a canvas. Dirty rectangles are
+ *  areas that contain cells that have changed since the last reset.
+ *
+ *  The dirty rectangles are used internally by display drivers to optimise
+ *  rendering by avoiding to redraw the whole screen. Once the display driver
+ *  has rendered the canvas, it resets the dirty rectangle list.
+ *
+ *  Dirty rectangles are guaranteed not to overlap.
+ *
+ *  This function never fails.
+ *
+ *  \param cv A libcaca canvas.
+ *  \return The number of dirty rectangles in the given canvas.
+ */
+int caca_get_dirty_rectangle_count(caca_canvas_t *cv)
+{
+    /* Ignore empty rectangles. */
+    if(cv->dirty_xmin > cv->dirty_xmax || cv->dirty_ymin > cv->dirty_ymax)
+        return 0;
+
+    /* Ignore out-of-bounds rectangles. */
+    if(cv->dirty_xmax < 0 || cv->dirty_xmin >= cv->width
+        || cv->dirty_ymax < 0 || cv->dirty_ymin >= cv->height)
+        return 0;
+
+    return 1;
+}
+
+/** \brief Get a canvas's dirty rectangle.
+ *
+ *  Get the canvas's given dirty rectangle coordinates. The index must be
+ *  within the dirty rectangle count. See caca_get_dirty_rectangle_count()
+ *  for how to compute this count.
+ *
+ *  If an error occurs, no coordinates are written in the pointer arguments,
+ *  -1 is returned and \b errno is set accordingly:
+ *  - \c EINVAL Specified rectangle index is out of bounds.
+ *
+ *  \param cv A libcaca canvas.
+ *  \param index The requested rectangle index.
+ *  \param xmin A pointer to an integer where the leftmost edge of the
+ *              dirty rectangle will be stored.
+ *  \param ymin A pointer to an integer where the topmost edge of the
+ *              dirty rectangle will be stored.
+ *  \param xmax A pointer to an integer where the rightmost edge of the
+ *              dirty rectangle will be stored.
+ *  \param ymax A pointer to an integer where the bottommost edge of the
+ *              dirty rectangle will be stored.
+ *  \return 0 in case of success, -1 if an error occurred.
+ */
+int caca_get_dirty_rectangle(caca_canvas_t *cv, int index,
+                             int *xmin, int *ymin, int *xmax, int *ymax)
+{
+    /* For now, index can only be zero, and if the dirty rectangle is
+     * empty, we don't return anything. */
+    if(index < 0 || index > 0
+        || cv->dirty_xmin > cv->dirty_xmax || cv->dirty_ymin > cv->dirty_ymax
+        || cv->dirty_xmax < 0 || cv->dirty_xmin >= cv->width
+        || cv->dirty_ymax < 0 || cv->dirty_ymin >= cv->height)
+    {
+        seterrno(EINVAL);
+        return -1;
+    }
+
+    /* Normalise dirty rectangle so that the values can be directly used. */
+    if(cv->dirty_xmin < 0)
+        cv->dirty_xmin = 0;
+
+    if(cv->dirty_xmax > cv->width - 1)
+        cv->dirty_xmax = cv->width - 1;
+
+    if(cv->dirty_ymin < 0)
+        cv->dirty_ymin = 0;
+
+    if(cv->dirty_ymax > cv->height - 1)
+        cv->dirty_ymax = cv->height - 1;
+
+    *xmin = cv->dirty_xmin;
+    *xmax = cv->dirty_xmax;
+    *ymin = cv->dirty_ymin;
+    *ymax = cv->dirty_ymax;
+
+    return 0;
+}
+
+/** \brief Add an area to the canvas's dirty rectangle list.
+ *
+ *  Add an invalidating zone to the canvas's dirty rectangle list. For more
+ *  information about the dirty rectangles, see caca_get_dirty_rectangle().
+ *
+ *  This function may be useful to force refresh of a given zone of the
+ *  canvas even if the dirty rectangle tracking indicates that it is
+ *  unchanged. This may happen if the canvas contents were somewhat
+ *  directly modified.
+ *
+ *  If an error occurs, -1 is returned and \b errno is set accordingly:
+ *  - \c EINVAL Specified rectangle coordinates are out of bounds.
+ *
+ *  \param cv A libcaca canvas.
+ *  \param xmin The leftmost edge of the additional dirty rectangle.
+ *  \param ymin The topmost edge of the additional dirty rectangle.
+ *  \param xmax The rightmost edge of the additional dirty rectangle.
+ *  \param ymax The bottommost edge of the additional dirty rectangle.
+ *  \return 0 in case of success, -1 if an error occurred.
+ */
+int caca_add_dirty_rectangle(caca_canvas_t *cv, int xmin, int ymin,
+                             int xmax, int ymax)
+{
+    /* Ignore empty and out-of-bounds rectangles */
+    if(xmin > xmax || ymin > ymax
+        || xmax < 0 || xmin >= cv->width || ymax < 0 || ymin >= cv->height)
+    {
+        seterrno(EINVAL);
+        return -1;
+    }
+
+    if(xmin < cv->dirty_xmin)
+        cv->dirty_xmin = xmin;
+
+    if(xmax > cv->dirty_xmax)
+        cv->dirty_xmax = xmax;
+
+    if(ymin < cv->dirty_ymin)
+        cv->dirty_ymin = ymin;
+
+    if(ymax > cv->dirty_ymax)
+        cv->dirty_ymax = ymax;
+
+    return 0;
+}
+
+/** \brief Remove an area from the dirty rectangle list.
+ *
+ *  Mark a cell area in the canvas as not dirty. For more information about
+ *  the dirty rectangles, see caca_get_dirty_rectangle().
+ *
+ *  Values such that \b xmin > \b xmax or \b ymin > \b ymax indicate that
+ *  the dirty rectangle is empty. They will be silently ignored.
+ *
+ *  If an error occurs, -1 is returned and \b errno is set accordingly:
+ *  - \c EINVAL Specified rectangle coordinates are out of bounds.
+ *
+ *  \param cv A libcaca canvas.
+ *  \param xmin The leftmost edge of the clean rectangle.
+ *  \param ymin The topmost edge of the clean rectangle.
+ *  \param xmax The rightmost edge of the clean rectangle.
+ *  \param ymax The bottommost edge of the clean rectangle.
+ *  \return 0 in case of success, -1 if an error occurred.
+ */
+int caca_remove_dirty_rectangle(caca_canvas_t *cv, int xmin, int ymin,
+                                int xmax, int ymax)
+{
+    /* Ignore empty and out-of-bounds rectangles */
+    if(xmin > xmax || ymin > ymax
+        || xmax < 0 || xmin >= cv->width || ymax < 0 || ymin >= cv->height)
+    {
+        seterrno(EINVAL);
+        return -1;
+    }
+
+    /* FIXME: implement this function. It's OK to have it do nothing,
+     * since we take a conservative approach in dirty rectangle handling,
+     * but we ought to help the rendering eventually. */
+
+    return 0;
+}
+
+/** \brief Clear a canvas's dirty rectangle list.
+ *
+ *  Empty the canvas's dirty rectangle list.
+ *
+ *  This function never fails.
+ *
+ *  \param cv A libcaca canvas.
+ *  \return This function always returns 0.
+ */
+int caca_clear_dirty_rectangle_list(caca_canvas_t *cv)
+{
+    cv->dirty_xmin = cv->width;
+    cv->dirty_xmax = -1;
+    cv->dirty_ymin = cv->height;
+    cv->dirty_ymax = -1;
+
+    return 0;
+}
+
Index: /libcaca/trunk/caca/graphics.c
===================================================================
--- /libcaca/trunk/caca/graphics.c	(revision 3469)
+++ /libcaca/trunk/caca/graphics.c	(revision 3470)
@@ -156,5 +156,5 @@
 
     /* Invalidate the dirty rectangle */
-    caca_set_dirty_rectangle(dp->cv, -1, -1, -1, -1);
+    caca_clear_dirty_rectangle_list(dp->cv);
 
     /* Once the display is finished, we can ack resizes */
Index: /libcaca/trunk/caca/string.c
===================================================================
--- /libcaca/trunk/caca/string.c	(revision 3469)
+++ /libcaca/trunk/caca/string.c	(revision 3470)
@@ -323,5 +323,5 @@
     }
 
-    caca_set_dirty_rectangle(cv, 0, 0, cv->width - 1, cv->height - 1);
+    caca_add_dirty_rectangle(cv, 0, 0, cv->width - 1, cv->height - 1);
 
     return 0;
@@ -529,5 +529,5 @@
 
     /* FIXME: this may be optimised somewhat */
-    caca_set_dirty_rectangle(cv, 0, 0, cv->width - 1, cv->height - 1);
+    caca_add_dirty_rectangle(cv, 0, 0, cv->width - 1, cv->height - 1);
 
     return 0;
Index: /libcaca/trunk/caca/driver/slang.c
===================================================================
--- /libcaca/trunk/caca/driver/slang.c	(revision 3469)
+++ /libcaca/trunk/caca/driver/slang.c	(revision 3470)
@@ -223,57 +223,59 @@
     uint32_t const *cvchars = (uint32_t const *)caca_get_canvas_chars(dp->cv);
     uint32_t const *cvattrs = (uint32_t const *)caca_get_canvas_attrs(dp->cv);
-    int x, y;
-    int xmin, ymin, xmax, ymax;
-
-    caca_get_dirty_rectangle(dp->cv, &xmin, &ymin, &xmax, &ymax);
-    if(xmin > xmax || ymin > ymax)
-        return;
-
-    for(y = ymin; y <= ymax; y++)
-    {
-        SLsmg_gotorc(y, 0);
-        for(x = xmax; x >= xmin; x--)
+    int x, y, i;
+
+    for(i = 0; i < caca_get_dirty_rectangle_count(dp->cv); i++)
+    {
+        int xmin, ymin, xmax, ymax;
+
+        caca_get_dirty_rectangle(dp->cv, i, &xmin, &ymin, &xmax, &ymax);
+
+        for(y = ymin; y <= ymax; y++)
         {
-            uint32_t ch = *cvchars++;
+            SLsmg_gotorc(y, 0);
+            for(x = xmax; x >= xmin; x--)
+            {
+                uint32_t ch = *cvchars++;
 
 #if defined(OPTIMISE_SLANG_PALETTE)
-            /* If foreground == background, just don't use this colour
-             * pair, and print a space instead of the real character.
-             * XXX: disabled, because I can't remember what it was
-             * here for, and in cases where SLang does not render
-             * bright backgrounds, it's just fucked up. */
+                /* If foreground == background, just don't use this colour
+                 * pair, and print a space instead of the real character. */
+                /* XXX: disabled, because I can't remember what it was
+                 * here for, and in cases where SLang does not render
+                 * bright backgrounds, it's just fucked up. */
 #if 0
-            uint8_t fgcolor = caca_attr_to_ansi_fg(*cvattrs);
-            uint8_t bgcolor = caca_attr_to_ansi_bg(*cvattrs);
-
-            if(fgcolor >= 0x10)
-                fgcolor = CACA_LIGHTGRAY;
-
-            if(bgcolor >= 0x10)
-                bgcolor = CACA_BLACK; /* FIXME: handle transparency */
-
-            if(fgcolor == bgcolor)
-            {
-                if(fgcolor == CACA_BLACK)
-                    fgcolor = CACA_WHITE;
-                else if(fgcolor == CACA_WHITE
-                         || fgcolor <= CACA_LIGHTGRAY)
-                    fgcolor = CACA_BLACK;
+                uint8_t fgcolor = caca_attr_to_ansi_fg(*cvattrs);
+                uint8_t bgcolor = caca_attr_to_ansi_bg(*cvattrs);
+
+                if(fgcolor >= 0x10)
+                    fgcolor = CACA_LIGHTGRAY;
+
+                if(bgcolor >= 0x10)
+                    bgcolor = CACA_BLACK; /* FIXME: handle transparency */
+
+                if(fgcolor == bgcolor)
+                {
+                    if(fgcolor == CACA_BLACK)
+                        fgcolor = CACA_WHITE;
+                    else if(fgcolor == CACA_WHITE
+                             || fgcolor <= CACA_LIGHTGRAY)
+                        fgcolor = CACA_BLACK;
+                    else
+                        fgcolor = CACA_WHITE;
+                    SLsmg_set_color(slang_assoc[fgcolor + 16 * bgcolor]);
+                    SLsmg_write_char(' ');
+                    cvattrs++;
+                }
                 else
-                    fgcolor = CACA_WHITE;
-                SLsmg_set_color(slang_assoc[fgcolor + 16 * bgcolor]);
-                SLsmg_write_char(' ');
-                cvattrs++;
+#endif
+                {
+                    SLsmg_set_color(slang_assoc[caca_attr_to_ansi(*cvattrs++)]);
+                    slang_write_utf32(ch);
+                }
+#else
+                SLsmg_set_color(caca_attr_to_ansi(*cvattrs++));
+                slang_write_utf32(ch);
+#endif
             }
-            else
-#endif
-            {
-                SLsmg_set_color(slang_assoc[caca_attr_to_ansi(*cvattrs++)]);
-                slang_write_utf32(ch);
-            }
-#else
-            SLsmg_set_color(caca_attr_to_ansi(*cvattrs++));
-            slang_write_utf32(ch);
-#endif
         }
     }
Index: /libcaca/trunk/caca/driver/x11.c
===================================================================
--- /libcaca/trunk/caca/driver/x11.c	(revision 3469)
+++ /libcaca/trunk/caca/driver/x11.c	(revision 3470)
@@ -294,56 +294,59 @@
     int width = caca_get_canvas_width(dp->cv);
     int height = caca_get_canvas_height(dp->cv);
-    int x, y, len;
-    int xmin, ymin, xmax, ymax;
-
-    caca_get_dirty_rectangle(dp->cv, &xmin, &ymin, &xmax, &ymax);
-    if(xmin > xmax || ymin > ymax)
-        return;
-
-    /* First draw the background colours. Splitting the process in two
-     * loops like this is actually slightly faster. */
-    for(y = ymin; y <= ymax; y++)
-    {
-        for(x = xmin; x <= xmax; x += len)
-        {
-            uint32_t const *attrs = cvattrs + x + y * width;
-            uint16_t bg = caca_attr_to_rgb12_bg(*attrs);
-
-            len = 1;
-            while(x + len < width
-                   && caca_attr_to_rgb12_bg(attrs[len]) == bg)
-                len++;
-
-            XSetForeground(dp->drv.p->dpy, dp->drv.p->gc,
-                           dp->drv.p->colors[bg]);
-            XFillRectangle(dp->drv.p->dpy, dp->drv.p->pixmap, dp->drv.p->gc,
-                           x * dp->drv.p->font_width,
-                           y * dp->drv.p->font_height,
-                           len * dp->drv.p->font_width,
-                           dp->drv.p->font_height);
-        }
-    }
-
-    /* Then print the foreground characters */
-    for(y = ymin; y <= ymax; y++)
-    {
-        int yoff = (y + 1) * dp->drv.p->font_height
-                                    - dp->drv.p->font_offset;
-        uint32_t const *chars = cvchars + y * width;
-        uint32_t const *attrs = cvattrs + y * width;
-
-        for(x = xmin; x <= xmax; x++, chars++, attrs++)
-        {
-            XSetForeground(dp->drv.p->dpy, dp->drv.p->gc,
+    int x, y, i, len;
+
+    for(i = 0; i < caca_get_dirty_rectangle_count(dp->cv); i++)
+    {
+        int xmin, ymin, xmax, ymax;
+
+        caca_get_dirty_rectangle(dp->cv, i, &xmin, &ymin, &xmax, &ymax);
+
+        /* First draw the background colours. Splitting the process in two
+         * loops like this is actually slightly faster. */
+        for(y = ymin; y <= ymax; y++)
+        {
+            for(x = xmin; x <= xmax; x += len)
+            {
+                uint32_t const *attrs = cvattrs + x + y * width;
+                uint16_t bg = caca_attr_to_rgb12_bg(*attrs);
+
+                len = 1;
+                while(x + len < width
+                       && caca_attr_to_rgb12_bg(attrs[len]) == bg)
+                    len++;
+
+                XSetForeground(dp->drv.p->dpy, dp->drv.p->gc,
+                               dp->drv.p->colors[bg]);
+                XFillRectangle(dp->drv.p->dpy, dp->drv.p->pixmap,
+                               dp->drv.p->gc,
+                               x * dp->drv.p->font_width,
+                               y * dp->drv.p->font_height,
+                               len * dp->drv.p->font_width,
+                               dp->drv.p->font_height);
+            }
+        }
+
+        /* Then print the foreground characters */
+        for(y = ymin; y <= ymax; y++)
+        {
+            int yoff = (y + 1) * dp->drv.p->font_height
+                                        - dp->drv.p->font_offset;
+            uint32_t const *chars = cvchars + y * width;
+            uint32_t const *attrs = cvattrs + y * width;
+
+            for(x = xmin; x <= xmax; x++, chars++, attrs++)
+            {
+                XSetForeground(dp->drv.p->dpy, dp->drv.p->gc,
                            dp->drv.p->colors[caca_attr_to_rgb12_fg(*attrs)]);
 
-            x11_put_glyph(dp, x * dp->drv.p->font_width,
-                          y * dp->drv.p->font_height, yoff,
-                          dp->drv.p->font_width, dp->drv.p->font_height,
-                          *attrs, *chars);
-        }
-    }
-
-    /* Print the cursor if necessary */
+                x11_put_glyph(dp, x * dp->drv.p->font_width,
+                              y * dp->drv.p->font_height, yoff,
+                              dp->drv.p->font_width, dp->drv.p->font_height,
+                              *attrs, *chars);
+            }
+        }
+    }
+
+    /* Print the cursor if necessary. FIXME: handle dirty rectangles! */
     if(dp->drv.p->cursor_flags)
     {
Index: /libcaca/trunk/caca/transform.c
===================================================================
--- /libcaca/trunk/caca/transform.c	(revision 3469)
+++ /libcaca/trunk/caca/transform.c	(revision 3470)
@@ -55,5 +55,5 @@
     }
 
-    caca_set_dirty_rectangle(cv, 0, 0, cv->width - 1, cv->height - 1);
+    caca_add_dirty_rectangle(cv, 0, 0, cv->width - 1, cv->height - 1);
 
     return 0;
@@ -116,5 +116,5 @@
     }
 
-    caca_set_dirty_rectangle(cv, 0, 0, cv->width - 1, cv->height - 1);
+    caca_add_dirty_rectangle(cv, 0, 0, cv->width - 1, cv->height - 1);
 
     return 0;
@@ -163,5 +163,5 @@
     }
 
-    caca_set_dirty_rectangle(cv, 0, 0, cv->width - 1, cv->height - 1);
+    caca_add_dirty_rectangle(cv, 0, 0, cv->width - 1, cv->height - 1);
 
     return 0;
@@ -222,5 +222,5 @@
     }
 
-    caca_set_dirty_rectangle(cv, 0, 0, cv->width - 1, cv->height - 1);
+    caca_add_dirty_rectangle(cv, 0, 0, cv->width - 1, cv->height - 1);
 
     return 0;
@@ -341,5 +341,5 @@
     _caca_load_frame_info(cv);
 
-    caca_set_dirty_rectangle(cv, 0, 0, cv->width - 1, cv->height - 1);
+    caca_add_dirty_rectangle(cv, 0, 0, cv->width - 1, cv->height - 1);
 
     return 0;
@@ -460,5 +460,5 @@
     _caca_load_frame_info(cv);
 
-    caca_set_dirty_rectangle(cv, 0, 0, cv->width - 1, cv->height - 1);
+    caca_add_dirty_rectangle(cv, 0, 0, cv->width - 1, cv->height - 1);
 
     return 0;
@@ -553,5 +553,5 @@
     _caca_load_frame_info(cv);
 
-    caca_set_dirty_rectangle(cv, 0, 0, cv->width - 1, cv->height - 1);
+    caca_add_dirty_rectangle(cv, 0, 0, cv->width - 1, cv->height - 1);
 
     return 0;
@@ -646,5 +646,5 @@
     _caca_load_frame_info(cv);
 
-    caca_set_dirty_rectangle(cv, 0, 0, cv->width - 1, cv->height - 1);
+    caca_add_dirty_rectangle(cv, 0, 0, cv->width - 1, cv->height - 1);
 
     return 0;
Index: /libcaca/trunk/caca/canvas.c
===================================================================
--- /libcaca/trunk/caca/canvas.c	(revision 3469)
+++ /libcaca/trunk/caca/canvas.c	(revision 3470)
@@ -297,131 +297,4 @@
 {
     return (uint8_t const *)cv->attrs;
-}
-
-/** \brief Get a canvas's dirty rectangle.
- *
- *  Get the canvas's dirty rectangle coordinates. The dirty rectangle is
- *  the smallest area containing all the cells that have changed since it
- *  was last reset.
- *
- *  The dirty rectangle is used internally by display drivers to optimise
- *  rendering by avoiding to redraw the whole screen. Once the display driver
- *  has rendered the canvas, it resets the dirty rectangle.
- *
- *  Values such that \b xmin > \b xmax or \b ymin > \b ymax indicate that
- *  the dirty rectangle is empty. It means that the canvas's contents have
- *  not changed since the dirty rectangle was last reset.
- *
- *  FIXME: having only one dirty rectangle instead of a list of rectangles
- *  is a severe limitation, but the potential gain does not yet look to be
- *  worth the implementation complexity of a multiple-rectangle scheme.
- *
- *  This function never fails.
- *
- *  \param cv A libcaca canvas.
- *  \param xmin A pointer to an integer where the leftmost edge of the
- *              dirty rectangle will be stored.
- *  \param ymin A pointer to an integer where the topmost edge of the
- *              dirty rectangle will be stored.
- *  \param xmax A pointer to an integer where the rightmost edge of the
- *              dirty rectangle will be stored.
- *  \param ymax A pointer to an integer where the bottommost edge of the
- *              dirty rectangle will be stored.
- *  \return This function always returns 0.
- */
-int caca_get_dirty_rectangle(caca_canvas_t *cv, int *xmin, int *ymin,
-                             int *xmax, int *ymax)
-{
-    *xmin = cv->dirty_xmin;
-    *xmax = cv->dirty_xmax;
-    *ymin = cv->dirty_ymin;
-    *ymax = cv->dirty_ymax;
-
-    return 0;
-}
-
-/** \brief Add a dirty rectangle to the canvas's dirty rectangle.
- *
- *  Add an invalidating zone to the canvas's dirty rectangle. For more
- *  information about the dirty rectangle, see caca_get_dirty_rectangle().
- *
- *  This function may be useful to force refresh of a given zone of the
- *  canvas even if the dirty rectangle tracking indicates that it is
- *  unchanged.
- *
- *  Values such that \b xmin > \b xmax or \b ymin > \b ymax indicate that
- *  the dirty rectangle is empty. They will be silently ignored.
- *
- *  This function never fails.
- *
- *  \param cv A libcaca canvas.
- *  \param xmin The leftmost edge of the additional dirty rectangle.
- *  \param ymin The topmost edge of the additional dirty rectangle.
- *  \param xmax The rightmost edge of the additional dirty rectangle.
- *  \param ymax The bottommost edge of the additional dirty rectangle.
- *  \return This function always returns 0.
- */
-int caca_add_dirty_rectangle(caca_canvas_t *cv, int xmin, int ymin,
-                             int xmax, int ymax)
-{
-    /* Ignore empty rectangles. */
-    if(xmin > xmax || ymin > ymax)
-        return 0;
-
-    /* Ignore out-of-bounds rectangles. */
-    if(xmax < 0 || xmin >= cv->width || ymax < 0 || ymin >= cv->height)
-        return 0;
-
-    if(xmin < cv->dirty_xmin)
-        cv->dirty_xmin = xmin;
-
-    if(xmax > cv->dirty_xmax)
-        cv->dirty_xmax = xmax;
-
-    if(ymin < cv->dirty_ymin)
-        cv->dirty_ymin = ymin;
-
-    if(ymax > cv->dirty_ymax)
-        cv->dirty_ymax = ymax;
-
-    return 0;
-}
-
-/** \brief Set a canvas's dirty rectangle.
- *
- *  Set the canvas's dirty rectangle coordinates. For more information
- *  about the dirty rectangle, see caca_get_dirty_rectangle().
- *
- *  Values such that \b xmin > \b xmax or \b ymin > \b ymax indicate that
- *  the dirty rectangle is empty.
- *
- *  This function never fails.
- *
- *  \param cv A libcaca canvas.
- *  \param xmin The leftmost edge of the desired dirty rectangle.
- *  \param ymin The topmost edge of the desired dirty rectangle.
- *  \param xmax The rightmost edge of the desired dirty rectangle.
- *  \param ymax The bottommost edge of the desired dirty rectangle.
- *  \return This function always returns 0.
- */
-int caca_set_dirty_rectangle(caca_canvas_t *cv, int xmin, int ymin,
-                             int xmax, int ymax)
-{
-    /* Normalise values indicating an empty or out-of-bounds rectangle. */
-    if(xmin > xmax || ymin > ymax ||
-        xmax < 0 || xmin >= cv->width || ymax < 0 || ymin >= cv->height)
-    {
-        xmin = cv->width;
-        xmax = -1;
-        ymin = cv->height;
-        ymax = -1;
-    }
-
-    cv->dirty_xmin = xmin;
-    cv->dirty_xmax = xmax;
-    cv->dirty_ymin = ymin;
-    cv->dirty_ymax = ymax;
-
-    return 0;
 }
 
Index: /libcaca/trunk/caca/Makefile.am
===================================================================
--- /libcaca/trunk/caca/Makefile.am	(revision 3469)
+++ /libcaca/trunk/caca/Makefile.am	(revision 3470)
@@ -24,4 +24,5 @@
 	caca0.h \
 	canvas.c \
+	dirty.c \
 	string.c \
         legacy.c \
Index: /libcaca/trunk/caca/frame.c
===================================================================
--- /libcaca/trunk/caca/frame.c	(revision 3469)
+++ /libcaca/trunk/caca/frame.c	(revision 3470)
@@ -73,5 +73,5 @@
     _caca_load_frame_info(cv);
 
-    caca_set_dirty_rectangle(cv, 0, 0, cv->width - 1, cv->height - 1);
+    caca_add_dirty_rectangle(cv, 0, 0, cv->width - 1, cv->height - 1);
 
     return 0;
@@ -237,5 +237,5 @@
         cv->frame = 0;
         _caca_load_frame_info(cv);
-        caca_set_dirty_rectangle(cv, 0, 0, cv->width - 1, cv->height - 1);
+        caca_add_dirty_rectangle(cv, 0, 0, cv->width - 1, cv->height - 1);
     }
 
Index: /libcaca/trunk/caca/caca.h
===================================================================
--- /libcaca/trunk/caca/caca.h	(revision 3469)
+++ /libcaca/trunk/caca/caca.h	(revision 3470)
@@ -221,8 +221,4 @@
  *  @{ */
 #define CACA_MAGIC_FULLWIDTH 0x000ffffe /**< Used to indicate that the previous character was a fullwidth glyph. */
-__extern int caca_get_dirty_rectangle(caca_canvas_t *, int *, int *,
-                                      int *, int *);
-__extern int caca_add_dirty_rectangle(caca_canvas_t *, int, int, int, int);
-__extern int caca_set_dirty_rectangle(caca_canvas_t *, int, int, int, int);
 __extern int caca_gotoxy(caca_canvas_t *, int, int);
 __extern int caca_get_cursor_x(caca_canvas_t const *);
@@ -239,4 +235,16 @@
                        caca_canvas_t const *);
 __extern int caca_set_canvas_boundaries(caca_canvas_t *, int, int, int, int);
+/*  @} */
+
+/** \defgroup caca_dirty libcaca dirty rectangle manipulation
+ *
+ *  These functions manipulate dirty rectangles for optimised blitting.
+ *  @{ */
+__extern int caca_get_dirty_rectangle_count(caca_canvas_t *);
+__extern int caca_get_dirty_rectangle(caca_canvas_t *, int, int *, int *,
+                                      int *, int *);
+__extern int caca_add_dirty_rectangle(caca_canvas_t *, int, int, int, int);
+__extern int caca_remove_dirty_rectangle(caca_canvas_t *, int, int, int, int);
+__extern int caca_clear_dirty_rectangle_list(caca_canvas_t *);
 /*  @} */
 
Index: /libcaca/trunk/caca/libcaca.vcproj
===================================================================
--- /libcaca/trunk/caca/libcaca.vcproj	(revision 3469)
+++ /libcaca/trunk/caca/libcaca.vcproj	(revision 3470)
@@ -418,4 +418,8 @@
 		</File>
 		<File
+			RelativePath=".\dirty.c"
+			>
+		</File>
+		<File
 			RelativePath=".\dither.c"
 			>
