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
20namespace doc {
21
22namespace {
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
48Mask::Mask()
49 : Object(ObjectType::Mask)
50{
51 initialize();
52}
53
54Mask::Mask(const Mask& mask)
55 : Object(mask)
56{
57 initialize();
58 copyFrom(&mask);
59}
60
61Mask::~Mask()
62{
63 ASSERT(m_freeze_count == 0);
64}
65
66void Mask::initialize()
67{
68 m_freeze_count = 0;
69 m_bounds = gfx::Rect(0, 0, 0, 0);
70}
71
72int Mask::getMemSize() const
73{
74 return sizeof(Mask) + (m_bitmap ? m_bitmap->getMemSize(): 0);
75}
76
77void Mask::setName(const char *name)
78{
79 m_name = name;
80}
81
82void Mask::freeze()
83{
84 ASSERT(m_freeze_count >= 0);
85 m_freeze_count++;
86}
87
88void 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
98bool 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
114void 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
132void Mask::offsetOrigin(int dx, int dy)
133{
134 m_bounds.offset(dx, dy);
135}
136
137void Mask::clear()
138{
139 m_bitmap.reset();
140 m_bounds = gfx::Rect(0, 0, 0, 0);
141}
142
143void 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
157void 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
170void 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
179void 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
191void 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
200void 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
216void 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
230void 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
254void 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
359void 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
424void 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
449void 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