Advertisement
grecomoran

mkvhs

Mar 1st, 2023
960
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C 21.57 KB | Source Code | 0 0
  1. /*
  2.  
  3.     ||||||||||  ||    ||  ||      ||  ||    ||  ||      
  4.     ||  ||  ||  ||||||      ||  ||    ||||||||  ||||||||
  5.     ||  ||  ||  ||    ||      ||      ||    ||        ||
  6.  
  7.  
  8.     mkvhs by G. Moran
  9.     version 1.0.0 [28/02/2023]
  10.    
  11.     This program is in the public domain, licensed under the Unlicense:
  12.     https://unlicense.org/
  13.  
  14. */
  15.  
  16.  
  17. /* MANUAL AND FAQ
  18. ______________________________________________________________________________*/
  19.  
  20.  
  21. /*
  22.  
  23.     * How do I use this?
  24.     ********************
  25.    
  26.     To use mkvhs you need to...
  27.    
  28.         (1) Compile this file.
  29.         (2) Download the SoX program.
  30.         (3) Know how to use the terminal or command prompt.
  31.    
  32.     Save this file as `mkvhs.c` then compile it using any C compiler.
  33.     For example if using GCC simply run:
  34.    
  35.         gcc mkvhs.c -o mkvhs
  36.    
  37.     and you'll get an executable named `mkvhs` or `mkvhs.exe` .
  38.     Then download SoX from here: https://sox.sourceforge.net/
  39.     and try running:
  40.    
  41.         sox --version
  42.    
  43.     If a version number is displayed then you're good to go.
  44.     You can now use mkvhs by running:
  45.    
  46.         mkvhs file.tga
  47.    
  48.     where `file.tga` is a 24-bit Targa image with dimensions 640 x 480.
  49.     The output will be named `file.tga.vhs.tga` .
  50.     You can customize the file naming in the OPTIONS section below.
  51.    
  52.    
  53.     * Can I use mkvhs with video?
  54.     *****************************
  55.    
  56.     Yes, by extracting the frames of the video as TGA images then
  57.     feeding each image into mkvhs like this:
  58.    
  59.         mkvhs frame0001.tga
  60.         mkvhs frame0002.tga
  61.         mkvhs frame0003.tga
  62.         ...
  63.    
  64.     Then combining the new frames back into a video file.
  65.    
  66.     WARNING: Be careful with long videos!
  67.     A single 640 x 480 TGA image is almost 1 MB in file size,
  68.     so a video that's 1 minute long running at 30 frames per second
  69.     will produce more than 1.5 GB worth of TGA files. You have been warned.
  70.    
  71.    
  72.     * How do I extract and recombine video frames?
  73.     **********************************************
  74.    
  75.     You can use a program called FFMPEG to manipulate video frames.
  76.     Download FFMPEG from here: https://ffmpeg.org/download.html
  77.    
  78.     Copy your video to an empty directory, then extract each video frame
  79.     into a separate TGA file by running:
  80.    
  81.         ffmpeg -i yourvideo.mp4 -rle 0 frame%04d.tga
  82.    
  83.     Then loop through the frame files and feed each one into mkvhs.
  84.     On linux for example you can achieve this by running:
  85.    
  86.         for i in frame*; do mkvhs `basename "$i"`; done
  87.    
  88.     Finally recombine the new frames into a video by running:
  89.    
  90.         ffmpeg -framerate 30 -pattern_type glob -i '*.vhs.tga' output.mp4
  91.    
  92.     while using the framerate value from the original video.
  93.    
  94.    
  95.     * How do I tweak the way the output looks?
  96.     ******************************************
  97.    
  98.     Scroll down to the OPTIONS section and change the values to your liking.
  99.     You can fine-tune noise, blur, colors...etc. Feel free to experiment.
  100.     Make sure to always read the WARNING messages.
  101.     When you're done making changes, recompile this file.
  102.    
  103.    
  104.     * What are the limitations of mkvhs?
  105.     ************************************
  106.    
  107.     (1) The input image is resampled row-by-row, so any sharp vertical
  108.     transitions in color are preserved. This is currently mitigated by
  109.     slightly blurring the resampled image in the vertical direction.
  110.    
  111.     (2) Since the Luma (Y) and Chroma (Cb/Cr) channels are resampled
  112.     separately, there's no interference between them. This causes
  113.     the output to lack color bleed and "rainbow artifacts".
  114.    
  115.    
  116.     * How do I force mkvhs to overwrite the input file?
  117.     ***************************************************
  118.    
  119.     Scroll down to the OPTIONS section and clear the OUTSUFFIX string:
  120.    
  121.         #define OUTSUFFIX   ""
  122.    
  123.     then recompile the program.
  124.  
  125. */
  126.  
  127.  
  128. #include <stdio.h>
  129. #include <stdlib.h>
  130.  
  131.  
  132. /* OPTIONS
  133. ______________________________________________________________________________*/
  134.  
  135.  
  136. /* output file names
  137.    WARNING: add a prefix only when passing base names!
  138.    i.e. `file.tga` not `path/to/file.tga`
  139. */
  140. #define OUTPREFIX   ""
  141. #define OUTSUFFIX   ".vhs.tga"
  142.  
  143. /* create file with raw Y/Cb/Cr triplets (default=no) */
  144. #define MAKEYUV     NO
  145. #define YUVPREFIX   ""
  146. #define YUVSUFFIX   ".vhs.yuv"
  147.  
  148. /* temporary files to be created in the current directory */
  149. /* WARNING: if these files already exist they will be overwritten! */
  150. #define TEMPY1      "y1.raw"
  151. #define TEMPU1      "u1.raw"
  152. #define TEMPV1      "v1.raw"
  153. #define TEMPY2      "y2.raw"
  154. #define TEMPU2      "u2.raw"
  155. #define TEMPV2      "v2.raw"
  156.  
  157. /* keep temporary files (default=no) */
  158. #define KEEPTEMP    NO
  159.  
  160. /* image dimensions (do not change) */
  161. #define WIDTH       640
  162. #define HEIGHT      480
  163.  
  164. /* account for overscan during processing (default=yes) */
  165. #define OVERSCAN    YES
  166.  
  167. /* image width incl. overscan, image offset (do not change) */
  168. #define OVERWIDTH   (WIDTH*5/4)
  169. #define OVEROFFSET  ((OVERWIDTH-WIDTH)/2)
  170. #define FINALWIDTH  (OVERSCAN ? OVERWIDTH : WIDTH)
  171.  
  172. /* "wash out" colors; ignore headroom and footroom (default=yes) */
  173. #define WASHOUT     YES
  174.  
  175. /* make image black and white (default=no) */
  176. #define GRAYSCALE   NO
  177.  
  178. /* add noise (default=yes) */
  179. #define NOISE       YES
  180.  
  181. /* noise levels (0=none, 8=maximum) */
  182. #define NOISEY      3
  183. #define NOISEU      4
  184. #define NOISEV      4
  185.  
  186. /* noise scales w.r.t. image resolution */
  187. #define NSCALEY     1/2
  188. #define NSCALEU     1/4
  189. #define NSCALEV     1/4
  190.  
  191. /* frequency ratios for resampling
  192.    brand new mint tape --> 4:2:1:1
  193.    2nd generation tape --> 6:2:1:1
  194.    3rd generation tape --> 8:2:1:1
  195.    ...
  196. */
  197. #define FREQO       "8k"
  198. #define FREQY       "2k"
  199. #define FREQU       "1k"
  200. #define FREQV       "1k"
  201.  
  202. /* vertically blur image after resampling (default=yes) */
  203. #define VBLUR       YES
  204.  
  205. /* vertical blur strength (0=none, 8=maximum) */
  206. #define VBLURY      1
  207. #define VBLURU      2
  208. #define VBLURV      2
  209.  
  210.  
  211. /* USEFUL MACROS
  212. ______________________________________________________________________________*/
  213.  
  214.  
  215. /* booleans */
  216. #define YES         1
  217. #define NO          0
  218.  
  219. /* make a string literal out of a macro */
  220. #define STR(A)      STR_(A)
  221. #define STR_(A)     #A
  222.  
  223. /* surround a string with escaped quotes and spaces */
  224. #define ESCQUO(SS)  ESCQUO_(SS)
  225. #define ESCQUO_(S)  " \"" S "\" "
  226.  
  227. /* bound a value between a lower and upper limit */
  228. #define BOUND(X,A,B)    ( BOUND_((X),(A),(B)) )
  229. #define BOUND_(X,A,B)   X<A? A : X>B? B : X
  230.  
  231. /* print stuff */
  232. #define DISPERROR(S)    !!printf("ERROR: %s\n", S)
  233. #define SHOWUSAGE()     !!printf("%s\n", "USAGE: mkvhs file.tga")
  234.  
  235.  
  236. /* NOISE GENERATOR
  237. ______________________________________________________________________________*/
  238.  
  239.  
  240. /*
  241. this RNG was chosen specifically because the lower bytes create a pattern
  242. similar to TV static
  243. https://en.wikipedia.org/wiki/Pseudorandom_binary_sequence
  244. https://archive.today/DHyJL
  245. */
  246. long
  247. randnum(long seed)
  248. {
  249.     long nbit = ~((seed >> 30) ^ (seed >> 27)) & 1;
  250.     seed = (seed << 1) | nbit;
  251.    
  252.     return seed &= 0xFFFFFFFF;
  253. }
  254.  
  255. /* write [length] bytes of noise into an array */
  256. int
  257. makenoise(unsigned char *out, long length, long seed)
  258. {
  259.     long i;
  260.     if (!out) return 1;
  261.    
  262.     for (i=0; i<length; i++) {
  263.         seed = randnum(seed);
  264.         out[i] = (unsigned char) (seed&0xFF);
  265.     }
  266.    
  267.     return 0;
  268. }
  269.  
  270.  
  271. /* BILINEAR FILTER
  272. ______________________________________________________________________________*/
  273.  
  274.  
  275. /* scale an image using bilinear interpolation */
  276. int
  277. scale(
  278. unsigned char *in,  /* input array (flattened 2D grid) */
  279. FILE *out,          /* output file */
  280. long w1, long h1,   /* input dimensions */
  281. long w2, long h2,   /* output dimensions */
  282. char ch,            /* number of channels (bytes) per pixel */
  283. long off            /* input offset to image data */
  284. )
  285. {
  286.     long i, j;
  287.     char k;
  288.    
  289.     if (!in | !out) return 1;
  290.    
  291.     for (j=0; j<h2; j++)
  292.         for (i=0; i<w2; i++)
  293.             for (k=0; k<ch; k++) {
  294.                 long    px, py, i0, i1, j0, j1;
  295.                 short   v00,    v01,    v10,    v11,
  296.                         v0,     v1,     v,
  297.                         dx,     dy;
  298.                
  299.                 /* ratios (%) */
  300.                 px  = (i*100)*(w1-1)/(w2-1);
  301.                 py  = (j*100)*(h1-1)/(h2-1);
  302.                 px  = BOUND(px,0,(w1-1)*100);
  303.                 py  = BOUND(py,0,(h1-1)*100);
  304.                 /* round down */
  305.                 i0  = px / 100;
  306.                 j0  = py / 100;
  307.                 /* round up */
  308.                 i1=i0+1;
  309.                 j1=j0+1;
  310.                
  311.                 /* bound */
  312.                 i0  = BOUND(i0,0,w1-1);
  313.                 i1  = BOUND(i1,0,w1-1);
  314.                 j0  = BOUND(j0,0,h1-1);
  315.                 j1  = BOUND(j1,0,h1-1);
  316.                
  317.                 /* read 4 pixels surrounding point */
  318.                 v00 = in[off+j0*w1*ch+i0*ch+k];
  319.                 v10 = in[off+j0*w1*ch+i1*ch+k];
  320.                 v01 = in[off+j1*w1*ch+i0*ch+k];
  321.                 v11 = in[off+j1*w1*ch+i1*ch+k];
  322.                
  323.                 /* horizontal interpolation */
  324.                 dx  = i1*100-px;
  325.                 v0  = (v00*dx + v10*(100-dx)) / 100;
  326.                 v1  = (v01*dx + v11*(100-dx)) / 100;
  327.                
  328.                 /* vertical interpolation */
  329.                 dy  = j1*100-py;
  330.                 v   = ( v0*dy +  v1*(100-dy)) / 100;
  331.                
  332.                 /* write interpolated byte */
  333.                 fputc(v, out);
  334.             }
  335.    
  336.     return 0;
  337. }
  338.  
  339.  
  340. /* BLURRING FILTER
  341. ______________________________________________________________________________*/
  342.  
  343.  
  344. /* vertically blur an image */
  345. int
  346. vblur(
  347. unsigned char *in,  /* input array (flattened 2D grid) */
  348. FILE *out,          /* output file */
  349. long w, long h,     /* input dimensions */
  350. char ch,            /* number of channels (bytes) per pixel */
  351. long off,           /* input offset to image data */
  352. char strength       /* blur strength */
  353. )
  354. {
  355.     long i, j;
  356.     char k;
  357.     short size;
  358.    
  359.     if (!in | !out) return 1;
  360.    
  361.     size = 1 << strength;
  362.    
  363.     for (j=0; j<h; j++)
  364.         for (i=0; i<w; i++)
  365.             for (k=0; k<ch; k++) {
  366.                 long jj, start, end, idx;
  367.                 unsigned total;
  368.                
  369.                 start   = j - (size/2-1);
  370.                 end     = j + (size/2);
  371.                 total   = 0;
  372.                
  373.                 for (jj=start; jj<=end; jj++) {
  374.                     idx = BOUND(jj,0,h-1);
  375.                     total += (long)in[off + idx*w*ch + i*ch + k];
  376.                 }
  377.                
  378.                 total >>= strength;
  379.                 fputc((int)total, out);
  380.             }
  381.    
  382.     return 0;
  383. }
  384.  
  385.  
  386. /* MAIN
  387. ______________________________________________________________________________*/
  388.  
  389.  
  390. /* start here */
  391. int main(int argc, char* argv[]) {
  392.     FILE *in, *out,
  393.          *fy1, *fu1, *fv1,
  394.          *fy2, *fu2, *fv2,
  395.          *yuv;
  396.     long nsy, nsu, nsv;
  397.     long i, j, o;
  398.     unsigned char buffer[FINALWIDTH * HEIGHT * 3];
  399.     char outname[FILENAME_MAX];
  400.     int temp, idfield, tgaorigin;
  401.    
  402.     /*** VERIFY INPUT ***/
  403.    
  404.     if (argc != 2)
  405.         return  (--argc && DISPERROR("Only one input file allowed")) &
  406.                 SHOWUSAGE();
  407.    
  408.     in = fopen(argv[1], "rb");
  409.     if (!in)
  410.         return DISPERROR("Could not open input file");
  411.    
  412.     /* start reading TGA image header */
  413.     idfield = fgetc(in);
  414.    
  415.     temp = fgetc(in);
  416.     if (temp || fgetc(in) != 2)
  417.         return DISPERROR("Only unmapped 24-bit TGA files are allowed");
  418.    
  419.     fgetc(in); fgetc(in); fgetc(in); fgetc(in); fgetc(in);
  420.     fgetc(in); fgetc(in); fgetc(in); fgetc(in);
  421.    
  422.     temp  = fgetc(in);
  423.     temp |= fgetc(in)<<8;
  424.     if (temp != WIDTH)
  425.         return DISPERROR("Image width is not " STR(WIDTH));
  426.     temp  = fgetc(in);
  427.     temp |= fgetc(in)<<8;
  428.     if (temp != HEIGHT)
  429.         return DISPERROR("Image height is not " STR(HEIGHT));
  430.    
  431.     if (fgetc(in) != 24)
  432.         return DISPERROR("Only unmapped 24-bit TGA files are allowed");
  433.    
  434.     tgaorigin = fgetc(in);
  435.     if (tgaorigin != 32 && tgaorigin)
  436.         return DISPERROR("Unknown TGA descriptor byte");
  437.    
  438.     while (idfield-- > 0)
  439.         fgetc(in);
  440.    
  441.     /*** GENERATE NOISE ***/
  442.    
  443.     if (NOISE) {
  444.         /* calculate seeds from filename */
  445.         unsigned char *name = (unsigned char*) argv[1];
  446.         unsigned seed, n;
  447.         long seedy, seedu, seedv;
  448.         for (seed=n=0; *name; name++, n++)
  449.             seed = (seed + (*name << (n&7))) & 0x7FFFFFFF;
  450.         seedy = (long) seed + 'Y';
  451.         seedu = (long) seed + 'C' + 'b';
  452.         seedv = (long) seed + 'C' + 'r';
  453.        
  454.         /* create noise files */
  455.         fy2 = fopen(TEMPY2, "wb");
  456.         fu2 = fopen(TEMPU2, "wb");
  457.         fv2 = fopen(TEMPV2, "wb");
  458.         if (!fy2 | !fu2 | !fv2)
  459.             return DISPERROR("Could not create noise files");
  460.        
  461.         /* generate noise in memory, then write it scaled up to file */
  462.         makenoise(buffer, WIDTH*NSCALEY * HEIGHT*NSCALEY, seedy);
  463.         scale(buffer,fy2, WIDTH*NSCALEY , HEIGHT*NSCALEY,WIDTH,HEIGHT,1,0);
  464.        
  465.         makenoise(buffer, WIDTH*NSCALEU * HEIGHT*NSCALEU, seedu);
  466.         scale(buffer,fu2, WIDTH*NSCALEU , HEIGHT*NSCALEU,WIDTH,HEIGHT,1,0);
  467.        
  468.         makenoise(buffer, WIDTH*NSCALEV * HEIGHT*NSCALEV, seedv);
  469.         scale(buffer,fv2, WIDTH*NSCALEV , HEIGHT*NSCALEV,WIDTH,HEIGHT,1,0);
  470.        
  471.         fclose(fy2);
  472.         fclose(fu2);
  473.         fclose(fv2);
  474.     }
  475.    
  476.     /*** CONVERT IMAGE TO YCBCR ***/
  477.    
  478.     /* create separate Y/Cb/Cr files */
  479.     fy1 = fopen(TEMPY1, "wb");
  480.     fu1 = fopen(TEMPU1, "wb");
  481.     fv1 = fopen(TEMPV1, "wb");
  482.     if (!fy1 | !fu1 | !fv1)
  483.         return DISPERROR("Could not create temporary files");
  484.    
  485.     /* open noise files */
  486.     if (NOISE) {
  487.         fy2 = fopen(TEMPY2, "rb");
  488.         fu2 = fopen(TEMPU2, "rb");
  489.         fv2 = fopen(TEMPV2, "rb");
  490.         if (!fy2 | !fu2 | !fv2)
  491.             return DISPERROR("Could not open noise files");
  492.         /* set noise levels (shifts) */
  493.         nsy = 8-NOISEY;
  494.         nsu = 8-NOISEU;
  495.         nsv = 8-NOISEV;
  496.     }
  497.    
  498.     /*
  499.     convert RGB to YCbCr BT.601
  500.     http://www.equasys.de/colorconversion.html
  501.     https://archive.today/8yPOE
  502.     */
  503.     for (j=0; j<HEIGHT; j++) {
  504.         for (o=OVERSCAN*OVEROFFSET; o; o--) {
  505.             fputc( 16, fy1);
  506.             fputc(128, fu1);
  507.             fputc(128, fv1);
  508.         }
  509.         for (i=0; i<WIDTH; i++) {
  510.             long r, g, b, y, u, v;
  511.             short sy, su, sv;
  512.            
  513.             b = fgetc(in);
  514.             g = fgetc(in);
  515.             r = fgetc(in);
  516.            
  517.             /*  half range: Y [16-235], Cb/Cr [16-240]
  518.             Y =  16 + 0.257 R + 0.504 G + 0.098 B
  519.             U = 128 - 0.148 R - 0.291 G + 0.439 B
  520.             V = 128 + 0.439 R - 0.368 G - 0.071 B
  521.             */
  522.             y =  16 + (  66*r + 129*g +  25*b) / 256;
  523.             u = 128 + (- 38*r -  74*g + 112*b) / 256;
  524.             v = 128 + ( 112*r -  94*g -  18*b) / 256;
  525.            
  526.             if (GRAYSCALE)
  527.                 u = v = 128;
  528.            
  529.             if (NOISE) {
  530.                 sy = (fgetc(fy2)>>nsy) - (128>>nsy);
  531.                 su = (fgetc(fu2)>>nsu) - (128>>nsu);
  532.                 sv = (fgetc(fv2)>>nsv) - (128>>nsv);
  533.                 y += sy; u += su; v += sv;
  534.                 y = BOUND(y,16,235);
  535.                 u = BOUND(u,16,240);
  536.                 v = BOUND(v,16,240);
  537.             }
  538.            
  539.             fputc(y, fy1);
  540.             fputc(u, fu1);
  541.             fputc(v, fv1);
  542.         }
  543.         for (o=OVERSCAN*OVEROFFSET; o; o--) {
  544.             fputc( 16, fy1);
  545.             fputc(128, fu1);
  546.             fputc(128, fv1);
  547.         }
  548.     }
  549.    
  550.     fclose(in);
  551.     fclose(fy1);
  552.     fclose(fu1);
  553.     fclose(fv1);
  554.     if (NOISE) {
  555.         fclose(fy2);
  556.         fclose(fu2);
  557.         fclose(fv2);
  558.     }
  559.    
  560.     /*** RESAMPLE IMAGE ***/
  561.    
  562.     /* Y */
  563.     system( "sox -r " FREQO "       -e unsigned -b 8 -c 1 -D" ESCQUO(TEMPY1)
  564.             "                       -e unsigned -b 8 -c 1 -D" ESCQUO(TEMPY2)
  565.             "rate -m -b 85 -p 50 -a " FREQY );
  566.     system( "sox -r " FREQY "       -e unsigned -b 8 -c 1 -D" ESCQUO(TEMPY2)
  567.             "                       -e unsigned -b 8 -c 1 -D" ESCQUO(TEMPY1)
  568.             "rate -m -b 85 -p 50 -a " FREQO );
  569.     /* Cb */
  570.     system( "sox -r " FREQO "       -e unsigned -b 8 -c 1 -D" ESCQUO(TEMPU1)
  571.             "                       -e unsigned -b 8 -c 1 -D" ESCQUO(TEMPU2)
  572.             "rate -m -b 85 -p 75 -a " FREQU );
  573.     system( "sox -r " FREQU "       -e unsigned -b 8 -c 1 -D" ESCQUO(TEMPU2)
  574.             "                       -e unsigned -b 8 -c 1 -D" ESCQUO(TEMPU1)
  575.             "rate -m -b 85 -p 75 -a " FREQO );
  576.     /* Cr */
  577.     system( "sox -r " FREQO "       -e unsigned -b 8 -c 1 -D" ESCQUO(TEMPV1)
  578.             "                       -e unsigned -b 8 -c 1 -D" ESCQUO(TEMPV2)
  579.             "rate -m -b 85 -p 75 -a " FREQV );
  580.     system( "sox -r " FREQV "       -e unsigned -b 8 -c 1 -D" ESCQUO(TEMPV2)
  581.             "                       -e unsigned -b 8 -c 1 -D" ESCQUO(TEMPV1)
  582.             "rate -m -b 85 -p 75 -a " FREQO );
  583.    
  584.     /*** VERTICALLY BLUR RESAMPLED IMAGE ***/
  585.    
  586.     if (VBLUR) {
  587.         int f;
  588.         char *filestrs[3] = {TEMPY1, TEMPU1, TEMPV1};
  589.         char  vbstrens[3] = {VBLURY, VBLURU, VBLURV};
  590.         FILE *fptr;
  591.        
  592.         for (f=0; f<3; f++) {
  593.            
  594.             /* read resampled Y/Cb/Cr file into memory */
  595.             fptr = fopen(filestrs[f], "rb");
  596.             if (!fptr)
  597.                 return DISPERROR("Could not open temporary files");
  598.             for (j=0; j<HEIGHT; j++)
  599.                 for (i=0; i<FINALWIDTH; i++)
  600.                     buffer[j*FINALWIDTH + i] = fgetc(fptr);
  601.             fclose(fptr);
  602.            
  603.             /* write blurred image into same file */
  604.             fptr = fopen(filestrs[f], "wb");
  605.             if (!fptr)
  606.                 return DISPERROR("Could not create temporary files");
  607.             vblur(buffer,fptr,FINALWIDTH,HEIGHT,1,0,vbstrens[f]);
  608.             fclose(fptr);
  609.         }
  610.     }
  611.    
  612.     /*** CONVERT BACK TO RGB AND WRITE FINAL IMAGE ***/
  613.    
  614.     fy1 = fopen(TEMPY1, "rb");
  615.     fu1 = fopen(TEMPU1, "rb");
  616.     fv1 = fopen(TEMPV1, "rb");
  617.     if (!fy1 | !fu1 | !fv1)
  618.         return DISPERROR("Could not open temporary files");
  619.    
  620.     sprintf(outname, "%s%s%s", OUTPREFIX, argv[1], OUTSUFFIX);
  621.     out = fopen(outname, "wb");
  622.     if (!out)
  623.         return DISPERROR("Could not create output RGB file");
  624.    
  625.     if (MAKEYUV) {
  626.         sprintf(outname, "%s%s%s", YUVPREFIX, argv[1], YUVSUFFIX);
  627.         yuv = fopen(outname, "wb");
  628.         if (!yuv)
  629.             return DISPERROR("Could not create output YUV file");
  630.     }
  631.    
  632.     /* write TGA image header */
  633.     fputc(0,out); fputc(0,out);
  634.     fputc(2,out);
  635.     fputc(0,out); fputc(0,out); fputc(0,out); fputc(0,out); fputc(0,out);
  636.     fputc(0,out); fputc(0,out); fputc(0,out); fputc(0,out);
  637.     fputc(WIDTH  & 0xFF,out);   fputc(WIDTH >>8 & 0xFF,out);
  638.     fputc(HEIGHT & 0xFF,out);   fputc(HEIGHT>>8 & 0xFF,out);
  639.     fputc(24,out);
  640.     fputc(tgaorigin,out);
  641.    
  642.     /*
  643.     convert YCbCr BT.601 back to RGB
  644.     http://www.equasys.de/colorconversion.html
  645.     https://archive.today/8yPOE
  646.     */
  647.     for (j=0; j<HEIGHT; j++) {
  648.         for (o=OVERSCAN*OVEROFFSET; o; o--) {
  649.             fgetc(fy1);
  650.             fgetc(fu1);
  651.             fgetc(fv1);
  652.         }
  653.         for (i=0; i<WIDTH; i++) {
  654.             long r, g, b, y, u, v;
  655.            
  656.             y = fgetc(fy1);
  657.             u = fgetc(fu1);
  658.             v = fgetc(fv1);
  659.            
  660.             if (MAKEYUV) {
  661.                 fputc(y, yuv);
  662.                 fputc(u, yuv);
  663.                 fputc(v, yuv);
  664.             }
  665.            
  666.             /*  full range: Y/Cb/Cr [0-255]
  667.             R = Y +               + 1.400 (V-128)
  668.             G = Y - 0.343 (U-128) - 0.711 (V-128)
  669.             B = Y + 1.765 (U-128)                  
  670.             */
  671.             if (WASHOUT) {
  672.                 u-=128, v-=128;
  673.                 r = y + (         358*v) / 256;
  674.                 g = y + (- 88*u - 182*v) / 256;
  675.                 b = y + ( 452*u        ) / 256;
  676.             }
  677.             /*  half range: Y [16-235], Cb/Cr [16-240]
  678.             R = 1.164 (Y-16)                  + 1.596 (Cr-128)
  679.             G = 1.164 (Y-16) - 0.392 (Cb-128) - 0.813 (Cr-128)
  680.             B = 1.164 (Y-16) + 2.017 (Cb-128)
  681.             */
  682.             else {
  683.                 y -= 16, u -= 128, v -= 128;
  684.                 r = (298*y         + 409*v) / 256;
  685.                 g = (298*y - 100*u - 208*v) / 256;
  686.                 b = (298*y + 516*u        ) / 256;
  687.             }
  688.            
  689.             /* necessary; values can be over 255 or negative */
  690.             r = BOUND(r,0,255);
  691.             g = BOUND(g,0,255);
  692.             b = BOUND(b,0,255);
  693.            
  694.             fputc(b,out);
  695.             fputc(g,out);
  696.             fputc(r,out);
  697.         }
  698.         for (o=OVERSCAN*OVEROFFSET; o; o--) {
  699.             fgetc(fy1);
  700.             fgetc(fu1);
  701.             fgetc(fv1);
  702.         }
  703.     }
  704.    
  705.     fclose(out);
  706.     fclose(fy1);
  707.     fclose(fu1);
  708.     fclose(fv1);
  709.     if (MAKEYUV) fclose(yuv);
  710.    
  711.     /* clean up */
  712.     if (!KEEPTEMP) {
  713.         remove(TEMPY1);
  714.         remove(TEMPU1);
  715.         remove(TEMPV1);
  716.         remove(TEMPY2);
  717.         remove(TEMPU2);
  718.         remove(TEMPV2);
  719.     }
  720.    
  721.     return 0;
  722. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement