private void process(Gray8Image in, BilevelImage out) {
   final int HEIGHT = in.getHeight();
   final int WIDTH = in.getWidth();
   if (out == null) {
     out = new MemoryBilevelImage(WIDTH, HEIGHT);
   }
   final int NUM_ERROR_PIXELS = errorNum.length;
   // create buffer
   int[] buffer = new int[newWidth * numRows];
   // System.out.println("buffer  length=" + buffer.length);
   // fill buffer with numRows (or HEIGHT, whatever is smaller) rows of data
   int n = Math.min(numRows, HEIGHT);
   int offset = leftColumns;
   int bufferYIndex = 0;
   while (n-- > 0) {
     fillBuffer(0, bufferYIndex++, buffer, offset);
     offset += newWidth;
   }
   int bufferLastRowOffset = offset - newWidth;
   // set complete output image to black
   out.clear(BilevelImage.BLACK);
   for (int y = 0; y < HEIGHT; y++) {
     int bufferIndex = leftColumns;
     for (int x = 0; x < WIDTH; x++) {
       int value = buffer[bufferIndex];
       if (value < 0) {
         value = 0;
       } else if (value > 255) {
         value = 255;
       }
       int error;
       if ((value & 0x80) == 0) {
         // black pixel need not be written to output image
         // because all of its pixels have initially been set
         // to that color
         error = value;
       } else {
         // white
         out.putWhite(x, y);
         error = value - 255;
       }
       for (int i = 0; i < NUM_ERROR_PIXELS; i++) {
         int errorPart = error * errorNum[i] / errorDen[i];
         buffer[bufferIndex + indexLut[i]] += errorPart;
       }
       bufferIndex++;
     }
     for (int i = 0, j = newWidth; j < buffer.length; i++, j++) {
       buffer[i] = buffer[j];
     }
     if (bufferYIndex < HEIGHT) {
       fillBuffer(0, bufferYIndex++, buffer, bufferLastRowOffset);
     }
     setProgress(y, HEIGHT);
   }
   setOutputImage(out);
 }
 private void process(Gray8Image in, Gray8Image out) {
   final int HEIGHT = in.getHeight();
   final int WIDTH = in.getWidth();
   final int RIGHT_SHIFT = 8 - grayBits;
   final int[] GRAY_LUT = new int[1 << grayBits];
   for (int i = 0; i < GRAY_LUT.length; i++) {
     GRAY_LUT[i] = i * 255 / (GRAY_LUT.length - 1);
   }
   if (out == null) {
     out = new MemoryGray8Image(WIDTH, HEIGHT);
   }
   final int NUM_ERROR_PIXELS = errorNum.length;
   // create buffer
   int[] buffer = new int[newWidth * numRows];
   // fill buffer with numRows (or HEIGHT, whatever is smaller) rows of data
   int n = Math.min(numRows, HEIGHT);
   int offset = leftColumns;
   int bufferYIndex = 0;
   while (n-- > 0) {
     fillBuffer(0, bufferYIndex++, buffer, offset);
     offset += newWidth;
   }
   int bufferLastRowOffset = offset - newWidth;
   for (int y = 0; y < HEIGHT; y++) {
     int bufferIndex = leftColumns;
     for (int x = 0; x < WIDTH; x++) {
       int value = buffer[bufferIndex];
       if (value < 0) {
         value = 0;
       } else if (value > 255) {
         value = 255;
       }
       int quantized = GRAY_LUT[value >> RIGHT_SHIFT];
       out.putSample(0, x, y, quantized);
       int error = value - quantized;
       for (int i = 0; i < NUM_ERROR_PIXELS; i++) {
         int errorPart = error * errorNum[i] / errorDen[i];
         buffer[bufferIndex + indexLut[i]] += errorPart;
       }
       bufferIndex++;
     }
     for (int i = 0, j = newWidth; j < buffer.length; i++, j++) {
       buffer[i] = buffer[j];
     }
     if (bufferYIndex < HEIGHT) {
       fillBuffer(0, bufferYIndex++, buffer, bufferLastRowOffset);
     }
     setProgress(y, HEIGHT);
   }
   setOutputImage(out);
 }
  private void fitTemplate() {
    int startX = 0, startY = 0;
    int endX = img.getWidth() - bestfit.getTemplate().getWidth();
    int endY = img.getHeight() - bestfit.getTemplate().getHeight();

    centerTemplate(startX, startY, endX, endY, 3);
    templateXOR(img, bestfit.getX(), bestfit.getY(), bestfit.getTemplate(), true);

    sizeTemplate();
    aspectTemplate();
    shiftTemplate();
    sizeTemplate();
    aspectTemplate();
    shiftTemplate();
    templateXOR(img, bestfit.getX(), bestfit.getY(), bestfit.getTemplate(), true);
  }
  private void centerTemplate(int startX, int startY, int endX, int endY, int granularity) {
    int stepX = bestfit.getTemplate().getWidth() / granularity;
    int stepY = bestfit.getTemplate().getHeight() / granularity;
    // System.out.println("stepX = " + stepX + ": stepY = " + stepY);

    double maxsim = -1;
    int simi = -1, simj = -1;
    for (int iCount = startX; iCount <= endX; iCount += stepX) {
      for (int jCount = startY; jCount <= endY; jCount += stepY) {
        double currsim = 1.0 - templateXOR(img, iCount, jCount, bestfit.getTemplate(), false);
        // System.out.println(i + ":" + j + ":" + currsim);
        if (maxsim == -1 || maxsim < currsim) {
          maxsim = currsim;
          simi = iCount;
          simj = jCount;
        }
      }
    }

    // System.out.println("--- maxsim = " + maxsim + ":"
    // + simi + ":" + simj);
    if (maxsim > 0.5) {
      if (stepX >= 4) { // up to an accuracy of 2 pixels
        centerTemplate(
            Math.max(simi - stepX / 2, 0),
            Math.max(simj - stepY / 2, 0),
            Math.min(simi + stepX / 2, img.getWidth()),
            Math.min(simj + stepY / 2, img.getHeight()),
            granularity * 2);
      } else {
        bestfit.setX(simi);
        bestfit.setY(simj);
        bestfit.setSim(maxsim);
      }
    }
  }
 /**
  * Returns the number of black and white differences between the template and exam file.
  *
  * @param img Image of exams
  * @param x x start coordinate of the file
  * @param y y start coordinate of the file
  * @param template Template of exams
  * @param dump Dump to screen
  * @return The difference
  */
 public static double templateXOR(
     Gray8Image img, int x, int y, Gray8Image template, boolean dump) {
   int diff = 0, total = 0;
   for (int j = y; j < y + template.getHeight() && j < img.getHeight(); j++) {
     for (int i = x; i < x + template.getWidth() && i < img.getWidth(); i++) {
       // XXX
       boolean isblack = (img.getSample(i, j) < 200 ? true : false);
       // if(dump) { System.out.print((isblack
       // & template.isWhite(i - x, j - y) ? "1" : ((!isblack)
       // & template.isBlack(i - x, j - y)) ? "-" : "0")); }
       if ((isblack & template.isWhite(i - x, j - y)
           | (!isblack) & template.isBlack(i - x, j - y))) {
         diff++;
       }
       total++;
     }
     // if(dump) { System.out.println(); }
   }
   return ((double) diff) / total;
 }
 private void fillTemplate(
     Gray8Image templateimg, double outerdiamX, double innerdiamX, double aspect) {
   double centerX = templateimg.getWidth() / 2;
   double centerY = templateimg.getHeight() / 2;
   double outerrad = outerdiamX / 2;
   double innerrad = innerdiamX / 2;
   for (int wCount = 0; wCount < templateimg.getWidth(); wCount++) {
     for (int hCount = 0; hCount < templateimg.getHeight(); hCount++) {
       double dist =
           Math.sqrt(
               (wCount - centerX) * (wCount - centerX)
                   + (hCount - centerY) / aspect * (hCount - centerY) / aspect);
       if (dist <= outerrad && dist > innerrad) {
         templateimg.putBlack(wCount, hCount);
       } else {
         templateimg.putWhite(wCount, hCount);
       }
     }
   }
 }