chiark / gitweb /
routesearch: make absolute/perleague into 2-element arrays rather than macros
[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;
448
449   search.tl= s.mr.tl;  search.tl.x +=  34;  search.tl.y -= 44;
450   search.br= s.mr.tl;  search.br.x += 114;  search.br.y -= 23;
451   MUST(search.br.x < cim->w-1 && search.tl.y > 0,
452        MR(search);MI(cim->w);MI(cim->h));
453   
454   debug_rect("commodselr",0, search);
455   ADJUST_BOX(search,"_",>=,10, cim->h, MUST, tl,y,+1);
456   ADJUST_BOX(search,"_",>=,10, 0,      MUST, br,y,-1);
457   debug_rect("commodselr",1, search);
458
459   static const char *all_small[]= {
460     "   ___________________________________   ",
461     "  ________X____X__X____________________  ",
462     " ________ X___ X_ X_____XXXXXXXXXXX_____ ",
463     "_________X_X__ X_ X______XXXXXXXXX_______",
464     "________ X X__ X_ X_______XXXXXXX________",
465     "________X_ _X_ X_ X________XXXXX_________",
466     "_______ X__ X_ X_ X_________XXX__________",
467     "_______XXXXXXX X_ X__________X___________",
468     " _____ X     X X_ X______________________",
469     "  ____X_____ _XX_ X______________________",
470     "   __ _______  __ ______________________ ",
471   };
472   static const char *all_big[]= {
473     "???_______________________________________???",
474     "??_________________________________________??",
475     "?_________X______X___X______________________?",
476     "_________?X_____?X__?X______XXXXXXXXXXX______",
477     "_________X_X____?X__?X_______XXXXXXXXX_______",
478     "________?X?X____?X__?X________XXXXXXX________",
479     "________X_?_X___?X__?X_________XXXXX_________",
480     "_______?X__?X___?X__?X__________XXX__________",
481     "_______?XXXXX___?X__?X___________X___________",
482     "_______X????_X__?X__?X_______________________",
483     "?_____?X____?X__?X__?X_______________________",
484     "??____X_____?_X_?X__?X_______________________",
485     "???__?_______?__?___?_______________________?",
486   };
487   static const char *all_fuzzy[]= {
488     "???___________________________________???",
489     "??_______???___X__X____________________??",
490     "?_______????__?X_?X_____XXXXXXXXXXX_____?",
491     "________?????_?X_?X______XXXXXXXXX_______",
492     "________?????_?X_?X_______XXXXXXX________",
493     "_______??????_?X_?X________XXXXX_________",
494     "_______??_?????X_?X_________XXX__________",
495     "______??XXXXX??X_?X__________X___________",
496     "?_____?????????X_?X______________________",
497     "??___???____???X_?X______________________",
498     "???__??_____???__?______________________?",
499   };
500
501 #define COMMOD_SELECTOR_MATCHES(all)                            \
502   commod_selector_matches(search, all,                          \
503                           sizeof((all))/sizeof((all)[0]),       \
504                           strlen((all)[0]))
505
506   if (!(COMMOD_SELECTOR_MATCHES(all_small) ||
507         COMMOD_SELECTOR_MATCHES(all_big) ||
508         COMMOD_SELECTOR_MATCHES(all_fuzzy)))
509     fatal("Commodities selector not set to `All'.");
510 }
511
512 CanonImage *alloc_canon_image(int w, int h) {
513   CanonImage *im= mmalloc(sizeof(CanonImage) + w*h);
514   im->w= w;
515   im->h= h;
516   memset(im->d,'?',w*h);
517   return im;
518 }
519
520 static void file_read_image_ppm(FILE *f) {
521   struct pam inpam;
522   unsigned char rgb_buf[3];
523   CanonImage *im;
524   RgbImage *ri;
525   PageStruct *pstruct;
526
527   progress("page %d reading       ...",npages);
528
529   pnm_readpaminit(f, &inpam, sizeof(inpam));
530   if (!(inpam.maxval == 255 &&
531         inpam.bytes_per_sample == 1 &&
532         inpam.format == RPPM_FORMAT))
533     fatal("PNM screenshot(s) file must be 8bpp 1 byte-per-sample RGB raw");
534
535   CANONICALISE_IMAGE(im, inpam.width, inpam.height, ri, {
536     errno=0; int rr= fread_unlocked(&rgb_buf,1,3,f);
537     sysassert(rr==3);
538     if (rr!=3) fatal("PNM screenshot(s) file ends unexpectedly");
539
540     rgb= rgb_buf[0] | (rgb_buf[1] << 8) | (rgb_buf[2] << 16);
541   });
542
543   sysassert(!ferror(screenshot_file));
544
545   if (!(npages < MAX_PAGES))
546     fatal("Too many images in screenshots file; max is %d.\n", MAX_PAGES);
547
548   find_structure(im,&pstruct, 0,0,0,0);
549   store_current_page(im,pstruct,ri);
550   npages++;
551 }
552
553 void store_current_page(CanonImage *ci, PageStruct *pstruct, RgbImage *rgb) {
554   assert(ci==cim);
555   progress("page %d unantialiasing...",npages);
556   adjust_colours(ci, rgb);
557   progress("page %d storing       ...",npages);
558   if (!npages) page0_rgbimage= rgb;
559   else free(rgb);
560   page_images[npages]= cim;
561   page_structs[npages]= *pstruct;
562   debugf("STORED page %d  pagerheight=%d\n", npages, pstruct->pagerheight);
563   free(pstruct);
564 }
565
566 void read_one_screenshot(void) {
567   progress("reading screenshot...");
568   file_read_image_ppm(screenshot_file);
569   progress_log("read screenshot.");
570 }
571
572 void read_screenshots(void) {
573   struct stat stab;
574   
575   sysassert(! fstat(fileno(screenshot_file), &stab) );
576   
577   for (;;) {
578     if (S_ISREG(stab.st_mode)) {
579       long pos= ftell(screenshot_file);
580       if (pos == stab.st_size) break;
581     } else {
582       int c= fgetc(screenshot_file);
583       if (c==EOF) break;
584       ungetc(c, screenshot_file);
585     }
586     file_read_image_ppm(screenshot_file);
587   }
588   sysassert(!ferror(screenshot_file));
589   progress_log("read %d screenshots.",npages);
590
591   check_pager_motion(1,npages);
592   /* When we are reading screenshots, the pages file contains the
593    * `determine where we're to click' page as well as the first
594    * actual data page, which we have to skip.
595    */
596 }
597
598 #define FIXPT_SHIFT 15
599
600 typedef long Fixpt;
601 static inline Fixpt int2fixpt(int x) { return x<<FIXPT_SHIFT; }
602 static inline Fixpt dbl2fixpt(double x) { return x * int2fixpt(1); }
603 static inline double fixpt2dbl(Fixpt x) { return x / (1.0*int2fixpt(1)); }
604 static inline Fixpt fixpt_mul(Fixpt a, Fixpt b) {
605   return (a*b + dbl2fixpt(0.5)) / int2fixpt(1);
606 }
607 #define MFP(v) fprintf(stderr," %s=%lx=%f", #v,(v),fixpt2dbl((v)))
608
609 static Fixpt aa_bg_chan[3], aa_scale_chan[3], aa_alpha_mean_max;
610 static Rgb aa_background, aa_foreground;
611
612 static void find_aa_density_prep(Rgb bg, Rgb fg, int fg_extra) {
613   int i;
614   unsigned char fg_chan[3];
615
616   aa_background= bg;
617   aa_foreground= fg;
618   aa_alpha_mean_max= fg_extra ? int2fixpt(1)-1 : int2fixpt(1);
619
620   for (i=0; i<3; i++) {
621     aa_bg_chan[i]= int2fixpt( (aa_background >> (i*8)) & 0xff );
622     fg_chan[i]=                aa_foreground >> (i*8);
623
624     aa_scale_chan[i]= 1.0 / (int2fixpt(fg_chan[i]) + fg_extra - aa_bg_chan[i])
625       * dbl2fixpt(1) * dbl2fixpt(1);
626   }
627 }
628
629 static inline Fixpt find_aa_density(const RgbImage *ri, Point p) {
630   Rgb here= ri_rgb(ri, p.x, p.y);
631
632   if (here==aa_background) return 0;
633
634   Fixpt alpha[3], alpha_total=0;
635   int i;
636   for (i=0; i<3; i++) {
637     unsigned char here_chan= here >> (i*8);
638
639     Fixpt alpha_chan= fixpt_mul(int2fixpt(here_chan) - aa_bg_chan[i],
640                                 aa_scale_chan[i]);
641     alpha[i]= alpha_chan;
642     alpha_total += alpha_chan;
643   }
644
645   Fixpt one_third= dbl2fixpt(1/3.0);
646   Fixpt alpha_mean= fixpt_mul(alpha_total, one_third);
647   
648   Fixpt thresh= dbl2fixpt(1.5/AAMAXVAL);
649   Fixpt alpha_min= alpha_mean - thresh*2;
650   Fixpt alpha_max= alpha_mean + thresh*2;
651
652   for (i=0; i<3; i++)
653     MUST( alpha_min <= alpha[i] && alpha[i] <= alpha_max,
654           MP(p);
655           MRGB(here);MRGB(aa_background);MRGB(aa_foreground);
656           MFP(aa_alpha_mean_max);MFP(thresh);MFP(alpha_mean);
657           MFP(alpha_min);MI(i);MFP(alpha[i]);MFP(alpha_max) );
658
659   MUST( -thresh <= alpha_mean && alpha_mean <= aa_alpha_mean_max + thresh,
660         MP(p);
661         MRGB(here);MRGB(aa_background);MRGB(aa_foreground);
662         MFP(aa_alpha_mean_max);MFP(thresh);
663         MFP(alpha_mean); MFP(alpha[0]);MFP(alpha[1]);MFP(alpha[2]); );
664
665   if (alpha_mean < 0)                 alpha_mean= 0;
666   if (alpha_mean > aa_alpha_mean_max) alpha_mean= aa_alpha_mean_max;
667
668   return alpha_mean;
669 }
670
671 static void find_commodity(int offset, Rect *rr) {
672   /* rr->tl.x==-1 if offset out of range */
673   rr->tl.y= s.commbasey - offset*s.comminty;
674   rr->br.y= rr->tl.y + s.comminty-2;
675   if (rr->tl.y < s.mr.tl.y || rr->br.y > s.mr.br.y) { rr->tl.x=-1; return; }
676   
677   rr->tl.x= s.mr.tl.x;
678   rr->br.x= s.mr.br.x;
679
680   if (rr->tl.y > s.mr.tl.y)
681     REQUIRE_RECTANGLE(rr->tl.x,rr->tl.y-1, rr->br.x,rr->tl.y-1, "+");
682   if (rr->br.y < s.mr.tl.y)
683     REQUIRE_RECTANGLE(rr->tl.x,rr->br.y+1, rr->br.x,rr->br.y+1, "+");
684 }
685
686 static void compute_table_location(Rect commod, int colno, Rect *cell) {
687   cell->tl.y= commod.tl.y;
688   cell->br.y= commod.br.y;
689   cell->tl.x= !colno ? commod.tl.x : s.colrightx[colno-1]+2;
690   cell->br.x=                        s.colrightx[colno];
691   debug_rect("cell", colno, *cell);
692 }
693
694 static void ocr_rectangle(Rect r, const OcrCellType ct, FILE *tsv_output) {
695   OcrResultGlyph *results, *res;
696
697   int w= r.br.x - r.tl.x + 1;
698   Pixcol cols[w+1];
699   int x,y;
700   for (x=0; x<w; x++) {
701     FILLZERO(cols[x]);
702     for (y=0; y<text_h; y++) {
703       Point here= { x+r.tl.x, y+r.tl.y };
704       int pixel= get_p(here);
705       if (pixel==' ') pixel= '0';
706       MUST( pixel >= '0' && pixel <= '0'+AAMAXVAL,
707             MC(pixel);MP(here);MSB(ocr_celltype_name(ct));MR(r); );
708       pixcol_p_add(&cols[x], y, pixel-'0');
709     }
710   }
711   FILLZERO(cols[w]);
712
713   results= ocr(rd,ct,w,cols);
714   for (res=results; res->s; res++)
715     fputs(res->s,tsv_output);
716 }
717
718 #define FOR_COMMODITY_CELL(ROW_START, CELL, ROW_END) do{        \
719     Rect rowr, cell;                                            \
720     int tryrect, colno;                                         \
721                                                                 \
722     for (tryrect= +cim->h; tryrect >= -cim->h; tryrect--) {     \
723       find_commodity(tryrect, &rowr);                           \
724       if (rowr.tl.x < 0)                                        \
725         continue;                                               \
726       debug_rect("commod",tryrect, rowr);                       \
727                                                                 \
728       ROW_START;                                                \
729                                                                 \
730       for (colno=0; colno<columns; colno++) {                   \
731         compute_table_location(rowr,colno,&cell);               \
732                                                                 \
733         CELL;                                                   \
734       }                                                         \
735                                                                 \
736       ROW_END;                                                  \
737     }                                                           \
738   }while(0);
739
740 static void adjust_colours_cell(CanonImage *ci, const RgbImage *ri,
741                                 int colno, Rect cell) {
742   Rgb background;
743   unsigned char chanbg[3];
744   long bg_count=0, light_count=0, dark_count=0;
745   int i;
746   Point p;
747
748   background= ri_rgb(ri, cell.br.x, cell.br.y);
749   for (i=0; i<3; i++)
750     chanbg[i]= background >> (i*8);
751
752   FOR_P_RECT(p,cell) {
753     Rgb herergb= ri_rgb(ri, p.x, p.y);
754     if (herergb==background) {
755       bg_count+=3;
756     } else {
757       for (i=0; i<3; i++) {
758         unsigned char here= herergb >> (i*8);
759         if (here == chanbg[i]) bg_count++;
760         else if (here < chanbg[i]) dark_count  += (chanbg[i] - here)/4 + 1;
761         else if (here > chanbg[i]) light_count += (here - chanbg[i])/4 + 1;
762       }
763     }
764   }
765   long total_count= RECT_W(cell) * RECT_H(cell) * 3;
766
767   MUST( bg_count > total_count / 2,
768         MR(cell);MIL(total_count);MIL(bg_count);
769         MIL(light_count);MIL(dark_count) );
770
771   if (bg_count == total_count)
772     return;
773
774   Rgb foreground;
775   double fg_extra;
776
777   if (light_count/16 > dark_count) {
778     foreground= 0xffffffU;
779     fg_extra= +1;
780   } else if (dark_count/16 > light_count) {
781     foreground= 0;
782     fg_extra= -1;
783   } else {
784     MUST( !"tell light from dark",
785           MR(cell);MIL(total_count);MIL(bg_count);
786           MIL(light_count);MIL(dark_count);MRGB(background); );
787   }
788
789   debugf("TABLEENTRY col=%d %d,%d..%d,%d bg=%ld light=%ld dark=%ld\n",
790          colno, cell.tl.x,cell.tl.y, cell.br.x,cell.br.y,
791          bg_count, light_count, dark_count);
792
793   int monochrome= 1;
794
795   find_aa_density_prep(background, foreground, fg_extra);
796
797   FOR_P_RECT(p,cell) {
798     Fixpt alpha= find_aa_density(ri,p);
799
800     int here_int= alpha >> (FIXPT_SHIFT - AADEPTH);
801     assert(here_int <= AAMAXVAL);
802     if (!(here_int==0 || here_int==AAMAXVAL)) monochrome=0;
803     ci->d[p.y * ci->w + p.x]= '0' + here_int;
804   }
805
806   debug_rect("cell0M", colno, cell);
807
808   require_rectangle_r(cell, "0123456789", __LINE__);
809 }
810
811 void adjust_colours(CanonImage *ci, const RgbImage *ri) {
812   if (!(o_mode & mf_analyse))
813     return;
814
815   cim= ci;
816
817   FOR_COMMODITY_CELL({},({
818     adjust_colours_cell(ci,ri,colno,cell);
819   }),{});
820 }
821
822 void analyse(FILE *tsv_output) {
823   int page;
824
825   for (page=0; page<npages; page++) {
826     select_page(page);
827
828     if (!page)
829       check_correct_commodities();
830
831     if (!rd)
832       rd= ocr_init(text_h);
833
834     progress("Processing page %d...",page);
835
836     const char *tab= "";
837     
838     FOR_COMMODITY_CELL({
839       tab= "";
840     },{
841       fputs(tab, tsv_output);
842       ocr_rectangle(cell,
843                     colno<TEXT_COLUMNS
844                     ? &ocr_celltype_text
845                     : &ocr_celltype_number,
846                     tsv_output);
847       tab= "\t";
848     },{
849       fputs("\n", tsv_output);
850       sysassert(!ferror(tsv_output));
851       sysassert(!fflush(tsv_output));
852     })
853   }
854   progress("Commodity table scan complete.");
855 }
856
857 //static Rect islandnamer;
858
859 DEBUG_DEFINE_SOME_DEBUGF(structcolon,colondebugf)
860
861 Rect find_sunshine_widget(void) {
862   Rect sunshiner;
863
864   sunshiner.tl.x= cim->w - 1034 +  885;
865   sunshiner.br.x= cim->w - 1034 + 1020;
866   sunshiner.tl.y= 227;
867   sunshiner.br.y= 228;
868
869   ADJUST_BOX(sunshiner,"o*",>=,30, 100,MUST, tl,y,-1);
870   ADJUST_BOX(sunshiner,"o*",>=,30, 100,MUST, br,y,+1);
871   debug_rect("sunshiner",0, sunshiner);
872
873   MUST(sunshiner.br.y - sunshiner.tl.y > 20, MR(sunshiner));
874   sunshiner.br.y--;
875
876   ADJUST_BOX(sunshiner,"o",>=,20, (cim->w - 1034 + 700), MUST, tl,x,-1);
877   ADJUST_BOX(sunshiner,"o",>=,20,  cim->w,               MUST, br,x,+1);
878   debug_rect("sunshiner",1, sunshiner);
879   return sunshiner;
880 }
881
882 void find_islandname(void) {
883   const RgbImage *rgbsrc= page0_rgbimage;
884   select_page(0);
885
886   RgbImage *ri= alloc_rgb_image(rgbsrc->w, rgbsrc->h);
887   memcpy(ri->data, rgbsrc->data, ri->w * ri->h * 3);
888   
889   Rect sunshiner= find_sunshine_widget();
890   char sunshine[MAXIMGIDENT], archisland[MAXIMGIDENT];
891
892   const Rgb *srcp;
893   Rgb *destp, *endp;
894   for (srcp= rgbsrc->data, destp=ri->data,
895          endp= ri->data + ri->w * ri->h;
896        destp < endp;
897        srcp++, destp++) {
898     Rgb new= *srcp & 0xf0f0f0;
899     *destp= new | (new>>4);
900   }
901
902   identify_rgbimage(ri, sunshiner, sunshine, "sunshine widget");
903   
904   if (!memcmp(sunshine,"Vessel ",5)) {
905     Rect islandnamer;
906     
907     islandnamer.tl.x= cim->w - 1034 +  885;
908     islandnamer.br.x= cim->w - 1034 + 1020;
909     islandnamer.tl.y=                 128;
910     islandnamer.br.y=                 156;
911
912     ADJUST_BOX(islandnamer,"o",>=,5, 0,      MUST, tl,y,+1);
913     ADJUST_BOX(islandnamer,"o",>=,5, cim->h, MUST, br,y,-1);
914
915     ADJUST_BOX(islandnamer,"o",>=,1, 0,      MUST, tl,x,+1);
916     ADJUST_BOX(islandnamer,"o",>=,1, cim->w, MUST, br,x,-1);
917
918     debug_rect("islandnamer",0, islandnamer);
919 //    int larger_islandnamebry= islandnamer.tl.y + 25;
920 //    MUST(islandnamer.br.y < larger_islandnamebry,
921 //       MR(islandnamer);MI(larger_islandnamebry));
922 //    islandnamer.br.y = larger_islandnamebry;
923     debug_rect("islandnamer",1, islandnamer);
924
925     int x,y;
926     for (x=islandnamer.tl.x; x<=islandnamer.br.x; x++)
927       for (y=islandnamer.tl.y; y<=islandnamer.br.y; y++) {
928         if ((ri_rgb(ri,x,y) & 0xff) < 0x40) {
929           *RI_PIXEL32(ri,x,y)= 0;
930         }
931       }
932
933     identify_rgbimage(ri, islandnamer, archisland, "island");
934   } else if (!strcmp(sunshine,"Land - Ahoy!")) {
935     Rect islandnamer;
936
937     islandnamer.tl.x= (sunshiner.tl.x + sunshiner.br.x) / 2;
938     islandnamer.tl.y= sunshiner.tl.y + 100;
939     islandnamer.br= islandnamer.tl;
940     debug_rect("islandnamer",__LINE__, islandnamer);
941     
942     WALK_UNTIL_MUST(islandnamer.tl,y, -1, sunshiner.br.y, 'H');
943     WALK_UNTIL_MUST(islandnamer.tl,x, -1, 0,              'o');
944     WALK_UNTIL_MUST(islandnamer.br,x, +1, cim->w,         'o');
945     debug_rect("islandnamer",__LINE__, islandnamer);
946
947 #define RW (RECT_W(islandnamer))
948 #define RH (RECT_H(islandnamer))
949
950     ADJUST_BOX(islandnamer,"O",>=,RW-4, cim->h, MUST,br,y,+1);
951     debug_rect("islandnamer",__LINE__, islandnamer);
952
953     islandnamer.br.y += 2;
954
955     ADJUST_BOX(islandnamer,"*",<,RW, cim->h, MUST,br,y,+1);
956     debug_rect("islandnamer",__LINE__, islandnamer);
957
958     islandnamer.tl.y= islandnamer.br.y-1;
959     islandnamer.br.y= islandnamer.br.y+1;
960     debug_rect("islandnamer",__LINE__, islandnamer);
961
962     ADJUST_BOX(islandnamer,"*",>=,RW, cim->h, MUST,br,y,+1);
963     debug_rect("islandnamer",__LINE__, islandnamer);
964
965     ADJUST_BOX(islandnamer,"*",<, RH, cim->w, MUST,tl,x,+1);
966     debug_rect("islandnamer",__LINE__, islandnamer);
967
968     MUST( RECT_H(islandnamer) <= 30, MR(islandnamer));
969
970     Point p;
971     int nspaces=1, might_be_colon=0;
972     uint32_t colon_pattern= 0;
973     p.y=-1;
974
975     for (p.x=islandnamer.br.x; p.x>islandnamer.tl.x; p.x--) {
976       colondebugf("structcolon: x=%4d nsp=%2d mbc=%d cp=%08"PRIx32" ",
977                   p.x, nspaces, might_be_colon, colon_pattern);
978
979       uint32_t pattern=0;
980       int runs[32], nruns=0;
981       runs[0]=0; runs[1]=0;
982
983       find_aa_density_prep(0xCCCCAA,0x002255,0);
984       
985       for (p.y=islandnamer.tl.y; p.y<=islandnamer.br.y; p.y++) {
986         pattern <<= 1;
987         Fixpt alpha= find_aa_density(ri,p);
988         if (alpha >= dbl2fixpt(0.49)) {
989            runs[nruns]++;
990            pattern |= 1u;
991         } else {
992           if (runs[nruns]) {
993             nruns++;
994             runs[nruns]=0;
995           }
996         }
997       }
998
999       colondebugf(" pat=%08"PRIx32" nruns=%d runs[]={%d,%d..} ",
1000                   pattern, nruns, runs[0],runs[1]);
1001
1002       if (!pattern) {
1003         if (might_be_colon)
1004           /* omg it _is_ a colon */
1005           goto colon_found;
1006         nspaces++;
1007         might_be_colon=0;
1008       } else {
1009         if (nruns==2 && runs[1]==runs[0]) {
1010           if (!nspaces) {
1011             if (pattern==colon_pattern)
1012               goto ok_might_be_colon;
1013           } else if (nspaces>=2) {
1014             colon_pattern= pattern;
1015             might_be_colon=1;
1016             goto ok_might_be_colon;
1017           }
1018         } else if (nruns==1 && runs[0]==1 && might_be_colon) {
1019           goto colon_found;
1020         }
1021         might_be_colon=0;
1022       ok_might_be_colon:
1023         nspaces= 0;
1024       }
1025       colondebugf(" nsp=%2d mbc=%d\n", nspaces, might_be_colon);
1026     }
1027     MUST(!"colon found", MP(p);MR(islandnamer) );
1028
1029   colon_found:
1030     colondebugf(" found\n");
1031     islandnamer.br.x= p.x;
1032
1033     identify_rgbimage(ri, islandnamer, archisland, "island");
1034   } else {
1035
1036     MUST(!"sunshine shows ship or ahoy", MS(sunshine) );
1037
1038   }
1039
1040   char *delim= strstr(archisland," - ");
1041   assert(delim);
1042   archipelago= masprintf("%.*s", (int)(delim-archisland), archisland);
1043   island= masprintf("%s", delim+3);
1044
1045 }
1046
1047 void check_pager_motion(int first, int stop) {
1048   /* We check that the pager moved by an appropriate amount.
1049    * The last gap can be smaller but not bigger.
1050    */
1051   int count= stop-first;
1052
1053 #define PH(p) (page_structs[(p)].pagerheight)
1054
1055   debugf("CHECK_PAGER_MOTION %d..%d count=%d\n", first,stop,count);
1056
1057   if (count <= 1) return; /* only one page */
1058
1059   double firstheight= PH(first);
1060   double max= count>2 ? firstheight / (count-2) : 1e6;
1061   double min=           firstheight / (count-1);
1062   max *= 1.1;
1063   min /= 1.1;
1064   max += 1.0;
1065   min -= 1.0;
1066   debugf("CHECK_PAGER_MOTION min=%g max=%g\n", min,max);
1067   assert(max>min);
1068
1069   int skips=0, firstskip=1;
1070   int stops=0, firststop=1;
1071
1072 #define REPORT(skipstop,msg) do{                        \
1073       skipstop##s++;                                    \
1074       if (first##skipstop<0) first##skipstop= page;     \
1075       if (skipstop##s<5)                                \
1076         fprintf(stderr,msg " (page %d)\n",page);        \
1077     }while(0)
1078
1079   int page;
1080   for (page=first+1; page<stop; page++) {
1081     int gap= PH(page-1) - PH(page);
1082     debugf("CHECK_PAGER_MOTION  page=%2d gap=%2d\n", page, gap);
1083     if (gap>max)
1084       REPORT(skip, "scrollbar motion probable page skip detected!");
1085     if (gap<min && page<stop-1)
1086       REPORT(stop, "scrollbar short motion gives lie to our ideas!");
1087   }
1088   MUST(!skips && !stops,
1089        MI(skips);MI(firstskip);MF(max);MI(PH(firstskip));MI(PH(firstskip-1));
1090        MI(stops);MI(firststop);MF(min);MI(PH(firststop));MI(PH(firststop-1));
1091        MF(firstheight);MI(PH(stop-2));MI(PH(stop-1));
1092        MSB(
1093 "\n\n"
1094 "Your YPP client seems to be paging irregularly.  Perhaps your computer\n"
1095 "is too slow or overloaded or perhaps you are messing with the mouse ?\n"
1096        ));
1097 }