1 | // Aseprite Document Library |
2 | // Copyright (C) 2019-2022 Igara Studio S.A. |
3 | // Copyright (C) 2001-2018 David Capello |
4 | // |
5 | // This file is released under the terms of the MIT license. |
6 | // Read LICENSE.txt for more information. |
7 | |
8 | #ifdef HAVE_CONFIG_H |
9 | #include "config.h" |
10 | #endif |
11 | |
12 | #include "doc/mask.h" |
13 | |
14 | #include "base/memory.h" |
15 | #include "doc/image_impl.h" |
16 | |
17 | #include <cstdlib> |
18 | #include <cstring> |
19 | |
20 | namespace doc { |
21 | |
22 | namespace { |
23 | |
24 | template<typename Func> |
25 | void for_each_mask_pixel(Mask& a, const Mask& b, Func f) { |
26 | a.reserve(b.bounds()); |
27 | |
28 | { |
29 | LockImageBits<BitmapTraits> aBits(a.bitmap()); |
30 | auto aIt = aBits.begin(); |
31 | |
32 | auto bounds = a.bounds(); |
33 | for (int y=0; y<bounds.h; ++y) { |
34 | for (int x=0; x<bounds.w; ++x, ++aIt) { |
35 | color_t aColor = *aIt; |
36 | color_t bColor = (b.containsPoint(bounds.x+x, |
37 | bounds.y+y) ? 1: 0); |
38 | *aIt = f(aColor, bColor); |
39 | } |
40 | } |
41 | } |
42 | |
43 | a.shrink(); |
44 | } |
45 | |
46 | } // namespace namespace |
47 | |
48 | Mask::Mask() |
49 | : Object(ObjectType::Mask) |
50 | { |
51 | initialize(); |
52 | } |
53 | |
54 | Mask::Mask(const Mask& mask) |
55 | : Object(mask) |
56 | { |
57 | initialize(); |
58 | copyFrom(&mask); |
59 | } |
60 | |
61 | Mask::~Mask() |
62 | { |
63 | ASSERT(m_freeze_count == 0); |
64 | } |
65 | |
66 | void Mask::initialize() |
67 | { |
68 | m_freeze_count = 0; |
69 | m_bounds = gfx::Rect(0, 0, 0, 0); |
70 | } |
71 | |
72 | int Mask::getMemSize() const |
73 | { |
74 | return sizeof(Mask) + (m_bitmap ? m_bitmap->getMemSize(): 0); |
75 | } |
76 | |
77 | void Mask::setName(const char *name) |
78 | { |
79 | m_name = name; |
80 | } |
81 | |
82 | void Mask::freeze() |
83 | { |
84 | ASSERT(m_freeze_count >= 0); |
85 | m_freeze_count++; |
86 | } |
87 | |
88 | void Mask::unfreeze() |
89 | { |
90 | ASSERT(m_freeze_count > 0); |
91 | m_freeze_count--; |
92 | |
93 | // Shrink just in case |
94 | if (m_freeze_count == 0) |
95 | shrink(); |
96 | } |
97 | |
98 | bool Mask::isRectangular() const |
99 | { |
100 | if (!m_bitmap) |
101 | return false; |
102 | |
103 | LockImageBits<BitmapTraits> bits(m_bitmap.get()); |
104 | LockImageBits<BitmapTraits>::iterator it = bits.begin(), end = bits.end(); |
105 | |
106 | for (; it != end; ++it) { |
107 | if (*it == 0) |
108 | return false; |
109 | } |
110 | |
111 | return true; |
112 | } |
113 | |
114 | void Mask::copyFrom(const Mask* sourceMask) |
115 | { |
116 | ASSERT(m_freeze_count == 0); |
117 | |
118 | clear(); |
119 | setName(sourceMask->name().c_str()); |
120 | |
121 | if (sourceMask->bitmap()) { |
122 | // Add all the area of "mask" |
123 | add(sourceMask->bounds()); |
124 | |
125 | // And copy the "mask" bitmap (m_bitmap can be nullptr if this is |
126 | // frozen, so add() doesn't created the bitmap) |
127 | if (m_bitmap) |
128 | copy_image(m_bitmap.get(), sourceMask->m_bitmap.get()); |
129 | } |
130 | } |
131 | |
132 | void Mask::offsetOrigin(int dx, int dy) |
133 | { |
134 | m_bounds.offset(dx, dy); |
135 | } |
136 | |
137 | void Mask::clear() |
138 | { |
139 | m_bitmap.reset(); |
140 | m_bounds = gfx::Rect(0, 0, 0, 0); |
141 | } |
142 | |
143 | void Mask::invert() |
144 | { |
145 | if (!m_bitmap) |
146 | return; |
147 | |
148 | LockImageBits<BitmapTraits> bits(m_bitmap.get()); |
149 | LockImageBits<BitmapTraits>::iterator it = bits.begin(), end = bits.end(); |
150 | |
151 | for (; it != end; ++it) |
152 | *it = (*it ? 0: 1); |
153 | |
154 | shrink(); |
155 | } |
156 | |
157 | void Mask::replace(const gfx::Rect& bounds) |
158 | { |
159 | if (bounds.isEmpty()) { |
160 | clear(); |
161 | return; |
162 | } |
163 | |
164 | m_bounds = bounds; |
165 | |
166 | m_bitmap.reset(Image::create(IMAGE_BITMAP, bounds.w, bounds.h, m_buffer)); |
167 | clear_image(m_bitmap.get(), 1); |
168 | } |
169 | |
170 | void Mask::add(const doc::Mask& mask) |
171 | { |
172 | for_each_mask_pixel( |
173 | *this, mask, |
174 | [](color_t a, color_t b) -> color_t { |
175 | return a | b; |
176 | }); |
177 | } |
178 | |
179 | void Mask::subtract(const doc::Mask& mask) |
180 | { |
181 | for_each_mask_pixel( |
182 | *this, mask, |
183 | [](color_t a, color_t b) -> color_t { |
184 | if (a) |
185 | return a - b; |
186 | else |
187 | return 0; |
188 | }); |
189 | } |
190 | |
191 | void Mask::intersect(const doc::Mask& mask) |
192 | { |
193 | for_each_mask_pixel( |
194 | *this, mask, |
195 | [](color_t a, color_t b) -> color_t { |
196 | return a & b; |
197 | }); |
198 | } |
199 | |
200 | void Mask::add(const gfx::Rect& bounds) |
201 | { |
202 | if (m_freeze_count == 0) |
203 | reserve(bounds); |
204 | |
205 | // m_bitmap can be nullptr if we have m_freeze_count > 0 |
206 | if (!m_bitmap) |
207 | return; |
208 | |
209 | fill_rect(m_bitmap.get(), |
210 | bounds.x-m_bounds.x, |
211 | bounds.y-m_bounds.y, |
212 | bounds.x-m_bounds.x+bounds.w-1, |
213 | bounds.y-m_bounds.y+bounds.h-1, 1); |
214 | } |
215 | |
216 | void Mask::subtract(const gfx::Rect& bounds) |
217 | { |
218 | if (!m_bitmap) |
219 | return; |
220 | |
221 | fill_rect(m_bitmap.get(), |
222 | bounds.x-m_bounds.x, |
223 | bounds.y-m_bounds.y, |
224 | bounds.x-m_bounds.x+bounds.w-1, |
225 | bounds.y-m_bounds.y+bounds.h-1, 0); |
226 | |
227 | shrink(); |
228 | } |
229 | |
230 | void Mask::intersect(const gfx::Rect& bounds) |
231 | { |
232 | if (!m_bitmap) |
233 | return; |
234 | |
235 | gfx::Rect newBounds = m_bounds.createIntersection(bounds); |
236 | |
237 | Image* image = NULL; |
238 | |
239 | if (!newBounds.isEmpty()) { |
240 | image = crop_image( |
241 | m_bitmap.get(), |
242 | newBounds.x-m_bounds.x, |
243 | newBounds.y-m_bounds.y, |
244 | newBounds.w, |
245 | newBounds.h, 0); |
246 | } |
247 | |
248 | m_bitmap.reset(image); |
249 | m_bounds = newBounds; |
250 | |
251 | shrink(); |
252 | } |
253 | |
254 | void Mask::byColor(const Image *src, int color, int fuzziness) |
255 | { |
256 | replace(src->bounds()); |
257 | |
258 | Image* dst = m_bitmap.get(); |
259 | |
260 | switch (src->pixelFormat()) { |
261 | |
262 | case IMAGE_RGB: { |
263 | const LockImageBits<RgbTraits> srcBits(src); |
264 | LockImageBits<BitmapTraits> dstBits(dst, Image::WriteLock); |
265 | LockImageBits<RgbTraits>::const_iterator src_it = srcBits.begin(), src_end = srcBits.end(); |
266 | LockImageBits<BitmapTraits>::iterator dst_it = dstBits.begin(); |
267 | #ifdef _DEBUG |
268 | LockImageBits<BitmapTraits>::iterator dst_end = dstBits.end(); |
269 | #endif |
270 | int src_r, src_g, src_b, src_a; |
271 | int dst_r, dst_g, dst_b, dst_a; |
272 | color_t c; |
273 | |
274 | dst_r = rgba_getr(color); |
275 | dst_g = rgba_getg(color); |
276 | dst_b = rgba_getb(color); |
277 | dst_a = rgba_geta(color); |
278 | |
279 | for (; src_it != src_end; ++src_it, ++dst_it) { |
280 | ASSERT(dst_it != dst_end); |
281 | c = *src_it; |
282 | |
283 | src_r = rgba_getr(c); |
284 | src_g = rgba_getg(c); |
285 | src_b = rgba_getb(c); |
286 | src_a = rgba_geta(c); |
287 | |
288 | if (!((src_r >= dst_r-fuzziness) && (src_r <= dst_r+fuzziness) && |
289 | (src_g >= dst_g-fuzziness) && (src_g <= dst_g+fuzziness) && |
290 | (src_b >= dst_b-fuzziness) && (src_b <= dst_b+fuzziness) && |
291 | (src_a >= dst_a-fuzziness) && (src_a <= dst_a+fuzziness))) |
292 | *dst_it = 0; |
293 | } |
294 | ASSERT(dst_it == dst_end); |
295 | break; |
296 | } |
297 | |
298 | case IMAGE_GRAYSCALE: { |
299 | const LockImageBits<GrayscaleTraits> srcBits(src); |
300 | LockImageBits<BitmapTraits> dstBits(dst, Image::WriteLock); |
301 | LockImageBits<GrayscaleTraits>::const_iterator src_it = srcBits.begin(), src_end = srcBits.end(); |
302 | LockImageBits<BitmapTraits>::iterator dst_it = dstBits.begin(); |
303 | #ifdef _DEBUG |
304 | LockImageBits<BitmapTraits>::iterator dst_end = dstBits.end(); |
305 | #endif |
306 | int src_k, src_a; |
307 | int dst_k, dst_a; |
308 | color_t c; |
309 | |
310 | dst_k = graya_getv(color); |
311 | dst_a = graya_geta(color); |
312 | |
313 | for (; src_it != src_end; ++src_it, ++dst_it) { |
314 | ASSERT(dst_it != dst_end); |
315 | c = *src_it; |
316 | |
317 | src_k = graya_getv(c); |
318 | src_a = graya_geta(c); |
319 | |
320 | if (!((src_k >= dst_k-fuzziness) && (src_k <= dst_k+fuzziness) && |
321 | (src_a >= dst_a-fuzziness) && (src_a <= dst_a+fuzziness))) |
322 | *dst_it = 0; |
323 | } |
324 | ASSERT(dst_it == dst_end); |
325 | break; |
326 | } |
327 | |
328 | case IMAGE_INDEXED: { |
329 | const LockImageBits<IndexedTraits> srcBits(src); |
330 | LockImageBits<BitmapTraits> dstBits(dst, Image::WriteLock); |
331 | LockImageBits<IndexedTraits>::const_iterator src_it = srcBits.begin(), src_end = srcBits.end(); |
332 | LockImageBits<BitmapTraits>::iterator dst_it = dstBits.begin(); |
333 | #ifdef _DEBUG |
334 | LockImageBits<BitmapTraits>::iterator dst_end = dstBits.end(); |
335 | #endif |
336 | color_t c, min, max; |
337 | |
338 | for (; src_it != src_end; ++src_it, ++dst_it) { |
339 | ASSERT(dst_it != dst_end); |
340 | c = *src_it; |
341 | |
342 | if (color > fuzziness) |
343 | min = color-fuzziness; |
344 | else |
345 | min = 0; |
346 | max = color + fuzziness; |
347 | |
348 | if (!((c >= min) && (c <= max))) |
349 | *dst_it = 0; |
350 | } |
351 | ASSERT(dst_it == dst_end); |
352 | break; |
353 | } |
354 | } |
355 | |
356 | shrink(); |
357 | } |
358 | |
359 | void Mask::crop(const Image *image) |
360 | { |
361 | #define ADVANCE(beg, end, o_end, cmp, op, getpixel1, getpixel) \ |
362 | { \ |
363 | done = true; \ |
364 | for (beg=beg_##beg; beg cmp beg_##end; beg op) { \ |
365 | old_color = getpixel1; \ |
366 | done = true; \ |
367 | for (c++; c<=beg_##o_end; c++) { \ |
368 | if (getpixel != old_color) { \ |
369 | done = false; \ |
370 | break; \ |
371 | } \ |
372 | } \ |
373 | if (!done) \ |
374 | break; \ |
375 | } \ |
376 | if (done) \ |
377 | done_count++; \ |
378 | } |
379 | |
380 | int beg_x1, beg_y1, beg_x2, beg_y2; |
381 | int c, x1, y1, x2, y2; |
382 | int done_count = 0; |
383 | int done; |
384 | color_t old_color; |
385 | |
386 | if (!m_bitmap) |
387 | return; |
388 | |
389 | beg_x1 = m_bounds.x; |
390 | beg_y1 = m_bounds.y; |
391 | beg_x2 = beg_x1 + m_bounds.w - 1; |
392 | beg_y2 = beg_y1 + m_bounds.h - 1; |
393 | |
394 | beg_x1 = std::clamp(beg_x1, 0, m_bounds.w-1); |
395 | beg_y1 = std::clamp(beg_y1, 0, m_bounds.h-1); |
396 | beg_x2 = std::clamp(beg_x2, beg_x1, m_bounds.w-1); |
397 | beg_y2 = std::clamp(beg_y2, beg_y1, m_bounds.h-1); |
398 | |
399 | /* left */ |
400 | ADVANCE(x1, x2, y2, <=, ++, |
401 | get_pixel(image, x1, c=beg_y1), |
402 | get_pixel(image, x1, c)); |
403 | /* right */ |
404 | ADVANCE(x2, x1, y2, >=, --, |
405 | get_pixel(image, x2, c=beg_y1), |
406 | get_pixel(image, x2, c)); |
407 | /* top */ |
408 | ADVANCE(y1, y2, x2, <=, ++, |
409 | get_pixel(image, c=beg_x1, y1), |
410 | get_pixel(image, c, y1)); |
411 | /* bottom */ |
412 | ADVANCE(y2, y1, x2, >=, --, |
413 | get_pixel(image, c=beg_x1, y2), |
414 | get_pixel(image, c, y2)); |
415 | |
416 | if (done_count < 4) |
417 | intersect(gfx::Rect(x1, y1, x2-x1+1, y2-y1+1)); |
418 | else |
419 | clear(); |
420 | |
421 | #undef ADVANCE |
422 | } |
423 | |
424 | void Mask::reserve(const gfx::Rect& bounds) |
425 | { |
426 | ASSERT(!bounds.isEmpty()); |
427 | |
428 | if (!m_bitmap) { |
429 | m_bounds = bounds; |
430 | m_bitmap.reset(Image::create(IMAGE_BITMAP, bounds.w, bounds.h, m_buffer)); |
431 | clear_image(m_bitmap.get(), 0); |
432 | } |
433 | else { |
434 | gfx::Rect newBounds = m_bounds.createUnion(bounds); |
435 | |
436 | if (m_bounds != newBounds) { |
437 | Image* image = crop_image( |
438 | m_bitmap.get(), |
439 | newBounds.x-m_bounds.x, |
440 | newBounds.y-m_bounds.y, |
441 | newBounds.w, |
442 | newBounds.h, 0); |
443 | m_bitmap.reset(image); |
444 | m_bounds = newBounds; |
445 | } |
446 | } |
447 | } |
448 | |
449 | void Mask::shrink() |
450 | { |
451 | // If the mask is frozen we avoid the shrinking |
452 | if (m_freeze_count > 0) |
453 | return; |
454 | |
455 | #define SHRINK_SIDE(u_begin, u_op, u_final, u_add, \ |
456 | v_begin, v_op, v_final, v_add, U, V, var) \ |
457 | { \ |
458 | for (u = u_begin; u u_op u_final; u u_add) { \ |
459 | for (v = v_begin; v v_op v_final; v v_add) { \ |
460 | if (get_pixel_fast<BitmapTraits>(m_bitmap.get(), U, V)) \ |
461 | break; \ |
462 | } \ |
463 | if (v == v_final) \ |
464 | var; \ |
465 | else \ |
466 | break; \ |
467 | } \ |
468 | } |
469 | |
470 | int u, v, x1, y1, x2, y2; |
471 | |
472 | x1 = m_bounds.x; |
473 | y1 = m_bounds.y; |
474 | x2 = m_bounds.x+m_bounds.w-1; |
475 | y2 = m_bounds.y+m_bounds.h-1; |
476 | |
477 | SHRINK_SIDE(0, <, m_bounds.w, ++, |
478 | 0, <, m_bounds.h, ++, u, v, x1++); |
479 | |
480 | SHRINK_SIDE(0, <, m_bounds.h, ++, |
481 | 0, <, m_bounds.w, ++, v, u, y1++); |
482 | |
483 | SHRINK_SIDE(m_bounds.w-1, >, 0, --, |
484 | 0, <, m_bounds.h, ++, u, v, x2--); |
485 | |
486 | SHRINK_SIDE(m_bounds.h-1, >, 0, --, |
487 | 0, <, m_bounds.w, ++, v, u, y2--); |
488 | |
489 | if ((x1 > x2) || (y1 > y2)) { |
490 | clear(); |
491 | } |
492 | else if ((x1 != m_bounds.x) || (x2 != m_bounds.x+m_bounds.w-1) || |
493 | (y1 != m_bounds.y) || (y2 != m_bounds.y+m_bounds.h-1)) { |
494 | u = m_bounds.x; |
495 | v = m_bounds.y; |
496 | |
497 | m_bounds.x = x1; |
498 | m_bounds.y = y1; |
499 | m_bounds.w = x2 - x1 + 1; |
500 | m_bounds.h = y2 - y1 + 1; |
501 | |
502 | Image* image = crop_image( |
503 | m_bitmap.get(), |
504 | m_bounds.x-u, m_bounds.y-v, |
505 | m_bounds.w, m_bounds.h, 0); |
506 | m_bitmap.reset(image); |
507 | } |
508 | |
509 | #undef SHRINK_SIDE |
510 | } |
511 | |
512 | } // namespace doc |
513 | |