1/**************************************************************************/
2/* bit_map.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "bit_map.h"
32
33#include "core/io/image_loader.h"
34#include "core/variant/typed_array.h"
35
36void BitMap::create(const Size2i &p_size) {
37 ERR_FAIL_COND(p_size.width < 1);
38 ERR_FAIL_COND(p_size.height < 1);
39
40 ERR_FAIL_COND(static_cast<int64_t>(p_size.width) * static_cast<int64_t>(p_size.height) > INT32_MAX);
41
42 Error err = bitmask.resize((((p_size.width * p_size.height) - 1) / 8) + 1);
43 ERR_FAIL_COND(err != OK);
44
45 width = p_size.width;
46 height = p_size.height;
47
48 memset(bitmask.ptrw(), 0, bitmask.size());
49}
50
51void BitMap::create_from_image_alpha(const Ref<Image> &p_image, float p_threshold) {
52 ERR_FAIL_COND(p_image.is_null() || p_image->is_empty());
53 Ref<Image> img = p_image->duplicate();
54 img->convert(Image::FORMAT_LA8);
55 ERR_FAIL_COND(img->get_format() != Image::FORMAT_LA8);
56
57 create(Size2i(img->get_width(), img->get_height()));
58
59 const uint8_t *r = img->get_data().ptr();
60 uint8_t *w = bitmask.ptrw();
61
62 for (int i = 0; i < width * height; i++) {
63 int bbyte = i / 8;
64 int bbit = i % 8;
65 if (r[i * 2 + 1] / 255.0 > p_threshold) {
66 w[bbyte] |= (1 << bbit);
67 }
68 }
69}
70
71void BitMap::set_bit_rect(const Rect2i &p_rect, bool p_value) {
72 Rect2i current = Rect2i(0, 0, width, height).intersection(p_rect);
73 uint8_t *data = bitmask.ptrw();
74
75 for (int i = current.position.x; i < current.position.x + current.size.x; i++) {
76 for (int j = current.position.y; j < current.position.y + current.size.y; j++) {
77 int ofs = width * j + i;
78 int bbyte = ofs / 8;
79 int bbit = ofs % 8;
80
81 uint8_t b = data[bbyte];
82
83 if (p_value) {
84 b |= (1 << bbit);
85 } else {
86 b &= ~(1 << bbit);
87 }
88
89 data[bbyte] = b;
90 }
91 }
92}
93
94int BitMap::get_true_bit_count() const {
95 int ds = bitmask.size();
96 const uint8_t *d = bitmask.ptr();
97 int c = 0;
98
99 // Fast, almost branchless version.
100
101 for (int i = 0; i < ds; i++) {
102 c += (d[i] & (1 << 7)) >> 7;
103 c += (d[i] & (1 << 6)) >> 6;
104 c += (d[i] & (1 << 5)) >> 5;
105 c += (d[i] & (1 << 4)) >> 4;
106 c += (d[i] & (1 << 3)) >> 3;
107 c += (d[i] & (1 << 2)) >> 2;
108 c += (d[i] & (1 << 1)) >> 1;
109 c += d[i] & 1;
110 }
111
112 return c;
113}
114
115void BitMap::set_bitv(const Point2i &p_pos, bool p_value) {
116 set_bit(p_pos.x, p_pos.y, p_value);
117}
118
119void BitMap::set_bit(int p_x, int p_y, bool p_value) {
120 ERR_FAIL_INDEX(p_x, width);
121 ERR_FAIL_INDEX(p_y, height);
122
123 int ofs = width * p_y + p_x;
124 int bbyte = ofs / 8;
125 int bbit = ofs % 8;
126
127 uint8_t b = bitmask[bbyte];
128
129 if (p_value) {
130 b |= (1 << bbit);
131 } else {
132 b &= ~(1 << bbit);
133 }
134
135 bitmask.write[bbyte] = b;
136}
137
138bool BitMap::get_bitv(const Point2i &p_pos) const {
139 return get_bit(p_pos.x, p_pos.y);
140}
141
142bool BitMap::get_bit(int p_x, int p_y) const {
143 ERR_FAIL_INDEX_V(p_x, width, false);
144 ERR_FAIL_INDEX_V(p_y, height, false);
145
146 int ofs = width * p_y + p_x;
147 int bbyte = ofs / 8;
148 int bbit = ofs % 8;
149
150 return (bitmask[bbyte] & (1 << bbit)) != 0;
151}
152
153Size2i BitMap::get_size() const {
154 return Size2i(width, height);
155}
156
157void BitMap::_set_data(const Dictionary &p_d) {
158 ERR_FAIL_COND(!p_d.has("size"));
159 ERR_FAIL_COND(!p_d.has("data"));
160
161 create(p_d["size"]);
162 bitmask = p_d["data"];
163}
164
165Dictionary BitMap::_get_data() const {
166 Dictionary d;
167 d["size"] = get_size();
168 d["data"] = bitmask;
169 return d;
170}
171
172Vector<Vector<Vector2>> BitMap::_march_square(const Rect2i &p_rect, const Point2i &p_start) const {
173 int stepx = 0;
174 int stepy = 0;
175 int prevx = 0;
176 int prevy = 0;
177 int startx = p_start.x;
178 int starty = p_start.y;
179 int curx = startx;
180 int cury = starty;
181 unsigned int count = 0;
182
183 HashMap<Point2i, int> cross_map;
184
185 Vector<Vector2> _points;
186 int points_size = 0;
187
188 Vector<Vector<Vector2>> ret;
189
190 // Add starting entry at start of return.
191 ret.resize(1);
192
193 do {
194 int sv = 0;
195 { // Square value
196
197 /*
198 checking the 2x2 pixel grid, assigning these values to each pixel, if not transparent
199 +---+---+
200 | 1 | 2 |
201 +---+---+
202 | 4 | 8 | <- current pixel (curx,cury)
203 +---+---+
204 */
205 Point2i tl = Point2i(curx - 1, cury - 1);
206 sv += (p_rect.has_point(tl) && get_bitv(tl)) ? 1 : 0;
207 Point2i tr = Point2i(curx, cury - 1);
208 sv += (p_rect.has_point(tr) && get_bitv(tr)) ? 2 : 0;
209 Point2i bl = Point2i(curx - 1, cury);
210 sv += (p_rect.has_point(bl) && get_bitv(bl)) ? 4 : 0;
211 Point2i br = Point2i(curx, cury);
212 sv += (p_rect.has_point(br) && get_bitv(br)) ? 8 : 0;
213 ERR_FAIL_COND_V(sv == 0 || sv == 15, Vector<Vector<Vector2>>());
214 }
215
216 switch (sv) {
217 case 1:
218 case 5:
219 case 13:
220 /* going UP with these cases:
221 1 5 13
222 +---+---+ +---+---+ +---+---+
223 | 1 | | | 1 | | | 1 | |
224 +---+---+ +---+---+ +---+---+
225 | | | | 4 | | | 4 | 8 |
226 +---+---+ +---+---+ +---+---+
227 */
228 stepx = 0;
229 stepy = -1;
230 break;
231
232 case 8:
233 case 10:
234 case 11:
235 /* going DOWN with these cases:
236 8 10 11
237 +---+---+ +---+---+ +---+---+
238 | | | | | 2 | | 1 | 2 |
239 +---+---+ +---+---+ +---+---+
240 | | 8 | | | 8 | | | 8 |
241 +---+---+ +---+---+ +---+---+
242 */
243 stepx = 0;
244 stepy = 1;
245 break;
246
247 case 4:
248 case 12:
249 case 14:
250 /* going LEFT with these cases:
251 4 12 14
252 +---+---+ +---+---+ +---+---+
253 | | | | | | | | 2 |
254 +---+---+ +---+---+ +---+---+
255 | 4 | | | 4 | 8 | | 4 | 8 |
256 +---+---+ +---+---+ +---+---+
257 */
258 stepx = -1;
259 stepy = 0;
260 break;
261
262 case 2:
263 case 3:
264 case 7:
265 /* going RIGHT with these cases:
266 2 3 7
267 +---+---+ +---+---+ +---+---+
268 | | 2 | | 1 | 2 | | 1 | 2 |
269 +---+---+ +---+---+ +---+---+
270 | | | | | | | 4 | |
271 +---+---+ +---+---+ +---+---+
272 */
273 stepx = 1;
274 stepy = 0;
275 break;
276 case 9:
277 /* Going DOWN if coming from the LEFT, otherwise go UP.
278 9
279 +---+---+
280 | 1 | |
281 +---+---+
282 | | 8 |
283 +---+---+
284 */
285
286 if (prevx == 1) {
287 stepx = 0;
288 stepy = 1;
289 } else {
290 stepx = 0;
291 stepy = -1;
292 }
293 break;
294 case 6:
295 /* Going RIGHT if coming from BELOW, otherwise go LEFT.
296 6
297 +---+---+
298 | | 2 |
299 +---+---+
300 | 4 | |
301 +---+---+
302 */
303
304 if (prevy == -1) {
305 stepx = 1;
306 stepy = 0;
307 } else {
308 stepx = -1;
309 stepy = 0;
310 }
311 break;
312 default:
313 ERR_PRINT("this shouldn't happen.");
314 }
315
316 // Handle crossing points.
317 if (sv == 6 || sv == 9) {
318 const Point2i cur_pos(curx, cury);
319
320 // Find if this point has occurred before.
321 if (HashMap<Point2i, int>::Iterator found = cross_map.find(cur_pos)) {
322 // Add points after the previous crossing to the result.
323 ret.push_back(_points.slice(found->value + 1, points_size));
324
325 // Remove points after crossing point.
326 points_size = found->value + 1;
327
328 // Erase trailing map elements.
329 while (cross_map.last() != found) {
330 cross_map.remove(cross_map.last());
331 }
332
333 cross_map.erase(cur_pos);
334 } else {
335 // Add crossing point to map.
336 cross_map.insert(cur_pos, points_size - 1);
337 }
338 }
339
340 // Small optimization:
341 // If the previous direction is same as the current direction,
342 // then we should modify the last vector to current.
343 curx += stepx;
344 cury += stepy;
345 if (stepx == prevx && stepy == prevy) {
346 _points.set(points_size - 1, Vector2(curx, cury) - p_rect.position);
347 } else {
348 _points.resize(MAX(points_size + 1, _points.size()));
349 _points.set(points_size, Vector2(curx, cury) - p_rect.position);
350 points_size++;
351 }
352
353 count++;
354 prevx = stepx;
355 prevy = stepy;
356
357 ERR_FAIL_COND_V((int)count > 2 * (width * height + 1), Vector<Vector<Vector2>>());
358 } while (curx != startx || cury != starty);
359
360 // Add remaining points to result.
361 _points.resize(points_size);
362
363 ret.set(0, _points);
364
365 return ret;
366}
367
368static float perpendicular_distance(const Vector2 &i, const Vector2 &start, const Vector2 &end) {
369 float res;
370 float slope;
371 float intercept;
372
373 if (start.x == end.x) {
374 res = Math::absf(i.x - end.x);
375 } else if (start.y == end.y) {
376 res = Math::absf(i.y - end.y);
377 } else {
378 slope = (end.y - start.y) / (end.x - start.x);
379 intercept = start.y - (slope * start.x);
380 res = Math::absf(slope * i.x - i.y + intercept) / Math::sqrt(Math::pow(slope, 2.0f) + 1.0);
381 }
382 return res;
383}
384
385static Vector<Vector2> rdp(const Vector<Vector2> &v, float optimization) {
386 if (v.size() < 3) {
387 return v;
388 }
389
390 int index = -1;
391 float dist = 0.0;
392 // Not looping first and last point.
393 for (size_t i = 1, size = v.size(); i < size - 1; ++i) {
394 float cdist = perpendicular_distance(v[i], v[0], v[v.size() - 1]);
395 if (cdist > dist) {
396 dist = cdist;
397 index = static_cast<int>(i);
398 }
399 }
400 if (dist > optimization) {
401 Vector<Vector2> left, right;
402 left.resize(index);
403 for (int i = 0; i < index; i++) {
404 left.write[i] = v[i];
405 }
406 right.resize(v.size() - index);
407 for (int i = 0; i < right.size(); i++) {
408 right.write[i] = v[index + i];
409 }
410 Vector<Vector2> r1 = rdp(left, optimization);
411 Vector<Vector2> r2 = rdp(right, optimization);
412
413 int middle = r1.size();
414 r1.resize(r1.size() + r2.size());
415 for (int i = 0; i < r2.size(); i++) {
416 r1.write[middle + i] = r2[i];
417 }
418 return r1;
419 } else {
420 Vector<Vector2> ret;
421 ret.push_back(v[0]);
422 ret.push_back(v[v.size() - 1]);
423 return ret;
424 }
425}
426
427static Vector<Vector2> reduce(const Vector<Vector2> &points, const Rect2i &rect, float epsilon) {
428 int size = points.size();
429 // If there are less than 3 points, then we have nothing.
430 ERR_FAIL_COND_V(size < 3, Vector<Vector2>());
431 // If there are less than 9 points (but more than 3), then we don't need to reduce it.
432 if (size < 9) {
433 return points;
434 }
435
436 float maxEp = MIN(rect.size.width, rect.size.height);
437 float ep = CLAMP(epsilon, 0.0, maxEp / 2);
438 Vector<Vector2> result = rdp(points, ep);
439
440 Vector2 last = result[result.size() - 1];
441
442 if (last.y > result[0].y && last.distance_to(result[0]) < ep * 0.5f) {
443 result.write[0].y = last.y;
444 result.resize(result.size() - 1);
445 }
446 return result;
447}
448
449struct FillBitsStackEntry {
450 Point2i pos;
451 int i = 0;
452 int j = 0;
453};
454
455static void fill_bits(const BitMap *p_src, Ref<BitMap> &p_map, const Point2i &p_pos, const Rect2i &rect) {
456 // Using a custom stack to work iteratively to avoid stack overflow on big bitmaps.
457 Vector<FillBitsStackEntry> stack;
458 // Tracking size since we won't be shrinking the stack vector.
459 int stack_size = 0;
460
461 Point2i pos = p_pos;
462 int next_i = 0;
463 int next_j = 0;
464
465 bool reenter = true;
466 bool popped = false;
467 do {
468 if (reenter) {
469 next_i = pos.x - 1;
470 next_j = pos.y - 1;
471 reenter = false;
472 }
473
474 for (int i = next_i; i <= pos.x + 1; i++) {
475 for (int j = next_j; j <= pos.y + 1; j++) {
476 if (popped) {
477 // The next loop over j must start normally.
478 next_j = pos.y - 1;
479 popped = false;
480 // Skip because an iteration was already executed with current counter values.
481 continue;
482 }
483
484 if (i < rect.position.x || i >= rect.position.x + rect.size.x) {
485 continue;
486 }
487 if (j < rect.position.y || j >= rect.position.y + rect.size.y) {
488 continue;
489 }
490
491 if (p_map->get_bit(i, j)) {
492 continue;
493
494 } else if (p_src->get_bit(i, j)) {
495 p_map->set_bit(i, j, true);
496
497 FillBitsStackEntry se = { pos, i, j };
498 stack.resize(MAX(stack_size + 1, stack.size()));
499 stack.set(stack_size, se);
500 stack_size++;
501
502 pos = Point2i(i, j);
503 reenter = true;
504 break;
505 }
506 }
507 if (reenter) {
508 break;
509 }
510 }
511 if (!reenter) {
512 if (stack_size) {
513 FillBitsStackEntry se = stack.get(stack_size - 1);
514 stack_size--;
515 pos = se.pos;
516 next_i = se.i;
517 next_j = se.j;
518 popped = true;
519 }
520 }
521 } while (reenter || popped);
522}
523
524Vector<Vector<Vector2>> BitMap::clip_opaque_to_polygons(const Rect2i &p_rect, float p_epsilon) const {
525 Rect2i r = Rect2i(0, 0, width, height).intersection(p_rect);
526
527 Point2i from;
528 Ref<BitMap> fill;
529 fill.instantiate();
530 fill->create(get_size());
531
532 Vector<Vector<Vector2>> polygons;
533 for (int i = r.position.y; i < r.position.y + r.size.height; i++) {
534 for (int j = r.position.x; j < r.position.x + r.size.width; j++) {
535 if (!fill->get_bit(j, i) && get_bit(j, i)) {
536 fill_bits(this, fill, Point2i(j, i), r);
537
538 for (Vector<Vector2> polygon : _march_square(r, Point2i(j, i))) {
539 polygon = reduce(polygon, r, p_epsilon);
540
541 if (polygon.size() < 3) {
542 print_verbose("Invalid polygon, skipped");
543 continue;
544 }
545
546 polygons.push_back(polygon);
547 }
548 }
549 }
550 }
551
552 return polygons;
553}
554
555void BitMap::grow_mask(int p_pixels, const Rect2i &p_rect) {
556 if (p_pixels == 0) {
557 return;
558 }
559
560 bool bit_value = p_pixels > 0;
561 p_pixels = Math::abs(p_pixels);
562
563 Rect2i r = Rect2i(0, 0, width, height).intersection(p_rect);
564
565 Ref<BitMap> copy;
566 copy.instantiate();
567 copy->create(get_size());
568 copy->bitmask = bitmask;
569
570 for (int i = r.position.y; i < r.position.y + r.size.height; i++) {
571 for (int j = r.position.x; j < r.position.x + r.size.width; j++) {
572 if (bit_value == get_bit(j, i)) {
573 continue;
574 }
575
576 bool found = false;
577
578 for (int y = i - p_pixels; y <= i + p_pixels; y++) {
579 for (int x = j - p_pixels; x <= j + p_pixels; x++) {
580 bool outside = false;
581
582 if ((x < p_rect.position.x) || (x >= p_rect.position.x + p_rect.size.x) || (y < p_rect.position.y) || (y >= p_rect.position.y + p_rect.size.y)) {
583 // Outside of rectangle counts as bit not set.
584 if (!bit_value) {
585 outside = true;
586 } else {
587 continue;
588 }
589 }
590
591 float d = Point2(j, i).distance_to(Point2(x, y)) - CMP_EPSILON;
592 if (d > p_pixels) {
593 continue;
594 }
595
596 if (outside || (bit_value == copy->get_bit(x, y))) {
597 found = true;
598 break;
599 }
600 }
601 if (found) {
602 break;
603 }
604 }
605
606 if (found) {
607 set_bit(j, i, bit_value);
608 }
609 }
610 }
611}
612
613void BitMap::shrink_mask(int p_pixels, const Rect2i &p_rect) {
614 grow_mask(-p_pixels, p_rect);
615}
616
617TypedArray<PackedVector2Array> BitMap::_opaque_to_polygons_bind(const Rect2i &p_rect, float p_epsilon) const {
618 Vector<Vector<Vector2>> result = clip_opaque_to_polygons(p_rect, p_epsilon);
619
620 // Convert result to bindable types.
621
622 TypedArray<PackedVector2Array> result_array;
623 result_array.resize(result.size());
624 for (int i = 0; i < result.size(); i++) {
625 const Vector<Vector2> &polygon = result[i];
626
627 PackedVector2Array polygon_array;
628 polygon_array.resize(polygon.size());
629
630 {
631 Vector2 *w = polygon_array.ptrw();
632 for (int j = 0; j < polygon.size(); j++) {
633 w[j] = polygon[j];
634 }
635 }
636
637 result_array[i] = polygon_array;
638 }
639
640 return result_array;
641}
642
643void BitMap::resize(const Size2i &p_new_size) {
644 ERR_FAIL_COND(p_new_size.width < 0 || p_new_size.height < 0);
645 if (p_new_size == get_size()) {
646 return;
647 }
648
649 Ref<BitMap> new_bitmap;
650 new_bitmap.instantiate();
651 new_bitmap->create(p_new_size);
652 // also allow for upscaling
653 int lw = (width == 0) ? 0 : p_new_size.width;
654 int lh = (height == 0) ? 0 : p_new_size.height;
655
656 float scale_x = ((float)width / p_new_size.width);
657 float scale_y = ((float)height / p_new_size.height);
658 for (int x = 0; x < lw; x++) {
659 for (int y = 0; y < lh; y++) {
660 bool new_bit = get_bit(x * scale_x, y * scale_y);
661 new_bitmap->set_bit(x, y, new_bit);
662 }
663 }
664
665 width = new_bitmap->width;
666 height = new_bitmap->height;
667 bitmask = new_bitmap->bitmask;
668}
669
670Ref<Image> BitMap::convert_to_image() const {
671 Ref<Image> image = Image::create_empty(width, height, false, Image::FORMAT_L8);
672
673 for (int i = 0; i < width; i++) {
674 for (int j = 0; j < height; j++) {
675 image->set_pixel(i, j, get_bit(i, j) ? Color(1, 1, 1) : Color(0, 0, 0));
676 }
677 }
678
679 return image;
680}
681
682void BitMap::blit(const Vector2i &p_pos, const Ref<BitMap> &p_bitmap) {
683 ERR_FAIL_COND_MSG(p_bitmap.is_null(), "It's not a reference to a valid BitMap object.");
684
685 int x = p_pos.x;
686 int y = p_pos.y;
687 int w = p_bitmap->get_size().width;
688 int h = p_bitmap->get_size().height;
689
690 for (int i = 0; i < w; i++) {
691 for (int j = 0; j < h; j++) {
692 int px = x + i;
693 int py = y + j;
694 if (px < 0 || px >= width) {
695 continue;
696 }
697 if (py < 0 || py >= height) {
698 continue;
699 }
700 if (p_bitmap->get_bit(i, j)) {
701 set_bit(px, py, true);
702 }
703 }
704 }
705}
706
707void BitMap::_bind_methods() {
708 ClassDB::bind_method(D_METHOD("create", "size"), &BitMap::create);
709 ClassDB::bind_method(D_METHOD("create_from_image_alpha", "image", "threshold"), &BitMap::create_from_image_alpha, DEFVAL(0.1));
710
711 ClassDB::bind_method(D_METHOD("set_bitv", "position", "bit"), &BitMap::set_bitv);
712 ClassDB::bind_method(D_METHOD("set_bit", "x", "y", "bit"), &BitMap::set_bit);
713 ClassDB::bind_method(D_METHOD("get_bitv", "position"), &BitMap::get_bitv);
714 ClassDB::bind_method(D_METHOD("get_bit", "x", "y"), &BitMap::get_bit);
715
716 ClassDB::bind_method(D_METHOD("set_bit_rect", "rect", "bit"), &BitMap::set_bit_rect);
717 ClassDB::bind_method(D_METHOD("get_true_bit_count"), &BitMap::get_true_bit_count);
718
719 ClassDB::bind_method(D_METHOD("get_size"), &BitMap::get_size);
720 ClassDB::bind_method(D_METHOD("resize", "new_size"), &BitMap::resize);
721
722 ClassDB::bind_method(D_METHOD("_set_data", "data"), &BitMap::_set_data);
723 ClassDB::bind_method(D_METHOD("_get_data"), &BitMap::_get_data);
724
725 ClassDB::bind_method(D_METHOD("grow_mask", "pixels", "rect"), &BitMap::grow_mask);
726 ClassDB::bind_method(D_METHOD("convert_to_image"), &BitMap::convert_to_image);
727 ClassDB::bind_method(D_METHOD("opaque_to_polygons", "rect", "epsilon"), &BitMap::_opaque_to_polygons_bind, DEFVAL(2.0));
728
729 ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
730}
731
732BitMap::BitMap() {}
733