/**********************************************************************
*
*   dctvgrad.c - Render a 24-bit RGB gradient visible on DCTV.
*
*   Copyright (C) 1991, 1992 Digital Creations, Inc.
*
*   DESCRIPTION
*       This example demonstrates how to call dctv.library to generate a
*       DCTV frame by suppling lines of 24-bit RGB data (in this case a
*       multi-point gradient).  The finished BitMap can then be written
*       to an ILBM file.
*
*       Program runs under Amiga OS v1.3 and higher.
*
*   USAGE
*       dctvgrad <width> <height> <depth>
*           width  - screen width (640..736)
*           height - screen height (200..241, 400..482)
*           depth  - screen depth (3..4)
*
*       Terminate with Control+C.
*
*   COMPILATION
*       SAS/C:      (v5.10 or higher.  Regular of ANSI libs)
*           lc -L+dctv.lib dctvgrad
*
*       Aztec C:    (v5.0a or higher)
*           cc dctvgrad [works w/ either 16 or 32 bit int's]
*           ln dctvgrad.o +l dctv.lib -lc
*
*   REQUIREMENTS
*       . Aztec C 5.0 or SAS/C 5.10.
*       . v37.4 or higher Amiga include files.
*       . OS #pragmas created for your compiler in the pragmas
*         directory of your include path.
*
**********************************************************************/

#include <dos/dos.h>
#include <intuition/screens.h>
#include <libraries/dctv.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <clib/dctv_protos.h>
#include <clib/exec_protos.h>
#include <clib/graphics_protos.h>
#include <clib/intuition_protos.h>

#include <pragmas/dctv_pragmas.h>
#include <pragmas/exec_pragmas.h>
#include <pragmas/graphics_pragmas.h>
#include <pragmas/intuition_pragmas.h>


/* -------------------- misc defines */

#define DCTVLIB_Version 3


/* -------------------- data */

struct Library *DCTVBase, *GfxBase, *IntuitionBase;


/* -------------------- local functions */

    /* screen processor */
void doscreen (short width, short height, short depth);

    /* gradient functions */
void initgrad (struct DCTVCvtHandle *);
void rendergradline (struct DCTVCvtHandle *);


/* -------------------- main() */

#if AZTEC_C
  void _wb_parse (void) {}      /* stub out wb window parser */
#endif

#if __SASC  /* suppress normal Ctrl-C processing (SAS/C) */
  void __chkabort (void) {}
  void chkabort (void) {}
#endif

int main (int argc, char **argv)
{
    if (argc < 4) goto clean;

  #if AZTEC_C           /* suppress normal Ctrl-C processing (Aztec C) */
    {
        extern int Enable_Abort;
        Enable_Abort = 0;
    }
  #endif

    if (!(IntuitionBase = OpenLibrary ("intuition.library", LIBRARY_MINIMUM))) goto clean;
    if (!(GfxBase = OpenLibrary ("graphics.library", LIBRARY_MINIMUM))) goto clean;
    if (!(DCTVBase = OpenLibrary ("dctv.library", DCTVLIB_Version))) {
        printf ("requires dctv.library v%d\n", DCTVLIB_Version);
        goto clean;
    }

    doscreen (atoi(argv[1]), atoi(argv[2]), atoi(argv[3]));

clean:
    if (DCTVBase) CloseLibrary (DCTVBase);
    if (GfxBase) CloseLibrary (GfxBase);
    if (IntuitionBase) CloseLibrary (IntuitionBase);
    return 0;
}


/* -------------------- doscreen() */

struct Screen *openscreen (short width, short height, short depth);
struct DCTVCvtHandle *alloccvt (struct Screen *);

