/*===========================================================================*
 * psearch.c                                     
 *                                       
 *  Procedures concerned with the P-frame motion search          
 *                                       
 *===========================================================================*/

/*==============*
 * HEADER FILES *
 *==============*/

#include "all.h"
#include "mtypes.h"
#include "frames.h"
#include "motion_search.h"
#include "prototypes.h"
#include "fsize.h"
#include "param.h"


/*==================*
 * STATIC VARIABLES *
 *==================*/

/* none */


/*==================*
 * GLOBAL VARIABLES *
 *==================*/

int **pmvHistogram = NULL;  /* histogram of P-frame motion vectors */
int **bbmvHistogram = NULL; /* histogram of B-frame bkwd motion vectors */
int **bfmvHistogram = NULL; /* histogram of B-frame fwd motion vectors */
int pixelFullSearch;
int searchRangeP,searchRangeB;
/* The range, in half pixels in each direction, that we are to search
       when detecting motion.  Specified by RANGE statement in parameter file.
    */
int psearchAlg;
/* specified by parameter file. */

/*===============================*
 * INTERNAL PROCEDURE prototypes *
 *===============================*/


/*=====================*
 * EXPORTED PROCEDURES *
 *=====================*/

/*===========================================================================*
 *
 * PMotionSearch
 *
 *  compute the best P-frame motion vector we can.  If it's better than
 *  (*motionXP, *motionYP), update *motionXP and *motionYP to it.
 *
 * PRECONDITIONS:   The relevant block in 'current' is valid (it has not
 *          been dct'd).  Thus, the data in 'current' can be
 *          accesed through y_blocks, cr_blocks, and cb_blocks.
 *          This is not the case for the blocks in 'prev.'
 *          Therefore, references into 'prev' should be done
 *          through the struct items ref_y, ref_cr, ref_cb
 *
 * POSTCONDITIONS:  current, prev unchanged.
 *          Some computation could be saved by requiring
 *          the dct'd difference to be put into current's block
 *          elements here, depending on the search technique.
 *          However, it was decided that it mucks up the code
 *          organization a little, and the saving in computation
 *          would be relatively little (if any).
 *
 * NOTES:   the search procedure need not check the (0,0) motion vector
 *      the calling procedure has a preference toward (0,0) and it
 *      will check it itself
 *
 * SIDE EFFECTS:    none
 *
 *===========================================================================*/
void
PMotionSearch(LumBlock currentBlock, 
              MpegFrame * const prev, 
              int const by, int const bx, 
              int * const motionYP, int * const motionXP) {

    /* CALL SEARCH PROCEDURE */

    switch(psearchAlg) {
    case PSEARCH_SUBSAMPLE:
        PSubSampleSearch(currentBlock, prev, by, bx, 
                         motionYP, motionXP, searchRangeP);
        break;
    case PSEARCH_EXHAUSTIVE:
        PLocalSearch(currentBlock, prev, by, bx, 
                     motionYP, motionXP, INT_MAX, searchRangeP);
        break;
    case PSEARCH_LOGARITHMIC:
        PLogarithmicSearch(currentBlock, prev, by, bx, 
                           motionYP, motionXP, searchRangeP);
        break;
    case PSEARCH_TWOLEVEL:
        PTwoLevelSearch(currentBlock, prev, by, bx, 
                        motionYP, motionXP, INT_MAX, searchRangeP);
        break;
    default:
        fprintf(stderr, "ILLEGAL PSEARCH ALG:  %d\n", psearchAlg);
        exit(1);
    }
}


/*===========================================================================*
 *
 * SetPixelSearch
 *
 *  set the pixel search type (half or full)
 *
 * RETURNS: nothing
 *
 * SIDE EFFECTS:    pixelFullSearch
 *
 *===========================================================================*/
void
SetPixelSearch(searchType)
    char *searchType;
{
    if ( (strcmp(searchType, "FULL") == 0 ) || ( strcmp(searchType, "WHOLE") == 0 )) {
        pixelFullSearch = TRUE;
    } else if ( strcmp(searchType, "HALF") == 0 ) {
        pixelFullSearch = FALSE;
    } else {
        fprintf(stderr, "ERROR:  Invalid pixel search type:  %s\n",
                searchType);
        exit(1);
    }
}


