コード例 #1
0
/**
 * \brief  Convert fitted Data Matrix region into a decoded message
 * \param  dec
 * \param  reg
 * \param  fix
 * \return Decoded message
 */
extern DmtxMessage *
dmtxDecodeMatrixRegion(DmtxDecode *dec, DmtxRegion *reg, int fix)
{
   DmtxMessage *msg;
   DmtxVector2 topLeft, topRight, bottomLeft, bottomRight;
   DmtxPixelLoc pxTopLeft, pxTopRight, pxBottomLeft, pxBottomRight;

   msg = dmtxMessageCreate(reg->sizeIdx, DmtxFormatMatrix);
   if(msg == NULL)
      return NULL;

   if(PopulateArrayFromMatrix(dec, reg, msg) != DmtxPass) {
      dmtxMessageDestroy(&msg);
      return NULL;
   }

   /* maybe place remaining logic into new dmtxDecodePopulatedArray()
      function so other people can pass in their own arrays */

   ModulePlacementEcc200(msg->array, msg->code,
         reg->sizeIdx, DmtxModuleOnRed | DmtxModuleOnGreen | DmtxModuleOnBlue);

   if(RsDecode(msg->code, reg->sizeIdx, fix) == DmtxFail)
   {
      dmtxMessageDestroy(&msg);
      return NULL;
   }

   topLeft.X = bottomLeft.X = topLeft.Y = topRight.Y = -0.1;
   topRight.X = bottomRight.X = bottomLeft.Y = bottomRight.Y = 1.1;

   dmtxMatrix3VMultiplyBy(&topLeft, reg->fit2raw);
   dmtxMatrix3VMultiplyBy(&topRight, reg->fit2raw);
   dmtxMatrix3VMultiplyBy(&bottomLeft, reg->fit2raw);
   dmtxMatrix3VMultiplyBy(&bottomRight, reg->fit2raw);

   pxTopLeft.X = (int)(0.5 + topLeft.X);
   pxTopLeft.Y = (int)(0.5 + topLeft.Y);
   pxBottomLeft.X = (int)(0.5 + bottomLeft.X);
   pxBottomLeft.Y = (int)(0.5 + bottomLeft.Y);
   pxTopRight.X = (int)(0.5 + topRight.X);
   pxTopRight.Y = (int)(0.5 + topRight.Y);
   pxBottomRight.X = (int)(0.5 + bottomRight.X);
   pxBottomRight.Y = (int)(0.5 + bottomRight.Y);

   CacheFillQuad(dec, pxTopLeft, pxTopRight, pxBottomRight, pxBottomLeft);

   DecodeDataStream(msg, reg->sizeIdx, NULL);

   return msg;
}
コード例 #2
0
ファイル: dmtxregion.c プロジェクト: RichardGrundy/Barcode
static double
RightAngleTrueness(DmtxVector2 c0, DmtxVector2 c1, DmtxVector2 c2, double angle)
{
   DmtxVector2 vA, vB;
   DmtxMatrix3 m;

   dmtxVector2Norm(dmtxVector2Sub(&vA, &c1, &c0));
   dmtxVector2Norm(dmtxVector2Sub(&vB, &c2, &c1));

   dmtxMatrix3Rotate(m, M_PI - angle);
   dmtxMatrix3VMultiplyBy(&vA, m);

   return dmtxVector2Dot(&vA, &vB);
}
コード例 #3
0
static PyObject *
dmtx_decode(PyObject *self, PyObject *arglist, PyObject *kwargs)
{
   int count=0;
   int found=0;
   int width;
   int height;
   int gap_size = DmtxUndefined;
   int max_count = DmtxUndefined;
   int timeout = DmtxUndefined;
   int shape = DmtxUndefined;
   int deviation = DmtxUndefined;
   int threshold = DmtxUndefined;
   int shrink = 1;
   int corrections = DmtxUndefined;
   int min_edge = DmtxUndefined;
   int max_edge = DmtxUndefined;

   PyObject *dataBuf = NULL;
   Py_ssize_t dataLen;
   PyObject *context = Py_None;
   PyObject *output = PyList_New(0);

   DmtxTime dmtx_timeout;
   DmtxImage *img;
   DmtxDecode *dec;
   DmtxRegion *reg;
   DmtxMessage *msg;
   DmtxVector2 p00, p10, p11, p01;
   const char *pxl; /* Input image buffer */

   static char *kwlist[] = { "width", "height", "data", "gap_size",
                             "max_count", "context", "timeout", "shape",
                             "deviation", "threshold", "shrink", "corrections",
                             "min_edge", "max_edge", NULL };

   /* Parse out the options which are applicable */
   PyObject *filtered_kwargs;
   filtered_kwargs = PyDict_New();
   count = 3; /* Skip the first 3 keywords as they are sent in arglist */
   while(kwlist[count]){
      if(PyDict_GetItemString(kwargs, kwlist[count])) {
         PyDict_SetItemString(filtered_kwargs, kwlist[count],
               PyDict_GetItemString(kwargs, kwlist[count]));
      }
      count++;
   }

   /* Get parameters from Python for libdmtx */
   if(!PyArg_ParseTupleAndKeywords(arglist, filtered_kwargs, "iiOi|iOiiiiiiii",
         kwlist, &width, &height, &dataBuf, &gap_size, &max_count, &context,
         &timeout, &shape, &deviation, &threshold, &shrink, &corrections,
         &min_edge, &max_edge)) {
      PyErr_SetString(PyExc_TypeError, "decode takes at least 3 arguments");
      return NULL;
   }

   Py_INCREF(context);

   /* Reset timeout for each new page */
   if(timeout != DmtxUndefined)
      dmtx_timeout = dmtxTimeAdd(dmtxTimeNow(), timeout);

   if(dataBuf == NULL) {
      PyErr_SetString(PyExc_TypeError, "Interleaved bitmapped data in buffer missing");
      return NULL;
   }

   PyObject_AsCharBuffer(dataBuf, &pxl, &dataLen);

   img = dmtxImageCreate((unsigned char *)pxl, width, height, DmtxPack24bppRGB);
   if(img == NULL)
      return NULL;

   dec = dmtxDecodeCreate(img, shrink);
   if(dec == NULL) {
      dmtxImageDestroy(&img);
      return NULL;
   }

   if(gap_size != DmtxUndefined)
      dmtxDecodeSetProp(dec, DmtxPropScanGap, gap_size);

   if(shape != DmtxUndefined)
      dmtxDecodeSetProp(dec, DmtxPropSymbolSize, shape);

   if(deviation != DmtxUndefined)
      dmtxDecodeSetProp(dec, DmtxPropSquareDevn, deviation);

   if(threshold != DmtxUndefined)
      dmtxDecodeSetProp(dec, DmtxPropEdgeThresh, threshold);

   if(min_edge != DmtxUndefined)
      dmtxDecodeSetProp(dec, DmtxPropEdgeMin, min_edge);

   if(max_edge != DmtxUndefined)
      dmtxDecodeSetProp(dec, DmtxPropEdgeMax, max_edge);

   for(count=1; ;count++) {
      Py_BEGIN_ALLOW_THREADS
      if(timeout == DmtxUndefined)
         reg = dmtxRegionFindNext(dec, NULL);
      else
         reg = dmtxRegionFindNext(dec, &dmtx_timeout);
      Py_END_ALLOW_THREADS

      /* Finished file or ran out of time before finding another region */
      if(reg == NULL)
         break;

      msg = dmtxDecodeMatrixRegion(dec, reg, corrections);
      if(msg != NULL) {
         p00.X = p00.Y = p10.Y = p01.X = 0.0;
         p10.X = p01.Y = p11.X = p11.Y = 1.0;
         dmtxMatrix3VMultiplyBy(&p00, reg->fit2raw);
         dmtxMatrix3VMultiplyBy(&p10, reg->fit2raw);
         dmtxMatrix3VMultiplyBy(&p11, reg->fit2raw);
         dmtxMatrix3VMultiplyBy(&p01, reg->fit2raw);

         PyList_Append(output, Py_BuildValue("s#((ii)(ii)(ii)(ii))", msg->output, msg->outputIdx,
               (int)((shrink * p00.X) + 0.5), height - 1 - (int)((shrink * p00.Y) + 0.5),
               (int)((shrink * p10.X) + 0.5), height - 1 - (int)((shrink * p10.Y) + 0.5),
               (int)((shrink * p11.X) + 0.5), height - 1 - (int)((shrink * p11.Y) + 0.5),
               (int)((shrink * p01.X) + 0.5), height - 1 - (int)((shrink * p01.Y) + 0.5)));

         Py_INCREF(output);
         dmtxMessageDestroy(&msg);
         found++;
      }

      dmtxRegionDestroy(&reg);

      /* Stop if we've reached maximium count */
      if(max_count != DmtxUndefined)
         if(found >= max_count) break;
   }

   dmtxDecodeDestroy(&dec);
   dmtxImageDestroy(&img);
   Py_DECREF(context);
   if(output == NULL) {
      Py_INCREF(Py_None);
      return Py_None;
   }

   return output;
}
コード例 #4
0
/**
 * Decode the image, returning tags found (as DMTXTag objects)
 */
