Root/scripts/ppmdiff/ppmdiff.c

Source at commit 78f6cfbfda4942457e7a158a4f9fafd5e1fca067 created 10 years 23 days ago.
By Werner Almesberger, Attempt at avoiding the false changes seen on Xue. Seems that alpha-blending produces minute differences where there should be none.
1/*
2 * ppmdiff.c - Mark differences in two PPM files
3 *
4 * Written 2010 by Werner Almesberger
5 * Copyright 2010 Werner Almesberger
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 */
12
13
14#include <stdint.h>
15#include <stdlib.h>
16#include <stdio.h>
17#include <unistd.h>
18#include <string.h>
19
20
21static uint8_t a_only[3] = { 255, 0, 0 };
22static uint8_t b_only[3] = { 0, 255, 0 };
23static uint8_t both[3] = { 220, 220, 220 };
24static uint8_t frame[3] = { 0, 0, 255 };
25static uint8_t frame_fill[3] = { 255, 255, 200 };
26static int frame_dist = 40;
27static int frame_width = 2;
28
29
30static uint8_t *load_ppm(const char *name, int *x, int *y)
31{
32    FILE *file;
33    char line[100];
34    int this_x, this_y, depth;
35    int n;
36    uint8_t *img;
37
38    file = fopen(name, "r");
39    if (!file) {
40        perror(name);
41        exit(1);
42    }
43    if (!fgets(line, sizeof(line), file)) {
44        fprintf(stderr, "can't read file type\n");
45        exit(1);
46    }
47    if (strcmp(line, "P6\n")) {
48        fprintf(stderr, "file type must be P6, not %s", line);
49        exit(1);
50    }
51    if (!fgets(line, sizeof(line), file)) {
52        fprintf(stderr, "can't read resolution\n");
53        exit(1);
54    }
55    if (sscanf(line, "%d %d", &this_x, &this_y) != 2) {
56        fprintf(stderr, "can't parse resolution: %s", line);
57        exit(1);
58    }
59    if (*x || *y) {
60        if (*x != this_x || *y != this_y) {
61            fprintf(stderr,
62                "resolution changed from %dx%d to %dx%d\n",
63                *x, *y, this_x, this_y);
64            exit(1);
65        }
66    } else {
67        *x = this_x;
68        *y = this_y;
69    }
70    if (!fgets(line, sizeof(line), file)) {
71        fprintf(stderr, "can't read depth\n");
72        exit(1);
73    }
74    if (sscanf(line, "%d", &depth) != 1) {
75        fprintf(stderr, "can't parse depth: %s", line);
76        exit(1);
77    }
78    if (depth != 255) {
79        fprintf(stderr, "depth must be 255, not %d\n", depth);
80        exit(1);
81    }
82    n = *x**y*3;
83    img = malloc(n);
84    if (!img) {
85        perror("malloc");
86        exit(1);
87    }
88    if (fread(img, 1, n, file) != n) {
89        fprintf(stderr, "can't read %d bytes\n", n);
90        exit(1);
91    }
92    fclose(file);
93    return img;
94}
95
96
97static struct area {
98    int x0, y0, x1, y1;
99    struct area *next;
100} *areas = NULL;
101
102
103static void add_area(struct area **root, int x0, int y0, int x1, int y1)
104{
105    while (*root) {
106        struct area *area = *root;
107
108        if (area->x0 > x1 || area->y0 > y1 ||
109            area->x1 < x0 || area->y1 < y0) {
110            root = &(*root)->next;
111            continue;
112        }
113        x0 = x0 < area->x0 ? x0 : area->x0;
114        y0 = y0 < area->y0 ? y0 : area->y0;
115        x1 = x1 > area->x1 ? x1 : area->x1;
116        y1 = y1 > area->y1 ? y1 : area->y1;
117        *root = area->next;
118        free(area);
119        add_area(&areas, x0, y0, x1, y1);
120        return;
121    }
122    *root = malloc(sizeof(**root));
123    if (!*root) {
124        perror("malloc");
125        exit(1);
126    }
127    (*root)->x0 = x0;
128    (*root)->y0 = y0;
129    (*root)->x1 = x1;
130    (*root)->y1 = y1;
131    (*root)->next = NULL;
132}
133
134
135static void change(int x, int y)
136{
137    add_area(&areas,
138        x-frame_dist, y-frame_dist, x+frame_dist, y+frame_dist);
139}
140
141
142static void set_pixel(uint8_t *p, const uint8_t *color, const uint8_t *value)
143{
144    double f;
145    int i;
146
147    f = (255-(value[0] | value[1] | value[2]))/255.0;
148    for (i = 0; i != 3; i++)
149        p[i] = 255-(255-color[i])*f;
150}
151
152
153static uint8_t *diff(const uint8_t *a, const uint8_t *b, int xres, int yres,
154    int mark_areas)
155{
156    uint8_t *res, *p;
157    int x, y;
158    unsigned val_a, val_b;
159
160    res = p = malloc(xres*yres*3);
161    if (!res) {
162        perror("malloc");
163        exit(1);
164    }
165    for (y = 0; y != yres; y++)
166        for (x = 0; x != xres; x++) {
167            val_a = a[0]+a[1]+a[2];
168            val_b = b[0]+b[1]+b[2];
169            if (val_a == val_b) {
170                set_pixel(p, both, b);
171            } else if (val_a < val_b) {
172                set_pixel(p, a_only, a);
173                if (mark_areas)
174                    change(x, y);
175            } else if (val_a > val_b) {
176                set_pixel(p, b_only, b);
177                if (mark_areas)
178                    change(x, y);
179            } else {
180                abort(); /* no longer used */
181                memset(p, 255, 3);
182// memcpy(p, "\0\0\xff", 3);
183            }
184            a += 3;
185            b += 3;
186            p += 3;
187        }
188    return res;
189}
190
191
192static void shadow_diff(const uint8_t *a, const uint8_t *b, int xres, int yres)
193{
194    int x, y;
195
196    for (y = 0; y != yres; y++)
197        for (x = 0; x != xres; x++) {
198            if (memcmp(a, b, 3))
199                change(x, y);
200            a += 3;
201            b += 3;
202        }
203}
204
205
206static void point(uint8_t *img, int x, int y, int xres, int yres)
207{
208    uint8_t *p;
209
210    if (x < 0 || y < 0 || x >= xres || y >= yres)
211        return;
212    p = img+(y*xres+x)*3;
213    if ((p[0] & p[1] & p[2]) != 255)
214        return;
215    memcpy(p, frame, 3);
216}
217
218
219static void hline(uint8_t *img, int x0, int x1, int y, int xres, int yres)
220{
221    int x;
222
223    for (x = x0; x <= x1; x++)
224        point(img, x, y, xres, yres);
225}
226
227
228static void vline(uint8_t *img, int y0, int y1, int x, int xres, int yres)
229{
230    int y;
231
232    for (y = y0; y <= y1; y++)
233        point(img, x, y, xres, yres);
234}
235
236
237static void fill(uint8_t *img, int x0, int y0, int x1, int y1,
238   int xres, int yres)
239{
240    int x, y;
241    uint8_t *p;
242
243    for (y = y0; y <= y1; y++) {
244        if (y < 0 || y >= yres)
245            continue;
246        p = img+(xres*y+x0)*3;
247        for (x = x0; x <= x1; x++) {
248            if (x >= 0 && x < xres && (p[0] & p[1] & p[2]) == 255)
249                memcpy(p, frame_fill, 3);
250            p += 3;
251        }
252    }
253}
254
255
256static void mark_areas(uint8_t *img, int x, int y)
257{
258    const struct area *area;
259    int r1 = 0, r2 = 0, i;
260
261    if (frame_width) {
262        r1 = (frame_width-1)/2;
263        r2 = (frame_width-1)-r1;
264    }
265    for (area = areas; area; area = area->next) {
266        if (frame_width)
267            for (i = -r1; i <= r2; i++) {
268                hline(img, area->x0-r1, area->x1+r2, area->y0+i,
269                    x, y);
270                hline(img, area->x0-r1, area->x1+r2, area->y1+i,
271                    x, y);
272                vline(img, area->y0+r1, area->y1-r2, area->x0+i,
273                    x, y);
274                vline(img, area->y0+r1, area->y1-r2, area->x1+i,
275                    x, y);
276            }
277        fill(img,
278            area->x0+r1, area->y0+r1, area->x1-r2, area->y1-r2, x, y);
279    }
280}
281
282
283static void usage(const char *name)
284{
285    fprintf(stderr,
286"usage: %s [-f] [-a color] [-b color] [-c color] [-d pixels]\n"
287"%6s %*s [-m color] [-n color] [-w pixels] file_a.ppm file_b.ppm\n"
288"%6s %*s [file_a'.ppm file_b'.ppm] [out.ppm]\n\n"
289" file_a.ppm and file_b.ppm are two input images\n"
290" file_a'.ppm and file_b'.ppm if present, are searched for changes as well\n"
291" out.ppm output file (default: standard output)\n\n"
292" -f generate output (and return success) even if there is no change\n"
293" -a color color of items only in image A\n"
294" -b color color of items only in image B\n"
295" -c color color of items in both images\n"
296" -d pixels distance between change and marker box. 0 disables markers.\n"
297" -m color color of the frame of the marker box.\n"
298" -n color color of the background of the marker box\n"
299" -w pixels width of the frame of the marker box. 0 disables frames.\n\n"
300" color is specified as R,B,G with each component as a floating-point\n"
301" value from 0 to 1. E.g., 1,1,1 is white.\n\n"
302" The images are expected to have dark colors on a perfectly white\n"
303" background.\n"
304        , name, "", (int) strlen(name), "", "", (int) strlen(name), "");
305    exit(1);
306}
307
308
309static void parse_color(uint8_t *c, const char *s, const char *name)
310{
311    float r, g, b;
312
313    if (sscanf(s, "%f,%f,%f", &r, &g, &b) != 3)
314        usage(name);
315    c[0] = 255*r;
316    c[1] = 255*g;
317    c[2] = 255*b;
318}
319
320
321int main(int argc, char *const *argv)
322{
323    int force = 0;
324    int x = 0, y = 0;
325    uint8_t *old, *new, *d, *a, *b;
326    char *shadow_old = NULL, *shadow_new = NULL, *out_name = NULL;
327    FILE *out;
328    char *end;
329    int c;
330
331    while ((c = getopt(argc, argv, "a:b:c:d:fm:n:w:")) != EOF)
332        switch (c) {
333        case 'a':
334            parse_color(a_only, optarg, *argv);
335            break;
336        case 'b':
337            parse_color(b_only, optarg, *argv);
338            break;
339        case 'c':
340            parse_color(both, optarg, *argv);
341            break;
342        case 'd':
343            frame_dist = strtoul(optarg, &end, 0);
344            if (*end)
345                usage(*argv);
346            break;
347        case 'f':
348            force = 1;
349            break;
350        case 'm':
351            parse_color(frame, optarg, *argv);
352            break;
353        case 'n':
354            parse_color(frame_fill, optarg, *argv);
355            break;
356        case 'w':
357            frame_width = strtoul(optarg, &end, 0);
358            if (*end)
359                usage(*argv);
360            break;
361        default:
362            usage(*argv);
363        }
364    switch (argc-optind) {
365    case 2:
366        break;
367    case 3:
368        out_name = argv[optind+2];
369        break;
370    case 5:
371        out_name = argv[optind+4];
372        /* fall through */
373    case 4:
374        shadow_old = argv[optind+2];
375        shadow_new = argv[optind+3];
376        break;
377    default:
378        usage(*argv);
379    }
380
381    old = load_ppm(argv[optind], &x, &y);
382    new = load_ppm(argv[optind+1], &x, &y);
383    if (shadow_old) {
384        a = load_ppm(shadow_old, &x, &y);
385        b = load_ppm(shadow_new, &x, &y);
386        if (!force && !memcmp(a, b, x*y*3))
387            return 1;
388        shadow_diff(a, b, x, y);
389    }
390    if (!force && !areas && !memcmp(old, new, x*y*3))
391        return 1;
392    d = diff(old, new, x, y, !shadow_old);
393    if (frame_dist)
394        mark_areas(d, x, y);
395
396    if (out_name) {
397        out = fopen(out_name, "w");
398        if (!out) {
399            perror(out_name);
400            exit(1);
401        }
402    } else {
403        out = stdout;
404    }
405    fprintf(out, "P6\n%d %d\n255\n", x, y);
406    fwrite(d, 1, x*y*3, out);
407    if (fclose(out) == EOF) {
408        perror("fclose");
409        exit(1);
410    }
411    return 0;
412}
413

Archive Download this file



interactive