[Spice-commits] 10 commits - client/canvas.cpp client/canvas.h client/jpeg_decoder.cpp client/jpeg_decoder.h client/Makefile.am client/red_gdi_canvas.cpp client/red_gl_canvas.cpp client/red_sw_canvas.cpp client/screen.cpp client/shared_cache.hpp client/windows client/x11 common/canvas_base.c common/canvas_base.h common/gdi_canvas.c common/gdi_canvas.h common/gl_canvas.c common/gl_canvas.h common/sw_canvas.c common/sw_canvas.h server/jpeg_encoder.c server/jpeg_encoder.h server/Makefile.am server/red_client_shared_cache.h server/red_worker.c

Alexander Larsson alexl at kemper.freedesktop.org
Wed Jun 9 02:42:25 PDT 2010


 client/Makefile.am               |    2 
 client/canvas.cpp                |    2 
 client/canvas.h                  |   27 
 client/jpeg_decoder.cpp          |  147 +++
 client/jpeg_decoder.h            |   91 ++
 client/red_gdi_canvas.cpp        |    3 
 client/red_gl_canvas.cpp         |    3 
 client/red_sw_canvas.cpp         |    6 
 client/screen.cpp                |    2 
 client/shared_cache.hpp          |   80 +
 client/windows/redc.vcproj       |    8 
 client/x11/Makefile.am           |    2 
 common/canvas_base.c             |  136 +++
 common/canvas_base.h             |   28 
 common/gdi_canvas.c              |    4 
 common/gdi_canvas.h              |    3 
 common/gl_canvas.c               |    2 
 common/gl_canvas.h               |    1 
 common/sw_canvas.c               |    6 
 common/sw_canvas.h               |    2 
 server/Makefile.am               |    2 
 server/jpeg_encoder.c            |  242 +++++
 server/jpeg_encoder.h            |   61 +
 server/red_client_shared_cache.h |   24 
 server/red_worker.c              | 1578 ++++++++++++++++++++++++++++++++++++---
 25 files changed, 2346 insertions(+), 116 deletions(-)

New commits:
commit 8b023600330588b959b151a1242b05f385633f90
Author: Yonit Halperin <yhalperi at redhat.com>
Date:   Tue Jun 1 10:30:58 2010 +0300

    init/destroy lossy surface region

