Home | History | Annotate | Download | only in fuzzymatch
      1 // Copyright (c) 2008 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 /* Fuzzy pixel test matching.
      6  *
      7  * This is designed to compare two layout test images (RGB 800x600) and manage
      8  * to ignore the noise caused by the font renderers choosing slightly different
      9  * pixels.
     10  *
     11  *    A             B
     12  *    |             |
     13  *    |--->delta<---|
     14  *           |
     15  *           V
     16  *          gray
     17  *           |
     18  *           V
     19  *         binary
     20  *           |
     21  *    |-------------|
     22  *    |             |
     23  *   1x3           3x1   Morphological openings
     24  *    |             |
     25  *    |-----OR------|
     26  *           |
     27  *           V
     28  *     count pixels
     29  *
     30  * The result is that three different pixels in a row (vertically or
     31  * horizontally) will cause the match to fail and the binary exits with code 1.
     32  * Otherwise the binary exists with code 0.
     33  *
     34  * This requires leptonica to be installed. On Ubuntu do
     35  *   # apt-get install libleptonica libleptonica-dev libtiff4-dev
     36  *
     37  * Build with:
     38  *   % gcc -o fuzzymatch fuzzymatch.c -llept -ljpeg -ltiff -lpng -lz -lm -Wall -O2
     39  *
     40  * Options:
     41  *   --highlight: write highlight.png which is a copy of the first image
     42  *     argument where the differing areas (after noise removal) are ringed
     43  *     in red
     44  *   --no-ignore-scrollbars: usually the rightmost 15px of the image are
     45  *     ignored to account for scrollbars. Use this flag to include them in
     46  *     consideration
     47  */
     48 
     49 #include <unistd.h>
     50 #include <stdio.h>
     51 #include <leptonica/allheaders.h>
     52 
     53 static int
     54 usage(const char *argv0) {
     55   fprintf(stderr, "Usage: %s [--highlight] [--no-ignore-scrollbars] "
     56                   "[--output filename] "
     57                   "<input a> <input b>\n", argv0);
     58   return 1;
     59 }
     60 
     61 int
     62 main(int argc, char **argv) {
     63   if (argc < 3)
     64     return usage(argv[0]);
     65 
     66   char highlight = 0;
     67   char ignore_scrollbars = 1;
     68   /* Default output filename; can be overridden by command line. */
     69   const char *output_filename = "highlight.png";
     70 
     71   int argi = 1;
     72 
     73   for (; argi < argc; ++argi) {
     74     if (strcmp("--highlight", argv[argi]) == 0) {
     75       highlight = 1;
     76     } else if (strcmp("--no-ignore-scrollbars", argv[argi]) == 0) {
     77       ignore_scrollbars = 0;
     78     } else if (strcmp("--output", argv[argi]) == 0) {
     79       if (argi + 1 >= argc) {
     80         fprintf(stderr, "missing argument to --output\n");
     81         return 1;
     82       }
     83       output_filename = argv[++argi];
     84     } else {
     85       break;
     86     }
     87   }
     88 
     89   if (argc - argi < 2)
     90     return usage(argv[0]);
     91 
     92   PIX *a = pixRead(argv[argi]);
     93   PIX *b = pixRead(argv[argi + 1]);
     94 
     95   if (!a) {
     96     fprintf(stderr, "Failed to open %s\n", argv[argi]);
     97     return 1;
     98   }
     99 
    100   if (!b) {
    101     fprintf(stderr, "Failed to open %s\n", argv[argi + 1]);
    102     return 1;
    103   }
    104 
    105   if (pixGetWidth(a) != pixGetWidth(b) ||
    106       pixGetHeight(a) != pixGetHeight(b)) {
    107     fprintf(stderr, "Inputs are difference sizes\n");
    108     return 1;
    109   }
    110 
    111   PIX *delta = pixAbsDifference(a, b);
    112   pixInvert(delta, delta);
    113   if (!highlight)
    114     pixDestroy(&a);
    115   pixDestroy(&b);
    116 
    117   PIX *deltagray = pixConvertRGBToGray(delta, 0, 0, 0);
    118   pixDestroy(&delta);
    119 
    120   PIX *deltabinary = pixThresholdToBinary(deltagray, 254);
    121   PIX *deltabinaryclipped;
    122   const int clipwidth = pixGetWidth(deltabinary) - 15;
    123   const int clipheight = pixGetHeight(deltabinary) - 15;
    124 
    125   if (ignore_scrollbars && clipwidth > 0 && clipheight > 0) {
    126     BOX *clip = boxCreate(0, 0, clipwidth, clipheight);
    127 
    128     deltabinaryclipped = pixClipRectangle(deltabinary, clip, NULL);
    129     boxDestroy(&clip);
    130     pixDestroy(&deltabinary);
    131   } else {
    132     deltabinaryclipped = deltabinary;
    133     deltabinary = NULL;
    134   }
    135 
    136   PIX *hopened = pixOpenBrick(NULL, deltabinaryclipped, 3, 1);
    137   PIX *vopened = pixOpenBrick(NULL, deltabinaryclipped, 1, 3);
    138   pixDestroy(&deltabinaryclipped);
    139 
    140   PIX *opened = pixOr(NULL, hopened, vopened);
    141   pixDestroy(&hopened);
    142   pixDestroy(&vopened);
    143 
    144   l_int32 count;
    145   pixCountPixels(opened, &count, NULL);
    146   fprintf(stderr, "%d\n", count);
    147 
    148   if (count && highlight) {
    149     PIX *d1 = pixDilateBrick(NULL, opened, 7, 7);
    150     PIX *d2 = pixDilateBrick(NULL, opened, 3, 3);
    151     pixInvert(d2, d2);
    152     pixAnd(d1, d1, d2);
    153     pixPaintThroughMask(a, d1, 0, 0, 0xff << 24);
    154     pixWrite(output_filename, a, IFF_PNG);
    155   }
    156 
    157   return count > 0;
    158 }
    159