/**********************************************************************
*
*   graygrad.c - Convert DCTV gradient to 4-bit gray screen.
*
*   Copyright (C) 1992 Digital Creations, Inc.
*
*   DESCRIPTION
*       This example demonstrates how to call dctv.library to convert
*       a DCTV display BitMap to 24-bit RGB.  The source DCTV BitMap
*       is a gradient generated in the same manner as in dctvgrad.c.
*       This DCTV image is then converted line by line to 24-bit RGB.
*       Then the upper 4 bits of the blue component are displayed on
*       a 16-color hires screen.
*
*       (not particularly flashy, but at least you get to see something).
*
*       Program runs under Amiga OS v1.3 and higher.
*
*   USAGE
*       graygrad <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 graygrad
*
*       Aztec C:    (v5.0a or higher)
*           cc graygrad [works w/ either 16 or 32 bit int's]
*           ln graygrad.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 <exec/memory.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
#define READPIXEL 0         /* selects DCTV->RGB conversion method: set to 1 to use ReadDCTVPixel(), 0 to use CvtDCTVLine() */

#define LaceHeight(height) (height > 350)


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

struct Library *DCTVBase, *GfxBase, *IntuitionBase;


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

void dograd (short width, short height, short depth);
BOOL makedctvbm (struct BitMap *dctvbm, UWORD *palette, short width, short height);
void showgrad (struct BitMap *dctvbm, UWORD *palette, short width, short height);

    /* bitmap allocator */
struct BitMap *allocbm (UWORD width, UWORD height, UBYTE depth);
void freebm (struct BitMap *);

    /* 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;
    }

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

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


/* -------------------- dograd() */

void dograd (short width, short height, short depth)
{
    struct BitMap *dctvbm = NULL;
    UWORD *palette = NULL;

        /* pad width to next multiple of 16 */
    width = width + 15 & ~15;

        /* allocate a bitmap and palette to hold DCTV display image.
           Notice that this BitMap is not allocated in chip ram as the
           conversions don't require the bitmap to be displayable. */
    if (!(dctvbm = allocbm (width, height, depth)) ||
        !(palette = AllocMem (sizeof (UWORD) << depth, MEMF_ANY | MEMF_CLEAR))) {
        puts ("Out of memory\n");
        goto clean;
    }

        /* build DCTV bitmap (draw same gradient as dctvgrad.c) */
    if (!makedctvbm (dctvbm, palette, width, height)) goto clean;

        /* convert DCTV display image to a 16-color gray screen of the
           same size as the BitMap */
    showgrad (dctvbm, palette, width, height);

clean:
    if (palette) FreeMem (palette, sizeof (UWORD) << depth);
    if (dctvbm) freebm (dctvbm);
}


/* -------------------- DCTV gradient bitmap builder */

/* this part is pretty much the same as in dctvgrad.c */

BOOL makedctvbm (struct BitMap *dctvbm, UWORD *palette, short width, short height)
{
    struct DCTVCvtHandle *cvt = NULL;
    ULONG errcode;
    BOOL rtn = FALSE;

    puts ("Making gradient.");

        /* Allocate DCTV conversion environment. */
    if (!(cvt = AllocDCTVCvtTags ( dctvbm,
                                   DCTVCVTA_Type,       (ULONG)DCTVCVTT_RGBtoDCTV,
                                   DCTVCVTA_Width,      (ULONG)width,
                                   DCTVCVTA_Height,     (ULONG)height,
                                   DCTVCVTA_Flags,      (ULONG)(LaceHeight(height) ? DCTVCVTF_Lace : 0),
                                   DCTVCVTA_ErrorCode,  &errcode,
                                   TAG_END ))) {

        printf ("AllocDCTVCvtTags failed %lu\n", errcode);
        goto clean;
    }

        /* copy ColorTable to palette */
    CopyMem (cvt->ColorTable, palette, cvt->NColors * sizeof (UWORD));

        /* initialize gradient */
    initgrad (cvt);                 /* Init gradient data. */

        /* Line rendering loop.  (See dctvgrad.c for a more complete explanation of this) */
    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. */
    }

    rtn = TRUE;

clean:
    if (cvt) FreeDCTVCvt (cvt);
    return rtn;
}


/* -------------------- gray gradient display */

struct Screen *openscreen (short width, short height);
BOOL rendergrayscreen (struct Screen *, struct DCTVCvtHandle *);

