/**
 * libdmtx - Data Matrix Encoding/Decoding Library
 * Copyright 2010 Mike Laughton. All rights reserved.
 *
 * See LICENSE file in the main project directory for full
 * terms of use and distribution.
 *
 * Contact: Mike Laughton <mike@dragonflylogic.com>
 *
 * \file visualize.c
 */

#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <SDL/SDL_gfxPrimitives.h>
#include <SDL/SDL_rotozoom.h>
#include "../../dmtx.h"
#include "multi_test.h"

void ValueGridCallback(DmtxValueGrid *valueGrid, int id)
{
   int x, y;
   int imageFlipY;
   int imageHeight;

   imageHeight = dmtxImageGetProp(gState.dmtxImage, DmtxPropHeight);
   imageFlipY = gState.screen->h - (gState.imageLocY + imageHeight) - 1;

   x = gState.localOffsetX - 1; /* not sure why it needs -1 to align */
   y = gState.localOffsetY;

   switch(id) {
      case 0:
         BlitSobelGrid(gState.screen, valueGrid, x, y, CTRL_ROW2_Y, CTRL_COL1_X);
         break;
      case 1:
         BlitSobelGrid(gState.screen, valueGrid, x, y, CTRL_ROW2_Y, CTRL_COL2_X);
         break;
      case 2:
         BlitSobelGrid(gState.screen, valueGrid, x, y, CTRL_ROW2_Y, CTRL_COL3_X);
         break;
      case 3:
         BlitSobelGrid(gState.screen, valueGrid, x, y, CTRL_ROW2_Y, CTRL_COL4_X);
         break;
      case 4:
         BlitSobelGrid(gState.screen, valueGrid, x, y, CTRL_ROW3_Y, CTRL_COL1_X);
         break;
      case 5:
         BlitSobelGrid(gState.screen, valueGrid, x, y, CTRL_ROW3_Y, CTRL_COL2_X);
         break;
      case 6:
         BlitSobelGrid(gState.screen, valueGrid, x, y, CTRL_ROW3_Y, CTRL_COL4_X);
         break;
      case 7:
         BlitSobelGrid(gState.screen, valueGrid, x, y, CTRL_ROW4_Y, CTRL_COL2_X);
         break;
      case 8:
         BlitSobelGrid(gState.screen, valueGrid, x, y, CTRL_ROW4_Y, CTRL_COL3_X);
         break;
      case 9:
         BlitSobelGrid(gState.screen, valueGrid, x, y, CTRL_ROW4_Y, CTRL_COL4_X);
         break;
      default:
         break;
   }
}

void ZeroCrossingCallback(ZeroCrossing zXing, int id)
{
   int xInt, yInt;

   if(zXing.mag < 50)
      return;

   xInt = gState.imageOffsetX + (int)zXing.x;
   yInt = gState.screen->h - (gState.imageOffsetY + (int)zXing.y) - 1;

   if(xInt < 0 || xInt > CTRL_COL1_X - 2 || yInt < 0 || yInt > gState.screen->h - 1)
      return;

   switch(id) {
      case 0:
         PutPixel(gState.screen, xInt, yInt, 0x00ff0000);
         break;
      default:
         break;
   }
}

void HoughLocalCallback(DmtxHoughLocal *hough, int id)
{
   switch(id) {
      case 0:
         BlitHoughLocal(gState.screen, hough, CTRL_ROW5_Y, CTRL_COL1_X + 1);
         break;
      case 1:
         BlitHoughLocal(gState.screen, hough, CTRL_ROW6_Y, CTRL_COL1_X + 1);
         break;
      case 2:
         BlitHoughLocal(gState.screen, hough, CTRL_ROW7_Y, CTRL_COL1_X + 1);
         break;
   }
}

void VanishPointCallback(VanishPointSort *vPoints, int id)
{
   if(gState.displayVanish == DmtxFalse)
      return;

   DrawVanishingPoints(gState.screen, vPoints, CTRL_ROW7_Y, CTRL_COL1_X + 1);
}

void TimingCallback(Timing *timing0, Timing *timing1, int id)
{
   int i;
   int rowNum;

   rowNum = (id == 0) ? CTRL_ROW6_Y : CTRL_ROW5_Y;

   /* Should this be called before, as soon as local is captured? */
   BlitActiveRegion(gState.screen, gState.local, 2, CTRL_ROW5_Y, CTRL_COL3_X);

   if(gState.displayTiming == DmtxFalse)
      return;

   /* Draw timed and untimed region lines */
   if(gState.displayTiming == DmtxTrue) {
      DrawTimingDots(gState.screen, timing0, rowNum, CTRL_COL1_X);
      DrawTimingDots(gState.screen, timing1, rowNum, CTRL_COL1_X);

      for(i = -64; i <= 64; i++) {
         DrawLine(gState.screen, 64, CTRL_COL3_X, CTRL_ROW5_Y, timing0->phi,
               timing0->shift + (timing0->period * i), 2, 0xff0000ff);
         DrawLine(gState.screen, 64, CTRL_COL3_X, CTRL_ROW5_Y, timing1->phi,
               timing1->shift + (timing1->period * i), 2, 0xff0000ff);
      }
   }
}

void GridCallback(AlignmentGrid *grid, int id)
{
   switch(id) {
      case 0:
         DrawSymbolPreview(gState.screen, gState.imgFull, grid, &gState,
               CTRL_ROW5_Y, CTRL_COL1_X + 1);
         break;
      case 1:
         DrawNormalizedRegion(gState.screen, gState.imgFull, grid, &gState,
               CTRL_ROW5_Y, CTRL_COL3_X);
         break;
   }
}

void PerimeterCallback(GridRegion *region, DmtxDirection side, DmtxBarType type)
{
   DrawPerimeterPatterns(gState.screen, region, &gState, side, type);
}

/******************************************************************************/

void ShowActiveRegion(SDL_Surface *screen, SDL_Surface *active)
{
   BlitActiveRegion(screen, active, 1, CTRL_ROW1_Y, CTRL_COL1_X);
   BlitActiveRegion(screen, active, 1, CTRL_ROW1_Y, CTRL_COL2_X);
   BlitActiveRegion(screen, active, 1, CTRL_ROW1_Y, CTRL_COL3_X);
   BlitActiveRegion(screen, active, 1, CTRL_ROW1_Y, CTRL_COL4_X);
}

/**
 *
 *
 */
void BlitActiveRegion(SDL_Surface *screen, SDL_Surface *active, int zoom, int screenY, int screenX)
{
   SDL_Surface *src;
   SDL_Rect clipRect;

   clipRect.w = LOCAL_SIZE;
   clipRect.h = LOCAL_SIZE;
   clipRect.x = screenX;
   clipRect.y = screenY;

   if(zoom == 1) {
      SDL_BlitSurface(active, NULL, screen, &clipRect);
   }
   else {
      /* DO NOT USE SMOOTHING OPTION -- distorts symbol proportions */
      src = zoomSurface(active, 2.0, 2.0, 0 /* smoothing */);
      SDL_BlitSurface(src, NULL, screen, &clipRect);
      SDL_FreeSurface(src);
   }
}

/**
 *
 *
 */
void BlitSobelGrid(SDL_Surface *screen, DmtxValueGrid *cache, int x, int y, int screenY, int screenX)
{
   int row, col;
   unsigned char rgb[3];
   int width, height;
   int offset;
   int flow;
   unsigned char pixbuf[12288]; /* 64 * 64 * 3 */
   int maxFlowMag = 1020;

   SDL_Surface *surface;
   SDL_Rect clipRect;
   Uint32 rmask, gmask, bmask, amask;

#if SDL_BYTEORDER == SDL_BIG_ENDIAN
   rmask = 0xff000000;
   gmask = 0x00ff0000;
   bmask = 0x0000ff00;
   amask = 0x000000ff;
#else
   rmask = 0x000000ff;
   gmask = 0x0000ff00;
   bmask = 0x00ff0000;
   amask = 0xff000000;
#endif

   width = height = 64;

   for(row = 0; row < 64; row++)
   {
      for(col = 0; col < 64; col++)
      {
         flow = dmtxValueGridGetValue(cache, col + x, row + y);
         if(flow > 0) {
            rgb[0] = 0;
            rgb[1] = (int)((abs(flow) * 254.0)/maxFlowMag + 0.5);
            rgb[2] = 0;
         }
         else {
            rgb[0] = (int)((abs(flow) * 254.0)/maxFlowMag + 0.5);
            rgb[1] = 0;
            rgb[2] = 0;
         }

         offset = ((height - row - 1) * width + col) * 3;
         pixbuf[offset] = rgb[0];
         pixbuf[offset+1] = rgb[1];
         pixbuf[offset+2] = rgb[2];
      }
   }

   clipRect.w = LOCAL_SIZE;
   clipRect.h = LOCAL_SIZE;
   clipRect.x = screenX;
   clipRect.y = screenY;

   surface = SDL_CreateRGBSurfaceFrom(pixbuf, width, height, 24, width * 3,
         rmask, gmask, bmask, 0);

   SDL_BlitSurface(surface, NULL, screen, &clipRect);
   SDL_FreeSurface(surface);
}

/**
 *
 *
 */
void BlitHoughLocal(SDL_Surface *screen, DmtxHoughLocal *hough, int screenY, int screenX)
{
   int row, col;
   int width, height;
   int maxVal;
   int rgb[3];
   unsigned int cache;
   int offset;
   unsigned char pixbuf[24576]; /* 128 * 64 * 3 */
   SDL_Surface *surface;
   SDL_Rect clipRect;
   Uint32 rmask, gmask, bmask, amask;

#if SDL_BYTEORDER == SDL_BIG_ENDIAN
   rmask = 0xff000000;
   gmask = 0x00ff0000;
   bmask = 0x0000ff00;
   amask = 0x000000ff;
#else
   rmask = 0x000000ff;
   gmask = 0x0000ff00;
   bmask = 0x00ff0000;
   amask = 0xff000000;
#endif

   width = 128;
   height = LOCAL_SIZE;

   maxVal = 0;
   for(row = 0; row < height; row++)
   {
      for(col = 0; col < width; col++)
      {
         if(hough->bucket[row][col] > maxVal)
            maxVal = hough->bucket[row][col];
      }
   }

   for(row = 0; row < height; row++)
   {
      for(col = 0; col < width; col++)
      {
         cache = hough->bucket[row][col];

         rgb[0] = rgb[1] = rgb[2] = (int)((cache * 254.0)/maxVal + 0.5);

         offset = ((height - row - 1) * width + col) * 3;
         pixbuf[offset] = rgb[0];
         pixbuf[offset+1] = rgb[1];
         pixbuf[offset+2] = rgb[2];
      }
   }

   clipRect.w = width;
   clipRect.h = height;
   clipRect.x = screenX;
   clipRect.y = screenY;

   surface = SDL_CreateRGBSurfaceFrom(pixbuf, width, height, 24, width * 3,
         rmask, gmask, bmask, 0);

   SDL_BlitSurface(surface, NULL, screen, &clipRect);
   SDL_FreeSurface(surface);
}

/**
 *
 *
 */
Uint32
GetPixel(SDL_Surface *surface, int x, int y)
{
   int bpp = surface->format->BytesPerPixel;
   Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;

   switch(bpp) {
      case 1:
         return *p;
      case 2:
         return *(Uint16 *)p;
      case 3:
         if(SDL_BYTEORDER == SDL_BIG_ENDIAN)
            return (p[0] << 16) | (p[1] << 8) | p[2];
         else
            return p[0] | (p[1] << 8) | (p[2] << 16);
      case 4:
         return *(Uint32 *)p;
      default:
         return 0;
   }
}

void
PutPixel(SDL_Surface *surface, int x, int y, Uint32 color)
{
   int bpp = surface->format->BytesPerPixel;
   Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;

   switch(bpp) {
   case 1:
      *p = color;
      break;
   case 2:
      *(Uint16 *)p = color;
      break;
   case 3:
      if(SDL_BYTEORDER == SDL_BIG_ENDIAN) {
         p[0] = (color >> 16) & 0xff;
         p[1] = (color >> 8) & 0xff;
         p[2] = color & 0xff;
      }
      else {
         p[0] = color & 0xff;
         p[1] = (color >> 8) & 0xff;
         p[2] = (color >> 16) & 0xff;
      }
      break;
   case 4:
      *(Uint32 *)p = color;
      break;
   }
}

/**
 *
 *
 */
int RayIntersect(double *t, DmtxRay2 p0, DmtxRay2 p1)
{
   double numer, denom;
   DmtxVector2 w;

   denom = dmtxVector2Cross(&(p1.v), &(p0.v));
   if(fabs(denom) <= 0.000001)
      return DmtxFail;

   dmtxVector2Sub(&w, &(p1.p), &(p0.p));
   numer = dmtxVector2Cross(&(p1.v), &w);

   *t = numer/denom;

   return DmtxTrue;
}

/**
 *
 *
 */
int IntersectBox(DmtxRay2 ray, DmtxVector2 bb0, DmtxVector2 bb1, DmtxVector2 *p0, DmtxVector2 *p1)
{
   double tTmp, xMin, xMax, yMin, yMax;
   DmtxVector2 p[2];
   int tCount = 0;
   double extent;
   DmtxRay2 rBtm, rTop, rLft, rRgt;
   DmtxVector2 unitX = { 1.0, 0.0 };
   DmtxVector2 unitY = { 0.0, 1.0 };

   if(bb0.X < bb1.X) {
      xMin = bb0.X;
      xMax = bb1.X;
   }
   else {
      xMin = bb1.X;
      xMax = bb0.X;
   }

   if(bb0.Y < bb1.Y) {
      yMin = bb0.Y;
      yMax = bb1.Y;
   }
   else {
      yMin = bb1.Y;
      yMax = bb0.Y;
   }

   extent = xMax - xMin;

   rBtm.p.X = rTop.p.X = rLft.p.X = xMin;
   rRgt.p.X = xMax;

   rBtm.p.Y = rLft.p.Y = rRgt.p.Y = yMin;
   rTop.p.Y = yMax;

   rBtm.v = rTop.v = unitX;
   rLft.v = rRgt.v = unitY;

   if(RayIntersect(&tTmp, rBtm, ray) == DmtxPass && tTmp >= 0.0 && tTmp < extent)
      dmtxPointAlongRay2(&(p[tCount++]), &rBtm, tTmp);

   if(RayIntersect(&tTmp, rTop, ray) == DmtxPass && tTmp >= 0.0 && tTmp < extent)
      dmtxPointAlongRay2(&(p[tCount++]), &rTop, tTmp);

   if(RayIntersect(&tTmp, rLft, ray) == DmtxPass && tTmp >= 0.0 && tTmp < extent)
      dmtxPointAlongRay2(&(p[tCount++]), &rLft, tTmp);

   if(RayIntersect(&tTmp, rRgt, ray) == DmtxPass && tTmp >= 0.0 && tTmp < extent)
      dmtxPointAlongRay2(&(p[tCount++]), &rRgt, tTmp);

   if(tCount != 2)
      return DmtxFail;

   *p0 = p[0];
   *p1 = p[1];

   return DmtxTrue;
}

/**
 *
 *
 */
void DrawActiveBorder(SDL_Surface *screen, int activeExtent)
{
   Sint16 x00, y00;
   Sint16 x10, y10;
   Sint16 x11, y11;
   Sint16 x01, y01;

   x01 = 158;
   y01 = 226;

   x00 = x01;
   y00 = y01 + activeExtent + 1;

   x10 = x00 + activeExtent + 1;
   y10 = y00;

   x11 = x10;
   y11 = y01;

   lineColor(screen, x00, y00, x10, y10, 0x0000ffff);
   lineColor(screen, x10, y10, x11, y11, 0x0000ffff);
   lineColor(screen, x11, y11, x01, y01, 0x0000ffff);
   lineColor(screen, x01, y01, x00, y00, 0x0000ffff);
}

/**
 *
 *
 */
void DrawLine(SDL_Surface *screen, int baseExtent, int screenX, int screenY,
      int phi, double d, int displayScale, Uint32 color)
{
   int scaledExtent;
   DmtxVector2 bb0, bb1;
   DmtxVector2 p0, p1;
   DmtxRay2 rLine;
   DmtxPixelLoc d0, d1;

   scaledExtent = baseExtent * displayScale;
   bb0.X = bb0.Y = 0.0;
   bb1.X = bb1.Y = scaledExtent - 1;

   rLine = HoughCompactToRay(phi, d);
   dmtxVector2ScaleBy(&rLine.p, (double)displayScale);

   p0.X = p0.Y = p1.X = p1.Y = 0.0;

   if(IntersectBox(rLine, bb0, bb1, &p0, &p1) == DmtxFalse)
      return;

   d0.X = (int)(p0.X + 0.5) + screenX;
   d1.X = (int)(p1.X + 0.5) + screenX;

   d0.Y = screenY + (scaledExtent - (int)(p0.Y + 0.5) - 1);
   d1.Y = screenY + (scaledExtent - (int)(p1.Y + 0.5) - 1);

   lineColor(screen, d0.X, d0.Y, d1.X, d1.Y, color);
}

/**
 *
 *
 */
void DrawVanishingPoints(SDL_Surface *screen, VanishPointSort *sort, int screenY, int screenX)
{
   int sortIdx;
   DmtxPixelLoc d0, d1;
   Uint32 rgba;

   for(sortIdx = 0; sortIdx < sort->count; sortIdx++)
   {
      d0.X = screenX + sort->bucket[sortIdx].phi - 2;
      d1.X = d0.X + 4;
      d0.Y = screenY + (63 - sort->bucket[sortIdx].d) - 2;;
      d1.Y = d0.Y + 4;

      if(sortIdx < 2)
         rgba = 0xff0000ff; /* red: strongest */
      else if(sortIdx < 4)
         rgba = 0x999900ff; /* yellow: weaker */
      else if(sortIdx < 6)
         rgba = 0x007700ff; /* green: even weaker */
      else
         rgba = 0x000077ff; /* blue: weakest */

      rectangleColor(screen, d0.X, d0.Y, d1.X, d1.Y, rgba);
   }
}

/**
 *
 *
 */
void DrawTimingDots(SDL_Surface *screen, Timing *timing, int screenY, int screenX)
{
   int i, d;

   for(i = 0; i < 64; i++) {
      d = (int)(i * timing->period + timing->shift);
      if(d >= 64)
         break;

      PutPixel(screen, screenX + timing->phi, screenY + 63 - d, 0x00ff0000);
   }
}

/**
 *
 *
 */
void DrawNormalizedRegion(SDL_Surface *screen, DmtxImage *img,
      AlignmentGrid *region, AppState *state, int screenY, int screenX)
{
   unsigned char pixbuf[49152]; /* 128 * 128 * 3 */
   unsigned char *ptrFit, *ptrRaw;
   SDL_Rect clipRect;
   SDL_Surface *surface;
   Uint32 rmask, gmask, bmask, amask;
   int x, yImage, yDmtx;
   int xRaw, yRaw;
   int extent = 128;
   int modulesToDisplay = 16;
   int dispModExtent = extent/modulesToDisplay;
   int bytesPerRow = extent * 3;
   DmtxVector2 pFit, pRaw, pRawActive, pTmp, pCtr;
   DmtxVector2 gridTest;
   int shiftX, shiftY;

#if SDL_BYTEORDER == SDL_BIG_ENDIAN
   rmask = 0xff000000;
   gmask = 0x00ff0000;
   bmask = 0x0000ff00;
   amask = 0x000000ff;
#else
   rmask = 0x000000ff;
   gmask = 0x0000ff00;
   bmask = 0x00ff0000;
   amask = 0xff000000;
#endif

   SDL_LockSurface(state->picture);

   pTmp.X = pTmp.Y = state->activeExtent/2.0;
   dmtxMatrix3VMultiply(&pCtr, &pTmp, region->raw2fitActive);

   for(yImage = 0; yImage < extent; yImage++) {
      for(x = 0; x < extent; x++) {

         yDmtx = (extent - 1) - yImage;

         /* Adjust fitted input so unfitted center is display centered */
         pFit.X = ((x-extent/2) * (double)modulesToDisplay) /
               (region->colCount * extent) + pCtr.X;

         pFit.Y = ((yDmtx-extent/2) * (double)modulesToDisplay) /
               (region->rowCount * extent) + pCtr.Y;

         dmtxMatrix3VMultiply(&pRaw, &pFit, region->fit2rawFull);
         dmtxMatrix3VMultiply(&pRawActive, &pFit, region->fit2rawActive);

         xRaw = (pRaw.X >= 0.0) ? (int)(pRaw.X + 0.5) : (int)(pRaw.X - 0.5);
         yRaw = (pRaw.Y >= 0.0) ? (int)(pRaw.Y + 0.5) : (int)(pRaw.Y - 0.5);

         ptrFit = pixbuf + (yImage * bytesPerRow + x * 3);
         if(xRaw < 0 || xRaw >= img->width || yRaw < 0 || yRaw >= img->height) {
            ptrFit[0] = 0;
            ptrFit[1] = 0;
            ptrFit[2] = 0;
         }
         else {
            ptrRaw = (unsigned char *)img->pxl + dmtxImageGetByteOffset(img, xRaw, yRaw);

            if(pRawActive.X < 0.0 || pRawActive.X >= state->activeExtent - 1 ||
                  pRawActive.Y < 0.0 || pRawActive.Y >= state->activeExtent - 1) {
               ptrFit[0] = ptrRaw[0]/2;
               ptrFit[1] = ptrRaw[1]/2;
               ptrFit[2] = ptrRaw[2]/2;
            }
            else {
               ptrFit[0] = ptrRaw[0];
               ptrFit[1] = ptrRaw[1];
               ptrFit[2] = ptrRaw[2];
            }
         }
      }
   }

   gridTest.X = pCtr.X * region->colCount * dispModExtent;
   gridTest.X += (gridTest.X >= 0.0) ? 0.5 : -0.5;
   shiftX = (int)gridTest.X % dispModExtent;

   gridTest.Y = pCtr.Y * region->rowCount * dispModExtent;
   gridTest.Y += (gridTest.Y >= 0.0) ? 0.5 : -0.5;
   shiftY = (int)gridTest.Y % dispModExtent;

   for(yImage = 0; yImage < extent; yImage++) {

      yDmtx = (extent - 1) - yImage;

      for(x = 0; x < extent; x++) {

         if((yDmtx + shiftY) % dispModExtent != 0 &&
               (x + shiftX) % dispModExtent != 0)
            continue;

         ptrFit = pixbuf + (yImage * bytesPerRow + x * 3);

         /* If reeaally dark then add brightened grid lines */
         if(ptrFit[0] + ptrFit[1] + ptrFit[2] < 60) {
            ptrFit[0] += (255 - ptrFit[0])/8;
            ptrFit[1] += (255 - ptrFit[1])/8;
            ptrFit[2] += (255 - ptrFit[2])/8;
         }
         /* Otherwise add darkened grid lines */
         else {
            ptrFit[0] = (ptrFit[0] * 8)/10;
            ptrFit[1] = (ptrFit[1] * 8)/10;
            ptrFit[2] = (ptrFit[2] * 8)/10;
         }
      }
   }

   clipRect.w = extent;
   clipRect.h = extent;
   clipRect.x = screenX;
   clipRect.y = screenY;

   surface = SDL_CreateRGBSurfaceFrom(pixbuf, extent, extent, 24, extent * 3,
         rmask, gmask, bmask, 0);

   SDL_BlitSurface(surface, NULL, screen, &clipRect);
   SDL_FreeSurface(surface);

   SDL_UnlockSurface(state->picture);
}

