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