]> git.proxmox.com Git - mirror_qemu.git/blame - vnc-encoding-tight.c
vnc: add support for tight fill encoding
[mirror_qemu.git] / vnc-encoding-tight.c
CommitLineData
380282b0
CC
1/*
2 * QEMU VNC display driver: tight encoding
3 *
4 * From libvncserver/libvncserver/tight.c
5 * Copyright (C) 2000, 2001 Const Kaplinsky. All Rights Reserved.
6 * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
7 *
8 * Copyright (C) 2010 Corentin Chary <corentin.chary@gmail.com>
9 *
10 * Permission is hereby granted, free of charge, to any person obtaining a copy
11 * of this software and associated documentation files (the "Software"), to deal
12 * in the Software without restriction, including without limitation the rights
13 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 * copies of the Software, and to permit persons to whom the Software is
15 * furnished to do so, subject to the following conditions:
16 *
17 * The above copyright notice and this permission notice shall be included in
18 * all copies or substantial portions of the Software.
19 *
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 * THE SOFTWARE.
27 */
28
29#include <stdbool.h>
30
31#include "vnc.h"
32#include "vnc-encoding-tight.h"
33
34/* Compression level stuff. The following array contains various
35 encoder parameters for each of 10 compression levels (0..9).
36 Last three parameters correspond to JPEG quality levels (0..9). */
37
38static const struct {
39 int max_rect_size, max_rect_width;
40 int mono_min_rect_size, gradient_min_rect_size;
41 int idx_zlib_level, mono_zlib_level, raw_zlib_level, gradient_zlib_level;
42 int gradient_threshold, gradient_threshold24;
43 int idx_max_colors_divisor;
44 int jpeg_quality, jpeg_threshold, jpeg_threshold24;
45} tight_conf[] = {
46 { 512, 32, 6, 65536, 0, 0, 0, 0, 0, 0, 4, 5, 10000, 23000 },
47 { 2048, 128, 6, 65536, 1, 1, 1, 0, 0, 0, 8, 10, 8000, 18000 },
48 { 6144, 256, 8, 65536, 3, 3, 2, 0, 0, 0, 24, 15, 6500, 15000 },
49 { 10240, 1024, 12, 65536, 5, 5, 3, 0, 0, 0, 32, 25, 5000, 12000 },
50 { 16384, 2048, 12, 65536, 6, 6, 4, 0, 0, 0, 32, 37, 4000, 10000 },
51 { 32768, 2048, 12, 4096, 7, 7, 5, 4, 150, 380, 32, 50, 3000, 8000 },
52 { 65536, 2048, 16, 4096, 7, 7, 6, 4, 170, 420, 48, 60, 2000, 5000 },
53 { 65536, 2048, 16, 4096, 8, 8, 7, 5, 180, 450, 64, 70, 1000, 2500 },
54 { 65536, 2048, 32, 8192, 9, 9, 8, 6, 190, 475, 64, 75, 500, 1200 },
55 { 65536, 2048, 32, 8192, 9, 9, 9, 6, 200, 500, 96, 80, 200, 500 }
56};
57
b4bea3f2
CC
58/*
59 * Check if a rectangle is all of the same color. If needSameColor is
60 * set to non-zero, then also check that its color equals to the
61 * *colorPtr value. The result is 1 if the test is successfull, and in
62 * that case new color will be stored in *colorPtr.
63 */
64
65#define DEFINE_CHECK_SOLID_FUNCTION(bpp) \
66 \
67 static bool \
68 check_solid_tile##bpp(VncState *vs, int x, int y, int w, int h, \
69 uint32_t* color, bool samecolor) \
70 { \
71 VncDisplay *vd = vs->vd; \
72 uint##bpp##_t *fbptr; \
73 uint##bpp##_t c; \
74 int dx, dy; \
75 \
76 fbptr = (uint##bpp##_t *) \
77 (vd->server->data + y * ds_get_linesize(vs->ds) + \
78 x * ds_get_bytes_per_pixel(vs->ds)); \
79 \
80 c = *fbptr; \
81 if (samecolor && (uint32_t)c != *color) { \
82 return false; \
83 } \
84 \
85 for (dy = 0; dy < h; dy++) { \
86 for (dx = 0; dx < w; dx++) { \
87 if (c != fbptr[dx]) { \
88 return false; \
89 } \
90 } \
91 fbptr = (uint##bpp##_t *) \
92 ((uint8_t *)fbptr + ds_get_linesize(vs->ds)); \
93 } \
94 \
95 *color = (uint32_t)c; \
96 return true; \
97 }
98
99DEFINE_CHECK_SOLID_FUNCTION(32)
100DEFINE_CHECK_SOLID_FUNCTION(16)
101DEFINE_CHECK_SOLID_FUNCTION(8)
102
103static bool check_solid_tile(VncState *vs, int x, int y, int w, int h,
104 uint32_t* color, bool samecolor)
105{
106 VncDisplay *vd = vs->vd;
107
108 switch(vd->server->pf.bytes_per_pixel) {
109 case 4:
110 return check_solid_tile32(vs, x, y, w, h, color, samecolor);
111 case 2:
112 return check_solid_tile16(vs, x, y, w, h, color, samecolor);
113 default:
114 return check_solid_tile8(vs, x, y, w, h, color, samecolor);
115 }
116}
117
118static void find_best_solid_area(VncState *vs, int x, int y, int w, int h,
119 uint32_t color, int *w_ptr, int *h_ptr)
120{
121 int dx, dy, dw, dh;
122 int w_prev;
123 int w_best = 0, h_best = 0;
124
125 w_prev = w;
126
127 for (dy = y; dy < y + h; dy += VNC_TIGHT_MAX_SPLIT_TILE_SIZE) {
128
129 dh = MIN(VNC_TIGHT_MAX_SPLIT_TILE_SIZE, y + h - dy);
130 dw = MIN(VNC_TIGHT_MAX_SPLIT_TILE_SIZE, w_prev);
131
132 if (!check_solid_tile(vs, x, dy, dw, dh, &color, true)) {
133 break;
134 }
135
136 for (dx = x + dw; dx < x + w_prev;) {
137 dw = MIN(VNC_TIGHT_MAX_SPLIT_TILE_SIZE, x + w_prev - dx);
138
139 if (!check_solid_tile(vs, dx, dy, dw, dh, &color, true)) {
140 break;
141 }
142 dx += dw;
143 }
144
145 w_prev = dx - x;
146 if (w_prev * (dy + dh - y) > w_best * h_best) {
147 w_best = w_prev;
148 h_best = dy + dh - y;
149 }
150 }
151
152 *w_ptr = w_best;
153 *h_ptr = h_best;
154}
155
156static void extend_solid_area(VncState *vs, int x, int y, int w, int h,
157 uint32_t color, int *x_ptr, int *y_ptr,
158 int *w_ptr, int *h_ptr)
159{
160 int cx, cy;
161
162 /* Try to extend the area upwards. */
163 for ( cy = *y_ptr - 1;
164 cy >= y && check_solid_tile(vs, *x_ptr, cy, *w_ptr, 1, &color, true);
165 cy-- );
166 *h_ptr += *y_ptr - (cy + 1);
167 *y_ptr = cy + 1;
168
169 /* ... downwards. */
170 for ( cy = *y_ptr + *h_ptr;
171 cy < y + h &&
172 check_solid_tile(vs, *x_ptr, cy, *w_ptr, 1, &color, true);
173 cy++ );
174 *h_ptr += cy - (*y_ptr + *h_ptr);
175
176 /* ... to the left. */
177 for ( cx = *x_ptr - 1;
178 cx >= x && check_solid_tile(vs, cx, *y_ptr, 1, *h_ptr, &color, true);
179 cx-- );
180 *w_ptr += *x_ptr - (cx + 1);
181 *x_ptr = cx + 1;
182
183 /* ... to the right. */
184 for ( cx = *x_ptr + *w_ptr;
185 cx < x + w &&
186 check_solid_tile(vs, cx, *y_ptr, 1, *h_ptr, &color, true);
187 cx++ );
188 *w_ptr += cx - (*x_ptr + *w_ptr);
189}
190
380282b0
CC
191static int tight_init_stream(VncState *vs, int stream_id,
192 int level, int strategy)
193{
194 z_streamp zstream = &vs->tight_stream[stream_id];
195
196 if (zstream->opaque == NULL) {
197 int err;
198
199 VNC_DEBUG("VNC: TIGHT: initializing zlib stream %d\n", stream_id);
200 VNC_DEBUG("VNC: TIGHT: opaque = %p | vs = %p\n", zstream->opaque, vs);
201 zstream->zalloc = vnc_zlib_zalloc;
202 zstream->zfree = vnc_zlib_zfree;
203
204 err = deflateInit2(zstream, level, Z_DEFLATED, MAX_WBITS,
205 MAX_MEM_LEVEL, strategy);
206
207 if (err != Z_OK) {
208 fprintf(stderr, "VNC: error initializing zlib\n");
209 return -1;
210 }
211
212 vs->tight_levels[stream_id] = level;
213 zstream->opaque = vs;
214 }
215
216 if (vs->tight_levels[stream_id] != level) {
217 if (deflateParams(zstream, level, strategy) != Z_OK) {
218 return -1;
219 }
220 vs->tight_levels[stream_id] = level;
221 }
222 return 0;
223}
224
225static void tight_send_compact_size(VncState *vs, size_t len)
226{
227 int lpc = 0;
228 int bytes = 0;
229 char buf[3] = {0, 0, 0};
230
231 buf[bytes++] = len & 0x7F;
232 if (len > 0x7F) {
233 buf[bytes-1] |= 0x80;
234 buf[bytes++] = (len >> 7) & 0x7F;
235 if (len > 0x3FFF) {
236 buf[bytes-1] |= 0x80;
237 buf[bytes++] = (len >> 14) & 0xFF;
238 }
239 }
b4bea3f2 240 for (lpc = 0; lpc < bytes; lpc++) {
380282b0
CC
241 vnc_write_u8(vs, buf[lpc]);
242 }
243}
244
245static int tight_compress_data(VncState *vs, int stream_id, size_t bytes,
246 int level, int strategy)
247{
248 z_streamp zstream = &vs->tight_stream[stream_id];
249 int previous_out;
250
251 if (bytes < VNC_TIGHT_MIN_TO_COMPRESS) {
252 vnc_write(vs, vs->tight.buffer, vs->tight.offset);
253 return bytes;
254 }
255
256 if (tight_init_stream(vs, stream_id, level, strategy)) {
257 return -1;
258 }
259
260 /* reserve memory in output buffer */
261 buffer_reserve(&vs->tight_zlib, bytes + 64);
262
263 /* set pointers */
264 zstream->next_in = vs->tight.buffer;
265 zstream->avail_in = vs->tight.offset;
266 zstream->next_out = vs->tight_zlib.buffer + vs->tight_zlib.offset;
267 zstream->avail_out = vs->tight_zlib.capacity - vs->tight_zlib.offset;
268 zstream->data_type = Z_BINARY;
269 previous_out = zstream->total_out;
270
271 /* start encoding */
272 if (deflate(zstream, Z_SYNC_FLUSH) != Z_OK) {
273 fprintf(stderr, "VNC: error during tight compression\n");
274 return -1;
275 }
276
277 vs->tight_zlib.offset = vs->tight_zlib.capacity - zstream->avail_out;
278 bytes = zstream->total_out - previous_out;
279
280 tight_send_compact_size(vs, bytes);
281 vnc_write(vs, vs->tight_zlib.buffer, bytes);
282
283 buffer_reset(&vs->tight_zlib);
284
285 return bytes;
286}
287
288/*
289 * Subencoding implementations.
290 */
291static void tight_pack24(VncState *vs, size_t count)
292{
293 unsigned char *buf;
294 uint32_t *buf32;
295 uint32_t pix;
296 int rshift, gshift, bshift;
297
298 buf = vs->tight.buffer;
299 buf32 = (uint32_t *)buf;
300
301 if ((vs->clientds.flags & QEMU_BIG_ENDIAN_FLAG) ==
302 (vs->ds->surface->flags & QEMU_BIG_ENDIAN_FLAG)) {
303 rshift = vs->clientds.pf.rshift;
304 gshift = vs->clientds.pf.gshift;
305 bshift = vs->clientds.pf.bshift;
306 } else {
307 rshift = 24 - vs->clientds.pf.rshift;
308 gshift = 24 - vs->clientds.pf.gshift;
309 bshift = 24 - vs->clientds.pf.bshift;
310 }
311
312 vs->tight.offset = count * 3;
313
314 while (count--) {
315 pix = *buf32++;
316 *buf++ = (char)(pix >> rshift);
317 *buf++ = (char)(pix >> gshift);
318 *buf++ = (char)(pix >> bshift);
319 }
320}
321
322static int send_full_color_rect(VncState *vs, int w, int h)
323{
324 int stream = 0;
325 size_t bytes;
326
327 vnc_write_u8(vs, stream << 4); /* no flushing, no filter */
328
329 if (vs->tight_pixel24) {
330 tight_pack24(vs, w * h);
331 bytes = 3;
332 } else {
333 bytes = vs->clientds.pf.bytes_per_pixel;
334 }
335
336 bytes = tight_compress_data(vs, stream, w * h * bytes,
337 tight_conf[vs->tight_compression].raw_zlib_level,
338 Z_DEFAULT_STRATEGY);
339
340 return (bytes >= 0);
341}
342
b4bea3f2
CC
343static int send_solid_rect(VncState *vs)
344{
345 size_t bytes;
346
347 vnc_write_u8(vs, VNC_TIGHT_FILL << 4); /* no flushing, no filter */
348
349 if (vs->tight_pixel24) {
350 tight_pack24(vs, 1);
351 bytes = 3;
352 } else {
353 bytes = vs->clientds.pf.bytes_per_pixel;
354 }
355
356 vnc_write(vs, vs->tight.buffer, bytes);
357 return 1;
358}
359
380282b0
CC
360static void vnc_tight_start(VncState *vs)
361{
362 buffer_reset(&vs->tight);
363
364 // make the output buffer be the zlib buffer, so we can compress it later
365 vs->tight_tmp = vs->output;
366 vs->output = vs->tight;
367}
368
369static void vnc_tight_stop(VncState *vs)
370{
371 // switch back to normal output/zlib buffers
372 vs->tight = vs->output;
373 vs->output = vs->tight_tmp;
374}
375
376static int send_sub_rect(VncState *vs, int x, int y, int w, int h)
377{
378 vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_TIGHT);
379
380 /*
381 * Convert pixels and store them in vs->tight
382 * We will probably rework that later, probably
383 * when adding other sub-encodings
384 */
385 vnc_tight_start(vs);
386 vnc_raw_send_framebuffer_update(vs, x, y, w, h);
387 vnc_tight_stop(vs);
388
389 return send_full_color_rect(vs, w, h);
390}
391
b4bea3f2
CC
392static int send_sub_rect_solid(VncState *vs, int x, int y, int w, int h)
393{
394 vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_TIGHT);
395
396 vnc_tight_start(vs);
397 vnc_raw_send_framebuffer_update(vs, x, y, w, h);
398 vnc_tight_stop(vs);
399
400 return send_solid_rect(vs);
401}
402
380282b0
CC
403static int send_rect_simple(VncState *vs, int x, int y, int w, int h)
404{
405 int max_size, max_width;
406 int max_sub_width, max_sub_height;
407 int dx, dy;
408 int rw, rh;
409 int n = 0;
410
411 max_size = tight_conf[vs->tight_compression].max_rect_size;
412 max_width = tight_conf[vs->tight_compression].max_rect_width;
413
414 if (w > max_width || w * h > max_size) {
415 max_sub_width = (w > max_width) ? max_width : w;
416 max_sub_height = max_size / max_sub_width;
417
418 for (dy = 0; dy < h; dy += max_sub_height) {
419 for (dx = 0; dx < w; dx += max_width) {
420 rw = MIN(max_sub_width, w - dx);
421 rh = MIN(max_sub_height, h - dy);
422 n += send_sub_rect(vs, x+dx, y+dy, rw, rh);
423 }
424 }
425 } else {
426 n += send_sub_rect(vs, x, y, w, h);
427 }
428
429 return n;
430}
431
b4bea3f2
CC
432static int find_large_solid_color_rect(VncState *vs, int x, int y,
433 int w, int h, int max_rows)
434{
435 int dx, dy, dw, dh;
436 int n = 0;
437
438 /* Try to find large solid-color areas and send them separately. */
439
440 for (dy = y; dy < y + h; dy += VNC_TIGHT_MAX_SPLIT_TILE_SIZE) {
441
442 /* If a rectangle becomes too large, send its upper part now. */
443
444 if (dy - y >= max_rows) {
445 n += send_rect_simple(vs, x, y, w, max_rows);
446 y += max_rows;
447 h -= max_rows;
448 }
449
450 dh = MIN(VNC_TIGHT_MAX_SPLIT_TILE_SIZE, (y + h - dy));
451
452 for (dx = x; dx < x + w; dx += VNC_TIGHT_MAX_SPLIT_TILE_SIZE) {
453 uint32_t color_value;
454 int x_best, y_best, w_best, h_best;
455
456 dw = MIN(VNC_TIGHT_MAX_SPLIT_TILE_SIZE, (x + w - dx));
457
458 if (!check_solid_tile(vs, dx, dy, dw, dh, &color_value, false)) {
459 continue ;
460 }
461
462 /* Get dimensions of solid-color area. */
463
464 find_best_solid_area(vs, dx, dy, w - (dx - x), h - (dy - y),
465 color_value, &w_best, &h_best);
466
467 /* Make sure a solid rectangle is large enough
468 (or the whole rectangle is of the same color). */
469
470 if (w_best * h_best != w * h &&
471 w_best * h_best < VNC_TIGHT_MIN_SOLID_SUBRECT_SIZE) {
472 continue;
473 }
474
475 /* Try to extend solid rectangle to maximum size. */
476
477 x_best = dx; y_best = dy;
478 extend_solid_area(vs, x, y, w, h, color_value,
479 &x_best, &y_best, &w_best, &h_best);
480
481 /* Send rectangles at top and left to solid-color area. */
482
483 if (y_best != y) {
484 n += send_rect_simple(vs, x, y, w, y_best-y);
485 }
486 if (x_best != x) {
487 n += vnc_tight_send_framebuffer_update(vs, x, y_best,
488 x_best-x, h_best);
489 }
490
491 /* Send solid-color rectangle. */
492 n += send_sub_rect_solid(vs, x_best, y_best, w_best, h_best);
493
494 /* Send remaining rectangles (at right and bottom). */
495
496 if (x_best + w_best != x + w) {
497 n += vnc_tight_send_framebuffer_update(vs, x_best+w_best,
498 y_best,
499 w-(x_best-x)-w_best,
500 h_best);
501 }
502 if (y_best + h_best != y + h) {
503 n += vnc_tight_send_framebuffer_update(vs, x, y_best+h_best,
504 w, h-(y_best-y)-h_best);
505 }
506
507 /* Return after all recursive calls are done. */
508 return n;
509 }
510 }
511 return n + send_rect_simple(vs, x, y, w, h);
512}
513
380282b0
CC
514int vnc_tight_send_framebuffer_update(VncState *vs, int x, int y,
515 int w, int h)
516{
b4bea3f2
CC
517 int max_rows;
518
380282b0
CC
519 if (vs->clientds.pf.bytes_per_pixel == 4 && vs->clientds.pf.rmax == 0xFF &&
520 vs->clientds.pf.bmax == 0xFF && vs->clientds.pf.gmax == 0xFF) {
521 vs->tight_pixel24 = true;
522 } else {
523 vs->tight_pixel24 = false;
524 }
525
b4bea3f2
CC
526 if (w * h < VNC_TIGHT_MIN_SPLIT_RECT_SIZE)
527 return send_rect_simple(vs, x, y, w, h);
528
529 /* Calculate maximum number of rows in one non-solid rectangle. */
530
531 max_rows = tight_conf[vs->tight_compression].max_rect_size;
532 max_rows /= MIN(tight_conf[vs->tight_compression].max_rect_width, w);
533
534 return find_large_solid_color_rect(vs, x, y, w, h, max_rows);
380282b0
CC
535}
536
537void vnc_tight_clear(VncState *vs)
538{
539 int i;
540 for (i=0; i<ARRAY_SIZE(vs->tight_stream); i++) {
541 if (vs->tight_stream[i].opaque) {
542 deflateEnd(&vs->tight_stream[i]);
543 }
544 }
545
546 buffer_free(&vs->tight);
547 buffer_free(&vs->tight_zlib);
548}