├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── bin └── README.md ├── build └── README.md ├── font └── font_ML.ppm ├── output └── README.md ├── src ├── README.md ├── ball.c ├── make_poster.c ├── mandelbrot.c ├── planet.c ├── pulsar.c ├── sinc.c ├── torus.c └── waggle.c ├── texture └── README.md └── thumbs ├── README.md ├── ball.jpg ├── mandelbrot.jpg ├── planet.jpg ├── pulsar.jpg ├── sinc.jpg ├── torus.jpg └── waggle.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | output/*.jpg 2 | output/*.ppm 3 | build/*.ppm 4 | bin/* 5 | texture/* 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Mike Field 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | COPTS=-Wall -pedantic -lm -O4 2 | 3 | all: images thumbs 4 | 5 | images : output/ball.jpg output/mandelbrot.jpg output/planet.jpg output/pulsar.jpg output/sinc.jpg output/torus.jpg output/waggle.jpg 6 | 7 | thumbs : thumbs/ball.jpg thumbs/mandelbrot.jpg thumbs/planet.jpg thumbs/pulsar.jpg thumbs/sinc.jpg thumbs/torus.jpg thumbs/waggle.jpg 8 | 9 | texture/bluemarble.ppm : 10 | # You need to download the blue marble from: 11 | # https://visibleearth.nasa.gov/images/57735/the-blue-marble-land-surface-ocean-color-sea-ice-and-clouds 12 | wget https://eoimages.gsfc.nasa.gov/images/imagerecords/57000/57735/land_ocean_ice_cloud_2048.jpg 13 | jpegtopnm < land_ocean_ice_cloud_2048.jpg > $@ 14 | 15 | output/waggle.jpg : output/waggle.ppm 16 | cat $< | ppmtojpeg > $@ 17 | 18 | thumbs/waggle.jpg : output/waggle.ppm 19 | cat $< | pnmscale 0.10 | ppmtojpeg > $@ 20 | 21 | output/torus.jpg : output/torus.ppm 22 | cat $< | ppmtojpeg > $@ 23 | 24 | thumbs/torus.jpg : output/torus.ppm 25 | cat $< | pnmscale 0.10 | ppmtojpeg > $@ 26 | 27 | output/pulsar.jpg : output/pulsar.ppm 28 | cat $< | ppmtojpeg > output/pulsar.jpg 29 | 30 | thumbs/pulsar.jpg : output/pulsar.ppm 31 | cat $< | pnmscale 0.10 | ppmtojpeg > $@ 32 | 33 | output/sinc.jpg : output/sinc.ppm 34 | cat $< | ppmtojpeg > $@ 35 | 36 | thumbs/sinc.jpg : output/sinc.ppm 37 | cat $< | pnmscale 0.10 | ppmtojpeg > $@ 38 | 39 | output/ball.jpg : output/ball.ppm 40 | cat $< | ppmtojpeg > $@ 41 | 42 | thumbs/ball.jpg : output/ball.ppm 43 | cat $< | pnmscale 0.10 | ppmtojpeg > $@ 44 | 45 | output/mandelbrot.jpg : output/mandelbrot.ppm 46 | cat $< | ppmtojpeg > $@ 47 | 48 | thumbs/mandelbrot.jpg : output/mandelbrot.ppm 49 | cat $< | pnmscale 0.10 | ppmtojpeg > $@ 50 | 51 | output/planet.jpg : output/planet.ppm 52 | cat $< | ppmtojpeg > $@ 53 | 54 | thumbs/planet.jpg : output/planet.ppm 55 | cat $< | pnmscale 0.10 | ppmtojpeg > $@ 56 | 57 | output/waggle.ppm : bin/waggle bin/make_poster 58 | bin/waggle build/img.ppm && bin/make_poster src/waggle.c build/img.ppm output/waggle.ppm 59 | 60 | output/torus.ppm : bin/torus bin/make_poster 61 | bin/torus build/img.ppm && bin/make_poster src/torus.c build/img.ppm output/torus.ppm 62 | 63 | output/pulsar.ppm : bin/pulsar bin/make_poster 64 | bin/pulsar build/img.ppm && bin/make_poster src/pulsar.c build/img.ppm output/pulsar.ppm 65 | 66 | output/sinc.ppm : bin/sinc bin/make_poster 67 | bin/sinc build/img.ppm && bin/make_poster src/sinc.c build/img.ppm output/sinc.ppm 68 | 69 | output/ball.ppm : bin/ball bin/make_poster 70 | bin/ball build/img.ppm && bin/make_poster src/ball.c build/img.ppm output/ball.ppm 71 | 72 | output/mandelbrot.ppm : bin/mandelbrot bin/make_poster 73 | bin/mandelbrot build/img.ppm && bin/make_poster src/mandelbrot.c build/img.ppm output/mandelbrot.ppm 74 | 75 | output/planet.ppm : bin/planet bin/make_poster texture/bluemarble.ppm 76 | bin/planet build/img.ppm && bin/make_poster src/planet.c build/img.ppm output/planet.ppm 77 | 78 | bin/waggle: src/waggle.c 79 | gcc -o $@ $< $(COPTS) 80 | 81 | bin/torus: src/torus.c 82 | gcc -o $@ $< $(COPTS) 83 | 84 | bin/ball: src/ball.c 85 | gcc -o $@ $< $(COPTS) 86 | 87 | bin/sinc: src/sinc.c 88 | gcc -o $@ $< $(COPTS) 89 | 90 | bin/mandelbrot: src/mandelbrot.c 91 | gcc -o $@ $< $(COPTS) 92 | 93 | bin/pulsar: src/pulsar.c 94 | gcc -o $@ $< $(COPTS) 95 | 96 | bin/planet: src/planet.c 97 | gcc -o $@ $< $(COPTS) 98 | 99 | bin/make_poster: src/make_poster.c 100 | gcc -o $@ $< $(COPTS) 101 | 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ProgrammingPosters 2 | C that that make nice posters of short C programs. 3 | 4 | This generates A2 300D DPI images of programs that implement intresting graphics. 5 | 6 | Hopefully these will encouage the viewer to look at the code and see how it runs. 7 | 8 | ## Rules for inclusion 9 | - Must only use standard C libraries 10 | - All code fits on the poster 11 | - Code is sensibly indented 12 | - Reasonable level of error checking 13 | - No external dependancies 14 | 15 | The 'pnmtojpeg' tool, part of netpbm, is used to convert the 'PNM P6' output files to a compressed JPG image. 16 | 17 | (I bent the rules slightly for planet.jpg, allowing it to pull down a texture from NASA) 18 | 19 | ## Known Issues 20 | - the Makefile is a mess 21 | 22 | ## Building 23 | If you have gcc and netpmb installed you should be just able to type 24 | make 25 | The images will be in the output directory 26 | 27 | ## Previews of posters 28 | ![Alt text](thumbs/ball.jpg?raw=true "Title") 29 | ![Alt text](thumbs/waggle.jpg?raw=true "Title") 30 | ![Alt text](thumbs/mandelbrot.jpg?raw=true "Title") 31 | ![Alt text](thumbs/planet.jpg?raw=true "Title") 32 | ![Alt text](thumbs/pulsar.jpg?raw=true "Title") 33 | ![Alt text](thumbs/sinc.jpg?raw=true "Title") 34 | ![Alt text](thumbs/torus.jpg?raw=true "Title") 35 | 36 | -------------------------------------------------------------------------------- /bin/README.md: -------------------------------------------------------------------------------- 1 | # bin - location for binaries 2 | -------------------------------------------------------------------------------- /build/README.md: -------------------------------------------------------------------------------- 1 | ## Build - location for temp files 2 | -------------------------------------------------------------------------------- /font/font_ML.ppm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hamsternz/ProgrammingPosters/cc6b5be4900ef57d5901f37c2c3cb7d2bf318250/font/font_ML.ppm -------------------------------------------------------------------------------- /output/README.md: -------------------------------------------------------------------------------- 1 | output - location for output images 2 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | # src - soruce files 2 | -------------------------------------------------------------------------------- /src/ball.c: -------------------------------------------------------------------------------- 1 | // This code creates the image to the right >> 2 | // 3 | // (c) 2020 Mike Field 4 | 5 | #include 6 | #include 7 | 8 | int main(int argc, char *argv[]) 9 | { 10 | int width = 4660; 11 | int height = 4660; 12 | FILE *file; 13 | 14 | if(argc != 2) { 15 | printf("You need to supply the file name\n"); 16 | return -1; 17 | } 18 | 19 | printf("Creating file %s\n", argv[1]); 20 | 21 | file = fopen(argv[1],"w"); 22 | if(file == NULL) { 23 | printf("Unable to open output image file\n"); 24 | return -1; 25 | } 26 | 27 | fprintf(file, "P6\n"); 28 | fprintf(file, "%i %i\n",width, height); 29 | fprintf(file, "255\n"); 30 | 31 | int i; 32 | for(i = 0; i < width; i++ ) { 33 | int j; 34 | for(j = 0; j < height; j++) { 35 | int y = i - width/2; 36 | int x = j - width/2; 37 | 38 | if(x*x+ y*y < (width/4.0)*(width/4.0)) { 39 | double dx = x/(width/4.0); 40 | double dy = y/(width/4.0); 41 | double dz = sqrt(1.0 - dx*dx - dy*dy); 42 | int light = (dx*0.57-dy*0.57+dz*0.57)*255; 43 | 44 | if(light < 0) { 45 | putc(0, file); // Red 46 | putc(0, file); // Green 47 | putc(0, file); // Blue 48 | } else { 49 | putc(light, file); 50 | putc(light, file); 51 | putc(light, file); 52 | } 53 | } else { 54 | putc(128, file); 55 | putc(128, file); 56 | putc(128, file); 57 | } 58 | } 59 | } 60 | fclose(file); 61 | return 0; 62 | } 63 | -------------------------------------------------------------------------------- /src/make_poster.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | const char *keywords[] = { 7 | "auto", "break", "case", "char", 8 | "const", "continue", "default", "double", 9 | "do", "else", "enum", "extern", 10 | "float", "for", "goto", "if", 11 | "int", "long", "register", "return", 12 | "short", "signed", "sizeof", "static", 13 | "struct", "switch", "typedef", "union", 14 | "unsigned", "void", "volatile", "while", 15 | NULL}; 16 | 17 | struct image { 18 | int width; 19 | int height; 20 | struct image *font; 21 | int x,y; 22 | uint8_t r,g,b; 23 | uint8_t **data; 24 | }; 25 | typedef struct image image_t; 26 | 27 | image_t *image_new(int w, int h) { 28 | image_t *img; 29 | int i; 30 | 31 | img = malloc(sizeof(image_t)); 32 | if(img == NULL) 33 | return NULL; 34 | 35 | img->width = w; 36 | img->height = h; 37 | 38 | img->x = 0; 39 | img->y = 0; 40 | 41 | img->r = 0; 42 | img->g = 0; 43 | img->b = 0; 44 | 45 | img->font = NULL; 46 | img->data = malloc(sizeof(char *)*h); 47 | if(img->data == NULL) { 48 | free(img); 49 | return NULL; 50 | } 51 | 52 | memset(img->data,0,sizeof(char *)*h); 53 | 54 | for(i = 0; i < h; i++) { 55 | img->data[i] = malloc(w*3); 56 | if(img->data[i] == NULL) 57 | break; 58 | memset(img->data[i],0,w*3); 59 | } 60 | if(i != h) { 61 | for(i = 0; i < h; i++) { 62 | if(img->data[i] != NULL) { 63 | free(img->data[i]); 64 | } 65 | } 66 | free(img->data); 67 | free(img); 68 | return NULL; 69 | } 70 | return img; 71 | } 72 | 73 | void image_set_font(image_t *img, image_t *font) { 74 | img->font = font; 75 | } 76 | 77 | void image_set_pos(image_t *img, int x, int y) { 78 | img->x = x; 79 | img->y = y; 80 | } 81 | 82 | void image_set_colour(image_t *img, uint8_t r, uint8_t g, uint8_t b) { 83 | img->r = r; 84 | img->g = g; 85 | img->b = b; 86 | } 87 | 88 | void image_merge(image_t *img, image_t *img2, int x, int y, int w, int h) { 89 | int i,j; 90 | if(w > img2->width) w = img2->width; 91 | if(h > img2->height) h = img2->height; 92 | if(y+h <= 0) return; 93 | if(x+w <= 0) return; 94 | if(x > img->width) return; 95 | if(y > img->height) return; 96 | 97 | if(x < 0) { 98 | w += x; 99 | x = 0; 100 | } 101 | if(x+w > img->width) { 102 | w = img->width -x; 103 | } 104 | if(y+h > img->height) { 105 | h = img->height -y; 106 | } 107 | 108 | if(y < 0) { 109 | h += y; 110 | y = 0; 111 | } 112 | 113 | for(j = 0; j < h; j++) { 114 | for(i = 0; i < w && i < img2->width; i++) { 115 | img->data[j+y][(x+i)*3+0] = img2->data[j][i*3+0]; 116 | img->data[j+y][(x+i)*3+1] = img2->data[j][i*3+1]; 117 | img->data[j+y][(x+i)*3+2] = img2->data[j][i*3+2]; 118 | } 119 | } 120 | } 121 | 122 | void image_rectangle(image_t *img, int x, int y, int w, int h) { 123 | int i,j; 124 | 125 | if(y+h <= 0) return; 126 | if(x+w <= 0) return; 127 | if(x > img->width) return; 128 | if(y > img->height) return; 129 | 130 | if(x < 0) { 131 | w += x; 132 | x = 0; 133 | } 134 | if(x+w > img->width) { 135 | w = img->width -x; 136 | } 137 | if(y+h > img->height) { 138 | h = img->height -y; 139 | } 140 | 141 | if(y < 0) { 142 | h += y; 143 | y = 0; 144 | } 145 | 146 | for(j = 0; j < h; j++) { 147 | for(i = 0; i < w; i++) { 148 | img->data[j+y][(x+i)*3+0] = img->r; 149 | img->data[j+y][(x+i)*3+1] = img->g; 150 | img->data[j+y][(x+i)*3+2] = img->b; 151 | } 152 | } 153 | } 154 | 155 | int image_write(image_t *img, char *fname) { 156 | int i; 157 | FILE *f; 158 | 159 | if(img == NULL) { 160 | return 0; 161 | } 162 | 163 | if(img->data == NULL) { 164 | return 0; 165 | } 166 | 167 | for(i = 0;i < img->height; i++) { 168 | if(img->data[i] == NULL) { 169 | return 0; 170 | } 171 | } 172 | 173 | f = fopen(fname,"w"); 174 | if(f == NULL) { 175 | return 0; 176 | } 177 | 178 | fprintf(f,"P6\n%i %i\n255\n", img->width, img->height); 179 | for(i = 0;i < img->height; i++) { 180 | if(fwrite(img->data[i],3,img->width,f) != img->width) { 181 | fclose(f); 182 | return 0; 183 | } 184 | } 185 | 186 | fclose(f); 187 | return 1; 188 | } 189 | void image_free(image_t *img) { 190 | int i; 191 | if(img->data != NULL) { 192 | for(i = 0; i < img->height; i++) { 193 | if(img->data[i] != NULL) { 194 | free(img->data[i]); 195 | } 196 | } 197 | free(img->data); 198 | } 199 | } 200 | 201 | static int whitespace(char c) { 202 | if(c == ' ') return 1; 203 | if(c == '\t') return 1; 204 | if(c == '\r') return 1; 205 | if(c == '\n') return 1; 206 | return 0; 207 | } 208 | 209 | static int digit(char c) { 210 | return (c >= '0' && c <= '9'); 211 | } 212 | 213 | image_t *image_from_ppm(char *file_name) { 214 | FILE *file; 215 | image_t *img; 216 | int c, last_c = 0; 217 | int width = 0, height = 0, maxval = 0; 218 | 219 | file = fopen(file_name, "r"); 220 | if(file == NULL) { 221 | fprintf(stderr,"File %s not able to be opened\n", file_name); 222 | return 0; 223 | } 224 | 225 | c = getc(file); 226 | if(c != 'P') { 227 | goto format_error; 228 | } 229 | 230 | c = getc(file); 231 | if(c != '6') { 232 | goto format_error; 233 | } 234 | 235 | c = getc(file); 236 | while(whitespace(c)) { 237 | last_c = c; 238 | c = getc(file); 239 | if(c == '#' && last_c == '\n') { 240 | c = getc(file); 241 | while(c != '\n' && c != EOF) { 242 | c = getc(file); 243 | } 244 | } 245 | } 246 | 247 | if(!digit(c)) { 248 | goto format_error; 249 | } 250 | 251 | while(digit(c)) { 252 | width = (width*10)+(c-'0'); 253 | c = getc(file); 254 | } 255 | 256 | while(whitespace(c)) { 257 | last_c = c; 258 | c = getc(file); 259 | if(c == '#' && last_c == '\n') { 260 | c = getc(file); 261 | while(c != '\n' && c != EOF) { 262 | c = getc(file); 263 | } 264 | } 265 | } 266 | 267 | if(!digit(c)) { 268 | goto format_error; 269 | } 270 | 271 | while(digit(c)) { 272 | height = (height*10)+(c-'0'); 273 | c = getc(file); 274 | } 275 | 276 | while(whitespace(c)) { 277 | last_c = c; 278 | c = getc(file); 279 | if(c == '#' && last_c == '\n') { 280 | c = getc(file); 281 | while(c != '\n' && c != EOF) { 282 | c = getc(file); 283 | } 284 | } 285 | } 286 | 287 | if(!digit(c)) { 288 | goto format_error; 289 | } 290 | 291 | while(digit(c)) { 292 | maxval = (maxval*10)+(c-'0'); 293 | c = getc(file); 294 | } 295 | 296 | if(!whitespace(c)) { 297 | goto format_error; 298 | } 299 | 300 | if(maxval != 255) { 301 | goto format_error; 302 | } 303 | img = image_new(width,height); 304 | 305 | if(img == NULL) { 306 | goto img_error; 307 | } 308 | for(int i = 0; i < height; i++) { 309 | if(fread(img->data[i],3, width,file) != width) { 310 | goto read_error; 311 | } 312 | } 313 | fclose(file); 314 | return img; 315 | 316 | read_error: 317 | image_free(img); 318 | fprintf(stderr,"Error reading data in %s\n", file_name); 319 | fclose(file); 320 | return 0; 321 | 322 | img_error: 323 | fprintf(stderr,"Unable to create image %s\n", file_name); 324 | fclose(file); 325 | return NULL; 326 | 327 | format_error: 328 | fprintf(stderr,"File format error\n"); 329 | fclose(file); 330 | return 0; 331 | } 332 | 333 | int char_write(image_t *img, char c) { 334 | int char_width, char_height; 335 | int fx, fy, dx, dy; 336 | 337 | if(img->font == NULL) { 338 | return 1; 339 | } 340 | char_width = img->font->width/16; 341 | char_height = img->font->height/6; 342 | // Test to see if off the page // 343 | if(img->y >= img->height || img->x >= img->width || img->y <= -char_height || img->x <= -char_width) { 344 | return 1; 345 | } 346 | if(c < 32) c = ' '; 347 | if(c >= 127) c = ' '; 348 | 349 | c -=32; 350 | fx = (c%16)*char_width; 351 | fy = (c/16)*char_height; 352 | 353 | for(dy = 0; dy < img->font->height/6 && img->y+dy < img->height; dy++) { 354 | if(img->y+dy >= 0) { 355 | int copy_w = char_width; 356 | int offset = 0; 357 | 358 | if(img->x+copy_w > img->width) { 359 | copy_w = img->width - img->x; 360 | } 361 | if(img->x < 0) { 362 | offset = -img->x; 363 | copy_w -= offset; 364 | } 365 | for(dx = 0; dx < copy_w; dx++) { 366 | uint8_t mix_r = img->font->data[fy+dy][3*(fx+offset+dx)+0]; 367 | uint8_t mix_g = img->font->data[fy+dy][3*(fx+offset+dx)+1]; 368 | uint8_t mix_b = img->font->data[fy+dy][3*(fx+offset+dx)+2]; 369 | int tx = img->x+offset+dx; 370 | img->data[img->y+dy][3*tx+0] = (img->data[img->y+dy][3*tx+0] * mix_r + img->r * (255-mix_r))/255; 371 | img->data[img->y+dy][3*tx+1] = (img->data[img->y+dy][3*tx+1] * mix_g + img->g * (255-mix_g))/255; 372 | img->data[img->y+dy][3*tx+2] = (img->data[img->y+dy][3*tx+2] * mix_b + img->b * (255-mix_b))/255; 373 | } 374 | } 375 | } 376 | return 1; 377 | } 378 | 379 | int syntax_highlight(char *text, char *colour_table) { 380 | while(*text) { 381 | if((*text >= 'a' && *text <= 'z') || (*text >= 'A' && *text <= 'Z') || *text == '_') { 382 | int i; 383 | for(i = 0; keywords[i] != NULL; i++) { 384 | int len = strlen(keywords[i]); 385 | if(memcmp(keywords[i], text, len) == 0) 386 | if(text[len] <= 'a' || text[len] >= 'z') 387 | break; 388 | } 389 | if( keywords[i] == NULL) { 390 | while((*text >= 'a' && *text <= 'z') || (*text >= 'A' && *text <= 'Z') || *text == '_' || (*text >= '0' && *text <= '9')) { 391 | *colour_table = 2; 392 | text++; 393 | colour_table++; 394 | } 395 | } else { 396 | int len = strlen(keywords[i]); 397 | memset(colour_table,4,len); 398 | text+= len; 399 | colour_table+=len; 400 | } 401 | } else if(*text >= '0' && *text <= '9') { 402 | while((*text >= '0' && *text <= '9') || *text == '.') { 403 | *colour_table = 6; 404 | text++; 405 | colour_table++; 406 | } 407 | } else { 408 | switch(*text) { 409 | case '"': 410 | *colour_table = 3; 411 | text++; 412 | colour_table++; 413 | while(*text != '"' && *text != '\0') { 414 | *colour_table = 3; 415 | text++; 416 | colour_table++; 417 | } 418 | if(*text == '"') { 419 | *colour_table = 3; 420 | text++; 421 | colour_table++; 422 | } 423 | break; 424 | 425 | case '/': 426 | if(text[1] == '/') { 427 | while(*text != '\n' && *text != '\0') { 428 | *colour_table = 5; 429 | text++; 430 | colour_table++; 431 | } 432 | } else { 433 | *colour_table = 0; 434 | text++; 435 | colour_table++; 436 | } 437 | break; 438 | case '#': 439 | while(*text != '\n' && *text != '\0') { 440 | *colour_table = 1; 441 | text++; 442 | colour_table++; 443 | } 444 | break; 445 | default: 446 | text++; colour_table++; 447 | break; 448 | } 449 | 450 | } 451 | } 452 | return 0; 453 | } 454 | int image_text(image_t *img, int x, int y, char *text, char *colour_table) { 455 | int cur_x = x; 456 | if(img->font == NULL) { 457 | return 0; 458 | } 459 | while(*text) { 460 | if(*text == '\n') { 461 | cur_x = x; 462 | y += img->font->height/6; 463 | } else { 464 | switch(colour_table[0]) { 465 | case 0: image_set_colour(img, 0, 0, 0); break; // Unknown 466 | case 1: image_set_colour(img, 128, 0, 0); break; // #defines 467 | case 2: image_set_colour(img, 0, 128, 0); break; // Keywords 468 | case 3: image_set_colour(img, 0, 0, 128); break; // Text 469 | case 4: image_set_colour(img, 0, 0, 0); break; // Misc text 470 | case 5: image_set_colour(img, 0, 192, 192); break; // Comments 471 | case 6: image_set_colour(img, 192, 0, 192); break; // Numbers 472 | default: image_set_colour(img, 0, 0, 0); break; 473 | } 474 | image_set_pos(img, cur_x, y); 475 | char_write(img, *text); 476 | cur_x += img->font->width/16; 477 | } 478 | text++; 479 | colour_table++; 480 | } 481 | return 1; 482 | } 483 | 484 | 485 | int main(int argc, char *argv[]) 486 | { 487 | char buffer[16*1024]; 488 | char colour_table[16*1024]; 489 | int width = 7016; 490 | int height = 4960; 491 | int boarder = 150; 492 | int lines = 0; 493 | if(argc != 4) { 494 | fprintf(stderr,"Usage: %s code_file.c picture_file.ppm output_file.ppm",argv[0]); 495 | return 0; 496 | } 497 | FILE *f = fopen(argv[1],"r"); 498 | int n; 499 | if(f == NULL) { 500 | fprintf(stderr, "Unable to open source\n"); 501 | return 0; 502 | } 503 | n = fread(buffer,1,sizeof(buffer)-1,f); 504 | if(n < 1) { 505 | fprintf(stderr, "Unable to read source\n"); 506 | fclose(f); 507 | return 0; 508 | } 509 | buffer[n] = 0; 510 | memset(colour_table,0,n); 511 | syntax_highlight(buffer, colour_table); 512 | 513 | for(int i = 0; buffer[i]; i++) { 514 | if(buffer[i] == '\n') 515 | lines++; 516 | } 517 | 518 | fclose(f); 519 | image_t *font = image_from_ppm("font/font_ML.ppm"); 520 | image_t *layout; 521 | if(font == NULL) { 522 | fprintf(stderr, "Unable to read font\n"); 523 | return 0; 524 | } 525 | printf("Font loaded - %i x %i characters\n", font->width/16, font->height/6); 526 | 527 | layout = image_new(width, height); 528 | if(layout == NULL) { 529 | image_free(font); 530 | return 0; 531 | } 532 | 533 | image_set_colour(layout, 64, 64, 64); 534 | image_rectangle(layout, 0, 0, width, height); 535 | 536 | image_set_colour(layout, 255, 255, 255); 537 | image_rectangle(layout, boarder, boarder, width-height-boarder, height-boarder*2); 538 | 539 | image_set_colour(layout, 192,192,192); 540 | image_rectangle(layout, width-boarder-(height-boarder*2), boarder, height-boarder*2, height-boarder*2); 541 | 542 | // Black text 543 | image_set_colour(layout, 0, 0, 0); 544 | image_set_font(layout, font); 545 | image_text(layout, 3*boarder/2, height/2-(lines*font->height/6)/2, buffer, colour_table); 546 | 547 | image_t *art = image_from_ppm(argv[2]); 548 | if(art == NULL) { 549 | fprintf(stderr, "Unable to open merge image\n"); 550 | } else { 551 | if(art->width != height-boarder*2 || art->height != height-boarder*2) { 552 | printf("Image should be %i x %i\n", height-boarder*2, height-boarder*2); 553 | } 554 | image_merge(layout, art, width-boarder-(height-boarder*2), boarder, height-boarder*2, height-boarder*2); 555 | image_free(art); 556 | } 557 | 558 | if(!image_write(layout, argv[3])) { 559 | fprintf(stderr,"Unable to write file\n"); 560 | } 561 | image_free(layout); 562 | image_free(font); 563 | } 564 | -------------------------------------------------------------------------------- /src/mandelbrot.c: -------------------------------------------------------------------------------- 1 | // This code creates the image to the right >> 2 | // 3 | // (c) 2020 Mike Field 4 | 5 | #include 6 | 7 | const int max_loops = 1024; 8 | 9 | double center_r = -0.83; 10 | double center_i = 0.19; 11 | double scale = 0.01; 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | int width = 4660; 16 | int height = 4660; 17 | FILE *file; 18 | 19 | if(argc != 2) { 20 | printf("You need to supply the file name\n"); 21 | return -1; 22 | } 23 | 24 | printf("Creating file %s\n", argv[1]); 25 | 26 | file = fopen(argv[1],"w"); 27 | if(file == NULL) { 28 | printf("Unable to open output image file\n"); 29 | return -1; 30 | } 31 | 32 | fprintf(file, "P6\n"); 33 | fprintf(file, "%i %i\n",width, height); 34 | fprintf(file, "255\n"); 35 | 36 | int i; 37 | for(i = 0; i < height; i++ ) { 38 | int j; 39 | for(j = 0; j < width; j++) { 40 | double c_r = center_r + scale/width*(j - width/2); 41 | double c_i = center_i + scale/width*(i - height/2); 42 | double a_r = 0.0; 43 | 44 | double a_i = 0.0; 45 | int loops = 1; 46 | while((a_r*a_r+a_i*a_i < 4.0) && loops < max_loops) { 47 | double t_r = (a_r*a_r - a_i * a_i); 48 | double t_i = (2 * a_r * a_i); 49 | a_r = t_r + c_r; 50 | a_i = t_i + c_i; 51 | loops++; 52 | } 53 | if(loops == max_loops) 54 | loops = 0; 55 | 56 | putc(loops, file); 57 | putc(loops << 3, file); 58 | putc(loops << 6, file); 59 | } 60 | } 61 | fclose(file); 62 | return 0; 63 | } 64 | -------------------------------------------------------------------------------- /src/planet.c: -------------------------------------------------------------------------------- 1 | // This code creates the image to the right 2 | // 3 | // (c) 2020 Mike Field 4 | // 5 | // Blue Marble data from NASA: 6 | // https://visibleearth.nasa.gov/collection/1484/blue-marble 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #define TEXTURE_WIDTH 2048 13 | #define TEXTURE_HEIGHT 1024 14 | static unsigned char marble[TEXTURE_HEIGHT][TEXTURE_WIDTH][3]; 15 | 16 | int main(int argc, char *argv[]) 17 | { 18 | int width = 4660; 19 | int height = 4660; 20 | FILE *file; 21 | 22 | if(argc != 2) { 23 | printf("You need to supply the output file name.\n"); 24 | return -1; 25 | } 26 | printf("Creating file %s\n", argv[1]); 27 | 28 | file = fopen("texture/bluemarble.ppm", "r"); 29 | if(file == NULL) { 30 | printf("Unable to open texture file\n"); 31 | return -1; 32 | } 33 | fseek(file,17,SEEK_SET); 34 | if(fread(marble,sizeof(marble),1,file) != 1) { 35 | printf("Unable to read texture file\n"); 36 | return -1; 37 | } 38 | fclose(file); 39 | 40 | file = fopen(argv[1],"w"); 41 | if(file == NULL) { 42 | printf("Unable to open output file\n"); 43 | return -1; 44 | } 45 | 46 | fprintf(file, "P6\n%i %i\n255\n",width, height); 47 | 48 | int i; 49 | for(i = 0; i < width; i++ ) { 50 | int j; 51 | for(j = 0; j < height; j++) { 52 | int y = i - width/2; 53 | int x = j - width/2; 54 | 55 | if(x*x+ y*y < height/4*height/4) { 56 | double dx = x/(height/4.0); 57 | double dy = y/(height/4.0); 58 | double dz = sqrt(1.0 - dx*dx - dy*dy); 59 | 60 | double longitude = -atan2(dz,dx)/(M_PI*2)+0.5; 61 | double latitude = atan2(dy,sqrt(dx*dx+dz*dz))/(M_PI)+0.5; 62 | 63 | double light = ( dx*0.806 + dy*0.000 + dz*0.500); 64 | if(light < 0) light = 0; 65 | 66 | if(latitude < 0) latitude = 0.0; 67 | if(longitude < 0) longitude = 0.0; 68 | if(latitude >= 1.0) latitude = 0.0; 69 | if(longitude >= 1.0) longitude = 0.0; 70 | 71 | int r = marble[(int)(latitude*1024)][(int)(longitude*2048)][0]; 72 | int g = marble[(int)(latitude*1024)][(int)(longitude*2048)][1]; 73 | int b = marble[(int)(latitude*1024)][(int)(longitude*2048)][2]; 74 | 75 | r *= light; 76 | g *= light; 77 | b *= light; 78 | 79 | putc(r, file); // Red 80 | putc(g, file); // Green 81 | putc(b, file); // Blue 82 | } else { 83 | if(rand() < RAND_MAX/(width/100)) { 84 | int star = (float)rand()*rand()*rand(); 85 | star /= 255.0*RAND_MAX/RAND_MAX/RAND_MAX; 86 | putc(star, file); // Red 87 | putc(star, file); // Green 88 | putc(star, file); // Blue 89 | } else { 90 | putc(2, file); // Red 91 | putc(2, file); // Green 92 | putc(2, file); // Blue 93 | } 94 | } 95 | } 96 | } 97 | fclose(file); 98 | return 0; 99 | } 100 | -------------------------------------------------------------------------------- /src/pulsar.c: -------------------------------------------------------------------------------- 1 | // This code creates the image to the right >> 2 | // 3 | // (c) 2020 Mike Field 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #define SIDE 4660 10 | #define TRACES 40 11 | #define DIV (TRACES+20) 12 | #define GAP (SIDE/DIV) 13 | #define SEGMENTS 40 14 | 15 | unsigned char p[SIDE][SIDE]; 16 | 17 | double line[SEGMENTS+1]; 18 | 19 | int main(int argc, char *argv[]) 20 | { 21 | int trace, x, y; 22 | 23 | if(argc != 2) { 24 | printf("Please supply output file name\n"); 25 | return -1; 26 | } 27 | 28 | printf("Creating file %s\n", argv[1]); 29 | FILE *file = fopen(argv[1],"w"); 30 | if(file == NULL) { 31 | printf("Unable to open output image file\n"); 32 | return -1; 33 | } 34 | 35 | fprintf(file, "P6\n%i %i\n255\n", SIDE, SIDE); 36 | 37 | for(trace = 0; trace <= TRACES; trace++) { 38 | int baseline = (trace+(DIV-TRACES)/2)*GAP; 39 | int points[SIDE/2]; 40 | 41 | for(x = 0; x < SEGMENTS+1; x++) { 42 | if(x <= 4*SEGMENTS/11 || x >= 7*SEGMENTS/11) 43 | line[x] = (SIDE/200.0)*rand()/RAND_MAX; 44 | else if(x > 5*SEGMENTS/11 && x <= 6*SEGMENTS/11) 45 | line[x] = (SIDE/40.0)*rand()/RAND_MAX+(SIDE/30); 46 | else 47 | line[x] = (SIDE/30.0)*rand()/RAND_MAX+(SIDE/50); 48 | } 49 | 50 | for(x = 0; x < SIDE/2; x++) { 51 | int a = (SEGMENTS*x)/(SIDE/2); 52 | int b = (SEGMENTS*x)%(SIDE/2); 53 | points[x] = baseline - (line[a]*(SIDE/2-b)+line[a+1]*(b))/(SIDE/2); 54 | } 55 | 56 | for(x = 0; x < SIDE/2; x++) { 57 | for(y=points[x]+3; y < SIDE; y++) 58 | for(int j = -2; j <=2; j++) 59 | p[y][x+SIDE/4+j] = 0; 60 | } 61 | 62 | for(x = 0; x < SIDE/2; x++) { 63 | for(int i = -2; i <=2; i++) 64 | for(int j = -2; j <=2; j++) 65 | p[points[x]+0+i][x+SIDE/4+0+j] = 255; 66 | } 67 | } 68 | 69 | for(y = 0; y < SIDE; y++ ) { 70 | for(x = 0; x < SIDE; x++) { 71 | putc(p[y][x], file); 72 | putc(p[y][x], file); 73 | putc(p[y][x], file); 74 | } 75 | } 76 | fclose(file); 77 | return 0; 78 | } 79 | -------------------------------------------------------------------------------- /src/sinc.c: -------------------------------------------------------------------------------- 1 | // This code creates the image to the right >> 2 | // 3 | // (c) 2020 Mike Field 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #define SIDE 4660 10 | #define TRACES 40 11 | #define DIV (TRACES+20) 12 | #define GAP (SIDE/DIV) 13 | #define SEGMENTS 40 14 | 15 | unsigned char p[SIDE][SIDE]; 16 | 17 | double line[SEGMENTS+1]; 18 | 19 | int main(int argc, char *argv[]) 20 | { 21 | int trace, x, y; 22 | 23 | if(argc != 2) { 24 | printf("Please supply output file name\n"); 25 | return -1; 26 | } 27 | 28 | printf("Creating file %s\n", argv[1]); 29 | FILE *file = fopen(argv[1],"w"); 30 | if(file == NULL) { 31 | printf("Unable to open output image file\n"); 32 | return -1; 33 | } 34 | 35 | fprintf(file, "P6\n%i %i\n255\n", SIDE, SIDE); 36 | 37 | for(trace = 0; trace <= TRACES; trace++) { 38 | int baseline = (trace+(DIV-TRACES)/2)*GAP; 39 | int points[SIDE/2]; 40 | 41 | for(x = 0; x < SIDE/2; x++) { 42 | double dx = (x-SIDE/4.0)/(SIDE/2.0)*25.0; 43 | double dy = (trace-TRACES/2.0)/(TRACES/2.0)*20.0; 44 | double d = sqrt(dx*dx+dy*dy+0.001); 45 | points[x] = baseline - sin(d)/d*SIDE/10.0; 46 | } 47 | 48 | for(x = 0; x < SIDE/2; x++) { 49 | for(y=points[x]+3; y < SIDE; y++) 50 | for(int j = -2; j <=2; j++) 51 | p[y][x+SIDE/4+j] = 0; 52 | } 53 | 54 | for(x = 0; x < SIDE/2; x++) { 55 | for(int i = -2; i <=2; i++) 56 | for(int j = -2; j <=2; j++) 57 | p[points[x]+0+i][x+SIDE/4+0+j] = 255; 58 | } 59 | } 60 | 61 | for(y = 0; y < SIDE; y++ ) { 62 | for(x = 0; x < SIDE; x++) { 63 | putc(p[y][x], file); 64 | putc(p[y][x], file); 65 | putc(p[y][x], file); 66 | } 67 | } 68 | fclose(file); 69 | return 0; 70 | } 71 | -------------------------------------------------------------------------------- /src/torus.c: -------------------------------------------------------------------------------- 1 | // This code creates the image to the right >> 2 | // 3 | // (c) 2021 Mike Field 4 | // Based on work by Zodiam on Discord 5 | 6 | #include 7 | #include 8 | 9 | #define WIDTH 4660 10 | #define HEIGHT 4660 11 | 12 | unsigned char frame[HEIGHT][WIDTH][3] = { 0 }; 13 | double z_buffer[HEIGHT][WIDTH] = { 0 }; 14 | 15 | const double A = -0.5, B = 0.4; // Angles of torus 16 | const double R1 = 0.4, R2 = 1.0; // Torus parameters 17 | const double depth = 5.0; // Depth offset 18 | 19 | typedef struct { 20 | double x, y, z; 21 | } Point ; 22 | 23 | typedef struct { 24 | double xx, xy, xz; 25 | double yx, yy, yz; 26 | double zx, zy, zz; 27 | } Rotation ; 28 | 29 | void rotate(Point *out, Point *in, Rotation *r ) { 30 | out->x = in->x * r->xx + in->y * r->xy + in->z * r->xz; 31 | out->y = in->x * r->yx + in->y * r->yy + in->z * r->yz; 32 | out->z = in->x * r->zx + in->y * r->zy + in->z * r->zz; 33 | } 34 | 35 | int main(int argc, char *argv[]) { 36 | if(argc != 2) { 37 | fprintf(stderr, "No file name supplied\n"); 38 | return 1; 39 | } 40 | 41 | for ( int j = 0; j < HEIGHT; j++ ) 42 | for ( int i = 0; i < WIDTH; i++ ) 43 | z_buffer[ j ][ i ] = -10.0; 44 | 45 | Rotation rz = { cos(B), sin(B), 0, 46 | -sin(B), cos(B), 0, 47 | 0, 0, 1.0}; 48 | Rotation rx = { 1.0, 0, 0, 49 | 0, cos(A), sin(A), 50 | 0, -sin(A), cos(A)}; 51 | 52 | for ( int i = 0.0; i < WIDTH; i++) { 53 | for (int j = 0.0; j < WIDTH*2; j++) { 54 | Point temp1, temp2, rotated, normal; 55 | double theta = i*2*M_PI/WIDTH, phi = j*M_PI/WIDTH; 56 | Point circle = {R2+R1*cos(theta), R1*sin(theta), 0}; 57 | Point fornormal = { cos(theta), sin(theta), 0}; 58 | 59 | Rotation ry = { cos(phi), 0.0, sin(phi), 60 | 0.0, 1.0, 0.0, 61 | -sin(phi), 0.0, cos(phi)}; 62 | 63 | rotate(&temp1, &circle, &ry); 64 | rotate(&temp2, &temp1, &rx); 65 | rotate(&rotated, &temp2, &rz); 66 | rotated.z -= depth; 67 | 68 | int xp = (int) (rotated.x/rotated.z *WIDTH) + WIDTH/2; 69 | int yp = (int) (rotated.y/rotated.z *WIDTH) + HEIGHT/2; 70 | if(yp < 0 || yp > HEIGHT -1 || xp < 0 || xp > WIDTH-1) 71 | continue; 72 | 73 | rotate(&temp1, &fornormal, &ry); 74 | rotate(&temp2, &temp1, &rx); 75 | rotate(&normal, &temp2, &rz); 76 | if ( rotated.z > z_buffer[ yp ][ xp ] ){ 77 | double luminance = (normal.y-normal.x)/sqrt(2.0); 78 | frame[yp][xp][0] = (luminance > 0 ? luminance*220+32 : 32); 79 | luminance = normal.y; 80 | frame[yp][xp][1] = (luminance > 0 ? luminance*220+32 : 32); 81 | luminance = (normal.y+normal.x)/sqrt(2.0); 82 | frame[yp][xp][2] = (luminance > 0 ? luminance*220+32 : 32); 83 | z_buffer[ yp ][ xp ] = rotated.z; 84 | } 85 | } 86 | } 87 | 88 | FILE *f = fopen(argv[1],"wb"); 89 | if(f != NULL) { 90 | fprintf(f, "P6\n%i %i\n255\n", WIDTH, HEIGHT); 91 | fwrite(frame, sizeof(frame), 1, f); 92 | fclose(f); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/waggle.c: -------------------------------------------------------------------------------- 1 | // This code creates the image to the right >> 2 | // 3 | // (c) 2021 Mike Field 4 | // Inspired by Zodian on Discord 5 | // 6 | #include 7 | #include 8 | 9 | #define WIDTH 4660 10 | #define HEIGHT 4660 11 | 12 | // Frequencies 13 | const float f1 = 2.01; 14 | const float f2 = 3.00; 15 | const float f3 = 3.01; 16 | const float f4 = 1.00; 17 | // Damp Force 18 | const float d1 = 0.0001; 19 | const float d2 = 0.0001; 20 | const float d3 = 0.001; 21 | const float d4 = 0.0001; 22 | // Phases 23 | const float p1 = 0; 24 | const float p2 = 4 * M_PI / 16; 25 | const float p3 = 0; 26 | const float p4 = 0; 27 | 28 | const float amp = HEIGHT/5.0; 29 | 30 | unsigned char pixels[HEIGHT][WIDTH]; 31 | 32 | int main(int argc, char *argv[]){ 33 | if(argc != 2) { 34 | fprintf(stderr,"Please supply image name\n"); 35 | return 0; 36 | } 37 | 38 | 39 | int x, y; 40 | float t = 0; 41 | float dt = M_PI/(WIDTH+HEIGHT); 42 | int iterations = 100*(WIDTH+HEIGHT); 43 | 44 | for ( int i = 0; i < iterations; i++ ){ 45 | x = amp * sin(f1*t+p1) * pow(M_E,(-t*d1)) + 46 | amp * sin(f2*t+p2) * pow(M_E,(-t*d2)); 47 | y = amp * sin(f3*t+p3) * pow(M_E,(-t*d3)) + 48 | amp * sin(f4*t+p4) * pow(M_E,(-t*d4)); 49 | 50 | x = ( int ) x + ( WIDTH / 2 ); 51 | y = ( int ) y + ( HEIGHT / 2 ); 52 | if(x > 1 && y > 1 && y < HEIGHT-1 && x < WIDTH-1) { 53 | pixels[y+1][x-1] = 1+i*254/iterations; 54 | pixels[y+0][x-1] = 1+i*254/iterations; 55 | pixels[y-1][x-1] = 1+i*254/iterations; 56 | pixels[y+1][x+0] = 1+i*254/iterations; 57 | pixels[y+0][x+0] = 1+i*254/iterations; 58 | pixels[y-1][x+0] = 1+i*254/iterations; 59 | pixels[y+1][x+1] = 1+i*254/iterations; 60 | pixels[y+0][x+1] = 1+i*254/iterations; 61 | pixels[y-1][x+1] = 1+i*254/iterations; 62 | } 63 | t += dt; 64 | } 65 | 66 | FILE *f = fopen(argv[1], "wb"); 67 | if(f != NULL) { 68 | fprintf(f,"P6\n%i %i\n255\n",HEIGHT,WIDTH); 69 | for(y = 0; y < HEIGHT; y++) { 70 | for(x = 0; x < WIDTH; x++) { 71 | if(pixels[y][x]>0) { 72 | putc(128+pixels[y][x]/2,f); 73 | putc(128-pixels[y][x]/2,f); 74 | putc(255-pixels[y][x]/2,f); 75 | } else { 76 | putc(0,f); 77 | putc(0,f); 78 | putc(0,f); 79 | } 80 | } 81 | } 82 | fclose(f); 83 | } 84 | return 0; 85 | } 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /texture/README.md: -------------------------------------------------------------------------------- 1 | # Texture are stored here 2 | -------------------------------------------------------------------------------- /thumbs/README.md: -------------------------------------------------------------------------------- 1 | # thumbs - thumbnail images of posters 2 | -------------------------------------------------------------------------------- /thumbs/ball.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hamsternz/ProgrammingPosters/cc6b5be4900ef57d5901f37c2c3cb7d2bf318250/thumbs/ball.jpg -------------------------------------------------------------------------------- /thumbs/mandelbrot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hamsternz/ProgrammingPosters/cc6b5be4900ef57d5901f37c2c3cb7d2bf318250/thumbs/mandelbrot.jpg -------------------------------------------------------------------------------- /thumbs/planet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hamsternz/ProgrammingPosters/cc6b5be4900ef57d5901f37c2c3cb7d2bf318250/thumbs/planet.jpg -------------------------------------------------------------------------------- /thumbs/pulsar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hamsternz/ProgrammingPosters/cc6b5be4900ef57d5901f37c2c3cb7d2bf318250/thumbs/pulsar.jpg -------------------------------------------------------------------------------- /thumbs/sinc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hamsternz/ProgrammingPosters/cc6b5be4900ef57d5901f37c2c3cb7d2bf318250/thumbs/sinc.jpg -------------------------------------------------------------------------------- /thumbs/torus.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hamsternz/ProgrammingPosters/cc6b5be4900ef57d5901f37c2c3cb7d2bf318250/thumbs/torus.jpg -------------------------------------------------------------------------------- /thumbs/waggle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hamsternz/ProgrammingPosters/cc6b5be4900ef57d5901f37c2c3cb7d2bf318250/thumbs/waggle.jpg --------------------------------------------------------------------------------