/*===========================================================================*
 *
 * SetPSearchAlg
 *
 *  set the P-search algorithm
 *
 * RETURNS: nothing
 *
 * SIDE EFFECTS:    psearchAlg
 *
 *===========================================================================*/
void
SetPSearchAlg(alg)
    char *alg;
{
    if ( strcmp(alg, "EXHAUSTIVE") == 0 ) {
        psearchAlg = PSEARCH_EXHAUSTIVE;
    } else if (strcmp(alg, "SUBSAMPLE") == 0 ) {
        psearchAlg = PSEARCH_SUBSAMPLE;
    } else if ( strcmp(alg, "LOGARITHMIC") == 0 ) {
        psearchAlg = PSEARCH_LOGARITHMIC;
    } else if ( strcmp(alg, "TWOLEVEL") == 0 ) {
        psearchAlg = PSEARCH_TWOLEVEL;
    } else {
        fprintf(stderr, "ERROR:  Invalid psearch algorithm:  %s\n", alg);
        exit(1);
    }
}


/*===========================================================================*
 *
 * PSearchName
 *
 *  returns a string containing the name of the search algorithm
 *
 * RETURNS: pointer to the string
 *
 * SIDE EFFECTS:    none
 *
 *===========================================================================*/
char *
PSearchName()
{
    char *retval;

    switch(psearchAlg) {
    case PSEARCH_EXHAUSTIVE:
        retval = "EXHAUSTIVE";break;
    case PSEARCH_SUBSAMPLE:
        retval = "SUBSAMPLE";break;
    case PSEARCH_LOGARITHMIC:
        retval = "LOGARITHMIC";break;
    case PSEARCH_TWOLEVEL:
        retval = "TWOLEVEL";break;
    default:
        fprintf(stderr, "ERROR:  Illegal PSEARCH ALG:  %d\n", psearchAlg);
        exit(1);
        break;
    }
    return retval;
}


/*===========================================================================*
 *
 * SetSearchRange
 *
 *  sets the range of the search to the given number of pixels,
 *  allocate histogram storage
 *
 *===========================================================================*/
void
SetSearchRange(int const pixelsP, int const pixelsB) {

    searchRangeP = 2*pixelsP;   /* +/- 'pixels' pixels */
    searchRangeB = 2*pixelsB;

    if ( computeMVHist ) {
        int const max_search = max(searchRangeP, searchRangeB);

        int index;
    
        pmvHistogram = (int **) malloc((2*searchRangeP+3)*sizeof(int *));
        bbmvHistogram = (int **) malloc((2*searchRangeB+3)*sizeof(int *));
        bfmvHistogram = (int **) malloc((2*searchRangeB+3)*sizeof(int *));
        for ( index = 0; index < 2*max_search+3; index++ ) {
            pmvHistogram[index] = 
                (int *) calloc(2*searchRangeP+3, sizeof(int));
            bbmvHistogram[index] = 
                (int *) calloc(2*searchRangeB+3, sizeof(int));
            bfmvHistogram[index] = 
                (int *) calloc(2*searchRangeB+3, sizeof(int));
        }
    }
}


/*===========================================================================*
 *
 *              USER-MODIFIABLE
 *
 * MotionSearchPreComputation
 *
 *  do whatever you want here; this is called once per frame, directly
 *  after reading
 *
 * RETURNS: whatever
 *
 * SIDE EFFECTS:    whatever
 *
 *===========================================================================*/
void
MotionSearchPreComputation(frame)
    MpegFrame *frame;
{
    /* do nothing */
}


/*===========================================================================*
 *
 * PSubSampleSearch
 *
 *  uses the subsampling algorithm to compute the P-frame vector
 *
 * RETURNS: motion vector
 *
 * SIDE EFFECTS:    none
 *
 * REFERENCE:  Liu and Zaccarin:  New Fast Algorithms for the Estimation
 *      of Block Motion Vectors, IEEE Transactions on Circuits
 *      and Systems for Video Technology, Vol. 3, No. 2, 1993.
 *
 *===========================================================================*/
