Root/
Source at commit 06c5f6b0fa8cc493d93cfb87f8cbc62a877c9bc7 created 11 years 1 month ago. By Werner Almesberger, ubb-la/gui.c (textf): use vsnprintf+valloca instead of vasprintf | |
---|---|
1 | /* |
2 | * gui.c - Graphical output for ubb-la |
3 | * |
4 | * Written 2013 by Werner Almesberger |
5 | * Copyright 2013 Werner Almesberger |
6 | * |
7 | * This program is free software; you can redistribute it and/or modify |
8 | * it under the terms of the GNU General Public License as published by |
9 | * the Free Software Foundation; either version 2 of the License, or |
10 | * (at your option) any later version. |
11 | */ |
12 | |
13 | #include <stdarg.h> |
14 | #include <stdint.h> |
15 | #include <stdlib.h> |
16 | #include <alloca.h> |
17 | |
18 | #include "SDL.h" |
19 | #include "SDL_gfxPrimitives.h" |
20 | #include "SDL_gfxPrimitives_font.h" |
21 | |
22 | #include "gui.h" |
23 | |
24 | |
25 | #if 0 |
26 | #define DEBUG(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__) |
27 | #else |
28 | #define DEBUG(fmt, ...) |
29 | #endif |
30 | |
31 | |
32 | #define XRES 320 /* canvas width */ |
33 | #define YRES 240 /* canvas height */ |
34 | |
35 | #define TEXT_RGBA 0xffffffff /* general text */ |
36 | #define MAP_BUF_RGBA 0x808080ff /* buffer in the map */ |
37 | #define MAP_VIEW_RGBA 0xffffffff /* current view in the map */ |
38 | #define LEVEL_RGBA 0xffff00ff /* constant level or single change */ |
39 | #define BOUNCE_RGBA 0xff8080ff /* bouncing signal */ |
40 | #define LABEL_RGBA 0xffffffff /* channel label */ |
41 | #define DIV_RGBA 0x808080ff /* divisions */ |
42 | |
43 | #define XCENTER ((XRES+CH_XOFF)/2) |
44 | #define XWIDTH (XRES-CH_XOFF) |
45 | |
46 | #define MAP_BUF_Y0 2 |
47 | #define MAP_BUF_Y1 8 |
48 | #define MAP_VIEW_Y0 0 |
49 | #define MAP_VIEW_Y1 10 |
50 | |
51 | #define CH_XOFF 30 |
52 | #define CH_YOFF 30 |
53 | #define CH_SKIP 16 |
54 | #define CH_HEIGHT 8 |
55 | |
56 | #define DIV_X 32 |
57 | #define DIV_Y 6 |
58 | |
59 | #define MAX_ZOOM 3 |
60 | |
61 | #define FREQ_X 0 |
62 | #define FREQ_Y 220 |
63 | #define INTERVAL_X 0 |
64 | #define INTERVAL_Y 230 |
65 | #define DIV_SAMP_X (80-8) |
66 | #define DIV_SAMP_Y FREQ_Y |
67 | #define DIV_INT_X 80 |
68 | #define DIV_INT_Y INTERVAL_Y |
69 | |
70 | #define REPEAT_DELAY_MS 300 |
71 | #define REPEAT_INTERVAL_MS 30 |
72 | |
73 | static SDL_Surface *surf; |
74 | |
75 | |
76 | /* ----- SDL initialization and screen-wide functions ---------------------- */ |
77 | |
78 | |
79 | void gui_init(void) |
80 | { |
81 | if (SDL_Init(SDL_INIT_VIDEO) < 0) { |
82 | fprintf(stderr, "SDL_init: %s\n", SDL_GetError()); |
83 | exit(1); |
84 | } |
85 | atexit(SDL_Quit); |
86 | |
87 | surf = SDL_SetVideoMode(XRES, YRES, 0, SDL_SWSURFACE); |
88 | if (!surf) { |
89 | fprintf(stderr, "SDL_SetVideoMode: %s\n", SDL_GetError()); |
90 | exit(1); |
91 | } |
92 | |
93 | SDL_EnableKeyRepeat(REPEAT_DELAY_MS, REPEAT_INTERVAL_MS); |
94 | } |
95 | |
96 | |
97 | static void clear(void) |
98 | { |
99 | SDL_LockSurface(surf); |
100 | SDL_FillRect(surf, NULL, SDL_MapRGB(surf->format, 0, 0, 0)); |
101 | } |
102 | |
103 | |
104 | static void update(void) |
105 | { |
106 | SDL_UnlockSurface(surf); |
107 | SDL_UpdateRect(surf, 0, 0, 0, 0); |
108 | } |
109 | |
110 | |
111 | /* ----- Text output ------------------------------------------------------- */ |
112 | |
113 | |
114 | /* |
115 | * stringColor from SDL_gfx fails for some reason. SDL_ttf is just too much |
116 | * compatibility trouble. So we do our own. |
117 | */ |
118 | |
119 | static void textf(int x, int y, uint32_t color, const char *fmt, ...) |
120 | { |
121 | va_list ap; |
122 | char *tmp, *s; |
123 | uint8_t *p; |
124 | int n, ix, iy; |
125 | |
126 | va_start(ap, fmt); |
127 | n = vsnprintf(NULL, 0, fmt, ap); |
128 | va_end(ap); |
129 | tmp = alloca(n+1); |
130 | va_start(ap, fmt); |
131 | vsnprintf(tmp, n+1, fmt, ap); |
132 | va_end(ap); |
133 | |
134 | for (s = tmp; *s; s++) { |
135 | p = gfxPrimitivesFontdata+(*s << 3); |
136 | for (iy = 0; iy != 8; iy++) { |
137 | for (ix = 0; ix != 8; ix++) |
138 | if ((*p << ix) & 0x80) |
139 | pixelColor(surf, x+ix, y+iy, color); |
140 | p++; |
141 | } |
142 | x += 8; |
143 | } |
144 | } |
145 | |
146 | |
147 | /* ----- Map of buffer and view -------------------------------------------- */ |
148 | |
149 | |
150 | static void show_map(int skip, int nibbles, int s0, int s1) |
151 | { |
152 | int w = nibbles-skip; |
153 | int m = (nibbles+skip) >> 1; |
154 | int scale = 0; |
155 | |
156 | while (w >= XWIDTH/2) { |
157 | w >>= 1; |
158 | scale++; |
159 | } |
160 | boxColor(surf, XCENTER-(w >> 1), MAP_BUF_Y0, |
161 | XCENTER+(w >> 1), MAP_BUF_Y1, MAP_BUF_RGBA); |
162 | rectangleColor(surf, (s0-m+(XCENTER << scale)) >> scale, MAP_VIEW_Y0, |
163 | (s1-m+(XCENTER << scale)) >> scale, MAP_VIEW_Y1, MAP_VIEW_RGBA); |
164 | } |
165 | |
166 | |
167 | /* ----- Waveform elements ------------------------------------------------- */ |
168 | |
169 | |
170 | static inline int ch_y(int ch, int v) |
171 | { |
172 | return CH_YOFF+CH_SKIP*ch+(v ? 0 : CH_HEIGHT); |
173 | } |
174 | |
175 | |
176 | static void bounce(int x, int ch) |
177 | { |
178 | vlineColor(surf, x, ch_y(ch, 0), ch_y(ch, 1), BOUNCE_RGBA); |
179 | } |
180 | |
181 | |
182 | static void change(int x, int ch) |
183 | { |
184 | vlineColor(surf, x, ch_y(ch, 0), ch_y(ch, 1), LEVEL_RGBA); |
185 | } |
186 | |
187 | |
188 | static void level(int x0, int x1, int ch, int v) |
189 | { |
190 | if (x0 == x1) |
191 | pixelColor(surf, x0, ch_y(ch, v), LEVEL_RGBA); |
192 | else |
193 | hlineColor(surf, x0, x1, ch_y(ch, v), LEVEL_RGBA); |
194 | } |
195 | |
196 | |
197 | /* ----- Show (part of) a buffer ------------------------------------------- */ |
198 | |
199 | |
200 | static void show_buffer_zoom_in(const uint8_t *buf, int skip, int nibbles, |
201 | int x0, int x1) |
202 | { |
203 | int f = (x1-x0)/(nibbles-skip); |
204 | int x, i, j; |
205 | int last[4] = { -1, -1, -1, -1}; |
206 | uint8_t v, bit; |
207 | |
208 | DEBUG("in: %d-%d (%d) Sa %d-%d (%d) pix %d f (%d)\n", |
209 | skip, nibbles, nibbles-skip, x0, x1, x1-x0, f, f*(nibbles-skip)); |
210 | x = x0; |
211 | for (i = skip; i != nibbles; i++) { |
212 | v = (buf[i >> 1] >> 4*(~i & 1)) & 0xf; |
213 | for (j = 0; j != 4; j++) { |
214 | bit = (v >> j) & 1; |
215 | if (bit == last[j] || last[j] == -1) { |
216 | level(x, x+f-1, j, bit); |
217 | } else { |
218 | change(x, j); |
219 | level(x+1, x+f-1, j, bit); |
220 | } |
221 | last[j] = bit; |
222 | } |
223 | x += f; |
224 | } |
225 | } |
226 | |
227 | |
228 | static void show_buffer_zoom_out(const uint8_t *buf, int skip, int nibbles, |
229 | int x0, int x1) |
230 | { |
231 | int f = (nibbles-skip)/(x1-x0); |
232 | int x; |
233 | int i = skip, n, j; |
234 | int changes[4], last[4] = { -1, -1, -1, -1}, next[4]; |
235 | uint8_t v, bit; |
236 | |
237 | DEBUG("out: %d-%d (%d) Sa %d-%d (%d) pix %d f (%d)\n", |
238 | skip, nibbles, nibbles-skip, x0, x1, x1-x0, f, f*(x1-x0)); |
239 | for (x = x0; x != x1; x++) { |
240 | n = i+f; |
241 | for (j = 0; j != 4; j++) { |
242 | next[j] = last[j]; |
243 | changes[j] = 0; |
244 | } |
245 | while (i != n) { |
246 | v = (buf[i >> 1] >> 4*(~i & 1)) & 0xf; |
247 | for (j = 0; j != 4; j++) { |
248 | bit = (v >> j) & 1; |
249 | if (bit != next[j]) { |
250 | changes[j]++; |
251 | next[j] = bit; |
252 | } |
253 | } |
254 | i++; |
255 | } |
256 | for (j = 0; j != 4; j++) { |
257 | if (changes[j] > 1) |
258 | bounce(x, j); |
259 | else if (!changes[j] || last[j] == -1) |
260 | level(x, x, j, next[j]); |
261 | else |
262 | change(x, j); |
263 | last[j] = next[j]; |
264 | } |
265 | } |
266 | } |
267 | |
268 | |
269 | static void show_buffer(const uint8_t *buf, int skip, int nibbles, |
270 | int x0, int x1, int zoom, int pos) |
271 | { |
272 | int xm, w, s, p0, p1; |
273 | int d, dp; |
274 | |
275 | xm = (x0+x1) >> 1; |
276 | dp = pos-((nibbles+skip) >> 1); |
277 | if (zoom < 0) { |
278 | s = (x1-x0) << -zoom; |
279 | show_map(skip, nibbles, pos-s/2, pos+s/2); |
280 | w = (nibbles-skip) >> -zoom; |
281 | p0 = xm-(w >> 1)-(dp >> -zoom); |
282 | p1 = xm+((w+1) >> 1)-(dp >> -zoom); |
283 | if (p0 < x0) { |
284 | skip += (x0-p0) << -zoom; |
285 | p0 = x0; |
286 | } |
287 | if (p1 > x1) { |
288 | nibbles -= (p1-x1) << -zoom; |
289 | p1 = x1; |
290 | } |
291 | show_buffer_zoom_out(buf, skip, nibbles, p0, p1); |
292 | } else { |
293 | s = (x1-x0) >> zoom; |
294 | show_map(skip, nibbles, pos-s/2, pos+s/2); |
295 | w = (nibbles-skip) << zoom; |
296 | p0 = xm-(w >> 1)-(dp << zoom); |
297 | p1 = xm+((w+1) >> 1)-(dp << zoom); |
298 | if (p0 < x0) { |
299 | d = ((x0-p0)+(1 << zoom)-1) >> zoom; |
300 | skip += d; |
301 | p0 += d << zoom; |
302 | } |
303 | if (p1 > x1) { |
304 | d = ((p1-x1)+(1 << zoom)-1) >> zoom; |
305 | nibbles -= d; |
306 | p1 -= d << zoom; |
307 | } |
308 | show_buffer_zoom_in(buf, skip, nibbles, p0, p1); |
309 | } |
310 | } |
311 | |
312 | |
313 | /* ----- Display the sample frequency -------------------------------------- */ |
314 | |
315 | |
316 | static void si_text(int x, int y, double v, const char *unit) |
317 | { |
318 | const char *pfx; |
319 | |
320 | if (v >= 1e6) { |
321 | v /= 1e6; |
322 | pfx = "M"; |
323 | } else if (v >= 1e3) { |
324 | v /= 1e3; |
325 | pfx = "k"; |
326 | } else if (v >= 1) { |
327 | pfx = " "; |
328 | } else if (v >= 1e-3) { |
329 | v *= 1e3; |
330 | pfx = "m"; |
331 | } else if (v >= 1e-6) { |
332 | v *= 1e6; |
333 | pfx = "u"; |
334 | } else { |
335 | v *= 1e9; |
336 | pfx = "n"; |
337 | } |
338 | if (v >= 10) |
339 | textf(x, y, TEXT_RGBA, "%3d%s%s", (int) (v+0.5), pfx, unit); |
340 | else |
341 | textf(x, y, TEXT_RGBA, "%3.1f%s%s", v, pfx, unit); |
342 | } |
343 | |
344 | |
345 | static void show_freq(double freq, int zoom) |
346 | { |
347 | int div; |
348 | |
349 | si_text(FREQ_X, FREQ_Y, freq, "Sa/s"); |
350 | si_text(INTERVAL_X, INTERVAL_Y, 1/freq, "s/Sa"); |
351 | div = (DIV_X >> MAX_ZOOM) << (MAX_ZOOM-zoom); |
352 | textf(DIV_SAMP_X, DIV_SAMP_Y, TEXT_RGBA, "%4dSa/div", div); |
353 | si_text(DIV_INT_X, DIV_INT_Y, div/freq, "s/div"); |
354 | } |
355 | |
356 | |
357 | /* ----- Show a divisions -------------------------------------------------- */ |
358 | |
359 | |
360 | static void show_divisions(void) |
361 | { |
362 | int n = XWIDTH/2/DIV_X; |
363 | int i; |
364 | |
365 | for (i = -n; i <= n; i++) |
366 | vlineColor(surf, XCENTER+i*DIV_X, |
367 | ch_y(0, 1)-DIV_Y, ch_y(3, 0)+DIV_Y, DIV_RGBA); |
368 | } |
369 | |
370 | |
371 | /* ----- Main event loop --------------------------------------------------- */ |
372 | |
373 | |
374 | static int pos_step(int zoom) |
375 | { |
376 | return 1 << (MAX_ZOOM-zoom+1); |
377 | } |
378 | |
379 | |
380 | void gui(const uint8_t *buf, int skip, int nibbles, double freq) |
381 | { |
382 | SDL_Event event; |
383 | int pos = (skip+nibbles) >> 1; |
384 | int zoom; /* < 0: zoom out; 0: 1 pixel = 1 sample; > 1: zoom in */ |
385 | int min_zoom = 0; |
386 | int i; |
387 | |
388 | while (XWIDTH < (nibbles-skip) >> -min_zoom) |
389 | min_zoom--; |
390 | zoom = min_zoom; |
391 | while (1) { |
392 | clear(); |
393 | for (i = 0; i != 4; i++) |
394 | textf(0, ch_y(i, 1), LABEL_RGBA, "CH%d", i); |
395 | show_divisions(); |
396 | show_buffer(buf, skip, nibbles, CH_XOFF, XRES, zoom, pos); |
397 | show_freq(freq, zoom); |
398 | update(); |
399 | |
400 | while (1) { |
401 | SDL_WaitEvent(&event); |
402 | switch (event.type) { |
403 | case SDL_KEYDOWN: |
404 | switch (event.key.keysym.sym) { |
405 | case SDLK_UP: |
406 | if (zoom < MAX_ZOOM) |
407 | zoom++; |
408 | break; |
409 | case SDLK_DOWN: |
410 | if (zoom > min_zoom) |
411 | zoom--; |
412 | break; |
413 | case SDLK_LEFT: |
414 | pos -= pos_step(zoom); |
415 | if (pos < 0) |
416 | pos = 0; |
417 | break; |
418 | case SDLK_RIGHT: |
419 | pos += pos_step(zoom); |
420 | if (pos > nibbles-1) |
421 | pos = nibbles-1; |
422 | break; |
423 | case SDLK_RETURN: |
424 | case SDLK_q: |
425 | return; |
426 | default: |
427 | printf("%x\n", event.key.keysym.sym); |
428 | continue; |
429 | } |
430 | break; |
431 | case SDL_QUIT: |
432 | return; |
433 | default: |
434 | continue; |
435 | } |
436 | break; |
437 | } |
438 | } |
439 | } |
440 |
Branches:
master