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