// 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; }