1 /*
2     Scan Tailor - Interactive post-processing tool for scanned pages.
3     Copyright (C)  Joseph Artsimovich <joseph.artsimovich@gmail.com>
4 
5     This program is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "SeedFill.h"
20 #include <QDebug>
21 #include "GrayImage.h"
22 #include "SeedFillGeneric.h"
23 
24 namespace imageproc {
25 namespace {
fillWordHorizontally(uint32_t word,const uint32_t mask)26 inline uint32_t fillWordHorizontally(uint32_t word, const uint32_t mask) {
27   uint32_t prev_word;
28 
29   do {
30     prev_word = word;
31     word |= (word << 1) | (word >> 1);
32     word &= mask;
33   } while (word != prev_word);
34 
35   return word;
36 }
37 
seedFill4Iteration(BinaryImage & seed,const BinaryImage & mask)38 void seedFill4Iteration(BinaryImage& seed, const BinaryImage& mask) {
39   const int w = seed.width();
40   const int h = seed.height();
41 
42   const int seed_wpl = seed.wordsPerLine();
43   const int mask_wpl = mask.wordsPerLine();
44   const int last_word_idx = (w - 1) >> 5;
45   const uint32_t last_word_mask = ~uint32_t(0) << (((last_word_idx + 1) << 5) - w);
46 
47   uint32_t* seed_line = seed.data();
48   const uint32_t* mask_line = mask.data();
49   const uint32_t* prev_line = seed_line;
50 
51   // Top to bottom.
52   for (int y = 0; y < h; ++y) {
53     uint32_t prev_word = 0;
54 
55     // Make sure offscreen bits are 0.
56     seed_line[last_word_idx] &= last_word_mask;
57     // Left to right (except the last word).
58     for (int i = 0; i <= last_word_idx; ++i) {
59       const uint32_t mask = mask_line[i];
60       uint32_t word = prev_word << 31;
61       word |= seed_line[i] | prev_line[i];
62       word &= mask;
63       word = fillWordHorizontally(word, mask);
64       seed_line[i] = word;
65       prev_word = word;
66     }
67 
68     // If we don't do this, prev_line[last_word_idx] on the next
69     // iteration may contain garbage in the off-screen area.
70     // That garbage can easily leak back.
71     seed_line[last_word_idx] &= last_word_mask;
72 
73     prev_line = seed_line;
74     seed_line += seed_wpl;
75     mask_line += mask_wpl;
76   }
77 
78   seed_line -= seed_wpl;
79   mask_line -= mask_wpl;
80   prev_line = seed_line;
81 
82   // Bottom to top.
83   for (int y = h - 1; y >= 0; --y) {
84     uint32_t prev_word = 0;
85 
86     // Make sure offscreen bits area 0.
87     seed_line[last_word_idx] &= last_word_mask;
88     // Right to left.
89     for (int i = last_word_idx; i >= 0; --i) {
90       const uint32_t mask = mask_line[i];
91       uint32_t word = prev_word >> 31;
92       word |= seed_line[i] | prev_line[i];
93       word &= mask;
94       word = fillWordHorizontally(word, mask);
95       seed_line[i] = word;
96       prev_word = word;
97     }
98     // If we don't do this, prev_line[last_word_idx] on the next
99     // iteration may contain garbage in the off-screen area.
100     // That garbage can easily leak back.
101     // Fortunately, garbage can't spread through prev_word,
102     // as only 1 bit is used from it, which can't be garbage.
103     seed_line[last_word_idx] &= last_word_mask;
104 
105     prev_line = seed_line;
106     seed_line -= seed_wpl;
107     mask_line -= mask_wpl;
108   }
109 }  // seedFill4Iteration
110 
seedFill8Iteration(BinaryImage & seed,const BinaryImage & mask)111 void seedFill8Iteration(BinaryImage& seed, const BinaryImage& mask) {
112   const int w = seed.width();
113   const int h = seed.height();
114 
115   const int seed_wpl = seed.wordsPerLine();
116   const int mask_wpl = mask.wordsPerLine();
117   const int last_word_idx = (w - 1) >> 5;
118   const uint32_t last_word_mask = ~uint32_t(0) << (((last_word_idx + 1) << 5) - w);
119 
120   uint32_t* seed_line = seed.data();
121   const uint32_t* mask_line = mask.data();
122   const uint32_t* prev_line = seed_line;
123 
124   // Note: we start with prev_line == seed_line, but in this case
125   // prev_line[i + 1] won't be clipped by its mask when we use it to
126   // update seed_line[i].  The wrong value may propagate further from
127   // there, so clipping we do on the anti-raster pass won't help.
128   // That's why we clip the first line here.
129   for (int i = 0; i <= last_word_idx; ++i) {
130     seed_line[i] &= mask_line[i];
131   }
132 
133   // Top to bottom.
134   for (int y = 0; y < h; ++y) {
135     uint32_t prev_word = 0;
136 
137     // Make sure offscreen bits area 0.
138     seed_line[last_word_idx] &= last_word_mask;
139     // Left to right (except the last word).
140     int i = 0;
141     for (; i < last_word_idx; ++i) {
142       const uint32_t mask = mask_line[i];
143       uint32_t word = prev_line[i];
144       word |= (word << 1) | (word >> 1);
145       word |= seed_line[i];
146       word |= prev_line[i + 1] >> 31;
147       word |= prev_word << 31;
148       word &= mask;
149       word = fillWordHorizontally(word, mask);
150       seed_line[i] = word;
151       prev_word = word;
152     }
153     // Last word.
154     const uint32_t mask = mask_line[i] & last_word_mask;
155     uint32_t word = prev_line[i];
156     word |= (word << 1) | (word >> 1);
157     word |= seed_line[i];
158     word |= prev_word << 31;
159     word &= mask;
160     word = fillWordHorizontally(word, mask);
161     seed_line[i] = word;
162 
163     prev_line = seed_line;
164     seed_line += seed_wpl;
165     mask_line += mask_wpl;
166   }
167 
168   seed_line -= seed_wpl;
169   mask_line -= mask_wpl;
170   prev_line = seed_line;
171 
172   // Bottom to top.
173   for (int y = h - 1; y >= 0; --y) {
174     uint32_t prev_word = 0;
175 
176     // Make sure offscreen bits area 0.
177     seed_line[last_word_idx] &= last_word_mask;
178     // Right to left (except the last word).
179     int i = last_word_idx;
180     for (; i > 0; --i) {
181       const uint32_t mask = mask_line[i];
182       uint32_t word = prev_line[i];
183       word |= (word << 1) | (word >> 1);
184       word |= seed_line[i];
185       word |= prev_line[i - 1] << 31;
186       word |= prev_word >> 31;
187       word &= mask;
188       word = fillWordHorizontally(word, mask);
189       seed_line[i] = word;
190       prev_word = word;
191     }
192 
193     // Last word.
194     const uint32_t mask = mask_line[i];
195     uint32_t word = prev_line[i];
196     word |= (word << 1) | (word >> 1);
197     word |= seed_line[i];
198     word |= prev_word >> 31;
199     word &= mask;
200     word = fillWordHorizontally(word, mask);
201     seed_line[i] = word;
202     // If we don't do this, prev_line[last_word_idx] on the next
203     // iteration may contain garbage in the off-screen area.
204     // That garbage can easily leak back.
205     // Fortunately, garbage can't spread through prev_word,
206     // as only 1 bit is used from it, which can't be garbage.
207     seed_line[last_word_idx] &= last_word_mask;
208 
209     prev_line = seed_line;
210     seed_line -= seed_wpl;
211     mask_line -= mask_wpl;
212   }
213 }  // seedFill8Iteration
214 
lightest(uint8_t lhs,uint8_t rhs)215 inline uint8_t lightest(uint8_t lhs, uint8_t rhs) {
216   return lhs > rhs ? lhs : rhs;
217 }
218 
darkest(uint8_t lhs,uint8_t rhs)219 inline uint8_t darkest(uint8_t lhs, uint8_t rhs) {
220   return lhs < rhs ? lhs : rhs;
221 }
222 
darker_than(uint8_t lhs,uint8_t rhs)223 inline bool darker_than(uint8_t lhs, uint8_t rhs) {
224   return lhs < rhs;
225 }
226 
seedFillGrayHorLine(uint8_t * seed,const uint8_t * mask,const int line_len)227 void seedFillGrayHorLine(uint8_t* seed, const uint8_t* mask, const int line_len) {
228   assert(line_len > 0);
229 
230   *seed = lightest(*seed, *mask);
231 
232   for (int i = 1; i < line_len; ++i) {
233     ++seed;
234     ++mask;
235     *seed = lightest(*mask, darkest(*seed, seed[-1]));
236   }
237 
238   for (int i = 1; i < line_len; ++i) {
239     --seed;
240     --mask;
241     *seed = lightest(*mask, darkest(*seed, seed[1]));
242   }
243 }
244 
seedFillGrayVertLine(uint8_t * seed,const int seed_stride,const uint8_t * mask,const int mask_stride,const int line_len)245 void seedFillGrayVertLine(uint8_t* seed,
246                           const int seed_stride,
247                           const uint8_t* mask,
248                           const int mask_stride,
249                           const int line_len) {
250   assert(line_len > 0);
251 
252   *seed = lightest(*seed, *mask);
253 
254   for (int i = 1; i < line_len; ++i) {
255     seed += seed_stride;
256     mask += mask_stride;
257     *seed = lightest(*mask, darkest(*seed, seed[-seed_stride]));
258   }
259 
260   for (int i = 1; i < line_len; ++i) {
261     seed -= seed_stride;
262     mask -= mask_stride;
263     *seed = lightest(*mask, darkest(*seed, seed[seed_stride]));
264   }
265 }
266 
267 /**
268  * \return non-zero if more iterations are required, zero otherwise.
269  */
seedFillGray4SlowIteration(GrayImage & seed,const GrayImage & mask)270 uint8_t seedFillGray4SlowIteration(GrayImage& seed, const GrayImage& mask) {
271   const int w = seed.width();
272   const int h = seed.height();
273 
274   uint8_t* seed_line = seed.data();
275   const uint8_t* mask_line = mask.data();
276   const uint8_t* prev_line = seed_line;
277 
278   const int seed_stride = seed.stride();
279   const int mask_stride = mask.stride();
280 
281   uint8_t modified = 0;
282 
283   // Top to bottom.
284   for (int y = 0; y < h; ++y) {
285     uint8_t prev_pixel = 0xff;
286     // Left to right.
287     for (int x = 0; x < w; ++x) {
288       const uint8_t pixel = lightest(mask_line[x], darkest(prev_pixel, darkest(seed_line[x], prev_line[x])));
289       modified |= seed_line[x] ^ pixel;
290       seed_line[x] = pixel;
291       prev_pixel = pixel;
292     }
293 
294     prev_line = seed_line;
295     seed_line += seed_stride;
296     mask_line += mask_stride;
297   }
298 
299   seed_line -= seed_stride;
300   mask_line -= mask_stride;
301   prev_line = seed_line;
302 
303   // Bottom to top.
304   for (int y = h - 1; y >= 0; --y) {
305     uint8_t prev_pixel = 0xff;
306     // Right to left.
307     for (int x = w - 1; x >= 0; --x) {
308       const uint8_t pixel = lightest(mask_line[x], darkest(prev_pixel, darkest(seed_line[x], prev_line[x])));
309       modified |= seed_line[x] ^ pixel;
310       seed_line[x] = pixel;
311       prev_pixel = pixel;
312     }
313 
314     prev_line = seed_line;
315     seed_line -= seed_stride;
316     mask_line -= mask_stride;
317   }
318 
319   return modified;
320 }  // seedFillGray4SlowIteration
321 
322 /**
323  * \return non-zero if more iterations are required, zero otherwise.
324  */
seedFillGray8SlowIteration(GrayImage & seed,const GrayImage & mask)325 uint8_t seedFillGray8SlowIteration(GrayImage& seed, const GrayImage& mask) {
326   const int w = seed.width();
327   const int h = seed.height();
328 
329   uint8_t* seed_line = seed.data();
330   const uint8_t* mask_line = mask.data();
331   const uint8_t* prev_line = seed_line;
332 
333   const int seed_stride = seed.stride();
334   const int mask_stride = mask.stride();
335 
336   uint8_t modified = 0;
337 
338   // Some code below doesn't handle such cases.
339   if (w == 1) {
340     seedFillGrayVertLine(seed_line, seed_stride, mask_line, mask_stride, h);
341 
342     return 0;
343   } else if (h == 1) {
344     seedFillGrayHorLine(seed_line, mask_line, w);
345 
346     return 0;
347   }
348 
349   // The prev_line[x + 1] below actually refers to seed_line[x + 1]
350   // for the first line in raster order.  When working with seed_line[x],
351   // seed_line[x + 1] would not yet be clipped by its mask.  So, we
352   // have to do it now.
353   for (int x = 0; x < w; ++x) {
354     seed_line[x] = lightest(seed_line[x], mask_line[x]);
355   }
356 
357   // Top to bottom.
358   for (int y = 0; y < h; ++y) {
359     int x = 0;
360     // Leftmost pixel.
361     uint8_t pixel = lightest(mask_line[x], darkest(seed_line[x], darkest(prev_line[x], prev_line[x + 1])));
362     modified |= seed_line[x] ^ pixel;
363     seed_line[x] = pixel;
364     // Left to right.
365     while (++x < w - 1) {
366       pixel
367           = lightest(mask_line[x],
368                      darkest(darkest(darkest(seed_line[x], seed_line[x - 1]), darkest(prev_line[x], prev_line[x - 1])),
369                              prev_line[x + 1]));
370       modified |= seed_line[x] ^ pixel;
371       seed_line[x] = pixel;
372     }
373     // Rightmost pixel.
374     pixel = lightest(mask_line[x],
375                      darkest(darkest(seed_line[x], seed_line[x - 1]), darkest(prev_line[x], prev_line[x - 1])));
376     modified |= seed_line[x] ^ pixel;
377     seed_line[x] = pixel;
378 
379     prev_line = seed_line;
380     seed_line += seed_stride;
381     mask_line += mask_stride;
382   }
383 
384   seed_line -= seed_stride;
385   mask_line -= mask_stride;
386   prev_line = seed_line;
387 
388   // Bottom to top.
389   for (int y = h - 1; y >= 0; --y) {
390     int x = w - 1;
391     // Rightmost pixel.
392     uint8_t pixel = lightest(mask_line[x], darkest(seed_line[x], darkest(prev_line[x], prev_line[x - 1])));
393     modified |= seed_line[x] ^ pixel;
394     seed_line[x] = pixel;
395     // Right to left.
396     while (--x > 0) {
397       pixel
398           = lightest(mask_line[x],
399                      darkest(darkest(darkest(seed_line[x], seed_line[x + 1]), darkest(prev_line[x], prev_line[x + 1])),
400                              prev_line[x - 1]));
401       modified |= seed_line[x] ^ pixel;
402       seed_line[x] = pixel;
403     }
404     // Leftmost pixel.
405     pixel = lightest(mask_line[x],
406                      darkest(darkest(seed_line[x], seed_line[x + 1]), darkest(prev_line[x], prev_line[x + 1])));
407     modified |= seed_line[x] ^ pixel;
408     seed_line[x] = pixel;
409 
410     prev_line = seed_line;
411     seed_line -= seed_stride;
412     mask_line -= mask_stride;
413   }
414 
415   return modified;
416 }  // seedFillGray8SlowIteration
417 }  // namespace
418 
seedFill(const BinaryImage & seed,const BinaryImage & mask,const Connectivity connectivity)419 BinaryImage seedFill(const BinaryImage& seed, const BinaryImage& mask, const Connectivity connectivity) {
420   if (seed.size() != mask.size()) {
421     throw std::invalid_argument("seedFill: seed and mask have different sizes");
422   }
423 
424   BinaryImage prev;
425   BinaryImage img(seed);
426 
427   do {
428     prev = img;
429     if (connectivity == CONN4) {
430       seedFill4Iteration(img, mask);
431     } else {
432       seedFill8Iteration(img, mask);
433     }
434   } while (img != prev);
435 
436   return img;
437 }
438 
seedFillGray(const GrayImage & seed,const GrayImage & mask,const Connectivity connectivity)439 GrayImage seedFillGray(const GrayImage& seed, const GrayImage& mask, const Connectivity connectivity) {
440   GrayImage result(seed);
441   seedFillGrayInPlace(result, mask, connectivity);
442 
443   return result;
444 }
445 
seedFillGrayInPlace(GrayImage & seed,const GrayImage & mask,const Connectivity connectivity)446 void seedFillGrayInPlace(GrayImage& seed, const GrayImage& mask, const Connectivity connectivity) {
447   if (seed.size() != mask.size()) {
448     throw std::invalid_argument("seedFillGrayInPlace: seed and mask have different sizes");
449   }
450 
451   if (seed.isNull()) {
452     return;
453   }
454 
455   seedFillGenericInPlace(&darkest, &lightest, connectivity, seed.data(), seed.stride(), seed.size(), mask.data(),
456                          mask.stride());
457 }
458 
seedFillGraySlow(const GrayImage & seed,const GrayImage & mask,const Connectivity connectivity)459 GrayImage seedFillGraySlow(const GrayImage& seed, const GrayImage& mask, const Connectivity connectivity) {
460   GrayImage img(seed);
461 
462   if (connectivity == CONN4) {
463     while (seedFillGray4SlowIteration(img, mask)) {
464       // Continue until done.
465     }
466   } else {
467     while (seedFillGray8SlowIteration(img, mask)) {
468       // Continue until done.
469     }
470   }
471 
472   return img;
473 }
474 }  // namespace imageproc