scripts/ppmdiff/ppmdiff.c |
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 | | |
21 | | static uint8_t a_only[3] = { 255, 0, 0 }; |
22 | | static uint8_t b_only[3] = { 0, 255, 0 }; |
23 | | static uint8_t both[3] = { 220, 220, 220 }; |
24 | | static uint8_t frame[3] = { 0, 0, 255 }; |
25 | | static uint8_t frame_fill[3] = { 255, 255, 200 }; |
26 | | static int frame_dist = 40; |
27 | | static int frame_width = 2; |
28 | | |
29 | | |
30 | | static 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 | | |
97 | | static struct area { |
98 | | int x0, y0, x1, y1; |
99 | | struct area *next; |
100 | | } *areas = NULL; |
101 | | |
102 | | |
103 | | static 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 | | |
135 | | static 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 | | |
142 | | static 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 | | |
153 | | static 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 | | |
192 | | static 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 | | |
206 | | static 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 | | |
219 | | static 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 | | |
228 | | static 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 | | |
237 | | static 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 | | |
256 | | static 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 | | |
283 | | static 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 | | |
309 | | static 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 | | |
321 | | int 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 | | } |
scripts/schhist2web |
1 | | #!/bin/bash |
2 | | # |
3 | | # schhist2web - Web-browseable graphical revision history of schematics |
4 | | # |
5 | | # Written 2010 by Werner Almesberger |
6 | | # Copyright 2010 Werner Almesberger |
7 | | # |
8 | | # This program is free software; you can redistribute it and/or modify |
9 | | # it under the terms of the GNU General Public License as published by |
10 | | # the Free Software Foundation; either version 2 of the License, or |
11 | | # (at your option) any later version. |
12 | | # |
13 | | |
14 | | |
15 | | OUTDIR=_out |
16 | | THUMB_OPTS="-w 3 -d 60 -c 0.5,0.5,0.5 -n 1,1,0" |
17 | | BG_COLOR="f0f0ff" |
18 | | FNAME_COLOR="#b0f0ff" |
19 | | SEP_COLOR="#000000" |
20 | | |
21 | | |
22 | | shrink() |
23 | | { |
24 | | pnmscale -width 120 "$@" || exit |
25 | | } |
26 | | |
27 | | |
28 | | pngdiff() |
29 | | { |
30 | | # pngdiff preproc outfile arg ... |
31 | | pp="$1" |
32 | | of="$2" |
33 | | shift 2 |
34 | | if ! PATH=$PATH:`dirname $0`/ppmdiff ppmdiff "$@" "$out/_tmp"; then |
35 | | rm -f "$out/_tmp" |
36 | | return 1 |
37 | | fi |
38 | | $pp "$out/_tmp" | pnmtopng >"$of" |
39 | | rm "$out/_tmp" |
40 | | } |
41 | | |
42 | | |
43 | | symlink() |
44 | | { |
45 | | local old=$1 new=$2 |
46 | | local src=`dirname "$new"`/$old |
47 | | |
48 | | if [ -L "$src" ]; then |
49 | | ln -sf "`readlink \"$src\"`" "$new" |
50 | | else |
51 | | ln -sf "$old" "$new" |
52 | | fi |
53 | | } |
54 | | |
55 | | |
56 | | commit_entry() |
57 | | { |
58 | | # usage: commit_entry <base-dir> <commit> |
59 | | # note: the repository's base in $dir must be provided by the caller |
60 | | |
61 | | local dir=$1 next=$2 |
62 | | |
63 | | cat <<EOF |
64 | | <TABLE bgcolor="$SEP_COLOR" cellspacing=0 width="100%"><TR><TD></TABLE> |
65 | | EOF |
66 | | echo "<PRE>" |
67 | | ( cd "$dir" && git show \ |
68 | | --pretty=format:"%aN <%aE>%n %ad, %ar%n%n %s" \ |
69 | | --quiet $next; ) | |
70 | | sed 's/&/&/g;s/</\</g;s/>/\>/g' | |
71 | | if [ -z "$SCHHIST_COMMIT_TEMPLATE" ]; then |
72 | | cat |
73 | | else |
74 | | url=`echo "$SCHHIST_COMMIT_TEMPLATE" | sed "s/{}/$next/g"` |
75 | | sed "1s|^|<A href=\"$url\"><B>\>\>\></B></a> |" |
76 | | fi |
77 | | echo "</PRE>" |
78 | | } |
79 | | |
80 | | |
81 | | wrapped_png() |
82 | | { |
83 | | local dir=$1 commit=$2 file=$3 |
84 | | |
85 | | mkdir -p "$dir/$commit/html" |
86 | | echo "<A href=\"$commit/html/$file.html\"><IMG src=\"$commit/thumb/$file.png\"></A>" |
87 | | cat <<EOF >"$dir/$commit/html/$file.html" |
88 | | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> |
89 | | <HTML> |
90 | | <TITLE>$file</TITLE> |
91 | | <BODY> |
92 | | <A href="../pdf/$file.pdf"><IMG src="../diff/$file.png"></A> |
93 | | </BODY> |
94 | | EOF |
95 | | } |
96 | | |
97 | | |
98 | | usage() |
99 | | { |
100 | | cat <<EOF 2>&1 |
101 | | usage: $0 [-c cache-dir] [-n] [-S] [top-dir] [top-schem] [out-dir] |
102 | | |
103 | | top-dir top-level directory of the git archive (default: locate it) |
104 | | top-schem root sheet of the schematics (default: locate it in top-dir) |
105 | | out-dir output directory (default: $OUTDIR) |
106 | | -c cache-dir cache directory (default: same as out-dir) |
107 | | -n don't use previous cache content (rebuild the cache) |
108 | | -S sanitize KiCad profile |
109 | | EOF |
110 | | exit 1 |
111 | | } |
112 | | |
113 | | |
114 | | # --- Parse command-line options ---------------------------------------------- |
115 | | |
116 | | |
117 | | no_cache=false |
118 | | sanitize= |
119 | | |
120 | | while true; do |
121 | | case "$1" in |
122 | | -n) no_cache=true |
123 | | shift;; |
124 | | -c) [ -z "$1" ] && usage |
125 | | cache="$1" |
126 | | shift 2;; |
127 | | -S) sanitize=-S |
128 | | shift;; |
129 | | -*) usage;; |
130 | | *) break;; |
131 | | esac |
132 | | done |
133 | | |
134 | | |
135 | | # --- Interpret the command-line arguments ------------------------------------ |
136 | | |
137 | | |
138 | | if [ ! -z "$1" -a -d "$1/.git" ]; then |
139 | | dir="$1" |
140 | | shift |
141 | | else |
142 | | dir=. |
143 | | while [ ! -d $dir/.git ]; do |
144 | | if [ $dir -ef $dir/.. ]; then |
145 | | echo "no .git/ directory found in hierarchy" 1>&2 |
146 | | exit 1 |
147 | | fi |
148 | | dir=$dir/.. |
149 | | done |
150 | | echo "found top-dir: $dir" 1>&2 |
151 | | fi |
152 | | |
153 | | if [ ! -z "$1" -a -f "$dir/$1" -a \ |
154 | | -f "$dir/${1%.sch}.pro" ]; then |
155 | | sch="$1" |
156 | | shift |
157 | | else |
158 | | for n in "$dir"/*.sch; do |
159 | | [ -f "${n%.sch}.pro" ] || continue |
160 | | if [ ! -z "$sch" ]; then |
161 | | echo "multiple choices for top-level .sch file" 1>&2 |
162 | | exit 1 |
163 | | fi |
164 | | sch="$n" |
165 | | done |
166 | | if [ -z "$sch" -o "$sch" = "$dir/*.sch" ]; then |
167 | | echo "no candidate for top-level .sch file found" 1>&2 |
168 | | exit 1 |
169 | | fi |
170 | | echo "found root sheet: $sch" 1>&2 |
171 | | fi |
172 | | |
173 | | if [ ! -z "$1" ] && [ ! -e "$1" ] || [ -d "$1" -a ! -d "$1"/.git ]; then |
174 | | out="$1" |
175 | | shift |
176 | | else |
177 | | out=$OUTDIR |
178 | | fi |
179 | | [ -z "$cache" ] && cache="$out" |
180 | | |
181 | | [ -z "$1" ] || usage |
182 | | |
183 | | |
184 | | # --- Set up some variables and the directories for cache and output ---------- |
185 | | |
186 | | |
187 | | PATH=`dirname "$0"`:"$PATH" |
188 | | first=`gitenealogy "$dir" "$sch" | sed '$s/ .*//p;d'` |
189 | | schname=`gitenealogy "$dir" "$sch" | sed '$s/^.* //p;d'` |
190 | | |
191 | | rm -rf "$out/*/"{diff,thumb,html,pdf} "$out/names" |
192 | | $no_cache && rm -rf "$cache" |
193 | | mkdir -p "$out/names" |
194 | | mkdir -p "$cache" |
195 | | |
196 | | ppmmake '#e0e0e0' 5 30 | pnmtopng >"$out"/unchanged.png |
197 | | |
198 | | |
199 | | # --- Generate/update the cache ----------------------------------------------- |
200 | | |
201 | | |
202 | | head= |
203 | | for n in $first `cd "$dir" && git rev-list --reverse $first..HEAD`; do |
204 | | ( cd "$dir" && git show --pretty=format:'' --name-only $n; ) | |
205 | | egrep -q '\.sch$|\.pro$|\.lib$' || continue |
206 | | echo Processing $n |
207 | | new=`gitenealogy "$dir" "$sch" | sed "/^$n /s///p;d"` |
208 | | if [ ! -z "$new" ]; then |
209 | | echo Name change $schname to $new 1>&2 |
210 | | schname="$new" |
211 | | fi |
212 | | tmp=`pwd`/_schhist2web |
213 | | trap "rm -rf \"$cache/$n/ps\" \"$cache/$n/ppm0\" \"$cache/$n/ppm1\" \ |
214 | | \"$cache/$n/ppm2\" \"$tmp\"" 0 |
215 | | if [ ! -d "$cache/$n/ppm2" ]; then |
216 | | rm -rf "$cache/$n/"{ps,ppm0,ppm1,ppm2} |
217 | | mkdir -p "$cache/$n/"{ps,ppm0,ppm1,ppm2} |
218 | | # |
219 | | # potential optimization here: remember Postscript files from previous |
220 | | # run (or their md5sum) and check if they have changed. If not, skip |
221 | | # the ghostscript run and just put a symlink, replacing the less |
222 | | # efficient optimization below. |
223 | | # |
224 | | gitsch2ps $sanitize "$dir" "$schname" $n "$tmp" || exit |
225 | | for m in "$tmp"/*.ps; do |
226 | | # Postscript, for making PDFs later |
227 | | ps="$cache/$n/ps/`basename "$m"`" |
228 | | normalizeschps "$m" "$ps" || exit |
229 | | |
230 | | # Unadorned pixmap, for comparison |
231 | | ppm="$cache/$n/ppm0/`basename "$m" .ps`.ppm" |
232 | | schps2ppm -n "$ps" "$ppm" || exit |
233 | | |
234 | | # Pixmap with thin lines, for the detail views |
235 | | ppm="$cache/$n/ppm1/`basename "$m" .ps`.ppm" |
236 | | normalizeschps -w 120 "$m" | schps2ppm - "$ppm" || exit |
237 | | |
238 | | # Pixmap with thick lines, for the thumbnails |
239 | | ppm="$cache/$n/ppm2/`basename "$m" .ps`.ppm" |
240 | | normalizeschps -w 500 "$m" | schps2ppm - "$ppm" || exit |
241 | | done |
242 | | rm -rf "$tmp" |
243 | | fi |
244 | | for m in "$cache/$n/ppm0/"*; do |
245 | | [ "$m" = "$cache/$n/ppm0/*" ] && break |
246 | | if [ ! -z "$head" ]; then |
247 | | prev="$cache/$head/ppm0/${m##*/}" |
248 | | if [ -r "$prev" ] && cmp -s "$prev" "$m"; then |
249 | | for d in ppm0 ppm1 ppm2; do |
250 | | symlink "../../$head/$d/${m##*/}" "$cache/$n/$d/${m##*/}" |
251 | | done |
252 | | m_ps=${m%.ppm}.ps |
253 | | symlink "../../$head/ps/${m_ps##*/}" "$cache/$n/ps/${m_ps##*/}" |
254 | | fi |
255 | | fi |
256 | | touch "$out/names/`basename \"$m\" .ppm`" |
257 | | done |
258 | | trap 0 |
259 | | head=$n |
260 | | done |
261 | | |
262 | | if [ -z "$head" ]; then |
263 | | echo "no usable head found" 2>&1 |
264 | | exit 1 |
265 | | fi |
266 | | |
267 | | |
268 | | # --- Title of the Web page and table header ---------------------------------- |
269 | | |
270 | | |
271 | | index="$out/index.html" |
272 | | all= |
273 | | { |
274 | | cat <<EOF |
275 | | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> |
276 | | <HTML> |
277 | | EOF |
278 | | if [ ! -z "$SCHHIST_TITLE" ]; then |
279 | | echo "<TITLE>$SCHHIST_TITLE</TITLE>" |
280 | | fi |
281 | | echo "<BODY>" |
282 | | if [ ! -z "$SCHHIST_TITLE" ]; then |
283 | | echo "<H1>" |
284 | | [ -z "$SCHHIST_HOME_URL" ] || echo "<A href=\"$SCHHIST_HOME_URL\">" |
285 | | echo "$SCHHIST_TITLE" |
286 | | [ -z "$SCHHIST_HOME_URL" ] || echo "</A>" |
287 | | echo "</H1>" |
288 | | fi |
289 | | cat <<EOF |
290 | | <TABLE bgcolor="$BG_COLOR" callpadding=1> |
291 | | <TR bgcolor="$FNAME_COLOR"> |
292 | | EOF |
293 | | while read m; do |
294 | | ps="$cache/$head/ps/$m.ps" |
295 | | if [ -r "$ps" ]; then |
296 | | # |
297 | | # Note: we read from variable ps_$head but we write to constant |
298 | | # pdf_head. We can't use pdf_$head here, because that may just be a |
299 | | # commit with a change and we thus generate a delta PDF below. |
300 | | # |
301 | | mkdir -p "$out/pdf_head" |
302 | | schps2pdf -o "$out/pdf_head/$m.pdf" "$ps" || exit |
303 | | all="$all \"$ps\"" |
304 | | echo "<TD><A href=\"pdf_head/$m.pdf\"><B>$m</B></A>" |
305 | | else |
306 | | echo "<TD><B>$m</B>" |
307 | | fi |
308 | | done < <(ls -1 "$out/names") |
309 | | proj=`basename "$sch" .sch` |
310 | | eval schps2pdf -t \""$proj-"\" -o \""$out/pdf_$proj.pdf"\" $all |
311 | | echo "<TD><A href=\"pdf_$proj.pdf\">All sheets</A>" |
312 | | } >"$index" |
313 | | |
314 | | |
315 | | # --- Diff all the revisions, newest to oldest -------------------------------- |
316 | | |
317 | | |
318 | | next="$head" |
319 | | for n in `cd "$dir" && git rev-list $first..HEAD~1` $first; do |
320 | | [ -d "$cache/$n/ppm0" ] || continue |
321 | | empty=true |
322 | | s="<TR>" |
323 | | mkdir -p "$out/$next/"{diff,thumb,html,pdf} |
324 | | while read m; do |
325 | | a0="$cache/$n/ppm0/$m.ppm" |
326 | | a1="$cache/$n/ppm1/$m.ppm" |
327 | | a2="$cache/$n/ppm2/$m.ppm" |
328 | | aps="$cache/$n/ps/$m.ps" |
329 | | |
330 | | b0="$cache/$next/ppm0/$m.ppm" |
331 | | b1="$cache/$next/ppm1/$m.ppm" |
332 | | b2="$cache/$next/ppm2/$m.ppm" |
333 | | bps="$cache/$next/ps/$m.ps" |
334 | | |
335 | | diff="$out/$next/diff/$m.png" |
336 | | thumb="$out/$next/thumb/$m.png" |
337 | | pdf="$out/$next/pdf/$m.pdf" |
338 | | |
339 | | if [ -f "$a0" -a -f "$b0" ]; then |
340 | | s="$s<TD align=\"center\" valign=\"middle\">" |
341 | | if ! pngdiff cat "$diff" "$a1" "$b1" "$a0" "$b0"; then |
342 | | s="$s<IMG src=\"unchanged.png\"" |
343 | | continue |
344 | | fi |
345 | | pngdiff shrink "$thumb" -f $THUMB_OPTS "$a2" "$b2" "$a0" "$b0" || |
346 | | exit |
347 | | schps2pdf -T BEFORE -T AFTER -o "$pdf" "$aps" "$bps" || exit |
348 | | elif [ -f "$a0" ]; then |
349 | | s="$s<TD>" |
350 | | pngdiff cat "$diff" -f -c 1,0,0 "$a1" "$a1" || exit |
351 | | pngdiff shrink "$thumb" -f $THUMB_OPTS -c 1,0,0 "$a2" "$a2" || |
352 | | exit |
353 | | schps2pdf -T DELETED -o "$pdf" "$aps" || exit |
354 | | elif [ -f "$b0" ]; then |
355 | | s="$s<TD>" |
356 | | pngdiff cat "$diff" -f -c 0,1,0 "$b1" "$b1" || exit |
357 | | pngdiff shrink "$thumb" -f $THUMB_OPTS -c 0,1,0 "$b2" "$b2" || |
358 | | exit |
359 | | schps2pdf -T NEW -o "$pdf" "$bps" || exit |
360 | | else |
361 | | s="$s<TD>" |
362 | | continue |
363 | | fi |
364 | | echo "$s" |
365 | | s= |
366 | | wrapped_png "$out" "$next" "$m" |
367 | | empty=false |
368 | | done < <(ls -1 "$out/names") |
369 | | if ! $empty; then |
370 | | echo "$s<TD valign=\"middle\">" |
371 | | commit_entry "$dir" $next |
372 | | fi |
373 | | next=$n |
374 | | done >>"$index" |
375 | | |
376 | | |
377 | | # --- Add creation entries for all files in the first commit ------------------ |
378 | | |
379 | | |
380 | | if [ -d "$cache/$next/ppm0" ]; then # could this ever be false ? |
381 | | empty=true |
382 | | echo "<TR>" |
383 | | mkdir -p "$out/$next/"{diff,thumb,html,pdf} |
384 | | while read m; do |
385 | | p1="$cache/$next/ppm1/$m.ppm" |
386 | | p2="$cache/$next/ppm2/$m.ppm" |
387 | | ps="$cache/$next/ps/$m.ps" |
388 | | diff="$out/$next/diff/$m.png" |
389 | | thumb="$out/$next/thumb/$m.png" |
390 | | pdf="$out/$next/pdf/$m.pdf" |
391 | | |
392 | | echo "<TD>" |
393 | | [ -f "$p1" ] || continue |
394 | | pngdiff cat "$diff" -f -c 0,1,0 "$p1" "$p1" || exit |
395 | | pngdiff shrink "$thumb" -f $THUMB_OPTS -c 0,1,0 "$p2" "$p2" || |
396 | | exit |
397 | | schps2pdf -T NEW -o "$pdf" "$ps" || exit |
398 | | wrapped_png "$out" "$next" "$m" |
399 | | empty=false |
400 | | done < <(ls -1 "$out/names") |
401 | | if ! $empty; then |
402 | | echo "<TD valign=\"middle\">" |
403 | | commit_entry "$dir" $next |
404 | | fi |
405 | | fi >>"$index" |
406 | | |
407 | | |
408 | | # --- Finish ------------------------------------------------------------------ |
409 | | |
410 | | |
411 | | cat <<EOF >>"$index" |
412 | | </TABLE> |
413 | | <HR> |
414 | | `date -u '+%F %X'` UTC |
415 | | </BODY> |
416 | | </HTML> |
417 | | EOF |