/* yuvtoppm.c - convert Abekas YUV bytes into a portable pixmap
**
** by Marc Boucher
** Internet: marc@PostImage.COM
**
** Based on Example Conversion Program, A60/A64 Digital Video Interface
** Manual, page 69
**
** Uses integer arithmetic rather than floating point for better performance
**
** Copyright (C) 1991 by DHD PostImage Inc.
** Copyright (C) 1987 by Abekas Video Systems Inc.
** Copyright (C) 1991 by Jef Poskanzer.
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted, provided
** that the above copyright notice appear in all copies and that both that
** copyright notice and this permission notice appear in supporting
** documentation.  This software is provided "as is" without express or
** implied warranty.
*/

#include "pm_c_util.h"
#include "mallocvar.h"
#include "shhopt.h"
#include "ppm.h"



struct CmdlineInfo {
    /* All the information the user supplied in the command line,
       in a form easy for the program to use.
    */
    unsigned int cols;
    unsigned int rows;
    const char * inputFileName;  /* Name of input file */
};



static void
parseCommandLine(int argc, const char ** argv,
                 struct CmdlineInfo * const cmdlineP) {

    optEntry * option_def;
    optStruct3 opt;
    unsigned int option_def_index;

    MALLOCARRAY_NOFAIL(option_def, 100);

    OPTENTINIT;

    opt.opt_table = option_def;
    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */

    pm_optParseOptions4(&argc, argv, opt, sizeof(opt), 0);
        /* Uses and sets argc, argv, and some of *cmdlineP and others. */

    if (argc-1 < 2)
        pm_error("You need at least two arguments: width and height in "
                 "pixels.  You specified %u", argc-1);
    else {
        int const widthArg  = atoi(argv[1]);
        int const heightArg = atoi(argv[2]);

        if (widthArg < 0)
            pm_error("Negative number for width: %d", widthArg);
        if (heightArg < 0)
            pm_error("Negative number for height: %d", heightArg);

        cmdlineP->cols = widthArg;
        cmdlineP->rows = heightArg;

        if (cmdlineP->cols % 2 != 0)
            pm_error("Number of columns (%u) is odd.  "
                     "A YUV image must have an "
                     "even number of columns.", cmdlineP->cols);

        if (argc-1 < 3)
            cmdlineP->inputFileName = "-";
        else {
            cmdlineP->inputFileName = argv[3];

            if (argc-1 > 3)
                pm_error("Too many arguments: %u.  "
                         "The only possible arguments are "
                         "width, height, and input file name", argc-1);
        }
    }
}



static int
limit(int const x) {

    if (x > 0xffffff)
        return 0xff;
    else if (x <= 0xffff)
        return 0;
    else
        return ((x >> 16) & 0xff);
}



static int
nonneg(int const x) {
/*----------------------------------------------------------------------------
  Raise 'x' to 0 if negative
-----------------------------------------------------------------------------*/
    return x < 0 ? 0 : x;
}



struct Yuv {
/*----------------------------------------------------------------------------
  This is an element of a YUV file.  It describes two side-by-side pixels.

  This is the actual layout of the data in the file (4 bytes).
-----------------------------------------------------------------------------*/
    unsigned char u;
    unsigned char y1;
    unsigned char v;
    unsigned char y2;
};



static void
readYuv(FILE *       const ifP,
        struct Yuv * const yuvP) {

    size_t readCt;

    readCt = fread(yuvP, sizeof(*yuvP), 1, ifP);

    if (readCt != 1) {
        if (feof(ifP))
            pm_error("Premature end of input.");
        else
            pm_error("Error reading input.");
    }
}



static void
yuvtoppm(FILE *       const ifP,
         unsigned int const cols,
         unsigned int const rows,
         FILE *       const ofP) {

    pixval const maxval = 255;

    pixel * pixrow;
    unsigned int row;

    ppm_writeppminit(ofP, cols, rows, maxval, 0);

    pixrow = ppm_allocrow(cols);

    for (row = 0; row < rows; ++row) {
        unsigned int col;
        for (col = 0; col < cols; col += 2) {
            /* Produce two pixels in pixrow[] */
            struct Yuv yuv;
            int     y1, u, v, y2, r, g, b;

            readYuv(ifP, &yuv);

            u = yuv.u - 128;
            y1 = nonneg (yuv.y1 - 16);
            v = yuv.v - 128;
            y2 = nonneg (yuv.y2 - 16);

            r = 104635 * v;
            g = -25690 * u + -53294 * v;
            b = 132278 * u;

            y1 *= 76310;
            y2 *= 76310;

            PPM_ASSIGN(pixrow[col],
                       limit(r + y1), limit(g + y1), limit(b + y1));
            PPM_ASSIGN(pixrow[col + 1],
                       limit(r + y2), limit(g + y2), limit(b + y2));
        }
        ppm_writeppmrow(ofP, pixrow, cols, maxval, 0);
    }

    ppm_freerow(pixrow);

    if (fgetc(ifP) != EOF)
        pm_message("Extraneous data at end of image.");
}



int
main (int argc, const char ** argv) {

    FILE * ifP;
    struct CmdlineInfo cmdline;

    pm_proginit(&argc, argv);

    parseCommandLine(argc, argv, &cmdline);

    ifP = pm_openr(cmdline.inputFileName);

    yuvtoppm(ifP, cmdline.cols, cmdline.rows, stdout);

    pm_close(ifP);
    pm_close(stdout);

    return 0;
}



