summary refs log tree commit diff stats
path: root/ui/vnc-enc-tight.c
diff options
context:
space:
mode:
Diffstat (limited to 'ui/vnc-enc-tight.c')
-rw-r--r--ui/vnc-enc-tight.c316
1 files changed, 274 insertions, 42 deletions
diff --git a/ui/vnc-enc-tight.c b/ui/vnc-enc-tight.c
index 4ff88a8b03..cc57c26796 100644
--- a/ui/vnc-enc-tight.c
+++ b/ui/vnc-enc-tight.c
@@ -26,13 +26,18 @@
  * THE SOFTWARE.
  */
 
-#include "qemu-common.h"
+#include "config-host.h"
 
+#ifdef CONFIG_VNC_PNG
+#include <png.h>
+#endif
 #ifdef CONFIG_VNC_JPEG
 #include <stdio.h>
 #include <jpeglib.h>
 #endif
 
+#include "qemu-common.h"
+
 #include "bswap.h"
 #include "qdict.h"
 #include "qint.h"
@@ -63,6 +68,29 @@ static const struct {
     { 65536, 2048,  32,  8192, 9, 9, 9, 6, 200, 500,  96, 80,   200,   500 }
 };
 
+
+static int tight_send_framebuffer_update(VncState *vs, int x, int y,
+                                         int w, int h);
+
+#ifdef CONFIG_VNC_PNG
+static int send_png_rect(VncState *vs, int x, int y, int w, int h,
+                         QDict *palette);
+
+static bool tight_can_send_png_rect(VncState *vs, int w, int h)
+{
+    if (vs->tight_type != VNC_ENCODING_TIGHT_PNG) {
+        return false;
+    }
+
+    if (ds_get_bytes_per_pixel(vs->ds) == 1 ||
+        vs->clientds.pf.bytes_per_pixel == 1) {
+        return false;
+    }
+
+    return true;
+}
+#endif
+
 /*
  * Code to guess if given rectangle is suitable for smooth image
  * compression (by applying "gradient" filter or JPEG coder).
@@ -466,6 +494,7 @@ static void print_palette(const char *key, QObject *obj, void *opaque)
         src = (uint##bpp##_t *) buf;                                    \
                                                                         \
         for (i = 0; i < count; i++) {                                   \
+                                                                        \
             rgb = *src++;                                               \
             rep = 0;                                                    \
             while (i < count && *src == rgb) {                          \
@@ -937,11 +966,17 @@ static void tight_pack24(VncState *vs, uint8_t *buf, size_t count, size_t *ret)
     }
 }
 
-static int send_full_color_rect(VncState *vs, int w, int h)
+static int send_full_color_rect(VncState *vs, int x, int y, int w, int h)
 {
     int stream = 0;
     size_t bytes;
 
+#ifdef CONFIG_VNC_PNG
+    if (tight_can_send_png_rect(vs, w, h)) {
+        return send_png_rect(vs, x, y, w, h, NULL);
+    }
+#endif
+
     vnc_write_u8(vs, stream << 4); /* no flushing, no filter */
 
     if (vs->tight_pixel24) {
@@ -975,12 +1010,27 @@ static int send_solid_rect(VncState *vs)
     return 1;
 }
 
-static int send_mono_rect(VncState *vs, int w, int h, uint32_t bg, uint32_t fg)
+static int send_mono_rect(VncState *vs, int x, int y,
+                          int w, int h, uint32_t bg, uint32_t fg)
 {
     size_t bytes;
     int stream = 1;
     int level = tight_conf[vs->tight_compression].mono_zlib_level;
 
+#ifdef CONFIG_VNC_PNG
+    if (tight_can_send_png_rect(vs, w, h)) {
+        int ret;
+        QDict *palette = qdict_new();
+        int bpp = vs->clientds.pf.bytes_per_pixel * 8;
+
+        tight_palette_insert(palette, bg, bpp, 2);
+        tight_palette_insert(palette, fg, bpp, 2);
+        ret = send_png_rect(vs, x, y, w, h, palette);
+        QDECREF(palette);
+        return ret;
+    }
+#endif
+
     bytes = ((w + 7) / 8) * h;
 
     vnc_write_u8(vs, (stream | VNC_TIGHT_EXPLICIT_FILTER) << 4);
@@ -1021,6 +1071,9 @@ static int send_mono_rect(VncState *vs, int w, int h, uint32_t bg, uint32_t fg)
 struct palette_cb_priv {
     VncState *vs;
     uint8_t *header;
+#ifdef CONFIG_VNC_PNG
+    png_colorp png_palette;
+#endif
 };
 
 static void write_palette(const char *key, QObject *obj, void *opaque)
@@ -1041,14 +1094,14 @@ static void write_palette(const char *key, QObject *obj, void *opaque)
     }
 }
 
-static bool send_gradient_rect(VncState *vs, int w, int h)
+static bool send_gradient_rect(VncState *vs, int x, int y, int w, int h)
 {
     int stream = 3;
     int level = tight_conf[vs->tight_compression].gradient_zlib_level;
     size_t bytes;
 
     if (vs->clientds.pf.bytes_per_pixel == 1)
-        return send_full_color_rect(vs, w, h);
+        return send_full_color_rect(vs, x, y, w, h);
 
     vnc_write_u8(vs, (stream | VNC_TIGHT_EXPLICIT_FILTER) << 4);
     vnc_write_u8(vs, VNC_TIGHT_FILTER_GRADIENT);
@@ -1076,13 +1129,20 @@ static bool send_gradient_rect(VncState *vs, int w, int h)
     return (bytes >= 0);
 }
 
-static int send_palette_rect(VncState *vs, int w, int h, struct QDict *palette)
+static int send_palette_rect(VncState *vs, int x, int y,
+                             int w, int h, struct QDict *palette)
 {
     int stream = 2;
     int level = tight_conf[vs->tight_compression].idx_zlib_level;
     int colors;
     size_t bytes;
 
+#ifdef CONFIG_VNC_PNG
+    if (tight_can_send_png_rect(vs, w, h)) {
+        return send_png_rect(vs, x, y, w, h, palette);
+    }
+#endif
+
     colors = qdict_size(palette);
 
     vnc_write_u8(vs, (stream | VNC_TIGHT_EXPLICIT_FILTER) << 4);
@@ -1130,12 +1190,9 @@ static int send_palette_rect(VncState *vs, int w, int h, struct QDict *palette)
     return (bytes >= 0);
 }
 
-/*
- * JPEG compression stuff.
- */
-#ifdef CONFIG_VNC_JPEG
-static void jpeg_prepare_row24(VncState *vs, uint8_t *dst, int x, int y,
-                                     int count)
+#if defined(CONFIG_VNC_JPEG) || defined(CONFIG_VNC_PNG)
+static void rgb_prepare_row24(VncState *vs, uint8_t *dst, int x, int y,
+                              int count)
 {
     VncDisplay *vd = vs->vd;
     uint32_t *fbptr;
@@ -1152,11 +1209,11 @@ static void jpeg_prepare_row24(VncState *vs, uint8_t *dst, int x, int y,
     }
 }
 
-#define DEFINE_JPEG_GET_ROW_FUNCTION(bpp)                               \
+#define DEFINE_RGB_GET_ROW_FUNCTION(bpp)                                \
                                                                         \
     static void                                                         \
-    jpeg_prepare_row##bpp(VncState *vs, uint8_t *dst,                   \
-                                int x, int y, int count)                \
+    rgb_prepare_row##bpp(VncState *vs, uint8_t *dst,                    \
+                         int x, int y, int count)                       \
     {                                                                   \
         VncDisplay *vd = vs->vd;                                        \
         uint##bpp##_t *fbptr;                                           \
@@ -1186,21 +1243,26 @@ static void jpeg_prepare_row24(VncState *vs, uint8_t *dst, int x, int y,
         }                                                               \
     }
 
-DEFINE_JPEG_GET_ROW_FUNCTION(16)
-DEFINE_JPEG_GET_ROW_FUNCTION(32)
+DEFINE_RGB_GET_ROW_FUNCTION(16)
+DEFINE_RGB_GET_ROW_FUNCTION(32)
 
-static void jpeg_prepare_row(VncState *vs, uint8_t *dst, int x, int y,
-                                       int count)
+static void rgb_prepare_row(VncState *vs, uint8_t *dst, int x, int y,
+                            int count)
 {
     if (vs->tight_pixel24)
-        jpeg_prepare_row24(vs, dst, x, y, count);
+        rgb_prepare_row24(vs, dst, x, y, count);
     else if (ds_get_bytes_per_pixel(vs->ds) == 4)
-        jpeg_prepare_row32(vs, dst, x, y, count);
+        rgb_prepare_row32(vs, dst, x, y, count);
     else
-        jpeg_prepare_row16(vs, dst, x, y, count);
+        rgb_prepare_row16(vs, dst, x, y, count);
 }