void doscreen (short width, short height, short depth)
{
    struct Screen *screen = NULL;
    struct DCTVCvtHandle *cvt = NULL;

        /* Open a screen (also conveniently creates a BitMap for use with dctv.library). */
    if (!(screen = openscreen (width, height, depth))) goto clean;

        /* Allocate DCTV conversion environment. */
    if (!(cvt = alloccvt (screen))) goto clean;

        /* Initialize Screen colors and format BitMap for a black DCTV display. */
    LoadRGB4 (&screen->ViewPort, cvt->ColorTable, cvt->NColors);
    FormatDCTV (cvt);

        /* Make a frame. */
    initgrad (cvt);                 /* Init gradient data. */

        /*
            Line rendering loop.

            Loop until all Destination lines have been converted
            (indicated by DstLineNum >= height).

            Because the filter process introduces an N-line delay
            between source and destination lines, you must call
            CvtDCTVLine() until all the destination lines have been
            converted.  That means the code in this loop will be
            executed (height + NDelayLines) times.

            Your rendering code should use SrcLineNum to indicate which
            line to render.  CvtDCTVLine() ignores the RGB line buffer for
            SrcLineNum values outside the range of the ImageBounds
            rectangle.  SrcLineNum is in the range of
            0 <= SrcLineNum < Height+NDelayLines before each call; avoid
            overindex your rendering code.
        */
    while (cvt->DstLineNum < cvt->Height) {

                                    /* Abort on Ctrl-C. */
        if (SetSignal (0, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) goto clean;

        rendergradline (cvt);       /* Render a line of the gradient to
                                       DCTVCvtHandle RGB line buffer.
                                       This function is smart enough to
                                       handle line #'s out of range. */

        CvtDCTVLine (cvt);          /* Convert the line in the line buffer to
                                       DCTV display data. Results in rendering
                                       a line of the BitMap. */
    }

    Wait (SIGBREAKF_CTRL_C);        /* Wait until user types Control+C
                                       into shell window before quitting. */

clean:
    if (cvt) FreeDCTVCvt (cvt);
    if (screen) CloseScreen (screen);
}

struct Screen *openscreen (short width, short height, short depth)
{
    static struct NewScreen newscreen = {
        0, 0, 0, 0, 0,
        -1, -1,
        0,
        CUSTOMSCREEN | SCREENQUIET,
        NULL,
        "dctvgrad",
        NULL,
        NULL
    };
    struct Screen *screen;

    newscreen.Width = width + 15 & ~15;     /* pad width to next multiple of 16 */
    newscreen.Height = height;
    newscreen.Depth = depth;
    newscreen.ViewModes = height > 350 ? HIRESLACE_KEY : HIRES_KEY;

    if (!(screen = OpenScreen (&newscreen))) {
        puts ("OpenScreen failed\n");
    }

    return screen;
}

struct DCTVCvtHandle *alloccvt (struct Screen *screen)
{
    ULONG flags = 0;
    struct DCTVCvtHandle *cvt;
    ULONG errcode;

    if (screen->ViewPort.Modes & LACE) flags |= DCTVCVTF_Lace;
    if (!(cvt = AllocDCTVCvtTags ( screen->RastPort.BitMap,
                                   DCTVCVTA_Type,       (ULONG)DCTVCVTT_RGBtoDCTV,
                                   DCTVCVTA_Width,      (ULONG)screen->Width,
                                   DCTVCVTA_Height,     (ULONG)screen->Height,
                                   DCTVCVTA_Flags,      (ULONG)flags,
                                   DCTVCVTA_ErrorCode,  &errcode,
                                   TAG_END ))) {
        printf ("AllocDCTVCvtTags failed %lu\n", errcode);
    }

    return cvt;
}


/* -------------------- 24-bit RGB gradient functions */

#define gcent(m,l)  (((m)-(l)) / 2)         /* centering macro */

void initgrad (struct DCTVCvtHandle *cvt)
{
    short bufwidth   = cvt->Width;
    short imagewidth = cvt->ImageBounds.MaxX - cvt->ImageBounds.MinX + 1;
    short i;

        /* init line to black */
    memset (cvt->Red, 0, bufwidth);
    memset (cvt->Green, 0, bufwidth);
    memset (cvt->Blue, 0, bufwidth);

        /* ramp red horizontally */
    for (i=0; i < imagewidth; i++)
        cvt->Red [i+cvt->ImageBounds.MinX] = (ULONG)i * 255 / imagewidth;
}

void rendergradline (struct DCTVCvtHandle *cvt)
{
    short imagewidth  = cvt->ImageBounds.MaxX - cvt->ImageBounds.MinX + 1;
    short imageheight = cvt->ImageBounds.MaxY - cvt->ImageBounds.MinY + 1;
    short y = cvt->SrcLineNum - cvt->ImageBounds.MinY;
    short bluewidth;
    UBYTE greenval;

    if (y >= 0 && y < imageheight) {
            /* constant green component for line */
        greenval = (long)y * 255 / imageheight;
        memset (cvt->Green + cvt->ImageBounds.MinX, greenval, imagewidth);

            /* reverse wedge blue component */
        bluewidth = (long)(imageheight - y) * imagewidth / imageheight;
        if (bluewidth > 0)
            memset (cvt->Blue + gcent (imagewidth, bluewidth) + cvt->ImageBounds.MinX, 255-greenval, bluewidth);
    }
}
