15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2008 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/* Fuzzy pixel test matching.
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * This is designed to compare two layout test images (RGB 800x600) and manage
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * to ignore the noise caused by the font renderers choosing slightly different
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * pixels.
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *    A             B
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *    |             |
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *    |--->delta<---|
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *           |
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *           V
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *          gray
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *           |
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *           V
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *         binary
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *           |
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *    |-------------|
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *    |             |
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *   1x3           3x1   Morphological openings
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *    |             |
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *    |-----OR------|
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *           |
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *           V
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     count pixels
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * The result is that three different pixels in a row (vertically or
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * horizontally) will cause the match to fail and the binary exits with code 1.
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Otherwise the binary exists with code 0.
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * This requires leptonica to be installed. On Ubuntu do
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *   # apt-get install libleptonica libleptonica-dev libtiff4-dev
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Build with:
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *   % gcc -o fuzzymatch fuzzymatch.c -llept -ljpeg -ltiff -lpng -lz -lm -Wall -O2
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Options:
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *   --highlight: write highlight.png which is a copy of the first image
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     argument where the differing areas (after noise removal) are ringed
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     in red
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *   --no-ignore-scrollbars: usually the rightmost 15px of the image are
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     ignored to account for scrollbars. Use this flag to include them in
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     consideration
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include <unistd.h>
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include <stdio.h>
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include <leptonica/allheaders.h>
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)static int
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)usage(const char *argv0) {
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fprintf(stderr, "Usage: %s [--highlight] [--no-ignore-scrollbars] "
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                  "[--output filename] "
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                  "<input a> <input b>\n", argv0);
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return 1;
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)int
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)main(int argc, char **argv) {
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (argc < 3)
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return usage(argv[0]);
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  char highlight = 0;
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  char ignore_scrollbars = 1;
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  /* Default output filename; can be overridden by command line. */
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  const char *output_filename = "highlight.png";
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  int argi = 1;
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for (; argi < argc; ++argi) {
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (strcmp("--highlight", argv[argi]) == 0) {
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      highlight = 1;
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else if (strcmp("--no-ignore-scrollbars", argv[argi]) == 0) {
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      ignore_scrollbars = 0;
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else if (strcmp("--output", argv[argi]) == 0) {
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (argi + 1 >= argc) {
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        fprintf(stderr, "missing argument to --output\n");
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return 1;
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      output_filename = argv[++argi];
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else {
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      break;
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (argc - argi < 2)
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return usage(argv[0]);
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  PIX *a = pixRead(argv[argi]);
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  PIX *b = pixRead(argv[argi + 1]);
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!a) {
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fprintf(stderr, "Failed to open %s\n", argv[argi]);
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return 1;
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!b) {
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fprintf(stderr, "Failed to open %s\n", argv[argi + 1]);
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return 1;
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (pixGetWidth(a) != pixGetWidth(b) ||
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      pixGetHeight(a) != pixGetHeight(b)) {
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fprintf(stderr, "Inputs are difference sizes\n");
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return 1;
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  PIX *delta = pixAbsDifference(a, b);
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  pixInvert(delta, delta);
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!highlight)
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    pixDestroy(&a);
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  pixDestroy(&b);
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  PIX *deltagray = pixConvertRGBToGray(delta, 0, 0, 0);
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  pixDestroy(&delta);
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  PIX *deltabinary = pixThresholdToBinary(deltagray, 254);
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  PIX *deltabinaryclipped;
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  const int clipwidth = pixGetWidth(deltabinary) - 15;
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  const int clipheight = pixGetHeight(deltabinary) - 15;
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (ignore_scrollbars && clipwidth > 0 && clipheight > 0) {
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    BOX *clip = boxCreate(0, 0, clipwidth, clipheight);
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    deltabinaryclipped = pixClipRectangle(deltabinary, clip, NULL);
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    boxDestroy(&clip);
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    pixDestroy(&deltabinary);
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else {
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    deltabinaryclipped = deltabinary;
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    deltabinary = NULL;
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  PIX *hopened = pixOpenBrick(NULL, deltabinaryclipped, 3, 1);
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  PIX *vopened = pixOpenBrick(NULL, deltabinaryclipped, 1, 3);
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  pixDestroy(&deltabinaryclipped);
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  PIX *opened = pixOr(NULL, hopened, vopened);
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  pixDestroy(&hopened);
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  pixDestroy(&vopened);
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  l_int32 count;
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  pixCountPixels(opened, &count, NULL);
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fprintf(stderr, "%d\n", count);
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (count && highlight) {
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    PIX *d1 = pixDilateBrick(NULL, opened, 7, 7);
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    PIX *d2 = pixDilateBrick(NULL, opened, 3, 3);
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    pixInvert(d2, d2);
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    pixAnd(d1, d1, d2);
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    pixPaintThroughMask(a, d1, 0, 0, 0xff << 24);
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    pixWrite(output_filename, a, IFF_PNG);
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return count > 0;
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
159