+#endif /* CONFIG_VNC_JPEG or CONFIG_VNC_PNG */
 
 /*
+ * JPEG compression stuff.
+ */
+#ifdef CONFIG_VNC_JPEG
+/*
  * Destination manager implementation for JPEG library.
  */
 
@@ -1245,7 +1307,7 @@ static int send_jpeg_rect(VncState *vs, int x, int y, int w, int h, int quality)
     int dy;
 
     if (ds_get_bytes_per_pixel(vs->ds) == 1)
-        return send_full_color_rect(vs, w, h);
+        return send_full_color_rect(vs, x, y, w, h);
 
     buffer_reserve(&vs->tight_jpeg, 2048);
 
@@ -1271,7 +1333,7 @@ static int send_jpeg_rect(VncState *vs, int x, int y, int w, int h, int quality)
     buf = qemu_malloc(w * 3);
     row[0] = buf;
     for (dy = 0; dy < h; dy++) {
-        jpeg_prepare_row(vs, buf, x, y + dy, w);
+        rgb_prepare_row(vs, buf, x, y + dy, w);
         jpeg_write_scanlines(&cinfo, row, 1);
     }
     qemu_free(buf);
@@ -1289,6 +1351,162 @@ static int send_jpeg_rect(VncState *vs, int x, int y, int w, int h, int quality)
 }
 #endif /* CONFIG_VNC_JPEG */
 
