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