/**
 *
 *
 */
Sint16 Clamp(Sint16 x, Sint16 xMin, Sint16 extent)
{
   Sint16 xMax;

   if(x < xMin)
      return xMin;

   xMax = xMin + extent - 1;
   if(x > xMax)
      return xMax;

   return x;
}

/**
 *
 *
 */
void DrawSymbolPreview(SDL_Surface *screen, DmtxImage *img, AlignmentGrid *grid,
      AppState *state, int screenY, int screenX)
{
   DmtxVector2 pTmp, pCtr;
   DmtxVector2 gridTest;
   int shiftX /*, shiftY*/;
/* int rColor, gColor, bColor, color; */
/* Sint16 x1, y1, x2, y2; */
   int extent = 128;
   int modulesToDisplay = 16;
   int dispModExtent = extent/modulesToDisplay;
/* int row, col; */
   int /*rowBeg,*/ colBeg;
   int /*rowEnd,*/ colEnd;

   SDL_LockSurface(state->picture);

   pTmp.X = pTmp.Y = state->activeExtent/2.0;
   dmtxMatrix3VMultiply(&pCtr, &pTmp, grid->raw2fitActive);

   gridTest.X = pCtr.X * grid->colCount * dispModExtent;
   gridTest.X += (gridTest.X >= 0.0) ? 0.5 : -0.5;
   shiftX = 64 - (int)gridTest.X;
   colBeg = (shiftX < 0) ? 0 : -shiftX/8 - 1;
   colEnd = max(colBeg + 17, grid->colCount);

/* something is uninitialized ... verify in cygwin
   fprintf(stdout, "colBeg:%d colEnd:%d\n", colBeg, colEnd); fflush(stdout);

   gridTest.Y = pCtr.Y * grid->rowCount * dispModExtent;
   gridTest.Y += (gridTest.Y >= 0.0) ? 0.5 : -0.5;
   shiftY = 64 - (int)gridTest.Y;
   rowBeg = (shiftY < 0) ? 0 : -shiftY/8 - 1;
   rowEnd = max(rowBeg + 17, grid->rowCount);

   for(row = rowBeg; row < rowEnd; row++) {

      y1 = row * dispModExtent + shiftY;
      y2 = y1 + dispModExtent - 1;

      y1 = (extent - 1 - y1);
      y2 = (extent - 1 - y2);

      y1 = Clamp(y1 + screenY, screenY, 128);
      y2 = Clamp(y2 + screenY, screenY, 128);

      for(col = colBeg; col < colEnd; col++) {
         rColor = dmtxReadModuleColor(img, grid, row, col, 0);
         gColor = dmtxReadModuleColor(img, grid, row, col, 1);
         bColor = dmtxReadModuleColor(img, grid, row, col, 2);
         color = (rColor << 24) | (gColor << 16) | (bColor << 8) | 0xff;

         x1 = col * dispModExtent + shiftX;
         x2 = x1 + dispModExtent - 1;

         x1 = Clamp(x1 + screenX, screenX, 128);
         x2 = Clamp(x2 + screenX, screenX, 128);

         boxColor(screen, x1, y1, x2, y2, color);
      }
   }
*/

   SDL_UnlockSurface(state->picture);
}