JNIEXPORT jobjectArray JNICALL
Java_org_libdmtx_DMTXImage_getTags(JNIEnv *aEnv, jobject aImage,
      jint aTagCount, jint lSearchTimeout)
{
   jclass        lImageClass, lTagClass, lPointClass;
   jmethodID     lTagConstructor, lPointConstructor;
   jfieldID      lWidth, lHeight, lData;
   DmtxImage    *lImage;
   DmtxDecode   *lDecode;
   DmtxRegion   *lRegion;
   DmtxTime      lTimeout;
   int           lW, lH, lI;
   jintArray     lJavaData;
   jint         *lPixels;
   jobject      *lTags;
   jobjectArray  lResult;
   int           lTagCount = 0;

   /* Find DMTXImage class */
   lImageClass = (*aEnv)->FindClass(aEnv, "org/libdmtx/DMTXImage");
   if(lImageClass == NULL)
      return NULL;

   /* Find Tag class */
   lTagClass = (*aEnv)->FindClass(aEnv, "org/libdmtx/DMTXTag");
   if(lTagClass == NULL)
      return NULL;

   /* Find Point class */
   lPointClass = (*aEnv)->FindClass(aEnv, "java/awt/Point");
   if(lPointClass == NULL)
      return NULL;

   /* Find constructors */
   lTagConstructor = (*aEnv)->GetMethodID(
      aEnv, lTagClass, "<init>",
      "(Ljava/lang/String;Ljava/awt/Point;Ljava/awt/Point;Ljava/awt/Point;Ljava/awt/Point;)V"
   );

   lPointConstructor = (*aEnv)->GetMethodID(aEnv, lPointClass, "<init>", "(II)V");
   if(lTagConstructor == NULL || lPointConstructor == NULL)
      return NULL;

   /* Find fields */
   lWidth = (*aEnv)->GetFieldID(aEnv, lImageClass, "width", "I");
   lHeight = (*aEnv)->GetFieldID(aEnv, lImageClass, "height", "I");
   lData = (*aEnv)->GetFieldID(aEnv, lImageClass, "data", "[I");

   if(lWidth == NULL || lHeight == NULL || lData == NULL)
      return NULL;

   /* Get fields */
   lW = (*aEnv)->GetIntField(aEnv, aImage, lWidth);
   lH = (*aEnv)->GetIntField(aEnv, aImage, lHeight);

   lJavaData = (*aEnv)->GetObjectField(aEnv, aImage, lData);
   lPixels = (*aEnv)->GetIntArrayElements(aEnv, lJavaData, NULL);

   /* Create DmtxImage object */
   lImage = dmtxImageCreate((unsigned char *)lPixels, lW, lH, DmtxPack32bppRGBX);
   if(lImage == NULL)
      return NULL;

   /* Create DmtxDecode object */
   lDecode = dmtxDecodeCreate(lImage, 1);
   if(lDecode == NULL)
      return NULL;

   /* Allocate temporary Tag array */
   lTags = (jobject *)malloc(aTagCount * sizeof(jobject));
   if(lTags == NULL)
      return NULL;

   /* Find all tags that we can inside timeout */
   lTimeout = dmtxTimeAdd(dmtxTimeNow(), lSearchTimeout);

   while(lTagCount < aTagCount && (lRegion = dmtxRegionFindNext(lDecode, &lTimeout))) {
      jstring sStringID;
      DmtxMessage *lMessage = dmtxDecodeMatrixRegion(lDecode, lRegion, DmtxUndefined);

      if(lMessage != NULL) {
         DmtxVector2 lCorner1, lCorner2, lCorner3, lCorner4;
         jobject lJCorner1, lJCorner2, lJCorner3, lJCorner4;

         /* Calculate position of Tag */
         lCorner1.X = lCorner1.Y = lCorner2.Y = lCorner4.X = 0.0;
         lCorner2.X = lCorner4.Y = lCorner3.X = lCorner3.Y = 1.0;

         dmtxMatrix3VMultiplyBy(&lCorner1, lRegion->fit2raw);
         dmtxMatrix3VMultiplyBy(&lCorner2, lRegion->fit2raw);
         dmtxMatrix3VMultiplyBy(&lCorner3, lRegion->fit2raw);
         dmtxMatrix3VMultiplyBy(&lCorner4, lRegion->fit2raw);

         /* Create Location instances for corners */
         lJCorner1 = (*aEnv)->NewObject(aEnv, lPointClass, lPointConstructor,
               (int) lCorner1.X, (int) (lH - lCorner1.Y - 1));

         lJCorner2 = (*aEnv)->NewObject(aEnv, lPointClass, lPointConstructor,
               (int) lCorner2.X, (int) (lH - lCorner2.Y - 1));

         lJCorner3 = (*aEnv)->NewObject(aEnv, lPointClass, lPointConstructor,
               (int) lCorner3.X, (int) (lH - lCorner3.Y - 1));

         lJCorner4 = (*aEnv)->NewObject(aEnv, lPointClass, lPointConstructor,
               (int) lCorner4.X, (int) (lH - lCorner4.Y - 1));

         /* Decode Message */
         sStringID = (*aEnv)->NewStringUTF(aEnv, lMessage->output);

         /* Create Tag instance */
         lTags[lTagCount] = (*aEnv)->NewObject(aEnv, lTagClass, lTagConstructor,
               sStringID, lJCorner1, lJCorner2, lJCorner3, lJCorner4);
         if(lTags[lTagCount] == NULL)
            return NULL;

         /* Increment Count */
         lTagCount++;

         /* Free Message */
         dmtxMessageDestroy(&lMessage);
      }

      /* Free Region */
      dmtxRegionDestroy(&lRegion);
   }

   /* Free DMTX Structures */
   dmtxDecodeDestroy(&lDecode);
   dmtxImageDestroy(&lImage);

   /* Release Image Data */
   (*aEnv)->ReleaseIntArrayElements(aEnv, lJavaData, lPixels, 0);

   /* Create result array */
   lResult = (*aEnv)->NewObjectArray(aEnv, lTagCount, lTagClass, NULL);

   for(lI = 0; lI < lTagCount; lI++) {
      (*aEnv)->SetObjectArrayElement(aEnv, lResult, lI, lTags[lI]);
   }

   /* Free local references */
   (*aEnv)->DeleteLocalRef(aEnv, lJavaData);
   (*aEnv)->DeleteLocalRef(aEnv, lImageClass);
   (*aEnv)->DeleteLocalRef(aEnv, lTagClass);
   (*aEnv)->DeleteLocalRef(aEnv, lPointClass);

   return lResult;
}
コード例 #5
0
ファイル: detector.cpp プロジェクト: HRZaheri/vision_visp
  bool Detector::detect(cv::Mat& image, int timeout, unsigned int offsetx, unsigned int offsety){
    bool detected = false;
    lines_.clear();
    message_.clear();
    polygon_.clear();
    DmtxRegion     *reg;
    DmtxDecode     *dec;
    DmtxImage      *img;
    DmtxMessage    *msg;
    DmtxTime       t;

    img = dmtxImageCreate(image.data, image.cols, image.rows, DmtxPack24bppRGB);
    //dmtxImageSetProp(img, DmtxPropImageFlip, DmtxFlipY);

    dec = dmtxDecodeCreate(img, 1);
    assert(dec != NULL);

    t = dmtxTimeAdd(dmtxTimeNow(), timeout);
    reg = dmtxRegionFindNext(dec, &t);

    if(reg != NULL) {

      int height;
      int dataWordLength;
      int rotateInt;
      double rotate;
      DmtxVector2 p00, p10, p11, p01;

      height = dmtxDecodeGetProp(dec, DmtxPropHeight);

      p00.X = p00.Y = p10.Y = p01.X = 0.0;
      p10.X = p01.Y = p11.X = p11.Y = 1.0;
      dmtxMatrix3VMultiplyBy(&p00, reg->fit2raw);
      dmtxMatrix3VMultiplyBy(&p10, reg->fit2raw);
      dmtxMatrix3VMultiplyBy(&p11, reg->fit2raw);
      dmtxMatrix3VMultiplyBy(&p01, reg->fit2raw);
      polygon_.push_back(cv::Point(p00.X + offsetx,image.rows-p00.Y + offsety));
      polygon_.push_back(cv::Point(p10.X + offsetx,image.rows-p10.Y + offsety));
      polygon_.push_back(cv::Point(p11.X + offsetx,image.rows-p11.Y + offsety));
      polygon_.push_back(cv::Point(p01.X + offsetx,image.rows-p01.Y + offsety));

      lines_.push_back(
                       std::pair<cv::Point,cv::Point>(
                                                      cv::Point(p00.X + offsetx,image.rows-p00.Y + offsety),
                                                      cv::Point(p10.X + offsetx,image.rows-p10.Y + offsety)
                                                      )
                       );
      lines_.push_back(
                             std::pair<cv::Point,cv::Point>(
                                                            cv::Point(p10.X + offsetx,image.rows-p10.Y + offsety),
                                                            cv::Point(p11.X + offsetx,image.rows-p11.Y + offsety)
                                                            )
                             );
      lines_.push_back(
                             std::pair<cv::Point,cv::Point>(
                                                            cv::Point(p11.X + offsetx,image.rows-p11.Y + offsety),
                                                            cv::Point(p01.X + offsetx,image.rows-p01.Y + offsety)
                                                            )
                             );
      lines_.push_back(
                             std::pair<cv::Point,cv::Point>(
                                                            cv::Point(p01.X + offsetx,image.rows-p01.Y + offsety),
                                                            cv::Point(p00.X + offsetx,image.rows-p00.Y + offsety)
                                                            )
                             );
      detected = true;
      msg = dmtxDecodeMatrixRegion(dec, reg, DmtxUndefined);
      if(msg != NULL) {
        message_ = (const char*)msg->output;
        dmtxMessageDestroy(&msg);
      }
      dmtxRegionDestroy(&reg);
    }
      
    dmtxDecodeDestroy(&dec);
    dmtxImageDestroy(&img);
    return detected;
  }