int
PSubSampleSearch(LumBlock currentBlock, MpegFrame *prev, 
                 int by, int bx, int * motionYP, int * motionXP, 
                 int searchRange)
{
    register int mx, my;
    int diff, bestBestDiff;
    int     stepSize;
    register int x;
    int     bestMY[4], bestMX[4], bestDiff[4];
    int     leftMY, leftMX;
    int     rightMY, rightMX;

    stepSize = (pixelFullSearch ? 2 : 1);

    COMPUTE_MOTION_BOUNDARY(by,bx,stepSize,leftMY,leftMX,rightMY,rightMX);

    if ( searchRange < rightMY ) {
        rightMY = searchRange;
    }

    if ( searchRange < rightMX ) {
        rightMX = searchRange;
    }

    for ( x = 0; x < 4; x++ ) {
        bestMY[x] = 0;
        bestMX[x] = 0;
        bestDiff[x] = INT_MAX;
    }

    /* do A pattern */
    for ( my = -searchRange; my < rightMY; my += 2*stepSize ) {
        if ( my < leftMY ) {
            continue;
        }

        for ( mx = -searchRange; mx < rightMX; mx += 2*stepSize ) {
            if ( mx < leftMX ) {
                continue;
            }

            diff = LumMotionErrorA(currentBlock, prev, by, bx, my, mx, 
                                   bestDiff[0]);

            if ( diff < bestDiff[0] ) {
                bestMY[0] = my;
                bestMX[0] = mx;
                bestDiff[0] = diff;
            }
        }
    }

    /* do B pattern */
    for ( my = stepSize-searchRange; my < rightMY; my += 2*stepSize ) {
        if ( my < leftMY ) {
            continue;
        }

        for ( mx = -searchRange; mx < rightMX; mx += 2*stepSize ) {
            if ( mx < leftMX ) {
                continue;
            }

            diff = LumMotionErrorB(currentBlock, prev, by, bx, my, mx, 
                                   bestDiff[1]);

            if ( diff < bestDiff[1] ) {
                bestMY[1] = my;
                bestMX[1] = mx;
                bestDiff[1] = diff;
            }
        }
    }

    /* do C pattern */
    for ( my = stepSize-searchRange; my < rightMY; my += 2*stepSize ) {
        if ( my < leftMY ) {
            continue;
        }

        for ( mx = stepSize-searchRange; mx < rightMX; mx += 2*stepSize ) {
            if ( mx < leftMX ) {
                continue;
            }

            diff = LumMotionErrorC(currentBlock, prev, by, bx, my, mx, 
                                   bestDiff[2]);

            if ( diff < bestDiff[2] ) {
                bestMY[2] = my;
                bestMX[2] = mx;
                bestDiff[2] = diff;
            }
        }
    }

    /* do D pattern */
    for ( my = -searchRange; my < rightMY; my += 2*stepSize ) {
        if ( my < leftMY ) {
            continue;
        }

        for ( mx = stepSize-searchRange; mx < rightMX; mx += 2*stepSize ) {
            if ( mx < leftMX ) {
                continue;
            }

            diff = LumMotionErrorD(currentBlock, prev, by, bx, my, mx, 
                                   bestDiff[3]);

            if ( diff < bestDiff[3] ) {
                bestMY[3] = my;
                bestMX[3] = mx;
                bestDiff[3] = diff;
            }
        }
    }

    /* first check old motion */
    if ( (*motionYP >= leftMY) && (*motionYP < rightMY) &&
         (*motionXP >= leftMX) && (*motionXP < rightMX) ) {
        bestBestDiff = LumMotionError(currentBlock, prev, by, bx, 
                                      *motionYP, *motionXP, INT_MAX);
    } else {
        bestBestDiff = INT_MAX;
    }

    /* look at Error of 4 different motion vectors */
    for ( x = 0; x < 4; x++ ) {
        bestDiff[x] = LumMotionError(currentBlock, prev, by, bx,
                                     bestMY[x], bestMX[x], bestBestDiff);

        if ( bestDiff[x] < bestBestDiff ) {
            bestBestDiff = bestDiff[x];
            *motionYP = bestMY[x];
            *motionXP = bestMX[x];
        }
    }

    return bestBestDiff;
}



