chiark / gitweb /
Support Hunter
[ypp-sc-tools.db-live.git] / yarrg / structure.c
1 /*
2  * Parsing of the structure of the YPP client's displayed image
3  */
4 /*
5  *  This is part of ypp-sc-tools, a set of third-party tools for assisting
6  *  players of Yohoho Puzzle Pirates.
7  * 
8  *  Copyright (C) 2009 Ian Jackson <ijackson@chiark.greenend.org.uk>
9  * 
10  *  This program is free software: you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License as published by
12  *  the Free Software Foundation, either version 3 of the License, or
13  *  (at your option) any later version.
14  * 
15  *  This program is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU General Public License for more details.
19  * 
20  *  You should have received a copy of the GNU General Public License
21  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
22  * 
23  *  Yohoho and Puzzle Pirates are probably trademarks of Three Rings and
24  *  are used without permission.  This program is not endorsed or
25  *  sponsored by Three Rings.
26  */
27
28 #include "structure.h"
29
30 DEBUG_DEFINE_DEBUGF(struct)
31
32 #define START_MAIN {200,200}
33 #define MIN_COLUMNS         6
34 #define INTERESTING_COLUMNS 7
35 #define TEXT_COLUMNS        2
36 #define MAX_COLUMNS         7
37
38 struct PageStruct {
39   Rect mr;
40   int commbasey, comminty, pagerheight;
41   int colrightx[INTERESTING_COLUMNS];
42 };
43
44 const CanonImage *page_images[MAX_PAGES];
45 static PageStruct page_structs[MAX_PAGES];
46 const RgbImage *page0_rgbimage;
47 int npages;
48
49 static int text_h=-1, columns=-1;
50
51 static OcrReader *rd;
52
53 static const CanonImage *cim;
54 static PageStruct s;
55
56 char *archipelago, *island;
57
58 #define OTHERCOORD_x y
59 #define OTHERCOORD_y x
60
61
62 void select_page(int page) {
63   cim= page_images[page];
64   s= page_structs[page];
65   assert(cim);
66 }
67
68
69 typedef struct {
70   Rgb rgbx; /* on screen, REVERSED BYTES ie r||g||b */
71   char c; /* canonical */
72 } CanonColourInfo;
73
74 const CanonColourInfo canoncolourinfo_table[]= {
75   { 0x475A5E, '*' }, /* edge */
76   { 0x2C5F7A, '*' }, /* edge just under box heading shadow */
77   { 0xC5C7AE, '*' }, /* blank area of partial commodities list */
78   { 0x6B828C, '*' }, /* background of ship status meter area */
79   { 0x934405, '*' }, /* border of ship meter area */
80   { 0x7D9094, '+' }, /* interbox */
81   { 0x022158, 'O' }, /* ahoy /w output foreground */
82   { 0xB5B686, 'H' }, /* ahoy /w output heading background */
83
84   { 0xBDC5BF, ' ' }, /* background - pale  Sugar cane, etc. */
85   { 0xADB5AF, ' ' }, /* background - dark                   */
86   { 0xC7E1C3, ' ' }, /* background - pale  Swill, etc.      */
87   { 0xB5CFB1, ' ' }, /* background - dark                   */
88   { 0xD6CEB0, ' ' }, /* background - pale  Madder, etc.     */
89   { 0xC8C0A2, ' ' }, /* background - dark                   */
90   { 0xE0E1D3, ' ' }, /* background - pale  Lorandite, etc.  */
91   { 0xD0D1C3, ' ' }, /* background - dark                   */
92   { 0xE5E6C1, ' ' }, /* background - pale  Cloth            */
93   { 0xD7D8B3, ' ' }, /* background - dark                   */
94   { 0xEDDED9, ' ' }, /* background - pale  Dye              */
95   { 0xDACBC6, ' ' }, /* background - dark                   */
96   { 0xD3DEDF, ' ' }, /* background - pale  Paint            */
97   { 0xC5D0D1, ' ' }, /* background - dark                   */
98   { 0xDCD1CF, ' ' }, /* background - pale  Enamel           */
99   { 0xCEC3C1, ' ' }, /* background - dark                   */
100   { 0xF3F6F5, ' ' }, /* background - pale  fruit            */
101   { 0xE2E7E5, ' ' }, /* background - dark                   */
102
103   { 0x000000, 'o' }, /* foreground */
104   { 0xD4B356, ' ' }, /* background (cursor) */
105   { 0xFFFFFF, 'o' }, /* foreground (cursor) */
106
107   { 0x5B93BF, '_' }, /* selector dropdown background */
108   { 0xD7C94F, 'X' }, /* selector dropdown foreground */
109   { 0,0 }
110 };
111
112 CanonColourInfoReds canoncolourinfo_tree;
113
114 void canon_colour_prepare(void) {
115   const CanonColourInfo *cci;
116   for (cci=canoncolourinfo_table; cci->c; cci++) {
117     unsigned char r= cci->rgbx >> 16;
118     unsigned char g= cci->rgbx >>  8;
119     unsigned char b= cci->rgbx;
120
121     CanonColourInfoGreens *greens= canoncolourinfo_tree.red2[r];
122     if (!greens) {
123       greens= canoncolourinfo_tree.red2[r]= mmalloc(sizeof(*greens));
124       FILLZERO(*greens);
125     }
126
127     CanonColourInfoBlues *blues= greens->green2[g];
128     if (!blues) {
129       blues= greens->green2[g]= mmalloc(sizeof(*blues));
130       memset(blues, '?', sizeof(*blues));
131     }
132
133     blues->blue2[b]= cci->c;
134   }
135 }
136
137 static inline char get(int x, int y) { return cim->d[y * cim->w + x]; }
138 static inline char get_p(Point p) { return get(p.x,p.y); }
139
140
141 static void mustfail1(const char *file, int line, const char *what) {
142   fprintf(stderr,
143  "\n\n"
144  "Unable to figure out contents of YPP client display.\n"
145  "Please check the following:\n"
146  "   * YPP client is showing commodity listing screen\n"
147  "   * YPP client window is on top (we try to raise it but your window\n"
148  "      manager might have prevented that from succeeding)\n"
149  "\n"
150  "If all of these are true, please report this as a fault.\n\n"
151           "Technical details:"
152           " %s:%d: requirement failed:\n"
153           " %s\n",
154           file, line, what);
155 }
156 static void mustfail2(void) NORET;
157 static void mustfail2(void) {
158   fprintf(stderr, "\n\nGiving up.\n");
159   exit(8);
160 }
161
162 #define MUST(x, ifnot) do{                      \
163     if (__builtin_expect(!(x), 0)) {            \
164       mustfail1(__FILE__,__LINE__,#x);          \
165       ifnot;                                    \
166       mustfail2();                              \
167     }                                           \
168   }while(0)
169
170 #define MP(v) fprintf(stderr," %s=%d,%d",#v,(v).x,(v).y)
171 #define MI(v) fprintf(stderr," %s=%d",   #v,(v))
172 #define MIL(v) fprintf(stderr," %s=%ld", #v,(v))
173 #define MRGB(v) fprintf(stderr," %s=%06"PRIx32, #v,(v))
174 #define MC(v) fprintf(stderr," %s='%c'", #v,(v))
175 #define MS(v) fprintf(stderr," %s=\"%s\"", #v,(v))
176 #define MF(v) fprintf(stderr," %s=%f", #v,(v))
177 #define MSB(v) fprintf(stderr," %s", (v))
178 #define MR(v) fprintf(stderr," %s=%d,%d..%d,%d",\
179                       #v,(v).tl.x,(v).tl.y,(v).br.x,(v).br.y)
180
181
182 #define REQUIRE_RECTANGLE(tlx,tly,brx,bry,ok) \
183  require_rectangle(tlx, tly, brx, bry, ok, __LINE__);
184
185 #define FOR_P_RECT(p,rr)                                \
186   for ((p).x=(rr).tl.x; (p).x<=(rr).br.x; (p).x++)      \
187     for ((p).y=(rr).tl.y; (p).y<=(rr).br.y; (p).y++)
188
189 static void require_rectangle_r(Rect rr, const char *ok, int lineno) {
190   Point p;
191   FOR_P_RECT(p,rr) {
192     int c= get_p(p);
193     MUST( strchr(ok,c), ({
194       MI(lineno),MR(rr);MP(p);MS(ok);
195     }));
196   }
197 }
198 static void require_rectangle(int tlx, int tly, int brx, int bry,
199                               const char *ok, int lineno) {
200   Rect rr= {{tlx,tly},{brx,bry}};
201   require_rectangle_r(rr, ok, lineno);
202 }
203
204 static void debug_rect(const char *what, int whati, Rect rr) {
205   if (!DEBUGP(rect)) return;
206   int y,w;
207   fprintf(debug, "%s %d: %d,%d..%d,%d:\n", what, whati,
208           rr.tl.x,rr.tl.y, rr.br.x,rr.br.y);
209   w= rr.br.x - rr.tl.x + 1;
210   for (y=rr.tl.y; y<=rr.br.y; y++) {
211     fprintf(debug, "%4d%*s|", y, rr.tl.x,"");
212     fwrite(cim->d + y*cim->w + rr.tl.x, 1, w, debug);
213     fputc('|',debug);
214     fputc('\n',debug);
215   }
216   debug_flush();
217 }
218
219 static int commod_selector_matches(Rect search, const char *const *all,
220                                    int allh, int allw) {
221   int alloffy, alloffx;
222   for (alloffy=0; alloffy < search.br.y; alloffy++) {
223     if (alloffy+allh-1 < search.tl.y) continue;
224     for (alloffx=search.tl.x; alloffx+allw-1 <= search.br.x; alloffx++) {
225       int good=0, bad=0;
226       int x,y;
227       for (x=0; x<allw; x++)
228         for (y=0; y<allh; y++) {
229           int want= all[y][x];
230           if (want==' ') continue;
231           if (get(alloffx+x, alloffy+y) == want)
232             good++;
233           else
234             bad++;
235         }
236       debugf("CHECKCOMMOD alloff=%d,%d good=%d bad=%d\n",
237              alloffx,alloffy, good,bad);
238       if (good > 20*bad)
239         return 1;
240     }
241   }
242   return 0;
243 }
244
245 #define WALK_UNTIL(point,coord,increm,last,edge)                        \
246   for (;;) {                                                            \
247     if ((point).coord == (last)+(increm)) break;                        \
248     if (get_p((point)) == (edge)) { (point).coord -= (increm); break; } \
249     (point).coord += (increm);                                          \
250   }
251
252 #define WALK_UNTIL_MUST(point,coord,increm,last,edge)   \
253   do {                                                  \
254     WALK_UNTIL(point,coord,increm,last,edge);           \
255     MUST( (point).coord != (last)+(increm),             \
256           MP(point); MI(increm); MI(last); MC(edge);    \
257           );                                            \
258   }while(0)
259
260 #define ADJUST_BOX(search,insidechrs,OP,want, lim,LIMIT_MUST, TLBR,XY,increm) \
261   for (;;) {                                                                  \
262     LIMIT_MUST( (search).tl.XY != (search).br.XY &&                           \
263                 (search).TLBR.XY != (lim),                                    \
264                 MR((search));MSB(#TLBR);MSB(#XY) );                           \
265     int got=0;                                                                \
266     Point p=(search).tl;                                                      \
267     for (p.XY=(search).TLBR.XY;                                               \
268          p.OTHERCOORD_##XY <= (search).br.OTHERCOORD_##XY;                    \
269          p.OTHERCOORD_##XY++)                                                 \
270       got += !!strchr(insidechrs, get_p(p));                                  \
271     if ((got) OP (want))                                                      \
272       break;                                                                  \
273     (search).TLBR.XY += increm;                                               \
274   }
275
276 void find_structure(const CanonImage *im,
277                     PageStruct **pagestruct_r,
278                     int *max_relevant_y_r,
279                     Point *commod_focus_point_r,
280                     Point *commod_page_point_r,
281                     Point *commod_focuslast_point_r) {
282   cim= im;
283
284   FILLZERO(s);
285   Rect whole = { {0,0}, {cim->w-1,cim->h-1} };
286
287   if (DEBUGP(rect)) {
288     int xscaleunit, y,x;
289     for (y=0, xscaleunit=1; y<4; y++, xscaleunit*=10) {
290       fprintf(debug,"     ");
291       for (x=0; x<=cim->w; x++) {
292         if (x % xscaleunit) fputc(' ',debug);
293         else fprintf(debug,"%d",(x / xscaleunit)%10);
294       }
295       fputc('\n',debug);
296     }
297   }
298
299   Point mainr_tl= START_MAIN;
300   s.mr.tl= mainr_tl;
301   WALK_UNTIL_MUST(s.mr.tl, y,-1, whole.tl.y, ' ');
302   s.mr.br= s.mr.tl;
303
304   WALK_UNTIL_MUST(s.mr.tl, x,-1, whole.tl.x, '*');
305   WALK_UNTIL_MUST(s.mr.tl, y,-1, whole.tl.y, '*');
306   WALK_UNTIL_MUST(s.mr.br, x,+1, whole.br.x, '*');
307   WALK_UNTIL_MUST(s.mr.br, y,+1, whole.br.y, '*');
308
309   REQUIRE_RECTANGLE(s.mr.tl.x-1, s.mr.tl.y, s.mr.tl.x-1, s.mr.br.y, "*");
310   REQUIRE_RECTANGLE(s.mr.br.x+1, s.mr.tl.y, s.mr.br.x+1, s.mr.br.y, "*");
311   REQUIRE_RECTANGLE(s.mr.tl.x, s.mr.tl.y-1, s.mr.br.x, s.mr.tl.y-1, "*");
312   REQUIRE_RECTANGLE(s.mr.tl.x, s.mr.br.y+1, s.mr.br.x, s.mr.br.y+1, "*");
313
314 #define CHECK_STRIP_BORDER(tlbr,xy,increm)              \
315   do {                                                  \
316     Point csb_p, csb_p2;                                \
317     Rect csb_r;                                         \
318     csb_p= s.mr.tl;                                     \
319     csb_p.x++; csb_p.y++;                               \
320     csb_p2= csb_p;                                      \
321     csb_p2.x++; csb_p2.y++;                             \
322     csb_p.xy= s.mr.tlbr.xy;                             \
323     csb_p2.xy= s.mr.tlbr.xy;                            \
324     if (get_p(csb_p)=='+' &&                            \
325         get_p(csb_p2)=='+') {                           \
326       csb_r= s.mr;                                      \
327       csb_r.tl.xy= csb_p.xy;                            \
328       csb_r.br.xy= csb_p.xy;                            \
329       require_rectangle_r(csb_r, "+", __LINE__);        \
330       s.mr.tlbr.xy += increm;                           \
331     }                                                   \
332   } while(0)
333
334   debug_rect("s.mr",0, s.mr);
335
336   CHECK_STRIP_BORDER(tl,x,+1);
337   CHECK_STRIP_BORDER(tl,y,+1);
338   CHECK_STRIP_BORDER(br,x,-1);
339   CHECK_STRIP_BORDER(br,y,-1);
340
341   debug_rect("s.mr",1, s.mr);
342
343   Rect updown= {START_MAIN,START_MAIN};
344   const int chkw= 100;
345   updown.br.x += chkw-1;
346   updown.br.y++;
347   debug_rect("updown",__LINE__,updown);
348
349   ADJUST_BOX(updown, "+", >=,chkw, s.mr.tl.y,   MUST, tl,y,-1);
350   debug_rect("updown",__LINE__,updown);
351   updown.br.y= updown.tl.y;
352   updown.tl.y= updown.tl.y-1;
353
354   ADJUST_BOX(updown, "+*",>=,chkw, s.mr.tl.y-1, MUST, tl,y,-1);
355   debug_rect("updown",__LINE__,updown);
356
357   s.commbasey= updown.tl.y + 1;
358   s.comminty= updown.br.y - updown.tl.y;
359
360   Rect across= {{ s.mr.tl.x - 1, s.commbasey                 },
361                 { s.mr.tl.x,     s.commbasey + s.comminty-2 }};
362   int colno=0;
363   for (;;) {
364
365 #define LIMIT_QUITEQ(cond,mp) { if (!(cond)) break; }
366     debug_rect("across",colno*1000000+__LINE__, across);
367     ADJUST_BOX(across, "+",>=,s.comminty-1,s.mr.br.x,LIMIT_QUITEQ,br,x,+1);
368     debug_rect("across",colno*1000000+__LINE__, across);
369
370     MUST( colno < MAX_COLUMNS,
371           MI(colno);MR(across);MR(s.mr);MI(s.commbasey); );
372     int colrx= across.br.x-1;
373     if (colrx >= s.mr.br.x) colrx= s.mr.br.x;
374     if (colno < INTERESTING_COLUMNS)
375       s.colrightx[colno]= colrx;
376       
377     colno++;
378     
379     if (across.br.x >= s.mr.br.x)
380       break;
381
382     REQUIRE_RECTANGLE(across.br.x,s.mr.tl.y, across.br.x,s.mr.br.y, "+");
383     across.br.x++;
384   }
385   MUST( colno >= MIN_COLUMNS, MI(colno);MR(s.mr);MR(across); );
386
387   const int pagerh= 6;
388   Rect pager= {{ s.mr.br.x,     s.mr.br.y - (pagerh-1) },
389                { s.mr.br.x + 1, s.mr.br.y              }};
390
391   debug_rect("pager",__LINE__,pager);
392   ADJUST_BOX(pager, "o",>=,pagerh-2, whole.br.x,MUST, br,x,+1);
393   debug_rect("pager",__LINE__,pager);
394
395   pager.tl.x= pager.br.x;
396   pager.br.x= pager.br.x + 1;
397   debug_rect("pager",__LINE__,pager);
398   ADJUST_BOX(pager, "o",>=,pagerh-2, whole.br.x,MUST, br,x,+1);
399   debug_rect("pager",__LINE__,pager);
400
401   ADJUST_BOX(pager, "o",>=,RECT_W(pager)-2, s.mr.tl.y,LIMIT_QUITEQ, tl,y,-1);
402   debug_rect("pager",__LINE__,pager);
403
404   pager.br.y= pager.tl.y;
405   pager.tl.y= pager.tl.y-1;
406   if (pager.tl.y > s.mr.tl.y)
407     ADJUST_BOX(pager, "+",>,1, s.mr.tl.y,LIMIT_QUITEQ, tl,y,-1);
408   debug_rect("pager",__LINE__,pager);
409   s.pagerheight= pager.br.y - pager.tl.y;
410
411 #define SET_ONCE(var,val) do{                                           \
412     int v= (val);                                                       \
413     if ((var)==-1) (var)= v;                                            \
414     else MUST( (var) == v, MSB(#var);MI((var));MI(v);MR(s.mr); );       \
415   }while(0)
416
417   SET_ONCE(columns, colno);
418   SET_ONCE(text_h, s.comminty - 1);
419
420   if (pagestruct_r) {
421     *pagestruct_r= mmalloc(sizeof(s));
422     **pagestruct_r= s;
423   }
424   
425   if (max_relevant_y_r)
426     SET_ONCE(*max_relevant_y_r, s.mr.br.y + 10);
427
428   if (commod_focus_point_r) {
429     *commod_focus_point_r= s.mr.tl;
430     commod_focus_point_r->x += 10;
431     commod_focus_point_r->y += s.comminty/3;
432   }
433   if (commod_focuslast_point_r) {
434     *commod_focuslast_point_r= s.mr.br;
435     commod_focuslast_point_r->x -= 10;
436     commod_focuslast_point_r->y -= s.comminty/3;
437   }
438   if (commod_page_point_r) {
439     commod_page_point_r->x= (pager.tl.x + pager.br.x) / 2;
440     commod_page_point_r->y=  pager.br.y - 1;
441   }
442
443   MUST( text_h <= OCR_MAX_H, MI(text_h) );
444 }                   
445
446 void check_correct_commodities(void) {
447   Rect search= { { 50,39 }, { 130,59 } };
448
449   ADJUST_BOX(search,"_",>=,10, cim->h, MUST, tl,y,+1);
450   ADJUST_BOX(search,"_",>=,10, 0,      MUST, br,y,-1);
451
452   debug_rect("commodselr",1, search);
453
454   static const char *all_small[]= {
455     "   ___________________________________   ",
456     "  ________X____X__X____________________  ",
457     " ________ X___ X_ X_____XXXXXXXXXXX_____ ",
458     "_________X_X__ X_ X______XXXXXXXXX_______",
459     "________ X X__ X_ X_______XXXXXXX________",
460     "________X_ _X_ X_ X________XXXXX_________",
461     "_______ X__ X_ X_ X_________XXX__________",
462     "_______XXXXXXX X_ X__________X___________",
463     " _____ X     X X_ X______________________",
464     "  ____X_____ _XX_ X______________________",
465     "   __ _______  __ ______________________ ",
466   };
467   static const char *all_big[]= {
468     "???_______________________________________???",
469     "??_________________________________________??",
470     "?_________X______X___X______________________?",
471     "_________?X_____?X__?X______XXXXXXXXXXX______",
472     "_________X_X____?X__?X_______XXXXXXXXX_______",
473     "________?X?X____?X__?X________XXXXXXX________",
474     "________X_?_X___?X__?X_________XXXXX_________",
475     "_______?X__?X___?X__?X__________XXX__________",
476     "_______?XXXXX___?X__?X___________X___________",
477     "_______X????_X__?X__?X_______________________",
478     "?_____?X____?X__?X__?X_______________________",
479     "??____X_____?_X_?X__?X_______________________",
480     "???__?_______?__?___?_______________________?",
481   };
482   static const char *all_fuzzy[]= {
483     "???___________________________________???",
484     "??_______???___X__X____________________??",
485     "?_______????__?X_?X_____XXXXXXXXXXX_____?",
486     "________?????_?X_?X______XXXXXXXXX_______",
487     "________?????_?X_?X_______XXXXXXX________",
488     "_______??????_?X_?X________XXXXX_________",
489     "_______??_?????X_?X_________XXX__________",
490     "______??XXXXX??X_?X__________X___________",
491     "?_____?????????X_?X______________________",
492     "??___???____???X_?X______________________",
493     "???__??_____???__?______________________?",
494   };
495
496 #define COMMOD_SELECTOR_MATCHES(all)                            \
497   commod_selector_matches(search, all,                          \
498                           sizeof((all))/sizeof((all)[0]),       \
499                           strlen((all)[0]))
500
501   if (!(COMMOD_SELECTOR_MATCHES(all_small) ||
502         COMMOD_SELECTOR_MATCHES(all_big) ||
503         COMMOD_SELECTOR_MATCHES(all_fuzzy)))
504     fatal("Commodities selector not set to `All'.");
505 }
506
507 CanonImage *alloc_canon_image(int w, int h) {
508   CanonImage *im= mmalloc(sizeof(CanonImage) + w*h);
509   im->w= w;
510   im->h= h;
511   memset(im->d,'?',w*h);
512   return im;
513 }
514
515 static void file_read_image_ppm(FILE *f) {
516   struct pam inpam;
517   unsigned char rgb_buf[3];
518   CanonImage *im;
519   RgbImage *ri;
520   PageStruct *pstruct;
521
522   progress("page %d reading       ...",npages);
523
524   pnm_readpaminit(f, &inpam, sizeof(inpam));
525   if (!(inpam.maxval == 255 &&
526         inpam.bytes_per_sample == 1 &&
527         inpam.format == RPPM_FORMAT))
528     fatal("PNM screenshot(s) file must be 8bpp 1 byte-per-sample RGB raw");
529
530   CANONICALISE_IMAGE(im, inpam.width, inpam.height, ri, {
531     errno=0; int rr= fread_unlocked(&rgb_buf,1,3,f);
532     sysassert(rr==3);
533     if (rr!=3) fatal("PNM screenshot(s) file ends unexpectedly");
534
535     rgb= rgb_buf[0] | (rgb_buf[1] << 8) | (rgb_buf[2] << 16);
536   });
537
538   sysassert(!ferror(screenshot_file));
539
540   if (!(npages < MAX_PAGES))
541     fatal("Too many images in screenshots file; max is %d.\n", MAX_PAGES);
542
543   find_structure(im,&pstruct, 0,0,0,0);
544   store_current_page(im,pstruct,ri);
545   npages++;
546 }
547
548 void store_current_page(CanonImage *ci, PageStruct *pstruct, RgbImage *rgb) {
549   assert(ci==cim);
550   progress("page %d unantialiasing...",npages);
551   adjust_colours(ci, rgb);
552   progress("page %d storing       ...",npages);
553   if (!npages) page0_rgbimage= rgb;
554   else free(rgb);
555   page_images[npages]= cim;
556   page_structs[npages]= *pstruct;
557   debugf("STORED page %d  pagerheight=%d\n", npages, pstruct->pagerheight);
558   free(pstruct);
559 }
560
561 void read_one_screenshot(void) {
562   progress("reading screenshot...");
563   file_read_image_ppm(screenshot_file);
564   progress_log("read screenshot.");
565 }
566
567 void read_screenshots(void) {
568   struct stat stab;
569   
570   sysassert(! fstat(fileno(screenshot_file), &stab) );
571   
572   for (;;) {
573     if (S_ISREG(stab.st_mode)) {
574       long pos= ftell(screenshot_file);
575       if (pos == stab.st_size) break;
576     } else {
577       int c= fgetc(screenshot_file);
578       if (c==EOF) break;
579       ungetc(c, screenshot_file);
580     }
581     file_read_image_ppm(screenshot_file);
582   }
583   sysassert(!ferror(screenshot_file));
584   progress_log("read %d screenshots.",npages);
585
586   check_pager_motion(1,npages);
587   /* When we are reading screenshots, the pages file contains the
588    * `determine where we're to click' page as well as the first
589    * actual data page, which we have to skip.
590    */
591 }
592
593 #define FIXPT_SHIFT 15
594
595 typedef long Fixpt;
596 static inline Fixpt int2fixpt(int x) { return x<<FIXPT_SHIFT; }
597 static inline Fixpt dbl2fixpt(double x) { return x * int2fixpt(1); }
598 static inline double fixpt2dbl(Fixpt x) { return x / (1.0*int2fixpt(1)); }
599 static inline Fixpt fixpt_mul(Fixpt a, Fixpt b) {
600   return (a*b + dbl2fixpt(0.5)) / int2fixpt(1);
601 }
602 #define MFP(v) fprintf(stderr," %s=%lx=%f", #v,(v),fixpt2dbl((v)))
603
604 static Fixpt aa_bg_chan[3], aa_scale_chan[3], aa_alpha_mean_max;
605 static Rgb aa_background, aa_foreground;
606
607 static void find_aa_density_prep(Rgb bg, Rgb fg, int fg_extra) {
608   int i;
609   unsigned char fg_chan[3];
610
611   aa_background= bg;
612   aa_foreground= fg;
613   aa_alpha_mean_max= fg_extra ? int2fixpt(1)-1 : int2fixpt(1);
614
615   for (i=0; i<3; i++) {
616     aa_bg_chan[i]= int2fixpt( (aa_background >> (i*8)) & 0xff );
617     fg_chan[i]=                aa_foreground >> (i*8);
618
619     aa_scale_chan[i]= 1.0 / (int2fixpt(fg_chan[i]) + fg_extra - aa_bg_chan[i])
620       * dbl2fixpt(1) * dbl2fixpt(1);
621   }
622 }
623
624 static inline Fixpt find_aa_density(const RgbImage *ri, Point p) {
625   Rgb here= ri_rgb(ri, p.x, p.y);
626
627   if (here==aa_background) return 0;
628
629   Fixpt alpha[3], alpha_total=0;
630   int i;
631   for (i=0; i<3; i++) {
632     unsigned char here_chan= here >> (i*8);
633
634     Fixpt alpha_chan= fixpt_mul(int2fixpt(here_chan) - aa_bg_chan[i],
635                                 aa_scale_chan[i]);
636     alpha[i]= alpha_chan;
637     alpha_total += alpha_chan;
638   }
639
640   Fixpt one_third= dbl2fixpt(1/3.0);
641   Fixpt alpha_mean= fixpt_mul(alpha_total, one_third);
642   
643   Fixpt thresh= dbl2fixpt(1.5/AAMAXVAL);
644   Fixpt alpha_min= alpha_mean - thresh*2;
645   Fixpt alpha_max= alpha_mean + thresh*2;
646
647   for (i=0; i<3; i++)
648     MUST( alpha_min <= alpha[i] && alpha[i] <= alpha_max,
649           MP(p);
650           MRGB(here);MRGB(aa_background);MRGB(aa_foreground);
651           MFP(aa_alpha_mean_max);MFP(thresh);MFP(alpha_mean);
652           MFP(alpha_min);MI(i);MFP(alpha[i]);MFP(alpha_max) );
653
654   MUST( -thresh <= alpha_mean && alpha_mean <= aa_alpha_mean_max + thresh,
655         MP(p);
656         MRGB(here);MRGB(aa_background);MRGB(aa_foreground);
657         MFP(aa_alpha_mean_max);MFP(thresh);
658         MFP(alpha_mean); MFP(alpha[0]);MFP(alpha[1]);MFP(alpha[2]); );
659
660   if (alpha_mean < 0)                 alpha_mean= 0;
661   if (alpha_mean > aa_alpha_mean_max) alpha_mean= aa_alpha_mean_max;
662
663   return alpha_mean;
664 }
665
666 static void find_commodity(int offset, Rect *rr) {
667   /* rr->tl.x==-1 if offset out of range */
668   rr->tl.y= s.commbasey - offset*s.comminty;
669   rr->br.y= rr->tl.y + s.comminty-2;
670   if (rr->tl.y < s.mr.tl.y || rr->br.y > s.mr.br.y) { rr->tl.x=-1; return; }
671   
672   rr->tl.x= s.mr.tl.x;
673   rr->br.x= s.mr.br.x;
674
675   if (rr->tl.y > s.mr.tl.y)
676     REQUIRE_RECTANGLE(rr->tl.x,rr->tl.y-1, rr->br.x,rr->tl.y-1, "+");
677   if (rr->br.y < s.mr.tl.y)
678     REQUIRE_RECTANGLE(rr->tl.x,rr->br.y+1, rr->br.x,rr->br.y+1, "+");
679 }
680
681 static void compute_table_location(Rect commod, int colno, Rect *cell) {
682   cell->tl.y= commod.tl.y;
683   cell->br.y= commod.br.y;
684   cell->tl.x= !colno ? commod.tl.x : s.colrightx[colno-1]+2;
685   cell->br.x=                        s.colrightx[colno];
686   debug_rect("cell", colno, *cell);
687 }
688
689 static void ocr_rectangle(Rect r, const OcrCellType ct, FILE *tsv_output) {
690   OcrResultGlyph *results, *res;
691
692   int w= r.br.x - r.tl.x + 1;
693   Pixcol cols[w+1];
694   int x,y;
695   for (x=0; x<w; x++) {
696     FILLZERO(cols[x]);
697     for (y=0; y<text_h; y++) {
698       Point here= { x+r.tl.x, y+r.tl.y };
699       int pixel= get_p(here);
700       if (pixel==' ') pixel= '0';
701       MUST( pixel >= '0' && pixel <= '0'+AAMAXVAL,
702             MC(pixel);MP(here);MSB(ocr_celltype_name(ct));MR(r); );
703       pixcol_p_add(&cols[x], y, pixel-'0');
704     }
705   }
706   FILLZERO(cols[w]);
707
708   results= ocr(rd,ct,w,cols);
709   for (res=results; res->s; res++)
710     fputs(res->s,tsv_output);
711 }
712
713 #define FOR_COMMODITY_CELL(ROW_START, CELL, ROW_END) do{        \
714     Rect rowr, cell;                                            \
715     int tryrect, colno;                                         \
716                                                                 \
717     for (tryrect= +cim->h; tryrect >= -cim->h; tryrect--) {     \
718       find_commodity(tryrect, &rowr);                           \
719       if (rowr.tl.x < 0)                                        \
720         continue;                                               \
721       debug_rect("commod",tryrect, rowr);                       \
722                                                                 \
723       ROW_START;                                                \
724                                                                 \
725       for (colno=0; colno<columns; colno++) {                   \
726         compute_table_location(rowr,colno,&cell);               \
727                                                                 \
728         CELL;                                                   \
729       }                                                         \
730                                                                 \
731       ROW_END;                                                  \
732     }                                                           \
733   }while(0);
734
735 static void adjust_colours_cell(CanonImage *ci, const RgbImage *ri,
736                                 int colno, Rect cell) {
737   Rgb background;
738   unsigned char chanbg[3];
739   long bg_count=0, light_count=0, dark_count=0;
740   int i;
741   Point p;
742
743   background= ri_rgb(ri, cell.br.x, cell.br.y);
744   for (i=0; i<3; i++)
745     chanbg[i]= background >> (i*8);
746
747   FOR_P_RECT(p,cell) {
748     Rgb herergb= ri_rgb(ri, p.x, p.y);
749     if (herergb==background) {
750       bg_count+=3;
751     } else {
752       for (i=0; i<3; i++) {
753         unsigned char here= herergb >> (i*8);
754         if (here == chanbg[i]) bg_count++;
755         else if (here < chanbg[i]) dark_count  += (chanbg[i] - here)/4 + 1;
756         else if (here > chanbg[i]) light_count += (here - chanbg[i])/4 + 1;
757       }
758     }
759   }
760   long total_count= RECT_W(cell) * RECT_H(cell) * 3;
761
762   MUST( bg_count > total_count / 2,
763         MR(cell);MIL(total_count);MIL(bg_count);
764         MIL(light_count);MIL(dark_count) );
765
766   if (bg_count == total_count)
767     return;
768
769   Rgb foreground;
770   double fg_extra;
771
772   if (light_count/16 > dark_count) {
773     foreground= 0xffffffU;
774     fg_extra= +1;
775   } else if (dark_count/16 > light_count) {
776     foreground= 0;
777     fg_extra= -1;
778   } else {
779     MUST( !"tell light from dark",
780           MR(cell);MIL(total_count);MIL(bg_count);
781           MIL(light_count);MIL(dark_count);MRGB(background); );
782   }
783
784   debugf("TABLEENTRY col=%d %d,%d..%d,%d bg=%ld light=%ld dark=%ld\n",
785          colno, cell.tl.x,cell.tl.y, cell.br.x,cell.br.y,
786          bg_count, light_count, dark_count);
787
788   int monochrome= 1;
789
790   find_aa_density_prep(background, foreground, fg_extra);
791
792   FOR_P_RECT(p,cell) {
793     Fixpt alpha= find_aa_density(ri,p);
794
795     int here_int= alpha >> (FIXPT_SHIFT - AADEPTH);
796     assert(here_int <= AAMAXVAL);
797     if (!(here_int==0 || here_int==AAMAXVAL)) monochrome=0;
798     ci->d[p.y * ci->w + p.x]= '0' + here_int;
799   }
800
801   debug_rect("cell0M", colno, cell);
802
803   require_rectangle_r(cell, "0123456789", __LINE__);
804 }
805
806 void adjust_colours(CanonImage *ci, const RgbImage *ri) {
807   if (!(o_mode & mf_analyse))
808     return;
809
810   cim= ci;
811
812   FOR_COMMODITY_CELL({},({
813     adjust_colours_cell(ci,ri,colno,cell);
814   }),{});
815 }
816
817 void analyse(FILE *tsv_output) {
818   int page;
819
820   for (page=0; page<npages; page++) {
821     select_page(page);
822
823     if (!page)
824       check_correct_commodities();
825
826     if (!rd)
827       rd= ocr_init(text_h);
828
829     progress("Processing page %d...",page);
830
831     const char *tab= "";
832     
833     FOR_COMMODITY_CELL({
834       tab= "";
835     },{
836       fputs(tab, tsv_output);
837       ocr_rectangle(cell,
838                     colno<TEXT_COLUMNS
839                     ? &ocr_celltype_text
840                     : &ocr_celltype_number,
841                     tsv_output);
842       tab= "\t";
843     },{
844       fputs("\n", tsv_output);
845       sysassert(!ferror(tsv_output));
846       sysassert(!fflush(tsv_output));
847     })
848   }
849   progress("Commodity table scan complete.");
850 }
851
852 //static Rect islandnamer;
853
854 DEBUG_DEFINE_SOME_DEBUGF(structcolon,colondebugf)
855
856 Rect find_sunshine_widget(void) {
857   Rect sunshiner;
858
859   sunshiner.tl.x= cim->w - 1034 +  885;
860   sunshiner.br.x= cim->w - 1034 + 1020;
861   sunshiner.tl.y= 227;
862   sunshiner.br.y= 228;
863
864   ADJUST_BOX(sunshiner,"o*",>=,30, 100,MUST, tl,y,-1);
865   ADJUST_BOX(sunshiner,"o*",>=,30, 100,MUST, br,y,+1);
866   debug_rect("sunshiner",0, sunshiner);
867
868   MUST(sunshiner.br.y - sunshiner.tl.y > 20, MR(sunshiner));
869   sunshiner.br.y--;
870
871   ADJUST_BOX(sunshiner,"o",>=,20, (cim->w - 1034 + 700), MUST, tl,x,-1);
872   ADJUST_BOX(sunshiner,"o",>=,20,  cim->w,               MUST, br,x,+1);
873   debug_rect("sunshiner",1, sunshiner);
874   return sunshiner;
875 }
876
877 void find_islandname(void) {
878   const RgbImage *rgbsrc= page0_rgbimage;
879   select_page(0);
880
881   RgbImage *ri= alloc_rgb_image(rgbsrc->w, rgbsrc->h);
882   memcpy(ri->data, rgbsrc->data, ri->w * ri->h * 3);
883   
884   Rect sunshiner= find_sunshine_widget();
885   char sunshine[MAXIMGIDENT], archisland[MAXIMGIDENT];
886
887   const Rgb *srcp;
888   Rgb *destp, *endp;
889   for (srcp= rgbsrc->data, destp=ri->data,
890          endp= ri->data + ri->w * ri->h;
891        destp < endp;
892        srcp++, destp++) {
893     Rgb new= *srcp & 0xf0f0f0;
894     *destp= new | (new>>4);
895   }
896
897   identify_rgbimage(ri, sunshiner, sunshine, "sunshine widget");
898   
899   if (!memcmp(sunshine,"Vessel ",5)) {
900     Rect islandnamer;
901     
902     islandnamer.tl.x= cim->w - 1034 +  885;
903     islandnamer.br.x= cim->w - 1034 + 1020;
904     islandnamer.tl.y=                 128;
905     islandnamer.br.y=                 156;
906
907     ADJUST_BOX(islandnamer,"o",>=,5, 0,      MUST, tl,y,+1);
908     ADJUST_BOX(islandnamer,"o",>=,5, cim->h, MUST, br,y,-1);
909
910     ADJUST_BOX(islandnamer,"o",>=,1, 0,      MUST, tl,x,+1);
911     ADJUST_BOX(islandnamer,"o",>=,1, cim->w, MUST, br,x,-1);
912
913     debug_rect("islandnamer",0, islandnamer);
914 //    int larger_islandnamebry= islandnamer.tl.y + 25;
915 //    MUST(islandnamer.br.y < larger_islandnamebry,
916 //       MR(islandnamer);MI(larger_islandnamebry));
917 //    islandnamer.br.y = larger_islandnamebry;
918     debug_rect("islandnamer",1, islandnamer);
919
920     int x,y;
921     for (x=islandnamer.tl.x; x<=islandnamer.br.x; x++)
922       for (y=islandnamer.tl.y; y<=islandnamer.br.y; y++) {
923         if ((ri_rgb(ri,x,y) & 0xff) < 0x40) {
924           *RI_PIXEL32(ri,x,y)= 0;
925         }
926       }
927
928     identify_rgbimage(ri, islandnamer, archisland, "island");
929   } else if (!strcmp(sunshine,"Land - Ahoy!")) {
930     Rect islandnamer;
931
932     islandnamer.tl.x= (sunshiner.tl.x + sunshiner.br.x) / 2;
933     islandnamer.tl.y= sunshiner.tl.y + 100;
934     islandnamer.br= islandnamer.tl;
935     debug_rect("islandnamer",__LINE__, islandnamer);
936     
937     WALK_UNTIL_MUST(islandnamer.tl,y, -1, sunshiner.br.y, 'H');
938     WALK_UNTIL_MUST(islandnamer.tl,x, -1, 0,              'o');
939     WALK_UNTIL_MUST(islandnamer.br,x, +1, cim->w,         'o');
940     debug_rect("islandnamer",__LINE__, islandnamer);
941
942 #define RW (RECT_W(islandnamer))
943 #define RH (RECT_H(islandnamer))
944
945     ADJUST_BOX(islandnamer,"O",>=,RW-4, cim->h, MUST,br,y,+1);
946     debug_rect("islandnamer",__LINE__, islandnamer);
947
948     islandnamer.br.y += 2;
949
950     ADJUST_BOX(islandnamer,"*",<,RW, cim->h, MUST,br,y,+1);
951     debug_rect("islandnamer",__LINE__, islandnamer);
952
953     islandnamer.tl.y= islandnamer.br.y-1;
954     islandnamer.br.y= islandnamer.br.y+1;
955     debug_rect("islandnamer",__LINE__, islandnamer);
956
957     ADJUST_BOX(islandnamer,"*",>=,RW, cim->h, MUST,br,y,+1);
958     debug_rect("islandnamer",__LINE__, islandnamer);
959
960     ADJUST_BOX(islandnamer,"*",<, RH, cim->w, MUST,tl,x,+1);
961     debug_rect("islandnamer",__LINE__, islandnamer);
962
963     MUST( RECT_H(islandnamer) <= 30, MR(islandnamer));
964
965     Point p;
966     int nspaces=1, might_be_colon=0;
967     uint32_t colon_pattern= 0;
968     p.y=-1;
969
970     for (p.x=islandnamer.br.x; p.x>islandnamer.tl.x; p.x--) {
971       colondebugf("structcolon: x=%4d nsp=%2d mbc=%d cp=%08"PRIx32" ",
972                   p.x, nspaces, might_be_colon, colon_pattern);
973
974       uint32_t pattern=0;
975       int runs[32], nruns=0;
976       runs[0]=0; runs[1]=0;
977
978       find_aa_density_prep(0xCCCCAA,0x002255,0);
979       
980       for (p.y=islandnamer.tl.y; p.y<=islandnamer.br.y; p.y++) {
981         pattern <<= 1;
982         Fixpt alpha= find_aa_density(ri,p);
983         if (alpha >= dbl2fixpt(0.49)) {
984            runs[nruns]++;
985            pattern |= 1u;
986         } else {
987           if (runs[nruns]) {
988             nruns++;
989             runs[nruns]=0;
990           }
991         }
992       }
993
994       colondebugf(" pat=%08"PRIx32" nruns=%d runs[]={%d,%d..} ",
995                   pattern, nruns, runs[0],runs[1]);
996
997       if (!pattern) {
998         if (might_be_colon)
999           /* omg it _is_ a colon */
1000           goto colon_found;
1001         nspaces++;
1002         might_be_colon=0;
1003       } else {
1004         if (nruns==2 && runs[1]==runs[0]) {
1005           if (!nspaces) {
1006             if (pattern==colon_pattern)
1007               goto ok_might_be_colon;
1008           } else if (nspaces>=2) {
1009             colon_pattern= pattern;
1010             might_be_colon=1;
1011             goto ok_might_be_colon;
1012           }
1013         } else if (nruns==1 && runs[0]==1 && might_be_colon) {
1014           goto colon_found;
1015         }
1016         might_be_colon=0;
1017       ok_might_be_colon:
1018         nspaces= 0;
1019       }
1020       colondebugf(" nsp=%2d mbc=%d\n", nspaces, might_be_colon);
1021     }
1022     MUST(!"colon found", MP(p);MR(islandnamer) );
1023
1024   colon_found:
1025     colondebugf(" found\n");
1026     islandnamer.br.x= p.x;
1027
1028     identify_rgbimage(ri, islandnamer, archisland, "island");
1029   } else {
1030
1031     MUST(!"sunshine shows ship or ahoy", MS(sunshine) );
1032
1033   }
1034
1035   char *delim= strstr(archisland," - ");
1036   assert(delim);
1037   archipelago= masprintf("%.*s", (int)(delim-archisland), archisland);
1038   island= masprintf("%s", delim+3);
1039
1040 }
1041
1042 void check_pager_motion(int first, int stop) {
1043   /* We check that the pager moved by an appropriate amount.
1044    * The last gap can be smaller but not bigger.
1045    */
1046   int count= stop-first;
1047
1048 #define PH(p) (page_structs[(p)].pagerheight)
1049
1050   debugf("CHECK_PAGER_MOTION %d..%d count=%d\n", first,stop,count);
1051
1052   if (count <= 1) return; /* only one page */
1053
1054   double firstheight= PH(first);
1055   double max= count>2 ? firstheight / (count-2) : 0;
1056   double min=           firstheight / (count-1);
1057   max *= 1.1;
1058   min /= 1.1;
1059   max += 1.0;
1060   min -= 1.0;
1061   debugf("CHECK_PAGER_MOTION min=%g max=%g\n", min,max);
1062   assert(max>min);
1063
1064   int skips=0, firstskip=1;
1065   int stops=0, firststop=1;
1066
1067 #define REPORT(skipstop,msg) do{                        \
1068       skipstop##s++;                                    \
1069       if (first##skipstop<0) first##skipstop= page;     \
1070       if (skipstop##s<5)                                \
1071         fprintf(stderr,msg " (page %d)\n",page);        \
1072     }while(0)
1073
1074   int page;
1075   for (page=first+1; page<stop; page++) {
1076     int gap= PH(page-1) - PH(page);
1077     debugf("CHECK_PAGER_MOTION  page=%2d gap=%2d\n", page, gap);
1078     if (gap>max)
1079       REPORT(skip, "scrollbar motion probable page skip detected!");
1080     if (gap<min && page<stop-1)
1081       REPORT(stop, "scrollbar short motion gives lie to our ideas!");
1082   }
1083   MUST(!skips && !stops,
1084        MI(skips);MI(firstskip);MF(max);MI(PH(firstskip));MI(PH(firstskip-1));
1085        MI(stops);MI(firststop);MF(min);MI(PH(firststop));MI(PH(firststop-1));
1086        MF(firstheight);MI(PH(stop-2));MI(PH(stop-1));
1087        MSB(
1088 "\n\n"
1089 "Your YPP client seems to be paging irregularly.  Perhaps your computer\n"
1090 "is too slow or overloaded or perhaps you are messing with the mouse ?\n"
1091        ));
1092 }