/**
 *
 *
 */
void DrawPerimeterPatterns(SDL_Surface *screen, GridRegion *region,
      AppState *state, DmtxDirection side, DmtxBarType type)
{
   DmtxVector2 pTmp, pCtr;
   DmtxVector2 gridTest;
   int shiftX, shiftY;
   int extent = 128;
   int modulesToDisplay = 16;
   int dispModExtent = extent/modulesToDisplay;
   Sint16 x00, y00;
   Sint16 x11, y11;

   pTmp.X = pTmp.Y = state->activeExtent/2.0;
   dmtxMatrix3VMultiply(&pCtr, &pTmp, region->grid.raw2fitActive);

   gridTest.X = pCtr.X * region->grid.colCount * dispModExtent;
   gridTest.X += (gridTest.X >= 0.0) ? 0.5 : -0.5;
   shiftX = 64 - (int)gridTest.X;

   gridTest.Y = pCtr.Y * region->grid.rowCount * dispModExtent;
   gridTest.Y += (gridTest.Y >= 0.0) ? 0.5 : -0.5;
   shiftY = 63 - (int)gridTest.Y;

   /* Calculate corner positions */
   x00 = region->x * dispModExtent + shiftX;
   y00 = region->y * dispModExtent + shiftY;
   x11 = x00 + region->width * dispModExtent;
   y11 = y00 + region->height * dispModExtent;

   DrawPerimeterSide(screen, x00, y00, x11, y11, dispModExtent, side, type);
}

/**
 *
 *
 */
void DrawPerimeterSide(SDL_Surface *screen, int x00, int y00, int x11, int y11,
      int dispModExtent, DmtxDirection side, DmtxBarType type)
{
   Sint16 xBeg, yBeg;
   Sint16 xEnd, yEnd;
   int extent = 128;
   const int screenX = CTRL_COL1_X + 1;
   const int screenY = CTRL_ROW5_Y;
   Uint32 color;

   switch(side) {
      case DmtxDirUp:
         xBeg = x00;
         xEnd = x11;
         yBeg = yEnd = y11 - dispModExtent/2;
         break;
      case DmtxDirLeft:
         yBeg = y00;
         yEnd = y11;
         xBeg = xEnd = x00 + dispModExtent/2;
         break;
      case DmtxDirDown:
         xBeg = x00;
         xEnd = x11;
         yBeg = yEnd = y00 + dispModExtent/2;
         break;
      case DmtxDirRight:
         yBeg = y00;
         yEnd = y11;
         xBeg = xEnd = x11 - dispModExtent/2;
         break;
      default:
         xBeg = x00;
         yBeg = y00;
         xEnd = x11;
         yEnd = y11;
         break;
   }

   yBeg = (extent - 1 - yBeg);
   yEnd = (extent - 1 - yEnd);

   xBeg = Clamp(xBeg + screenX, screenX, 128);
   yBeg = Clamp(yBeg + screenY, screenY, 128);

   xEnd = Clamp(xEnd + screenX, screenX, 128);
   yEnd = Clamp(yEnd + screenY, screenY, 128);

   if(type & DmtxBarFinder)
      color = 0x0000ffff;
   else if(type & DmtxBarTiming)
      color = 0xff0000ff;
   else
      color = 0x00ff00ff;

   lineColor(screen, xBeg, yBeg, xEnd, yEnd, color);
}
