/**
   * Creates a random image and looks for corners in it. Sees if the naive and fast algorithm
   * produce exactly the same results.
   */
  @Test
  public void compareToNaive() {
    GrayU8 img = new GrayU8(width, height);
    ImageMiscOps.fillUniform(img, new Random(0xfeed), 0, 100);

    GrayS16 derivX = new GrayS16(img.getWidth(), img.getHeight());
    GrayS16 derivY = new GrayS16(img.getWidth(), img.getHeight());

    GradientSobel.process(img, derivX, derivY, new ImageBorder1D_S32(BorderIndex1D_Extend.class));

    BoofTesting.checkSubImage(this, "compareToNaive", true, derivX, derivY);
  }
  private static void renderBinary(
      GrayU8 binaryImage, boolean invert, ByteInterleavedRaster raster) {
    int rasterIndex = 0;
    byte data[] = raster.getDataStorage();

    int w = binaryImage.getWidth();
    int h = binaryImage.getHeight();

    int numBands = raster.getNumBands();
    if (numBands == 1) {
      if (invert) {
        for (int y = 0; y < h; y++) {
          int indexSrc = binaryImage.startIndex + y * binaryImage.stride;
          for (int x = 0; x < w; x++) {
            data[rasterIndex++] = (byte) ((1 - binaryImage.data[indexSrc++]) * 255);
          }
        }
      } else {
        for (int y = 0; y < h; y++) {
          int indexSrc = binaryImage.startIndex + y * binaryImage.stride;
          for (int x = 0; x < w; x++) {
            data[rasterIndex++] = (byte) (binaryImage.data[indexSrc++] * 255);
          }
        }
      }
    } else {
      if (invert) {
        for (int y = 0; y < h; y++) {
          int indexSrc = binaryImage.startIndex + y * binaryImage.stride;
          for (int x = 0; x < w; x++) {
            byte val = (byte) ((1 - binaryImage.data[indexSrc++]) * 255);
            for (int i = 0; i < numBands; i++) {
              data[rasterIndex++] = val;
            }
          }
        }
      } else {
        for (int y = 0; y < h; y++) {
          int indexSrc = binaryImage.startIndex + y * binaryImage.stride;
          for (int x = 0; x < w; x++) {
            byte val = (byte) (binaryImage.data[indexSrc++] * 255);
            for (int i = 0; i < numBands; i++) {
              data[rasterIndex++] = val;
            }
          }
        }
      }
    }
  }
  public static void horizontal(Kernel1D_I32 kernel, GrayU8 input, GrayI8 output) {
    final byte[] dataSrc = input.data;
    final byte[] dataDst = output.data;
    final int[] dataKer = kernel.data;

    final int kernelWidth = kernel.getWidth();
    final int offsetL = kernel.getOffset();
    final int offsetR = kernelWidth - offsetL - 1;

    final int width = input.getWidth();
    final int height = input.getHeight();

    for (int i = 0; i < height; i++) {
      int indexDest = output.startIndex + i * output.stride;
      int j = input.startIndex + i * input.stride;
      final int jStart = j;
      int jEnd = j + offsetL;

      for (; j < jEnd; j++) {
        int total = 0;
        int weight = 0;
        int indexSrc = jStart;
        for (int k = kernelWidth - (offsetR + 1 + j - jStart); k < kernelWidth; k++) {
          int w = dataKer[k];
          weight += w;
          total += (dataSrc[indexSrc++] & 0xFF) * w;
        }
        dataDst[indexDest++] = (byte) ((total + weight / 2) / weight);
      }

      j += width - (offsetL + offsetR);
      indexDest += width - (offsetL + offsetR);

      jEnd = jStart + width;
      for (; j < jEnd; j++) {
        int total = 0;
        int weight = 0;
        int indexSrc = j - offsetL;
        final int kEnd = jEnd - indexSrc;

        for (int k = 0; k < kEnd; k++) {
          int w = dataKer[k];
          weight += w;
          total += (dataSrc[indexSrc++] & 0xFF) * w;
        }
        dataDst[indexDest++] = (byte) ((total + weight / 2) / weight);
      }
    }
  }
  public void process(final BufferedImage image) {
    imageInput.reshape(image.getWidth(), image.getHeight());
    imageBinary.reshape(image.getWidth(), image.getHeight());
    imageOutput.reshape(image.getWidth(), image.getHeight());

    ConvertBufferedImage.convertFromSingle(image, imageInput, imageType);

    final double threshold = GThresholdImageOps.computeOtsu(imageInput, 0, 255);
    SwingUtilities.invokeLater(
        new Runnable() {
          public void run() {
            selectThresh.setThreshold((int) threshold);
            setInputImage(image);
            selectThresh.getHistogramPanel().update(imageInput);
            selectThresh.repaint();
          }
        });
    doRefreshAll();
  }
  private static void _renderBinary(GrayU8 binaryImage, boolean invert, BufferedImage out) {
    int w = binaryImage.getWidth();
    int h = binaryImage.getHeight();

    if (invert) {
      for (int y = 0; y < h; y++) {
        int indexSrc = binaryImage.startIndex + y * binaryImage.stride;
        for (int x = 0; x < w; x++) {
          int rgb = binaryImage.data[indexSrc++] > 0 ? 0 : 0x00FFFFFF;
          out.setRGB(x, y, rgb);
        }
      }
    } else {
      for (int y = 0; y < h; y++) {
        int indexSrc = binaryImage.startIndex + y * binaryImage.stride;
        for (int x = 0; x < w; x++) {
          int rgb = binaryImage.data[indexSrc++] > 0 ? 0x00FFFFFF : 0;
          out.setRGB(x, y, rgb);
        }
      }
    }
  }
  /**
   * Renders a binary image. 0 = black and 1 = white.
   *
   * @param binaryImage (Input) Input binary image.
   * @param invert (Input) if true it will invert the image on output
   * @param out (Output) optional storage for output image
   * @return Output rendered binary image
   */
  public static BufferedImage renderBinary(GrayU8 binaryImage, boolean invert, BufferedImage out) {

    if (out == null) {
      out =
          new BufferedImage(
              binaryImage.getWidth(), binaryImage.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
    }

    try {
      if (out.getRaster() instanceof ByteInterleavedRaster) {
        renderBinary(binaryImage, invert, (ByteInterleavedRaster) out.getRaster());
      } else if (out.getRaster() instanceof IntegerInterleavedRaster) {
        renderBinary(binaryImage, invert, (IntegerInterleavedRaster) out.getRaster());
      } else {
        _renderBinary(binaryImage, invert, out);
      }
    } catch (SecurityException e) {
      _renderBinary(binaryImage, invert, out);
    }
    // hack so that it knows the buffer has been modified
    out.setRGB(0, 0, out.getRGB(0, 0));
    return out;
  }
  private static void renderBinary(
      GrayU8 binaryImage, boolean invert, IntegerInterleavedRaster raster) {
    int rasterIndex = 0;
    int data[] = raster.getDataStorage();

    int w = binaryImage.getWidth();
    int h = binaryImage.getHeight();

    if (invert) {
      for (int y = 0; y < h; y++) {
        int indexSrc = binaryImage.startIndex + y * binaryImage.stride;
        for (int x = 0; x < w; x++) {
          data[rasterIndex++] = binaryImage.data[indexSrc++] > 0 ? 0 : 0xFFFFFFFF;
        }
      }
    } else {
      for (int y = 0; y < h; y++) {
        int indexSrc = binaryImage.startIndex + y * binaryImage.stride;
        for (int x = 0; x < w; x++) {
          data[rasterIndex++] = binaryImage.data[indexSrc++] > 0 ? 0xFFFFFFFF : 0;
        }
      }
    }
  }
  public static void convolve(Kernel2D_I32 kernel, GrayU8 input, GrayI8 output) {
    final byte[] dataSrc = input.data;
    final byte[] dataDst = output.data;
    final int[] dataKer = kernel.data;

    final int kernelWidth = kernel.getWidth();
    final int offsetL = kernel.getOffset();
    final int offsetR = kernelWidth - offsetL - 1;

    final int width = input.getWidth();
    final int height = input.getHeight();

    // convolve across the left and right borders
    for (int y = 0; y < height; y++) {

      int minI = y >= offsetL ? -offsetL : -y;
      int maxI = y < height - offsetR ? offsetR : height - y - 1;

      int indexDst = output.startIndex + y * output.stride;

      for (int x = 0; x < offsetL; x++) {

        int total = 0;
        int weight = 0;

        for (int i = minI; i <= maxI; i++) {
          int indexSrc = input.startIndex + (y + i) * input.stride + x;
          int indexKer = (i + offsetL) * kernelWidth;

          for (int j = -x; j <= offsetR; j++) {
            int w = dataKer[indexKer + j + offsetL];
            weight += w;
            total += (dataSrc[indexSrc + j] & 0xFF) * w;
          }
        }

        dataDst[indexDst++] = (byte) ((total + weight / 2) / weight);
      }

      indexDst = output.startIndex + y * output.stride + width - offsetR;
      for (int x = width - offsetR; x < width; x++) {

        int maxJ = width - x - 1;

        int total = 0;
        int weight = 0;

        for (int i = minI; i <= maxI; i++) {
          int indexSrc = input.startIndex + (y + i) * input.stride + x;
          int indexKer = (i + offsetL) * kernelWidth;

          for (int j = -offsetL; j <= maxJ; j++) {
            int w = dataKer[indexKer + j + offsetL];
            weight += w;
            total += (dataSrc[indexSrc + j] & 0xFF) * w;
          }
        }

        dataDst[indexDst++] = (byte) ((total + weight / 2) / weight);
      }
    }

    // convolve across the top border while avoiding convolving the corners again
    for (int y = 0; y < offsetL; y++) {

      int indexDst = output.startIndex + y * output.stride + offsetL;

      for (int x = offsetL; x < width - offsetR; x++) {

        int total = 0;
        int weight = 0;

        for (int i = -y; i <= offsetR; i++) {
          int indexSrc = input.startIndex + (y + i) * input.stride + x;
          int indexKer = (i + offsetL) * kernelWidth;

          for (int j = -offsetL; j <= offsetR; j++) {
            int w = dataKer[indexKer + j + offsetL];
            weight += w;
            total += (dataSrc[indexSrc + j] & 0xFF) * w;
          }
        }
        dataDst[indexDst++] = (byte) ((total + weight / 2) / weight);
      }
    }

    // convolve across the bottom border
    for (int y = height - offsetR; y < height; y++) {

      int maxI = height - y - 1;
      int indexDst = output.startIndex + y * output.stride + offsetL;

      for (int x = offsetL; x < width - offsetR; x++) {

        int total = 0;
        int weight = 0;

        for (int i = -offsetL; i <= maxI; i++) {
          int indexSrc = input.startIndex + (y + i) * input.stride + x;
          int indexKer = (i + offsetL) * kernelWidth;

          for (int j = -offsetL; j <= offsetR; j++) {
            int w = dataKer[indexKer + j + offsetL];
            weight += w;
            total += (dataSrc[indexSrc + j] & 0xFF) * w;
          }
        }
        dataDst[indexDst++] = (byte) ((total + weight / 2) / weight);
      }
    }
  }