1f245676bf0236598497349eaa7c045c288c912b
[swftools.git] / lib / pdf / bbox.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <memory.h>
4 #include <math.h>
5 #include <assert.h>
6 #include "../types.h"
7 #include "../mem.h"
8
9 typedef struct _ibbox {
10     int xmin,ymin,xmax,ymax;
11     struct _ibbox*next;
12 } ibbox_t;
13
14 ibbox_t* ibbox_new(int x1, int y1, int x2, int y2)
15 {
16     ibbox_t*b = (ibbox_t*)rfx_calloc(sizeof(ibbox_t)); 
17     b->xmin = x1;
18     b->ymin = y1;
19     b->xmax = x2;
20     b->ymax = y2;
21     return b;
22 }
23
24 void ibbox_destroy(ibbox_t*b)
25 {
26     while(b) {
27         ibbox_t*next = b->next;
28         free(b);
29         b = next;
30     }
31 }
32
33 ibbox_t*get_bitmap_bboxes_simple(unsigned char*alpha, int width, int height)
34 {
35     int ymin = -1;
36     int ymax = -1;
37     int xmin = width;
38     int xmax = 0;
39
40     int x,y;
41     for(y=0;y<height;y++) {
42         unsigned char*a = &alpha[y*width];
43         for(x=0;x<width;x++) {
44             if(a[x]) break;
45         }
46         int left = x; //first occupied pixel from left
47         int right = x+1; //last non-occupied pixel from right
48         for(;x<width;x++) {
49             if(a[x]) right=x+1;
50         }
51
52         if(left!=width) {
53             if(ymin<0) 
54                 ymin=y;
55             ymax=y+1;
56             if(left<xmin) xmin = left;
57             if(right>xmax) xmax = right;
58         }
59     }
60     ibbox_t* bbox = 0;
61     if(xmin<xmax || ymin<ymax) {
62         bbox = ibbox_new(xmin, ymin, xmax, ymax);
63     }
64     return bbox;
65 }
66
67 typedef struct _head {
68     ptroff_t magic;
69     ibbox_t bbox;
70     int nr;
71     int pos;
72     int rank;
73     int x,y;
74     char seen;
75     struct _head*next;
76     struct _head*prev;
77 } head_t;
78
79 typedef struct _context {
80     void**group;
81     unsigned char*alpha;
82     int width;
83     int height;
84     head_t*heads;
85     int count;
86 } context_t;
87
88 #define HEAD_MAGIC ((ptroff_t)-1)
89
90 static head_t*head_new(context_t*context, int x, int y)
91 {
92     int pos = context->width*y+x;
93     head_t*h = rfx_calloc(sizeof(head_t));
94     h->magic = HEAD_MAGIC;
95     h->nr = context->count++;
96     h->pos = pos;
97     h->x = x;
98     h->y = y;
99     h->bbox.xmin = h->bbox.xmax = x;
100     h->bbox.ymin = h->bbox.ymax = y;
101     h->next = context->heads;
102     context->heads = h;
103     if(h->next) {
104         h->next->prev = h;
105     }
106     return h;
107 }
108
109 static void head_delete(context_t*context, head_t*h)
110 {
111     if(h->prev) {
112         h->prev->next = h->next;
113     }
114     if(h->next) {
115         h->next->prev = h->prev;
116     }
117     if(h==context->heads) {
118         assert(!h->prev);
119         context->heads = h->next;
120     }
121     free(h);
122 }
123
124 #define POINTS_TO_HEAD(ptr) (((head_t*)(ptr))->magic==HEAD_MAGIC)
125
126 static inline void link_to(context_t*context, int from, int to)
127 {
128     // path compression
129     void**data = context->group;
130     int head = to;
131     assert(data[head]);
132     while(!POINTS_TO_HEAD(data[head])) {
133         assert(data[head]!=(void*)&data[head]); // check that we're not in an infinite loop
134         head=(void**)data[head]-(void**)data;
135     }
136     head_t*h = (head_t*)data[head];
137     int x = from%context->width;
138     int y = from/context->width;
139     if(x < h->bbox.xmin) h->bbox.xmin = x;
140     if(y < h->bbox.ymin) h->bbox.ymin = y;
141     if(x > h->bbox.xmax) h->bbox.xmax = x;
142     if(y > h->bbox.ymax) h->bbox.ymax = y;
143     
144     data[from] = (void*)&data[head];
145 }
146 static char ibbox_does_overlap(ibbox_t*b1, ibbox_t*b2)
147 {
148     if(b1->xmax < b2->xmin) return 0;
149     if(b2->xmax < b1->xmin) return 0;
150     if(b1->ymax < b2->ymin) return 0;
151     if(b2->ymax < b1->ymin) return 0;
152     return 1;
153 }
154 static void ibbox_expand(ibbox_t*src, ibbox_t*add) 
155 {
156     if(add->xmin < src->xmin)
157         src->xmin = add->xmin;
158     if(add->ymin < src->ymin)
159         src->ymin = add->ymin;
160     if(add->xmax > src->xmax)
161         src->xmax = add->xmax;
162     if(add->ymax > src->ymax)
163         src->ymax = add->ymax;
164 }
165 static inline void merge(context_t*context, int set1, int set2)
166 {
167     void**data = context->group;
168     assert(data[set1]);
169     assert(data[set2]);
170     int head1 = set1;
171     int head2 = set2;
172     while(!POINTS_TO_HEAD(data[head1])) {
173         head1=(void**)data[head1]-(void**)data;
174     }
175     while(!POINTS_TO_HEAD(data[head2])) {
176         head2=(void**)data[head2]-(void**)data;
177     }
178     head_t*h1 = (head_t*)data[head1];
179     head_t*h2 = (head_t*)data[head2];
180     if(h1==h2)
181         return;
182
183     if(h1->rank>h2->rank) {
184         h1->rank++;
185         ibbox_expand(&h1->bbox,&h2->bbox);
186         data[head2] = (void*)&data[head1];
187         head_delete(context, h2);
188     } else {
189         h2->rank++;
190         ibbox_expand(&h2->bbox,&h1->bbox);
191         data[head1] = (void*)&data[head2];
192         head_delete(context, h1);
193     }
194 }
195
196 static void** annotate(context_t*context)
197 {
198     unsigned char*alpha = context->alpha;
199     int width = context->width;
200     int height = context->height;
201     void** group = rfx_calloc(width*height*sizeof(void*));
202     context->group = group;
203     int x,y;
204
205     for(x=1;x<width;x++) {
206         if(alpha[x]) {
207             if(group[x-1])
208                 link_to(context,x,x-1);
209             else
210                 group[x]=head_new(context,x,0);
211         }
212     }
213     int pos = 0;
214     for(y=1;y<height;y++) {
215         pos += width;
216         if(alpha[pos]) {
217             if(group[pos-width])
218                 link_to(context,pos,pos-width);
219             else
220                 group[pos]=head_new(context,0,y);
221         }
222         for(x=1;x<width;x++) {
223             /* once this code is stable we should copy&paste it
224                out of the loop, change the loop end to width-1 and
225                add the pos-width+1 case */
226             if(alpha[pos+x]) {
227                 if(group[pos+x-width]) {
228                     link_to(context,pos+x,pos+x-width);
229                     if(group[pos+x-1])
230                         merge(context,pos+x,pos+x-1);
231                 } else if(group[pos+x-1]) {
232                     link_to(context,pos+x,pos+x-1);
233                 } else if(group[pos+x-width-1]) {
234                     link_to(context,pos+x,pos+x-width-1);
235                 } else {
236                     group[pos+x]=head_new(context,x,y);
237                 }
238             }
239         }
240     }
241     return group;
242 }
243
244 static void overlap_bboxes(context_t*context)
245 {
246     char changed;
247     do {
248         head_t*h1 = context->heads;
249         changed = 0;
250         while(h1) {
251             head_t*next = h1->next;
252             head_t*h2 = context->heads;
253             while(h2) {
254                 if(h1!=h2) {
255                     if(ibbox_does_overlap(&h1->bbox, &h2->bbox)) {
256                         merge(context, h1->pos, h2->pos);
257                         changed = 1;
258                         break;
259                     }
260                 }
261                 h2 = h2->next;
262             }
263             h1 = next;
264         }
265     } while(changed);
266 }
267
268 typedef struct _circle_coord {
269     S16 x,y;
270 } circle_coord_t;
271
272 static int compare_circle_coord(const void *_v1, const void *_v2)
273 {
274     circle_coord_t*v1=(circle_coord_t*)_v1;
275     circle_coord_t*v2=(circle_coord_t*)_v2;
276     return (v1->x*v1->x + v1->y*v1->y) - (v2->x*v2->x + v2->y*v2->y);
277 }
278
279 static head_t* search_vicinity(context_t*context, head_t*h, int max_radius, double*cos, double*sin)
280 {
281     static circle_coord_t*circle_order = 0;
282     static int circle_order_size = 0;
283     
284     if(!circle_order) {
285         circle_order_size = (max_radius*(max_radius+1))/2;
286         circle_order = malloc(sizeof(circle_coord_t)*circle_order_size);
287         int x,y;
288         int i = 0;
289         for(y=0;y<max_radius;y++) {
290             for(x=0;x<=y;x++) {
291                 circle_order[i].x=x;
292                 circle_order[i].y=y;
293                 i++;
294             }
295         }
296         assert(i==circle_order_size);
297         qsort(circle_order, circle_order_size, sizeof(circle_coord_t), compare_circle_coord);
298     }
299
300     int t;
301     void**data = context->group;
302     int signx[4] = {-1,1,-1,1};
303     int signy[4] = {-1,-1,1,1};
304     for(t=1;t<circle_order_size;t++) {
305         int xx = circle_order[t].x;
306         int yy = circle_order[t].y;
307         int s;
308         for(s=0;s<4;s++) {
309             int x=h->x+xx*signx[s];
310             int y=h->y+yy*signy[s];
311             if(x>=0 && y>=0 && x<context->width && y<context->height) {
312                 int pos = y*context->width+x;
313                 if(data[pos]) {
314                     while(!POINTS_TO_HEAD(data[pos])) {
315                         pos=(void**)data[pos]-(void**)data;
316                     }
317                     head_t*new_head = (head_t*)data[pos];
318                     if(new_head != h) {
319                         return new_head;
320                     }
321                 }
322             }
323         }
324     }
325     return 0;
326 }
327
328 static void fix_small_boxes(context_t*context)
329 {
330     double sintab[256];
331     double costab[256];
332     int t;
333     for(t=0;t<256;t++) {
334         sintab[t] = sin(t*M_PI/128);
335         costab[t] = cos(t*M_PI/128);
336     }
337         
338     head_t*h = context->heads;
339     while(h) {
340         h->seen = 0;
341         h = h->next;
342     }
343
344     char changed;
345     do {
346         changed = 0;
347         head_t*h = context->heads;
348         while(h) {
349             head_t*next = h->next;
350             if(!h->seen) {
351                 if(h->bbox.xmax - h->bbox.xmin < 32
352                 || h->bbox.ymax - h->bbox.ymin < 32) {
353                     head_t*other = search_vicinity(context, h, 64, costab, sintab);
354                     if(other) {
355                         merge(context, h->pos, other->pos);
356                         changed = 1;
357                         break;
358                     } else {
359                         //printf("nothing in the vicinity of %d,%d,%d,%d\n", h->bbox);
360                         h->seen = 1;
361                     }
362                 } /*else {
363                     printf("area %d,%d,%d,%d is large enough (%dx%d)\n", 
364                             h->bbox.xmin,
365                             h->bbox.ymin,
366                             h->bbox.xmax,
367                             h->bbox.ymax,
368                             h->bbox.xmax - h->bbox.xmin,
369                             h->bbox.ymax - h->bbox.ymin);
370                 } */
371             }
372             h = next;
373         }
374     } while(changed);
375 }
376
377 static void display(context_t*context)
378 {
379     int width = context->width;
380     int height = context->height;
381     void**group = context->group;
382
383     int x,y;
384     for(y=0;y<height;y++) {
385         for(x=0;x<width;x++) {
386             if(!group[y*width+x]) {
387                 printf(" -- ");
388             } else if(POINTS_TO_HEAD(group[y*width+x])) {
389                 printf("g%02d ", ((head_t*)group[y*width+x])->nr);
390             } else {
391                 printf("x%02d ", (void**)group[y*width+x]-(void**)group);
392             }
393         }
394         printf("\n");
395     }
396
397     head_t*h = context->heads;
398     while(h) {
399         printf("head: %d\n", h->nr);
400         printf(" pos: %d/%d\n", h->pos%width, h->pos/width);
401         printf(" bbox: [%d/%d,%d/%d]\n", h->bbox.xmin, h->bbox.ymin, h->bbox.xmax, h->bbox.ymax);
402         h = h->next;
403     }
404 }
405
406 ibbox_t*get_bitmap_bboxes(unsigned char*alpha, int width, int height)
407 {
408     int size = width*height;
409     if(width<=1 || height<=1)
410         return get_bitmap_bboxes_simple(alpha, width, height);
411     
412     context_t context;
413     context.alpha = alpha;
414     context.width = width;
415     context.height = height;
416     context.heads = 0;
417     context.count = 1;
418
419     void**group = annotate(&context);
420     fix_small_boxes(&context);
421     overlap_bboxes(&context);
422 #ifdef MAIN
423     display(&context);
424 #endif
425
426     ibbox_t*bboxes = 0;
427
428     head_t*h = context.heads;
429     while(h) {
430         head_t*next = h->next;
431         ibbox_t*bbox = malloc(sizeof(ibbox_t));
432         memcpy(bbox, &h->bbox, sizeof(ibbox_t));
433
434         /* ibbox_t defines the open upper bound */
435         bbox->xmax++;
436         bbox->ymax++;
437
438         bbox->next = bboxes;
439         bboxes = bbox;
440         free(h);
441         h = next;
442     }
443     free(context.group);
444     return bboxes;
445 }
446
447 #ifdef MAIN
448 int main(int argn, char*argv[])
449 {
450     unsigned char alpha[8*8]=
451         "\0\0\1\0\0\0\0\0"
452         "\1\0\0\1\0\1\0\0"
453         "\0\0\0\0\0\0\1\0"
454         "\0\0\1\0\1\0\0\0"
455         "\1\0\1\0\1\0\0\0"
456         "\1\0\1\1\1\0\0\1"
457         "\1\0\0\0\0\0\1\0"
458         "\1\1\1\0\0\0\0\0";
459
460     get_bitmap_bboxes(alpha, 8,8);
461 }
462 #endif