chiark / gitweb /
query_routesearch: fix to not complain about >=
[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;
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
650   MUST( -thresh <= alpha_mean && alpha_mean <= aa_alpha_mean_max + thresh,
651         MP(p);
652         MRGB(here);MRGB(aa_background);MRGB(aa_foreground);
653         MFP(aa_alpha_mean_max);MFP(thresh);
654         MFP(alpha_mean); MFP(alpha[0]);MFP(alpha[1]);MFP(alpha[2]); );
655
656   if (alpha_mean < 0)                 alpha_mean= 0;
657   if (alpha_mean > aa_alpha_mean_max) alpha_mean= aa_alpha_mean_max;
658
659   return alpha_mean;
660 }
661
662 static void find_commodity(int offset, Rect *rr) {
663   /* rr->tl.x==-1 if offset out of range */
664   rr->tl.y= s.commbasey - offset*s.comminty;
665   rr->br.y= rr->tl.y + s.comminty-2;
666   if (rr->tl.y < s.mr.tl.y || rr->br.y > s.mr.br.y) { rr->tl.x=-1; return; }
667   
668   rr->tl.x= s.mr.tl.x;
669   rr->br.x= s.mr.br.x;
670
671   if (rr->tl.y > s.mr.tl.y)
672     REQUIRE_RECTANGLE(rr->tl.x,rr->tl.y-1, rr->br.x,rr->tl.y-1, "+");
673   if (rr->br.y < s.mr.tl.y)
674     REQUIRE_RECTANGLE(rr->tl.x,rr->br.y+1, rr->br.x,rr->br.y+1, "+");
675 }
676
677 static void compute_table_location(Rect commod, int colno, Rect *cell) {
678   cell->tl.y= commod.tl.y;
679   cell->br.y= commod.br.y;
680   cell->tl.x= !colno ? commod.tl.x : s.colrightx[colno-1]+2;
681   cell->br.x=                        s.colrightx[colno];
682   debug_rect("cell", colno, *cell);
683 }
684
685 static void ocr_rectangle(Rect r, const OcrCellType ct, FILE *tsv_output) {
686   OcrResultGlyph *results, *res;
687
688   int w= r.br.x - r.tl.x + 1;
689   Pixcol cols[w+1];
690   int x,y;
691   for (x=0; x<w; x++) {
692     FILLZERO(cols[x]);
693     for (y=0; y<text_h; y++) {
694       Point here= { x+r.tl.x, y+r.tl.y };
695       int pixel= get_p(here);
696       if (pixel==' ') pixel= '0';
697       MUST( pixel >= '0' && pixel <= '0'+AAMAXVAL,
698             MC(pixel);MP(here);MSB(ocr_celltype_name(ct));MR(r); );
699       pixcol_p_add(&cols[x], y, pixel-'0');
700     }
701   }
702   FILLZERO(cols[w]);
703
704   results= ocr(rd,ct,w,cols);
705   for (res=results; res->s; res++)
706     fputs(res->s,tsv_output);
707 }
708
709 #define FOR_COMMODITY_CELL(ROW_START, CELL, ROW_END) do{        \
710     Rect rowr, cell;                                            \
711     int tryrect, colno;                                         \
712                                                                 \
713     for (tryrect= +cim->h; tryrect >= -cim->h; tryrect--) {     \
714       find_commodity(tryrect, &rowr);                           \
715       if (rowr.tl.x < 0)                                        \
716         continue;                                               \
717       debug_rect("commod",tryrect, rowr);                       \
718                                                                 \
719       ROW_START;                                                \
720                                                                 \
721       for (colno=0; colno<columns; colno++) {                   \
722         compute_table_location(rowr,colno,&cell);               \
723                                                                 \
724         CELL;                                                   \
725       }                                                         \
726                                                                 \
727       ROW_END;                                                  \
728     }                                                           \
729   }while(0);
730
731 static void adjust_colours_cell(CanonImage *ci, const RgbImage *ri,
732                                 int colno, Rect cell) {
733   Rgb background;
734   unsigned char chanbg[3];
735   long bg_count=0, light_count=0, dark_count=0;
736   int i;
737   Point p;
738
739   background= ri_rgb(ri, cell.br.x, cell.br.y);
740   for (i=0; i<3; i++)
741     chanbg[i]= background >> (i*8);
742
743   FOR_P_RECT(p,cell) {
744     Rgb herergb= ri_rgb(ri, p.x, p.y);
745     if (herergb==background) {
746       bg_count+=3;
747     } else {
748       for (i=0; i<3; i++) {
749         unsigned char here= herergb >> (i*8);
750         if (here == chanbg[i]) bg_count++;
751         else if (here < chanbg[i]) dark_count  += (chanbg[i] - here)/4 + 1;
752         else if (here > chanbg[i]) light_count += (here - chanbg[i])/4 + 1;
753       }
754     }
755   }
756   long total_count= RECT_W(cell) * RECT_H(cell) * 3;
757
758   MUST( bg_count > total_count / 2,
759         MR(cell);MIL(total_count);MIL(bg_count);
760         MIL(light_count);MIL(dark_count) );
761
762   if (bg_count == total_count)
763     return;
764
765   Rgb foreground;
766   double fg_extra;
767
768   if (light_count/16 > dark_count) {
769     foreground= 0xffffffU;
770     fg_extra= +1;
771   } else if (dark_count/16 > light_count) {
772     foreground= 0;
773     fg_extra= -1;
774   } else {
775     MUST( !"tell light from dark",
776           MR(cell);MIL(total_count);MIL(bg_count);
777           MIL(light_count);MIL(dark_count);MRGB(background); );
778   }
779
780   debugf("TABLEENTRY col=%d %d,%d..%d,%d bg=%ld light=%ld dark=%ld\n",
781          colno, cell.tl.x,cell.tl.y, cell.br.x,cell.br.y,
782          bg_count, light_count, dark_count);
783
784   int monochrome= 1;
785
786   find_aa_density_prep(background, foreground, fg_extra);
787
788   FOR_P_RECT(p,cell) {
789     Fixpt alpha= find_aa_density(ri,p);
790
791     int here_int= alpha >> (FIXPT_SHIFT - AADEPTH);
792     assert(here_int <= AAMAXVAL);
793     if (!(here_int==0 || here_int==AAMAXVAL)) monochrome=0;
794     ci->d[p.y * ci->w + p.x]= '0' + here_int;
795   }
796
797   debug_rect("cell0M", colno, cell);
798
799   require_rectangle_r(cell, "0123456789", __LINE__);
800 }
801
802 void adjust_colours(CanonImage *ci, const RgbImage *ri) {
803   if (!(o_mode & mf_analyse))
804     return;
805
806   cim= ci;
807
808   FOR_COMMODITY_CELL({},({
809     adjust_colours_cell(ci,ri,colno,cell);
810   }),{});
811 }
812
813 void analyse(FILE *tsv_output) {
814   int page;
815
816   for (page=0; page<npages; page++) {
817     select_page(page);
818
819     if (!page)
820       check_correct_commodities();
821
822     if (!rd)
823       rd= ocr_init(text_h);
824
825     progress("Processing page %d...",page);
826
827     const char *tab= "";
828     
829     FOR_COMMODITY_CELL({
830       tab= "";
831     },{
832       fputs(tab, tsv_output);
833       ocr_rectangle(cell,
834                     colno<TEXT_COLUMNS
835                     ? &ocr_celltype_text
836                     : &ocr_celltype_number,
837                     tsv_output);
838       tab= "\t";
839     },{
840       fputs("\n", tsv_output);
841       sysassert(!ferror(tsv_output));
842       sysassert(!fflush(tsv_output));
843     })
844   }
845   progress("Commodity table scan complete.");
846 }
847
848 //static Rect islandnamer;
849
850 DEBUG_DEFINE_SOME_DEBUGF(structcolon,colondebugf)
851
852 Rect find_sunshine_widget(void) {
853   Rect sunshiner;
854
855   sunshiner.tl.x= cim->w - 1034 +  885;
856   sunshiner.br.x= cim->w - 1034 + 1020;
857   sunshiner.tl.y= 227;
858   sunshiner.br.y= 228;
859
860   ADJUST_BOX(sunshiner,"o*",>=,30, 100,MUST, tl,y,-1);
861   ADJUST_BOX(sunshiner,"o*",>=,30, 100,MUST, br,y,+1);
862   debug_rect("sunshiner",0, sunshiner);
863
864   MUST(sunshiner.br.y - sunshiner.tl.y > 20, MR(sunshiner));
865   sunshiner.br.y--;
866
867   ADJUST_BOX(sunshiner,"o",>=,20, (cim->w - 1034 + 700), MUST, tl,x,-1);
868   ADJUST_BOX(sunshiner,"o",>=,20,  cim->w,               MUST, br,x,+1);
869   debug_rect("sunshiner",1, sunshiner);
870   return sunshiner;
871 }
872
873 void find_islandname(void) {
874   const RgbImage *rgbsrc= page0_rgbimage;
875   select_page(0);
876
877   RgbImage *ri= alloc_rgb_image(rgbsrc->w, rgbsrc->h);
878   memcpy(ri->data, rgbsrc->data, ri->w * ri->h * 3);
879   
880   Rect sunshiner= find_sunshine_widget();
881   char sunshine[MAXIMGIDENT], archisland[MAXIMGIDENT];
882
883   const Rgb *srcp;
884   Rgb *destp, *endp;
885   for (srcp= rgbsrc->data, destp=ri->data,
886          endp= ri->data + ri->w * ri->h;
887        destp < endp;
888        srcp++, destp++) {
889     Rgb new= *srcp & 0xf0f0f0;
890     *destp= new | (new>>4);
891   }
892
893   identify_rgbimage(ri, sunshiner, sunshine, "sunshine widget");
894   
895   if (!memcmp(sunshine,"Vessel ",5)) {
896     Rect islandnamer;
897     
898     islandnamer.tl.x= cim->w - 1034 +  885;
899     islandnamer.br.x= cim->w - 1034 + 1020;
900     islandnamer.tl.y=                 128;
901     islandnamer.br.y=                 156;
902
903     ADJUST_BOX(islandnamer,"o",>=,5, 0,      MUST, tl,y,+1);
904     ADJUST_BOX(islandnamer,"o",>=,5, cim->h, MUST, br,y,-1);
905
906     ADJUST_BOX(islandnamer,"o",>=,1, 0,      MUST, tl,x,+1);
907     ADJUST_BOX(islandnamer,"o",>=,1, cim->w, MUST, br,x,-1);
908
909     debug_rect("islandnamer",0, islandnamer);
910 //    int larger_islandnamebry= islandnamer.tl.y + 25;
911 //    MUST(islandnamer.br.y < larger_islandnamebry,
912 //       MR(islandnamer);MI(larger_islandnamebry));
913 //    islandnamer.br.y = larger_islandnamebry;
914     debug_rect("islandnamer",1, islandnamer);
915
916     int x,y;
917     for (x=islandnamer.tl.x; x<=islandnamer.br.x; x++)
918       for (y=islandnamer.tl.y; y<=islandnamer.br.y; y++) {
919         if ((ri_rgb(ri,x,y) & 0xff) < 0x40) {
920           *RI_PIXEL32(ri,x,y)= 0;
921         }
922       }
923
924     identify_rgbimage(ri, islandnamer, archisland, "island");
925   } else if (!strcmp(sunshine,"Land - Ahoy!")) {
926     Rect islandnamer;
927
928     islandnamer.tl.x= (sunshiner.tl.x + sunshiner.br.x) / 2;
929     islandnamer.tl.y= sunshiner.tl.y + 100;
930     islandnamer.br= islandnamer.tl;
931     debug_rect("islandnamer",__LINE__, islandnamer);
932     
933     WALK_UNTIL_MUST(islandnamer.tl,y, -1, sunshiner.br.y, 'H');
934     WALK_UNTIL_MUST(islandnamer.tl,x, -1, 0,              'o');
935     WALK_UNTIL_MUST(islandnamer.br,x, +1, cim->w,         'o');
936     debug_rect("islandnamer",__LINE__, islandnamer);
937
938 #define RW (RECT_W(islandnamer))
939 #define RH (RECT_H(islandnamer))
940
941     ADJUST_BOX(islandnamer,"O",>=,RW-4, cim->h, MUST,br,y,+1);
942     debug_rect("islandnamer",__LINE__, islandnamer);
943
944     islandnamer.br.y += 2;
945
946     ADJUST_BOX(islandnamer,"*",<,RW, cim->h, MUST,br,y,+1);
947     debug_rect("islandnamer",__LINE__, islandnamer);
948
949     islandnamer.tl.y= islandnamer.br.y-1;
950     islandnamer.br.y= islandnamer.br.y+1;
951     debug_rect("islandnamer",__LINE__, islandnamer);
952
953     ADJUST_BOX(islandnamer,"*",>=,RW, cim->h, MUST,br,y,+1);
954     debug_rect("islandnamer",__LINE__, islandnamer);
955
956     ADJUST_BOX(islandnamer,"*",<, RH, cim->w, MUST,tl,x,+1);
957     debug_rect("islandnamer",__LINE__, islandnamer);
958
959     MUST( RECT_H(islandnamer) <= 30, MR(islandnamer));
960
961     Point p;
962     int nspaces=1, might_be_colon=0;
963     uint32_t colon_pattern= 0;
964     p.y=-1;
965
966     for (p.x=islandnamer.br.x; p.x>islandnamer.tl.x; p.x--) {
967       colondebugf("structcolon: x=%4d nsp=%2d mbc=%d cp=%08"PRIx32" ",
968                   p.x, nspaces, might_be_colon, colon_pattern);
969
970       uint32_t pattern=0;
971       int runs[32], nruns=0;
972       runs[0]=0; runs[1]=0;
973
974       find_aa_density_prep(0xCCCCAA,0x002255,0);
975       
976       for (p.y=islandnamer.tl.y; p.y<=islandnamer.br.y; p.y++) {
977         pattern <<= 1;
978         Fixpt alpha= find_aa_density(ri,p);
979         if (alpha >= dbl2fixpt(0.49)) {
980            runs[nruns]++;
981            pattern |= 1u;
982         } else {
983           if (runs[nruns]) {
984             nruns++;
985             runs[nruns]=0;
986           }
987         }
988       }
989
990       colondebugf(" pat=%08"PRIx32" nruns=%d runs[]={%d,%d..} ",
991                   pattern, nruns, runs[0],runs[1]);
992
993       if (!pattern) {
994         if (might_be_colon)
995           /* omg it _is_ a colon */
996           goto colon_found;
997         nspaces++;
998         might_be_colon=0;
999       } else {
1000         if (nruns==2 && runs[1]==runs[0]) {
1001           if (!nspaces) {
1002             if (pattern==colon_pattern)
1003               goto ok_might_be_colon;
1004           } else if (nspaces>=2) {
1005             colon_pattern= pattern;
1006             might_be_colon=1;
1007             goto ok_might_be_colon;
1008           }
1009         } else if (nruns==1 && runs[0]==1 && might_be_colon) {
1010           goto colon_found;
1011         }
1012         might_be_colon=0;
1013       ok_might_be_colon:
1014         nspaces= 0;
1015       }
1016       colondebugf(" nsp=%2d mbc=%d\n", nspaces, might_be_colon);
1017     }
1018     MUST(!"colon found", MP(p);MR(islandnamer) );
1019
1020   colon_found:
1021     colondebugf(" found\n");
1022     islandnamer.br.x= p.x;
1023
1024     identify_rgbimage(ri, islandnamer, archisland, "island");
1025   } else {
1026
1027     MUST(!"sunshine shows ship or ahoy", MS(sunshine) );
1028
1029   }
1030
1031   char *delim= strstr(archisland," - ");
1032   assert(delim);
1033   archipelago= masprintf("%.*s", (int)(delim-archisland), archisland);
1034   island= masprintf("%s", delim+3);
1035
1036 }
1037
1038 void check_pager_motion(int first, int stop) {
1039   /* We check that the pager moved by an appropriate amount.
1040    * The last gap can be smaller but not bigger.
1041    */
1042   int count= stop-first;
1043
1044 #define PH(p) (page_structs[(p)].pagerheight)
1045
1046   debugf("CHECK_PAGER_MOTION %d..%d count=%d\n", first,stop,count);
1047
1048   if (count <= 1) return; /* only one page */
1049
1050   double firstheight= PH(first);
1051   double max= count>2 ? firstheight / (count-2) : 1e6;
1052   double min=           firstheight / (count-1);
1053   max *= 1.1;
1054   min /= 1.1;
1055   max += 1.0;
1056   min -= 1.0;
1057   debugf("CHECK_PAGER_MOTION min=%g max=%g\n", min,max);
1058   assert(max>min);
1059
1060   int skips=0, firstskip=1;
1061   int stops=0, firststop=1;
1062
1063 #define REPORT(skipstop,msg) do{                        \
1064       skipstop##s++;                                    \
1065       if (first##skipstop<0) first##skipstop= page;     \
1066       if (skipstop##s<5)                                \
1067         fprintf(stderr,msg " (page %d)\n",page);        \
1068     }while(0)
1069
1070   int page;
1071   for (page=first+1; page<stop; page++) {
1072     int gap= PH(page-1) - PH(page);
1073     debugf("CHECK_PAGER_MOTION  page=%2d gap=%2d\n", page, gap);
1074     if (gap>max)
1075       REPORT(skip, "scrollbar motion probable page skip detected!");
1076     if (gap<min && page<stop-1)
1077       REPORT(stop, "scrollbar short motion gives lie to our ideas!");
1078   }
1079   MUST(!skips && !stops,
1080        MI(skips);MI(firstskip);MF(max);MI(PH(firstskip));MI(PH(firstskip-1));
1081        MI(stops);MI(firststop);MF(min);MI(PH(firststop));MI(PH(firststop-1));
1082        MF(firstheight);MI(PH(stop-2));MI(PH(stop-1));
1083        MSB(
1084 "\n\n"
1085 "Your YPP client seems to be paging irregularly.  Perhaps your computer\n"
1086 "is too slow or overloaded or perhaps you are messing with the mouse ?\n"
1087        ));
1088 }