+/*
+ * PNG compression stuff.
+ */
+#ifdef CONFIG_VNC_PNG
+static void write_png_palette(const char *key, QObject *obj, void *opaque)
+{
+    struct palette_cb_priv *priv = opaque;
+    VncState *vs = priv->vs;
+    uint32_t bytes = vs->clientds.pf.bytes_per_pixel;
+    uint8_t idx = qint_get_int(qobject_to_qint(obj));
+    png_colorp color = &priv->png_palette[idx];
+    uint32_t pix;
+
+    if (bytes == 4) {
+        pix = tight_palette_buf2rgb(32, (uint8_t *)key);
+    } else {
+        pix = tight_palette_buf2rgb(16, (uint8_t *)key);
+    }
+
+    if (vs->tight_pixel24)
+    {
+        color->red = (pix >> vs->clientds.pf.rshift) & vs->clientds.pf.rmax;
+        color->green = (pix >> vs->clientds.pf.gshift) & vs->clientds.pf.gmax;
+        color->blue = (pix >> vs->clientds.pf.bshift) & vs->clientds.pf.bmax;
+    }
+    else
+    {
+        int red, green, blue;
+
+        red = (pix >> vs->clientds.pf.rshift) & vs->clientds.pf.rmax;
+        green = (pix >> vs->clientds.pf.gshift) & vs->clientds.pf.gmax;
+        blue = (pix >> vs->clientds.pf.bshift) & vs->clientds.pf.bmax;
+        color->red = ((red * 255 + vs->clientds.pf.rmax / 2) /
+                      vs->clientds.pf.rmax);
+        color->green = ((green * 255 + vs->clientds.pf.gmax / 2) /
+                        vs->clientds.pf.gmax);
+        color->blue = ((blue * 255 + vs->clientds.pf.bmax / 2) /
+                       vs->clientds.pf.bmax);
+    }
+}
+
+static void png_write_data(png_structp png_ptr, png_bytep data,
+                           png_size_t length)
+{
+    VncState *vs = png_get_io_ptr(png_ptr);
+
+    buffer_reserve(&vs->tight_png, vs->tight_png.offset + length);
+    memcpy(vs->tight_png.buffer + vs->tight_png.offset, data, length);
+
+    vs->tight_png.offset += length;
+}
+
+static void png_flush_data(png_structp png_ptr)
+{
+}
+
+static void *vnc_png_malloc(png_structp png_ptr, png_size_t size)
+{
+    return qemu_malloc(size);
+}
+
+static void vnc_png_free(png_structp png_ptr, png_voidp ptr)
+{
+    qemu_free(ptr);
+}
+
+static int send_png_rect(VncState *vs, int x, int y, int w, int h,
+                         QDict *palette)
+{
+    png_byte color_type;
+    png_structp png_ptr;
+    png_infop info_ptr;
+    png_colorp png_palette = NULL;
+    size_t offset;
+    int level = tight_conf[vs->tight_compression].raw_zlib_level;
+    uint8_t *buf;
+    int dy;
+
+    png_ptr = png_create_write_struct_2(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL,
+                                        NULL, vnc_png_malloc, vnc_png_free);
+
+    if (png_ptr == NULL)
+        return -1;
+
+    info_ptr = png_create_info_struct(png_ptr);
+
+    if (info_ptr == NULL) {
+        png_destroy_write_struct(&png_ptr, NULL);
+        return -1;
+    }
+
+    png_set_write_fn(png_ptr, (void *) vs, png_write_data, png_flush_data);
+    png_set_compression_level(png_ptr, level);
+
+    if (palette) {
+        color_type = PNG_COLOR_TYPE_PALETTE;
+    } else {
+        color_type = PNG_COLOR_TYPE_RGB;
+    }
+
+    png_set_IHDR(png_ptr, info_ptr, w, h,
+                 8, color_type, PNG_INTERLACE_NONE,
+                 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+
+    if (color_type == PNG_COLOR_TYPE_PALETTE) {
+        struct palette_cb_priv priv;
+
+        png_palette = png_malloc(png_ptr, sizeof(*png_palette) *
+                                 qdict_size(palette));
+
+        priv.vs = vs;
+        priv.png_palette = png_palette;
+        qdict_iter(palette, write_png_palette, &priv);
+
+        png_set_PLTE(png_ptr, info_ptr, png_palette, qdict_size(palette));
+
+        offset = vs->tight.offset;
+        if (vs->clientds.pf.bytes_per_pixel == 4) {
+            tight_encode_indexed_rect32(vs->tight.buffer, w * h, palette);
+        } else {
+            tight_encode_indexed_rect16(vs->tight.buffer, w * h, palette);
+        }
+    }
+
+    png_write_info(png_ptr, info_ptr);
+
+    buffer_reserve(&vs->tight_png, 2048);
+    buf = qemu_malloc(w * 3);
+    for (dy = 0; dy < h; dy++)
+    {
+        if (color_type == PNG_COLOR_TYPE_PALETTE) {
+            memcpy(buf, vs->tight.buffer + (dy * w), w);
+        } else {
+            rgb_prepare_row(vs, buf, x, y + dy, w);
+        }
+        png_write_row(png_ptr, buf);
+    }
+    qemu_free(buf);
+
+    png_write_end(png_ptr, NULL);
+
+    if (color_type == PNG_COLOR_TYPE_PALETTE) {
+        png_free(png_ptr, png_palette);
+    }
+
+    png_destroy_write_struct(&png_ptr, &info_ptr);
+
+    vnc_write_u8(vs, VNC_TIGHT_PNG << 4);
+
+    tight_send_compact_size(vs, vs->tight_png.offset);
+    vnc_write(vs, vs->tight_png.buffer, vs->tight_png.offset);
+    buffer_reset(&vs->tight_png);
+    return 1;
+}
+#endif /* CONFIG_VNC_PNG */
+
 static void vnc_tight_start(VncState *vs)
 {
     buffer_reset(&vs->tight);
@@ -1312,7 +1530,7 @@ static int send_sub_rect(VncState *vs, int x, int y, int w, int h)
     int colors;
     int ret = 0;
 
-    vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_TIGHT);
+    vnc_framebuffer_update(vs, x, y, w, h, vs->tight_type);
 
     vnc_tight_start(vs);
     vnc_raw_send_framebuffer_update(vs, x, y, w, h);
@@ -1323,23 +1541,23 @@ static int send_sub_rect(VncState *vs, int x, int y, int w, int h)
     if (colors == 0) {
         if (tight_detect_smooth_image(vs, w, h)) {
             if (vs->tight_quality == -1) {
-                ret = send_gradient_rect(vs, w, h);
+                ret = send_gradient_rect(vs, x, y, w, h);
             } else {
 #ifdef CONFIG_VNC_JPEG
                 int quality = tight_conf[vs->tight_quality].jpeg_quality;
 
                 ret = send_jpeg_rect(vs, x, y, w, h, quality);
 #else
-                ret = send_full_color_rect(vs, w, h);
+                ret = send_full_color_rect(vs, x, y, w, h);
 #endif
             }
         } else {
-            ret = send_full_color_rect(vs, w, h);
+            ret = send_full_color_rect(vs, x, y, w, h);
         }
     } else if (colors == 1) {
         ret = send_solid_rect(vs);
     } else if (colors == 2) {
-        ret = send_mono_rect(vs, w, h, bg, fg);
+        ret = send_mono_rect(vs, x, y, w, h, bg, fg);
     } else if (colors <= 256) {
 #ifdef CONFIG_VNC_JPEG
         if (colors > 96 && vs->tight_quality != -1 && vs->tight_quality <= 3 &&
@@ -1348,10 +1566,10 @@ static int send_sub_rect(VncState *vs, int x, int y, int w, int h)
 
             ret = send_jpeg_rect(vs, x, y, w, h, quality);
         } else {
-            ret = send_palette_rect(vs, w, h, palette);
+            ret = send_palette_rect(vs, x, y, w, h, palette);
         }
 #else
-        ret = send_palette_rect(vs, w, h, palette);
+        ret = send_palette_rect(vs, x, y, w, h, palette);
 #endif
     }
     QDECREF(palette);
@@ -1360,7 +1578,7 @@ static int send_sub_rect(VncState *vs, int x, int y, int w, int h)
 
 static int send_sub_rect_solid(VncState *vs, int x, int y, int w, int h)
 {
-    vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_TIGHT);
+    vnc_framebuffer_update(vs, x, y, w, h, vs->tight_type);
 
     vnc_tight_start(vs);
     vnc_raw_send_framebuffer_update(vs, x, y, w, h);
@@ -1453,8 +1671,8 @@ static int find_large_solid_color_rect(VncState *vs, int x, int y,
                 n += send_rect_simple(vs, x, y, w, y_best-y);
             }
             if (x_best != x) {
-                n += vnc_tight_send_framebuffer_update(vs, x, y_best,
-                                                       x_best-x, h_best);
+                n += tight_send_framebuffer_update(vs, x, y_best,
+                                                   x_best-x, h_best);
             }
 
             /* Send solid-color rectangle. */
@@ -1463,14 +1681,14 @@ static int find_large_solid_color_rect(VncState *vs, int x, int y,
             /* Send remaining rectangles (at right and bottom). */
 
             if (x_best + w_best != x + w) {
-                n += vnc_tight_send_framebuffer_update(vs, x_best+w_best,
-                                                       y_best,
-                                                       w-(x_best-x)-w_best,
-                                                       h_best);
+                n += tight_send_framebuffer_update(vs, x_best+w_best,
+                                                   y_best,
+                                                   w-(x_best-x)-w_best,
+                                                   h_best);
             }
             if (y_best + h_best != y + h) {
-                n += vnc_tight_send_framebuffer_update(vs, x, y_best+h_best,
-                                                       w, h-(y_best-y)-h_best);
+                n += tight_send_framebuffer_update(vs, x, y_best+h_best,
+                                                   w, h-(y_best-y)-h_best);
             }
 
             /* Return after all recursive calls are done. */
@@ -1480,8 +1698,8 @@ static int find_large_solid_color_rect(VncState *vs, int x, int y,
     return n + send_rect_simple(vs, x, y, w, h);
 }
 
-int vnc_tight_send_framebuffer_update(VncState *vs, int x, int y,
-                                      int w, int h)
+static int tight_send_framebuffer_update(VncState *vs, int x, int y,
+                                         int w, int h)
 {
     int max_rows;
 
@@ -1503,6 +1721,20 @@ int vnc_tight_send_framebuffer_update(VncState *vs, int x, int y,
     return find_large_solid_color_rect(vs, x, y, w, h, max_rows);
 }
 
+int vnc_tight_send_framebuffer_update(VncState *vs, int x, int y,
+                                      int w, int h)
+{
+    vs->tight_type = VNC_ENCODING_TIGHT;
+    return tight_send_framebuffer_update(vs, x, y, w, h);
+}
+
+int vnc_tight_png_send_framebuffer_update(VncState *vs, int x, int y,
+                                          int w, int h)
+{
+    vs->tight_type = VNC_ENCODING_TIGHT_PNG;
+    return tight_send_framebuffer_update(vs, x, y, w, h);
+}
+
 void vnc_tight_clear(VncState *vs)
 {
     int i;