diff --git a/server/red_worker.c b/server/red_worker.c
index e8487ff..b1fb642 100644
--- a/server/red_worker.c
+++ b/server/red_worker.c
@@ -8965,6 +8965,7 @@ static void red_send_surface_create(DisplayChannel *display, SpiceMsgSurfaceCrea
     ASSERT(display);
     channel = &display->base;
 
+    region_init(&display->surface_client_lossy_region[surface_create->surface_id]);
     channel->send_data.header.type = SPICE_MSG_DISPLAY_SURFACE_CREATE;
     display->send_data.u.surface_create = *surface_create;
 
@@ -8981,6 +8982,7 @@ static void red_send_surface_destroy(DisplayChannel *display, uint32_t surface_i
     ASSERT(display);
     channel = &display->base;
 
+    region_destroy(&display->surface_client_lossy_region[surface_id]);
     channel->send_data.header.type = SPICE_MSG_DISPLAY_SURFACE_DESTROY;
     display->send_data.u.surface_destroy.surface_id = surface_id;
 
commit ba32024ad1d9b36a22f8ea5b02a90e16624302e8
Author: Yonit Halperin <yhalperi at redhat.com>
Date:   Tue Jun 1 10:30:57 2010 +0300

    enabling jpeg on low bandwidth connection

diff --git a/server/red_worker.c b/server/red_worker.c
index bdc7ebb..e8487ff 100644
--- a/server/red_worker.c
+++ b/server/red_worker.c
@@ -710,6 +710,8 @@ struct DisplayChannel {
         FreeList free_list;
     } send_data;
 
+    int enable_jpeg;
+    int jpeg_quality;
 #ifdef RED_STATISTICS
     StatNodeRef stat;
     uint64_t *cache_hits_counter;
@@ -1020,8 +1022,6 @@ typedef struct RedWorker {
 #endif
     SpiceVirtMapping preload_group_virt_mapping;
 
-    int enable_jpeg;
-    int jpeg_quality;
 } RedWorker;
 
 typedef enum {
@@ -5926,10 +5926,6 @@ static inline void red_init_jpeg(RedWorker *worker)
     if (!worker->jpeg) {
         PANIC("create jpeg encoder failed");
     }
-
-    // TODO: configure via qemu command line and monitor, and activate only on WAN
-    worker->enable_jpeg = TRUE;
-    worker->jpeg_quality = 85;
 }
 
 #ifdef __GNUC__
@@ -6359,13 +6355,13 @@ static int red_jpeg_compress_image(DisplayChannel *display_channel, RedImage *de
                                  sizeof(jpeg_data->data.u.unstable_lines_data.input_bufs[0]->buf) /
                                  jpeg_data->data.u.unstable_lines_data.dest_stride;
             jpeg_data->usr.more_lines = jpeg_usr_more_lines_unstable;
-            size = jpeg_encode(jpeg, worker->jpeg_quality, jpeg_in_type, src->x, src->y, NULL, 0, src->stride,
-                               (uint8_t*)jpeg_data->data.bufs_head->buf,
+            size = jpeg_encode(jpeg, display_channel->jpeg_quality, jpeg_in_type, src->x, src->y,
+                               NULL, 0, src->stride, (uint8_t*)jpeg_data->data.bufs_head->buf,
                                sizeof(jpeg_data->data.bufs_head->buf));
         } else {
             jpeg_data->usr.more_lines = jpeg_usr_no_more_lines;
-            size = jpeg_encode(jpeg, worker->jpeg_quality, jpeg_in_type, src->x, src->y, data, src->y, stride,
-                               (uint8_t*)jpeg_data->data.bufs_head->buf,
+            size = jpeg_encode(jpeg, display_channel->jpeg_quality, jpeg_in_type, src->x, src->y,
+                               data, src->y, stride, (uint8_t*)jpeg_data->data.bufs_head->buf,
                                sizeof(jpeg_data->data.bufs_head->buf));
         }
     } else {
@@ -6403,8 +6399,8 @@ static int red_jpeg_compress_image(DisplayChannel *display_channel, RedImage *de
             jpeg_data->usr.more_lines = jpeg_usr_more_lines_reverse;
             stride = -src->stride;
         }
-        size = jpeg_encode(jpeg, worker->jpeg_quality, jpeg_in_type, src->x, src->y, NULL, 0, stride,
-                           (uint8_t*)jpeg_data->data.bufs_head->buf,
+        size = jpeg_encode(jpeg, display_channel->jpeg_quality, jpeg_in_type, src->x, src->y, NULL,
+                           0, stride, (uint8_t*)jpeg_data->data.bufs_head->buf,
                            sizeof(jpeg_data->data.bufs_head->buf));
     }
 
@@ -6633,7 +6629,7 @@ static inline int red_compress_image(DisplayChannel *display_channel,
         red_printf("QUIC compress");
 #endif
         // if bitmaps is picture-like, compress it using jpeg
-        if (can_lossy && display_channel->base.worker->enable_jpeg &&
+        if (can_lossy && display_channel->enable_jpeg &&
             ((image_compression == SPICE_IMAGE_COMPRESS_AUTO_LZ) ||
             (image_compression == SPICE_IMAGE_COMPRESS_AUTO_GLZ))) {
             if (src->format != SPICE_BITMAP_FMT_RGBA) {
@@ -6755,7 +6751,7 @@ static FillBitsType fill_bits(DisplayChannel *display_channel, QXLPHYSICAL *in_b
         if (pixmap_cache_hit(display_channel->pixmap_cache, image->descriptor.id,
                              &lossy_cache_item, display_channel)) {
             if (can_lossy || !lossy_cache_item) {
-                if (!worker->enable_jpeg || lossy_cache_item) {
+                if (!display_channel->enable_jpeg || lossy_cache_item) {
                     image->descriptor.type = SPICE_IMAGE_TYPE_FROM_CACHE;
                 } else {
                     // making sure, in multiple monitor scenario, that lossy items that
@@ -8511,7 +8507,7 @@ static inline void send_qxl_drawable(DisplayChannel *display_channel, Drawable *
     if (item->stream && red_send_stream_data(display_channel, item)) {
         return;
     }
-    if (!display_channel->base.worker->enable_jpeg)
+    if (!display_channel->enable_jpeg)
         red_send_qxl_drawable(display_channel->base.worker, display_channel, item);
     else
         red_lossy_send_qxl_drawable(display_channel->base.worker, display_channel, item);
@@ -8711,7 +8707,7 @@ static void red_send_image(DisplayChannel *display_channel, ImageItem *item)
                                                           &bitmap,
                                                           worker->mem_slots.internal_groupslot_id);
                 if (grad_level == BITMAP_GRADUAL_HIGH) {
-                    lossy_comp = worker->enable_jpeg && item->can_lossy &&
+                    lossy_comp = display_channel->enable_jpeg && item->can_lossy &&
                                  (item->image_format != SPICE_BITMAP_FMT_RGBA);
                 } else {
                     lz_comp = TRUE;
@@ -10035,6 +10031,7 @@ static RedChannel *__new_channel(RedWorker *worker, int size, RedsStreamContext
     }
 
     channel->migrate = migrate;
+
     return channel;
 
 error2:
@@ -10144,6 +10141,10 @@ static void handle_new_display_channel(RedWorker *worker, RedsStreamContext *pee
         spice_malloc(sizeof(SpiceResourceList) +
                      DISPLAY_FREE_LIST_DEFAULT_SIZE * sizeof(SpiceResourceID));
     display_channel->send_data.free_list.res_size = DISPLAY_FREE_LIST_DEFAULT_SIZE;
+
+    display_channel->enable_jpeg = IS_LOW_BANDWIDTH();
+    display_channel->jpeg_quality = 85;
+
     red_ref_channel((RedChannel*)display_channel);
     on_new_display_channel(worker);
     red_unref_channel((RedChannel*)display_channel);
commit 1a752e3884cc052a96df1f0e041308b218197a60
Author: Yonit Halperin <yhalperi at redhat.com>
Date:   Tue Jun 1 10:30:56 2010 +0300

    not using jpeg when sending a surface to the client (the whole surface)
    
    When a surface is sent to the client using red_send_surface_image, operations were already
    performed on it. Thus it may combine, especially if it is a primary surface, both "picture-like" areas
    and areas that are more "artificial". In order to avoid noticeable artifacts, such surface will be sent lossless.

diff --git a/server/red_worker.c b/server/red_worker.c
index 91e5a5f..bdc7ebb 100644
--- a/server/red_worker.c
+++ b/server/red_worker.c
@@ -5198,7 +5198,9 @@ static void red_add_surface_image(RedWorker *worker, int surface_id)
     area.right = surface->context.width;
     area.bottom = surface->context.height;
 
-    item = red_add_surface_area_image(worker, surface_id, &area, NULL, TRUE);
+    /* not allowing lossy compression because probably, especially if it is a primary surface,
+       it combines both "picture-like" areas with areas that are more "artificial"*/
+    item = red_add_surface_area_image(worker, surface_id, &area, NULL, FALSE);
     display_channel_push(worker);
 }
 
commit 0c199b0886a9deb57f61f4000110d26df36f4bd8
Author: Yonit Halperin <yhalperi at redhat.com>
Date:   Tue Jun 1 10:30:55 2010 +0300

    send qxl commands to client: support for resending lossy components when jpeg compression is enabled

diff --git a/server/red_worker.c b/server/red_worker.c
index f33474f..91e5a5f 100644
--- a/server/red_worker.c
+++ b/server/red_worker.c
@@ -6705,10 +6705,19 @@ static inline void red_display_add_image_to_pixmap_cache(DisplayChannel *display
     }
 }
 
+typedef enum {
+    FILL_BITS_TYPE_INVALID,
+    FILL_BITS_TYPE_CACHE,
+    FILL_BITS_TYPE_SURFACE,
+    FILL_BITS_TYPE_COMPRESS_LOSSLESS,
+    FILL_BITS_TYPE_COMPRESS_LOSSY,
+    FILL_BITS_TYPE_BITMAP,
+} FillBitsType;
+
 /* if the number of times fill_bits can be called per one qxl_drawable increases -
    MAX_LZ_DRAWABLE_INSTANCES must be increased as well */
-static void fill_bits(DisplayChannel *display_channel, QXLPHYSICAL *in_bitmap,
-                      Drawable *drawable, int can_lossy)
+static FillBitsType fill_bits(DisplayChannel *display_channel, QXLPHYSICAL *in_bitmap,
+                              Drawable *drawable, int can_lossy)
 {
     RedChannel *channel = &display_channel->base;
     RedWorker *worker = channel->worker;
@@ -6754,7 +6763,7 @@ static void fill_bits(DisplayChannel *display_channel, QXLPHYSICAL *in_bitmap,
                 }
                 add_buf(channel, BUF_TYPE_RAW, image, sizeof(SpiceImageDescriptor), 0, 0);
                 stat_inc_counter(display_channel->cache_hits_counter, 1);
-                return;
+                return FILL_BITS_TYPE_CACHE;
             } else {
                 pixmap_cache_set_lossy(display_channel->pixmap_cache, qxl_image->descriptor.id,
                                        FALSE);
@@ -6779,7 +6788,7 @@ static void fill_bits(DisplayChannel *display_channel, QXLPHYSICAL *in_bitmap,
 
         image->surface.surface_id = surface_id;
         add_buf(channel, BUF_TYPE_RAW, image, sizeof(SpiceSurfaceImage), 0, 0);
-        break;
+        return FILL_BITS_TYPE_SURFACE;
     }
     case SPICE_IMAGE_TYPE_BITMAP:
 #ifdef DUMP_BITMAP
@@ -6814,6 +6823,7 @@ static void fill_bits(DisplayChannel *display_channel, QXLPHYSICAL *in_bitmap,
                                            drawable->group_id);
                 add_buf(channel, BUF_TYPE_CHUNK, data, y * stride, memslot_id, drawable->group_id);
             }
+            return FILL_BITS_TYPE_BITMAP;
         } else {
             red_display_add_image_to_pixmap_cache(display_channel, qxl_image, image,
                                                   comp_send_data.is_lossy);
@@ -6827,6 +6837,9 @@ static void fill_bits(DisplayChannel *display_channel, QXLPHYSICAL *in_bitmap,
                 fill_palette(display_channel, comp_send_data.plt_ptr, comp_send_data.flags_ptr,
                              drawable->group_id);
             }
+            ASSERT(!comp_send_data.is_lossy || can_lossy);
+            return (comp_send_data.is_lossy ? FILL_BITS_TYPE_COMPRESS_LOSSY :
+                                              FILL_BITS_TYPE_COMPRESS_LOSSLESS);
         }
         break;
     case SPICE_IMAGE_TYPE_QUIC:
@@ -6835,7 +6848,7 @@ static void fill_bits(DisplayChannel *display_channel, QXLPHYSICAL *in_bitmap,
         add_buf(channel, BUF_TYPE_RAW, image, sizeof(SpiceQUICImage), 0, 0);
         add_buf(channel, BUF_TYPE_CHUNK, qxl_image->quic.data, qxl_image->quic.data_size,
                 memslot_id, drawable->group_id);
-        break;
+        return FILL_BITS_TYPE_COMPRESS_LOSSLESS;
     default:
         red_error("invalid image type %u", image->descriptor.type);
     }
@@ -7031,8 +7044,8 @@ static int is_bitmap_lossy(DisplayChannel *display_channel, SPICE_ADDRESS bitmap
     }
 }
 
-SPICE_GNUC_UNUSED static int is_brush_lossy(DisplayChannel *display_channel, SpiceBrush *brush,
-                                            Drawable *drawable, BitmapData *out_data)
+static int is_brush_lossy(DisplayChannel *display_channel, SpiceBrush *brush,
+                          Drawable *drawable, BitmapData *out_data)
 {
     if (brush->type == SPICE_BRUSH_TYPE_PATTERN) {
         return is_bitmap_lossy(display_channel, brush->u.pattern.pat, NULL,
@@ -7043,8 +7056,8 @@ SPICE_GNUC_UNUSED static int is_brush_lossy(DisplayChannel *display_channel, Spi
     }
 }
 
-SPICE_GNUC_UNUSED static void surface_lossy_region_update(RedWorker *worker, DisplayChannel *display_channel,
-                                                          Drawable *item, int has_mask, int lossy)
+static void surface_lossy_region_update(RedWorker *worker, DisplayChannel *display_channel,
+                                        Drawable *item, int has_mask, int lossy)
 {
     QRegion *surface_lossy_region;
     QXLDrawable *drawable;
@@ -7160,12 +7173,12 @@ static void red_pipe_replace_rendered_drawables_with_images(RedWorker *worker,
     }
 }
 
-SPICE_GNUC_UNUSED static void red_add_lossless_drawable_dependencies(RedWorker *worker,
-                                                                     DisplayChannel *display_channel,
-                                                                     Drawable *item,
-                                                                     int deps_surfaces_ids[],
-                                                                     SpiceRect *deps_areas[],
-                                                                     int num_deps)
+static void red_add_lossless_drawable_dependencies(RedWorker *worker,
+                                                   DisplayChannel *display_channel,
+                                                   Drawable *item,
+                                                   int deps_surfaces_ids[],
+                                                   SpiceRect *deps_areas[],
+                                                   int num_deps)
 {
     QXLDrawable *drawable = item->qxl_drawable;
     int sync_rendered = FALSE;
@@ -7228,6 +7241,613 @@ SPICE_GNUC_UNUSED static void red_add_lossless_drawable_dependencies(RedWorker *
     }
 }
 
+static inline void red_lossy_send_qxl_draw_fill(RedWorker *worker,
+                                                DisplayChannel *display_channel,
+                                                Drawable *item)
+{
+    QXLDrawable *drawable = item->qxl_drawable;
+
+    RedChannel *channel = &display_channel->base;
+    int dest_allowed_lossy = FALSE;
+    int dest_is_lossy = FALSE;
+    SpiceRect dest_lossy_area;
+    int brush_is_lossy;
+    BitmapData brush_bitmap_data;
+    uint16_t rop;
+
+    rop = drawable->u.fill.rop_decriptor;
+
+    dest_allowed_lossy = !((rop & SPICE_ROPD_OP_OR) ||
+                           (rop & SPICE_ROPD_OP_AND) ||
+                           (rop & SPICE_ROPD_OP_XOR));
+    
+    brush_is_lossy = is_brush_lossy(display_channel, &drawable->u.fill.brush, item,
+                                    &brush_bitmap_data);
+    if (!dest_allowed_lossy) {
+        dest_is_lossy = is_surface_area_lossy(display_channel, item->surface_id, &drawable->bbox,
+                                              &dest_lossy_area);
+    }
+                  
+    if (!dest_is_lossy && 
+        !(brush_is_lossy && (brush_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE))) {
+        int has_mask = !!drawable->u.fill.mask.bitmap;
+        channel->send_data.header.type = SPICE_MSG_DISPLAY_DRAW_FILL;
+        fill_base(display_channel, &display_channel->send_data.u.fill.base, item,
+                  sizeof(SpiceMsgDisplayDrawFill), item->surface_id);
+        display_channel->send_data.u.fill.data = drawable->u.fill;
+        fill_brush(display_channel, &display_channel->send_data.u.fill.data.brush, item);
+        fill_mask(display_channel, &display_channel->send_data.u.fill.data.mask, item);
+
+        // eitehr the brush operation is opaque, or the dest is not lossy
+        surface_lossy_region_update(worker, display_channel, item, has_mask, FALSE);
+    } else {
+        int resend_surface_ids[2];
+        SpiceRect *resend_areas[2];
+        int num_resend = 0;
+
+        if (dest_is_lossy) {
+            resend_surface_ids[num_resend] = item->surface_id;
+            resend_areas[num_resend] = &dest_lossy_area;
+            num_resend++;
+        }
+
+        if (brush_is_lossy && (brush_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) {
+            resend_surface_ids[num_resend] = brush_bitmap_data.id;
+            resend_areas[num_resend] = &brush_bitmap_data.lossy_rect;
+            num_resend++;
+        }
+
+        red_add_lossless_drawable_dependencies(worker, display_channel, item,
+                                               resend_surface_ids, resend_areas, num_resend);
+    }
+}
+
+static inline void red_lossy_send_qxl_draw_opaque(RedWorker *worker,
+                                                  DisplayChannel *display_channel,
+                                                  Drawable *item)
+{
+    QXLDrawable *drawable = item->qxl_drawable;
+    RedChannel *channel = &display_channel->base;
+
+    int src_allowed_lossy;
+    int rop;
+    int src_is_lossy = FALSE;
+    int brush_is_lossy = FALSE;
+    BitmapData src_bitmap_data;
+    BitmapData brush_bitmap_data;
+
+    rop = drawable->u.opaque.rop_decriptor;
+    src_allowed_lossy = !((rop & SPICE_ROPD_OP_OR) ||
+                          (rop & SPICE_ROPD_OP_AND) ||
+                          (rop & SPICE_ROPD_OP_XOR));
+
+    brush_is_lossy = is_brush_lossy(display_channel, &drawable->u.opaque.brush, item,
+                                    &brush_bitmap_data);
+
+    if (!src_allowed_lossy) {
+        src_is_lossy = is_bitmap_lossy(display_channel, drawable->u.opaque.src_bitmap,
+                                       &drawable->u.opaque.src_area,
+                                       item,
+                                       &src_bitmap_data);
+    }
+
+    if (!(brush_is_lossy && (brush_bitmap_data.type == SPICE_IMAGE_TYPE_SURFACE)) &&
+        !(src_is_lossy && (src_bitmap_data.type == SPICE_IMAGE_TYPE_SURFACE))) {
+        FillBitsType src_send_type;
+        int has_mask = !!drawable->u.opaque.mask.bitmap;
+
+        channel->send_data.header.type = SPICE_MSG_DISPLAY_DRAW_OPAQUE;
+        fill_base(display_channel, &display_channel->send_data.u.opaque.base, item,
+                  sizeof(SpiceMsgDisplayDrawOpaque), item->surface_id);
+        display_channel->send_data.u.opaque.data = drawable->u.opaque;
+        src_send_type = fill_bits(display_channel,
+                                  &display_channel->send_data.u.opaque.data.src_bitmap,
+                                  item, src_allowed_lossy);
+        fill_brush(display_channel, &display_channel->send_data.u.opaque.data.brush, item);
+        fill_mask(display_channel, &display_channel->send_data.u.opaque.data.mask, item);
+
+        if (src_send_type == FILL_BITS_TYPE_COMPRESS_LOSSY) {
+            src_is_lossy = TRUE;
+        } else if (src_send_type == FILL_BITS_TYPE_COMPRESS_LOSSLESS) {
+            src_is_lossy = FALSE;
+        }
+
+        surface_lossy_region_update(worker, display_channel, item, has_mask, src_is_lossy);
+    } else {
+        int resend_surface_ids[2];
+        SpiceRect *resend_areas[2];
+        int num_resend = 0;      
+
+        if (src_is_lossy && (src_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) {
+            resend_surface_ids[num_resend] = src_bitmap_data.id;
+            resend_areas[num_resend] = &src_bitmap_data.lossy_rect;
+            num_resend++;
+        }
+
+        if (brush_is_lossy && (brush_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) {
+            resend_surface_ids[num_resend] = brush_bitmap_data.id;
+            resend_areas[num_resend] = &brush_bitmap_data.lossy_rect;
+            num_resend++;
+        }
+
+        red_add_lossless_drawable_dependencies(worker, display_channel, item,
+                                               resend_surface_ids, resend_areas, num_resend);
+    }
+}
+
+static inline void red_lossy_send_qxl_draw_copy(RedWorker *worker,
+                                                DisplayChannel *display_channel,
+                                                Drawable *item)
+{
+    QXLDrawable *drawable = item->qxl_drawable;
+    RedChannel *channel = &display_channel->base;
+    int has_mask = !!drawable->u.copy.mask.bitmap;
+    int src_is_lossy;
+    BitmapData src_bitmap_data;
+    FillBitsType src_send_type;
+
+    src_is_lossy = is_bitmap_lossy(display_channel, drawable->u.copy.src_bitmap,
+                                   &drawable->u.copy.src_area, item, &src_bitmap_data);
+
+    channel->send_data.header.type = SPICE_MSG_DISPLAY_DRAW_COPY;
+    fill_base(display_channel, &display_channel->send_data.u.copy.base, item,
+              sizeof(SpiceMsgDisplayDrawCopy), item->surface_id);
+    display_channel->send_data.u.copy.data = drawable->u.copy;
+    src_send_type = fill_bits(display_channel, &display_channel->send_data.u.copy.data.src_bitmap,
+                              item, TRUE);
+    fill_mask(display_channel, &display_channel->send_data.u.copy.data.mask, item);
+
+    if (src_send_type == FILL_BITS_TYPE_COMPRESS_LOSSY) {
+        src_is_lossy = TRUE;
+    } else if (src_send_type == FILL_BITS_TYPE_COMPRESS_LOSSLESS) {
+        src_is_lossy = FALSE;
+    }
+
+    surface_lossy_region_update(worker, display_channel, item, has_mask,
+                                src_is_lossy);
+}
+
+static inline void red_lossy_send_qxl_draw_transparent(RedWorker *worker,
+                                                       DisplayChannel *display_channel,
+                                                       Drawable *item)
+{
+    QXLDrawable *drawable = item->qxl_drawable;
+    RedChannel *channel = &display_channel->base;
+    int src_is_lossy;
+    BitmapData src_bitmap_data;
+
+    src_is_lossy = is_bitmap_lossy(display_channel, drawable->u.transparent.src_bitmap,
+                                   &drawable->u.transparent.src_area, item, &src_bitmap_data);
+
+    if (!src_is_lossy || (src_bitmap_data.type != BITMAP_DATA_TYPE_SURFACE)) {  
+        channel->send_data.header.type = SPICE_MSG_DISPLAY_DRAW_TRANSPARENT;
+        fill_base(display_channel, &display_channel->send_data.u.transparent.base, item,
+                  sizeof(SpiceMsgDisplayDrawTransparent), item->surface_id);
+        display_channel->send_data.u.transparent.data = drawable->u.transparent;
+        fill_bits(display_channel, &display_channel->send_data.u.transparent.data.src_bitmap,
+                  item, FALSE);
+
+        // don't update surface lossy region since transperent areas might be lossy
+    } else {
+        int resend_surface_ids[1];
+        SpiceRect *resend_areas[1];
+
+        resend_surface_ids[0] = src_bitmap_data.id;
+        resend_areas[0] = &src_bitmap_data.lossy_rect;
+
+        red_add_lossless_drawable_dependencies(worker, display_channel, item,
+                                               resend_surface_ids, resend_areas, 1);
+    }
+}
+
+static inline void red_lossy_send_qxl_draw_alpha_blend(RedWorker *worker,
+                                                       DisplayChannel *display_channel,
+                                                       Drawable *item)
+{
+    QXLDrawable *drawable = item->qxl_drawable;
+    RedChannel *channel = &display_channel->base;
+    int src_is_lossy;
+    BitmapData src_bitmap_data;
+    FillBitsType src_send_type;
+
+    src_is_lossy = is_bitmap_lossy(display_channel, drawable->u.alpha_blend.src_bitmap,
+                                   &drawable->u.alpha_blend.src_area, item, &src_bitmap_data);
+    
+    channel->send_data.header.type = SPICE_MSG_DISPLAY_DRAW_ALPHA_BLEND;
+    fill_base(display_channel, &display_channel->send_data.u.alpha_blend.base, item,
+              sizeof(SpiceMsgDisplayDrawAlphaBlend), item->surface_id);
+    display_channel->send_data.u.alpha_blend.data = drawable->u.alpha_blend;
+    src_send_type = fill_bits(display_channel,
+                              &display_channel->send_data.u.alpha_blend.data.src_bitmap,
+                              item, TRUE);
+
+    if (src_send_type == FILL_BITS_TYPE_COMPRESS_LOSSY) {
+        src_is_lossy = TRUE;
+    } else if (src_send_type == FILL_BITS_TYPE_COMPRESS_LOSSLESS) {
+        src_is_lossy = FALSE;
+    }
+
+    if (src_is_lossy) {
+        surface_lossy_region_update(worker, display_channel, item, FALSE, src_is_lossy);
+    } // else, the area stays lossy/lossless as the destination
+}
+
+static inline void red_lossy_send_qxl_copy_bits(RedWorker *worker,
+                                                DisplayChannel *display_channel,
+                                                Drawable *item)
+{
+    QXLDrawable *drawable = item->qxl_drawable;
+    RedChannel *channel = &display_channel->base;
+    SpiceRect src_rect;
+    int horz_offset;
+    int vert_offset;
+    int src_is_lossy;
+    SpiceRect src_lossy_area;
+
+    channel->send_data.header.type = SPICE_MSG_DISPLAY_COPY_BITS;
+    fill_base(display_channel, &display_channel->send_data.u.copy_bits.base, item,
+              sizeof(SpiceMsgDisplayCopyBits), item->surface_id);
+    display_channel->send_data.u.copy_bits.src_pos = drawable->u.copy_bits.src_pos;
+    
+    horz_offset = drawable->u.copy_bits.src_pos.x - drawable->bbox.left;
+    vert_offset = drawable->u.copy_bits.src_pos.y - drawable->bbox.top;
+
+
+    src_rect.left = drawable->u.copy_bits.src_pos.x;
+    src_rect.top = drawable->u.copy_bits.src_pos.y;
+    src_rect.right = drawable->bbox.right + horz_offset;
+    src_rect.bottom = drawable->bbox.bottom + vert_offset;
+
+    src_is_lossy = is_surface_area_lossy(display_channel, item->surface_id,
+                                         &src_rect, &src_lossy_area);
+
+    surface_lossy_region_update(worker, display_channel, item, FALSE,
+                                src_is_lossy);
+}
+
+static inline void red_lossy_send_qxl_draw_blend(RedWorker *worker,
+                                                 DisplayChannel *display_channel,
+                                                 Drawable *item)
+{
+    QXLDrawable *drawable = item->qxl_drawable;
+    RedChannel *channel = &display_channel->base;
+    int src_is_lossy;
+    BitmapData src_bitmap_data;
+    int dest_is_lossy;
+    SpiceRect dest_lossy_area;
+
+    src_is_lossy = is_bitmap_lossy(display_channel, drawable->u.blend.src_bitmap,
+                                   &drawable->u.blend.src_area, item, &src_bitmap_data);
+    dest_is_lossy = is_surface_area_lossy(display_channel, drawable->surface_id,
+                                          &drawable->bbox, &dest_lossy_area);
+
+    if (!dest_is_lossy && (!src_is_lossy || (src_bitmap_data.type != BITMAP_DATA_TYPE_SURFACE))) {
+        channel->send_data.header.type = SPICE_MSG_DISPLAY_DRAW_BLEND;
+        fill_base(display_channel, &display_channel->send_data.u.blend.base, item,
+                  sizeof(SpiceMsgDisplayDrawBlend), item->surface_id);
+        display_channel->send_data.u.blend.data = drawable->u.blend;
+        fill_bits(display_channel, &display_channel->send_data.u.blend.data.src_bitmap,
+                  item, FALSE);
+        fill_mask(display_channel, &display_channel->send_data.u.blend.data.mask, item);
+    } else {
+        int resend_surface_ids[2];
+        SpiceRect *resend_areas[2];
+        int num_resend = 0; 
+
+        if (src_is_lossy && (src_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) {
+            resend_surface_ids[num_resend] = src_bitmap_data.id;
+            resend_areas[num_resend] = &src_bitmap_data.lossy_rect;
+            num_resend++;
+        }
+
+        if (dest_is_lossy) {
+            resend_surface_ids[num_resend] = item->surface_id;
+            resend_areas[num_resend] = &dest_lossy_area;
+            num_resend++;
+        }
+
+        red_add_lossless_drawable_dependencies(worker, display_channel, item,
+                                               resend_surface_ids, resend_areas, num_resend);
+    }
+}
+
+static inline void red_lossy_send_qxl_draw_blackness(RedWorker *worker,
+                                                     DisplayChannel *display_channel,
+                                                     Drawable *item)
+{
+    QXLDrawable *drawable = item->qxl_drawable;
+    RedChannel *channel = &display_channel->base;
+    int has_mask = !!drawable->u.blackness.mask.bitmap;
+
+    channel->send_data.header.type = SPICE_MSG_DISPLAY_DRAW_BLACKNESS;
+    fill_base(display_channel, &display_channel->send_data.u.blackness.base, item,
+              sizeof(SpiceMsgDisplayDrawBlackness), item->surface_id);
+    display_channel->send_data.u.blackness.data = drawable->u.blackness;
+    fill_mask(display_channel, &display_channel->send_data.u.blackness.data.mask, item);
+
+    surface_lossy_region_update(worker, display_channel, item, has_mask, FALSE);
+}
+
+static inline void red_lossy_send_qxl_draw_whiteness(RedWorker *worker,
+                                                     DisplayChannel *display_channel,
+                                                     Drawable *item)
+{
+    QXLDrawable *drawable = item->qxl_drawable;
+    RedChannel *channel = &display_channel->base;
+    int has_mask = !!drawable->u.whiteness.mask.bitmap;
+
+    channel->send_data.header.type = SPICE_MSG_DISPLAY_DRAW_WHITENESS;
+    fill_base(display_channel, &display_channel->send_data.u.whiteness.base, item,
+              sizeof(SpiceMsgDisplayDrawWhiteness), item->surface_id);
+    display_channel->send_data.u.whiteness.data = drawable->u.whiteness;
+    fill_mask(display_channel, &display_channel->send_data.u.whiteness.data.mask, item);
+
+    surface_lossy_region_update(worker, display_channel, item, has_mask, FALSE);
+}
+
+static inline void red_lossy_send_qxl_draw_inverse(RedWorker *worker,
+                                                   DisplayChannel *display_channel,
+                                                   Drawable *item)
+{
+    QXLDrawable *drawable = item->qxl_drawable;
+    RedChannel *channel = &display_channel->base;
+
+    channel->send_data.header.type = SPICE_MSG_DISPLAY_DRAW_INVERS;
+    fill_base(display_channel, &display_channel->send_data.u.invers.base, item,
+              sizeof(SpiceMsgDisplayDrawInvers), item->surface_id);
+    display_channel->send_data.u.invers.data = drawable->u.invers;
+    fill_mask(display_channel, &display_channel->send_data.u.invers.data.mask, item);
+}
+
+static inline void red_lossy_send_qxl_draw_rop3(RedWorker *worker,
+                                                DisplayChannel *display_channel,
+                                                Drawable *item)
+{
+    QXLDrawable *drawable = item->qxl_drawable;
+    RedChannel *channel = &display_channel->base;
+
+    int src_is_lossy;
+    BitmapData src_bitmap_data;
+    int brush_is_lossy;
+    BitmapData brush_bitmap_data;
+    int dest_is_lossy;
+    SpiceRect dest_lossy_area;
+
+    src_is_lossy = is_bitmap_lossy(display_channel, drawable->u.rop3.src_bitmap,
+                                   &drawable->u.rop3.src_area, item, &src_bitmap_data);
+    brush_is_lossy = is_brush_lossy(display_channel, &drawable->u.rop3.brush, item,
+                                    &brush_bitmap_data);
+    dest_is_lossy = is_surface_area_lossy(display_channel, drawable->surface_id,
+                                          &drawable->bbox, &dest_lossy_area);
+
+    if ((!src_is_lossy || (src_bitmap_data.type != BITMAP_DATA_TYPE_SURFACE)) &&
+        (!brush_is_lossy || (brush_bitmap_data.type != BITMAP_DATA_TYPE_SURFACE)) &&
+        !dest_is_lossy) {
+        int has_mask = !!drawable->u.rop3.mask.bitmap;
+        channel->send_data.header.type = SPICE_MSG_DISPLAY_DRAW_ROP3;
+        fill_base(display_channel, &display_channel->send_data.u.rop3.base, item,
+                  sizeof(SpiceMsgDisplayDrawRop3), item->surface_id);
+        display_channel->send_data.u.rop3.data = drawable->u.rop3;
+        fill_bits(display_channel, &display_channel->send_data.u.rop3.data.src_bitmap,
+                  item, FALSE);
+        fill_brush(display_channel, &display_channel->send_data.u.rop3.data.brush, item);
+        fill_mask(display_channel, &display_channel->send_data.u.rop3.data.mask, item);
+
+        surface_lossy_region_update(worker, display_channel, item, has_mask, FALSE);
+    } else {
+        int resend_surface_ids[3];
+        SpiceRect *resend_areas[3];
+        int num_resend = 0; 
+
+        if (src_is_lossy && (src_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) {
+            resend_surface_ids[num_resend] = src_bitmap_data.id;
+            resend_areas[num_resend] = &src_bitmap_data.lossy_rect;
+            num_resend++;
+        }
+
+        if (brush_is_lossy && (brush_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) {
+            resend_surface_ids[num_resend] = brush_bitmap_data.id;
+            resend_areas[num_resend] = &brush_bitmap_data.lossy_rect;
+            num_resend++;
+        }
+
+        if (dest_is_lossy) {
+            resend_surface_ids[num_resend] = item->surface_id;
+            resend_areas[num_resend] = &dest_lossy_area;
+            num_resend++;
+        }
+
+        red_add_lossless_drawable_dependencies(worker, display_channel, item,
+                                               resend_surface_ids, resend_areas, num_resend);
+    }
+}
+
+static inline void red_lossy_send_qxl_draw_stroke(RedWorker *worker,
+                                                  DisplayChannel *display_channel,
+                                                  Drawable *item)
+{
+    QXLDrawable *drawable = item->qxl_drawable;
+    RedChannel *channel = &display_channel->base;
+    int brush_is_lossy;
+    BitmapData brush_bitmap_data;
+    int dest_is_lossy = FALSE;
+    SpiceRect dest_lossy_area;
+    int rop;
+
+    brush_is_lossy = is_brush_lossy(display_channel, &drawable->u.stroke.brush, item,
+                                    &brush_bitmap_data);
+
+    // back_mode is not used at the client. Ignoring.
+    rop = drawable->u.stroke.fore_mode;
+
+    // assuming that if the brush type is solid, the destination can
+    // be lossy, no matter what the rop is. 
+    if (drawable->u.stroke.brush.type != SPICE_BRUSH_TYPE_SOLID &&
+        ((rop & SPICE_ROPD_OP_OR) || (rop & SPICE_ROPD_OP_AND) ||
+        (rop & SPICE_ROPD_OP_XOR))) {
+        dest_is_lossy = is_surface_area_lossy(display_channel, drawable->surface_id,
+                                              &drawable->bbox, &dest_lossy_area);
+    }
+    
+    if (!dest_is_lossy &&
+        (!brush_is_lossy || (brush_bitmap_data.type != BITMAP_DATA_TYPE_SURFACE)))
+    {
+        channel->send_data.header.type = SPICE_MSG_DISPLAY_DRAW_STROKE;
+        fill_base(display_channel, &display_channel->send_data.u.stroke.base, item,
+                  sizeof(SpiceMsgDisplayDrawStroke), item->surface_id);
+        display_channel->send_data.u.stroke.data = drawable->u.stroke;
+        fill_path(display_channel, &display_channel->send_data.u.stroke.data.path, item->group_id);
+        fill_attr(display_channel, &display_channel->send_data.u.stroke.data.attr, item->group_id);
+        fill_brush(display_channel, &display_channel->send_data.u.stroke.data.brush, item);
+    } else {
+        int resend_surface_ids[2];
+        SpiceRect *resend_areas[2];
+        int num_resend = 0;
+
+        if (brush_is_lossy && (brush_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) {
+            resend_surface_ids[num_resend] = brush_bitmap_data.id;
+            resend_areas[num_resend] = &brush_bitmap_data.lossy_rect;
+            num_resend++;
+        }
+
+        // TODO: use the path in order to resend smaller areas
+        if (dest_is_lossy) {
+            resend_surface_ids[num_resend] = drawable->surface_id;
+            resend_areas[num_resend] = &dest_lossy_area;
+            num_resend++;
+        }
+
+        red_add_lossless_drawable_dependencies(worker, display_channel, item,
+                                               resend_surface_ids, resend_areas, num_resend);
+    }
+
+}
+
+static inline void red_lossy_send_qxl_draw_text(RedWorker *worker,
+                                                DisplayChannel *display_channel,
+                                                Drawable *item)
+{
+    QXLDrawable *drawable = item->qxl_drawable;
+    RedChannel *channel = &display_channel->base;
+    int fg_is_lossy;
+    BitmapData fg_bitmap_data;
+    int bg_is_lossy;
+    BitmapData bg_bitmap_data;
+    int dest_is_lossy = FALSE;
+    SpiceRect dest_lossy_area;
+    int rop = 0;
+
+    fg_is_lossy = is_brush_lossy(display_channel, &drawable->u.text.fore_brush, item,
+                                 &fg_bitmap_data);
+    bg_is_lossy = is_brush_lossy(display_channel, &drawable->u.text.back_brush, item,
+                                 &bg_bitmap_data);
+
+    // assuming that if the brush type is solid, the destination can
+    // be lossy, no matter what the rop is.
+    if (drawable->u.text.fore_brush.type != SPICE_BRUSH_TYPE_SOLID) {
+        rop = drawable->u.text.fore_mode;
+    }
+
+    if (drawable->u.text.back_brush.type != SPICE_BRUSH_TYPE_SOLID) {
+        rop |= drawable->u.text.back_mode;
+    }
+
+    if ((rop & SPICE_ROPD_OP_OR) || (rop & SPICE_ROPD_OP_AND) ||
+        (rop & SPICE_ROPD_OP_XOR)) {
+        dest_is_lossy = is_surface_area_lossy(display_channel, drawable->surface_id,
+                                              &drawable->bbox, &dest_lossy_area);
+    }
+
+    if (!dest_is_lossy &&
+        (!fg_is_lossy || (fg_bitmap_data.type != BITMAP_DATA_TYPE_SURFACE)) &&
+        (!bg_is_lossy || (bg_bitmap_data.type != BITMAP_DATA_TYPE_SURFACE))) {
+        channel->send_data.header.type = SPICE_MSG_DISPLAY_DRAW_TEXT;
+        fill_base(display_channel, &display_channel->send_data.u.text.base, item,
+                  sizeof(SpiceMsgDisplayDrawText), item->surface_id);
+        display_channel->send_data.u.text.data = drawable->u.text;
+        fill_brush(display_channel, &display_channel->send_data.u.text.data.fore_brush, item);
+        fill_brush(display_channel, &display_channel->send_data.u.text.data.back_brush, item);
+        fill_str(display_channel, &display_channel->send_data.u.text.data.str, item->group_id);
+    } else {
+        int resend_surface_ids[3];
+        SpiceRect *resend_areas[3];
+        int num_resend = 0;
+
+        if (fg_is_lossy && (fg_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) {
+            resend_surface_ids[num_resend] = fg_bitmap_data.id;
+            resend_areas[num_resend] = &fg_bitmap_data.lossy_rect;
+            num_resend++;
+        }
+
+        if (bg_is_lossy && (bg_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) {
+            resend_surface_ids[num_resend] = bg_bitmap_data.id;
+            resend_areas[num_resend] = &bg_bitmap_data.lossy_rect;
+            num_resend++;
+        }
+
+        if (dest_is_lossy) {
+            resend_surface_ids[num_resend] = drawable->surface_id;
+            resend_areas[num_resend] = &dest_lossy_area;
+            num_resend++;
+        }
+        red_add_lossless_drawable_dependencies(worker, display_channel, item,
+                                               resend_surface_ids, resend_areas, num_resend);
+    }
+}
+
+static void red_lossy_send_qxl_drawable(RedWorker *worker, DisplayChannel *display_channel,
+                                   Drawable *item)
+{
+    switch (item->qxl_drawable->type) {
+    case QXL_DRAW_FILL:
+        red_lossy_send_qxl_draw_fill(worker, display_channel, item);
+        break;
+    case QXL_DRAW_OPAQUE:
+        red_lossy_send_qxl_draw_opaque(worker, display_channel, item);
+        break;
+    case QXL_DRAW_COPY:
+        red_lossy_send_qxl_draw_copy(worker, display_channel, item);
+        break;
+    case QXL_DRAW_TRANSPARENT:
+        red_lossy_send_qxl_draw_transparent(worker, display_channel, item);
+        break;
+    case QXL_DRAW_ALPHA_BLEND:
+        red_lossy_send_qxl_draw_alpha_blend(worker, display_channel, item);
+        break;
+    case QXL_COPY_BITS:
+        red_lossy_send_qxl_copy_bits(worker, display_channel, item);
+        break;
+    case QXL_DRAW_BLEND:
+        red_lossy_send_qxl_draw_blend(worker, display_channel, item);
+        break;
+    case QXL_DRAW_BLACKNESS:
+        red_lossy_send_qxl_draw_blackness(worker, display_channel, item);
+        break;
+     case QXL_DRAW_WHITENESS:
+        red_lossy_send_qxl_draw_whiteness(worker, display_channel, item);
+        break;   
+    case QXL_DRAW_INVERS:
+        red_lossy_send_qxl_draw_inverse(worker, display_channel, item);
+        break;
+    case QXL_DRAW_ROP3:
+        red_lossy_send_qxl_draw_rop3(worker, display_channel, item);
+        break;
+    case QXL_DRAW_STROKE:
+        red_lossy_send_qxl_draw_stroke(worker, display_channel, item);
+        break;      
+    case QXL_DRAW_TEXT:
+        red_lossy_send_qxl_draw_text(worker, display_channel, item);
+        break;
+    default:
+        red_error("invalid type");
+    }
+
+    // a message is pending
+    if (display_channel->base.send_data.header.size) {
+        display_begin_send_massage(display_channel, &item->pipe_item);
+    }
+}
+
 static inline void red_send_qxl_drawable(RedWorker *worker, DisplayChannel *display_channel,
                                          Drawable *item)
 {
@@ -7889,7 +8509,10 @@ static inline void send_qxl_drawable(DisplayChannel *display_channel, Drawable *
     if (item->stream && red_send_stream_data(display_channel, item)) {
         return;
     }
-    red_send_qxl_drawable(display_channel->base.worker, display_channel, item);
+    if (!display_channel->base.worker->enable_jpeg)
+        red_send_qxl_drawable(display_channel->base.worker, display_channel, item);
+    else
+        red_lossy_send_qxl_drawable(display_channel->base.worker, display_channel, item);
 }
 
 static void red_send_set_ack(RedChannel *channel)
commit c970f41f8e90b4f63eb210910d9a505e490c9a8b
Author: Yonit Halperin <yhalperi at redhat.com>
Date:   Tue Jun 1 10:30:54 2010 +0300

    infrastructure routines for resending to the client part of surfaces that have been sent lossy.
    
    The code also handles cases in which the server doesn't hold anymore these surfaces parts, i.e., when
    it holds a more updated version of them. This scenario is handled by replacing commands that were rendered, with images.

diff --git a/server/red_worker.c b/server/red_worker.c
index 1ac8383..f33474f 100644
--- a/server/red_worker.c
+++ b/server/red_worker.c
@@ -49,6 +49,7 @@
 #include "mjpeg_encoder.h"
 #include "red_memslots.h"
 #include "jpeg_encoder.h"
+#include "rect.h"
 
 //#define COMPRESS_STAT
 //#define DUMP_BITMAP
@@ -1337,6 +1338,15 @@ static inline void red_pipe_add_drawable(RedWorker *worker, Drawable *drawable)
     red_pipe_add(&worker->display_channel->base, &drawable->pipe_item);
 }
 
+static inline void red_pipe_add_drawable_to_tail(RedWorker *worker, Drawable *drawable)
+{
+    if (!worker->display_channel) {
+        return;
+    }
+    drawable->refs++;
+    red_pipe_add_tail(&worker->display_channel->base, &drawable->pipe_item);
+}
+
 static inline void red_pipe_add_drawable_after(RedWorker *worker, Drawable *drawable,
                                                Drawable *pos_after)
 {
@@ -1352,6 +1362,15 @@ static inline void red_pipe_add_drawable_after(RedWorker *worker, Drawable *draw
     red_pipe_add_after(&worker->display_channel->base, &drawable->pipe_item, &pos_after->pipe_item);
 }
 
+static inline PipeItem *red_pipe_get_tail(RedWorker *worker)
+{
+    if (!worker->display_channel) {
+        return NULL;
+    }
+
+    return (PipeItem*)ring_get_tail(&worker->display_channel->base.pipe);
+}
+
 static inline void red_destroy_surface(RedWorker *worker, uint32_t surface_id);
 
 static inline void red_pipe_remove_drawable(RedWorker *worker, Drawable *drawable)
@@ -1372,6 +1391,16 @@ static inline void red_pipe_add_image_item(RedWorker *worker, ImageItem *item)
     red_pipe_add(&worker->display_channel->base, &item->link);
 }
 
+static inline void red_pipe_add_image_item_after(RedWorker *worker, ImageItem *item,
+                                                 PipeItem *pos)
+{
+    if (!worker->display_channel) {
+        return;
+    }
+    item->refs++;
+    red_pipe_add_after(&worker->display_channel->base, &item->link, pos);
+}
+
 static inline uint64_t channel_message_serial(RedChannel *channel)
 {
     return channel->send_data.header.serial;
@@ -2337,12 +2366,6 @@ static int is_equal_line_attr(SpiceLineAttr *a1, SpiceLineAttr *a2)
            a1->style_nseg == 0;
 }
 
-static inline int rect_is_equal(const SpiceRect *r1, const SpiceRect *r2)
-{
-    return r1->top == r2->top && r1->left == r2->left &&
-           r1->bottom == r2->bottom && r1->right == r2->right;
-}
-
 // partial imp
 static int is_same_geometry(RedWorker *worker, Drawable *d1, Drawable *d2)
 {
@@ -4696,6 +4719,91 @@ static void red_update_area(RedWorker *worker, const SpiceRect *area, int surfac
 
 #else
 
+/*
+    Renders drawables for updating the requested area, but only drawables that are older
+    than 'last' (exclusive).
+*/
+static void red_update_area_till(RedWorker *worker, const SpiceRect *area, int surface_id,
+                                 Drawable *last)
+{
+    // TODO: if we use UPDATE_AREA_BY_TREE, a corresponding red_update_area_till
+    // should be implemented
+
+    RedSurface *surface;
+    Drawable *surface_last = NULL;
+    Ring *ring;
+    RingItem *ring_item;
+    Drawable *now;
+    QRegion rgn;
+
+    ASSERT(last);
+    ASSERT(ring_item_is_linked(&last->list_link));
+
+    surface = &worker->surfaces[surface_id];
+
+    if (surface_id != last->surface_id) {
+        // find the nearest older drawable from the appropriate surface
+        ring = &worker->current_list;
+        ring_item = &last->list_link;
+        while ((ring_item = ring_next(ring, ring_item))) {
+            now = SPICE_CONTAINEROF(ring_item, Drawable, list_link);
+            if (now->surface_id == surface_id) {
+                surface_last = now;
+                break;
+            }
+        }
+    } else {
+        ring_item = ring_next(&surface->current_list, &last->surface_list_link);
+        if (ring_item) {
+            surface_last = SPICE_CONTAINEROF(ring_item, Drawable, surface_list_link);
+        }
+    }
+
+    if (!surface_last) {
+        return;
+    }
+
+    ring = &surface->current_list;
+    ring_item = &surface_last->surface_list_link;
+
+    region_init(&rgn);
+    region_add(&rgn, area);
+
+    // find the first older drawable that intersects with the area 
+    do {
+        now = SPICE_CONTAINEROF(ring_item, Drawable, surface_list_link);
+        if (region_intersects(&rgn, &now->tree_item.base.rgn)) {
+            surface_last = now;
+            break;
+        }
+    } while ((ring_item = ring_next(ring, ring_item)));
+
+    region_destroy(&rgn);
+    
+    if (!surface_last) {
+        return;
+    }
+
+    do {
+        Container *container;
+
+        ring_item = ring_get_tail(&surface->current_list);
+        now = SPICE_CONTAINEROF(ring_item, Drawable, surface_list_link);
+        now->refs++;
+        container = now->tree_item.base.container;
+        current_remove_drawable(worker, now);
+        container_cleanup(worker, container);
+        /* red_draw_drawable may call red_update_area for the surfaces 'now' depends on. Notice,
+           that it is valid to call red_update_area in this case and not red_update_area_till:
+           It is impossible that there was newer item then 'last' in one of the surfaces
+           that red_update_area is called for, Otherwise, 'now' would have already been rendered.
+           See the call for red_handle_depends_on_target_surface in red_process_drawable */
+        red_draw_drawable(worker, now);
+        release_drawable(worker, now);
+    } while (now != surface_last);
+    validate_area(worker, area, surface_id);
+}
+
 static void red_update_area(RedWorker *worker, const SpiceRect *area, int surface_id)
 {
     RedSurface *surface;
@@ -5012,25 +5120,27 @@ static void red_current_flush(RedWorker *worker, int surface_id)
     red_current_clear(worker, surface_id);
 }
 
-static void red_add_surface_image(RedWorker *worker, int surface_id)
+// adding the pipe item after pos. If pos == NULL, adding to head.
+static ImageItem *red_add_surface_area_image(RedWorker *worker, int surface_id, SpiceRect *area,
+                                             PipeItem *pos, int can_lossy)
 {
     ImageItem *item;
     int stride;
-    SpiceRect area;
-    SpiceCanvas *canvas = worker->surfaces[surface_id].context.canvas;
-    RedSurface *surface;
+    int width;
+    int height;
+    RedSurface *surface = &worker->surfaces[surface_id];
+    SpiceCanvas *canvas = surface->context.canvas;
+    int bpp;
     int all_set;
 
-    surface = &worker->surfaces[surface_id];
+    ASSERT(area);
 
-    if (!worker->display_channel || !surface->context.canvas) {
-        return;
-    }
-
-    stride = abs(surface->context.stride);
-
-    item = (ImageItem *)spice_malloc_n_m(surface->context.height, stride,
-                                         sizeof(ImageItem));
+    width = area->right - area->left;
+    height = area->bottom - area->top;
+    bpp = SPICE_SURFACE_FMT_DEPTH(surface->context.format) / 8;
+    stride = SPICE_ALIGN(width * bpp, 4); 
+                                      
+    item = (ImageItem *)spice_malloc_n_m(height, stride, sizeof(ImageItem));
 
     red_pipe_item_init(&item->link, PIPE_ITEM_TYPE_IMAGE);
 
@@ -5039,17 +5149,15 @@ static void red_add_surface_image(RedWorker *worker, int surface_id)
     item->image_format =
         surface_format_to_image_type(surface->context.format);
     item->image_flags = 0;
-    item->pos.x = item->pos.y = 0;
-    item->width = surface->context.width;
-    item->height = surface->context.height;
+    item->pos.x = area->left;
+    item->pos.y = area->top;
+    item->width = width;
+    item->height = height;
     item->stride = stride;
     item->top_down = surface->context.top_down;
-    item->can_lossy = TRUE;
+    item->can_lossy = can_lossy;
 
-    area.top = area.left = 0;
-    area.right = surface->context.width;
-    area.bottom = surface->context.height;
-    canvas->ops->read_bits(canvas, item->data, stride, &area);
+    canvas->ops->read_bits(canvas, item->data, stride, area);
 
     /* For 32bit non-primary surfaces we need to keep any non-zero
        high bytes as the surface may be used as source to an alpha_blend */
@@ -5063,8 +5171,34 @@ static void red_add_surface_image(RedWorker *worker, int surface_id)
         }
     }
 
-    red_pipe_add_image_item(worker, item);
+    if (!pos) {
+        red_pipe_add_image_item(worker, item);
+    } else {
+        red_pipe_add_image_item_after(worker, item, pos);
+    }
+
     release_image_item(item);
+
+    return item;
+}
+
+static void red_add_surface_image(RedWorker *worker, int surface_id)
+{
+    SpiceRect area;
+    RedSurface *surface;
+    ImageItem *item;
+
+    surface = &worker->surfaces[surface_id];
+
+    if (!worker->display_channel || !surface->context.canvas) {
+        return;
+    }
+
+    area.top = area.left = 0;
+    area.right = surface->context.width;
+    area.bottom = surface->context.height;
+
+    item = red_add_surface_area_image(worker, surface_id, &area, NULL, TRUE);
     display_channel_push(worker);
 }
 
@@ -6950,6 +7084,150 @@ SPICE_GNUC_UNUSED static void surface_lossy_region_update(RedWorker *worker, Dis
     } // else SPICE_CLIP_TYPE_PATH and lossless: leave the area as is
 }
 
+static inline int drawable_intersects_with_areas(Drawable *drawable, int surface_ids[],
+                                                 SpiceRect *surface_areas[],
+                                                 int num_surfaces) 
+{   
+    int i;
+    for (i = 0; i < num_surfaces; i++) {
+        if (surface_ids[i] == drawable->qxl_drawable->surface_id) {
+            if (rect_intersects(surface_areas[i], &drawable->qxl_drawable->bbox)) {
+                return TRUE;
+            }
+        }
+    }
+    return FALSE;
+}
+
+static int pipe_rendered_drawables_intersect_with_areas(RedWorker *worker,
+                                                        DisplayChannel *display_channel,
+                                                        int surface_ids[],
+                                                        SpiceRect *surface_areas[],
+                                                        int num_surfaces)
+{
+    PipeItem *pipe_item;
+    Ring *pipe;
+
+    ASSERT(num_surfaces);
+    pipe = &display_channel->base.pipe;
+
+    for (pipe_item = (PipeItem *)ring_get_head(pipe);
+         pipe_item;
+         pipe_item = (PipeItem *)ring_next(pipe, &pipe_item->link))
+    {
+        Drawable *drawable;
+
+        if (pipe_item->type != PIPE_ITEM_TYPE_DRAW)
+            continue;
+        drawable = SPICE_CONTAINEROF(pipe_item, Drawable, pipe_item);
+      
+        if (ring_item_is_linked(&drawable->list_link))
+            continue; // item hasn't been rendered
+
+        if (drawable_intersects_with_areas(drawable, surface_ids, surface_areas, num_surfaces)) {
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+static void red_pipe_replace_rendered_drawables_with_images(RedWorker *worker,
+                                                            DisplayChannel *display_channel)
+{
+    PipeItem *pipe_item;
+    Ring *pipe;
+
+    pipe = &display_channel->base.pipe;
+    // going from the newest to oldest
+    for (pipe_item = (PipeItem *)ring_get_head(pipe);
+         pipe_item;
+         pipe_item = (PipeItem *)ring_next(pipe, &pipe_item->link)) {
+
+        Drawable *drawable;
+        ImageItem *image;
+        if (pipe_item->type != PIPE_ITEM_TYPE_DRAW)
+            continue;
+        drawable = SPICE_CONTAINEROF(pipe_item, Drawable, pipe_item);
+        if (ring_item_is_linked(&drawable->list_link))
+            continue; // item hasn't been rendered
+        image = red_add_surface_area_image(worker, drawable->qxl_drawable->surface_id,
+                                           &drawable->qxl_drawable->bbox, pipe_item, TRUE);
+
+        ASSERT(image);
+        red_pipe_remove_drawable(worker, drawable);
+        pipe_item = &image->link;
+    }
+}
+
+SPICE_GNUC_UNUSED static void red_add_lossless_drawable_dependencies(RedWorker *worker,
+                                                                     DisplayChannel *display_channel,
+                                                                     Drawable *item,
+                                                                     int deps_surfaces_ids[],
+                                                                     SpiceRect *deps_areas[],
+                                                                     int num_deps)
+{
+    QXLDrawable *drawable = item->qxl_drawable;
+    int sync_rendered = FALSE;
+    int i;
+
+
+    if (!ring_item_is_linked(&item->list_link)) {
+        /* drawable was already rendered, we may not be able to retrieve the lossless data
+           for the lossy areas */
+        sync_rendered = TRUE;
+
+        // checking if the drawable itself or one of the other commands
+        // that were rendered, affected the areas that need to be resent
+        if (!drawable_intersects_with_areas(item, deps_surfaces_ids,
+                                            deps_areas, num_deps)) {
+            if (pipe_rendered_drawables_intersect_with_areas(worker, display_channel,
+                                                             deps_surfaces_ids,
+                                                             deps_areas,
+                                                             num_deps)) {
+                sync_rendered = TRUE;
+            }
+        } else {
+            sync_rendered = TRUE;
+        }
+    } else {
+        sync_rendered = FALSE;
+        for (i = 0; i < num_deps; i++) {
+            red_update_area_till(worker, deps_areas[i], deps_surfaces_ids[i], item);
+        }
+    }
+
+    if (!sync_rendered) {
+        // pushing the pipe item back to the pipe
+        red_pipe_add_drawable_to_tail(worker, item);
+        // the surfaces areas will be sent as DRAW_COPY commands, that
+        // will be executed before the current drawable
+        for (i = 0; i < num_deps; i++) {
+            red_add_surface_area_image(worker, deps_surfaces_ids[i], deps_areas[i],
+                                       red_pipe_get_tail(worker), FALSE);
+
+        }
+    } else {
+        int drawable_surface_id[1];
+        SpiceRect *drawable_bbox[1];
+
+        drawable_surface_id[0] = drawable->surface_id;
+        drawable_bbox[0] = &drawable->bbox;
+
+        // check if the other rendered images in the pipe have updated the drawable bbox
+        if (pipe_rendered_drawables_intersect_with_areas(worker, display_channel,
+                                                         drawable_surface_id,
+                                                         drawable_bbox,
+                                                         1)) {
+            red_pipe_replace_rendered_drawables_with_images(worker, display_channel);
+        }
+
+
+        red_add_surface_area_image(worker, drawable->surface_id, &drawable->bbox,
+                                   red_pipe_get_tail(worker), TRUE);
+    }
+}
+
 static inline void red_send_qxl_drawable(RedWorker *worker, DisplayChannel *display_channel,
                                          Drawable *item)
 {
commit 5d01cae4303b5b073bb905ca73a6b27c6d7b76e5
Author: Yonit Halperin <yhalperi at redhat.com>
Date:   Tue Jun 1 10:30:53 2010 +0300

    introducing lossy and lz compression in red_send_image

diff --git a/server/red_worker.c b/server/red_worker.c
index bedb82b..1ac8383 100644
--- a/server/red_worker.c
+++ b/server/red_worker.c
@@ -405,6 +405,7 @@ typedef struct ImageItem {
     int surface_id;
     int image_format;
     uint32_t image_flags;
+    int can_lossy;
     uint8_t data[0];
 } ImageItem;
 
@@ -5043,6 +5044,7 @@ static void red_add_surface_image(RedWorker *worker, int surface_id)
     item->height = surface->context.height;
     item->stride = stride;
     item->top_down = surface->context.top_down;
+    item->can_lossy = TRUE;
 
     area.top = area.left = 0;
     area.right = surface->context.width;
@@ -7746,6 +7748,11 @@ static void red_send_image(DisplayChannel *display_channel, ImageItem *item)
     RedImage *red_image;
     RedWorker *worker;
     SpiceBitmap bitmap;
+    QRegion *surface_lossy_region;
+    int comp_succeeded;
+    int lossy_comp = FALSE;
+    int lz_comp = FALSE;
+    spice_image_compression_t comp_mode;
 
     ASSERT(display_channel && item);
     channel = &display_channel->base;
@@ -7790,11 +7797,54 @@ static void red_send_image(DisplayChannel *display_channel, ImageItem *item)
 
     compress_send_data_t comp_send_data;
 
-    if (red_quic_compress_image(display_channel, red_image, &bitmap, &comp_send_data,
-                                worker->mem_slots.internal_groupslot_id)) {
+    comp_mode = display_channel->base.worker->image_compression;
+
+    if ((comp_mode == SPICE_IMAGE_COMPRESS_AUTO_LZ) ||
+        (comp_mode == SPICE_IMAGE_COMPRESS_AUTO_GLZ)) {
+        if (BITMAP_FMT_IS_RGB[item->image_format]) {
+            if (!_stride_is_extra(&bitmap)) {
+                BitmapGradualType grad_level;
+                grad_level = _get_bitmap_graduality_level(display_channel->base.worker,
+                                                          &bitmap,
+                                                          worker->mem_slots.internal_groupslot_id);
+                if (grad_level == BITMAP_GRADUAL_HIGH) {
+                    lossy_comp = worker->enable_jpeg && item->can_lossy &&
+                                 (item->image_format != SPICE_BITMAP_FMT_RGBA);
+                } else {
+                    lz_comp = TRUE;
+                }
+            }
+        } else {
+            lz_comp = TRUE;
+        }
+    }
+
+    if (lossy_comp) {
+        comp_succeeded = red_jpeg_compress_image(display_channel, red_image,
+                                                 &bitmap, &comp_send_data,
+                                                 worker->mem_slots.internal_groupslot_id);
+    } else {
+        if (!lz_comp) {
+            comp_succeeded = red_quic_compress_image(display_channel, red_image, &bitmap,
+                                                     &comp_send_data,
+                                                     worker->mem_slots.internal_groupslot_id);
+        } else {
+            comp_succeeded = red_lz_compress_image(display_channel, red_image, &bitmap,
+                                                   &comp_send_data,
+                                                   worker->mem_slots.internal_groupslot_id);
+        }
+    }
+  
+    surface_lossy_region = &display_channel->surface_client_lossy_region[item->surface_id];
+    if (comp_succeeded) {
         add_buf(channel, BUF_TYPE_RAW, red_image, comp_send_data.raw_size, 0, 0);
         add_buf(channel, BUF_TYPE_COMPRESS_BUF, comp_send_data.comp_buf,
                 comp_send_data.comp_buf_size, 0, 0);
+        if (lossy_comp) {
+            region_add(surface_lossy_region, &display_channel->send_data.u.copy.base.box);
+        } else {
+            region_remove(surface_lossy_region, &display_channel->send_data.u.copy.base.box);
+        }
     } else {
         red_image->descriptor.type = SPICE_IMAGE_TYPE_BITMAP;
         red_image->bitmap = bitmap;
@@ -7802,6 +7852,7 @@ static void red_send_image(DisplayChannel *display_channel, ImageItem *item)
         add_buf(channel, BUF_TYPE_RAW, red_image, sizeof(SpiceBitmapImage), 0, 0);
         red_image->bitmap.data = channel->send_data.header.size;
         add_buf(channel, BUF_TYPE_RAW, item->data, bitmap.y * bitmap.stride, 0, 0);
+        region_remove(surface_lossy_region, &display_channel->send_data.u.copy.base.box);
     }
     display_begin_send_massage(display_channel, &item->link);
 }
commit 2fc2f13be90b2246ddcb8ed0d3a669868a24f69e
Author: Yonit Halperin <yhalperi at redhat.com>
Date:   Tue Jun 1 10:30:52 2010 +0300

    lossy surface regions in the client: infrastructure for tracking and updating

diff --git a/server/red_worker.c b/server/red_worker.c
index aa3b2a5..bedb82b 100644
--- a/server/red_worker.c
+++ b/server/red_worker.c
@@ -657,6 +657,7 @@ struct DisplayChannel {
     pthread_mutex_t glz_drawables_inst_to_free_lock;
 
     uint8_t surface_client_created[NUM_SURFACES];
+    QRegion surface_client_lossy_region[NUM_SURFACES];
 
     struct {
         union {
@@ -1021,6 +1022,20 @@ typedef struct RedWorker {
     int jpeg_quality;
 } RedWorker;
 
+typedef enum {
+    BITMAP_DATA_TYPE_INVALID,
+    BITMAP_DATA_TYPE_CACHE,
+    BITMAP_DATA_TYPE_SURFACE,
+    BITMAP_DATA_TYPE_BITMAP,
+    BITMAP_DATA_TYPE_BITMAP_TO_CACHE,
+} BitmapDataType;
+
+typedef struct BitmapData {
+    BitmapDataType type;
+    uint64_t id; // surface id or cache item id
+    SpiceRect lossy_rect;
+} BitmapData;
+
 static void red_draw_qxl_drawable(RedWorker *worker, Drawable *drawable);
 static void red_current_flush(RedWorker *worker, int surface_id);
 static void display_channel_push(RedWorker *worker);
@@ -6788,6 +6803,151 @@ static inline void red_display_reset_send_data(DisplayChannel *channel)
     memset(channel->send_data.free_list.sync, 0, sizeof(channel->send_data.free_list.sync));
 }
 
+/* set area=NULL for testing the whole surface */
+static int is_surface_area_lossy(DisplayChannel *display_channel, uint32_t surface_id,
+                                 const SpiceRect *area, SpiceRect *out_lossy_area)
+{
+    RedSurface *surface;
+    QRegion *surface_lossy_region;
+    QRegion lossy_region;
+
+    validate_surface(display_channel->base.worker, surface_id);
+    surface = &display_channel->base.worker->surfaces[surface_id];
+    surface_lossy_region = &display_channel->surface_client_lossy_region[surface_id];
+
+    if (!area) {
+        if (region_is_empty(surface_lossy_region)) {
+            return FALSE;
+        } else {
+            out_lossy_area->top = 0;
+            out_lossy_area->left = 0;
+            out_lossy_area->bottom = surface->context.height;
+            out_lossy_area->right = surface->context.width;
+            return TRUE;
+        }
+    }
+
+    region_init(&lossy_region);
+    region_add(&lossy_region, area);
+    region_and(&lossy_region, surface_lossy_region);
+    if (!region_is_empty(&lossy_region)) {
+        out_lossy_area->left = lossy_region.extents.x1;
+        out_lossy_area->top = lossy_region.extents.y1;
+        out_lossy_area->right = lossy_region.extents.x2;
+        out_lossy_area->bottom = lossy_region.extents.y2;
+        region_destroy(&lossy_region);
+        return TRUE;
+    } else {
+        return FALSE;
+    }
+}
+/* returns if the bitmap was already sent lossy to the client. If the bitmap hasn't been sent yet
+   to the client, returns false. "area" is for surfaces. If area = NULL,
+   all the surface is considered. out_lossy_data will hold info about the bitmap, and its lossy
+   area in case it is lossy and part of a surface. */
+static int is_bitmap_lossy(DisplayChannel *display_channel, SPICE_ADDRESS bitmap, SpiceRect *area,
+                           Drawable *drawable, BitmapData *out_data)
+{
+    RedWorker *worker = display_channel->base.worker;
+    QXLImage *qxl_image;
+
+    if (bitmap == 0) {
+        // self bitmap
+        out_data->type = BITMAP_DATA_TYPE_BITMAP;
+        return FALSE;
+    }
+
+    qxl_image = (QXLImage *)get_virt(&worker->mem_slots, bitmap, sizeof(QXLImage),
+                                     drawable->group_id);
+
+    if ((qxl_image->descriptor.flags & QXL_IMAGE_CACHE)) {
+        int is_hit_lossy;
+
+        out_data->id = qxl_image->descriptor.id;
+        if (pixmap_cache_hit(display_channel->pixmap_cache, qxl_image->descriptor.id,
+                             &is_hit_lossy, display_channel)) {
+            out_data->type = BITMAP_DATA_TYPE_CACHE;
+            if (is_hit_lossy) {
+                return TRUE;
+            } else {
+                return FALSE;
+            }
+        } else {
+            out_data->type = BITMAP_DATA_TYPE_BITMAP_TO_CACHE;
+        }
+    } else {
+         out_data->type = BITMAP_DATA_TYPE_BITMAP;
+    }
+
+    if (qxl_image->descriptor.type != SPICE_IMAGE_TYPE_SURFACE) {
+        return FALSE;
+    }
+
+    out_data->type = BITMAP_DATA_TYPE_SURFACE;
+    out_data->id = qxl_image->surface_image.surface_id;
+
+    if (is_surface_area_lossy(display_channel, qxl_image->surface_image.surface_id,
+                              area, &out_data->lossy_rect))
+    {
+        return TRUE;
+    } else {
+        return FALSE;
+    }
+}
+
+SPICE_GNUC_UNUSED static int is_brush_lossy(DisplayChannel *display_channel, SpiceBrush *brush,
+                                            Drawable *drawable, BitmapData *out_data)
+{
+    if (brush->type == SPICE_BRUSH_TYPE_PATTERN) {
+        return is_bitmap_lossy(display_channel, brush->u.pattern.pat, NULL,
+                               drawable, out_data);
+    } else {
+        out_data->type = BITMAP_DATA_TYPE_INVALID;
+        return FALSE;
+    }
+}
+
+SPICE_GNUC_UNUSED static void surface_lossy_region_update(RedWorker *worker, DisplayChannel *display_channel,
+                                                          Drawable *item, int has_mask, int lossy)
+{
+    QRegion *surface_lossy_region;
+    QXLDrawable *drawable;
+
+    if (has_mask && !lossy) {
+        return;
+    }
+
+    surface_lossy_region = &display_channel->surface_client_lossy_region[item->surface_id];
+    drawable = item->qxl_drawable;
+
+    if ((drawable->clip.type == SPICE_CLIP_TYPE_NONE) ||
+        ((drawable->clip.type == SPICE_CLIP_TYPE_PATH) && lossy)) {
+        if (!lossy) {
+            region_remove(surface_lossy_region, &drawable->bbox);
+        } else {
+            region_add(surface_lossy_region, &drawable->bbox);
+        }
+    } else if (drawable->clip.type == SPICE_CLIP_TYPE_RECTS) {
+        QRegion clip_rgn;
+        QRegion draw_region;
+        region_init(&clip_rgn);
+        region_init(&draw_region);
+        region_add(&draw_region, &drawable->bbox);
+        add_clip_rects(worker, &clip_rgn,
+                       drawable->clip.data + SPICE_OFFSETOF(QXLClipRects, chunk),
+                       item->group_id);
+        region_and(&draw_region, &clip_rgn);
+        if (lossy) {
+            region_or(surface_lossy_region, &draw_region);
+        } else {
+            region_exclude(surface_lossy_region, &draw_region);
+        }
+
+        region_destroy(&clip_rgn);
+        region_destroy(&draw_region);
+    } // else SPICE_CLIP_TYPE_PATH and lossless: leave the area as is
+}
+
 static inline void red_send_qxl_drawable(RedWorker *worker, DisplayChannel *display_channel,
                                          Drawable *item)
 {
commit 5d2ae66f5022187e0028a1d7ccf67fe48fdaa94b
Author: Yonit Halperin <yhalperi at redhat.com>
Date:   Tue Jun 1 10:30:51 2010 +0300

    support for lossy images in the pixmap cache and fill bits
    
    1) add an option to determine if a bitmap can be sent lossy to the client
    2) when required, replacing lossy cache items with their correspending
       lossless bitmaps

diff --git a/client/canvas.cpp b/client/canvas.cpp
index 0a4d8e5..4ed1e18 100644
--- a/client/canvas.cpp
+++ b/client/canvas.cpp
@@ -91,6 +91,7 @@ void Canvas::localalize_image(SPICE_ADDRESS* in_bitmap)
     case SPICE_IMAGE_TYPE_JPEG:
         break;
     case SPICE_IMAGE_TYPE_FROM_CACHE:
+    case SPICE_IMAGE_TYPE_FROM_CACHE_LOSSLESS:
         break;
     default:
         THROW("invalid image type %u", image->type);
diff --git a/client/canvas.h b/client/canvas.h
index 8d64ca3..f0314b9 100644
--- a/client/canvas.h
+++ b/client/canvas.h
@@ -163,17 +163,38 @@ public:
         cache->add(id, surface);
     }
 
+    static void op_put_lossy(SpiceImageCache *c, uint64_t id, pixman_image_t *surface)
+    {
+        PixmapCache* cache = reinterpret_cast<PixmapCache*>(c);
+        cache->add(id, surface, TRUE);
+    }
+
+    static void op_replace_lossy(SpiceImageCache *c, uint64_t id, pixman_image_t *surface)
+    {
+        PixmapCache* cache = reinterpret_cast<PixmapCache*>(c);
+        cache->replace(id, surface);
+    }
+
     static pixman_image_t* op_get(SpiceImageCache *c, uint64_t id)
     {
         PixmapCache* cache = reinterpret_cast<PixmapCache*>(c);
         return cache->get(id);
     }
 
+    static pixman_image_t* op_get_lossless(SpiceImageCache *c, uint64_t id)
+    {
+        PixmapCache* cache = reinterpret_cast<PixmapCache*>(c);
+        return cache->get_lossless(id);
+    }
+
     SpiceImageCacheBase()
     {
         static SpiceImageCacheOps cache_ops = {
             op_put,
-            op_get
+            op_get,
+            op_put_lossy,
+            op_replace_lossy,
+            op_get_lossless
         };
         base.ops = &cache_ops;
     }
diff --git a/client/shared_cache.hpp b/client/shared_cache.hpp
index 8836025..a830854 100644
--- a/client/shared_cache.hpp
+++ b/client/shared_cache.hpp
@@ -41,7 +41,7 @@ public:
         clear();
     }
 
-    void add(uint64_t id, T* data)
+    void add(uint64_t id, T* data, bool is_lossy = FALSE)
     {
         Lock lock(_lock);
         Item** item = &_hash[key(id)];
@@ -53,7 +53,7 @@ public:
             }
             item = &(*item)->next;
         }
-        *item = new Item(id, data);
+        *item = new Item(id, data, is_lossy);
         _new_item_cond.notify_all();
     }
 
@@ -81,6 +81,68 @@ public:
         }
     }
 
+    T* get_lossless(uint64_t id)
+    {
+        Lock lock(_lock);
+        Item* item = _hash[key(id)];
+
+        for (;;) {
+            if (!item) {
+                if (_aborting) {
+                    THROW("%s aborting", Treat::name());
+                }
+                _new_item_cond.wait(lock);
+                item = _hash[key(id)];
+                continue;
+            }
+
+            if (item->id != id) {
+                item = item->next;
+                continue;
+            }
+            break;
+        }
+
+        // item has been retreived. Now checking if lossless
+        for (;;) {
+            if (item->lossy) {
+                if (_aborting) {
+                    THROW("%s aborting", Treat::name());
+                }
+                _replace_data_cond.wait(lock);
+                continue;
+            }
+
+            return Treat::get(item->data);
+        }
+    }
+
+    void replace(uint64_t id, T* data, bool is_lossy = FALSE)
+    {
+        Lock lock(_lock);
+        Item* item = _hash[key(id)];
+
+        for (;;) {
+            if (!item) {
+                if (_aborting) {
+                    THROW("%s aborting", Treat::name());
+                }
+                _new_item_cond.wait(lock);
+                item = _hash[key(id)];
+                continue;
+            }
+
+            if (item->id != id) {
+                item = item->next;
+                continue;
+            }
+
+            item->replace(data, is_lossy);
+            break;
+        }
+        _replace_data_cond.notify_all();
+    }
+
     void remove(uint64_t id)
     {
         Lock lock(_lock);
@@ -125,26 +187,36 @@ private:
 private:
     class Item {
     public:
-        Item(uint64_t in_id, T* data)
+        Item(uint64_t in_id, T* data, bool is_lossy = FALSE)
             : id (in_id)
             , refs (1)
             , next (NULL)
-            , data (Treat::get(data)) {}
+            , data (Treat::get(data))
+            , lossy (is_lossy) {}
 
         ~Item()
         {
             Treat::release(data);
         }
 
+        void replace(T* new_data, bool is_lossy = FALSE)
+        {
+            Treat::release(data);
+            data = Treat::get(new_data);
+            lossy = is_lossy;
+        }
+
         uint64_t id;
         int refs;
         Item* next;
         T* data;
+        bool lossy;
     };
 
     Item* _hash[HASH_SIZE];
     Mutex _lock;
     Condition _new_item_cond;
+    Condition _replace_data_cond;
     bool _aborting;
 };
 
diff --git a/common/canvas_base.c b/common/canvas_base.c
index 8180f09..aac472c 100644
--- a/common/canvas_base.c
+++ b/common/canvas_base.c
@@ -567,7 +567,7 @@ static void dump_jpeg(uint8_t* data, int data_size)
     if (!f) {
         return;
     }
-    
+
     fwrite(data, 1, data_size, f);
     fclose(f);
 }
@@ -1044,12 +1044,19 @@ static pixman_image_t *canvas_get_image_internal(CanvasBase *canvas, SPICE_ADDRE
      * to happen which breaks if we don't. */
     if (!real_get &&
         !(descriptor->flags & SPICE_IMAGE_FLAGS_CACHE_ME) &&
+#ifdef SW_CANVAS_CACHE
+        !(descriptor->flags & SPICE_IMAGE_FLAGS_CACHE_REPLACE_ME) &&
+#endif
         (descriptor->type != SPICE_IMAGE_TYPE_GLZ_RGB)) {
         return NULL;
     }
 
     saved_want_original = want_original;
-    if (descriptor->flags & SPICE_IMAGE_FLAGS_CACHE_ME) {
+    if (descriptor->flags & SPICE_IMAGE_FLAGS_CACHE_ME
+#ifdef SW_CANVAS_CACHE
+        || descriptor->flags & SPICE_IMAGE_FLAGS_CACHE_REPLACE_ME
+#endif
+       ) {
         want_original = TRUE;
     }
 
@@ -1092,7 +1099,11 @@ static pixman_image_t *canvas_get_image_internal(CanvasBase *canvas, SPICE_ADDRE
     case SPICE_IMAGE_TYPE_FROM_CACHE:
         surface = canvas->bits_cache->ops->get(canvas->bits_cache, descriptor->id);
         break;
-
+#ifdef SW_CANVAS_CACHE
+    case SPICE_IMAGE_TYPE_FROM_CACHE_LOSSLESS:
+        surface = canvas->bits_cache->ops->get_lossless(canvas->bits_cache, descriptor->id);
+        break;
+#endif
     case SPICE_IMAGE_TYPE_BITMAP: {
         SpiceBitmapImage *bitmap = (SpiceBitmapImage *)descriptor;
         access_test(canvas, descriptor, sizeof(SpiceBitmapImage));
@@ -1107,6 +1118,9 @@ static pixman_image_t *canvas_get_image_internal(CanvasBase *canvas, SPICE_ADDRE
 
     if (descriptor->flags & SPICE_IMAGE_FLAGS_HIGH_BITS_SET &&
         descriptor->type != SPICE_IMAGE_TYPE_FROM_CACHE &&
+#ifdef SW_CANVAS_CACHE
+        descriptor->type != SPICE_IMAGE_TYPE_FROM_CACHE_LOSSLESS &&
+#endif
         surface_format == PIXMAN_x8r8g8b8) {
         spice_pixman_fill_rect_rop(surface,
                                    0, 0,
@@ -1116,13 +1130,39 @@ static pixman_image_t *canvas_get_image_internal(CanvasBase *canvas, SPICE_ADDRE
     }
 
     if (descriptor->flags & SPICE_IMAGE_FLAGS_CACHE_ME &&
-        descriptor->type != SPICE_IMAGE_TYPE_FROM_CACHE) {
+#ifdef SW_CANVAS_CACHE
+        descriptor->type != SPICE_IMAGE_TYPE_FROM_CACHE_LOSSLESS &&
+#endif
+        descriptor->type != SPICE_IMAGE_TYPE_FROM_CACHE ) {
+#ifdef SW_CANVAS_CACHE
+       if (descriptor->type != SPICE_IMAGE_TYPE_JPEG) {
+            canvas->bits_cache->ops->put(canvas->bits_cache, descriptor->id, surface);
+        } else {
+            canvas->bits_cache->ops->put_lossy(canvas->bits_cache, descriptor->id, surface);
+        }
+#else
         canvas->bits_cache->ops->put(canvas->bits_cache, descriptor->id, surface);
+#endif
+#ifdef DEBUG_DUMP_SURFACE
+        dump_surface(surface, 1);
+#endif
+#ifdef SW_CANVAS_CACHE
+    } else if (descriptor->flags & SPICE_IMAGE_FLAGS_CACHE_REPLACE_ME) {
+        if (descriptor->type == SPICE_IMAGE_TYPE_JPEG) {
+            CANVAS_ERROR("invalid cache replace request: the image is lossy");
+        }
+        canvas->bits_cache->ops->replace_lossy(canvas->bits_cache, descriptor->id, surface);
 #ifdef DEBUG_DUMP_SURFACE
         dump_surface(surface, 1);
 #endif
-    } else if (descriptor->type != SPICE_IMAGE_TYPE_FROM_CACHE) {
+#endif
 #ifdef DEBUG_DUMP_SURFACE
+    } else if (descriptor->type != SPICE_IMAGE_TYPE_FROM_CACHE
+#ifdef SW_CANVAS_CACHE
+               && descriptor->type != SPICE_IMAGE_TYPE_FROM_CACHE_LOSSLESS
+#endif
+    ) {
+
         dump_surface(surface, 0);
 #endif
     }
@@ -1438,6 +1478,12 @@ static pixman_image_t *canvas_get_mask(CanvasBase *canvas, SpiceQMask *mask, int
         is_invers = 0;
         break;
 #endif
+#ifdef SW_CANVAS_CACHE
+    case SPICE_IMAGE_TYPE_FROM_CACHE_LOSSLESS:
+        surface = canvas->bits_cache->ops->get_lossless(canvas->bits_cache, descriptor->id);
+        is_invers = 0;
+        break;
+#endif
     default:
         CANVAS_ERROR("invalid image type");
     }
diff --git a/common/canvas_base.h b/common/canvas_base.h
index 4eaafbb..1bbe465 100644
--- a/common/canvas_base.h
+++ b/common/canvas_base.h
@@ -41,6 +41,16 @@ typedef struct {
                 pixman_image_t *surface);
     pixman_image_t *(*get)(SpiceImageCache *cache,
                            uint64_t id);
+#ifdef SW_CANVAS_CACHE
+    void (*put_lossy)(SpiceImageCache *cache,
+                      uint64_t id,
+                      pixman_image_t *surface);
+    void (*replace_lossy)(SpiceImageCache *cache,
+                          uint64_t id,
+                          pixman_image_t *surface);
+    pixman_image_t *(*get_lossless)(SpiceImageCache *cache,
+                                    uint64_t id);
+#endif
 } SpiceImageCacheOps;
 
 struct _SpiceImageCache {
diff --git a/server/red_client_shared_cache.h b/server/red_client_shared_cache.h
index 4d1989d..716b812 100644
--- a/server/red_client_shared_cache.h
+++ b/server/red_client_shared_cache.h
@@ -35,7 +35,7 @@
 #endif
 
 
-static int FUNC_NAME(hit)(CACHE *cache, uint64_t id, CHANNEL *channel)
+static int FUNC_NAME(hit)(CACHE *cache, uint64_t id, int *lossy, CHANNEL *channel)
 {
     NewCacheItem *item;
     uint64_t serial;
@@ -51,6 +51,7 @@ static int FUNC_NAME(hit)(CACHE *cache, uint64_t id, CHANNEL *channel)
             ASSERT(channel->base.id < MAX_CACHE_CLIENTS)
             item->sync[channel->base.id] = serial;
             cache->sync[channel->base.id] = serial;
+            *lossy = item->lossy;
             break;
         }
         item = item->next;
@@ -60,7 +61,25 @@ static int FUNC_NAME(hit)(CACHE *cache, uint64_t id, CHANNEL *channel)
     return !!item;
 }
 
-static int FUNC_NAME(add)(CACHE *cache, uint64_t id, uint32_t size, CHANNEL *channel)
+static int FUNC_NAME(set_lossy)(CACHE *cache, uint64_t id, int lossy)
+{
+    NewCacheItem *item;
+    pthread_mutex_lock(&cache->lock);
+
+    item = cache->hash_table[CACHE_HASH_KEY(id)];
+
+    while (item) {
+        if (item->id == id) {
+            item->lossy = lossy;
+            break;
+        }
+        item = item->next;
+   } 
+    pthread_mutex_unlock(&cache->lock);
+    return !!item;
+}
+
+static int FUNC_NAME(add)(CACHE *cache, uint64_t id, uint32_t size, int lossy, CHANNEL *channel)
 {
     NewCacheItem *item;
     uint64_t serial;
@@ -119,6 +138,7 @@ static int FUNC_NAME(add)(CACHE *cache, uint64_t id, uint32_t size, CHANNEL *cha
     ring_add(&cache->lru, &item->lru_link);
     item->id = id;
     item->size = size;
+    item->lossy = lossy;
     memset(item->sync, 0, sizeof(item->sync));
     item->sync[channel->base.id] = serial;
     cache->sync[channel->base.id] = serial;
diff --git a/server/red_worker.c b/server/red_worker.c
index aac1d27..aa3b2a5 100644
--- a/server/red_worker.c
+++ b/server/red_worker.c
@@ -268,6 +268,7 @@ struct NewCacheItem {
     uint64_t id;
     uint64_t sync[MAX_CACHE_CLIENTS];
     size_t size;
+    int lossy;
 };
 
 typedef struct CacheItem CacheItem;
@@ -5969,6 +5970,7 @@ typedef struct compress_send_data_t {
     uint32_t comp_buf_size;
     SPICE_ADDRESS  *plt_ptr;
     uint8_t    *flags_ptr;
+    int is_lossy;
 } compress_send_data_t;
 
 
@@ -6034,6 +6036,7 @@ static inline int red_glz_compress_image(DisplayChannel *display_channel,
     o_comp_data->comp_buf_size = size;
     o_comp_data->plt_ptr = NULL;
     o_comp_data->flags_ptr = NULL;
+    o_comp_data->is_lossy = FALSE;
 
     stat_compress_add(&display_channel->glz_stat, start_time, src->stride * src->y,
                       o_comp_data->comp_buf_size);
@@ -6121,6 +6124,8 @@ static inline int red_lz_compress_image(DisplayChannel *display_channel,
         o_comp_data->plt_ptr = &(dest->lz_plt.palette);
         o_comp_data->flags_ptr = &(dest->lz_plt.flags);
     }
+
+    o_comp_data->is_lossy = FALSE;
     stat_compress_add(&display_channel->lz_stat, start_time, src->stride * src->y,
                       o_comp_data->comp_buf_size);
     return TRUE;
@@ -6263,6 +6268,7 @@ static int red_jpeg_compress_image(DisplayChannel *display_channel, RedImage *de
     o_comp_data->comp_buf_size = size;
     o_comp_data->plt_ptr = NULL;
     o_comp_data->flags_ptr = NULL;
+    o_comp_data->is_lossy = TRUE;
     stat_compress_add(&display_channel->jpeg_stat, start_time, src->stride * src->y,
                       o_comp_data->comp_buf_size);
     return TRUE;
@@ -6409,6 +6415,7 @@ static inline int red_quic_compress_image(DisplayChannel *display_channel, RedIm
     o_comp_data->comp_buf_size = size << 2;
     o_comp_data->plt_ptr = NULL;
     o_comp_data->flags_ptr = NULL;
+    o_comp_data->is_lossy = FALSE;
 
     stat_compress_add(&display_channel->quic_stat, start_time, src->stride * src->y,
                       o_comp_data->comp_buf_size);
@@ -6419,6 +6426,7 @@ static inline int red_quic_compress_image(DisplayChannel *display_channel, RedIm
 #define MIN_DIMENSION_TO_QUIC 3
 static inline int red_compress_image(DisplayChannel *display_channel,
                                      RedImage *dest, SpiceBitmap *src, Drawable *drawable,
+                                     int can_lossy,
                                      compress_send_data_t* o_comp_data)
 {
     spice_image_compression_t image_compression =
@@ -6472,7 +6480,7 @@ static inline int red_compress_image(DisplayChannel *display_channel,
         red_printf("QUIC compress");
 #endif
         // if bitmaps is picture-like, compress it using jpeg
-        if (display_channel->base.worker->enable_jpeg &&
+        if (can_lossy && display_channel->base.worker->enable_jpeg &&
             ((image_compression == SPICE_IMAGE_COMPRESS_AUTO_LZ) ||
             (image_compression == SPICE_IMAGE_COMPRESS_AUTO_GLZ))) {
             if (src->format != SPICE_BITMAP_FMT_RGBA) {
@@ -6526,15 +6534,18 @@ static inline int red_compress_image(DisplayChannel *display_channel,
 }
 
 static inline void red_display_add_image_to_pixmap_cache(DisplayChannel *display_channel,
-                                                         QXLImage *qxl_image, RedImage *io_image)
+                                                         QXLImage *qxl_image, RedImage *io_image,
+                                                         int is_lossy)
 {
     if ((qxl_image->descriptor.flags & QXL_IMAGE_CACHE)) {
         ASSERT(qxl_image->descriptor.width * qxl_image->descriptor.height > 0);
-        if (pixmap_cache_add(display_channel->pixmap_cache, qxl_image->descriptor.id,
-                             qxl_image->descriptor.width * qxl_image->descriptor.height,
-                             display_channel)) {
-            io_image->descriptor.flags |= SPICE_IMAGE_FLAGS_CACHE_ME;
-            stat_inc_counter(display_channel->add_to_cache_counter, 1);
+        if (!(io_image->descriptor.flags & SPICE_IMAGE_FLAGS_CACHE_REPLACE_ME)) {
+            if (pixmap_cache_add(display_channel->pixmap_cache, qxl_image->descriptor.id,
+                                 qxl_image->descriptor.width * qxl_image->descriptor.height, is_lossy,
+                                 display_channel)) {
+                io_image->descriptor.flags |= SPICE_IMAGE_FLAGS_CACHE_ME;
+                stat_inc_counter(display_channel->add_to_cache_counter, 1);
+            }
         }
     }
 
@@ -6545,7 +6556,8 @@ static inline void red_display_add_image_to_pixmap_cache(DisplayChannel *display
 
 /* if the number of times fill_bits can be called per one qxl_drawable increases -
    MAX_LZ_DRAWABLE_INSTANCES must be increased as well */
-static void fill_bits(DisplayChannel *display_channel, QXLPHYSICAL *in_bitmap, Drawable *drawable)
+static void fill_bits(DisplayChannel *display_channel, QXLPHYSICAL *in_bitmap,
+                      Drawable *drawable, int can_lossy)
 {
     RedChannel *channel = &display_channel->base;
     RedWorker *worker = channel->worker;
@@ -6577,12 +6589,26 @@ static void fill_bits(DisplayChannel *display_channel, QXLPHYSICAL *in_bitmap, D
     memslot_id = get_memslot_id(&worker->mem_slots, *in_bitmap);
     *in_bitmap = channel->send_data.header.size;
     if ((qxl_image->descriptor.flags & QXL_IMAGE_CACHE)) {
+        int lossy_cache_item;
         if (pixmap_cache_hit(display_channel->pixmap_cache, image->descriptor.id,
-                             display_channel)) {
-            image->descriptor.type = SPICE_IMAGE_TYPE_FROM_CACHE;
-            add_buf(channel, BUF_TYPE_RAW, image, sizeof(SpiceImageDescriptor), 0, 0);
-            stat_inc_counter(display_channel->cache_hits_counter, 1);
-            return;
+                             &lossy_cache_item, display_channel)) {
+            if (can_lossy || !lossy_cache_item) {
+                if (!worker->enable_jpeg || lossy_cache_item) {
+                    image->descriptor.type = SPICE_IMAGE_TYPE_FROM_CACHE;
+                } else {
+                    // making sure, in multiple monitor scenario, that lossy items that
+                    // should have been replaced with lossless data by one display channel,
+                    // will be retrieved as lossless by another display channel.
+                    image->descriptor.type = SPICE_IMAGE_TYPE_FROM_CACHE_LOSSLESS;
+                }
+                add_buf(channel, BUF_TYPE_RAW, image, sizeof(SpiceImageDescriptor), 0, 0);
+                stat_inc_counter(display_channel->cache_hits_counter, 1);
+                return;
+            } else {
+                pixmap_cache_set_lossy(display_channel->pixmap_cache, qxl_image->descriptor.id,
+                                       FALSE);
+                image->descriptor.flags |= SPICE_IMAGE_FLAGS_CACHE_REPLACE_ME;
+            }
         }
     }
 
@@ -6612,12 +6638,12 @@ static void fill_bits(DisplayChannel *display_channel, QXLPHYSICAL *in_bitmap, D
            in order to prevent starvation in the client between pixmap_cache and
            global dictionary (in cases of multiple monitors) */
         if (!red_compress_image(display_channel, image, &qxl_image->bitmap,
-                                drawable, &comp_send_data)) {
+                                drawable, can_lossy, &comp_send_data)) {
             uint32_t y;
             uint32_t stride;
             SPICE_ADDRESS image_data;
 
-            red_display_add_image_to_pixmap_cache(display_channel, qxl_image, image);
+            red_display_add_image_to_pixmap_cache(display_channel, qxl_image, image, FALSE);
 
             image->bitmap = qxl_image->bitmap;
             y = image->bitmap.y;
@@ -6638,7 +6664,8 @@ static void fill_bits(DisplayChannel *display_channel, QXLPHYSICAL *in_bitmap, D
                 add_buf(channel, BUF_TYPE_CHUNK, data, y * stride, memslot_id, drawable->group_id);
             }
         } else {
-            red_display_add_image_to_pixmap_cache(display_channel, qxl_image, image);
+            red_display_add_image_to_pixmap_cache(display_channel, qxl_image, image,
+                                                  comp_send_data.is_lossy);
 
             add_buf((RedChannel *)display_channel, BUF_TYPE_RAW, image, comp_send_data.raw_size,
                     0, 0);
@@ -6652,7 +6679,7 @@ static void fill_bits(DisplayChannel *display_channel, QXLPHYSICAL *in_bitmap, D
         }
         break;
     case SPICE_IMAGE_TYPE_QUIC:
-        red_display_add_image_to_pixmap_cache(display_channel, qxl_image, image);
+        red_display_add_image_to_pixmap_cache(display_channel, qxl_image, image, FALSE);
         image->quic = qxl_image->quic;
         add_buf(channel, BUF_TYPE_RAW, image, sizeof(SpiceQUICImage), 0, 0);
         add_buf(channel, BUF_TYPE_CHUNK, qxl_image->quic.data, qxl_image->quic.data_size,
@@ -6666,7 +6693,7 @@ static void fill_bits(DisplayChannel *display_channel, QXLPHYSICAL *in_bitmap, D
 static void fill_brush(DisplayChannel *display_channel, SpiceBrush *brush, Drawable *drawable)
 {
     if (brush->type == SPICE_BRUSH_TYPE_PATTERN) {
-        fill_bits(display_channel, &brush->u.pattern.pat, drawable);
+        fill_bits(display_channel, &brush->u.pattern.pat, drawable, FALSE);
     }
 }
 
@@ -6677,10 +6704,10 @@ static void fill_mask(DisplayChannel *display_channel, SpiceQMask *mask, Drawabl
             spice_image_compression_t save_img_comp =
                 display_channel->base.worker->image_compression;
             display_channel->base.worker->image_compression = SPICE_IMAGE_COMPRESS_OFF;
-            fill_bits(display_channel, &mask->bitmap, drawable);
+            fill_bits(display_channel, &mask->bitmap, drawable, FALSE);
             display_channel->base.worker->image_compression = save_img_comp;
         } else {
-            fill_bits(display_channel, &mask->bitmap, drawable);
+            fill_bits(display_channel, &mask->bitmap, drawable, FALSE);
         }
     }
 }
@@ -6781,7 +6808,8 @@ static inline void red_send_qxl_drawable(RedWorker *worker, DisplayChannel *disp
         fill_base(display_channel, &display_channel->send_data.u.opaque.base, item,
                   sizeof(SpiceMsgDisplayDrawOpaque), item->surface_id);
         display_channel->send_data.u.opaque.data = drawable->u.opaque;
-        fill_bits(display_channel, &display_channel->send_data.u.opaque.data.src_bitmap, item);
+        fill_bits(display_channel, &display_channel->send_data.u.opaque.data.src_bitmap,
+                  item, FALSE);
         fill_brush(display_channel, &display_channel->send_data.u.opaque.data.brush, item);
         fill_mask(display_channel, &display_channel->send_data.u.opaque.data.mask, item);
         break;
@@ -6790,7 +6818,7 @@ static inline void red_send_qxl_drawable(RedWorker *worker, DisplayChannel *disp
         fill_base(display_channel, &display_channel->send_data.u.copy.base, item,
                   sizeof(SpiceMsgDisplayDrawCopy), item->surface_id);
         display_channel->send_data.u.copy.data = drawable->u.copy;
-        fill_bits(display_channel, &display_channel->send_data.u.copy.data.src_bitmap, item);
+        fill_bits(display_channel, &display_channel->send_data.u.copy.data.src_bitmap, item, FALSE);
         fill_mask(display_channel, &display_channel->send_data.u.copy.data.mask, item);
         break;
     case QXL_DRAW_TRANSPARENT:
@@ -6798,14 +6826,16 @@ static inline void red_send_qxl_drawable(RedWorker *worker, DisplayChannel *disp
         fill_base(display_channel, &display_channel->send_data.u.transparent.base, item,
                   sizeof(SpiceMsgDisplayDrawTransparent), item->surface_id);
         display_channel->send_data.u.transparent.data = drawable->u.transparent;
-        fill_bits(display_channel, &display_channel->send_data.u.transparent.data.src_bitmap, item);
+        fill_bits(display_channel, &display_channel->send_data.u.transparent.data.src_bitmap,
+                  item, FALSE);
         break;
     case QXL_DRAW_ALPHA_BLEND:
         channel->send_data.header.type = SPICE_MSG_DISPLAY_DRAW_ALPHA_BLEND;
         fill_base(display_channel, &display_channel->send_data.u.alpha_blend.base, item,
                   sizeof(SpiceMsgDisplayDrawAlphaBlend), item->surface_id);
         display_channel->send_data.u.alpha_blend.data = drawable->u.alpha_blend;
-        fill_bits(display_channel, &display_channel->send_data.u.alpha_blend.data.src_bitmap, item);
+        fill_bits(display_channel, &display_channel->send_data.u.alpha_blend.data.src_bitmap,
+                  item, FALSE);
         break;
     case QXL_COPY_BITS:
         channel->send_data.header.type = SPICE_MSG_DISPLAY_COPY_BITS;
@@ -6818,7 +6848,8 @@ static inline void red_send_qxl_drawable(RedWorker *worker, DisplayChannel *disp
         fill_base(display_channel, &display_channel->send_data.u.blend.base, item,
                   sizeof(SpiceMsgDisplayDrawBlend), item->surface_id);
         display_channel->send_data.u.blend.data = drawable->u.blend;
-        fill_bits(display_channel, &display_channel->send_data.u.blend.data.src_bitmap, item);
+        fill_bits(display_channel, &display_channel->send_data.u.blend.data.src_bitmap,
+                  item, FALSE);
         fill_mask(display_channel, &display_channel->send_data.u.blend.data.mask, item);
         break;
     case QXL_DRAW_BLACKNESS:
@@ -6847,7 +6878,8 @@ static inline void red_send_qxl_drawable(RedWorker *worker, DisplayChannel *disp
         fill_base(display_channel, &display_channel->send_data.u.rop3.base, item,
                   sizeof(SpiceMsgDisplayDrawRop3), item->surface_id);
         display_channel->send_data.u.rop3.data = drawable->u.rop3;
-        fill_bits(display_channel, &display_channel->send_data.u.rop3.data.src_bitmap, item);
+        fill_bits(display_channel, &display_channel->send_data.u.rop3.data.src_bitmap,
+                  item, FALSE);
         fill_brush(display_channel, &display_channel->send_data.u.rop3.data.brush, item);
         fill_mask(display_channel, &display_channel->send_data.u.rop3.data.mask, item);
         break;
@@ -7638,7 +7670,7 @@ static void red_display_send_upgrade(DisplayChannel *display_channel, UpgradeIte
     add_buf(channel, BUF_TYPE_RAW, &item->n_rects, sizeof(uint32_t), 0, 0);
     add_buf(channel, BUF_TYPE_RAW, item->rects, sizeof(SpiceRect) * item->n_rects, 0, 0);
     copy->data = qxl_drawable->u.copy;
-    fill_bits(display_channel, &copy->data.src_bitmap, item->drawable);
+    fill_bits(display_channel, &copy->data.src_bitmap, item->drawable, FALSE);
 
     display_begin_send_massage(display_channel, &item->base);
 }
commit 263646a1f7e705766f7d46017679812d4b1406b8
Author: Yonit Halperin <yhalperi at redhat.com>
Date:   Wed Jun 9 11:40:25 2010 +0200

    JPEG support: introducing jpeg encoding for spice bitmaps

diff --git a/client/Makefile.am b/client/Makefile.am
index e1c31fd..55bc0f8 100644
--- a/client/Makefile.am
+++ b/client/Makefile.am
@@ -43,6 +43,8 @@ RED_COMMON_SRCS =			\
 	inputs_channel.cpp		\
 	inputs_channel.h		\
 	inputs_handler.h		\
+	jpeg_decoder.cpp		\
+	jpeg_decoder.h			\
 	lz.cpp				\
 	monitor.cpp			\
 	monitor.h			\
diff --git a/client/canvas.cpp b/client/canvas.cpp
index 419bfc9..0a4d8e5 100644
--- a/client/canvas.cpp
+++ b/client/canvas.cpp
@@ -88,6 +88,7 @@ void Canvas::localalize_image(SPICE_ADDRESS* in_bitmap)
     case SPICE_IMAGE_TYPE_LZ_RGB:
     case SPICE_IMAGE_TYPE_GLZ_RGB:
     case SPICE_IMAGE_TYPE_QUIC:
+    case SPICE_IMAGE_TYPE_JPEG:
         break;
     case SPICE_IMAGE_TYPE_FROM_CACHE:
         break;
diff --git a/client/canvas.h b/client/canvas.h
index 135783b..8d64ca3 100644
--- a/client/canvas.h
+++ b/client/canvas.h
@@ -29,6 +29,7 @@
 #include "canvas_utils.h"
 #include "glz_decoded_image.h"
 #include "glz_decoder.h"
+#include "jpeg_decoder.h"
 
 enum CanvasType {
     CANVAS_TYPE_INVALID,
@@ -423,6 +424,7 @@ protected:
     CSurfaces& csurfaces() { return _csurfaces; }
 
     GlzDecoder& glz_decoder() {return _glz_decoder;}
+    JpegDecoder& jpeg_decoder() { return _jpeg_decoder;}
 
 private:
     void access_test(void* ptr, size_t size);
@@ -445,6 +447,8 @@ private:
     GlzDecoderCanvasDebug _glz_debug;
     GlzDecoder _glz_decoder;
 
+    JpegDecoder _jpeg_decoder;
+
     CSurfaces& _csurfaces;
 
     unsigned long _base;
diff --git a/client/jpeg_decoder.cpp b/client/jpeg_decoder.cpp
new file mode 100644
index 0000000..a7824a9
--- /dev/null
+++ b/client/jpeg_decoder.cpp
@@ -0,0 +1,147 @@
+/*
+   Copyright (C) 2009 Red Hat, Inc.
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "common.h"
+#include "jpeg_decoder.h"
+#include "debug.h"
+#include "utils.h"
+
+static void op_begin_decode(SpiceJpegDecoder *decoder,
+                            uint8_t* data,
+                            int data_size,
+                            int* out_width,
+                            int* out_height)
+{
+    JpegDecoder* _decoder = static_cast<JpegDecoder*>(decoder);
+    _decoder->begin_decode(data, data_size, *out_width, *out_height);
+}
+
+static void op_decode(SpiceJpegDecoder *decoder,
+                      uint8_t* dest,
+                      int stride,
+                      int format)
+{
+    JpegDecoder* _decoder = static_cast<JpegDecoder*>(decoder);
+    _decoder->decode(dest, stride, format);
+}
+
+extern "C" {
+
+    void jpeg_decoder_init_source(j_decompress_ptr cinfo)
+    {
+    }
+
+    static boolean jpeg_decoder_fill_input_buffer(j_decompress_ptr cinfo)
+    {
+        PANIC("no more data for jpeg");
+        return FALSE;
+    }
+
+    static void jpeg_decoder_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
+    {
+        ASSERT(num_bytes < (long)cinfo->src->bytes_in_buffer);
+        cinfo->src->next_input_byte += num_bytes;
+        cinfo->src->bytes_in_buffer -= num_bytes;
+    }
+
+    static void jpeg_decoder_term_source (j_decompress_ptr cinfo)
+    {
+        return;
+    }
+}
+
+
+JpegDecoder::JpegDecoder()
+    : _data (NULL)
+    , _data_size (0)
+{
+    _cinfo.err = jpeg_std_error(&_jerr);
+    jpeg_create_decompress(&_cinfo);
+
+    _cinfo.src = &_jsrc;
+    _cinfo.src->init_source = jpeg_decoder_init_source;
+    _cinfo.src->fill_input_buffer = jpeg_decoder_fill_input_buffer;
+    _cinfo.src->skip_input_data = jpeg_decoder_skip_input_data;
+    _cinfo.src->resync_to_restart = jpeg_resync_to_restart;
+    _cinfo.src->term_source = jpeg_decoder_term_source;
+    
+    static SpiceJpegDecoderOps decoder_ops = {
+        op_begin_decode,
+        op_decode,
+    };
+
+    ops = &decoder_ops;
+}
+
+JpegDecoder::~JpegDecoder()
+{
+    jpeg_destroy_decompress(&_cinfo);
+}
+
+void JpegDecoder::begin_decode(uint8_t* data, int data_size, int& out_width, int& out_height)
+{
+    ASSERT(data);
+    ASSERT(data_size);
+
+    if (_data) {
+        jpeg_abort_decompress(&_cinfo);
+    }
+
+    _data = data;
+    _data_size = data_size;
+
+    _cinfo.src->next_input_byte = _data;
+    _cinfo.src->bytes_in_buffer = _data_size;
+
+    jpeg_read_header(&_cinfo, TRUE);
+
+    _cinfo.out_color_space = JCS_RGB;
+    _width = _cinfo.image_width;
+    _height = _cinfo.image_height;
+
+    out_width = _width;
+    out_height = _height;
+}
+
+void JpegDecoder::decode(uint8_t *dest, int stride, int format)
+{
+    uint8_t* scan_line = new uint8_t[_width*3];
+    RGBConverter* rgb_converter;
+
+    switch (format) {
+    case SPICE_BITMAP_FMT_24BIT:
+        rgb_converter = &_rgb2bgr;
+        break;
+    case SPICE_BITMAP_FMT_32BIT:
+        rgb_converter = &_rgb2bgrx;
+        break;
+    default:
+        THROW("bad bitmap format, %d", format);
+    }
+
+    jpeg_start_decompress(&_cinfo);
+
+    for (int row = 0; row < _height; row++) {
+        jpeg_read_scanlines(&_cinfo, &scan_line, 1);
+        rgb_converter->convert(scan_line, dest, _width);
+        dest += stride;
+    }
+
+    delete [] scan_line;
+
+    jpeg_finish_decompress(&_cinfo);
+}
diff --git a/client/jpeg_decoder.h b/client/jpeg_decoder.h
new file mode 100644
index 0000000..5c38cfa
--- /dev/null
+++ b/client/jpeg_decoder.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2010 Red Hat, Inc.
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _H_JPEG_DECODER
+#define _H_JPEG_DECODER
+
+#include "common.h"
+#include "canvas_base.h"
+
+#ifdef WIN32
+/* We need some hacks to avoid warnings from the jpeg headers */
+#define XMD_H
+#undef FAR
+#endif
+
+extern "C" {
+#include <jpeglib.h>
+}
+
+class RGBConverter {
+public:
+    virtual ~RGBConverter() {}
+    virtual void convert(uint8_t* src, uint8_t* dest, int width) = 0;
+};
+
+class RGBToBGRConverter : public RGBConverter {
+public:
+    void convert(uint8_t* src, uint8_t* dest, int width)
+    {
+        for (int x = 0; x < width; x++) {
+            *dest++ = src[2];
+            *dest++ = src[1];
+            *dest++ = src[0];
+            src += 3;
+        }
+    }
+};
+
+class RGBToBGRXConverter : public RGBConverter {
+public:
+    void convert(uint8_t* src, uint8_t* dest, int width)
+    {
+        for (int x = 0; x < width; x++) {
+            *dest++ = src[2];
+            *dest++ = src[1];
+            *dest++ = src[0];
+            *dest++ = 0;
+            src += 3;
+        }
+    }
+};
+
+class JpegDecoder : public SpiceJpegDecoder {
+public:
+    JpegDecoder();
+    ~JpegDecoder();
+
+    void begin_decode(uint8_t* data, int data_size, int& out_width, int& out_height);
+    /* format is SPICE_BITMAP_FMT_<X> for the dest; currently, only
+       x=32BIT and x=24BIT are supported */
+    void decode(uint8_t* dest, int stride, int format);
+
+private:
+    struct jpeg_decompress_struct _cinfo;
+    struct jpeg_error_mgr _jerr;
+    struct jpeg_source_mgr _jsrc;
+
+    uint8_t* _data;
+    int _data_size;
+    int _width;
+    int _height;
+
+    RGBToBGRConverter _rgb2bgr;
+    RGBToBGRXConverter _rgb2bgrx;
+};
+#endif
\ No newline at end of file
diff --git a/client/red_gdi_canvas.cpp b/client/red_gdi_canvas.cpp
index 391883b..453023e 100644
--- a/client/red_gdi_canvas.cpp
+++ b/client/red_gdi_canvas.cpp
@@ -37,7 +37,8 @@ GDICanvas::GDICanvas(int width, int height, uint32_t format,
                                       format, &pixmap_cache.base,
                                       &palette_cache.base,
                                       &csurfaces.base,
-                                      &glz_decoder()))) {
+                                      &glz_decoder(),
+                                      &jpeg_decoder()))) {
         THROW("create canvas failed");
     }
 }
diff --git a/client/red_gl_canvas.cpp b/client/red_gl_canvas.cpp
index 13e4723..1a219cd 100644
--- a/client/red_gl_canvas.cpp
+++ b/client/red_gl_canvas.cpp
@@ -40,7 +40,8 @@ GCanvas::GCanvas(int width, int height, uint32_t format, RedWindow *win,
                                      &pixmap_cache.base,
                                      &palette_cache.base,
                                      &csurfaces.base,
-                                     &glz_decoder()))) {
+                                     &glz_decoder(),
+                                     &jpeg_decoder()))) {
         THROW("create canvas failed");
     }
 }
diff --git a/client/red_sw_canvas.cpp b/client/red_sw_canvas.cpp
index 05da430..7a8daf4 100644
--- a/client/red_sw_canvas.cpp
+++ b/client/red_sw_canvas.cpp
@@ -42,13 +42,15 @@ SCanvas::SCanvas(bool onscreen,
                                          &pixmap_cache.base,
                                          &palette_cache.base,
                                          &csurfaces.base,
-                                         &glz_decoder());
+                                         &glz_decoder(),
+                                         &jpeg_decoder());
     } else {
         _canvas = canvas_create(width, height, format,
                                 &pixmap_cache.base,
                                 &palette_cache.base,
                                 &csurfaces.base,
-                                &glz_decoder());
+                                &glz_decoder(),
+                                &jpeg_decoder());
     }
     if (_canvas == NULL) {
         THROW("create canvas failed");
diff --git a/client/screen.cpp b/client/screen.cpp
index 1567978..bc87646 100644
--- a/client/screen.cpp
+++ b/client/screen.cpp
@@ -692,7 +692,7 @@ void RedScreen::on_mouse_button_release(SpiceMouseButton button, unsigned int bu
 
 void RedScreen::on_pointer_leave()
 {
-    ASSERT(!_mouse_captured);
+//    ASSERT(!_mouse_captured);
 
     if (_pointer_layer) {
         _pointer_layer->on_pointer_leave();
diff --git a/client/windows/redc.vcproj b/client/windows/redc.vcproj
index d2540ce..c0ba905 100644
--- a/client/windows/redc.vcproj
+++ b/client/windows/redc.vcproj
@@ -252,6 +252,10 @@
 				>
 			</File>
 			<File
+				RelativePath="..\jpeg_decoder.cpp"
+				>
+			</File>
+			<File
 				RelativePath="..\lines.cpp"
 				>
 			</File>
@@ -524,6 +528,10 @@
 				>
 			</File>
 			<File
+				RelativePath="..\jpeg_decoder.h"
+				>
+			</File>
+			<File
 				RelativePath="..\menu.h"
 				>
 			</File>
diff --git a/client/x11/Makefile.am b/client/x11/Makefile.am
index 904fb42..26140f4 100644
--- a/client/x11/Makefile.am
+++ b/client/x11/Makefile.am
@@ -69,6 +69,8 @@ RED_COMMON_SRCS =					\
 	$(CLIENT_DIR)/inputs_channel.cpp		\
 	$(CLIENT_DIR)/inputs_channel.h			\
 	$(CLIENT_DIR)/inputs_handler.h			\
+	$(CLIENT_DIR)/jpeg_decoder.cpp			\
+	$(CLIENT_DIR)/jpeg_decoder.h			\
 	$(CLIENT_DIR)/lz.cpp				\
 	$(CLIENT_DIR)/lines.cpp				\
 	$(CLIENT_DIR)/monitor.cpp			\
diff --git a/common/canvas_base.c b/common/canvas_base.c
index 9d9c977..8180f09 100644
--- a/common/canvas_base.c
+++ b/common/canvas_base.c
@@ -199,6 +199,7 @@ typedef struct CanvasBase {
 
     LzData lz_data;
     GlzData glz_data;
+    SpiceJpegDecoder* jpeg;
 
     void *usr_data;
     spice_destroy_fn_t usr_data_destroy;
@@ -547,6 +548,78 @@ static pixman_image_t *canvas_get_quic(CanvasBase *canvas, SpiceQUICImage *image
     return surface;
 }
 
+
+//#define DUMP_JPEG
+#ifdef DUMP_JPEG
+static int jpeg_id = 0;
+static void dump_jpeg(uint8_t* data, int data_size)
+{
+    char file_str[200];
+    uint32_t id = ++jpeg_id;
+
+#ifdef WIN32
+    sprintf(file_str, "c:\\tmp\\spice_dump\\%u.jpg", id);
+#else
+    sprintf(file_str, "/tmp/spice_dump/%u.jpg", id);
+#endif
+
+    FILE *f = fopen(file_str, "wb");
+    if (!f) {
+        return;
+    }
+    
+    fwrite(data, 1, data_size, f);
+    fclose(f);
+}
+#endif
+
+static pixman_image_t *canvas_get_jpeg(CanvasBase *canvas, SpiceJPEGImage *image, int invers)
+{
+    pixman_image_t *surface = NULL;
+    int stride;
+    int width;
+    int height;
+    uint8_t *dest;
+
+    canvas->jpeg->ops->begin_decode(canvas->jpeg, image->jpeg.data, image->jpeg.data_size,
+                                    &width, &height);
+    ASSERT((uint32_t)width == image->descriptor.width);
+    ASSERT((uint32_t)height == image->descriptor.height);
+
+    surface = surface_create(
+#ifdef WIN32
+                             canvas->dc,
+#endif
+                             PIXMAN_x8r8g8b8,
+                             width, height, FALSE);
+    if (surface == NULL) {
+        CANVAS_ERROR("create surface failed");
+    }
+
+    dest = (uint8_t *)pixman_image_get_data(surface);
+    stride = pixman_image_get_stride(surface);
+
+    canvas->jpeg->ops->decode(canvas->jpeg, dest, stride, SPICE_BITMAP_FMT_32BIT);
+
+    if (invers) {
+        uint8_t *end = dest + height * stride;
+        for (; dest != end; dest += stride) {
+            uint32_t *pix;
+            uint32_t *end_pix;
+
+            pix = (uint32_t *)dest;
+            end_pix = pix + width;
+            for (; pix < end_pix; pix++) {
+                *pix ^= 0x00ffffff;
+            }
+        }
+    }
+#ifdef DUMP_JPEG
+    dump_jpeg(image->jpeg.data, image->jpeg.data_size);
+#endif
+    return surface;
+}
+
 static pixman_image_t *canvas_bitmap_to_surface(CanvasBase *canvas, SpiceBitmap* bitmap,
                                                 SpicePalette *palette, int want_original)
 {
@@ -1001,7 +1074,12 @@ static pixman_image_t *canvas_get_image_internal(CanvasBase *canvas, SPICE_ADDRE
         break;
     }
 #endif
-
+    case SPICE_IMAGE_TYPE_JPEG: {
+        SpiceJPEGImage *image = (SpiceJPEGImage *)descriptor;
+        access_test(canvas, descriptor, sizeof(SpiceJPEGImage));
+        surface = canvas_get_jpeg(canvas, image, 0);
+        break;
+    }
 #if defined(SW_CANVAS_CACHE)
     case SPICE_IMAGE_TYPE_GLZ_RGB: {
         access_test(canvas, descriptor, sizeof(SpiceLZRGBImage));
@@ -3234,6 +3312,7 @@ static int canvas_base_init(CanvasBase *canvas, SpiceCanvasOps *ops,
 #endif
                             , SpiceImageSurfaces *surfaces
                             , SpiceGlzDecoder *glz_decoder
+                            , SpiceJpegDecoder *jpeg_decoder
 #ifndef SW_CANVAS_NO_CHUNKS
                             , SpiceVirtMapping *virt_mapping
 #endif
@@ -3267,6 +3346,7 @@ static int canvas_base_init(CanvasBase *canvas, SpiceCanvasOps *ops,
 #endif
     canvas->surfaces = surfaces;
     canvas->glz_data.decoder = glz_decoder;
+    canvas->jpeg = jpeg_decoder;
 
     canvas->format = format;
 
diff --git a/common/canvas_base.h b/common/canvas_base.h
index f78b0b8..4eaafbb 100644
--- a/common/canvas_base.h
+++ b/common/canvas_base.h
@@ -31,6 +31,7 @@ typedef struct _SpiceImageCache SpiceImageCache;
 typedef struct _SpiceImageSurfaces SpiceImageSurfaces;
 typedef struct _SpicePaletteCache SpicePaletteCache;
 typedef struct _SpiceGlzDecoder SpiceGlzDecoder;
+typedef struct _SpiceJpegDecoder SpiceJpegDecoder;
 typedef struct _SpiceVirtMapping SpiceVirtMapping;
 typedef struct _SpiceCanvas SpiceCanvas;
 
@@ -79,6 +80,23 @@ struct _SpiceGlzDecoder {
   SpiceGlzDecoderOps *ops;
 };
 
+
+typedef struct SpiceJpegDecoderOps {
+    void (*begin_decode)(SpiceJpegDecoder *decoder,
+                         uint8_t* data,
+                         int data_size,
+                         int* out_width,
+                         int* out_height);
+    void (*decode)(SpiceJpegDecoder *decoder,
+                   uint8_t* dest,
+                   int stride,
+                   int format);
+} SpiceJpegDecoderOps;
+
+struct _SpiceJpegDecoder {
+    SpiceJpegDecoderOps *ops;
+};
+
 typedef struct {
     void *(*get_virt)(SpiceVirtMapping *mapping, unsigned long addr, uint32_t add_size);
     void (*validate_virt)(SpiceVirtMapping *mapping, unsigned long virt,
diff --git a/common/gdi_canvas.c b/common/gdi_canvas.c
index af1de95..d01d9cd 100644
--- a/common/gdi_canvas.c
+++ b/common/gdi_canvas.c
@@ -1866,6 +1866,7 @@ SpiceCanvas *gdi_canvas_create(int width, int height,
 #endif
                             , SpiceImageSurfaces *surfaces
                             , SpiceGlzDecoder *glz_decoder
+                            , SpiceJpegDecoder *jpeg_decoder
                             )
 {
     GdiCanvas *canvas;
@@ -1884,7 +1885,8 @@ SpiceCanvas *gdi_canvas_create(int width, int height,
                                , bits_cache
 #endif
                                , surfaces
-                               , glz_decoder);
+                               , glz_decoder
+                               , jpeg_decoder);
     canvas->dc = dc;
     canvas->lock = lock;
     return (SpiceCanvas *)canvas;
diff --git a/common/gdi_canvas.h b/common/gdi_canvas.h
index 7af0e02..02e053d 100644
--- a/common/gdi_canvas.h
+++ b/common/gdi_canvas.h
@@ -31,7 +31,8 @@ SpiceCanvas *gdi_canvas_create(int width, int height,
                                SpiceImageCache *bits_cache,
                                SpicePaletteCache *palette_cache,
 			       SpiceImageSurfaces *surfaces,
-                               SpiceGlzDecoder *glz_decoder);
+                               SpiceGlzDecoder *glz_decoder,
+                               SpiceJpegDecoder *jpeg_decoder);
 
 void gdi_canvas_init();
 
diff --git a/common/gl_canvas.c b/common/gl_canvas.c
index ea10c96..f98c72a 100644
--- a/common/gl_canvas.c
+++ b/common/gl_canvas.c
@@ -830,6 +830,7 @@ SpiceCanvas *gl_canvas_create(int width, int height, uint32_t format
 #endif
                               , SpiceImageSurfaces *surfaces
                               , SpiceGlzDecoder *glz_decoder
+                              , SpiceJpegDecoder *jpeg_decoder
 #ifndef SW_CANVAS_NO_CHUNKS
                               , SpiceVirtMapping *virt_mapping
 #endif
@@ -857,6 +858,7 @@ SpiceCanvas *gl_canvas_create(int width, int height, uint32_t format
 #endif
                                , surfaces
                                , glz_decoder
+                               , jpeg_decoder
 #ifndef SW_CANVAS_NO_CHUNKS
                                , virt_mapping
 #endif
diff --git a/common/gl_canvas.h b/common/gl_canvas.h
index 6dd25e9..cd76f8d 100644
--- a/common/gl_canvas.h
+++ b/common/gl_canvas.h
@@ -30,6 +30,7 @@ SpiceCanvas *gl_canvas_create(int width, int height, uint32_t format
 #endif
 			   , SpiceImageSurfaces *surfaces
                            , SpiceGlzDecoder *glz_decoder
+                           , SpiceJpegDecoder *jpeg_decoder
 #ifndef SW_CANVAS_NO_CHUNKS
                             , SpiceVirtMapping *virt_mapping
 #endif
diff --git a/common/sw_canvas.c b/common/sw_canvas.c
index a541c7d..8280362 100644
--- a/common/sw_canvas.c
+++ b/common/sw_canvas.c
@@ -1180,6 +1180,7 @@ static SpiceCanvas *canvas_create_common(pixman_image_t *image,
 #endif
                            , SpiceImageSurfaces *surfaces
                            , SpiceGlzDecoder *glz_decoder
+                           , SpiceJpegDecoder *jpeg_decoder
 #ifndef SW_CANVAS_NO_CHUNKS
                            , SpiceVirtMapping *virt_mapping
 #endif
@@ -1207,6 +1208,7 @@ static SpiceCanvas *canvas_create_common(pixman_image_t *image,
 #endif
                                , surfaces
                                , glz_decoder
+                               , jpeg_decoder
 #ifndef SW_CANVAS_NO_CHUNKS
                                , virt_mapping
 #endif
@@ -1228,6 +1230,7 @@ SpiceCanvas *canvas_create(int width, int height, uint32_t format
 #endif
                            , SpiceImageSurfaces *surfaces
                            , SpiceGlzDecoder *glz_decoder
+                           , SpiceJpegDecoder *jpeg_decoder
 #ifndef SW_CANVAS_NO_CHUNKS
                            , SpiceVirtMapping *virt_mapping
 #endif
@@ -1247,6 +1250,7 @@ SpiceCanvas *canvas_create(int width, int height, uint32_t format
 #endif
                                 , surfaces
                                 , glz_decoder
+                                , jpeg_decoder
 #ifndef SW_CANVAS_NO_CHUNKS
                                 , virt_mapping
 #endif
@@ -1263,6 +1267,7 @@ SpiceCanvas *canvas_create_for_data(int width, int height, uint32_t format,
 #endif
                            , SpiceImageSurfaces *surfaces
                            , SpiceGlzDecoder *glz_decoder
+                           , SpiceJpegDecoder *jpeg_decoder
 #ifndef SW_CANVAS_NO_CHUNKS
                            , SpiceVirtMapping *virt_mapping
 #endif
@@ -1282,6 +1287,7 @@ SpiceCanvas *canvas_create_for_data(int width, int height, uint32_t format,
 #endif
                                 , surfaces
                                 , glz_decoder
+                                , jpeg_decoder
 #ifndef SW_CANVAS_NO_CHUNKS
                                 , virt_mapping
 #endif
diff --git a/common/sw_canvas.h b/common/sw_canvas.h
index c3aef24..63a7863 100644
--- a/common/sw_canvas.h
+++ b/common/sw_canvas.h
@@ -35,6 +35,7 @@ SpiceCanvas *canvas_create(int width, int height, uint32_t format
 #endif
 			   , SpiceImageSurfaces *surfaces
                            , SpiceGlzDecoder *glz_decoder
+                           , SpiceJpegDecoder *jpeg_decoder
 #ifndef SW_CANVAS_NO_CHUNKS
                            , SpiceVirtMapping *virt_mapping
 #endif
@@ -49,6 +50,7 @@ SpiceCanvas *canvas_create_for_data(int width, int height, uint32_t format, uint
 #endif
 			   , SpiceImageSurfaces *surfaces
                            , SpiceGlzDecoder *glz_decoder
+                           , SpiceJpegDecoder *jpeg_decoder
 #ifndef SW_CANVAS_NO_CHUNKS
                            , SpiceVirtMapping *virt_mapping
 #endif
diff --git a/server/Makefile.am b/server/Makefile.am
index 77f533a..8d93618 100644
--- a/server/Makefile.am
+++ b/server/Makefile.am
@@ -66,6 +66,8 @@ libspice_server_la_SOURCES =			\
 	glz_encoder_dictionary.h		\
 	glz_encoder_dictionary_protected.h	\
 	glz_encoder.h				\
+	jpeg_encoder.c				\
+	jpeg_encoder.h				\
 	mjpeg_encoder.h				\
 	mjpeg_encoder.c				\
 	red_bitmap_utils.h			\
diff --git a/server/jpeg_encoder.c b/server/jpeg_encoder.c
new file mode 100644
index 0000000..95cf240
--- /dev/null
+++ b/server/jpeg_encoder.c
@@ -0,0 +1,242 @@
+/*
+   Copyright (C) 2009 Red Hat, Inc.
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "red_common.h"
+#include "jpeg_encoder.h"
+#include <jpeglib.h>
+
+typedef struct JpegEncoder {
+    JpegEncoderUsrContext *usr;
+
+    struct jpeg_destination_mgr dest_mgr;
+    struct jpeg_compress_struct cinfo;
+    struct jpeg_error_mgr jerr;
+
+    struct {
+        JpegEncoderImageType type;
+        int width;
+        int height;
+        int stride;
+        unsigned int out_size;
+        void (*convert_line_to_RGB24) (uint8_t *line, int width, uint8_t **out_line);
+    } cur_image;
+} JpegEncoder;
+
+/* jpeg destination manager callbacks */
+
+static void dest_mgr_init_destination(j_compress_ptr cinfo) 
+{
+    JpegEncoder *enc = (JpegEncoder *)cinfo->client_data;
+    if (enc->dest_mgr.free_in_buffer == 0) {
+        enc->dest_mgr.free_in_buffer = enc->usr->more_space(enc->usr, 
+                                                            &enc->dest_mgr.next_output_byte);
+        
+        if (enc->dest_mgr.free_in_buffer == 0) {
+            red_error("not enough space");
+        }
+    }
+
+    enc->cur_image.out_size = enc->dest_mgr.free_in_buffer;
+}
+
+static boolean dest_mgr_empty_output_buffer(j_compress_ptr cinfo)
+{
+    JpegEncoder *enc = (JpegEncoder *)cinfo->client_data;
+    enc->dest_mgr.free_in_buffer = enc->usr->more_space(enc->usr, 
+                                                        &enc->dest_mgr.next_output_byte);
+        
+    if (enc->dest_mgr.free_in_buffer == 0) {
+        red_error("not enough space");
+    }    
+    enc->cur_image.out_size += enc->dest_mgr.free_in_buffer;
+    return TRUE;
+}
+
+static void dest_mgr_term_destination(j_compress_ptr cinfo)
+{
+    JpegEncoder *enc = (JpegEncoder *)cinfo->client_data;
+    enc->cur_image.out_size -= enc->dest_mgr.free_in_buffer;
+}
+
+JpegEncoderContext* jpeg_encoder_create(JpegEncoderUsrContext *usr)
+{
+    JpegEncoder *enc;
+    if (!usr->more_space || !usr->more_lines) {
+        return NULL;
+    }
+
+    enc = spice_new0(JpegEncoder, 1);
+
+    enc->usr = usr;
+    
+    enc->dest_mgr.init_destination = dest_mgr_init_destination;
+    enc->dest_mgr.empty_output_buffer = dest_mgr_empty_output_buffer;
+    enc->dest_mgr.term_destination = dest_mgr_term_destination;
+
+    enc->cinfo.err = jpeg_std_error(&enc->jerr);
+   
+    jpeg_create_compress(&enc->cinfo);
+    enc->cinfo.client_data = enc;
+    enc->cinfo.dest = &enc->dest_mgr;
+    return (JpegEncoderContext*)enc;
+}
+
+void jpeg_encoder_destroy(JpegEncoderContext* encoder)
+{    
+    jpeg_destroy_compress(&((JpegEncoder*)encoder)->cinfo);
+    free(encoder);
+}
+
+static void convert_RGB16_to_RGB24(uint8_t *line, int width, uint8_t **out_line)
+{
+    uint16_t *src_line = (uint16_t *)line;
+    uint8_t *out_pix;
+    int x;
+
+    ASSERT(out_line && *out_line);
+
+    out_pix = *out_line;
+
+    for (x = 0; x < width; x++) {
+       uint16_t pixel = *src_line++;
+       *out_pix++ = ((pixel >> 7) & 0xf8) | ((pixel >> 12) & 0x7);
+       *out_pix++ = ((pixel >> 2) & 0xf8) | ((pixel >> 7) & 0x7);
+       *out_pix++ = ((pixel << 3) & 0xf8) | ((pixel >> 2) & 0x7);
+   }
+}
+
+static void convert_BGR24_to_RGB24(uint8_t *line, int width, uint8_t **out_line)
+{
+    int x;
+    uint8_t *out_pix;
+
+    ASSERT(out_line && *out_line);
+
+    out_pix = *out_line;
+
+    for (x = 0; x < width; x++) {
+        *out_pix++ = line[2];
+        *out_pix++ = line[1];
+        *out_pix++ = line[0];
+        line += 3;
+    }
+}
+
+static void convert_BGRX32_to_RGB24(uint8_t *line, int width, uint8_t **out_line)
+{
+    uint32_t *src_line = (uint32_t *)line;
+    uint8_t *out_pix;
+    int x;
+    
+	ASSERT(out_line && *out_line);
+
+    out_pix = *out_line;
+
+    for (x = 0; x < width; x++) {
+        uint32_t pixel = *src_line++;
+        *out_pix++ = (pixel >> 16) & 0xff;
+        *out_pix++ = (pixel >> 8) & 0xff;
+        *out_pix++ = pixel & 0xff;
+    }
+}
+
+static void convert_RGB24_to_RGB24(uint8_t *line, int width, uint8_t **out_line)
+{
+    *out_line = line;
+}
+
+
+#define FILL_LINES() {                                                  \
+    if (lines == lines_end) {                                           \
+        int n = jpeg->usr->more_lines(jpeg->usr, &lines);               \
+        if (n <= 0) {                                                   \
+            red_error("more lines failed\n");                           \
+        }                                                               \
+        lines_end = lines + n * stride;                                 \
+    }                                                                   \
+}
+
+static void do_jpeg_encode(JpegEncoder *jpeg, uint8_t *lines, unsigned int num_lines)
+{    
+    uint8_t *lines_end;
+    uint8_t *RGB24_line;
+    int stride, width, height;
+    JSAMPROW row_pointer[1];
+    width = jpeg->cur_image.width;
+    height = jpeg->cur_image.height;
+    stride = jpeg->cur_image.stride;
+
+    if (jpeg->cur_image.type != JPEG_IMAGE_TYPE_RGB24) {
+        RGB24_line = (uint8_t *)spice_malloc(width*3);
+    }
+
+    lines_end = lines + (stride * num_lines);
+
+    for (;jpeg->cinfo.next_scanline < jpeg->cinfo.image_height; lines += stride) {
+        FILL_LINES();
+        jpeg->cur_image.convert_line_to_RGB24(lines, width, &RGB24_line);
+        row_pointer[0] = RGB24_line;
+        jpeg_write_scanlines(&jpeg->cinfo, row_pointer, 1);
+    }
+}
+
+int jpeg_encode(JpegEncoderContext *jpeg, int quality, JpegEncoderImageType type,
+                int width, int height, uint8_t *lines, unsigned int num_lines, int stride,
+                uint8_t *io_ptr, unsigned int num_io_bytes)
+{
+    JpegEncoder *enc = (JpegEncoder *)jpeg; 
+
+    enc->cur_image.type = type;
+    enc->cur_image.width = width;
+    enc->cur_image.height = height;
+    enc->cur_image.stride = stride;
+    enc->cur_image.out_size = 0;
+
+    switch (type) {
+    case JPEG_IMAGE_TYPE_RGB16:
+        enc->cur_image.convert_line_to_RGB24 = convert_RGB16_to_RGB24;
+        break;
+    case JPEG_IMAGE_TYPE_RGB24:
+        enc->cur_image.convert_line_to_RGB24 = convert_RGB24_to_RGB24;
+        break;
+    case JPEG_IMAGE_TYPE_BGR24:
+        enc->cur_image.convert_line_to_RGB24 = convert_BGR24_to_RGB24;
+        break;
+    case JPEG_IMAGE_TYPE_BGRX32:
+        enc->cur_image.convert_line_to_RGB24 = convert_BGRX32_to_RGB24;
+        break;
+    default:
+        red_error("bad image type");
+    }
+
+    enc->cinfo.image_width = width;
+    enc->cinfo.image_height = height;
+    enc->cinfo.input_components = 3;
+    enc->cinfo.in_color_space = JCS_RGB;
+    jpeg_set_defaults(&enc->cinfo);
+    jpeg_set_quality(&enc->cinfo, quality, TRUE);
+
+    enc->dest_mgr.next_output_byte = io_ptr;
+    enc->dest_mgr.free_in_buffer = num_io_bytes;
+
+    jpeg_start_compress(&enc->cinfo, TRUE);
+
+    do_jpeg_encode(enc, lines, num_lines);
+
+   jpeg_finish_compress(&enc->cinfo);
+   return enc->cur_image.out_size;
+}
diff --git a/server/jpeg_encoder.h b/server/jpeg_encoder.h
new file mode 100644
index 0000000..2b8d8b2
--- /dev/null
+++ b/server/jpeg_encoder.h
@@ -0,0 +1,61 @@
+/*
+   Copyright (C) 2009 Red Hat, Inc.
+
+   Redistribution and use in source and binary forms, with or without
+   modification, are permitted provided that the following conditions are
+   met:
+
+       * Redistributions of source code must retain the above copyright
+         notice, this list of conditions and the following disclaimer.
+       * Redistributions in binary form must reproduce the above copyright
+         notice, this list of conditions and the following disclaimer in
+         the documentation and/or other materials provided with the
+         distribution.
+       * Neither the name of the copyright holder nor the names of its
+         contributors may be used to endorse or promote products derived
+         from this software without specific prior written permission.
+
+   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
+   IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+   TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+   PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+   HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+#ifndef _H_JPEG_ENCODER
+#define _H_JPEG_ENCODER
+
+#include <spice/types.h>
+
+typedef enum {
+    JPEG_IMAGE_TYPE_INVALID,
+    JPEG_IMAGE_TYPE_RGB16,
+    /* in byte per color types, the notation is according to the order of the 
+       colors in the memory */
+    JPEG_IMAGE_TYPE_RGB24,
+    JPEG_IMAGE_TYPE_BGR24,
+    JPEG_IMAGE_TYPE_BGRX32,
+} JpegEncoderImageType;
+
+typedef void* JpegEncoderContext;
+typedef struct JpegEncoderUsrContext JpegEncoderUsrContext;
+
+struct JpegEncoderUsrContext {
+    int (*more_space)(JpegEncoderUsrContext *usr, uint8_t **io_ptr);
+    int (*more_lines)(JpegEncoderUsrContext *usr, uint8_t **lines);
+};
+
+JpegEncoderContext* jpeg_encoder_create(JpegEncoderUsrContext *usr);
+void jpeg_encoder_destroy(JpegEncoderContext *encoder);
+
+/* returns the total size of the encoded data. Images must be supplied from the the 
+   top line to the bottom */
+int jpeg_encode(JpegEncoderContext *jpeg, int quality, JpegEncoderImageType type,
+                int width, int height, uint8_t *lines, unsigned int num_lines, int stride,
+                uint8_t *io_ptr, unsigned int num_io_bytes);
+#endif
diff --git a/server/red_worker.c b/server/red_worker.c
index c2c78ea..aac1d27 100644
--- a/server/red_worker.c
+++ b/server/red_worker.c
@@ -48,6 +48,7 @@
 #include "ring.h"
 #include "mjpeg_encoder.h"
 #include "red_memslots.h"
+#include "jpeg_encoder.h"
 
 //#define COMPRESS_STAT
 //#define DUMP_BITMAP
@@ -167,6 +168,7 @@ static inline void stat_add(stat_info_t *info, stat_time_t start)
 static const char *lz_stat_name = "lz";
 static const char *glz_stat_name = "glz";
 static const char *quic_stat_name = "quic";
+static const char *jpeg_stat_name = "jpeg";
 
 static inline void stat_compress_init(stat_info_t *info, const char *name)
 {
@@ -464,6 +466,7 @@ typedef struct  __attribute__ ((__packed__)) RedImage {
         SpiceLZRGBData lz_rgb;
         SpiceLZPLTData lz_plt;
         SpiceSurface surface;
+        SpiceJPEGData jpeg;
     };
 } RedImage;
 
@@ -577,6 +580,11 @@ typedef struct {
     EncoderData data;
 } GlzData;
 
+typedef struct {
+    JpegEncoderUsrContext usr;
+    EncoderData data;
+} JpegData;
+
 /**********************************/
 /* LZ dictionary related entities */
 /**********************************/
@@ -708,6 +716,7 @@ struct DisplayChannel {
     stat_info_t lz_stat;
     stat_info_t glz_stat;
     stat_info_t quic_stat;
+    stat_info_t jpeg_stat;
 #endif
 };
 
@@ -987,6 +996,9 @@ typedef struct RedWorker {
     LzData lz_data;
     LzContext  *lz;
 
+    JpegData jpeg_data;
+    JpegEncoderContext *jpeg;
+
 #ifdef PIPE_DEBUG
     uint32_t last_id;
 #endif
@@ -1003,6 +1015,9 @@ typedef struct RedWorker {
     uint64_t *command_counter;
 #endif
     SpiceVirtMapping preload_group_virt_mapping;
+
+    int enable_jpeg;
+    int jpeg_quality;
 } RedWorker;
 
 static void red_draw_qxl_drawable(RedWorker *worker, Drawable *drawable);
@@ -1068,19 +1083,29 @@ static void print_compress_stats(DisplayChannel *display_channel)
                stat_byte_to_mega(display_channel->lz_stat.comp_size),
                stat_cpu_time_to_sec(display_channel->lz_stat.total)
                );
+    red_printf("JPEG     \t%8d\t%13.2f\t%12.2f\t%12.2f",
+               display_channel->jpeg_stat.count,
+               stat_byte_to_mega(display_channel->jpeg_stat.orig_size),
+               stat_byte_to_mega(display_channel->jpeg_stat.comp_size),
+               stat_cpu_time_to_sec(display_channel->jpeg_stat.total)
+               );
     red_printf("-------------------------------------------------------------------");
     red_printf("Total    \t%8d\t%13.2f\t%12.2f\t%12.2f",
                display_channel->lz_stat.count + display_channel->glz_stat.count +
-                                                display_channel->quic_stat.count,
+                                                display_channel->quic_stat.count +
+                                                display_channel->jpeg_stat.count,
                stat_byte_to_mega(display_channel->lz_stat.orig_size +
                                  display_channel->glz_stat.orig_size +
-                                 display_channel->quic_stat.orig_size),
+                                 display_channel->quic_stat.orig_size +
+                                 display_channel->jpeg_stat.orig_size),
                stat_byte_to_mega(display_channel->lz_stat.comp_size +
                                  display_channel->glz_stat.comp_size +
-                                 display_channel->quic_stat.comp_size),
+                                 display_channel->quic_stat.comp_size +
+                                 display_channel->jpeg_stat.comp_size),
                stat_cpu_time_to_sec(display_channel->lz_stat.total +
                                     display_channel->glz_stat.total +
-                                    display_channel->quic_stat.total)
+                                    display_channel->quic_stat.total +
+                                    display_channel->jpeg_stat.total)
                );
 }
 
@@ -5517,6 +5542,12 @@ static int glz_usr_more_space(GlzEncoderUsrContext *usr, uint8_t **io_ptr)
     return (encoder_usr_more_space(usr_data, (uint32_t **)io_ptr) << 2);
 }
 
+static int jpeg_usr_more_space(JpegEncoderUsrContext *usr, uint8_t **io_ptr)
+{
+    EncoderData *usr_data = &(((JpegData *)usr)->data);
+    return (encoder_usr_more_space(usr_data, (uint32_t **)io_ptr) << 2);
+}
+
 static inline int encoder_usr_more_lines(EncoderData *enc_data, uint8_t **lines)
 {
     uint32_t data_size;
@@ -5564,63 +5595,91 @@ static int glz_usr_more_lines(GlzEncoderUsrContext *usr, uint8_t **lines)
     return encoder_usr_more_lines(usr_data, lines);
 }
 
-static int quic_usr_more_lines_revers(QuicUsrContext *usr, uint8_t **lines)
+static int jpeg_usr_more_lines(JpegEncoderUsrContext *usr, uint8_t **lines)
+{
+    EncoderData *usr_data = &(((JpegData *)usr)->data);
+    return encoder_usr_more_lines(usr_data, lines);
+}
+
+static int encoder_usr_more_lines_reverse(EncoderData *enc_data, uint8_t **lines)
 {
     uint8_t *data;
     uint32_t data_size;
-    EncoderData *quic_data = &(((QuicData *)usr)->data);
 
-    if (!quic_data->u.lines_data.next) {
+    if (!enc_data->u.lines_data.next) {
         return 0;
     }
 
-    QXLDataChunk *chunk = (QXLDataChunk *)quic_data->u.lines_data.enc_get_virt(
-                          quic_data->u.lines_data.enc_get_virt_opaque,
-                          quic_data->u.lines_data.next,
-                          sizeof(QXLDataChunk), quic_data->u.lines_data.group_id);
+    QXLDataChunk *chunk = (QXLDataChunk *)enc_data->u.lines_data.enc_get_virt(
+                          enc_data->u.lines_data.enc_get_virt_opaque,
+                          enc_data->u.lines_data.next,
+                          sizeof(QXLDataChunk), enc_data->u.lines_data.group_id);
 
     data_size = chunk->data_size;
     data = chunk->data;
 
-    if (data_size % quic_data->u.lines_data.stride) {
+    if (data_size % enc_data->u.lines_data.stride) {
         return 0;
     }
 
-    quic_data->u.lines_data.enc_validate_virt(quic_data->u.lines_data.enc_validate_virt_opaque,
-                                              (unsigned long)data,
-                                              quic_data->u.lines_data.next, data_size,
-                                              quic_data->u.lines_data.group_id);
+    enc_data->u.lines_data.enc_validate_virt(enc_data->u.lines_data.enc_validate_virt_opaque,
+                                             (unsigned long)data,
+                                             enc_data->u.lines_data.next, data_size,
+                                             enc_data->u.lines_data.group_id);
 
-    quic_data->u.lines_data.next = chunk->prev_chunk;
-    *lines = data + data_size - quic_data->u.lines_data.stride;
-    return data_size / quic_data->u.lines_data.stride;
+    enc_data->u.lines_data.next = chunk->prev_chunk;
+    *lines = data + data_size - enc_data->u.lines_data.stride;
+    return data_size / enc_data->u.lines_data.stride;
 }
 
-static int quic_usr_more_lines_unstable(QuicUsrContext *usr, uint8_t **out_lines)
+static int quic_usr_more_lines_reverse(QuicUsrContext *usr, uint8_t **lines)
 {
-    EncoderData *quic_data = &(((QuicData *)usr)->data);
+    EncoderData *usr_data = &(((QuicData *)usr)->data);
+    return encoder_usr_more_lines_reverse(usr_data, lines);
+
+}
+
+static int jpeg_usr_more_lines_reverse(JpegEncoderUsrContext *usr, uint8_t **lines)
+{
+    EncoderData *usr_data = &(((JpegData *)usr)->data);
+    return encoder_usr_more_lines_reverse(usr_data, lines);
+}
 
-    if (!quic_data->u.unstable_lines_data.lines) {
+static int encoder_usr_more_lines_unstable(EncoderData *enc_data, uint8_t **out_lines)
+{
+    if (!enc_data->u.unstable_lines_data.lines) {
         return 0;
     }
-    uint8_t *src = quic_data->u.unstable_lines_data.next;
-    int lines = MIN(quic_data->u.unstable_lines_data.lines,
-                    quic_data->u.unstable_lines_data.max_lines_bunch);
-    quic_data->u.unstable_lines_data.lines -= lines;
-    uint8_t *end = src + lines * quic_data->u.unstable_lines_data.src_stride;
-    quic_data->u.unstable_lines_data.next = end;
+    uint8_t *src = enc_data->u.unstable_lines_data.next;
+    int lines = MIN(enc_data->u.unstable_lines_data.lines,
+                    enc_data->u.unstable_lines_data.max_lines_bunch);
+    enc_data->u.unstable_lines_data.lines -= lines;
+    uint8_t *end = src + lines * enc_data->u.unstable_lines_data.src_stride;
+    enc_data->u.unstable_lines_data.next = end;
 
-    uint8_t *out = (uint8_t *)quic_data->u.unstable_lines_data.input_bufs[
-            quic_data->u.unstable_lines_data.input_bufs_pos++ & 1]->buf;
+    uint8_t *out = (uint8_t *)enc_data->u.unstable_lines_data.input_bufs[
+                enc_data->u.unstable_lines_data.input_bufs_pos++ & 1]->buf;
     uint8_t *dest = out;
-    for (; src != end; src += quic_data->u.unstable_lines_data.src_stride,
-                       dest += quic_data->u.unstable_lines_data.dest_stride) {
-        memcpy(dest, src, quic_data->u.unstable_lines_data.dest_stride);
+    for (; src != end; src += enc_data->u.unstable_lines_data.src_stride,
+                       dest += enc_data->u.unstable_lines_data.dest_stride) {
+        memcpy(dest, src, enc_data->u.unstable_lines_data.dest_stride);
     }
     *out_lines = out;
     return lines;
 }
 
+static int quic_usr_more_lines_unstable(QuicUsrContext *usr, uint8_t **lines)
+{
+    EncoderData *usr_data = &(((QuicData *)usr)->data);
+    return encoder_usr_more_lines_unstable(usr_data, lines);
+}
+
+static int jpeg_usr_more_lines_unstable(JpegEncoderUsrContext *usr, uint8_t **lines)
+{
+    EncoderData *usr_data = &(((JpegData *)usr)->data);
+    return encoder_usr_more_lines_unstable(usr_data, lines);
+}
+
 static int quic_usr_no_more_lines(QuicUsrContext *usr, uint8_t **lines)
 {
     return 0;
@@ -5636,6 +5695,11 @@ static int glz_usr_no_more_lines(GlzEncoderUsrContext *usr, uint8_t **lines)
     return 0;
 }
 
+static int jpeg_usr_no_more_lines(JpegEncoderUsrContext *usr, uint8_t **lines)
+{
+    return 0;
+}
+
 static void glz_usr_free_image(GlzEncoderUsrContext *usr, GlzUsrImageContext *image)
 {
     GlzData *lz_data = (GlzData *)usr;
@@ -5698,6 +5762,22 @@ static inline void red_display_init_glz_data(DisplayChannel *display)
     display->glz_data.usr.free_image = glz_usr_free_image;
 }
 
+static inline void red_init_jpeg(RedWorker *worker)
+{
+    worker->jpeg_data.usr.more_space = jpeg_usr_more_space;
+    worker->jpeg_data.usr.more_lines = jpeg_usr_more_lines;
+
+    worker->jpeg = jpeg_encoder_create(&worker->jpeg_data.usr);
+
+    if (!worker->jpeg) {
+        PANIC("create jpeg encoder failed");
+    }
+
+    // TODO: configure via qemu command line and monitor, and activate only on WAN
+    worker->enable_jpeg = TRUE;
+    worker->jpeg_quality = 85;
+}
+
 #ifdef __GNUC__
 #define ATTR_PACKED __attribute__ ((__packed__))
 #else
@@ -6046,6 +6126,148 @@ static inline int red_lz_compress_image(DisplayChannel *display_channel,
     return TRUE;
 }
 
+static int red_jpeg_compress_image(DisplayChannel *display_channel, RedImage *dest,
+                                   SpiceBitmap *src, compress_send_data_t* o_comp_data,
+                                   uint32_t group_id)
+{
+    RedWorker *worker = display_channel->base.worker;
+    JpegData *jpeg_data = &worker->jpeg_data;
+    JpegEncoderContext *jpeg = worker->jpeg;
+    JpegEncoderImageType jpeg_in_type;
+    int size;
+#ifdef COMPRESS_STAT
+    stat_time_t start_time = stat_now();
+#endif
+    switch (src->format) {
+    case SPICE_BITMAP_FMT_32BIT:
+        jpeg_in_type = JPEG_IMAGE_TYPE_BGRX32;
+        break;
+    case SPICE_BITMAP_FMT_16BIT:
+        jpeg_in_type = JPEG_IMAGE_TYPE_RGB16;
+        break;
+    case SPICE_BITMAP_FMT_24BIT:
+        jpeg_in_type = JPEG_IMAGE_TYPE_BGR24;
+        break;
+    default:
+        return FALSE;
+    }
+
+    jpeg_data->data.bufs_tail = red_display_alloc_compress_buf(display_channel);
+    jpeg_data->data.bufs_head = jpeg_data->data.bufs_tail;
+
+    if (!jpeg_data->data.bufs_head) {
+        red_printf("failed to allocate compress buffer");
+        return FALSE;
+    }
+
+    jpeg_data->data.bufs_head->send_next = NULL;
+    jpeg_data->data.display_channel = display_channel;
+
+    if (setjmp(jpeg_data->data.jmp_env)) {
+        while (jpeg_data->data.bufs_head) {
+            RedCompressBuf *buf = jpeg_data->data.bufs_head;
+            jpeg_data->data.bufs_head = buf->send_next;
+            red_display_free_compress_buf(display_channel, buf);
+        }
+        return FALSE;
+    }
+
+    if ((src->flags & QXL_BITMAP_DIRECT)) {
+        int stride;
+        uint8_t *data;
+
+        if (!(src->flags & QXL_BITMAP_TOP_DOWN)) {
+            data = (uint8_t*)get_virt(&worker->mem_slots, src->data, src->stride * src->y, group_id) +
+                   src->stride * (src->y - 1);
+            stride = -src->stride;
+        } else {
+            data = (uint8_t*)get_virt(&worker->mem_slots, src->data, src->stride * src->y, group_id);
+            stride = src->stride;
+        }
+
+        if ((src->flags & QXL_BITMAP_UNSTABLE)) {
+            jpeg_data->data.u.unstable_lines_data.next = data;
+            jpeg_data->data.u.unstable_lines_data.src_stride = stride;
+            jpeg_data->data.u.unstable_lines_data.dest_stride = src->stride;
+            jpeg_data->data.u.unstable_lines_data.lines = src->y;
+            jpeg_data->data.u.unstable_lines_data.input_bufs_pos = 0;
+            if (!(jpeg_data->data.u.unstable_lines_data.input_bufs[0] =
+                                            red_display_alloc_compress_buf(display_channel)) ||
+                !(jpeg_data->data.u.unstable_lines_data.input_bufs[1] =
+                                            red_display_alloc_compress_buf(display_channel))) {
+                return FALSE;
+            }
+            jpeg_data->data.u.unstable_lines_data.max_lines_bunch =
+                                 sizeof(jpeg_data->data.u.unstable_lines_data.input_bufs[0]->buf) /
+                                 jpeg_data->data.u.unstable_lines_data.dest_stride;
+            jpeg_data->usr.more_lines = jpeg_usr_more_lines_unstable;
+            size = jpeg_encode(jpeg, worker->jpeg_quality, jpeg_in_type, src->x, src->y, NULL, 0, src->stride,
+                               (uint8_t*)jpeg_data->data.bufs_head->buf,
+                               sizeof(jpeg_data->data.bufs_head->buf));
+        } else {
+            jpeg_data->usr.more_lines = jpeg_usr_no_more_lines;
+            size = jpeg_encode(jpeg, worker->jpeg_quality, jpeg_in_type, src->x, src->y, data, src->y, stride,
+                               (uint8_t*)jpeg_data->data.bufs_head->buf,
+                               sizeof(jpeg_data->data.bufs_head->buf));
+        }
+    } else {
+        int stride;
+
+        if ((src->flags & QXL_BITMAP_UNSTABLE)) {
+            red_printf_once("unexpected unstable bitmap");
+            return FALSE;
+        }
+        jpeg_data->data.u.lines_data.enc_get_virt = cb_get_virt;
+        jpeg_data->data.u.lines_data.enc_get_virt_opaque = &worker->mem_slots;
+        jpeg_data->data.u.lines_data.enc_validate_virt = cb_validate_virt;
+        jpeg_data->data.u.lines_data.enc_validate_virt_opaque = &worker->mem_slots;
+        jpeg_data->data.u.lines_data.stride = src->stride;
+        jpeg_data->data.u.lines_data.group_id = group_id;
+
+        if ((src->flags & QXL_BITMAP_TOP_DOWN)) {
+            jpeg_data->data.u.lines_data.next = src->data;
+            jpeg_data->usr.more_lines = jpeg_usr_more_lines;
+            stride = src->stride;
+        } else {
+            SPICE_ADDRESS prev_addr = src->data;
+            QXLDataChunk *chunk = (QXLDataChunk *)get_virt(&worker->mem_slots, src->data,
+                                                           sizeof(QXLDataChunk), group_id);
+            while (chunk->next_chunk) {
+                prev_addr = chunk->next_chunk;
+                chunk = (QXLDataChunk *)get_virt(&worker->mem_slots, chunk->next_chunk, sizeof(QXLDataChunk),
+                                                 group_id);
+                ASSERT(chunk->prev_chunk);
+            }
+            jpeg_data->data.u.lines_data.next = (SPICE_ADDRESS)prev_addr -
+                                                get_virt_delta(&worker->mem_slots,
+                                                               get_memslot_id(&worker->mem_slots, src->data),
+                                                               group_id);
+            jpeg_data->usr.more_lines = jpeg_usr_more_lines_reverse;
+            stride = -src->stride;
+        }
+        size = jpeg_encode(jpeg, worker->jpeg_quality, jpeg_in_type, src->x, src->y, NULL, 0, stride,
+                           (uint8_t*)jpeg_data->data.bufs_head->buf,
+                           sizeof(jpeg_data->data.bufs_head->buf));
+    }
+
+    // the compressed buffer is bigger than the original data
+    if (size > (src->y * src->stride)) {
+        longjmp(jpeg_data->data.jmp_env, 1);
+    }
+
+    dest->descriptor.type = SPICE_IMAGE_TYPE_JPEG;
+    dest->jpeg.data_size = size;
+
+    o_comp_data->raw_size = sizeof(SpiceJPEGImage);
+    o_comp_data->comp_buf = jpeg_data->data.bufs_head;
+    o_comp_data->comp_buf_size = size;
+    o_comp_data->plt_ptr = NULL;
+    o_comp_data->flags_ptr = NULL;
+    stat_compress_add(&display_channel->jpeg_stat, start_time, src->stride * src->y,
+                      o_comp_data->comp_buf_size);
+    return TRUE;
+}
+
 static inline int red_quic_compress_image(DisplayChannel *display_channel, RedImage *dest,
                                           SpiceBitmap *src, compress_send_data_t* o_comp_data,
                                           uint32_t group_id)
@@ -6166,7 +6388,7 @@ static inline int red_quic_compress_image(DisplayChannel *display_channel, RedIm
                                                 get_virt_delta(&worker->mem_slots,
                                                                get_memslot_id(&worker->mem_slots, src->data),
                                                                group_id);
-            quic_data->usr.more_lines = quic_usr_more_lines_revers;
+            quic_data->usr.more_lines = quic_usr_more_lines_reverse;
             stride = -src->stride;
         }
         size = quic_encode(quic, type, src->x, src->y, NULL, 0, stride,
@@ -6249,7 +6471,17 @@ static inline int red_compress_image(DisplayChannel *display_channel,
 #ifdef COMPRESS_DEBUG
         red_printf("QUIC compress");
 #endif
-        return red_quic_compress_image(display_channel, dest, src, o_comp_data, drawable->group_id);
+        // if bitmaps is picture-like, compress it using jpeg
+        if (display_channel->base.worker->enable_jpeg &&
+            ((image_compression == SPICE_IMAGE_COMPRESS_AUTO_LZ) ||
+            (image_compression == SPICE_IMAGE_COMPRESS_AUTO_GLZ))) {
+            if (src->format != SPICE_BITMAP_FMT_RGBA) {
+                return red_jpeg_compress_image(display_channel, dest,
+                                               src, o_comp_data, drawable->group_id);
+            }
+        }
+        return red_quic_compress_image(display_channel, dest,
+                                       src, o_comp_data, drawable->group_id);
     } else {
         int glz;
         int ret;
@@ -7915,7 +8147,7 @@ static SpiceCanvas *create_ogl_context_common(RedWorker *worker, OGLCtx *ctx, ui
 
     oglctx_make_current(ctx);
     if (!(canvas = gl_canvas_create(width, height, depth, &worker->image_cache.base,
-                                    &worker->image_surfaces, NULL,
+                                    &worker->image_surfaces, NULL, NULL,
                                     &worker->preload_group_virt_mapping))) {
         return NULL;
     }
@@ -7973,7 +8205,7 @@ static inline void *create_canvas_for_surface(RedWorker *worker, RedSurface *sur
         canvas = canvas_create_for_data(width, height, format,
                                         line_0, stride,
                                         &worker->image_cache.base,
-                                        &worker->image_surfaces, NULL,
+                                        &worker->image_surfaces, NULL, NULL,
                                         &worker->preload_group_virt_mapping);
         surface->context.top_down = TRUE;
         surface->context.canvas_draws_on_surface = TRUE;
@@ -8773,6 +9005,7 @@ static void handle_new_display_channel(RedWorker *worker, RedsStreamContext *pee
     stat_compress_init(&display_channel->lz_stat, lz_stat_name);
     stat_compress_init(&display_channel->glz_stat, glz_stat_name);
     stat_compress_init(&display_channel->quic_stat, quic_stat_name);
+    stat_compress_init(&display_channel->jpeg_stat, jpeg_stat_name);
 }
 
 static void red_disconnect_cursor(RedChannel *channel)
@@ -9321,6 +9554,7 @@ static void handle_dev_input(EventListener *listener, uint32_t events)
             stat_reset(&worker->display_channel->quic_stat);
             stat_reset(&worker->display_channel->lz_stat);
             stat_reset(&worker->display_channel->glz_stat);
+            stat_reset(&worker->display_channel->jpeg_stat);
         }
 #endif
         break;
@@ -9483,6 +9717,7 @@ void *red_worker_main(void *arg)
     red_init(&worker, (WorkerInitData *)arg);
     red_init_quic(&worker);
     red_init_lz(&worker);
+    red_init_jpeg(&worker);
     worker.epoll_timeout = INF_EPOLL_WAIT;
     for (;;) {
         struct epoll_event events[MAX_EPOLL_SOURCES];
commit ea74fc64543ef486085a82aec0c67a3b265ba3ac
Author: Yonit Halperin <yhalperi at redhat.com>
Date:   Wed Jun 9 11:25:44 2010 +0200

    server: fix bitmap flags assignment in red_send_image

diff --git a/server/red_worker.c b/server/red_worker.c
index 33ff373..c2c78ea 100644
--- a/server/red_worker.c
+++ b/server/red_worker.c
@@ -7332,12 +7332,12 @@ static void red_send_image(DisplayChannel *display_channel, ImageItem *item)
 
     QXL_SET_IMAGE_ID(red_image, QXL_IMAGE_GROUP_RED, ++worker->bits_unique);
     red_image->descriptor.type = SPICE_IMAGE_TYPE_BITMAP;
-    red_image->descriptor.flags = 0;
+    red_image->descriptor.flags = item->image_flags;
     red_image->descriptor.width = item->width;
     red_image->descriptor.height = item->height;
 
     bitmap.format = item->image_format;
-    bitmap.flags = QXL_BITMAP_DIRECT | item->image_flags;
+    bitmap.flags = QXL_BITMAP_DIRECT;
     bitmap.flags |= item->top_down ? QXL_BITMAP_TOP_DOWN : 0;
     bitmap.x = item->width;
     bitmap.y = item->height;


More information about the Spice-commits mailing list