]>
Commit | Line | Data |
---|---|---|
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 | ||
38 | static 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 | ||
58 | static int tight_init_stream(VncState *vs, int stream_id, | |
59 | int level, int strategy) | |
60 | { | |
61 | z_streamp zstream = &vs->tight_stream[stream_id]; | |
62 | ||
63 | if (zstream->opaque == NULL) { | |
64 | int err; | |
65 | ||
66 | VNC_DEBUG("VNC: TIGHT: initializing zlib stream %d\n", stream_id); | |
67 | VNC_DEBUG("VNC: TIGHT: opaque = %p | vs = %p\n", zstream->opaque, vs); | |
68 | zstream->zalloc = vnc_zlib_zalloc; | |
69 | zstream->zfree = vnc_zlib_zfree; | |
70 | ||
71 | err = deflateInit2(zstream, level, Z_DEFLATED, MAX_WBITS, | |
72 | MAX_MEM_LEVEL, strategy); | |
73 | ||
74 | if (err != Z_OK) { | |
75 | fprintf(stderr, "VNC: error initializing zlib\n"); | |
76 | return -1; | |
77 | } | |
78 | ||
79 | vs->tight_levels[stream_id] = level; | |
80 | zstream->opaque = vs; | |
81 | } | |
82 | ||
83 | if (vs->tight_levels[stream_id] != level) { | |
84 | if (deflateParams(zstream, level, strategy) != Z_OK) { | |
85 | return -1; | |
86 | } | |
87 | vs->tight_levels[stream_id] = level; | |
88 | } | |
89 | return 0; | |
90 | } | |
91 | ||
92 | static void tight_send_compact_size(VncState *vs, size_t len) | |
93 | { | |
94 | int lpc = 0; | |
95 | int bytes = 0; | |
96 | char buf[3] = {0, 0, 0}; | |
97 | ||
98 | buf[bytes++] = len & 0x7F; | |
99 | if (len > 0x7F) { | |
100 | buf[bytes-1] |= 0x80; | |
101 | buf[bytes++] = (len >> 7) & 0x7F; | |
102 | if (len > 0x3FFF) { | |
103 | buf[bytes-1] |= 0x80; | |
104 | buf[bytes++] = (len >> 14) & 0xFF; | |
105 | } | |
106 | } | |
107 | for(lpc = 0; lpc < bytes; lpc++) { | |
108 | vnc_write_u8(vs, buf[lpc]); | |
109 | } | |
110 | } | |
111 | ||
112 | static int tight_compress_data(VncState *vs, int stream_id, size_t bytes, | |
113 | int level, int strategy) | |
114 | { | |
115 | z_streamp zstream = &vs->tight_stream[stream_id]; | |
116 | int previous_out; | |
117 | ||
118 | if (bytes < VNC_TIGHT_MIN_TO_COMPRESS) { | |
119 | vnc_write(vs, vs->tight.buffer, vs->tight.offset); | |
120 | return bytes; | |
121 | } | |
122 | ||
123 | if (tight_init_stream(vs, stream_id, level, strategy)) { | |
124 | return -1; | |
125 | } | |
126 | ||
127 | /* reserve memory in output buffer */ | |
128 | buffer_reserve(&vs->tight_zlib, bytes + 64); | |
129 | ||
130 | /* set pointers */ | |
131 | zstream->next_in = vs->tight.buffer; | |
132 | zstream->avail_in = vs->tight.offset; | |
133 | zstream->next_out = vs->tight_zlib.buffer + vs->tight_zlib.offset; | |
134 | zstream->avail_out = vs->tight_zlib.capacity - vs->tight_zlib.offset; | |
135 | zstream->data_type = Z_BINARY; | |
136 | previous_out = zstream->total_out; | |
137 | ||
138 | /* start encoding */ | |
139 | if (deflate(zstream, Z_SYNC_FLUSH) != Z_OK) { | |
140 | fprintf(stderr, "VNC: error during tight compression\n"); | |
141 | return -1; | |
142 | } | |
143 | ||
144 | vs->tight_zlib.offset = vs->tight_zlib.capacity - zstream->avail_out; | |
145 | bytes = zstream->total_out - previous_out; | |
146 | ||
147 | tight_send_compact_size(vs, bytes); | |
148 | vnc_write(vs, vs->tight_zlib.buffer, bytes); | |
149 | ||
150 | buffer_reset(&vs->tight_zlib); | |
151 | ||
152 | return bytes; | |
153 | } | |
154 | ||
155 | /* | |
156 | * Subencoding implementations. | |
157 | */ | |
158 | static void tight_pack24(VncState *vs, size_t count) | |
159 | { | |
160 | unsigned char *buf; | |
161 | uint32_t *buf32; | |
162 | uint32_t pix; | |
163 | int rshift, gshift, bshift; | |
164 | ||
165 | buf = vs->tight.buffer; | |
166 | buf32 = (uint32_t *)buf; | |
167 | ||
168 | if ((vs->clientds.flags & QEMU_BIG_ENDIAN_FLAG) == | |
169 | (vs->ds->surface->flags & QEMU_BIG_ENDIAN_FLAG)) { | |
170 | rshift = vs->clientds.pf.rshift; | |
171 | gshift = vs->clientds.pf.gshift; | |
172 | bshift = vs->clientds.pf.bshift; | |
173 | } else { | |
174 | rshift = 24 - vs->clientds.pf.rshift; | |
175 | gshift = 24 - vs->clientds.pf.gshift; | |
176 | bshift = 24 - vs->clientds.pf.bshift; | |
177 | } | |
178 | ||
179 | vs->tight.offset = count * 3; | |
180 | ||
181 | while (count--) { | |
182 | pix = *buf32++; | |
183 | *buf++ = (char)(pix >> rshift); | |
184 | *buf++ = (char)(pix >> gshift); | |
185 | *buf++ = (char)(pix >> bshift); | |
186 | } | |
187 | } | |
188 | ||
189 | static int send_full_color_rect(VncState *vs, int w, int h) | |
190 | { | |
191 | int stream = 0; | |
192 | size_t bytes; | |
193 | ||
194 | vnc_write_u8(vs, stream << 4); /* no flushing, no filter */ | |
195 | ||
196 | if (vs->tight_pixel24) { | |
197 | tight_pack24(vs, w * h); | |
198 | bytes = 3; | |
199 | } else { | |
200 | bytes = vs->clientds.pf.bytes_per_pixel; | |
201 | } | |
202 | ||
203 | bytes = tight_compress_data(vs, stream, w * h * bytes, | |
204 | tight_conf[vs->tight_compression].raw_zlib_level, | |
205 | Z_DEFAULT_STRATEGY); | |
206 | ||
207 | return (bytes >= 0); | |
208 | } | |
209 | ||
210 | static void vnc_tight_start(VncState *vs) | |
211 | { | |
212 | buffer_reset(&vs->tight); | |
213 | ||
214 | // make the output buffer be the zlib buffer, so we can compress it later | |
215 | vs->tight_tmp = vs->output; | |
216 | vs->output = vs->tight; | |
217 | } | |
218 | ||
219 | static void vnc_tight_stop(VncState *vs) | |
220 | { | |
221 | // switch back to normal output/zlib buffers | |
222 | vs->tight = vs->output; | |
223 | vs->output = vs->tight_tmp; | |
224 | } | |
225 | ||
226 | static int send_sub_rect(VncState *vs, int x, int y, int w, int h) | |
227 | { | |
228 | vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_TIGHT); | |
229 | ||
230 | /* | |
231 | * Convert pixels and store them in vs->tight | |
232 | * We will probably rework that later, probably | |
233 | * when adding other sub-encodings | |
234 | */ | |
235 | vnc_tight_start(vs); | |
236 | vnc_raw_send_framebuffer_update(vs, x, y, w, h); | |
237 | vnc_tight_stop(vs); | |
238 | ||
239 | return send_full_color_rect(vs, w, h); | |
240 | } | |
241 | ||
242 | static int send_rect_simple(VncState *vs, int x, int y, int w, int h) | |
243 | { | |
244 | int max_size, max_width; | |
245 | int max_sub_width, max_sub_height; | |
246 | int dx, dy; | |
247 | int rw, rh; | |
248 | int n = 0; | |
249 | ||
250 | max_size = tight_conf[vs->tight_compression].max_rect_size; | |
251 | max_width = tight_conf[vs->tight_compression].max_rect_width; | |
252 | ||
253 | if (w > max_width || w * h > max_size) { | |
254 | max_sub_width = (w > max_width) ? max_width : w; | |
255 | max_sub_height = max_size / max_sub_width; | |
256 | ||
257 | for (dy = 0; dy < h; dy += max_sub_height) { | |
258 | for (dx = 0; dx < w; dx += max_width) { | |
259 | rw = MIN(max_sub_width, w - dx); | |
260 | rh = MIN(max_sub_height, h - dy); | |
261 | n += send_sub_rect(vs, x+dx, y+dy, rw, rh); | |
262 | } | |
263 | } | |
264 | } else { | |
265 | n += send_sub_rect(vs, x, y, w, h); | |
266 | } | |
267 | ||
268 | return n; | |
269 | } | |
270 | ||
271 | int vnc_tight_send_framebuffer_update(VncState *vs, int x, int y, | |
272 | int w, int h) | |
273 | { | |
274 | if (vs->clientds.pf.bytes_per_pixel == 4 && vs->clientds.pf.rmax == 0xFF && | |
275 | vs->clientds.pf.bmax == 0xFF && vs->clientds.pf.gmax == 0xFF) { | |
276 | vs->tight_pixel24 = true; | |
277 | } else { | |
278 | vs->tight_pixel24 = false; | |
279 | } | |
280 | ||
281 | return send_rect_simple(vs, x, y, w, h); | |
282 | } | |
283 | ||
284 | void vnc_tight_clear(VncState *vs) | |
285 | { | |
286 | int i; | |
287 | for (i=0; i<ARRAY_SIZE(vs->tight_stream); i++) { | |
288 | if (vs->tight_stream[i].opaque) { | |
289 | deflateEnd(&vs->tight_stream[i]); | |
290 | } | |
291 | } | |
292 | ||
293 | buffer_free(&vs->tight); | |
294 | buffer_free(&vs->tight_zlib); | |
295 | } |