• fullscreen
  • Bridger.pde
  • ConnectedComponentLabeler.pde
  • ContourTracer.pde
  • FileLoading.pde
  • Gui.pde
  • HoboCodes.pde
  • QR_STENCILER_applet.pde
  • Utils.pde
  • // Author: "Golan Levin" <golan@flong.com>, 01 August, 2011
    //================================================================
    class RowInfo {
      int x0; 
      int x1;
      int y;
    }
    
    
    
    //================================================================
    void bridgeCorners (color[] bufferToProcess, int bufW, int bufH) {
      // thicken the corners of grid units under certain geometric conditions. 
    
      int N = CORNER_THICKENING;
      if (N > 0) {
        
        color cornerColor = white;
        if (DO_WHITE_PAINT_STENCIL){
          cornerColor = black;
        }
        
        
        for (int y=N; y<(bufH-N); y++) {
          for (int x=N; x<(bufW-N); x++) {
    
            int indexC  = y*bufW + x;
            int indexE  = indexC + 1;
            int indexS  = indexC + bufW;
            int indexSE = indexS + 1;
    
            color colorC  = bufferToProcess [indexC];
            color colorE  = bufferToProcess [indexE];
            color colorS  = bufferToProcess [indexS];      
            color colorSE = bufferToProcess [indexSE];
    
            if (((colorC  == black)
              && (colorE  == white)
              && (colorS  == white)
              && (colorSE == black)) ||
    
              ((colorC  == white)
              && (colorE  == black)
              && (colorS  == black)
              && (colorSE == white))) {
    
              //------------------------------------------
              for (int i=0; i<=N; i++) {
                int Cx = indexC % bufW;
                int Cy = indexC / bufW; 
                int Ex = indexE % bufW;
                int Ey = indexE / bufW; 
                int Sx = indexS % bufW;
                int Sy = indexS / bufW;
                int SEx = indexSE % bufW;
                int SEy = indexSE / bufW;
    
                boolean bUseObsoleteRoundedCorners = false;
                if (bUseObsoleteRoundedCorners) {
                  // legacy code block; remove or don't use. 
                  for (int j=0; j<=N; j++) {
                    int dx = N - i;
                    int dy = N - j;
                    float dh = sqrt(dx*dx + dy*dy); 
                    if (dh >= N) {
                      int indexCc  = ((Cy-j) * bufW)  + (Cx-i);
                      int indexEc  = ((Ey-j) * bufW)  + (Ex+i);
                      int indexSc  = ((Sy+j) * bufW)  + (Sx-i);
                      int indexSEc = ((SEy+j) * bufW) + (SEx+i);
                      bufferToProcess [indexCc]  = cornerColor;
                      bufferToProcess [indexEc]  = cornerColor;
                      bufferToProcess [indexSc]  = cornerColor;
                      bufferToProcess [indexSEc] = cornerColor;
                    }
                  }
                }
                else {
    
                  for (int j=0; j<i; j++) {
                    int Cxc = Cx - (N-i);
                    int Cyc = Cy -    j;
                    int indexCc = (Cyc * bufW) + Cxc;
                    bufferToProcess [indexCc] = cornerColor;
    
                    int Exc = Ex + (N-i);
                    int Eyc = Ey -    j;
                    int indexEc = (Eyc * bufW) + Exc;
                    bufferToProcess [indexEc] = cornerColor;
    
                    int Sxc = Sx - (N-i);
                    int Syc = Sy +    j;
                    int indexSc = (Syc * bufW) + Sxc;
                    bufferToProcess [indexSc] = cornerColor;
    
                    int SExc = SEx + (N-i);
                    int SEyc = SEy +    j;
                    int indexSEc = (SEyc * bufW) + SExc;
                    bufferToProcess [indexSEc] = cornerColor;
                  }
                }
              }
            }
          }
        }
      }
    }
    
    //===============================================================
    void  bridgeIslands (color[] inputBuffer, int inputW, int inputH) {
      // Dispatches the advanced or the simple bridging routine. 
      // SIMPLE: just one or two bridges on the top edge of islands. Produces fragile stencils.
      // ADVANCED: produces multiple, quasi-optimal bridges for each island, on multiple sides. 
    
      if (DO_ADVANCED_BRIDGING) {
        bridgeIslandsAdvanced (inputBuffer, inputW, inputH);
      } 
      else {
        bridgeIslandsSimple (inputBuffer, inputW, inputH);
      }
    }
    
    
    //===============================================================
    void bridgeIslandsSimple (color[] inputBuffer, int inputW, int inputH) {
      // inputBuffer is an array of colors(i.e. ints), which 
      // are a 'pure' black-and-white version of the QR code. 
    
      int gridSize = computeGridSize (inputBuffer, inputW, inputH); // see Utils.pde
    
      int ccLabelColors[] = CCL.getLabelColors();
      int nLabelColors = ccLabelColors.length;
      while (nLabelColors > 2) {
        
        //---------------------
        // Find out where to build the bridge(s).
        // Get the first color other than black or white.
        // (Assuming white and black are colors 0 and 1.)
        color firstLabelCol = ccLabelColors[2];
    
        RowInfo responseTop = getRowInfoOfTheTopRowOfABlobWithACertainColor (firstLabelCol, coloredLabeledImage, inputW, inputH);
        int topLeftXValue  = responseTop.x0; 
        int topRightXValue = responseTop.x1; 
        int topRowYValue   = responseTop.y; 
    
        //----------
        // If I have found the row of that color, then build the bridges.
        if ((topLeftXValue > -1) && (topRightXValue > -1)) {
          int bridgeBottomY = topRowYValue;
          if (((topRightXValue - topLeftXValue)/gridSize) > 2) {
            buildABridge (topLeftXValue, bridgeBottomY, gridSize, DIR_UP, gridSize, inputBuffer, inputW, inputH);
    
            int bridgeWidth = getBridgeWidthFromGridSize (gridSize);
            int rightHandBridgeX = topRightXValue - (bridgeWidth-1);
            buildABridge (rightHandBridgeX, bridgeBottomY, gridSize, DIR_UP, gridSize, inputBuffer, inputW, inputH);
          } 
          else {
            int centerX = (topLeftXValue + topRightXValue)/2;
            buildABridge (centerX, bridgeBottomY, gridSize, DIR_UP, gridSize, inputBuffer, inputW, inputH);
          }
        }
    
        //----------
        // re-compute the connected components now that the bridge exists.
        coloredLabeledImage = CCL.doLabel( inputBuffer, inputW, inputH);
    
        //----------
        // re-extract the number of current labels.
        // presumably, because we built a bridge, it's one less than it was before.
        ccLabelColors = CCL.getLabelColors();
        nLabelColors = ccLabelColors.length;
      }
    }
    
    
    
    //===============================================================
    void bridgeIslandsAdvanced (color[] inputBuffer, int inputW, int inputH) {
    
      int gridSize = computeGridSize (inputBuffer, inputW, inputH); // see Utils.pde
      int ccLabelColors[] = CCL.getLabelColors();
      int nLabelColors = ccLabelColors.length;
      //while (nLabelColors > 2) {
      for (int LC=2; LC<nLabelColors; LC++) {
    
        //---------------------
        // Find out where to build the bridge(s); get the first color other than black or white.
        // firstLabelCol = ccLabelColors[2];
        color firstLabelCol = ccLabelColors[LC];
    
        //---------------------------------------------------------
        // Find the extreme locations of the blob with that color.
        // Note that e.g. indexA might be the same as indexE, etc. 
        final int UNDEFINED = -1;
        int indexA = UNDEFINED; // (A) the first pixel of the top row;    top's left
        int indexB = UNDEFINED; // (B) the last  pixel of the top row;    top's right 
        int indexC = UNDEFINED; // (C) the first pixel of the bottom row; bottom's left
        int indexD = UNDEFINED; // (D) the last  pixel of the bottom row; bottom's right
        int indexE = UNDEFINED; // (E) the top pixel of leftmost column;  left's top
        int indexF = UNDEFINED; // (F) the bot pixel of leftmost column;  left's bottom
        int indexG = UNDEFINED; // (G) the top pixel of rightmost column; right's top
        int indexH = UNDEFINED; // (H) the bot pixel of rightmost column; right's bottom
    
        int topRowY    = UNDEFINED;
        int bottomRowY = UNDEFINED;
        int leftRowX   = UNDEFINED; 
        int rightRowX  = UNDEFINED;
    
        //        A---B
        //        |   |
        //     E---   ---G
        //     |         |
        //     F---   ---H
        //        |   |
        //        C---D
        //
    
        // find indexA and indexB
        for (int y = 0; y < inputH; y++) {
          for (int x = 0; x < inputW; x++) { 
            int index = y*inputW + x;
            color someCol = coloredLabeledImage[index];
            if ((someCol == firstLabelCol) && (indexA == UNDEFINED)) {
              indexA  = index;
              topRowY  = y;
            }
            if ((someCol == firstLabelCol) && (y == topRowY)) {
              indexB = index;
            }
          }
        }
    
        // find indexC and indexD
        for (int y = (inputH-1); y >= 0; y--) {
          for (int x = (inputW-1); x >= 0; x--) { 
            int index = y*inputW + x;
            color someCol = coloredLabeledImage[index];
            if ((someCol == firstLabelCol) && (indexD == UNDEFINED)) {
              indexD  = index;
              bottomRowY  = y;
            }
            if ((someCol == firstLabelCol) && (y == bottomRowY)) {
              indexC = index;
            }
          }
        }
    
        // find indexE and indexF
        for (int x=0; x< inputW; x++) {
          for (int y=0; y< inputH; y++) {
            int index = y*inputW + x;
            color someCol = coloredLabeledImage[index];
            if ((someCol == firstLabelCol) && (indexE == UNDEFINED)) {
              indexE = index;
              leftRowX = x;
            }
            if ((someCol == firstLabelCol) && (x == leftRowX)) {
              indexF = index;
            }
          }
        }
    
        // find indexG and indexH
        for (int x=(inputW-1); x>=0; x--) {
          for (int y=(inputH-1); y>=0; y--) {
            int index = y*inputW + x;
            color someCol = coloredLabeledImage[index];
            if ((someCol == firstLabelCol) && (indexH == UNDEFINED)) {
              indexH = index;
              rightRowX = x;
            }
            if ((someCol == firstLabelCol) && (x == rightRowX)) {
              indexG = index;
            }
          }
        }
    
        //---------------------------------------------------------
        // for each of the locations (A-H), search in the appropriate direction
        // until the next non-black pixel is encountered. Store that distance (dA, dB, dC, ...) in a Pier object
        final int idA = 0; 
        final int idB = 1; 
        final int idC = 2; 
        final int idD = 3;
        final int idE = 4; 
        final int idF = 5; 
        final int idG = 6; 
        final int idH = 7;
    
        ArrayList<Pier> piers; 
        piers = new ArrayList<Pier>();  // Constructor requests: int id_, int index_, int bearing_, int distance_
        piers.clear();
    
        if (indexA != UNDEFINED) { // check UP and LEFT from A
          piers.add (new Pier (idA, indexA, DIR_UP, getVDistanceToNearestNonBlackPixel (indexA, inputW, inputH, DIR_UP)    )); 
          piers.add (new Pier (idA, indexA, DIR_LEFT, getHDistanceToNearestNonBlackPixel (indexA, inputW, inputH, DIR_LEFT ) ));
        }
        if (indexB != UNDEFINED) { // check UP and RIGHT from B
          piers.add (new Pier (idB, indexB, DIR_UP, getVDistanceToNearestNonBlackPixel (indexB, inputW, inputH, DIR_UP)    )); 
          piers.add (new Pier (idB, indexB, DIR_RIGHT, getHDistanceToNearestNonBlackPixel (indexB, inputW, inputH, DIR_RIGHT) ));
        }
        if (indexC != UNDEFINED) { // check DOWN and LEFT from C
          piers.add (new Pier (idC, indexC, DIR_DOWN, getVDistanceToNearestNonBlackPixel (indexC, inputW, inputH, DIR_DOWN ) )); 
          piers.add (new Pier (idC, indexC, DIR_LEFT, getHDistanceToNearestNonBlackPixel (indexC, inputW, inputH, DIR_LEFT ) ));
        }
        if (indexD != UNDEFINED) { // check DOWN and RIGHT from D
          piers.add (new Pier (idD, indexD, DIR_DOWN, getVDistanceToNearestNonBlackPixel (indexD, inputW, inputH, DIR_DOWN ) )); 
          piers.add (new Pier (idD, indexD, DIR_RIGHT, getHDistanceToNearestNonBlackPixel (indexD, inputW, inputH, DIR_RIGHT) ));
        }
    
        if ((indexE != UNDEFINED) && (indexE != indexA)) { // check UP and LEFT from E
          piers.add (new Pier (idE, indexE, DIR_UP, getVDistanceToNearestNonBlackPixel (indexE, inputW, inputH, DIR_UP)    )); 
          piers.add (new Pier (idE, indexE, DIR_LEFT, getHDistanceToNearestNonBlackPixel (indexE, inputW, inputH, DIR_LEFT ) ));
        }
        if ((indexG != UNDEFINED) && (indexG != indexB)) { // check UP and RIGHT from G
          piers.add (new Pier (idG, indexG, DIR_UP, getVDistanceToNearestNonBlackPixel (indexG, inputW, inputH, DIR_UP)    )); 
          piers.add (new Pier (idG, indexG, DIR_RIGHT, getHDistanceToNearestNonBlackPixel (indexG, inputW, inputH, DIR_RIGHT) ));
        }
        if ((indexF != UNDEFINED) && (indexF != indexC)) { // check DOWN and LEFT from F
          piers.add (new Pier (idF, indexF, DIR_DOWN, getVDistanceToNearestNonBlackPixel (indexF, inputW, inputH, DIR_DOWN ) )); 
          piers.add (new Pier (idF, indexF, DIR_LEFT, getHDistanceToNearestNonBlackPixel (indexF, inputW, inputH, DIR_LEFT ) ));
        }
        if ((indexH != UNDEFINED) && (indexH != indexD)) { // check DOWN and RIGHT from H
          piers.add (new Pier (idH, indexH, DIR_DOWN, getVDistanceToNearestNonBlackPixel (indexH, inputW, inputH, DIR_DOWN ) )); 
          piers.add (new Pier (idH, indexH, DIR_RIGHT, getHDistanceToNearestNonBlackPixel (indexH, inputW, inputH, DIR_RIGHT) ));
        }
    
    
        // Compute the area of the blob with that color. 
        // We'll use the area as a direct basis for deciding how many bridges to build. 
        int nPixelsInThatBlob = getAreaOfBlobWithACertainColor (firstLabelCol, coloredLabeledImage, inputW, inputH);
        int nGridCellsInThatBlob = nPixelsInThatBlob/ (gridSize*gridSize); 
        int N_BRIDGES_TO_MAKE = 4; // default.
        if (DO_ALL_COMPUTED_BRIDGES) {
          N_BRIDGES_TO_MAKE = piers.size();
        } 
        else {
          N_BRIDGES_TO_MAKE = min(piers.size(), max(MIN_BRIDGES_PER_ISLAND, nGridCellsInThatBlob));
        }
    
    
        for (int Br=0; Br < N_BRIDGES_TO_MAKE; Br++) {
    
          // Select (at least 2 of) the shortest distances (that are not on the same side), and build bridges there. 
          // We can build more bridges (as appropriate) depending on the area of the blob:
          // extremely large blobs (whose areas contain many grid units) deserve more bridges. 
          // Sort the Piers by their length. 
          Collections.sort (piers, new Comparator<Pier>() {
            public int compare(Pier e0, Pier e1) {
              return ((Integer)(e0.distance)).compareTo((Integer)(e1.distance));
            }
          }
          ); 
          boolean bPrintPiers = false;
          if (bPrintPiers) {
            println("------------------"); 
            for (int i=0; i<piers.size(); i++) {
              piers.get(i).print();
            }
          }
    
          //------------------------------------
          // First: bridge the shortest pier:
          // Get the length of the shortest pier. (Remember, we sorted piers, above.)
          if (piers.size() > 0) { // safety check
            int lengthOfShortestPier = piers.get(0).distance;
    
            // (Discard degenerate piers (if any!) which are shorter than gridSize.)
            if (lengthOfShortestPier < gridSize) { 
              int len = 9999999;
              for (int i=0; i<piers.size(); i++) {
                if (piers.get(i).distance >= gridSize) { // kosher
                  lengthOfShortestPier = min(lengthOfShortestPier, piers.get(i).distance);
                }
              }
            }
    
            Pier shortestPier = null; 
            int countOfPiersWithTheShortestLength = 0; 
            for (int i=0; i<piers.size(); i++) {
              if (piers.get(i).distance == lengthOfShortestPier) {
                countOfPiersWithTheShortestLength++;
                shortestPier = piers.get(i);
              }
            }
    
            if (countOfPiersWithTheShortestLength > 1) {
              // This is rather ridiculous, but it's late.
              // If there is more than one pier with the shortest length, 
              // Tally the total number of piers for each bearing (up, down, left, right) that has a pier with that length
              // And select the pier (with that length) from the direction with the fewest piers.
              // This leaves the maximum number of other possible piers for connecting later. 
              int nPiersU = 0; // UP
              int nPiersD = 0; // DOWN
              int nPiersL = 0; // LEFT
              int nPiersR = 0; // RIGHT
              boolean bHasShortestU = false;
              boolean bHasShortestD = false;
              boolean bHasShortestL = false;
              boolean bHasShortestR = false;
    
              for (int i=0; i<piers.size(); i++) {
                int bearing = piers.get(i).bearing;
                switch(bearing) {
                case DIR_UP: 
                  if (piers.get(i).distance == lengthOfShortestPier) {
                    bHasShortestU = true;
                  } 
                  nPiersU++;
                  break;
                case DIR_DOWN:  
                  if (piers.get(i).distance == lengthOfShortestPier) {
                    bHasShortestD = true;
                  } 
                  nPiersD++; 
                  break;
                case DIR_LEFT:
                  if (piers.get(i).distance == lengthOfShortestPier) {
                    bHasShortestL = true;
                  }   
                  nPiersL++;
                  break;
                case DIR_RIGHT: 
                  if (piers.get(i).distance == lengthOfShortestPier) {
                    bHasShortestR = true;
                  } 
                  nPiersR++;
                  break;
                }
              }
    
              int nPiersInBearingsWithShortestPiers[] = new int[4];
              nPiersInBearingsWithShortestPiers[DIR_UP]    = (bHasShortestU) ? nPiersU : 0; 
              nPiersInBearingsWithShortestPiers[DIR_DOWN]  = (bHasShortestD) ? nPiersD : 0; 
              nPiersInBearingsWithShortestPiers[DIR_LEFT]  = (bHasShortestL) ? nPiersL : 0; 
              nPiersInBearingsWithShortestPiers[DIR_RIGHT] = (bHasShortestR) ? nPiersR : 0; 
    
              int bearingWithFewestPiersThatAlsoHasAShortestPier = UNDEFINED;
              int minNumPiers = 999;
              for (int i=0; i<4; i++) {
                if (nPiersInBearingsWithShortestPiers[i] > 0) {
                  if (nPiersInBearingsWithShortestPiers[i] < minNumPiers) {
                    minNumPiers = nPiersInBearingsWithShortestPiers[i];
                    bearingWithFewestPiersThatAlsoHasAShortestPier = i;
                  }
                }
              }
    
              // Therefore, search for the pier that (1) has the shortest length and (2) has the bearingWithFewestPiersThatAlsoHasAShortestPier.
              for (int i=0; i<piers.size(); i++) {
                int bearing = piers.get(i).bearing;
                if (bearing == bearingWithFewestPiersThatAlsoHasAShortestPier) {
                  int len = piers.get(i).distance;
                  if (len == lengthOfShortestPier) {
                    shortestPier = piers.get(i);
                  }
                }
              }
              // Now we have found the shortest pier, and from an underrepresented side, to boot!
            } 
            else if (countOfPiersWithTheShortestLength == 1) {
              ; // Solo case. Just use shortestPier. We're good!
            }
    
            // Actually do the BRIDGING. 
            if (shortestPier != null) {
              int bridgeIndex = shortestPier.index;
              int bridgeX     = bridgeIndex % inputW;
              int bridgeY     = bridgeIndex / inputW;
              int bearing     = shortestPier.bearing;
              int distance    = shortestPier.distance;
    
              // Hey, why not do some bridge pixel HINTING!
              int bridgeWidth = getBridgeWidthFromGridSize (gridSize);
              int id = shortestPier.ID;
              switch (id) {
              case idA: 
              case idE:
                ; // should be good to go
                break;
    
              case idB: 
              case idG:
                if (bearing == DIR_UP) {
                  bridgeX -= (bridgeWidth-1);
                } 
                break;
    
              case idC: 
              case idF: 
                if (bearing == DIR_LEFT) {
                  bridgeY -= (bridgeWidth-1);
                }
                break;
    
              case idD: 
              case idH: 
                if (bearing == DIR_DOWN) {
                  bridgeX -= (bridgeWidth-1);
                } 
                else if (bearing == DIR_RIGHT) {
                  bridgeY -= (bridgeWidth-1);
                }
                break;
              }
    
              buildABridge (bridgeX, bridgeY, gridSize, bearing, distance, inputBuffer, inputW, inputH);
            }
    
            // Remove shortestPier. 
            // Also, with some probability, remove all piers with the same bearing. 
            piers.remove(shortestPier); 
            if (DO_ALL_COMPUTED_BRIDGES == false) {
              ArrayList<Pier> PiersToRemove = new ArrayList<Pier>();
              for (int i=0; i<(piers.size()); i++) {
                if (piers.get(i).bearing == shortestPier.bearing) {
                  // the higher BRIDGE_CULLING_FACTOR is closer to 1.0, 
                  // the more likely to enforce only having single bridges per side.
                  if ((random(0, 1) < BRIDGE_CULLING_FACTOR)) { // !!!!!!!!!!!!!!!!! DITHER
                    PiersToRemove.add (piers.get(i));
                  }
                }
              }
              for (int i=0; i<PiersToRemove.size(); i++) {
                piers.remove (PiersToRemove.get(i));
              }
            }
          }
        } // repeat the adding of bridges!
    
    
    
    
        /*
        //----------
         // re-compute the connected components now that the bridge exists, and re-extract the number of current labels.
         coloredLabeledImage = CCL.doLabel( inputBuffer, inputW, inputH);
         ccLabelColors = CCL.getLabelColors();
         nLabelColors = ccLabelColors.length;
         */
      }
    }
    
    
    //===============================================================
    class Pier {    // A starting point for a possible bridge!
    
      //-------------
      Pier (int id_, int index_, int bearing_, int distance_) {
        ID = id_;
        index = index_;
        bearing = bearing_;
        distance = distance_;
      }
    
      //-------------
      void print() {
    
        String dir = ""; 
        switch(bearing) {
        case DIR_UP:    
          dir = "UP"; 
          break;
        case DIR_DOWN:  
          dir = "DOWN"; 
          break;
        case DIR_LEFT:  
          dir = "LEFT"; 
          break;
        case DIR_RIGHT: 
          dir = "RIGHT"; 
          break;
        }
        println("Pier: From point " + ID + "\t" + distance + "\t" + dir);
      }
    
      //-------------
      int ID;       // the "name" of the start point
      int index;    // the index (in the QR-image sized buffer) of the start point
      int bearing;  // which way the bridge would go from here
      int distance; // how long the bridge would need to be
    }
    
    
    //===============================================================
    int getVDistanceToNearestNonBlackPixel (int startIndex, int inputW, int inputH, int direction) {
      int distance = 0;
    
      if (startIndex > -1) {
        int x = startIndex % inputW;
        int y = startIndex / inputW;
    
        if (direction == DIR_UP) {
          y = y-1; 
          int testIndex = y*inputW + x; 
          color testColor = coloredLabeledImage[testIndex]; 
          while ( (testColor == black) && (y > 0)) {
            y = y-1;
            testIndex = y*inputW + x; 
            testColor = coloredLabeledImage[testIndex];
          }
          distance = abs(y - (startIndex/inputW)) -1;
        } 
        else if (direction == DIR_DOWN) {
          y = y+1; 
          int testIndex = y*inputW + x; 
          color testColor = coloredLabeledImage[testIndex]; 
          while ( (testColor == black) && (y < (inputH-1))) {
            y = y+1;
            testIndex = y*inputW + x; 
            testColor = coloredLabeledImage[testIndex];
          }
          distance = abs(y - (startIndex/inputW)) -1;
        }
      }
      return distance;
    }
    
    
    //===============================================================
    int getHDistanceToNearestNonBlackPixel (int startIndex, int inputW, int inputH, int direction) {
      int distance = 0;
    
      if (startIndex > -1) {
        int x = startIndex % inputW;
        int y = startIndex / inputW;
    
        if (direction == DIR_LEFT) {
          x = x-1; // to get started, on the pixel above
          int testIndex = y*inputW + x; 
          color testColor = coloredLabeledImage[testIndex]; 
          while ( (testColor == black) && (x > 0)) {
            x = x-1;
            testIndex = y*inputW + x; 
            testColor = coloredLabeledImage[testIndex];
          }
          distance = abs(x - (startIndex%inputW)) -1;
        }
        else if (direction == DIR_RIGHT) {
          x = x+1; // to get started, on the pixel above
          int testIndex = y*inputW + x; 
          color testColor = coloredLabeledImage[testIndex]; 
          while ( (testColor == black) && (x < (inputW-1))) {
            x = x+1;
            testIndex = y*inputW + x; 
            testColor = coloredLabeledImage[testIndex];
          }
          distance = abs(x - (startIndex%inputW)) -1;
        }
      }
      return distance;
    }
    
    
    
    //===============================================================
    void buildABridge (int bridgeX, int bridgeY, int gridSize, int direction, int distance, color[] inputBuffer, int inputW, int inputH) {
    
      int bridgeWidth = getBridgeWidthFromGridSize (gridSize);
    
      if (direction == DIR_UP) {
        // bridgeY is interpreted as the Y end value.
        int yStart = bridgeY - distance - 1;
        for (int y=bridgeY; y>=yStart; y--) {
          for (int x=bridgeX; x<(bridgeX+bridgeWidth); x++) {
            if ((y >= 0) && (x >= 0)) {
              int index = y*inputW + x;
              inputBuffer[index] = white;
            }
          }
        }
      } 
    
      else if (direction == DIR_DOWN) {
        // bridgeY is interpreted as the Y start value.
        int yEnd = bridgeY + distance + 1;
        for (int y=bridgeY; y<yEnd; y++) {
          for (int x=bridgeX; x<(bridgeX+bridgeWidth); x++) {
            if ((y < inputH) && (x < inputW)) {
              int index = y*inputW + x;
              blackAndWhiteImage[index] = white;
            }
          }
        }
      }
    
      else if (direction == DIR_LEFT) {
        int xStart = bridgeX;
        int xEnd   = bridgeX - distance - 1;
        for (int x=xStart; x >= xEnd; x--) {
          for (int y=bridgeY; y<(bridgeY+bridgeWidth); y++) {
            if ((y >= 0) && (x >= 0)) {
              int index = y*inputW + x;
              blackAndWhiteImage[index] = white;
            }
          }
        }
      }
    
      else if (direction == DIR_RIGHT) {
        int xStart = bridgeX;
        int xEnd   = bridgeX + distance + 1; 
        for (int x=xStart; x<= xEnd; x++) {
          for (int y=bridgeY; y<(bridgeY+bridgeWidth); y++) {
            if ((y < inputH) && (x < inputW)) {
              int index = y*inputW + x;
              blackAndWhiteImage[index] = white;
            }
          }
        }
      }
    }
    
    
    //===============================================================
    int getBridgeWidthFromGridSize (int grs) {
      int bridgeWidth = (int)(grs * BRIDGE_THICKNESS); // ratio: bridge is ~1/6 of gridSize
      if ((bridgeWidth > 1) && (bridgeWidth %2 == 1)) {
        bridgeWidth--;
      }
    
      bridgeWidth = max(1, bridgeWidth); 
      return bridgeWidth;
    }
    
    
    //===============================================================
    RowInfo getRowInfoOfTheTopRowOfABlobWithACertainColor (color firstLabelCol, color[] coloredBuffer, int inputW, int inputH) {
      RowInfo response = new RowInfo();
      response.x0 = -1;
      response.x1 = -1;
      response.y  = -1;
    
      // find the indexes of the first & last pixels in the top row of the blob with that color.
      int topLeftIndex  = -1; // this will hold the index of the first pixel to contain that color.
      int topRightIndex = -1; // this will hold the index of the last pixel to contain that color, from the same row. 
      int topRowYValue  = -1; 
      int topLeftXValue = -1;
      int topRightXValue = -1;
      boolean bFoundTopLeft = false;
    
      for (int y = 0; y < inputH; y++) {
        for (int x = 0; x < inputW; x++) { 
          int index = y*inputW + x;
          color someCol = coloredBuffer[index];
    
          if ((someCol == firstLabelCol) && (bFoundTopLeft == false)) {
            topLeftIndex  = index;
            bFoundTopLeft = true;
            topLeftXValue = x;
            topRowYValue  = y;
          }
    
          if ((y == topRowYValue) && (someCol == firstLabelCol)) {
            topRightIndex = index;
            topRightXValue = x;
          }
        }
      }
    
      response.x0 = topLeftXValue; 
      response.x1 = topRightXValue; 
      response.y  = topRowYValue; 
      return response;
    }
    
    
    //===============================================================
    int getAreaOfBlobWithACertainColor (color testColor, color[] colorBuffer, int inputW, int inputH) {
    
      int pixelCount = 0;
      for (int y=0; y<inputH; y++) {
        for (int x=0; x<inputW; x++) {
          int index = y*inputW + x; 
          if (colorBuffer[index] == testColor) {
            pixelCount++;
          }
        }
      }
    
      return pixelCount;
    }
    
    
    // Author: "Golan Levin" <golan@flong.com>, 01 August, 2011
    //================================================================
    class ConnectedComponentLabeler {
      
      // Adapted from: 
      // http://courses.csail.mit.edu/6.141/spring2010/pub/labs/VisualServo/src/ConnectedComponents.java
      // ConnectedComponentLabeler is an algorithm that applies Connected Component Labeling
      // algorithm to an input image. Only mono images are catered for.
      // author: Neil Brown, DAI
      // author: Judy Robertson, SELLIC OnLine
    
      //----------------------------------------------------------------------
      //the width of the input image in pixels
      int i1_w;
    
      //the width and height of the output image
      int d_w;
      int d_h;
      
      int numberOfLabels;
      boolean labelsValid = false;
      int dest_1d[];
      int labels[];
      int labelColors[];
      
    
      //----------------------------------------------------------------------
      // Constructs a new Image Operator
      // param firstwidth  The width of the input image
      // param firstheight  The height of the input image
      ConnectedComponentLabeler( int firstwidth, int firstheight ) {
        labels = new int[firstwidth * firstheight];
        i1_w = firstwidth;
      }
    
      //----------------------------------------------------------------------
      // getNeighbors will get the pixel value of i's neighbor that's ox and oy
      // away from i, if the point is outside the image, then 0 is returned.
      // This version gets from source image.
      int getNeighbors( int [] src1d, int i, int ox, int oy ) {
        int x, y, result;
    
        x = ( i % d_w ) + ox; // d_w and d_h are assumed to be set to the
        y = ( i / d_w ) + oy; // width and height of scr1d
    
        if ( ( x < 0 ) || ( x >= d_w ) || ( y < 0 ) || ( y >= d_h ) ) {
          result = 0;
        } 
        else {
          result = src1d[ y * d_w + x ] & 0x000000ff;
        }
        return result;
      }
    
      //----------------------------------------------------------------------
      // getNeighbord will get the pixel value of i's neighbor that's ox and oy
      // away from i, if the point is outside the image, then 0 is returned.
      // This version gets from destination image.
      int getNeighbord( int [] src1d, int i, int ox, int oy ) {
        int x, y, result;
    
        x = ( i % d_w ) + ox; // d_w and d_h are assumed to be set to the
        y = ( i / d_w ) + oy; // width and height of scr1d
    
        if ( (x < 0) || (x >= d_w) || (y < 0) || (y >= d_h) ) {
          result = 0;
        } 
        else {
          result = src1d[ y * d_w + x ];
        }
        return result;
      }
    
      //----------------------------------------------------------------------
      // Associate(equivalence) a with b.
      // a should be less than b to give some ordering (sorting)
      // if b is already associated with some other value, then propagate
      // down the list.
      void associate( int a, int b ) {
    
        if ( a > b ) {
          associate( b, a );
          return;
        }
        if ( ( a == b ) || ( labels[ b ] == a ) ) return;
        if ( labels[ b ] == b ) {
          labels[ b ] = a;
        } 
        else {
          associate ( labels[ b ], a );
          if (labels[ b ] > a) {             
            labels[ b ] = a;
          }
        }
      }
    
      //----------------------------------------------------------------------
      // Reduces the number of labels.
      int reduce( int a ) {
        if ( labels[ a ] == a ) {
          return a;
        } 
        else {
          return reduce( labels[ a ] );
        }
      }
    
      //----------------------------------------------------------------------
      // doLabel applies the Labeling algorithm plus offset and scaling
      // The input image is expected to be 8-bit mono, 0=black everything else=white
      // param src1_1d The input pixel array
      // param width width of the destination image in pixels
      // param height height of the destination image in pixels
      // return A pixel array containing the labelled image
      // NB For images  0,0 is the top left corner.
      int [] doLabel(int [] src1_1d, int wid, int hig) {
    
        int nextlabel = 1;
        int nbs[]  = new int[ 4 ];
        int nbls[] = new int[ 4 ];
    
        //Get size of image and make 1d_arrays
        d_w = wid;
        d_h = hig;
    
        dest_1d = new int[d_w*d_h];
        labels  = new int[d_w*d_h / 2]; // the most labels there can be is 1/2 of the points in checkerboard
    
        int src1rgb;
        int result = 0;
        int px, py, count, found;
    
        labelsValid = false; // only set to true once we've complete the task
        //initialise labels
        for (int i=0; i<labels.length; i++) labels[ i ] = i;
    
        //now Label the image
        for (int i=0; i< src1_1d. length; i++) {
    
          src1rgb = src1_1d[ i ] & 0x000000ff;
    
          if ( src1rgb == 0 ) {
            result = 0;  //nothing here
          } 
          else {
    
            //The 4 visited neighbors
            nbs[ 0 ]  = getNeighbors( src1_1d, i, -1,  0 );
            nbs[ 1 ]  = getNeighbors( src1_1d, i,  0, -1 );
            nbs[ 2 ]  = getNeighbors( src1_1d, i, -1, -1 );
            nbs[ 3 ]  = getNeighbors( src1_1d, i,  1, -1 );
    
            //Their corresponding labels
            nbls[ 0 ] = getNeighbord( dest_1d, i, -1,  0 );
            nbls[ 1 ] = getNeighbord( dest_1d, i,  0, -1 );
            nbls[ 2 ] = getNeighbord( dest_1d, i, -1, -1 );
            nbls[ 3 ] = getNeighbord( dest_1d, i,  1, -1 );
    
            //label the point
            if ( (nbs[0] == nbs[1]) && (nbs[1] == nbs[2]) && (nbs[2] == nbs[3])
              && (nbs[0] == 0 )) { 
              // all neighbors are 0 so gives this point a new label
              result = nextlabel;
              nextlabel++;
            } 
            else { //one or more neighbors have already got labels
              count = 0;
              found = -1;
              for ( int j=0; j<4; j++) {
                if ( nbs[ j ] != 0 ) {
                  count +=1;
                  found = j;
                }
              }
              if ( count == 1 ) {
                // only one neighbor has a label, so assign the same label to this.
                result = nbls[ found ];
              } 
              else {
                // more than 1 neighbor has a label
                result = nbls[ found ];
                // Equivalence the connected points
                for ( int j=0; j<4; j++) {
                  if ( ( nbls[ j ] != 0 ) && (nbls[ j ] != result ) ) {
                    associate( nbls[ j ], result );
                  }
                }
              }
            }
          }
          dest_1d[i] = result;
        }
    
        // reduce labels ie 76=23=22=3 -> 76=3
        // done in reverse order to preserve sorting
        for ( int i= labels.length -1; i > 0; i-- ) {
          labels[ i ] = reduce( i );
        }
    
        // now labels will look something like 1=1 2=2 3=2 4=2 5=5.. 76=5 77=5
        // this needs to be condensed down again, so that there is no wasted
        // space. E.g. in the above, the labels 3 and 4 are not used, instead it jumps to 5
        int condensed[] = new int[ nextlabel ]; // cant be more than nextlabel labels
    
        count = 0;
        for (int i=0; i< nextlabel; i++) {
          if ( i == labels[ i ] ) condensed[ i ] = count++;
        }
        // Record the number of labels
        numberOfLabels = count - 1;
    
        // now run back through our preliminary results, replacing the raw label
        // with the reduced and condensed one, and do the scaling and offsets too
    
        // Now generate an array of colours which will be used to label the image
        labelColors = new int[numberOfLabels+1];
      
        // The likelihood that two colors would be the same... is very small. 
        for (int i = 0; i < labelColors.length; i++) {
          int rr = (int) random(1, 254); 
          int rg = (int) random(1, 254); 
          int rb = (int) random(1, 254); 
          color randomColor = color(rr, rg, rb, 255); 
          labelColors[i] = randomColor;
    
          if (i == 0) labelColors[i] = black;
          if (i == 1) labelColors[i] = white;
        }
    
        for (int i=0; i< src1_1d. length; i++) {
          result = condensed[ labels[ dest_1d[ i ] ] ];
          dest_1d[i] = labelColors[result];
        }
    
        labelsValid = true; // only set to true now we've complete the task
        return dest_1d;
      }
    
      //----------------------------------------------------------------------
      //return the number of unique, non zero colours. -1 if not valid
      int getColors() {
        if ( labelsValid ) {
          return numberOfLabels;
        } 
        else {
          return -1;
        }
      }
    
      //----------------------------------------------------------------------
      //Returns the number of labels.
      int getNumberOfLabels() {
        return numberOfLabels;
      }
    
      //----------------------------------------------------------------------
      int[] getLabelColors() {
        return labelColors;
      }
    }
    
    
    
    
    // Author: "Golan Levin" <golan@flong.com>, 01 August, 2011
    //================================================================
    class Point2d {
      int x, y;
      Point2d (int inx, int iny) {
        x = inx;
        y = iny;
      }
    }
    
    
    //===============================================
    class ContourTracer {
    
      Point2d firstPixel;  
      ArrayList<ArrayList> chains;
      ArrayList colorLabels;
    
      ContourTracer () {
        chains      = new ArrayList<ArrayList>(); // a collection of contours.
        colorLabels = new ArrayList();
        firstPixel  = new Point2d(0, 0);
      }
    
    
      //===============================================================
      void traceBlobContours (color[] coloredBlobBuffer, int bufW, int bufH) {
        int nLabels = compileLabelColors ( coloredBlobBuffer, bufW, bufH);
    
        chains.clear(); 
        for (int i=0; i<nLabels; i++) {
          findBlobStartPixel(i, coloredBlobBuffer, bufW, bufH);
          compute8NeighborChainCode (firstPixel.x, firstPixel.y, coloredBlobBuffer, bufW, bufH);
          repairLeftTurningInteriorCorners();
        }
      }
    
    
    
      //===============================================
      void drawAllChains() {
    
        for (int n=0; n<chains.size(); n++) {
          ArrayList aContour = (ArrayList)chains.get(n);
    
          beginShape();
          for (int i=0; i<aContour.size(); i++) {
            Point2d P = (Point2d) aContour.get(i);
            vertex(P.x, P.y);
          }
          endShape(CLOSE);
        }
      }
    
    
      //===============================================
      void drawAllChainsWithFilletedCorners() {
    
        for (int n=0; n<chains.size(); n++) {
          ArrayList aContour = (ArrayList)chains.get(n);
          if (aContour.size() > 0) {
    
            float px0, py0; 
            float px1, py1; 
            float px2, py2;
            Point2d P0 = (Point2d) aContour.get(0);
            px2 = px1 = px0 = P0.x;
            py2 = py1 = py0 = P0.y;
    
            int orientationPrev = -1; 
            int orientationCurr = -1;
            int indexOfPrevAppendedPoint = -1;
    
            beginShape();
            boolean pPrevWasDiagonal = true;
            int nPointsInContour = aContour.size();
    
            for (int i=0; i<(2+nPointsInContour); i++) {
              int index = i%nPointsInContour;
              Point2d P2 = (Point2d) aContour.get(index);
              px2 = P2.x;
              py2 = P2.y;
    
              if ((px2 != px1) && (py2 != py1)) {
                // if this (P2) point's x and y are BOTH different from the previous points',
                // then this point is DIAGONAL from the previous one. Don't include it, but store it as a start point (p0).
                if (pPrevWasDiagonal == false) {
                  pPrevWasDiagonal = true;
                  // if we have just begun a diagonal span, make a note of the first point, P0.
                  px0 = px1;
                  py0 = py1;
                }
              } 
              else {
                // Since this (P2's) x and y are (horizontally or vertically) aligned with the previous, 
                // then -- if we had been tracing a diagonal -- the previous point was the last in the diagonal.
                // Add it (p1) to the output chain/drawing, and also the new one (p2) as well. 
    
                if (pPrevWasDiagonal) {
                  pPrevWasDiagonal = false; 
                  orientationPrev = -1; 
                  orientationCurr = -1;
    
                  // NOTE: we'll need to detect whether it was an interior corner.
                  // check whether p00 (before 0) is left or down from p0
    
                  // Add the bezier corner curve. For more details on the math, see:
                  // http://www.cgafaq.info/wiki/B%C3%A9zier_circle_approximation
                  // http://www.tinaja.com/glib/ellipse4.pdf
                  vertex(px0, py0); 
                  float dx = px1 - px0; 
                  float dy = py1 - py0;
                  float r  = dx; // dx and dy are the same -- but the sign matters. 
                  float d  = r * 4.0*(sqrt(2.0) - 1.0)/3.0; //i.e. 0.55228475
    
                  //---------------
                  if (((px1 > px0) && (py1 < py0)) || ((px1 < px0) && (py1 > py0))) {
                    if (USE_BEZIER_NOT_ARCS) {
                      float cxa = px0    ; 
                      float cya = py0 - d; 
                      float cxb = px1 - d; 
                      float cyb = py1    ; 
                      bezierVertex(cxa, cya, cxb, cyb, px1, py1);
                    } 
                    else {
                      for (int t=1; t<=ARC_RESOLUTION; t++) {
                        float angle = PI + HALF_PI * (float)t/ (float)ARC_RESOLUTION;
                        float cx = px1 + r * cos(angle);
                        float cy = py0 + r * sin(angle);
                        vertex(cx, cy);
                      }
                    }
                  } 
                  //---------------
                  else if (((px1 < px0) && (py1 < py0)) || ((px1 > px0) && (py1 > py0))) {
                    if (USE_BEZIER_NOT_ARCS) {
                      float cxa = px0 + d; 
                      float cya = py0    ; 
                      float cxb = px1    ; 
                      float cyb = py1 - d; 
                      bezierVertex(cxa, cya, cxb, cyb, px1, py1);
                    } 
                    else {
                      for (int t=1; t<=ARC_RESOLUTION; t++) {
                        float angle = HALF_PI + HALF_PI * (float)t/ (float)ARC_RESOLUTION;
                        float cx = px0 - r * cos(angle);
                        float cy = py1 - r * sin(angle);
                        vertex(cx, cy);
                      }
                    }
                  } 
    
                  else {
                    if (i > 0) {
                      // no good reason why this would be the case, but...
                      vertex(px1, py1);
                    }
                  }
    
                  indexOfPrevAppendedPoint = index;
                } 
                else if (!pPrevWasDiagonal) {
    
                  // Point p2 is aligned (horizontally or vertically) with the previous point, p1. 
                  // We only append p1 if the orientation has changed (i.e., turned a corner). 
                  if ((px2 > px1) && (py2 == py1)) {
                    orientationCurr = DIR_RIGHT;
                  } 
                  else if ((px2 < px1) && (py2 == py1)) {
                    orientationCurr = DIR_LEFT;
                  } 
                  else if ((px2 == px1) && (py2 > py1)) {
                    orientationCurr = DIR_DOWN;
                  } 
                  else if ((px2 == px1) && (py2 < py1)) {
                    orientationCurr = DIR_UP;
                  } 
                  else { 
                    // WTF; not sure how this would happen.
                  }
    
                  if (orientationCurr != orientationPrev) { // if there has been a change in orientation
                    if (true) { //index != (indexOfPrevAppendedPoint +1)) { // if it's not just the next point
                      if (i < nPointsInContour) { // prevent extra looparoundie
                        vertex(px1, py1);
                        indexOfPrevAppendedPoint = index;
                      }
                    }
                  }
                  orientationPrev = orientationCurr;
                }
              }
              px1 = px2; 
              py1 = py2;
            }
            endShape(CLOSE);
          }
        }
      }
    
    
    
    
      //===============================================
      int compileLabelColors(int buffer[], int bufW, int bufH) {
    
        colorLabels.clear();
        color black = color(0, 0, 0);
        for (int y=0; y<bufH; y++) {
          for (int x=0; x<bufW; x++) {
            color val = buffer[y*bufW + x];
    
            if (val != black) {
              if (colorLabels.size() == 0) {
                colorLabels.add(val);
              }
              else {
    
                boolean bFound = false;
                for (int i=0; i<colorLabels.size(); i++) {
                  color col = (color)(((Integer)(colorLabels.get(i))).intValue()); // yech
                  if (val == col) {
                    bFound = true;
                  }
                }
                if (bFound == false) {
                  colorLabels.add(val);
                }
              }
            }
          }
        }
    
        return colorLabels.size();
      }
    
      //===============================================
      void findBlobStartPixel (int whichLabel, int[] buffer, int bufW, int bufH) {
    
        color searchColor = (color)(((Integer)(colorLabels.get(whichLabel))).intValue());
        boolean foundBlobStartPixel = false;
        color black = color(0, 0, 0);
    
        for (int y=0; y<bufH; y++) {
          for (int x=0; x<bufW; x++) {
            color val = buffer[y*bufW + x];
    
            if (!foundBlobStartPixel && (val == searchColor)) {
              firstPixel.x = x;
              firstPixel.y = y;
              foundBlobStartPixel = true;
            }
          }
        }
      }
    
    
      //===============================================
      boolean isPixelLocationLegal (int x, int y, int bufW, int bufH) {
        if (x < 0 || x >= bufW) return false;
        if (y < 0 || y >= bufH) return false;
        return true;
      }
    
      //===============================================
      /*  Compute the chain code of the object beginning at pixel (i,j).
       Return the code as NN integers in the array C.          */
      void compute8NeighborChainCode (int i, int j, int[] buffer, int bufW, int bufH) {
        int val, m, q, r, ii, d, dii;
        int lastdir, jj;
    
        ArrayList aContour = new ArrayList<Point2d>();
        aContour.clear();
    
        // Table given index offset for each of the 8 directions.
        int di[] = {
          0, -1, -1, -1, 0, 1, 1, 1
        };
        int dj[] = {
          1, 1, 0, -1, -1, -1, 0, 1
        };
    
    
        val = buffer[j*bufW+i]; 
        q = i;   
        r = j; 
        lastdir = 4;
    
        do {
          m = 0;
          dii = -1;  
          d = 100;
          for (ii=lastdir+1; ii<lastdir+8; ii++) {     /* Look for next */
            jj = ii%8;
            if (isPixelLocationLegal (di[jj]+q, dj[jj]+r, bufW, bufH)) {
              if ( buffer[(dj[jj]+r)*bufW + (di[jj]+q)] == val) {
                dii = jj;
                m = 1;
                break;
              }
            }
          }
    
          if (m != 0) { /* Found the next pixel ... */
            Point2d P = new Point2d(q, r);
            aContour.add(P);  
    
            q += di[dii];
            r += dj[dii];
            lastdir = (dii+5)%8;
          }
          else {
            break;    /* NO next pixel */
          }
        }
        while ( (q!=i) || (r!=j) );   /* Stop when next to start pixel */
        chains.add(aContour);
      }
    
    
      //===============================================
      void repairLeftTurningInteriorCorners () {
        // left-turning interior corners were clipped by one pixel. Fix 'em.
    
        for (int n=0; n<chains.size(); n++) {
          ArrayList aContour = (ArrayList)chains.get(n);
    
          for (int i=1; i<(aContour.size()-1); i++) {
            Point2d P0 = (Point2d) aContour.get(i-1);
            Point2d P1 = (Point2d) aContour.get(i  );
            Point2d P2 = (Point2d) aContour.get(i+1);
    
            if      ((P1.x == P0.x) && (P1.y <  P0.y) && (P2.x < P1.x) && (P2.y < P1.y)) {
              Point2d P = new Point2d(P1.x, P2.y);
              aContour.add(i+1, P);
            } 
            else if ((P1.x >  P0.x) && (P1.y == P0.y) && (P2.x > P1.x) && (P2.y < P1.y)) {
              Point2d P = new Point2d(P2.x, P1.y);
              aContour.add(i+1, P);
            } 
            else if ((P1.x <  P0.x) && (P1.y == P0.y) && (P2.x < P1.x) && (P2.y > P1.y)) {
              Point2d P = new Point2d(P2.x, P1.y);
              aContour.add(i+1, P);
            } 
            else if ((P1.x == P0.x) && (P1.y >  P0.y) && (P2.x > P1.x) && (P2.y > P1.y)) {
              Point2d P = new Point2d(P1.x, P2.y);
              aContour.add(i+1, P);
            }
          }
        }
      }
    
      // end ContourTracer class
    }
    
    
    // Author: "Golan Levin" <golan@flong.com>, 01 August, 2011
    //================================================================
    String getUserSelectedQRCodeImageFilename () {
      // Ask the user to load a QR code image file.
      // These commands to open a file chooser are taken from: 
      // http://processing.org/discourse/yabb2/YaBB.pl?board=Syntax;action=display;num=1210972905
      
      println("Please select a QR code image file."); 
      QRImageFilename = QRDefaultImageFilename;
      JFileChooser chooser = new JFileChooser();
      boolean bFailed = false;
    
      try
      {
        File dataDir = new File(sketchPath, "data/");
        if (!dataDir.exists()) {
          dataDir.mkdirs();
        }
        
        String fileExtensions[] = {"gif", "jpg", "jpeg", "png", "tiff", "tif"};
        ImageFileFilter IFF = new ImageFileFilter (fileExtensions, "Images");
        
        chooser.setAcceptAllFileFilterUsed (true); 
        chooser.addChoosableFileFilter(IFF);
        chooser.setFileFilter(IFF);
     
        chooser.setDialogTitle("Please select a QR code image!");
        chooser.setApproveButtonText("Load QR Image"); 
        chooser.setApproveButtonToolTipText ("Load the selected QR code. This should be a png, jpg, gif or tif."); 
        chooser.setCurrentDirectory(dataDir); 
    
        int returnVal = chooser.showOpenDialog(null);
        if (returnVal == JFileChooser.APPROVE_OPTION) {
    
          String tmpString = chooser.getSelectedFile().getName();
          String ext = tmpString.substring(tmpString.lastIndexOf('.') + 1);
          ext.toLowerCase();
          if (ext.equals("jpg") || ext.equals("jpeg") || ext.equals("gif") || ext.equals("png")) {
          //QRImageFilename = chooser.getSelectedFile().getName();
            QRImageFilename = chooser.getSelectedFile().getPath();
            println("You chose to open this file:\n  " + QRImageFilename);
            println();
          } 
          else {
            bFailed = true;
          }
        } 
        else {
          bFailed = true;
        }
      }
      catch(Exception e) {
        e.printStackTrace();
      }
    
      if (bFailed) {
        println("Loading default QR code image: " + QRImageFilename);
      }
      
      return QRImageFilename;
    }
    
    
    //===============================================================
    // adapted from http://www.student.nada.kth.se/~u1eetop7/prost/ExampleFileFilter.java
    class ImageFileFilter extends FileFilter { 
    
      private String TYPE_UNKNOWN = "Type Unknown";
      private String HIDDEN_FILE = "Hidden File";
    
      private Hashtable filters = null;
      private String description = null;
      private String fullDescription = null;
      private boolean useExtensionsInDescription = true;
    
      //----------------------------------------------------------------
      // Example: new ImageFileFilter (String {"gif", "jpg"}, "Gif and JPG Images");
      // "gif", "jpg", "jpeg", "png", "tiff", "tif"
      public ImageFileFilter(String[] filters, String description) {
        this.filters = new Hashtable();
        for (int i = 0; i < filters.length; i++) {
          addExtension(filters[i]);
        }
        if (description!=null) {
          setDescription(description);
        }
      }
    
      //----------------------------------------------------------------
      public void addExtension(String extension) {
        if (filters == null) {
          filters = new Hashtable(5);
        }
        filters.put(extension.toLowerCase(), this);
        fullDescription = null;
      }
      
      //----------------------------------------------------------------
      public void setDescription(String description) {
        this.description = description;
        fullDescription = null;
      }
    
      //----------------------------------------------------------------
      // Returns the human readable description of this filter. For
      // example: "JPEG and GIF Image Files (*.jpg, *.gif)"
      public String getDescription() {
        if (fullDescription == null) {
          if (description == null || isExtensionListInDescription()) {
            fullDescription = description==null ? "(" : description + " (";
            // build the description from the extension list
            Enumeration extensions = filters.keys();
            if (extensions != null) {
              fullDescription += "." + (String) extensions.nextElement();
              while (extensions.hasMoreElements ()) {
                fullDescription += ", " + (String) extensions.nextElement();
              }
            }
            fullDescription += ")";
          } 
          else {
            fullDescription = description;
          }
        }
        return fullDescription;
      }
    
      //----------------------------------------------------------------
      public boolean accept(File f) {
        if (f != null) {
          if (f.isDirectory()) {
            return true;
          }
          String extension = getExtension(f);
          if (extension != null && filters.get(getExtension(f)) != null) {
            return true;
          };
        }
        return false;
      }
      
      //----------------------------------------------------------------
      public boolean isExtensionListInDescription() {
        return useExtensionsInDescription;
      }
    
      //----------------------------------------------------------------
      public String getExtension(File f) {
        if (f != null) {
          String filename = f.getName();
          int i = filename.lastIndexOf('.');
          if (i>0 && i<filename.length()-1) {
            return filename.substring(i+1).toLowerCase();
          };
        }
        return null;
      }
      
      //----------------------------------------------------------------
      public void setExtensionListInDescription(boolean b) {
        useExtensionsInDescription = b;
        fullDescription = null;
      }
    }
    
    
    
    
    //===============================================================
    void setupGUI() {
      controlP5 = new ControlP5(this);
    
      myTextlabelA = controlP5.addTextlabel("label", "QR_STENCILER BY F.A.T. LAB      ", 20, 50);
    
      bang1    = controlP5.addBang("recomputeStencil", 20, 80, 20, 20);
      bang1.setTriggerEvent(Bang.RELEASE);
      bang1.setLabel("RE-COMPUTE STENCIL");
    
      if (!DISABLE_PDF_EXPORT_FOR_APPLET) {
        bang2  = controlP5.addBang("openPDFInAcrobat", 20, 350, 20, 20);
        bang2.setTriggerEvent(Bang.RELEASE);
        bang2.setLabel("OPEN PDF IN ACROBAT");
      }
    
    
      checkbox = controlP5.addCheckBox("checkBox", 20, 130);
      checkbox.setColorForeground(color(120));
      checkbox.setColorActive(color(255));
      checkbox.setColorLabel(color(255));
      checkbox.setItemsPerRow(1);
      checkbox.setSpacingColumn(0);
      checkbox.setSpacingRow(10);
      // add items to a checkbox.
      checkbox.addItem("INVERT STENCIL", 1);
      checkbox.addItem("DO ALL COMPUTED BRIDGES", 1); 
      checkbox.addItem("DO ROUNDED CORNERS", 1);
      checkbox.addItem("USE BEZIER NOT ARCS", 1);
    
      // float sliders
      slider0  = controlP5.addSlider("BRIDGE_CULLING_FACTOR", 0.00, 0.99, 0.50, 20, 230, 90, 10);
      slider1  = controlP5.addSlider("BRIDGE_THICKNESS", 0.05, 0.30, 0.17, 20, 250, 90, 10);
      slider2  = controlP5.addSlider("PDF_LINE_THICKNESS", 0.0072, 1.00, 0.50, 20, 270, 90, 10);
    
      // int sliders
      slider3  = controlP5.addSlider("MIN_BRIDGES_PER_ISLAND", 1, 4, 3, 20, 290, 90, 10);
      slider4  = controlP5.addSlider("CORNER_THICKENING", 1, 10, 3, 20, 320, 90, 10);
    
      slider3.setNumberOfTickMarks(4);
      slider3.setSliderMode(Slider.FLEXIBLE);
      slider4.setNumberOfTickMarks(10);
      slider4.setSliderMode(Slider.FLEXIBLE);
    
    
      int n = 0;
      if (DO_WHITE_PAINT_STENCIL) { 
        checkbox.activate(n);
      } 
      else {
        checkbox.deactivate(n);
      }
      n++;
    
      if (DO_ALL_COMPUTED_BRIDGES) {
        checkbox.activate(n);
      } 
      else {
        checkbox.deactivate(n);
      }
      n++;
    
      if (DO_ROUNDED_CORNERS) {
        checkbox.activate(n);
      } 
      else {
        checkbox.deactivate(n);
      }
      n++;
    
      if (USE_BEZIER_NOT_ARCS) {
        checkbox.activate(n);
      } 
      else {
        checkbox.deactivate(n);
      }
      n++;
    }
    
    //===============================================================
    void updateParametersFromGUI() {
      BRIDGE_CULLING_FACTOR  =       slider0.value();
      BRIDGE_THICKNESS       =       slider1.value();
      PDF_LINE_THICKNESS     =       slider2.value();
      MIN_BRIDGES_PER_ISLAND = (int) slider3.value();
      CORNER_THICKENING      = (int) slider4.value();
    }
    
    //===============================================================
    public void recomputeStencil() {
      bCompleted = false;
    }
    
    //===============================================================
    public void openPDFInAcrobat() {
      if (bComputedStencilPDF && !DISABLE_PDF_EXPORT_FOR_APPLET) {
        open (QRStencilPDFFilename);
      }
    }
    
    //===============================================================
    void controlEvent(ControlEvent theEvent) {
      if (!bSetupPhase) {
        if (theEvent.isGroup()) {
          recomputeStencil();
          // println("got an event from "+theEvent.group().name()+"\t");
          // checkbox uses arrayValue to store the state of individual checkbox-items:
          for (int i=0;i<theEvent.group().arrayValue().length;i++) {
            int val = (int)theEvent.group().arrayValue()[i];
    
            int n = 0;
            if (i== n++) {
              DO_WHITE_PAINT_STENCIL = (val==1);
            } 
            else if (i==n++) {
              DO_ALL_COMPUTED_BRIDGES= (val==1);
            }
            else if (i==n++) {
              DO_ROUNDED_CORNERS     = (val==1);
            } 
            else if (i==n++) {
              USE_BEZIER_NOT_ARCS    = (val==1);
            }
            println (i + " " + val);
          }
        } 
        else {
          String name = theEvent.name();
          if (name.equals("BRIDGE_CULLING_FACTOR") || 
            name.equals("BRIDGE_THICKNESS") || 
            name.equals("PDF_LINE_THICKNESS") || 
            name.equals("MIN_BRIDGES_PER_ISLAND") || 
            name.equals("CORNER_THICKENING")) {
            if (mouseX != pmouseX) {
              recomputeStencil();
            }
          }
        }
      }
    }
    
    
    // Author: "Golan Levin" <golan@flong.com>, 01 August, 2011
    //================================================================
    // Code in this file is NOT USED OR REQUIRED by the QR_STENCILER program. 
    // It was used to process the 100 QR hobo codes that accompany QR_STENCILER. 
    // Feel free to delete this file. 
    
    // in keyPressed():
    // QRImageFilename = getNextHoboFile();
    // QR = loadImage (QRImageFilename);
    // println("Loading:\t" + QRImageFilename);
    
    
    ArrayList<String> hoboQRFiles;
    int currentHoboFileIndex; 
    //===============================================================
    void loadHoboFileList(){
      
      hoboQRFiles = new ArrayList<String>();
      currentHoboFileIndex = 0; 
      
      String aFilename;
      String myPath = sketchPath + "/data/QR_hobo_codes/";
      File aFolder = new File(myPath);
      File[] listOfFiles = aFolder.listFiles(); 
    
      int count = 0;
      for (int i = 0; i < listOfFiles.length; i++) {
        if (listOfFiles[i].isFile()) {
          aFilename = listOfFiles[i].getName();
          if (aFilename.endsWith (".png")) {
            aFilename = myPath + aFilename;
            hoboQRFiles.add(aFilename); 
          }
        }
      }
    }
    
    //===============================================================
    String getNextHoboFile(){
      String hoboFilename = QRDefaultImageFilename;
      if (currentHoboFileIndex < hoboQRFiles.size()){
        hoboFilename = hoboQRFiles.get(currentHoboFileIndex); 
        currentHoboFileIndex = currentHoboFileIndex+1;
      } 
      return hoboFilename;
    }
    
    
    //===============================================================
    void generateHoboCodeHtml() {
      // generates a fragment of an HTML table to present all of the QR hobo codes. 
      String aFilename;
      String myPath = sketchPath + "/data/QR_hobo_codes/";
      File aFolder = new File(myPath);
      File[] listOfFiles = aFolder.listFiles(); 
    
      int count = 0;
      for (int i = 0; i < listOfFiles.length; i++) {
        if (listOfFiles[i].isFile()) {
          aFilename = listOfFiles[i].getName();
          if (aFilename.endsWith (".png")) {
    
            String aPDFFile = aFilename.substring(0, aFilename.lastIndexOf('.')) + ".pdf"; 
            String transcription = aFilename.substring(0, aFilename.lastIndexOf('.'));
    
            String newTranscription = ""; 
            for (int j=0; j<transcription.length(); j++) {
              char c = transcription.charAt(j); 
              if (c == '_') {
                c = ' ';
              }
              newTranscription += c;
            }
    
            if (count%4 == 0) {
              println("<tr>");
            }
    
            print("\t<td width=\"125\" align=\"center\" valign=\"top\">");
            print("<a href=\"QR_hobo_codes/" + aFilename + "\">");
            print("<img \n\t\tsrc=\"QR_hobo_codes/" + aFilename + "\" "); 
            print("width=\"108\" height=\"108\" border=\"0\" /></a><br />"); 
            print("<em>" + newTranscription + "</em><br />"); 
            println();
            print("\t\t<a href=\"QR_hobo_codes/" + aFilename + "\">png</a> | "); 
            print("<a href=\"QR_hobo_codes/" + aPDFFile  + "\">stencil</a></td>"); 
            println();
    
            if ((count%4 == 3) || (i==(listOfFiles.length - 1))) {
              println("</tr>");
            }
            count++;
          }
        }
      }
    }
    
    
    // Author: "Golan Levin" <golan@flong.com>
    // Note: this is the applet version. For the executable version, please see:
    // https://github.com/golanlevin/QR_STENCILER
    // To convert this into the executable version, set
    // DISABLE_PDF_EXPORT_FOR_APPLET = false; 
    // 
    // For more information about this project, please see: 
    // http://fffff.at/qr-stenciler-and-qr-hobo-codes/
    
    //=====================================================================
    void QRStencilerInfo() {
      // 
      //            ___  ___   ___ _____ ___ _  _  ___ ___ _    ___ ___ 
      //           / _ \| _ \ / __|_   _| __| \| |/ __|_ _| |  | __| _ \
      //          | (_) |   / \__ \ | | | _|| .` | (__ | || |__| _||   /
      //           \__\_\_|_\ |___/ |_| |___|_|\_|\___|___|____|___|_|_\
      //           By Golan Levin and Asa Foster III for FFFFF.AT, 2011 
      //
      println ();
      println (" ********************************************************");
      println (" *                                                      *"); 
      println (" *  QR_STENCILER                                        *");
      println (" *  Version: 01 August, 2011                            *");  
      println (" *  http://fffff.at/qr-stenciler-and-qr-hobo-codes/     *");
      println (" *  By Golan Levin and Asa Foster III for FFFFF.AT      *"); 
      println (" *  Developed with Processing v.0198, a free, cross-    *"); 
      println (" *  platform Java programming toolkit for the arts.     *");
      println (" *                                                      *"); 
      println (" *  ABOUT                                               *"); 
      println (" *  This free program loads a user-specified QR code    *");
      println (" *  image, from which it generates a topologically      *");
      println (" *  correct stencil PDF, suitable for laser-cutting.    *"); 
      println (" *                                                      *"); 
      println (" *  INSTRUCTIONS                                        *"); 
      println (" *  >> QR_STENCILER has been tested in MacOSX 10.6.8.   *"); 
      println (" *  1. Make a QR code image which embeds a short text.  *");  
      println (" *     Try GoQR.me, Kaywa, or the Google Chart API.     *"); 
      println (" *  2. Download and install 'Processing' from           *"); 
      println (" *     http://www.processing.org/download               *");
      println (" *     We used v.0198 but v.1.5.1 seems OK too.         *");
      println (" *  3. Unzip 'QR_STENCILER.zip' to a folder.            *");
      println (" *  4. Put your QR code image in 'QR_STENCILER/data/'   *"); 
      println (" *  5. Launch Processing and open 'QR_STENCILER.pde'    *"); 
      println (" *  6. Press 'Run' (Command-R) to start the stenciler.  *"); 
      println (" *  7. You will be prompted to Open your QR code image. *");
      println (" *     A default will be opened if none is provided.    *"); 
      println (" *  8. After doing so, the program will generate a      *");
      println (" *     stencil PDF in the 'data' folder.                *"); 
      println (" *  9. That PDF can now be opened in your favorite CAD  *");
      println (" *     software, for laser-cutting cardboard, etc.      *");
      println (" *  10.After marking your stencil, test it with a QR    *"); 
      println (" *     reader, such as TapMedia's free iPhone app.      *"); 
      println (" *                                                      *"); 
      println (" *  LICENSE                                             *"); 
      println (" *  QR_STENCILER shall be used for Good, not Evil.      *");  
      println (" *  It is licensed under a Creative Commons             *"); 
      println (" *  Attribution-NonCommercial-ShareAlike 3.0 Unported   *");  
      println (" *  License (by-nc-sa 3.0), as described at             *"); 
      println (" *  http://creativecommons.org/licenses/by-nc-sa/3.0/   *"); 
      println (" *  You are free to distribute, remix, and modify       *"); 
      println (" *  QR_STENCILER, so long as you share alike and        *");
      println (" *  provide attribution to FFFFF.AT. The repackaging    *");
      println (" *  of QR_STENCILER as or into commercial software, is  *");
      println (" *  expressly prohibited. Please note that QR_STENCILER *"); 
      println (" *  also enjoys protections under the GRL Repercussions *"); 
      println (" *  3.0 license, http://bit.ly/cc-repercussions.        *"); 
      println (" *  For other uses, please contact FFFFF.AT.            *"); 
      println (" *  The 100 QR_HOBO_CODES and their respective stencils *"); 
      println (" *  are hereby dedicated to the public domain.          *");
      println (" *                                                      *"); 
      println (" *  WARRANTY                                            *");  
      println (" *  This software is provided AS IS, without warranty   *"); 
      println (" *  of any kind, expressed or implied, including but    *");
      println (" *  not limited to the warranties of merchantibility,   *"); 
      println (" *  fitness for a particular purpose, and noninfringe-  *"); 
      println (" *  ment. In no event shall the authors be liable for   *");
      println (" *  any claim, damages or other liability, whether in   *");
      println (" *  an action of contract, tort or otherwise, arising   *");
      println (" *  from, out of or in connection with this software    *");
      println (" *  or its use.                                         *");
      println (" *                                                      *");
      println (" *  ACKNOWLEDGEMENTS                                    *");
      println (" *  QR_STENCILER was created by Golan Levin and         *");
      println (" *  Asa Foster III with support from the STUDIO for     *");
      println (" *  Creative Inquiry and the School of Art at           *");
      println (" *  Carnegie Mellon University. Thanks to Ben Fry,      *");
      println (" *  Marcus Beausang, Neil Brown & Judy Robertson for    *");  
      println (" *  great code. A tip of the hat to Fred Trotter,       *");
      println (" *  Jovino, le Suedois, Patrick Donnelly and others     *");
      println (" *  who have gone down similar paths. Additional        *"); 
      println (" *  thanks to Andrea Boykowycz for creative input.      *"); 
      println (" *  Some of the QR Hobo Codes are inspired by:          *");
      println (" *  http://www.worldpath.net/~minstrel/hobosign.htm     *");
      println (" *  http://cockeyed.com/archive/hobo/modern_hobo.html   *");
      println (" *  http://en.wikipedia.org/wiki/Warchalking            *"); 
      println (" * 'QR code' is trademarked by Denso Wave, Inc.         *");
      println (" *                                                      *"); 
      println (" *  CONTACT                                             *"); 
      println (" *  Inquiries about QR_STENCILER may be directed to:    *"); 
      println (" *  Golan Levin <golan@flong.com>                       *"); 
      println (" *                                                      *");
      println (" ********************************************************"); 
      println ();
    }
    
    import processing.pdf.*;
    import javax.swing.*;
    import javax.swing.filechooser.FileFilter;
    
    import controlP5.*;
    
    
    //=====================================================================
    // ADJUSTABLE PARAMETERS: You can change these, if you so desire
    //
    float BRIDGE_THICKNESS             = 0.17;   // Ratio of bridge thickness to QR grid size, i.e. 1:6
    float PDF_LINE_THICKNESS           = 0.50;   // For 0.001" thick lines, use 0.072.
    float BRIDGE_CULLING_FACTOR        = 0.60;   // Set between 0..1; Higher values cull more bridges.
    int   MIN_BRIDGES_PER_ISLAND       = 3;      // Just what it says
    int   CORNER_THICKENING            = 3;      // Number of pixels of corner thickening.
    int   ARC_RESOLUTION               = 12;     // Number of points on a generated circular arc corner.
    boolean DO_WHITE_PAINT_STENCIL     = false;
    boolean DO_ROUNDED_CORNERS         = true;   // Enables corner rounding and path simplification.
    boolean USE_BEZIER_NOT_ARCS        = false;  // Enables Bezier exporting instead of generating arc points. 
    boolean DO_ADVANCED_BRIDGING       = true;   // Chooses between advanced and simple bridging. 
    boolean DO_ALL_COMPUTED_BRIDGES    = false;  // Invalidates RANDOM_BRIDGE_CULLING_FACTOR
    boolean DO_OPEN_PDF_WHEN_DONE      = false;   // Open the stencil PDF in Acrobat when done? 
    
    // IMPORTANT: 
    boolean DISABLE_PDF_EXPORT_FOR_APPLET = true; // turns this into an executable version that can export PDFs!
    
    
    //=====================================================================
    // For more information about generating your own QR codes, see:
    // http://code.google.com/apis/chart/image/docs/gallery/qr_codes.html
    // Note: You may obtain better results by experimenting with the QR
    // "error correction level" (L,M,Q,H) in the "chld" field. 
    //
    // Our default QR code image ("hello world") is from: 
    // https://chart.googleapis.com/chart?chs=540x540&cht=qr&chld=L|1&chl=hello%20world
    String QRDefaultImageFilename;
    String QRImageFilename;
    String QRStencilPDFFilename;
    boolean bComputedStencilPDF = false;
    PImage QR; 
    
    
    ControlP5 controlP5;
    CheckBox  checkbox;
    Slider    slider0;
    Slider    slider1;
    Slider    slider2;
    Slider    slider3;
    Slider    slider4;
    Bang      bang1;
    Bang      bang2;
    Textlabel myTextlabelA;
    
    //=====================================================================
    // Other internal (global) variables and constants.
    //
    ConnectedComponentLabeler CCL;  // Determines which pixels are holes/islands   
    ContourTracer CT;   // Traces hole contours for PDF output
    PFont tinyPdfFont;
    boolean bCompleted;
    boolean bSetupPhase;
    
    int blackAndWhiteImage[];
    int blackAndWhiteImageInverse[];
    int coloredLabeledImage[];
    int nPixels;
    
    final color white = color(255);
    final color black = color(0);
    final int inverseMargin = 1;
    final int controlPanelWidth = 900/4;
    
    final int DIR_UP    = 0; 
    final int DIR_DOWN  = 1;
    final int DIR_LEFT  = 2; 
    final int DIR_RIGHT = 3;
    
    
    //===============================================================
    void setup() {
      bSetupPhase = true;
      QRStencilerInfo(); 
    
      QRDefaultImageFilename = sketchPath + "/data/" + "QR_hello_world.png";
      QRImageFilename = "QR_hello_world.png";
      if (!DISABLE_PDF_EXPORT_FOR_APPLET){
        QRImageFilename = getUserSelectedQRCodeImageFilename(); // See FileLoading.pde
      } 
      QR = loadImage (QRImageFilename);
      size (900, 675, JAVA2D); 
    
      nPixels = QR.width * QR.height;
      blackAndWhiteImage        = new int[nPixels];
      blackAndWhiteImageInverse = new int[nPixels];
      coloredLabeledImage       = new int[nPixels];
    
      CCL = new ConnectedComponentLabeler (QR.width, QR.height);
      CT  = new ContourTracer();
      bCompleted = false;
    
      hint (ENABLE_NATIVE_FONTS);
      tinyPdfFont = createFont("Arial", 6);
    
      setupGUI(); // see Gui.pde
      bSetupPhase = false;
    }
    
    
    
    //===============================================================
    void draw() {
      updateParametersFromGUI();
      if (bCompleted == false) {
        doMainProcess();
        bCompleted = true;
      }
    }
    
    //===============================================================
    void keyPressed() {
      // Restart computation of a new stencil. Useful if you have some randomization enabled.  
      if (key == ' ') { 
        bCompleted = false;
      }
    }
    
    //===============================================================
    void doMainProcess() {
      // This is the main processing routine for the program. 
      // Assuming PImage QR contains a valid QR code image, 
      // this routine will export a PDF stencil file for it. 
    
    
      // 1. Extract the pixels (as a color[] array) from the loaded QR image. 
      // See Utils.pde for implementation. 
      copyBlackAndWhiteImageFromPImage (QR); // see Utils.pde
      println("* Extracted pixels."); 
    
      // 2. Some jpgs have compression artifacts that produce "near-black" or "near-white"; enforce pure colors. 
      // See Utils.pde for implementation. 
      binarizeTheImage (blackAndWhiteImage, QR.width, QR.height); // see Utils.pde
      println("* Input image binarized."); 
    
      // 3. Thicken the corners of adjacent grid units, creating a stronger and more effective stencil.
      // See Bridger.pde for implementation. 
      bridgeCorners (blackAndWhiteImage, QR.width, QR.height);  
      println("* Stencil interior corners bridged."); 
    
      // 4. Deal with white-painted QR stenciling, if requested.
      // Involves adding a margin and inversion.
      // See Utils.pde for implementation.
      handleInverseStencil();
    
      // 5. Uniquely label (with individual colors) each of the blobs; this identifies "islands". 
      // See: http://en.wikipedia.org/wiki/Connected-component_labeling
      // See: http://en.wikipedia.org/wiki/Stencil
      // See ConnectedComponentLabeler.pde for implementation. 
      coloredLabeledImage = CCL.doLabel(blackAndWhiteImage, QR.width, QR.height); 
      println("* Stencil islands identified."); 
    
      // 6. Create bridges from the main stencil body to all floaters... so they don't fall out!
      // See Bridger.pde for implementation.
      // bridgeIslands (blackAndWhiteImage, QR.width, QR.height); 
      bridgeIslands (blackAndWhiteImage, QR.width, QR.height); 
      println("* Stencil islands bridged."); 
    
      // 7. Invert the QR image, so that we can find its *holes* with the contour tracer.
      // See Utils.pde for implementation. 
      computeBlackAndWhiteInverseBuffer (blackAndWhiteImage, blackAndWhiteImageInverse, nPixels);
      println("* Stencil inverted, for hole-finding."); 
    
      // 8. Uniquely label all of the holes in the stencil.
      // See ConnectedComponentLabeler.pde for implementation. 
      coloredLabeledImage = CCL.doLabel(blackAndWhiteImageInverse, QR.width, QR.height);
      println("* Stencil holes identified."); 
    
      // 9. Trace all of the holes, producing vector-based 8-connected chain codes. See: 
      // http://imageprocessingplace.com/downloads_V3/root_downloads/tutorials/contour_tracing_Abeer_George_Ghuneim/alg.html
      // See ContourExtraction.pde for implementation. 
      CT.traceBlobContours (coloredLabeledImage, QR.width, QR.height);
      println("* Stencil holes traced."); 
    
      // 10. Draw the contours (chain codes) of the holes, and export a PDF of the result. 
      // This PDF can now be loaded into Illustrator, etc. and used for laser-cutting. 
      if (!DISABLE_PDF_EXPORT_FOR_APPLET) {
        String QrPdfFileName = drawAndExportPDF(); // See below for implementation.
        println("* Stencil PDF exported to the 'data' folder of this sketch:\n  " + QrPdfFileName);
      }
    
      // 11. Draw the QR to the screen, as it will appear and with the exported stencil contour lines. 
      drawPrettyResultsToScreen();
      println ("  Done creating QR stencil at " + hour() + ":" + minute() + ":" + second() + " \n");
      println (" ********************************************************");
    }
    
    
    //===============================================================
    void drawPrettyResultsToScreen() {
    
      background ((DO_WHITE_PAINT_STENCIL) ? black : white); 
      fill (100); 
      rect(0, 0, controlPanelWidth, height); 
    
    
    
      boolean bCenterVersusScale = true;
      pushMatrix();
    
      float drawMargin = 50; 
      float scaleFactor = (float)(height - drawMargin*2) / (float)(max(QR.height, QR.width));
      translate(controlPanelWidth, 0);
      translate(drawMargin, drawMargin); 
      scale (scaleFactor, scaleFactor); 
    
    
    
      stroke(255, 0, 128);
      strokeWeight(1.0/scaleFactor);
      fill ((DO_WHITE_PAINT_STENCIL) ? white : black); 
      if (DO_ROUNDED_CORNERS) {
        CT.drawAllChainsWithFilletedCorners();
      } 
      else { 
        CT.drawAllChains();
      }
    
      popMatrix();
    }
    
    
    //===============================================================
    String drawAndExportPDF() {
    
      // generate the filename for the stencil PDF.
      int QRImageFilenameLen = QRImageFilename.length();
      String QR_PDFFilename = QRImageFilename.substring(0, QRImageFilename.lastIndexOf('.'));
      String QR_PdfFullFilename  = QR_PDFFilename;
      if (DO_WHITE_PAINT_STENCIL) {
        QR_PdfFullFilename += "_inverse";
      }
      QR_PdfFullFilename += ".pdf";
      QRStencilPDFFilename = QR_PdfFullFilename;
      beginRecord(PDF, QR_PdfFullFilename);
    
      // Generate the text written on the QR code stencil
      boolean bDrawTitleText = true;
      if (bDrawTitleText) {
        String QR_StencilText = QRImageFilename.substring(QRImageFilename.lastIndexOf('/')+1, QRImageFilename.lastIndexOf('.'));
        if (DO_WHITE_PAINT_STENCIL) {
          QR_StencilText += " (for LIGHT/WHITE marking) ";
        } 
        else {
          QR_StencilText += " (for DARK/BLACK marking) ";
        }
        QR_StencilText += " --- Generated by QR_STENCILER, http://fffff.at/qr-stenciler-and-qr-hobo-codes/"; 
        fill(0, 0, 0); 
        textFont(tinyPdfFont, 6);
        text(QR_StencilText, 10, 10);
      }
    
    
      pushMatrix();
      translate((width - QR.width)/2.0, (height - QR.height)/2.0);  
    
      strokeWeight (PDF_LINE_THICKNESS); 
      stroke(0, 0, 0); 
      noFill();
      if (DO_ROUNDED_CORNERS) {
        CT.drawAllChainsWithFilletedCorners();
      } 
      else { 
        CT.drawAllChains();
      }
    
      popMatrix();
      endRecord();
    
      bComputedStencilPDF = true;
      if (DO_OPEN_PDF_WHEN_DONE) {
        open (QR_PdfFullFilename);
      }
      return QR_PdfFullFilename;
    }
    
    
    // Author: "Golan Levin" <golan@flong.com>, 01 August, 2011
    //================================================================
    void copyBlackAndWhiteImageFromPImage (PImage img) {
      for (int i=0; i<nPixels; i++) {
        blackAndWhiteImage[i] = img.pixels[i];
      }
    }
    
    
    //===============================================================
    void copyBufferToScreen(color[] imageBuffer, int nPix) {
      // Note: no safety checks here. 
      loadPixels();
      for (int i=0; i<nPix; i++) {
        pixels[i] = imageBuffer[i];
      }
      updatePixels();
    }
    
    
    //===============================================================
    void computeBlackAndWhiteInverseBuffer (color[] srcBuffer, color[] dstBuffer, int nPix) {
      for (int i=0; i<nPix; i++) {
        if (srcBuffer[i] == black) {
          dstBuffer[i] = white;
        } 
        else {
          dstBuffer[i] = black;
        }
      }
    }
    
    
    //===============================================================
    void binarizeTheImage (color[] bufferToProcess, int bufW, int bufH) {
      // We clobber the image to ensure that .JPG artifacts don't confuse us. 
      // This forces pixels to pure black and pure white. 
    
      int binarizationThreshold = 127;
      for (int y=0; y<bufH; y++) {
        for (int x=0; x<bufW; x++) {
    
          int index  = y*bufW + x;
          color c = bufferToProcess[index];
          float bri = brightness(c); 
    
          if (bri < binarizationThreshold) {
            bufferToProcess[index] = black;
          } 
          else {
            bufferToProcess[index] = white;
          }
        }
      }
    }
    
    
    //===============================================================
    int computeGridSize (color[] qrImageBuffer, int qrImageW, int qrImageH) {
      // calculate the size of the QR code's grid units. 
      // we know that the first thing is a black square which is 7 units wide.
      // use this information to automatically calculate the number of pixels per unit.  
      
      int marginX = 0;
      int marginY = 0;  
      color searchColor = black;
      if (DO_WHITE_PAINT_STENCIL){
        marginX = inverseMargin+1;
        marginY = inverseMargin+1; 
        searchColor = white;
      }
    
      boolean bFoundFirstSearchColorPixel = false;
      boolean bColorHasntChangedSinceIFoundTheFirstSearchColorPixel = true;
      int searchColorLocStartX = -1; 
      int searchColorLocEndX   = -1; 
      int searchColorLocY      = -1; 
    
      for (int y = marginY; y < (qrImageH-marginY); y++) {
        for (int x = marginX; x < (qrImageW-marginX); x++) { 
    
          int index = y*qrImageW + x;
          color someCol = qrImageBuffer[index];
    
          if ((someCol == searchColor) && (bFoundFirstSearchColorPixel == false)) {
            bFoundFirstSearchColorPixel = true;
            searchColorLocStartX = x;
            searchColorLocY = y;
          }
    
          if (y == searchColorLocY) { // if we are still in the same row
            if (bColorHasntChangedSinceIFoundTheFirstSearchColorPixel) {
              if (someCol != searchColor) {
                bColorHasntChangedSinceIFoundTheFirstSearchColorPixel = false;
              } 
              else {
                searchColorLocEndX = x;
              }
            }
          }
        }
      }
    
      if (bFoundFirstSearchColorPixel && (searchColorLocStartX > -1) && (searchColorLocEndX > -1)) {
        int distance = 1 + searchColorLocEndX - searchColorLocStartX; // +1 corrects off-by-one err
        int gridSize = distance / 7; // fixed.
        return gridSize;
      }
      
      println ("Error determining QR code grid size. Is your image a valid QR code?"); 
      return -1;
    }
    
    
    
    //===============================================================
    void handleInverseStencil(){
      if (DO_WHITE_PAINT_STENCIL) {
        
        // put a narrow margin around everything
        int m = inverseMargin;
        for (int y=0; y<QR.height; y++) {
          for (int x=0; x<m; x++) {
            int index = y*QR.width + x;
            blackAndWhiteImage[index] = black;
          }
          for (int x=(QR.width-m); x<QR.width; x++) {
            int index = y*QR.width + x;
            blackAndWhiteImage[index] = black;
          }
        }
        for (int y=0; y<m; y++) {
          for (int x=0; x<QR.width; x++) {
            int index = y*QR.width + x;
            blackAndWhiteImage[index] = black;
          }
          for (int x=0; x<QR.width; x++) {
            int index = (QR.height-1-y)*QR.width + x;
            blackAndWhiteImage[index] = black;
          }
        }
        
        // invert the image for further processing
        for (int y=0; y<QR.height; y++) {
          for (int x=0; x<QR.width; x++) {
            int index = y*QR.width + x;
            if (blackAndWhiteImage[index] == black){
              blackAndWhiteImage[index] = white; 
            } else {
              blackAndWhiteImage[index] = black; 
            }
            
          }
        }
      }
    }
    

    code

    tweaks (0)

    license

    advertisement


    Golan Levin

    QR_STENCILER

    Add to Faves Me Likey@! 2
    You must login/register to add this sketch to your favorites.

    The QR_STENCILER is a free, fully-automated utility which converts QR codes into vector-based stencil patterns suitable for laser-cutting. For information, see QR_STENCILER_applet.pde.

    See also:
    http://github.com/golanlevin/QR_STENCILER
    http://fffff.at/qr-stenciler-and-qr-hobo-codes

    You need to login/register to comment.