static void
findBestSpaced(int const minBoundY, int const minBoundX, 
               int const maxBoundY, int const maxBoundX,
               int const spacing,
               LumBlock currentBlock, 
               MpegFrame * const prev,
               int const by, int const bx,
               int * const bestDiffP, 
               int * const centerYP, int * const centerXP) {
/*----------------------------------------------------------------------------
   Examine every 'spacing'th half-pixel within the rectangle 
   ('minBoundX', 'minBoundY', 'maxBoundX', 'maxBoundY'), 

   If one of the half-pixels examined has a lower "LumMotionError" value
   than *bestDiffP, update *bestDiffP to that value and update
   *centerYP and *centerXP to the location of that half-pixel.
-----------------------------------------------------------------------------*/
    int my;
    int bestDiff;
    
    bestDiff = *bestDiffP; /* initial value */

    for (my = minBoundY; my < maxBoundY; my += spacing) {
        int mx;

        for (mx = minBoundX; mx < maxBoundX; mx += spacing) {
            int diff;
            
            diff = LumMotionError(currentBlock, prev, by, bx, my, mx, 
                                  bestDiff);
            
            if ( diff < bestDiff ) {
                *centerYP = my;
                *centerXP = mx;
                
                bestDiff = diff;
            }
        }
    }
    *bestDiffP = bestDiff;
}



/*===========================================================================*
 *
 * PLogarithmicSearch
 *
 *  uses logarithmic search to compute the P-frame vector
 *
 * RETURNS: motion vector
 *
 * SIDE EFFECTS:    none
 *
 * REFERENCE:  MPEG-I specification, pages 32-33
 *
 *===========================================================================*/
int
PLogarithmicSearch(LumBlock currentBlock, 
                   MpegFrame * const prev, 
                   int const by, int const bx, 
                   int * const motionYP, int * const motionXP, 
                   int const searchRange) {

    int const stepSize = (pixelFullSearch ? 2 : 1);

    int minMY, minMX, maxMY, maxMX;
    int spacing;  /* grid spacing */
    int motionX, motionY;
        /* Distance from (bx,by) (in half-pixels) of the block that is most
           like the current block among those that we have examined so far.
           (0,0) means we haven't examined any.
        */
    int     bestDiff;
        /* The difference between the current block and the block offset
           (motionX, motionY) from it.
        */

    COMPUTE_MOTION_BOUNDARY(by, bx, stepSize, minMY, minMX, maxMY, maxMX);
    minMX = max(minMX, - searchRange);
    minMY = max(minMY, - searchRange);
    maxMX = min(maxMX, + searchRange);
    maxMY = min(maxMY, + searchRange);

    /* Note: The clipping to 'searchRange' above may seem superfluous because
       the basic algorithm would never want to look more than 'searchRange'
       pixels away, but with rounding error, it can.
    */

    motionX = 0;
    motionY = 0;
    bestDiff = INT_MAX;

    for( spacing = searchRange; spacing >= stepSize; ) {
        if ( stepSize == 2 ) {  /* make sure spacing is even */
            if ( spacing == 2 ) 
                spacing = 0;
            else {
                spacing = (spacing+1)/2;
                if ( (spacing % 2) != 0 ) 
                    spacing--;
            }
        } else {
            if ( spacing == 1 ) {
                spacing = 0;
            } else 
                spacing = (spacing+1)/2;
        }
        if (spacing >= stepSize) 
            findBestSpaced(max(minMY, motionY - spacing),
                           max(minMX, motionX - spacing),
                           min(maxMY, motionY + spacing + 1),
                           min(maxMX, motionX + spacing + 1),
                           spacing,
                           currentBlock, prev, by, bx,
                           &bestDiff, &motionY, &motionX);
    }

    {
        int     diff;
        /* check old motion -- see if it's better */
        if ( (*motionYP >= minMY) && (*motionYP < maxMY) &&
             (*motionXP >= minMX) && (*motionXP < maxMX) ) {
            diff = LumMotionError(currentBlock, prev, by, bx, 
                                  *motionYP, *motionXP, bestDiff);
        } else 
            diff = INT_MAX;
        
        if ( bestDiff < diff ) {
            *motionYP = motionY;
            *motionXP = motionX;
        } else 
            bestDiff = diff;
    }

    return bestDiff;
}


/*===========================================================================*
 *
 * PLocalSearch
 *
 *  uses local exhaustive search to compute the P-frame vector
 *
 * RETURNS: motion vector
 *
 * SIDE EFFECTS:    none
 *
 *===========================================================================*/