コード例 #6
0
bool Marker_DMTX::findPattern(const sensor_msgs::Image &img, std::vector<SMarker> &res)
{
  int count=0;
  DmtxImage *dimg = dmtxImageCreate((unsigned char*)&img.data[0], img.width, img.height, DmtxPack24bppRGB);
  ROS_ASSERT(dimg);

  DmtxDecode *dec = dmtxDecodeCreate(dimg, 1);
  ROS_ASSERT(dec);

  dmtxDecodeSetProp(dec, DmtxPropEdgeThresh, 1);

  DmtxRegion *reg;
  for(count=1; ;count++) {
  
    DmtxTime timeout = dmtxTimeAdd(dmtxTimeNow(), timeout_);
    reg = dmtxRegionFindNext(dec, &timeout);
    /* Finished file or ran out of time before finding another region */
    if(reg == NULL)
    	break;

    if (reg != NULL)
    {
      DmtxMessage *msg = dmtxDecodeMatrixRegion(dec, reg, DmtxUndefined);
      if (msg != NULL)
      {
        SMarker m;
        m.code_ = std::string((const char*)msg->output,msg->outputSize);
        m.format_ = "datamatrix";

        DmtxVector2 p00, p10, p11, p01;
        p00.X = p00.Y = p10.Y = p01.X = 0.0;
        p10.X = p01.Y = p11.X = p11.Y = 1.0;
        dmtxMatrix3VMultiplyBy(&p00, reg->fit2raw);
        dmtxMatrix3VMultiplyBy(&p10, reg->fit2raw);
        dmtxMatrix3VMultiplyBy(&p11, reg->fit2raw);
        dmtxMatrix3VMultiplyBy(&p01, reg->fit2raw);

        Eigen::Vector2i v;
        v(0)=(int)((p01.X) + 0.5);
        v(1)=img.height - 1 - (int)((p01.Y) + 0.5);
        m.pts_.push_back(v);

        v(0)=(int)((p00.X) + 0.5);
        v(1)=img.height - 1 - (int)((p00.Y) + 0.5);
        m.pts_.push_back(v);

        v(0)=(int)((p11.X) + 0.5);
        v(1)=img.height - 1 - (int)((p11.Y) + 0.5);
        m.pts_.push_back(v);

        v(0)=(int)((p10.X) + 0.5);
        v(1)=img.height - 1 - (int)((p10.Y) + 0.5);
        m.pts_.push_back(v);

        res.push_back(m);

        dmtxMessageDestroy(&msg);
      }
      dmtxRegionDestroy(&reg);
    }
  }

  dmtxDecodeDestroy(&dec);
  dmtxImageDestroy(&dimg);

  return false;
}
コード例 #7
0
ファイル: dmtxregion.c プロジェクト: RichardGrundy/Barcode
/**
 * @brief  XXX
 * @param  image
 * @param  reg
 * @param  edgeLoc
 * @param  preFit2Raw
 * @param  postRaw2Fit
 * @return DMTX_SUCCESS | DMTX_FAILURE
 */
