· 6 years ago · Mar 21, 2019, 07:26 AM
1/*
2Author: Anurag Singh
3Purpose:
4Date: 21/03/2019
5Last Mod: 21/03/2019
6*/
7/*
8** cab202_graphics.c
9**
10** Simple character-based "graphics" library for CAB202, 2016 semester 2.
11**
12** Authors:
13** Lawrence Buckingham
14** Benjamin Talbot
15** Jenna Riseley
16**
17** $Revision:Sat Feb 23 00:47:31 EAST 2019$
18*/
19
20#include <stdbool.h>
21#include <stdio.h>
22#include <string.h>
23#include <stdlib.h>
24#include <signal.h>
25#include <curses.h>
26#include <assert.h>
27#include "cab202_graphics.h"
28#include "cab202_timers.h"
29
30#define ABS(x) (((x) >= 0) ? (x) : -(x))
31#define MIN(x,y) (((x) < (y)) ? (x) : (y))
32#define MAX(x,y) (((x) > (y)) ? (x) : (y))
33#define SIGN(x) (((x) > 0) - ((x) < 0))
34
35 // Global variables to support automated testing.
36FILE * zdk_save_stream = NULL;
37FILE * zdk_input_stream = NULL;
38bool zdk_suppress_output = false;
39
40// Private helper functions.
41static void save_screen_(FILE * f);
42static void destroy_screen(Screen * scr);
43static void save_char(int char_code);
44
45/*
46 * Screen buffers. The most recent screen displayed by show_screen
47 * remains in zdk_prev_screen until the next call to show_screen. The
48 * data that will appear next time show_screen is called is stored
49 * in zdk_screen.
50 *
51 * Applications can check these objects for example, to see if a
52 * character has changed, or if something else is already present
53 * at a location.
54 */
55Screen * zdk_screen = NULL;
56Screen * zdk_prev_screen = NULL;
57
58/*
59 * The current foreground and background colour.
60 */
61static int foreground = WHITE;
62static int background = BLACK;
63static int colour_flags = 0;
64static int colour_num = 0;
65
66/*
67** Helper function which gets the colour number corresponding to a designated
68** (foreground,background) combination.
69**
70** Input:
71** fg - The foreground colour: an integer which must be between 0 and
72** (NUM_COLORS-1), inclusive.
73** bg - The background colour: an integer which must be between 0 and
74** (NUM_COLORS-1), inclusive.
75**
76** Output:
77** Returns a unique non-zero integer which represents the colour combination.
78*/
79
80static int colour_index(int fg, int bg) {
81 const int PAIR_OFFSET = 1;
82 return bg * NUM_COLOURS + fg + PAIR_OFFSET;
83}
84
85/*
86** Helper function which gets the ncurses attribute corresponding to the
87** current (foreground,background) combination.
88**
89** Input:
90** None (uses file-scope variables foreground, background, bright)
91**
92** Output:
93** Returns a unique non-zero integer which represents the colour combination.
94*/
95
96static void update_colour_num(void) {
97 int pair = COLOR_PAIR(colour_index(foreground, background));
98
99 if (colour_flags & BRIGHT) {
100 pair |= A_BOLD;
101 }
102
103 if (colour_flags & INVERSE) {
104 pair |= A_REVERSE;
105 }
106
107 colour_num = pair;
108
109 // colour_num = colour_index(foreground, background);
110}
111
112/*
113** See graphics.h for documentation.
114*/
115/*
116Purpose:
117Date: 21/03/2019
118*/
119void set_background(int colour) {
120 background = colour & (NUM_COLOURS - 1);
121 update_colour_num();
122}
123
124/*
125** See graphics.h for documentation.
126*/
127/*
128Purpose:
129Date: 21/03/2019
130*/
131void set_foreground(int colour) {
132 foreground = colour & (NUM_COLOURS - 1);
133 colour_flags = colour & (TRANSPARENT | INVERSE | BRIGHT);
134 update_colour_num();
135}
136
137/*
138** See graphics.h for documentation.
139*/
140/*
141Purpose:
142Date: 21/03/2019
143*/
144void set_colours(int foreground_, int background_) {
145 background = background_ & (NUM_COLOURS - 1);
146 foreground = foreground_ & (NUM_COLOURS - 1);
147 colour_flags = foreground_ & (TRANSPARENT | INVERSE | BRIGHT);
148 update_colour_num();
149}
150
151/*
152** See graphics.h for documentation.
153*/
154/*
155Purpose:
156Date: 21/03/2019
157*/
158void get_colours(int *foreground_, int *background_) {
159 *background_ = background;
160 *foreground_ = foreground | colour_flags;
161}
162
163/*
164** See graphics.h for documentation.
165*/
166/*
167Purpose:
168Date: 21/03/2019
169*/
170int get_background(void) {
171 return background;
172}
173
174/*
175** See graphics.h for documentation.
176*/
177/*
178Purpose:
179Date: 21/03/2019
180*/
181int get_foreground(void) {
182 return foreground | colour_flags;
183}
184
185/*
186** See graphics.h for documentation.
187*/
188/*
189Purpose:
190Date: 21/03/2019
191*/
192void setup_screen(void) {
193 if (!zdk_suppress_output) {
194 // Enter curses mode.
195 initscr();
196 start_color();
197
198 // Set up a colour pair for each terminal colour.
199 // The default background colour is black.
200 for (int fg = 0; fg < NUM_COLOURS; fg++) {
201 for (int bg = 0; bg < NUM_COLOURS; bg++) {
202 init_pair(colour_index(fg, bg), fg, bg);
203 }
204 }
205
206 foreground = COLOR_WHITE;
207 background = COLOR_BLACK;
208 update_colour_num();
209 bkgd(colour_num);
210
211 // Do not echo keypresses.
212 noecho();
213
214 // Turn off the cursor.
215 curs_set(0);
216
217 // Cause getch to return ERR if no key pressed within 0 milliseconds.
218 timeout(0);
219
220 // Enable the keypad.
221 keypad(stdscr, TRUE);
222
223 // Turn on mouse reporting.
224 mousemask(ALL_MOUSE_EVENTS, NULL);
225
226 // Erase any previous content that may be lingering in this screen.
227 clear();
228 }
229
230 // Create buffers
231 fit_screen_to_window();
232
233 // Add exit procedure to cleanup the screen before the program exists.
234 // Guard to ensure cleanup_screen is added at most once, because in certain
235 // situations (e.g. AMS) this function may be called multiple times.
236 static bool deja_vu = false;
237
238 if (!deja_vu) {
239 void ctrl_c_handler(int signal_code);
240 signal(SIGINT, ctrl_c_handler);
241 atexit(cleanup_screen);
242 deja_vu = true;
243 }
244}
245
246/**
247 * Signal handler for ctrl-c to ensure screen is cleaned up properly.
248 */
249/*
250Purpose:
251Date: 21/03/2019
252*/
253void ctrl_c_handler(int signal_code) {
254 // atexit handler has already been installed, so we should be able to
255 // use exit();
256 exit(1);
257}
258
259/*
260** See graphics.h for documentation.
261*/
262/*
263Purpose:
264Date: 21/03/2019
265*/
266void cleanup_screen(void) {
267 if (!zdk_suppress_output) {
268 // cleanup curses.
269 endwin();
270 }
271
272 // cleanup the drawing buffers.
273 destroy_screen(zdk_screen);
274 zdk_screen = NULL;
275
276 destroy_screen(zdk_prev_screen);
277 zdk_prev_screen = NULL;
278
279 // Close the screen-cast file, if open.
280 if (zdk_save_stream) {
281 fflush(zdk_save_stream);
282 fclose(zdk_save_stream);
283 zdk_save_stream = NULL;
284 }
285
286 // Null the input file (somebody else is responsible for its life cycle).
287 if (zdk_save_stream) {
288 zdk_input_stream = NULL;
289 }
290}
291
292/*
293** See graphics.h for documentation.
294*/
295/*
296Purpose:
297Date: 21/03/2019
298*/
299void clear_screen(void) {
300 if (zdk_screen != NULL) {
301 int w = zdk_screen->width;
302 int h = zdk_screen->height;
303
304 set_foreground(WHITE);
305
306 char * scr = zdk_screen->pixels[0];
307 int * colours = zdk_screen->colours[0];
308
309 memset(scr, ' ', w * h);
310
311 for (int i = 0; i < w*h; i++) {
312 colours[i] = colour_num;
313 }
314 }
315}
316
317/*
318** See graphics.h for documentation.
319*/
320/*
321Purpose:
322Date: 21/03/2019
323*/
324void show_screen(void) {
325 // Draw parts of the display that are different in the front
326 // buffer from the back buffer.
327 char ** back_px = zdk_prev_screen->pixels;
328 char ** front_px = zdk_screen->pixels;
329 int ** back_colour = zdk_prev_screen->colours;
330 int ** front_colour = zdk_screen->colours;
331 int w = zdk_screen->width;
332 int h = zdk_screen->height;
333 bool changed = false;
334
335 // Check each character to see if it has changed (either in value or colour)
336 // since the last time the function was called.
337 for (int y = 0; y < h; y++) {
338 for (int x = 0; x < w; x++) {
339 if (front_px[y][x] != back_px[y][x] || front_colour[y][x] != back_colour[y][x]) {
340 // Send changed char data to terminal.
341 attrset(front_colour[y][x]);
342 // color_set(COLOR_PAIR(front_colour[y][x]), NULL);
343 mvaddch(y, x, front_px[y][x]);
344
345 // Save new char data in back buffer.
346 back_px[y][x] = front_px[y][x];
347 back_colour[y][x] = front_colour[y][x];
348 changed = true;
349 }
350 }
351 }
352
353 if (!changed) {
354 return;
355 }
356
357 // Save a screen shot, if automatic saves are enabled.
358 save_screen_(zdk_save_stream);
359
360 // Force an update of the curses display.
361 if (!zdk_suppress_output) {
362 refresh();
363 }
364}
365
366/*
367** See graphics.h for documentation.
368*/
369/*
370Purpose:
371Date: 21/03/2019
372*/
373void draw_char(int x, int y, char value) {
374 if (zdk_screen != NULL) {
375 int w = zdk_screen->width;
376 int h = zdk_screen->height;
377
378 if (x >= 0 && x < w && y >= 0 && y < h) {
379 zdk_screen->pixels[y][x] = value;
380 zdk_screen->colours[y][x] = colour_num;
381 }
382 }
383}
384
385/*
386** See graphics.h for documentation.
387*/
388/*
389Purpose:
390Date: 21/03/2019
391*/
392void draw_line(int x1, int y1, int x2, int y2, char value) {
393 if (x1 == x2) {
394 // Draw vertical line
395 int y_min = MIN(y1, y2);
396 int y_max = MAX(y1, y2);
397
398 for (int i = y_min; i <= y_max; i++) {
399 draw_char(x1, i, value);
400 }
401 }
402 else if (y1 == y2) {
403 // Draw horizontal line
404 int x_min = MIN(x1, x2);
405 int x_max = MAX(x1, x2);
406
407 for (int i = x_min; i <= x_max; i++) {
408 draw_char(i, y1, value);
409 }
410 }
411 else {
412 // Inserted to ensure that lines are always drawn in the same direction, regardless of
413 // the order the endpoints are presented.
414 if (x1 > x2) {
415 int t = x1;
416 x1 = x2;
417 x2 = t;
418 t = y1;
419 y1 = y2;
420 y2 = t;
421 }
422
423 // Use the Bresenham algorithm to render the line.
424 // TODO: Convert to an integer-only implementation such as
425 // https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
426 float dx = x2 - x1;
427 float dy = y2 - y1;
428 float err = 0.0;
429 float derr = ABS(dy / dx);
430
431 for (int x = x1, y = y1; (dx > 0) ? x <= x2 : x >= x2; (dx > 0) ? x++ : x--) {
432 draw_char(x, y, value);
433 err += derr;
434 while (err >= 0.5 && ((dy > 0) ? y <= y2 : y >= y2)) {
435 draw_char(x, y, value);
436 y += (dy > 0) - (dy < 0);
437
438 err -= 1.0;
439 }
440 }
441 }
442}
443
444/*
445** See graphics.h for documentation.
446*/
447/*
448Purpose:
449Date: 21/03/2019
450*/
451char scrape_char(int x, int y) {
452 if (x < 0 || y < 0 ||
453 x >= zdk_prev_screen->width ||
454 y >= zdk_prev_screen->height
455 ) {
456 return -1;
457 }
458 else {
459 return zdk_prev_screen->pixels[y][x];
460 }
461}
462
463/*
464** See graphics.h for documentation.
465*/
466/*
467Purpose:
468Date: 21/03/2019
469*/
470void draw_solid_line(int x1, int y1, int x2, int y2, int colour) {
471 int fg = get_foreground();
472 int bg = get_background();
473 set_foreground(colour | INVERSE);
474 set_background(colour);
475 draw_line(x1, y1, x2, y2, ' ');
476 set_foreground(fg);
477 set_background(bg);
478}
479
480/*
481** See graphics.h for documentation.
482*/
483/*
484Purpose:
485Date: 21/03/2019
486*/
487void draw_string(int x, int y, char * text) {
488 for (int i = 0; text[i]; i++) {
489 draw_char(x + i, y, text[i]);
490 }
491}
492
493/*
494** See graphics.h for documentation.
495*/
496/*
497Purpose:
498Date: 21/03/2019
499*/
500void draw_int(int x, int y, int value) {
501 char buffer[100];
502 snprintf(buffer, sizeof(buffer), "%d", value);
503 draw_string(x, y, buffer);
504}
505
506/*
507** See graphics.h for documentation.
508*/
509/*
510Purpose:
511Date: 21/03/2019
512*/
513void draw_double(int x, int y, double value) {
514 char buffer[100];
515 snprintf(buffer, sizeof(buffer), "%g", value);
516 draw_string(x, y, buffer);
517}
518
519/*
520** See graphics.h for documentation.
521*/
522/*
523Purpose:
524Date: 21/03/2019
525*/
526void draw_formatted(int x, int y, const char * format, ...) {
527 va_list args;
528 va_start(args, format);
529 char buffer[1000];
530 vsnprintf(buffer, sizeof(buffer), format, args);
531 draw_string(x, y, buffer);
532}
533
534/* static */ MEVENT mouse_event;
535
536/*
537** See graphics.h for documentation.
538*/
539/*
540Purpose:
541Date: 21/03/2019
542*/
543int get_char() {
544 int current_char;
545
546 if (zdk_input_stream) {
547 current_char = fgetc(zdk_input_stream);
548 }
549 else {
550 current_char = getch();
551 }
552
553 save_char(current_char);
554
555 if (current_char == KEY_MOUSE) {
556 memset(&mouse_event, 0, sizeof(mouse_event));
557 getmouse(&mouse_event);
558 }
559
560 return current_char;
561}
562
563/*
564** See graphics.h for documentation.
565*/
566/*
567Purpose:
568Date: 21/03/2019
569*/
570int get_mouse_x() {
571 return mouse_event.x;
572}
573
574/*
575** See graphics.h for documentation.
576*/
577/*
578Purpose:
579Date: 21/03/2019
580*/
581int get_mouse_y() {
582 return mouse_event.y;
583}
584
585/*
586** See graphics.h for documentation.
587*/
588unsigned long get_mouse_buttons() {
589 return mouse_event.bstate;
590}
591
592/*
593** See graphics.h for documentation.
594*/
595/*
596Purpose:
597Date: 21/03/2019
598*/
599int wait_char() {
600 int current_char;
601
602 if (zdk_input_stream) {
603 current_char = fgetc(zdk_input_stream);
604 }
605 else {
606 timeout(-1);
607 current_char = getch();
608 timeout(0);
609 }
610
611 save_char(current_char);
612
613 if (current_char == KEY_MOUSE) {
614 memset(&mouse_event, 0, sizeof(mouse_event));
615 getmouse(&mouse_event);
616 }
617
618 return current_char;
619}
620
621/*
622** See graphics.h for documentation.
623*/
624/*
625Purpose:
626Date: 21/03/2019
627*/
628void get_screen_size(int * width, int * height) {
629 *width = screen_width();
630 *height = screen_height();
631}
632
633/*
634** See graphics.h for documentation.
635*/
636/*
637Purpose:
638Date: 21/03/2019
639*/
640int screen_width(void) {
641 return zdk_screen->width;
642}
643
644/*
645** See graphics.h for documentation.
646*/
647/*
648Purpose:
649Date: 21/03/2019
650*/
651int screen_height(void) {
652 return zdk_screen->height;
653}
654
655/*
656** See graphics.h for documentation.
657*/
658/*
659Purpose:
660Date: 21/03/2019
661*/
662void save_screen(const char * file_name) {
663 FILE * f = fopen(file_name, "a");
664 save_screen_(f);
665 fclose(f);
666}
667
668/*
669** See graphics.h for documentation.
670*/
671/*
672Purpose:
673Date: 21/03/2019
674*/
675void save_screen_(FILE * f) {
676 if (f == NULL) return;
677
678 if (zdk_screen) {
679 int width = zdk_screen->width;
680 int height = zdk_screen->height;
681
682 fprintf(f, "Frame(%d,%d,%f)\n", width, height, get_current_time());
683
684 for (int y = 0; y < height; y++) {
685 char * row = zdk_screen->pixels[y];
686
687 for (int x = 0; x < width; x++) {
688 fputc(row[x], f);
689 }
690
691 fputc('\n', f);
692 }
693
694 fprintf(f, "EndFrame\n");
695 }
696}
697
698/**
699 * Saves the current character to an automatically named local file.
700 */
701/*
702Purpose:
703Date: 21/03/2019
704*/
705void save_char(int char_code) {
706 if (zdk_save_stream && char_code != ERR) {
707 fprintf(zdk_save_stream, "Char(%d,%f)\n", char_code, get_current_time());
708 }
709}
710
711/**
712 * This function is provided to support programmatic emulation
713 * of a resized terminal window.
714 * Subsequent calls to screen_width() and screen_height() will
715 * return the supplied values of width and height.
716 */
717/*
718Purpose:
719Date: 21/03/2019
720*/
721void override_screen_size(int width, int height) {
722 void update_buffer(Screen ** buffer, int width, int height, char character, char colour_num);
723
724 update_buffer(&zdk_screen, width, height, ' ', colour_num);
725 update_buffer(&zdk_prev_screen, width, height, ' ', colour_num);
726}
727
728// Private helper function to allocate sccreen buffer.
729static void ** allocate_screen_buffer(int width, int height, char data, size_t element_size);
730
731/**
732 * Private helper function which reallocates and clears the designated buffer.
733 * PRE: buffer ≠ NULL
734 * AND width > 0
735 * AND height > 0.
736 */
737
738/*
739Purpose:
740Date: 21/03/2019
741*/
742void update_buffer(Screen ** screen, int width, int height, char character, char colour_num) {
743 assert(width > 0);
744 assert(height > 0);
745
746 if (screen == NULL) {
747 return;
748 }
749
750 Screen * old_screen = (*screen);
751
752 if (old_screen != NULL && width == old_screen->width && height == old_screen->height) {
753 return;
754 }
755
756 Screen * new_screen = calloc(1, sizeof(Screen));
757
758 if (!new_screen) {
759 *screen = NULL;
760 return;
761 }
762
763 new_screen->width = width;
764 new_screen->height = height;
765
766 new_screen->pixels = (char**)allocate_screen_buffer(width, height, character, sizeof(char));
767
768 if (!new_screen->pixels) {
769 destroy_screen(new_screen);
770 return;
771 }
772
773 new_screen->colours = (int**)allocate_screen_buffer(width, height, colour_num, sizeof(int));
774
775 if (!new_screen->colours) {
776 destroy_screen(new_screen);
777 return;
778 }
779
780 void copy_screen(Screen * old_scr, Screen * new_scr);
781
782 copy_screen(old_screen, new_screen);
783
784 destroy_screen(old_screen);
785
786 (*screen) = new_screen;
787}
788
789/*
790** Creates a table organised as a 2D "array of arrays", having elements of a
791** designated size. This may be used either as a character buffer or a colour
792** info buffer.
793**
794** Input:
795** width, height: the number of columns and rows respectively in the table.
796**
797** default_value: a char code which will be used to initialise all cells
798** in the table.
799**
800** element_size: the number of bytes required for each element.
801**
802** Output:
803** Returns the address of the 2D aray structure.
804*/
805static void ** allocate_screen_buffer(int width, int height, char default_value, size_t element_size) {
806 void ** buffer = calloc(height, sizeof(void *));
807
808 if (!buffer) {
809 return NULL;
810 }
811
812 buffer[0] = calloc(width * height, element_size);
813
814 if (!buffer[0]) {
815 free(buffer);
816 return NULL;
817 }
818
819 for (int y = 1; y < height; y++) {
820 buffer[y] = (char *)buffer[y - 1] + width * element_size;
821 }
822
823 memset(buffer[0], default_value, width * height * element_size);
824 return buffer;
825}
826
827/**
828 * Copies the data from one screen into the bitmap of another,
829 * clipping to ensure that data is only copied in the smallest
830 * rectangle that fits on both screens.
831 *
832 * Input:
833 * src - the address of a Screen from which data is to be copied.
834 * dest - the address of a Screen into which the data is to be copied.
835 *
836 * Output: void.
837 *
838 * Postcondition:
839 * (src is null OR dest is null OR src is dest ) AND nothing happens;
840 * OR:
841 * The contents of the source screen lying in the area that overlaps
842 * with the destination has been copied into dest at the corresponding
843 * position.
844 */
845
846/*
847Purpose:
848Date: 21/03/2019
849*/
850void copy_screen(Screen * src, Screen * dest) {
851 if (src == NULL || dest == NULL || src == dest) return;
852
853 int clip_width = MIN(src->width, dest->width);
854 int clip_height = MIN(src->height, dest->height);
855
856 for (int y = 0; y < clip_height; y++) {
857 memcpy(dest->pixels[y], src->pixels[y], clip_width);
858 memcpy(dest->colours[y], src->colours[y], clip_width * sizeof(int));
859 }
860}
861
862/*
863** This function is provided to support programmatic emulation
864** of a resized terminal window. It undoes the effects of
865** override_screen_size.
866** Subsequent calls to screen_width() and screen_height() will
867** return the width and height of the current terminal window,
868** respectively.
869*/
870/*
871Purpose:
872Date: 21/03/2019
873*/
874void fit_screen_to_window(void) {
875 if (zdk_suppress_output) {
876 override_screen_size(80, 24);
877 }
878 else {
879 override_screen_size(getmaxx(stdscr), getmaxy(stdscr));
880 }
881}
882
883/**
884 * Releases all memory allocated to a given Screen.
885 *
886 * Parameters:
887 * scr - a (possibly null) pointer to a Screen structure.
888 *
889 * Notes: if scr is NULL no action is taken.
890 */
891
892/*
893Purpose:
894Date: 21/03/2019
895*/
896void destroy_screen(Screen * scr) {
897 if (scr) {
898 if (scr->pixels) {
899 if (scr->pixels[0]) {
900 free(scr->pixels[0]);
901 }
902
903 free(scr->pixels);
904 }
905
906 if (scr->colours) {
907 if (scr->colours[0]) {
908 free(scr->colours[0]);
909 }
910
911 free(scr->colours);
912 }
913
914 free(scr);
915 }
916}
917
918/*
919Purpose:
920Date: 21/03/2019
921*/
922void auto_save_screen(bool save_if_true) {
923 if (save_if_true && !zdk_save_stream) {
924 char file_name[100];
925
926 // A somewhat arbitrary upper limit on the number of save files.
927 for (int i = 1; i < 1000000; i++) {
928 sprintf(file_name, "zdk_screen.%d.txt", i);
929 FILE * existing_file = fopen(file_name, "r");
930
931 if (existing_file) {
932 // File exists; leave it alone.
933 fclose(existing_file);
934 }
935 else {
936 // File does not exist; use this name.
937 break;
938 }
939 }
940
941 zdk_save_stream = fopen(file_name, "w");
942 }
943 else if (zdk_save_stream && !save_if_true) {
944 fflush(zdk_save_stream);
945 fclose(zdk_save_stream);
946 zdk_save_stream = NULL;
947 }
948}