int
PLocalSearch(LumBlock currentBlock, MpegFrame * prev, 
             int by, int bx, int * motionYP, int * motionXP, 
             int bestSoFar, int searchRange)
{
    register int mx, my;
    int     diff, bestDiff;
    int     stepSize;
    int     leftMY, leftMX;
    int     rightMY, rightMX;
    int     distance;
    int     tempRightMY, tempRightMX;

    stepSize = (pixelFullSearch ? 2 : 1);

    COMPUTE_MOTION_BOUNDARY(by,bx,stepSize,leftMY,leftMX,rightMY,rightMX);

    /* try old motion vector first */
    if ( VALID_MOTION(*motionYP, *motionXP) ) {
        bestDiff = LumMotionError(currentBlock, prev, by, bx, 
                                  *motionYP, *motionXP, bestSoFar);

        if ( bestSoFar < bestDiff ) {
            bestDiff = bestSoFar;
        }
    } else {
        *motionYP = 0;
        *motionXP = 0;

        bestDiff = bestSoFar;
    }

    /* try a spiral pattern */    
    for ( distance = stepSize; distance <= searchRange;
          distance += stepSize ) {
        tempRightMY = rightMY;
        if ( distance < tempRightMY ) {
            tempRightMY = distance;
        }
        tempRightMX = rightMX;
        if ( distance < tempRightMX ) {
            tempRightMX = distance;
        }

        /* do top, bottom */
        for ( my = -distance; my < tempRightMY;
              my += max(tempRightMY+distance-stepSize, stepSize) ) {
            if ( my < leftMY ) {
                continue;
            }

            for ( mx = -distance; mx < tempRightMX; mx += stepSize ) {
                if ( mx < leftMX ) {
                    continue;
                }

                diff = LumMotionError(currentBlock, prev, by, bx, my, mx, 
                                      bestDiff);

                if ( diff < bestDiff ) {
                    *motionYP = my;
                    *motionXP = mx;
                    bestDiff = diff;
                }
            }
        }

        /* do left, right */
        for ( mx = -distance; mx < tempRightMX;
              mx += max(tempRightMX+distance-stepSize, stepSize) ) {
            if ( mx < leftMX ) {
                continue;
            }

            for ( my = -distance+stepSize; my < tempRightMY-stepSize;
                  my += stepSize ) {
                if ( my < leftMY ) {
                    continue;
                }

                diff = LumMotionError(currentBlock, prev, by, bx, my, mx, 
                                      bestDiff);

                if ( diff < bestDiff ) {
                    *motionYP = my;
                    *motionXP = mx;
                    bestDiff = diff;
                }
            }
        }
    }

    return bestDiff;
}


/*===========================================================================*
 *
 * PTwoLevelSearch
 *
 *  uses two-level search to compute the P-frame vector
 *  first does exhaustive full-pixel search, then looks at neighboring
 *  half-pixel motion vectors
 *
 * RETURNS: motion vector
 *
 * SIDE EFFECTS:    none
 *
 *===========================================================================*/