static int
MatrixRegionAlignCalibEdge(DmtxImage *image, DmtxRegion *reg,
      DmtxEdgeLoc edgeLoc, DmtxMatrix3 preFit2Raw, DmtxMatrix3 postRaw2Fit)
{
   DmtxVector2 p0, p1, pCorner;
   double slope;
   int hitCount;
   int weakCount;

   assert(edgeLoc == DmtxEdgeTop || edgeLoc == DmtxEdgeRight);

   hitCount = MatrixRegionAlignEdge(image, reg, postRaw2Fit, preFit2Raw, &p0, &p1, &pCorner, &weakCount);
   if(hitCount < 2)
      return DMTX_FAILURE;

   if(edgeLoc == DmtxEdgeRight) {
      SetCornerLoc(reg, DmtxCorner10, pCorner);
      if(MatrixRegionUpdateXfrms(image, reg) != DMTX_SUCCESS)
         return DMTX_FAILURE;

      dmtxMatrix3VMultiplyBy(&p0, reg->raw2fit);
      dmtxMatrix3VMultiplyBy(&p1, reg->raw2fit);

      assert(fabs(p1.Y - p0.Y) > DMTX_ALMOST_ZERO);
      slope = (p1.X - p0.X) / (p1.Y - p0.Y);

      p0.X = p0.X - slope * p0.Y;
      p0.Y = 0.0;
      p1.X = p0.X + slope;
      p1.Y = 1.0;

      dmtxMatrix3VMultiplyBy(&p0, reg->fit2raw);
      dmtxMatrix3VMultiplyBy(&p1, reg->fit2raw);

      SetCornerLoc(reg, DmtxCorner10, p0);
      SetCornerLoc(reg, DmtxCorner11, p1);
   }
   else {
      SetCornerLoc(reg, DmtxCorner01, pCorner);
      if(MatrixRegionUpdateXfrms(image, reg) != DMTX_SUCCESS)
         return DMTX_FAILURE;

      dmtxMatrix3VMultiplyBy(&p0, reg->raw2fit);
      dmtxMatrix3VMultiplyBy(&p1, reg->raw2fit);

      assert(fabs(p1.X - p0.X) > DMTX_ALMOST_ZERO);
      slope = (p1.Y - p0.Y) / (p1.X - p0.X);

      p0.Y = p0.Y - slope * p0.X;
      p0.X = 0.0;
      p1.Y = p0.Y + slope;
      p1.X = 1.0;

      dmtxMatrix3VMultiplyBy(&p0, reg->fit2raw);
      dmtxMatrix3VMultiplyBy(&p1, reg->fit2raw);

      SetCornerLoc(reg, DmtxCorner01, p0);
      SetCornerLoc(reg, DmtxCorner11, p1);
   }
   if(MatrixRegionUpdateXfrms(image, reg) != DMTX_SUCCESS)
      return DMTX_FAILURE;

   return DMTX_SUCCESS;
}
コード例 #8
0
ファイル: dmtxregion.c プロジェクト: RichardGrundy/Barcode
/**
 * @brief  XXX
 * @param  image
 * @param  reg
 * @param  postRaw2Fit
 * @param  preFit2Raw
 * @param  p0
 * @param  p1
 * @param  pCorner
 * @param  weakCount
 * @return 3 points in raw coordinates
 */