void showgrad (struct BitMap *dctvbm, UWORD *palette, short width, short height)
{
    struct Screen *screen = NULL;
    struct DCTVCvtHandle *cvt = NULL;
    ULONG errcode;

    puts ("Building Screen.");

    if (!(screen = openscreen (width, height))) {
        puts ("openscreen() failed\n");
        goto clean;
    }

    if (!(cvt = AllocDCTVCvtTags ( dctvbm,
                                   DCTVCVTA_Type,       (ULONG)DCTVCVTT_DCTVtoRGB,
                                   DCTVCVTA_Width,      (ULONG)width,
                                   DCTVCVTA_Height,     (ULONG)height,
                                   DCTVCVTA_Flags,      (ULONG)(LaceHeight(height) ? DCTVCVTF_Lace : 0),
                                   DCTVCVTA_ColorTable, palette,
                                   DCTVCVTA_ErrorCode,  &errcode,
                                   TAG_END ))) {

        printf ("AllocDCTVCvtTags() failed %lu\n", errcode);
        goto clean;
    }

    if (!rendergrayscreen (screen, cvt)) goto clean;

    puts ("Press Ctrl-C");
    Wait (SIGBREAKF_CTRL_C);

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

struct Screen *openscreen (short width, short height)
{
    static struct NewScreen newscreen = {
        0, 0, 0, 0, 4,
        -1, -1,
        0,
        CUSTOMSCREEN | SCREENQUIET,
        NULL,
        "graygrad",
        NULL,
        NULL
    };
    static UWORD palette[] = {          /* gray palette */
        0x000, 0x111, 0x222, 0x333,
        0x444, 0x555, 0x666, 0x777,
        0x888, 0x999, 0xaaa, 0xbbb,
        0xccc, 0xddd, 0xeee, 0xfff
    };
    struct Screen *screen;

    newscreen.Width = width;
    newscreen.Height = height;
    newscreen.ViewModes = LaceHeight(height) ? HIRESLACE_KEY : HIRES_KEY;

    if (screen = OpenScreen (&newscreen)) {
        LoadRGB4 (&screen->ViewPort, palette, 16);
    }

    return screen;
}

#if READPIXEL

/* display gradient using ReadDCTVPixel() */

BOOL rendergrayscreen (struct Screen *screen, struct DCTVCvtHandle *cvt)
{
    struct RastPort *rp = &screen->RastPort;
    short x, y;

    /* !!! fix docs! */
        /*
            Line rendering loop.

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

            Because the conversion process may introduce 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 RGB processing code should use DstLineNum-1
            after converting each line to figure out which RGB line was
            just made.

            Since the range of DstLineNum is
            -NDelayLines < DstLineNum <= Height after each call, you need
            to make sure that CvtDCTVLine() actually output a line for you
            to use.  If DstLineNum is in the range of
            0 < DstLineNum <= Height after calling CvtDCTVLine() then a
            line of RGB was just emitted.
        */

    /*
        Convert the current line in the RGB line buffer to 4-bit gray by
        extracting just the upper 4 bits of the Blue component.

        The caller of this function is expected to avoid calling it for
        DstLineNum values out of range.

        The line being displayed is DstLineNum-1 (DstLineNum indicates the
        _next_ line to be output by CvtDCTVLine()).

        NOTE: this loop is pathetically slow (mainly due to WritePixel()),
        but is sufficient for this example.
    */

    SetDrMd (rp, JAM1);

    for (x = 0; x < screen->Width; x++) {
                                    /* Abort on Ctrl-C. */
        if (SetSignal (0, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) return FALSE;

        for (y = 0; y < screen->Height; y++) {
            SetAPen (rp, (ReadDCTVPixel (cvt, x, y) & 0xf0) >> 4);      /* get upper 4 bits of blue */
            WritePixel (rp, x, y);
        }
    }

    return TRUE;
}

#else

/* display gradient using CvtDCTVLine() */

void rendergrayline (struct RastPort *, struct DCTVCvtHandle *);

BOOL rendergrayscreen (struct Screen *screen, struct DCTVCvtHandle *cvt)
{
        /*
            Line rendering loop.

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

            Because the conversion process may introduce 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 RGB processing code should use DstLineNum-1
            after converting each line to figure out which RGB line was
            just made.

            Since the range of DstLineNum is
            -NDelayLines < DstLineNum <= Height after each call, you need
            to make sure that CvtDCTVLine() actually output a line for you
            to use.  If DstLineNum is in the range of
            0 < DstLineNum <= Height after calling CvtDCTVLine() then a
            line of RGB was just emitted.
        */

    while (cvt->DstLineNum < cvt->Height) {

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

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

                                    /* Display 4-bit gray from 24-bit RGB.
                                       This draws directly to the Screen's Rastport. */
        if (cvt->DstLineNum > 0)
            rendergrayline (&screen->RastPort, cvt);
    }

    return TRUE;
}

void rendergrayline (struct RastPort *rp, struct DCTVCvtHandle *cvt)
{
    short x;
    short y = cvt->DstLineNum - 1;      /* after call, subtract 1 for line just emitted */

    /*
        Convert the current line in the RGB line buffer to 4-bit gray by
        extracting just the upper 4 bits of the Blue component.

        The caller of this function is expected to avoid calling it for
        DstLineNum values out of range.

        The line being displayed is DstLineNum-1 (DstLineNum indicates the
        _next_ line to be output by CvtDCTVLine()).

        NOTE: this loop is pathetically slow (mainly due to WritePixel()),
        but is sufficient for this example.
    */

    SetDrMd (rp, JAM1);

    for (x = 0; x < cvt->Width; x++) {
        SetAPen (rp, cvt->Blue[x] >> 4);
        WritePixel (rp, x, y);
    }
}

#endif


/* -------------------- bitmap allocator */

struct BitMap *allocbm (UWORD width, UWORD height, UBYTE depth)
{
    ULONG memsize;
    PLANEPTR *ras;
    struct BitMap *bm;

    if (depth > 8) goto clean;

    if (!(bm = AllocMem (sizeof *bm, MEMF_ANY | MEMF_CLEAR))) goto clean;
    InitBitMap (bm, depth, width, height);

    memsize = (ULONG)bm->Rows * bm->BytesPerRow;
    for (ras=bm->Planes; depth--;) {
        if (!(*ras++ = AllocMem (memsize, MEMF_ANY | MEMF_CLEAR))) goto clean;
    }

    return bm;

clean:
    if (bm) freebm (bm);
    return NULL;
}

void freebm (struct BitMap *bm)
{
    ULONG rassize = (ULONG)bm->Rows * bm->BytesPerRow;
    PLANEPTR *ras;
    short i;

    for (ras = bm->Planes, i = bm->Depth; i--; ras++) {
        if (*ras) {
            FreeMem (*ras, rassize);
            *ras = NULL;
        }
    }
    FreeMem (bm, sizeof *bm);
}


/* -------------------- 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);
    }
}