int
PTwoLevelSearch(LumBlock currentBlock, MpegFrame * prev, 
                int by, int bx, 
                int * motionYP, int * motionXP, 
                int bestSoFar, int searchRange)
{
    register int mx, my;
    register int   loopInc;
    int diff, bestDiff;
    int     leftMY, leftMX;
    int     rightMY, rightMX;
    int     distance;
    int     tempRightMY, tempRightMX;
    int     xOffset, yOffset;

    /* exhaustive full-pixel search first */

    COMPUTE_MOTION_BOUNDARY(by,bx,2,leftMY,leftMX,rightMY,rightMX);

    rightMY--;
    rightMX--;

    /* convert vector into full-pixel vector */
    if ( *motionYP > 0 ) {
        if ( ((*motionYP) % 2) == 1 ) {
            (*motionYP)--;
        }
    } else if ( ((-(*motionYP)) % 2) == 1 ) {
        (*motionYP)++;
    }

    if ( *motionXP > 0 ) {
        if ( ((*motionXP) % 2) == 1 ) {
            (*motionXP)--;
        }
    } else if ( ((-(*motionXP)) % 2) == 1 ) {
        (*motionXP)++;
    }

    /* try old motion vector first */
    if ( VALID_MOTION(*motionYP, *motionXP) ) {
        bestDiff = LumMotionError(currentBlock, prev, by, bx, 
                                  *motionYP, *motionXP, bestSoFar);

        if ( bestSoFar < bestDiff ) {
            bestDiff = bestSoFar;
        }
    } else {
        *motionYP = 0;
        *motionXP = 0;

        bestDiff = bestSoFar;
    }

    rightMY++;
    rightMX++;

    /* try a spiral pattern */    
    for ( distance = 2; distance <= searchRange; distance += 2 ) {
        tempRightMY = rightMY;
        if ( distance < tempRightMY ) {
            tempRightMY = distance;
        }
        tempRightMX = rightMX;
        if ( distance < tempRightMX ) {
            tempRightMX = distance;
        }

        /* do top, bottom */
        loopInc = max(tempRightMY+distance-2, 2);
        for ( my = -distance; my < tempRightMY; my += loopInc ) {
            if ( my < leftMY ) {
                continue;
            }

            for ( mx = -distance; mx < tempRightMX; mx += 2 ) {
                if ( mx < leftMX ) {
                    continue;
                }

                diff = LumMotionError(currentBlock, prev, by, bx, my, mx, 
                                      bestDiff);

                if ( diff < bestDiff ) {
                    *motionYP = my;
                    *motionXP = mx;
                    bestDiff = diff;
                }
            }
        }

        /* do left, right */
        loopInc = max(tempRightMX+distance-2, 2);
        for ( mx = -distance; mx < tempRightMX; mx += loopInc ) {
            if ( mx < leftMX ) {
                continue;
            }

            for ( my = -distance+2; my < tempRightMY-2; my += 2 ) {
                if ( my < leftMY ) {
                    continue;
                }

                diff = LumMotionError(currentBlock, prev, by, bx, my, mx, 
                                      bestDiff);

                if ( diff < bestDiff ) {
                    *motionYP = my;
                    *motionXP = mx;
                    bestDiff = diff;
                }
            }
        }
    }

    /* now look at neighboring half-pixels */
    my = *motionYP;
    mx = *motionXP;

    rightMY--;
    rightMX--;

    for ( yOffset = -1; yOffset <= 1; yOffset++ ) {
        for ( xOffset = -1; xOffset <= 1; xOffset++ ) {
            if ( (yOffset == 0) && (xOffset == 0) )
                continue;

            if ( VALID_MOTION(my+yOffset, mx+xOffset) &&
                 ((diff = LumMotionError(currentBlock, prev, by, bx,
                                         my+yOffset, mx+xOffset, bestDiff)) 
                  < bestDiff) ) {
                *motionYP = my+yOffset;
                *motionXP = mx+xOffset;
                bestDiff = diff;
            }
        }
    }

    return bestDiff;
}


void
ShowPMVHistogram(fpointer)
    FILE *fpointer;
{
    register int x, y;
    int *columnTotals;
    int rowTotal;

    columnTotals = (int *) calloc(2*searchRangeP+3, sizeof(int));

#ifdef COMPLETE_DISPLAY
    fprintf(fpointer, "    ");
    for ( y = 0; y < 2*searchRange+3; y++ ) {
        fprintf(fpointer, "%3d ", y-searchRangeP-1);
    }
    fprintf(fpointer, "\n");
#endif

    for ( x = 0; x < 2*searchRangeP+3; x++ ) {
#ifdef COMPLETE_DISPLAY
        fprintf(fpointer, "%3d ", x-searchRangeP-1);
#endif
        rowTotal = 0;
        for ( y = 0; y < 2*searchRangeP+3; y++ ) {
            fprintf(fpointer, "%3d ", pmvHistogram[x][y]);
            rowTotal += pmvHistogram[x][y];
            columnTotals[y] += pmvHistogram[x][y];
        }
#ifdef COMPLETE_DISPLAY
        fprintf(fpointer, "%4d\n", rowTotal);
#else
        fprintf(fpointer, "\n");
#endif
    }

#ifdef COMPLETE_DISPLAY
    fprintf(fpointer, "Tot ");
    for ( y = 0; y < 2*searchRangeP+3; y++ ) {
        fprintf(fpointer, "%3d ", columnTotals[y]);
    }
#endif
    fprintf(fpointer, "\n");
}