static int
MatrixRegionAlignEdge(DmtxImage *image, DmtxRegion *reg,
      DmtxMatrix3 postRaw2Fit, DmtxMatrix3 preFit2Raw, DmtxVector2 *p0,
      DmtxVector2 *p1, DmtxVector2 *pCorner, int *weakCount)
{
   int hitCount, edgeHit, prevEdgeHit;
   DmtxVector2 c00, c10, c01;
   DmtxMatrix3 sRaw2Fit, sFit2Raw;
   DmtxVector2 forward, lateral;
   DmtxVector2 pFitExact, pFitProgress, pRawProgress, pRawExact, pLast;
   double interceptTest, intercept[8];
   DmtxVector2 adjust[8];
   double slope[8];
   int i;
   int stepsSinceStarAdjust;
   DmtxVector2 pTmp;

/*fprintf(stdout, "MatrixRegionAlignEdge()\n"); */
   dmtxMatrix3Multiply(sRaw2Fit, reg->raw2fit, postRaw2Fit);
   dmtxMatrix3Multiply(sFit2Raw, preFit2Raw, reg->fit2raw);

   /* Draw skewed image in bottom left pane */
/* CALLBACK_DECODE_FUNC1(buildMatrixCallback3, dec, sFit2Raw); */

   /* Set starting point */
   pFitProgress.X = -0.003;
   pFitProgress.Y = 0.9;
   *pCorner = pFitExact = pFitProgress;
   dmtxMatrix3VMultiply(&pRawProgress, &pFitProgress, sFit2Raw);

   /* Initialize star lines */
   for(i = 0; i < 8; i++) {
      slope[i] = tan((M_PI_2/8.0) * i);
      intercept[i] = 0.9;
      adjust[i].X = 0.0;
      adjust[i].Y = 0.9;
   }

   hitCount = 0;
   stepsSinceStarAdjust = 0;
   *weakCount = 0;

   prevEdgeHit = edgeHit = DMTX_EDGE_STEP_EXACT;

   for(;;) {
      /* XXX technically we don't need to recalculate lateral & forward once we have left the finder bar */
      dmtxMatrix3VMultiply(&pTmp, &pRawProgress, sRaw2Fit);

      /* XXX move this outside of this loop ? */
      c00 = pTmp;
      c10.X = c00.X + 1;
      c10.Y = c00.Y;
      c01.X = c00.X - 0.087155743;
      c01.Y = c00.Y + 0.996194698;

      if(dmtxMatrix3VMultiplyBy(&c00, sFit2Raw) != DMTX_SUCCESS)
         return 0;

      if(dmtxMatrix3VMultiplyBy(&c10, sFit2Raw) != DMTX_SUCCESS)
         return 0;

      if(dmtxMatrix3VMultiplyBy(&c01, sFit2Raw) != DMTX_SUCCESS)
         return 0;

      if(RightAngleTrueness(c01, c00, c10, M_PI) < 0.1) {
         /* XXX instead of just failing here, hopefully find what happened
                upstream to trigger this condition. we can probably avoid
                this earlier on, and even avoid assertion failures elsewhere */
         return 0;
      }

      /* Calculate forward and lateral directions in raw coordinates */
      dmtxVector2Sub(&forward, &c10, &c00);
      if(dmtxVector2Norm(&forward) != DMTX_SUCCESS)
         return 0;

      dmtxVector2Sub(&lateral, &c01, &c00);
      if(dmtxVector2Norm(&lateral) != DMTX_SUCCESS)
         return 0;

      prevEdgeHit = edgeHit;
      edgeHit = StepAlongEdge(image, reg, &pRawProgress, &pRawExact, forward, lateral);
      dmtxMatrix3VMultiply(&pFitProgress, &pRawProgress, sRaw2Fit);

      if(edgeHit == DMTX_EDGE_STEP_EXACT) {
/**
         if(prevEdgeHit == DMTX_EDGE_STEP_TOO_WEAK) <-- XXX REVIST LATER ... doesn't work
            hitCount++;
*/
         hitCount++;

         dmtxMatrix3VMultiply(&pFitExact, &pRawExact, sRaw2Fit);

         /* Adjust star lines upward (non-vertical) */
         for(i = 0; i < 8; i++) {
            interceptTest = pFitExact.Y - slope[i] * pFitExact.X;
            if(interceptTest > intercept[i]) {
               intercept[i] = interceptTest;
               adjust[i] = pFitExact;
               stepsSinceStarAdjust = 0;

               /* XXX still "turning corner" but not as bad anymore */
               if(i == 7) {
                  *pCorner = pFitExact;
/*                CALLBACK_DECODE_FUNC4(plotPointCallback, dec, pRawExact, 1, 1, DMTX_DISPLAY_POINT); */
               }

               if(i == 0) {
                  pLast = pFitExact;
/*                CALLBACK_DECODE_FUNC4(plotPointCallback, dec, pRawExact, 1, 1, DMTX_DISPLAY_POINT); */
               }
            }
         }

         /* Draw edge hits along skewed edge in bottom left pane */
/*       CALLBACK_DECODE_FUNC4(xfrmPlotPointCallback, dec, pFitExact, NULL, 4, DMTX_DISPLAY_POINT); */
      }
      else if(edgeHit == DMTX_EDGE_STEP_TOO_WEAK) {
         stepsSinceStarAdjust++;
         if(prevEdgeHit == DMTX_EDGE_STEP_TOO_WEAK)
            (*weakCount)++;
      }

      /* XXX also change stepsSinceNear and use this in the break condition */
      if(hitCount >= 20 && stepsSinceStarAdjust > hitCount)
         break;

      if(pFitProgress.X > 1.0) {
/*       CALLBACK_DECODE_FUNC4(plotPointCallback, dec, pRawProgress, 1, 1, DMTX_DISPLAY_SQUARE); */
         break;
      }

      if(pRawProgress.X < 1 || pRawProgress.X > image->width - 1 ||
         pRawProgress.Y < 1 || pRawProgress.Y > image->height - 1)
         break;
   }

   /* Find lowest available horizontal starline adjustment */
   for(i = 0; i < 8; i++) {
      if(adjust[i].X < 0.1)
         break;
   }
   if(i == -1)
      return 0;

   *p0 = adjust[i];

   /* Find highest available non-horizontal starline adjustment */
   for(i = 7; i > 1; i--) {
      if(adjust[i].X > 0.8)
         break;
   }
   if(i == -1)
      return 0;

   *p1 = adjust[i];

   if(fabs(p0->X - p1->X) < 0.1 || p0->Y < 0.2 || p1->Y < 0.2) {
      return 0;
   }

   dmtxMatrix3VMultiplyBy(pCorner, sFit2Raw);
   dmtxMatrix3VMultiplyBy(p0, sFit2Raw);
   dmtxMatrix3VMultiplyBy(p1, sFit2Raw);

   return hitCount;
}