Date:2010-08-27 08:54:26 (10 years 1 month ago)
Author:Werner Almesberger
Commit:c10d643c1fd6ddfbd1eba60ee91fc4435462711b
Message:Tools for making a browseable graphical revision history of schematics.

- scripts/gitsch2ppm: extract schematics as PPM files from a specfific git
revision
- scripts/gitenealogy: show the commit history of a file, tracking renames
- scripts/ppmdiff/Makefile, scripts/ppmdiff/ppmdiff.c: compare two PPM
files and highlight differences
- scripts/schhist2web: generate a browseable graphical revision history
of schematics
Files: scripts/gitenealogy (1 diff)
scripts/gitsch2ppm (1 diff)
scripts/ppmdiff/Makefile (1 diff)
scripts/ppmdiff/ppmdiff.c (1 diff)
scripts/schhist2web (1 diff)

Change Details

scripts/gitenealogy
1#!/bin/sh
2#
3# gitenealogy - Trace the ancestry of a file in git across renames
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
15usage()
16{
17    echo "usage: $0 path" 2>&1
18    exit 1
19}
20
21
22[ -z "$1" -o ! -z "$2" ] && usage
23[ ! -f "$1" ] && usage
24
25git log --follow --name-status "$1" |
26    awk '
27/^commit /{ if (c) print c, n; c = $2 }
28{ if (NF) n = $(NF) }
29END { if (c) print c, n; }'
scripts/gitsch2ppm
1#!/bin/sh
2#
3# gitsch2ppm - Generate PPM files for KiCad schematics in git
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
15RES=1280x850
16LINEWIDTH=120
17
18
19ps2ppm()
20{
21    X=`echo $RES | sed 's/x.*//'`
22    Y=`echo $RES | sed 's/.*x//'`
23    IRES=${Y}x$X
24    res=`expr 72 \* $X / 800`
25
26    ( cat <<EOF
27%!PS-Adobe-3.0
28/setlinewidth { $LINEWIDTH 2 copy lt { exch } if pop setlinewidth } bind def
29EOF
30    sed 1d <"$1"; ) |
31    gs -sDEVICE=ppmraw -sOutputFile=- -g$IRES -r$res \
32      -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q - |
33      pnmflip -r270 |
34      cat >`dirname "$1"`/`basename "$1" .ps`.ppm
35}
36
37
38usage()
39{
40    cat <<EOF 1>&2
41usage: $0 [options] top-dir top-schem [commit] outdir
42
43  -r XxY image resolution (default: $RES)
44  -w points Postscript line width (default: $LINEWIDTH)
45EOF
46    exit 1
47}
48
49
50while true; do
51    case "$1" in
52    -r) [ -z "$2" ] && usage
53    RES="$2"
54    shift 2
55    break;;
56    -w) [ -z "$2" ] && usage
57    LINEWIDTH="$2"
58    shift 2
59    break;;
60    -*)
61    usage;;
62    *)
63    break;;
64    esac
65done
66
67[ ! -z "$3" -a -z "$5" ] || usage
68dir="$1"
69schem="$2"
70sdir=`dirname "$schem"`
71if [ -z "$4" ]; then
72    commit=HEAD
73    outdir="$3"
74else
75    commit="$3"
76    outdir="$4"
77fi
78
79[ "$dir" != "${dir#/}" ] || dir=`pwd`/$dir
80
81[ "$commit" != HEAD -o -f "$dir/$schem" ] || usage
82[ -d "$dir/.git" ] || usage
83
84tmp="$dir/../_schdiff_a"
85sch="$tmp/$sdir"
86
87rm -rf "$tmp"
88
89git clone -s -n "$dir/.git" "$tmp" || exit
90( cd "$tmp" && git checkout -q "$commit"; ) || exit
91
92if [ ! -f "$tmp/$schem" ]; then
93    echo "$schem not found (checked out into $tmp)" 1>&2
94    exit 1
95fi
96
97( cd "$sch" && rm -f *.ps *.ppm && eeschema --plot "$tmp/$schem"; ) || exit
98
99for n in "$sch"/*.ps; do
100    ps2ppm "$n"
101done
102
103rm -rf "$outdir"
104mkdir -p "$outdir"
105
106mv "$sch"/*.ppm "$outdir"
107
108rm -rf "$tmp"
scripts/ppmdiff/Makefile
1CFLAGS=-Wall -g
2
3ppmdiff:
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
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{
155    uint8_t *res, *p;
156    int x, y;
157    int has_a, has_b;
158
159    res = p = malloc(xres*yres*3);
160    if (!res) {
161        perror("malloc");
162        exit(1);
163    }
164    for (y = 0; y != yres; y++)
165        for (x = 0; x != xres; x++) {
166            has_a = (a[0] & a[1] & a[2]) != 255;
167            has_b = (b[0] & b[1] & b[2]) != 255;
168            if (has_a && has_b) {
169                set_pixel(p, both, b);
170            } else if (has_a) {
171                set_pixel(p, a_only, a);
172                change(x, y);
173            } else if (has_b) {
174                set_pixel(p, b_only, b);
175                change(x, y);
176            } else {
177                memset(p, 255, 3);
178// memcpy(p, "\0\0\xff", 3);
179            }
180            a += 3;
181            b += 3;
182            p += 3;
183        }
184    return res;
185}
186
187
188static void point(uint8_t *img, int x, int y, int xres, int yres)
189{
190    uint8_t *p;
191
192    if (x < 0 || y < 0 || x >= xres || y >= yres)
193        return;
194    p = img+(y*xres+x)*3;
195    if ((p[0] & p[1] & p[2]) != 255)
196        return;
197    memcpy(p, frame, 3);
198}
199
200
201static void hline(uint8_t *img, int x0, int x1, int y, int xres, int yres)
202{
203    int x;
204
205    for (x = x0; x <= x1; x++)
206        point(img, x, y, xres, yres);
207}
208
209
210static void vline(uint8_t *img, int y0, int y1, int x, int xres, int yres)
211{
212    int y;
213
214    for (y = y0; y <= y1; y++)
215        point(img, x, y, xres, yres);
216}
217
218
219static void fill(uint8_t *img, int x0, int y0, int x1, int y1,
220   int xres, int yres)
221{
222    int x, y;
223    uint8_t *p;
224
225    for (y = y0; y <= y1; y++) {
226        if (y < 0 || y >= yres)
227            continue;
228        p = img+(xres*y+x0)*3;
229        for (x = x0; x <= x1; x++) {
230            if (x >= 0 && x < xres && (p[0] & p[1] & p[2]) == 255)
231                memcpy(p, frame_fill, 3);
232            p += 3;
233        }
234    }
235}
236
237
238static void mark_areas(uint8_t *img, int x, int y)
239{
240    const struct area *area;
241    int r1 = 0, r2 = 0, i;
242
243    if (frame_width) {
244        r1 = (frame_width-1)/2;
245        r2 = (frame_width-1)-r1;
246    }
247    for (area = areas; area; area = area->next) {
248        if (frame_width)
249            for (i = -r1; i <= r2; i++) {
250                hline(img, area->x0-r1, area->x1+r2, area->y0+i,
251                    x, y);
252                hline(img, area->x0-r1, area->x1+r2, area->y1+i,
253                    x, y);
254                vline(img, area->y0+r1, area->y1-r2, area->x0+i,
255                    x, y);
256                vline(img, area->y0+r1, area->y1-r2, area->x1+i,
257                    x, y);
258            }
259        fill(img,
260            area->x0+r1, area->y0+r1, area->x1-r2, area->y1-r2, x, y);
261    }
262}
263
264
265static void usage(const char *name)
266{
267    fprintf(stderr,
268"usage: %s [-f] [-a color] [-b color] [-c color] [-d pixels]\n"
269"%6s %*s [-m color] [-n color] [-w pixels] file_a.ppm file_b.ppm\n\n"
270" -f generate output (and return success) even if there is no change\n"
271" -a color color of items only in image A\n"
272" -b color color of items only in image B\n"
273" -c color color of items in both images\n"
274" -d pixels distance between change and marker box. 0 disables markers.\n"
275" -m color color of the frame of the marker box.\n"
276" -n color color of the background of the marker box\n"
277" -w pixels width of the frame of the marker box. 0 disables frames.\n\n"
278" color is specified as R,B,G with each component as a floating-point\n"
279" value from 0 to 1. E.g., 1,1,1 is white.\n\n"
280" The images are expected to have dark colors on a perfectly white\n"
281" background.\n"
282        , name, "", (int) strlen(name), "");
283    exit(1);
284}
285
286
287static void parse_color(uint8_t *c, const char *s, const char *name)
288{
289    float r, g, b;
290
291    if (sscanf(s, "%f,%f,%f", &r, &g, &b) != 3)
292        usage(name);
293    c[0] = 255*r;
294    c[1] = 255*g;
295    c[2] = 255*b;
296}
297
298
299int main(int argc, char *const *argv)
300{
301    int force = 0;
302    int x = 0, y = 0;
303    uint8_t *old, *new, *d;
304    char *end;
305    int c;
306
307    while ((c = getopt(argc, argv, "a:b:c:d:fm:n:w:")) != EOF)
308        switch (c) {
309        case 'a':
310            parse_color(a_only, optarg, *argv);
311            break;
312        case 'b':
313            parse_color(b_only, optarg, *argv);
314            break;
315        case 'c':
316            parse_color(both, optarg, *argv);
317            break;
318        case 'd':
319            frame_dist = strtoul(optarg, &end, 0);
320            if (*end)
321                usage(*argv);
322            break;
323        case 'f':
324            force = 1;
325            break;
326        case 'm':
327            parse_color(frame, optarg, *argv);
328            break;
329        case 'n':
330            parse_color(frame_fill, optarg, *argv);
331            break;
332        case 'w':
333            frame_width = strtoul(optarg, &end, 0);
334            if (*end)
335                usage(*argv);
336            break;
337        default:
338            usage(*argv);
339        }
340    if (argc-optind != 2)
341        usage(*argv);
342    old = load_ppm(argv[optind], &x, &y);
343    new = load_ppm(argv[optind+1], &x, &y);
344    d = diff(old, new, x, y);
345    if (frame_dist)
346        mark_areas(d, x, y);
347    if (!areas && !force)
348        return 1;
349    printf("P6\n%d %d\n255\n", x, y);
350    fwrite(d, 1, x*y*3, stdout);
351    if (fclose(stdout) == EOF) {
352        perror("fclose");
353        exit(1);
354    }
355    return 0;
356}
scripts/schhist2web
1#!/bin/sh
2
3THUMB_OPTS="-w 3 -d 60 -n 1,1,0"
4
5
6shrink()
7{
8    pnmscale -width 120 "$@" || exit
9}
10
11
12pngdiff()
13{
14    # pngdiff preproc outfile arg ...
15    pp="$1"
16    of="$2"
17    shift 2
18    if ! PATH=$PATH:`dirname $0`/ppmdiff ppmdiff "$@" >"$out/_tmp"; then
19    rm "$out/_tmp"
20    return 1
21    fi
22    $pp "$out/_tmp" | pnmtopng >"$of"
23    rm -f "$out/_tmp"
24}
25
26
27usage()
28{
29    echo "usage: $0 [top-dir] [top-schem] [outdir]" 2>&1
30    exit 1
31}
32
33
34if [ ! -z "$1" -a -d "$1/.git" ]; then
35    dir="$1"
36    shift
37else
38    dir=.
39    while [ ! -d $dir/.git ]; do
40    if [ $dir -ef $dir/.. ]; then
41        echo "no .git/ directory found in hierarchy" 1>&2
42        exit 1
43    fi
44    dir=$dir/..
45    done
46fi
47
48if [ ! -z "$1" -a -f "$dir/$1" -a \
49  -f "$dir"/`dirname "$1"`/`basename "$1" .sch`.pro ]; then
50    sch="$1"
51    shift
52else
53    for n in "$dir"/*.sch; do
54    [ -f `dirname "$n"`/`basename "$n" .sch`.pro ] || continue
55    if [ ! -z "$sch" ]; then
56        echo "multiple choices for top-level .sch file" 1>&2
57        exit 1
58    fi
59    sch="$n"
60    done
61    if [ -z "$sch" -o "$sch" = "$dir/*.sch" ]; then
62    echo "no candidate for top-level .sch file found" 1>&2
63    exit 1
64    fi
65fi
66
67if [ ! -z "$1" ] && [ ! -e "$1" ] || [ -d "$1" -a ! -d "$1"/.git ]; then
68    out="$1"
69    shift
70else
71    out=_out
72fi
73
74[ -z "$1" ] || usage
75
76PATH=`dirname "$0"`:"$PATH"
77first=`gitenealogy "$dir/$sch" | sed '$s/ .*//p;d'`
78schname=`gitenealogy "$dir/$sch" | sed '$s/^.* //p;d'`
79
80# @@@ POOR MAN'S CACHE
81if true; then
82
83rm -rf "$out"
84mkdir -p "$out/names"
85
86for n in $first `git rev-list --reverse $first..HEAD`; do
87echo Processing $n
88    new=`gitenealogy "$dir/$sch" | sed "/^$n /s///p;d"`
89    if [ ! -z "$new" ]; then
90echo Name change $schname to $new
91    schname="$new"
92    fi
93    mkdir "$out/ppm_$n"
94    gitsch2ppm "$dir" "$schname" $n "$out/ppm_$n" || exit
95    gitsch2ppm -w 500 "$dir" "$schname" $n "$out/fat_$n" || exit
96    for m in "$out/ppm_$n/"*; do
97    [ "$m" = "$out/ppm_$n/*" ] && break
98    touch "$out/names/"`basename "$m" .ppm`
99    done
100done
101
102fi
103
104cat <<EOF >"$out/index.html"
105<HTML>
106<BODY>
107<TABLE bgcolor="#f0f0ff" callpadding=1>
108<TR>
109EOF
110for m in `ls -1 "$out/names"`; do
111    echo "<TD><B>$m</B>" >>"$out/index.html"
112done
113
114head=`git rev-list HEAD~1..HEAD`
115next="$head"
116for n in `git rev-list $first..HEAD~1` $first; do
117    empty=true
118    s="<TR><TR>"
119    mkdir -p "$out/diff_$next" "$out/thumb_$next"
120    for m in `ls -1 "$out/names"`; do
121    a="$out/ppm_$n/$m.ppm"
122    fat_a="$out/fat_$n/$m.ppm"
123    b="$out/ppm_$next/$m.ppm"
124    fat_b="$out/fat_$next/$m.ppm"
125    diff="$out/diff_$next/$m.png"
126    thumb="$out/thumb_$next/$m.png"
127
128    if [ -f "$a" -a -f "$b" ]; then
129        s="$s<TD>"
130        pngdiff cat "$diff" "$a" "$b" || continue
131        pngdiff shrink "$thumb" -f $THUMB_OPTS "$fat_a" "$fat_b" || exit
132    elif [ -f "$a" ]; then
133        s="$s<TD>"
134        pngdiff cat "$diff" -f -c 1,0,0 "$a" || exit
135        pngdiff shrink "$thumb" -f -c 1,0,0 $THUMB_OPTS "$fat_a" || exit
136    elif [ -f "$out/$next/$m.ppm" ]; then
137        s="$s<TD>"
138        pngdiff cat "$diff" -f -c 0,1,0 "$b" || exit
139        pngdiff shrink "$thumb" -f -c 0,1,0 $THUMB_OPTS "$fat_b" || exit
140    else
141        continue
142    fi
143    echo "$s" >>"$out/index.html"
144    s=
145    empty=false
146    echo "<A href=\"diff_$next/$m.png\"><IMG src=\"thumb_$next/$m.png\"></A>" >>"$out/index.html"
147    done
148    if ! $empty; then
149    (
150        cat <<EOF
151$s<TD valign="middle">
152<TABLE bgcolor="#000000" cellspacing=0 width="100%"><TR><TD></TABLE>
153EOF
154    mkdir -p "$out/diff_$next" "$out/thumb_$next"
155        echo "<PRE>"
156        git log --pretty=short $n..$next
157        echo "</PRE>"
158    ) >>"$out/index.html"
159    fi
160    next=$n
161done
162
163echo "</TABLE>" >>"$out/index.html"
164exit 1

Archive Download the corresponding diff file



interactive