fbui | Framebuffer-based graphical environment |
| download: https://git.y1.nz/archives/fbui.tar.gz | |
| README | Files | Log | Refs |
libfbui/MPEG/store.c
1 /* store.c, picture output routines */
2
3 /* Copyright (C) 1996, MPEG Software Simulation Group. All Rights Reserved. */
4
5 /*
6 * Disclaimer of Warranty
7 *
8 * These software programs are available to the user without any license fee or
9 * royalty on an "as is" basis. The MPEG Software Simulation Group disclaims
10 * any and all warranties, whether express, implied, or statuary, including any
11 * implied warranties or merchantability or of fitness for a particular
12 * purpose. In no event shall the copyright-holder be liable for any
13 * incidental, punitive, or consequential damages of any kind whatsoever
14 * arising from the use of these programs.
15 *
16 * This disclaimer of warranty extends to the user of these programs and user's
17 * customers, employees, agents, transferees, successors, and assigns.
18 *
19 * The MPEG Software Simulation Group does not represent or warrant that the
20 * programs furnished hereunder are free of infringement of any third-party
21 * patents.
22 *
23 * Commercial implementations of MPEG-1 and MPEG-2 video, including shareware,
24 * are subject to royalty fees to patent holders. Many of these patents are
25 * general enough such that they are unavoidable regardless of implementation
26 * design.
27 *
28 */
29
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <fcntl.h>
33 #include <time.h>
34
35 #include "config.h"
36 #include "global.h"
37
38 #include "../libfbui.h"
39
40 #ifndef DISPLAY /* if not X11 */
41 static Display *fbui_display = NULL;
42 static Window *fbui_window = NULL;
43
44 extern int fbui_console;
45
46 static time_t t = 0;
47 static int frame_counter = 0;
48
49 void shutdown_fbui () {
50 if (fbui_display)
51 fbui_display_close (fbui_display);
52 }
53 #endif
54
55 /* private prototypes */
56 static void store_one _ANSI_ARGS_((char *outname, unsigned char *src[],
57 int offset, int incr, int height));
58 static void store_yuv _ANSI_ARGS_((char *outname, unsigned char *src[],
59 int offset, int incr, int height));
60 static void store_sif _ANSI_ARGS_((char *outname, unsigned char *src[],
61 int offset, int incr, int height));
62 static void store_fbui _ANSI_ARGS_((char *outname, unsigned char *src[],
63 int offset, int incr, int height, int tgaflag));
64 static void store_ppm_tga _ANSI_ARGS_((char *outname, unsigned char *src[],
65 int offset, int incr, int height, int tgaflag));
66 static void store_yuv1 _ANSI_ARGS_((char *name, unsigned char *src,
67 int offset, int incr, int width, int height));
68 static void putbyte _ANSI_ARGS_((int c));
69 static void putword _ANSI_ARGS_((int w));
70 static void conv422to444 _ANSI_ARGS_((unsigned char *src, unsigned char *dst));
71 static void conv420to422 _ANSI_ARGS_((unsigned char *src, unsigned char *dst));
72
73 #define OBFRSIZE 4096
74 static unsigned char obfr[OBFRSIZE];
75 static unsigned char *optr;
76 static int outfile;
77
78 /*
79 * store a picture as either one frame or two fields
80 */
81 void Write_Frame(src,frame)
82 unsigned char *src[];
83 int frame;
84 {
85 char outname[FILENAME_LENGTH];
86
87 if (progressive_sequence || progressive_frame || Frame_Store_Flag)
88 {
89 /* progressive */
90 sprintf(outname,Output_Picture_Filename,frame,'f');
91 store_one(outname,src,0,Coded_Picture_Width,vertical_size);
92 }
93 else
94 {
95 /* interlaced */
96 sprintf(outname,Output_Picture_Filename,frame,'a');
97 store_one(outname,src,0,Coded_Picture_Width<<1,vertical_size>>1);
98
99 sprintf(outname,Output_Picture_Filename,frame,'b');
100 store_one(outname,src,
101 Coded_Picture_Width,Coded_Picture_Width<<1,vertical_size>>1);
102 }
103 }
104
105 /*
106 * store one frame or one field
107 */
108 static void store_one(outname,src,offset,incr,height)
109 char *outname;
110 unsigned char *src[];
111 int offset, incr, height;
112 {
113 switch (Output_Type)
114 {
115 case T_YUV:
116 store_yuv(outname,src,offset,incr,height);
117 break;
118 case T_SIF:
119 store_sif(outname,src,offset,incr,height);
120 break;
121 case T_TGA:
122 store_ppm_tga(outname,src,offset,incr,height,1);
123 break;
124 case T_PPM:
125 store_ppm_tga(outname,src,offset,incr,height,0);
126 break;
127 case T_FBUI:
128 store_fbui (outname,src,offset,incr,height,0);
129 break;
130
131 #ifdef DISPLAY
132 case T_X11:
133 dither(src);
134 break;
135 #endif
136 default:
137 break;
138 }
139 }
140
141 /* separate headerless files for y, u and v */
142 static void store_yuv(outname,src,offset,incr,height)
143 char *outname;
144 unsigned char *src[];
145 int offset,incr,height;
146 {
147 int hsize;
148 char tmpname[FILENAME_LENGTH];
149
150 hsize = horizontal_size;
151
152 sprintf(tmpname,"%s.Y",outname);
153 store_yuv1(tmpname,src[0],offset,incr,hsize,height);
154
155 if (chroma_format!=CHROMA444)
156 {
157 offset>>=1; incr>>=1; hsize>>=1;
158 }
159
160 if (chroma_format==CHROMA420)
161 {
162 height>>=1;
163 }
164
165 sprintf(tmpname,"%s.U",outname);
166 store_yuv1(tmpname,src[1],offset,incr,hsize,height);
167
168 sprintf(tmpname,"%s.V",outname);
169 store_yuv1(tmpname,src[2],offset,incr,hsize,height);
170 }
171
172 /* auxiliary routine */
173 static void store_yuv1(name,src,offset,incr,width,height)
174 char *name;
175 unsigned char *src;
176 int offset,incr,width,height;
177 {
178 int i, j;
179 unsigned char *p;
180
181 if (!Quiet_Flag)
182 fprintf(stderr,"saving %s\n",name);
183
184 if ((outfile = open(name,O_CREAT|O_TRUNC|O_WRONLY|O_BINARY,0666))==-1)
185 {
186 sprintf(Error_Text,"Couldn't create %s\n",name);
187 Error(Error_Text);
188 }
189
190 optr=obfr;
191
192 for (i=0; i<height; i++)
193 {
194 p = src + offset + incr*i;
195 for (j=0; j<width; j++)
196 putbyte(*p++);
197 }
198
199 if (optr!=obfr)
200 write(outfile,obfr,optr-obfr);
201
202 close(outfile);
203 }
204
205 /*
206 * store as headerless file in U,Y,V,Y format
207 */
208 static void store_sif (outname,src,offset,incr,height)
209 char *outname;
210 unsigned char *src[];
211 int offset, incr, height;
212 {
213 int i,j;
214 unsigned char *py, *pu, *pv;
215 static unsigned char *u422, *v422;
216
217 if (chroma_format==CHROMA444)
218 Error("4:4:4 not supported for SIF format");
219
220 if (chroma_format==CHROMA422)
221 {
222 u422 = src[1];
223 v422 = src[2];
224 }
225 else
226 {
227 if (!u422)
228 {
229 if (!(u422 = (unsigned char *)malloc((Coded_Picture_Width>>1)
230 *Coded_Picture_Height)))
231 Error("malloc failed");
232 if (!(v422 = (unsigned char *)malloc((Coded_Picture_Width>>1)
233 *Coded_Picture_Height)))
234 Error("malloc failed");
235 }
236
237 conv420to422(src[1],u422);
238 conv420to422(src[2],v422);
239 }
240
241 strcat(outname,".SIF");
242
243 if (!Quiet_Flag)
244 fprintf(stderr,"saving %s\n",outname);
245
246 if ((outfile = open(outname,O_CREAT|O_TRUNC|O_WRONLY|O_BINARY,0666))==-1)
247 {
248 sprintf(Error_Text,"Couldn't create %s\n",outname);
249 Error(Error_Text);
250 }
251
252 optr = obfr;
253
254 for (i=0; i<height; i++)
255 {
256 py = src[0] + offset + incr*i;
257 pu = u422 + (offset>>1) + (incr>>1)*i;
258 pv = v422 + (offset>>1) + (incr>>1)*i;
259
260 for (j=0; j<horizontal_size; j+=2)
261 {
262 putbyte(*pu++);
263 putbyte(*py++);
264 putbyte(*pv++);
265 putbyte(*py++);
266 }
267 }
268
269 if (optr!=obfr)
270 write(outfile,obfr,optr-obfr);
271
272 close(outfile);
273 }
274
275 /*
276 * store as PPM (PBMPLUS) or uncompressed Truevision TGA ('Targa') file
277 */
278 static void store_ppm_tga(outname,src,offset,incr,height,tgaflag)
279 char *outname;
280 unsigned char *src[];
281 int offset, incr, height;
282 int tgaflag;
283 {
284 int i, j;
285 int y, u, v, r, g, b;
286 int crv, cbu, cgu, cgv;
287 unsigned char *py, *pu, *pv;
288 static unsigned char tga24[14] = {0,0,2,0,0,0,0, 0,0,0,0,0,24,32};
289 char header[FILENAME_LENGTH];
290 static unsigned char *u422, *v422, *u444, *v444;
291
292 if (chroma_format==CHROMA444)
293 {
294 u444 = src[1];
295 v444 = src[2];
296 }
297 else
298 {
299 if (!u444)
300 {
301 if (chroma_format==CHROMA420)
302 {
303 if (!(u422 = (unsigned char *)malloc((Coded_Picture_Width>>1)
304 *Coded_Picture_Height)))
305 Error("malloc failed");
306 if (!(v422 = (unsigned char *)malloc((Coded_Picture_Width>>1)
307 *Coded_Picture_Height)))
308 Error("malloc failed");
309 }
310
311 if (!(u444 = (unsigned char *)malloc(Coded_Picture_Width
312 *Coded_Picture_Height)))
313 Error("malloc failed");
314
315 if (!(v444 = (unsigned char *)malloc(Coded_Picture_Width
316 *Coded_Picture_Height)))
317 Error("malloc failed");
318 }
319
320 if (chroma_format==CHROMA420)
321 {
322 conv420to422(src[1],u422);
323 conv420to422(src[2],v422);
324 conv422to444(u422,u444);
325 conv422to444(v422,v444);
326 }
327 else
328 {
329 conv422to444(src[1],u444);
330 conv422to444(src[2],v444);
331 }
332 }
333
334 strcat(outname,tgaflag ? ".tga" : ".ppm");
335
336 if (!Quiet_Flag)
337 fprintf(stderr,"saving %s\n",outname);
338
339 if ((outfile = open(outname,O_CREAT|O_TRUNC|O_WRONLY|O_BINARY,0666))==-1)
340 {
341 sprintf(Error_Text,"Couldn't create %s\n",outname);
342 Error(Error_Text);
343 }
344
345 optr = obfr;
346
347 if (tgaflag)
348 {
349 /* TGA header */
350 for (i=0; i<12; i++)
351 putbyte(tga24[i]);
352
353 putword(horizontal_size); putword(height);
354 putbyte(tga24[12]); putbyte(tga24[13]);
355 }
356 else
357 {
358 /* PPM header */
359 sprintf(header,"P6\n%d %d\n255\n",horizontal_size,height);
360
361 for (i=0; header[i]!=0; i++)
362 putbyte(header[i]);
363 }
364
365 /* matrix coefficients */
366 crv = Inverse_Table_6_9[matrix_coefficients][0];
367 cbu = Inverse_Table_6_9[matrix_coefficients][1];
368 cgu = Inverse_Table_6_9[matrix_coefficients][2];
369 cgv = Inverse_Table_6_9[matrix_coefficients][3];
370
371 for (i=0; i<height; i++)
372 {
373 py = src[0] + offset + incr*i;
374 pu = u444 + offset + incr*i;
375 pv = v444 + offset + incr*i;
376
377 for (j=0; j<horizontal_size; j++)
378 {
379 u = *pu++ - 128;
380 v = *pv++ - 128;
381 y = 76309 * (*py++ - 16); /* (255/219)*65536 */
382 r = Clip[(y + crv*v + 32768)>>16];
383 g = Clip[(y - cgu*u - cgv*v + 32768)>>16];
384 b = Clip[(y + cbu*u + 32786)>>16];
385
386 if (tgaflag)
387 {
388 putbyte(b); putbyte(g); putbyte(r);
389 }
390 else
391 {
392 putbyte(r); putbyte(g); putbyte(b);
393 }
394 }
395 }
396
397 if (optr!=obfr)
398 write(outfile,obfr,optr-obfr);
399
400 close(outfile);
401 }
402
403 /*
404 * store to FBUI
405 */
406 static void store_fbui (outname,src,offset,incr,height,tgaflag)
407 char *outname;
408 unsigned char *src[];
409 int offset, incr, height;
410 int tgaflag;
411 {
412 int i, j;
413 int y, u, v, r, g, b;
414 int crv, cbu, cgu, cgv;
415 unsigned char *py, *pu, *pv;
416 static unsigned char tga24[14] = {0,0,2,0,0,0,0, 0,0,0,0,0,24,32};
417 char header[FILENAME_LENGTH];
418 static unsigned char *u422, *v422, *u444, *v444;
419
420 if (chroma_format==CHROMA444)
421 {
422 u444 = src[1];
423 v444 = src[2];
424 }
425 else
426 {
427 if (!u444)
428 {
429 if (chroma_format==CHROMA420)
430 {
431 if (!(u422 = (unsigned char *)malloc((Coded_Picture_Width>>1)
432 *Coded_Picture_Height)))
433 Error("malloc failed");
434 if (!(v422 = (unsigned char *)malloc((Coded_Picture_Width>>1)
435 *Coded_Picture_Height)))
436 Error("malloc failed");
437 }
438
439 if (!(u444 = (unsigned char *)malloc(Coded_Picture_Width
440 *Coded_Picture_Height)))
441 Error("malloc failed");
442
443 if (!(v444 = (unsigned char *)malloc(Coded_Picture_Width
444 *Coded_Picture_Height)))
445 Error("malloc failed");
446 }
447
448 if (chroma_format==CHROMA420)
449 {
450 conv420to422(src[1],u422);
451 conv420to422(src[2],v422);
452 conv422to444(u422,u444);
453 conv422to444(v422,v444);
454 }
455 else
456 {
457 conv422to444(src[1],u444);
458 conv422to444(src[2],v444);
459 }
460 }
461
462 optr = obfr;
463
464 if (!fbui_display) {
465 fbui_display = fbui_display_open ();
466 if (!fbui_display)
467 FATAL ("cannot open display");
468
469 t = time(NULL);
470
471 short win_w;
472 short win_h;
473 unsigned long fg = RGB_WHITE, bg = RGB_BLACK;
474 int argc = 1;
475 char *argv[] = { "foo" };
476 fbui_window = fbui_window_open (fbui_display,
477 horizontal_size,height, &win_w, &win_h, 9999,9999, 0, 15,
478 &fg, &bg, "mpeg2decode", "",
479 FBUI_PROGTYPE_APP,
480 false,
481 false,
482 fbui_console,
483 true,
484 false,
485 false,
486 argc,argv);
487 if (!fbui_window)
488 FATAL ("cannot create window");
489 }
490
491 /* matrix coefficients */
492 crv = Inverse_Table_6_9[matrix_coefficients][0];
493 cbu = Inverse_Table_6_9[matrix_coefficients][1];
494 cgu = Inverse_Table_6_9[matrix_coefficients][2];
495 cgv = Inverse_Table_6_9[matrix_coefficients][3];
496
497 unsigned char *rows = (unsigned char*) malloc (3 * horizontal_size * height);
498 if (!rows)
499 FATAL ("cannot allocate image buffer");
500
501 for (i=0; i<height; i++)
502 {
503 py = src[0] + offset + incr*i;
504 pu = u444 + offset + incr*i;
505 pv = v444 + offset + incr*i;
506
507 for (j=0; j<horizontal_size; j++)
508 {
509 u = *pu++ - 128;
510 v = *pv++ - 128;
511 y = 76309 * (*py++ - 16); /* (255/219)*65536 */
512 r = Clip[(y + crv*v + 32768)>>16];
513 g = Clip[(y - cgu*u - cgv*v + 32768)>>16];
514 b = Clip[(y + cbu*u + 32786)>>16];
515
516 unsigned long row_ix = 3 * (j + i * horizontal_size);
517 rows [row_ix++] = r;
518 rows [row_ix++] = g;
519 rows [row_ix++] = b;
520 }
521 }
522
523 for (i=0; i<height; i++)
524 {
525 fbui_put_rgb3 (fbui_display, fbui_window, 0, i, horizontal_size,
526 rows + i * horizontal_size * 3);
527 fbui_flush (fbui_display, fbui_window);
528 }
529
530 time_t t2 = time(NULL);
531 if (t != t2) {
532 t = t2;
533 printf ("mpeg2decode fps = %d\n", frame_counter);
534 frame_counter = 0;
535 }
536 frame_counter++;
537
538 Event ev;
539 if (!fbui_poll_event (fbui_display,&ev,FBUI_EVENTMASK_ALL)) {
540 if (ev.type == FBUI_EVENT_KEY) {
541 short ch = fbui_convert_key (fbui_display, ev.key);
542 if (ch == 'q') {
543 shutdown_fbui ();
544 exit(0);
545 }
546 }
547 }
548
549 free (rows);
550 }
551
552 static void putbyte(c)
553 int c;
554 {
555 *optr++ = c;
556
557 if (optr == obfr+OBFRSIZE)
558 {
559 write(outfile,obfr,OBFRSIZE);
560 optr = obfr;
561 }
562 }
563
564 static void putword(w)
565 int w;
566 {
567 putbyte(w); putbyte(w>>8);
568 }
569
570 /* horizontal 1:2 interpolation filter */
571 static void conv422to444(src,dst)
572 unsigned char *src,*dst;
573 {
574 int i, i2, w, j, im3, im2, im1, ip1, ip2, ip3;
575
576 w = Coded_Picture_Width>>1;
577
578 if (base.MPEG2_Flag)
579 {
580 for (j=0; j<Coded_Picture_Height; j++)
581 {
582 for (i=0; i<w; i++)
583 {
584 i2 = i<<1;
585 im2 = (i<2) ? 0 : i-2;
586 im1 = (i<1) ? 0 : i-1;
587 ip1 = (i<w-1) ? i+1 : w-1;
588 ip2 = (i<w-2) ? i+2 : w-1;
589 ip3 = (i<w-3) ? i+3 : w-1;
590
591 /* FIR filter coefficients (*256): 21 0 -52 0 159 256 159 0 -52 0 21 */
592 /* even samples (0 0 256 0 0) */
593 dst[i2] = src[i];
594
595 /* odd samples (21 -52 159 159 -52 21) */
596 dst[i2+1] = Clip[(int)(21*(src[im2]+src[ip3])
597 -52*(src[im1]+src[ip2])
598 +159*(src[i]+src[ip1])+128)>>8];
599 }
600 src+= w;
601 dst+= Coded_Picture_Width;
602 }
603 }
604 else
605 {
606 for (j=0; j<Coded_Picture_Height; j++)
607 {
608 for (i=0; i<w; i++)
609 {
610
611 i2 = i<<1;
612 im3 = (i<3) ? 0 : i-3;
613 im2 = (i<2) ? 0 : i-2;
614 im1 = (i<1) ? 0 : i-1;
615 ip1 = (i<w-1) ? i+1 : w-1;
616 ip2 = (i<w-2) ? i+2 : w-1;
617 ip3 = (i<w-3) ? i+3 : w-1;
618
619 /* FIR filter coefficients (*256): 5 -21 70 228 -37 11 */
620 dst[i2] = Clip[(int)( 5*src[im3]
621 -21*src[im2]
622 +70*src[im1]
623 +228*src[i]
624 -37*src[ip1]
625 +11*src[ip2]+128)>>8];
626
627 dst[i2+1] = Clip[(int)( 5*src[ip3]
628 -21*src[ip2]
629 +70*src[ip1]
630 +228*src[i]
631 -37*src[im1]
632 +11*src[im2]+128)>>8];
633 }
634 src+= w;
635 dst+= Coded_Picture_Width;
636 }
637 }
638 }
639
640 /* vertical 1:2 interpolation filter */
641 static void conv420to422(src,dst)
642 unsigned char *src,*dst;
643 {
644 int w, h, i, j, j2;
645 int jm6, jm5, jm4, jm3, jm2, jm1, jp1, jp2, jp3, jp4, jp5, jp6, jp7;
646
647 w = Coded_Picture_Width>>1;
648 h = Coded_Picture_Height>>1;
649
650 if (progressive_frame)
651 {
652 /* intra frame */
653 for (i=0; i<w; i++)
654 {
655 for (j=0; j<h; j++)
656 {
657 j2 = j<<1;
658 jm3 = (j<3) ? 0 : j-3;
659 jm2 = (j<2) ? 0 : j-2;
660 jm1 = (j<1) ? 0 : j-1;
661 jp1 = (j<h-1) ? j+1 : h-1;
662 jp2 = (j<h-2) ? j+2 : h-1;
663 jp3 = (j<h-3) ? j+3 : h-1;
664
665 /* FIR filter coefficients (*256): 5 -21 70 228 -37 11 */
666 /* New FIR filter coefficients (*256): 3 -16 67 227 -32 7 */
667 dst[w*j2] = Clip[(int)( 3*src[w*jm3]
668 -16*src[w*jm2]
669 +67*src[w*jm1]
670 +227*src[w*j]
671 -32*src[w*jp1]
672 +7*src[w*jp2]+128)>>8];
673
674 dst[w*(j2+1)] = Clip[(int)( 3*src[w*jp3]
675 -16*src[w*jp2]
676 +67*src[w*jp1]
677 +227*src[w*j]
678 -32*src[w*jm1]
679 +7*src[w*jm2]+128)>>8];
680 }
681 src++;
682 dst++;
683 }
684 }
685 else
686 {
687 /* intra field */
688 for (i=0; i<w; i++)
689 {
690 for (j=0; j<h; j+=2)
691 {
692 j2 = j<<1;
693
694 /* top field */
695 jm6 = (j<6) ? 0 : j-6;
696 jm4 = (j<4) ? 0 : j-4;
697 jm2 = (j<2) ? 0 : j-2;
698 jp2 = (j<h-2) ? j+2 : h-2;
699 jp4 = (j<h-4) ? j+4 : h-2;
700 jp6 = (j<h-6) ? j+6 : h-2;
701
702 /* Polyphase FIR filter coefficients (*256): 2 -10 35 242 -18 5 */
703 /* New polyphase FIR filter coefficients (*256): 1 -7 30 248 -21 5 */
704 dst[w*j2] = Clip[(int)( 1*src[w*jm6]
705 -7*src[w*jm4]
706 +30*src[w*jm2]
707 +248*src[w*j]
708 -21*src[w*jp2]
709 +5*src[w*jp4]+128)>>8];
710
711 /* Polyphase FIR filter coefficients (*256): 11 -38 192 113 -30 8 */
712 /* New polyphase FIR filter coefficients (*256):7 -35 194 110 -24 4 */
713 dst[w*(j2+2)] = Clip[(int)( 7*src[w*jm4]
714 -35*src[w*jm2]
715 +194*src[w*j]
716 +110*src[w*jp2]
717 -24*src[w*jp4]
718 +4*src[w*jp6]+128)>>8];
719
720 /* bottom field */
721 jm5 = (j<5) ? 1 : j-5;
722 jm3 = (j<3) ? 1 : j-3;
723 jm1 = (j<1) ? 1 : j-1;
724 jp1 = (j<h-1) ? j+1 : h-1;
725 jp3 = (j<h-3) ? j+3 : h-1;
726 jp5 = (j<h-5) ? j+5 : h-1;
727 jp7 = (j<h-7) ? j+7 : h-1;
728
729 /* Polyphase FIR filter coefficients (*256): 11 -38 192 113 -30 8 */
730 /* New polyphase FIR filter coefficients (*256):7 -35 194 110 -24 4 */
731 dst[w*(j2+1)] = Clip[(int)( 7*src[w*jp5]
732 -35*src[w*jp3]
733 +194*src[w*jp1]
734 +110*src[w*jm1]
735 -24*src[w*jm3]
736 +4*src[w*jm5]+128)>>8];
737
738 dst[w*(j2+3)] = Clip[(int)( 1*src[w*jp7]
739 -7*src[w*jp5]
740 +30*src[w*jp3]
741 +248*src[w*jp1]
742 -21*src[w*jm1]
743 +5*src[w*jm3]+128)>>8];
744 }
745 src++;
746 dst++;
747 }
748 }
749 }
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.