void
ShowBBMVHistogram(fpointer)
    FILE *fpointer;
{
    register int x, y;
    int *columnTotals;
    int rowTotal;

    fprintf(fpointer, "B-frame Backwards:\n");

    columnTotals = (int *) calloc(2*searchRangeB+3, sizeof(int));

#ifdef COMPLETE_DISPLAY
    fprintf(fpointer, "    ");
    for ( y = 0; y < 2*searchRangeB+3; y++ ) {
        fprintf(fpointer, "%3d ", y-searchRangeB-1);
    }
    fprintf(fpointer, "\n");
#endif

    for ( x = 0; x < 2*searchRangeB+3; x++ ) {
#ifdef COMPLETE_DISPLAY
        fprintf(fpointer, "%3d ", x-searchRangeB-1);
#endif
        rowTotal = 0;
        for ( y = 0; y < 2*searchRangeB+3; y++ ) {
            fprintf(fpointer, "%3d ", bbmvHistogram[x][y]);
            rowTotal += bbmvHistogram[x][y];
            columnTotals[y] += bbmvHistogram[x][y];
        }
#ifdef COMPLETE_DISPLAY
        fprintf(fpointer, "%4d\n", rowTotal);
#else
        fprintf(fpointer, "\n");
#endif
    }

#ifdef COMPLETE_DISPLAY
    fprintf(fpointer, "Tot ");
    for ( y = 0; y < 2*searchRangeB+3; y++ ) {
        fprintf(fpointer, "%3d ", columnTotals[y]);
    }
#endif
    fprintf(fpointer, "\n");
}


void
ShowBFMVHistogram(fpointer)
    FILE *fpointer;
{
    register int x, y;
    int *columnTotals;
    int rowTotal;

    fprintf(fpointer, "B-frame Forwards:\n");

    columnTotals = (int *) calloc(2*searchRangeB+3, sizeof(int));

#ifdef COMPLETE_DISPLAY
    fprintf(fpointer, "    ");
    for ( y = 0; y < 2*searchRangeB+3; y++ ) {
        fprintf(fpointer, "%3d ", y-searchRangeB-1);
    }
    fprintf(fpointer, "\n");
#endif

    for ( x = 0; x < 2*searchRangeB+3; x++ ) {
#ifdef COMPLETE_DISPLAY
        fprintf(fpointer, "%3d ", x-searchRangeB-1);
#endif
        rowTotal = 0;
        for ( y = 0; y < 2*searchRangeB+3; y++ ) {
            fprintf(fpointer, "%3d ", bfmvHistogram[x][y]);
            rowTotal += bfmvHistogram[x][y];
            columnTotals[y] += bfmvHistogram[x][y];
        }
#ifdef COMPLETE_DISPLAY
        fprintf(fpointer, "%4d\n", rowTotal);
#else
        fprintf(fpointer, "\n");
#endif
    }

#ifdef COMPLETE_DISPLAY
    fprintf(fpointer, "Tot ");
    for ( y = 0; y < 2*searchRangeB+3; y++ ) {
        fprintf(fpointer, "%3d ", columnTotals[y]);
    }
#endif
    fprintf(fpointer, "\n");
}


/*
 * Copyright (c) 1995 The Regents of the University of California.
 * All rights reserved.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose, without fee, and without written agreement is
 * hereby granted, provided that the above copyright notice and the following
 * two paragraphs appear in all copies of this software.
 *
 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
 * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
 * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 */

/*  
 *  $Header: /u/smoot/md/mpeg_encode/RCS/psearch.c,v 1.9 1995/01/19 23:09:12 eyhung Exp $
 *  $Log: psearch.c,v $
 * Revision 1.9  1995/01/19  23:09:12  eyhung
 * Changed copyrights
 *
 * Revision 1.9  1995/01/19  23:09:12  eyhung
 * Changed copyrights
 *
 * Revision 1.8  1994/12/07  00:40:36  smoot
 * Added seperate P and B search ranges
 *
 * Revision 1.7  1994/11/12  02:09:45  eyhung
 * full pixel bug
 * fixed on lines 512 and 563
 *
 * Revision 1.6  1994/03/15  00:27:11  keving
 * nothing
 *
 * Revision 1.5  1993/12/22  19:19:01  keving
 * nothing
 *
 * Revision 1.4  1993/07/22  22:23:43  keving
 * nothing
 *
 * Revision 1.3  1993/06/30  20:06:09  keving
 * nothing
 *
 * Revision 1.2  1993/06/03  21:08:08  keving
 * nothing
 *
 * Revision 1.1  1993/03/02  18:27:05  keving
 * nothing
 *
 */


