[Spice-devel] [PATCH 1/9] JPEG support: introducing jpeg encoding for spice bitmaps.

Yonit Halperin yhalperi at redhat.com
Tue Jun 1 00:30:50 PDT 2010


---
 client/Makefile.am         |    2 +
 client/canvas.cpp          |    1 +
 client/canvas.h            |    4 +
 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/windows/redc.vcproj |    8 +
 client/x11/Makefile.am     |    2 +
 common/canvas_base.c       |   82 ++++++++++++-
 common/canvas_base.h       |   18 +++
 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_worker.c        |  309 ++++++++++++++++++++++++++++++++++++++------
 23 files changed, 956 insertions(+), 45 deletions(-)
 create mode 100644 client/jpeg_decoder.cpp
 create mode 100644 client/jpeg_decoder.h
 create mode 100644 server/jpeg_encoder.c
 create mode 100644 server/jpeg_encoder.h

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 33ff373..89c3deb 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];
-- 
1.6.6.1



More information about the Spice-devel mailing list