// Copyright (c) 2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/* Fuzzy pixel test matching.
 *
 * This is designed to compare two layout test images (RGB 800x600) and manage
 * to ignore the noise caused by the font renderers choosing slightly different
 * pixels.
 *
 *    A             B
 *    |             |
 *    |--->delta<---|
 *           |
 *           V
 *          gray
 *           |
 *           V
 *         binary
 *           |
 *    |-------------|
 *    |             |
 *   1x3           3x1   Morphological openings
 *    |             |
 *    |-----OR------|
 *           |
 *           V
 *     count pixels
 *
 * The result is that three different pixels in a row (vertically or
 * horizontally) will cause the match to fail and the binary exits with code 1.
 * Otherwise the binary exists with code 0.
 *
 * This requires leptonica to be installed. On Ubuntu do
 *   # apt-get install libleptonica libleptonica-dev libtiff4-dev
 *
 * Build with:
 *   % gcc -o fuzzymatch fuzzymatch.c -llept -ljpeg -ltiff -lpng -lz -lm -Wall -O2
 *
 * Options:
 *   --highlight: write highlight.png which is a copy of the first image
 *     argument where the differing areas (after noise removal) are ringed
 *     in red
 *   --no-ignore-scrollbars: usually the rightmost 15px of the image are
 *     ignored to account for scrollbars. Use this flag to include them in
 *     consideration
 */

#include <unistd.h>
#include <stdio.h>
#include <leptonica/allheaders.h>

static int
usage(const char *argv0) {
  fprintf(stderr, "Usage: %s [--highlight] [--no-ignore-scrollbars] "
                  "[--output filename] "
                  "<input a> <input b>\n", argv0);
  return 1;
}

int
main(int argc, char **argv) {
  if (argc < 3)
    return usage(argv[0]);

  char highlight = 0;
  char ignore_scrollbars = 1;
  /* Default output filename; can be overridden by command line. */
  const char *output_filename = "highlight.png";

  int argi = 1;

  for (; argi < argc; ++argi) {
    if (strcmp("--highlight", argv[argi]) == 0) {
      highlight = 1;
    } else if (strcmp("--no-ignore-scrollbars", argv[argi]) == 0) {
      ignore_scrollbars = 0;
    } else if (strcmp("--output", argv[argi]) == 0) {
      if (argi + 1 >= argc) {
        fprintf(stderr, "missing argument to --output\n");
        return 1;
      }
      output_filename = argv[++argi];
    } else {
      break;
    }
  }

  if (argc - argi < 2)
    return usage(argv[0]);

  PIX *a = pixRead(argv[argi]);
  PIX *b = pixRead(argv[argi + 1]);

  if (!a) {
    fprintf(stderr, "Failed to open %s\n", argv[argi]);
    return 1;
  }

  if (!b) {
    fprintf(stderr, "Failed to open %s\n", argv[argi + 1]);
    return 1;
  }

  if (pixGetWidth(a) != pixGetWidth(b) ||
      pixGetHeight(a) != pixGetHeight(b)) {
    fprintf(stderr, "Inputs are difference sizes\n");
    return 1;
  }

  PIX *delta = pixAbsDifference(a, b);
  pixInvert(delta, delta);
  if (!highlight)
    pixDestroy(&a);
  pixDestroy(&b);

  PIX *deltagray = pixConvertRGBToGray(delta, 0, 0, 0);
  pixDestroy(&delta);

  PIX *deltabinary = pixThresholdToBinary(deltagray, 254);
  PIX *deltabinaryclipped;
  const int clipwidth = pixGetWidth(deltabinary) - 15;
  const int clipheight = pixGetHeight(deltabinary) - 15;

  if (ignore_scrollbars && clipwidth > 0 && clipheight > 0) {
    BOX *clip = boxCreate(0, 0, clipwidth, clipheight);

    deltabinaryclipped = pixClipRectangle(deltabinary, clip, NULL);
    boxDestroy(&clip);
    pixDestroy(&deltabinary);
  } else {
    deltabinaryclipped = deltabinary;
    deltabinary = NULL;
  }

  PIX *hopened = pixOpenBrick(NULL, deltabinaryclipped, 3, 1);
  PIX *vopened = pixOpenBrick(NULL, deltabinaryclipped, 1, 3);
  pixDestroy(&deltabinaryclipped);

  PIX *opened = pixOr(NULL, hopened, vopened);
  pixDestroy(&hopened);
  pixDestroy(&vopened);

  l_int32 count;
  pixCountPixels(opened, &count, NULL);
  fprintf(stderr, "%d\n", count);

  if (count && highlight) {
    PIX *d1 = pixDilateBrick(NULL, opened, 7, 7);
    PIX *d2 = pixDilateBrick(NULL, opened, 3, 3);
    pixInvert(d2, d2);
    pixAnd(d1, d1, d2);
    pixPaintThroughMask(a, d1, 0, 0, 0xff << 24);
    pixWrite(output_filename, a, IFF_PNG);
  }

  return count > 0;
}