1 /*
2 * Copyright © 2005-2007 Jens Gulden
3 * Copyright © 2011-2011 Diego Elio Pettenò
4 * Copyright © 2016 Jerome Flesch
5 *
6 * Pypillowfight is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, version 2 of the License.
9 *
10 * Pypillowfight 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 <assert.h>
20 #include <stdint.h>
21 #include <stdio.h>
22 #include <string.h>
23
24 #include <pillowfight/pillowfight.h>
25 #include <pillowfight/util.h>
26
27 #ifndef NO_PYTHON
28 #include "_pymod.h"
29 #endif
30
31 /*!
32 * \brief Algorithm 'mask' from unpaper, partially rewritten.
33 */
34
35 #define SCAN_SIZE 50
36 #define SCAN_STEP 5
37 #define SCAN_THRESHOLD 0.1
38 #define SCAN_MIN 100
39
40 #define MASK_COLOR PF_WHOLE_WHITE
41
42 /**
43 * Returns the average brightness of a rectagular area.
44 */
brightness_rect(const struct pf_bitmap * img,int x1,int y1,int x2,int y2)45 static int brightness_rect(const struct pf_bitmap *img, int x1, int y1, int x2, int y2)
46 {
47 int x;
48 int y;
49 int total = 0;
50 const int count = (x2 - x1) * (y2 - y1);
51
52 for (x = x1; x < x2; x++) {
53 for (y = y1; y < y2; y++) {
54 total += PF_GET_PIXEL_GRAYSCALE(img, x, y);
55 }
56 }
57 return total / count;
58 }
59
60 /**
61 * Finds one edge of non-black pixels heading from one starting point towards edge direction.
62 *
63 * @return number of shift-steps until blank edge found
64 */
detect_edge(const struct pf_bitmap * img,int start_x,int start_y,int shift_x)65 static int detect_edge(const struct pf_bitmap *img, int start_x, int start_y, int shift_x)
66 {
67 int left;
68 int top;
69 int right;
70 int bottom;
71
72 int total = 0;
73 int count = 0;
74 int scan_depth;
75
76 double blackness, threshold;
77
78 assert(shift_x != 0);
79
80 // horizontal border is to be detected, vertical shifting of scan-bar
81 // vertical border is to be detected, horizontal shifting of scan-bar
82 scan_depth = img->size.y;
83 left = start_x - (SCAN_SIZE / 2);
84 top = start_y - (scan_depth / 2);
85 right = start_x + (SCAN_SIZE / 2);
86 bottom = start_y + (scan_depth / 2);
87
88 while (1) {
89 blackness = (double)(((int)PF_WHITE) - brightness_rect(img, left, top, right, bottom));
90 total += blackness;
91 count++;
92 // is blackness below threshold*average?
93 threshold = ((((double)SCAN_THRESHOLD) * total) / count);
94 if ((blackness < threshold)
95 // this will surely become true when pos reaches
96 // the outside of the actual image area and
97 // blacknessRect() will deliver 0 because all
98 // pixels outside are considered white
99 || (((int)blackness) == 0)) {
100 return count; // return here, return absolute value of shifting difference
101 }
102 left += shift_x;
103 right += shift_x;
104 }
105 }
106
detect_mask(const struct pf_bitmap * img,int x,int y)107 static struct pf_rectangle detect_mask(const struct pf_bitmap *img, int x, int y)
108 {
109 int width;
110 struct pf_rectangle out;
111 int edge;
112
113 memset(&out, 0, sizeof(out));
114 out.a.x = -1;
115 out.a.y = -1;
116 out.b.x = -1;
117 out.b.y = -1;
118
119 /* we work horizontally only */
120 edge = detect_edge(img, x, y, -SCAN_STEP);
121 out.a.x = (x
122 - (SCAN_STEP * edge)
123 - (SCAN_SIZE / 2));
124 edge = detect_edge(img, x, y, SCAN_STEP);
125 out.b.x = (x
126 + (SCAN_STEP * edge)
127 + (SCAN_SIZE / 2));
128
129 // we don't work vertically --> full range of sheet
130 out.a.y = 0;
131 out.b.y = img->size.y;
132
133 // if below minimum or above maximum, set to maximum
134 width = out.b.x - out.a.x;
135 if (width < SCAN_MIN || width >= img->size.x) {
136 out.a.x = 0;
137 out.b.x = img->size.x;
138 }
139 return out;
140 }
141
142 #ifndef NO_PYTHON
143 static
144 #endif
pf_unpaper_masks(const struct pf_bitmap * in,struct pf_bitmap * out)145 void pf_unpaper_masks(const struct pf_bitmap *in, struct pf_bitmap *out)
146 {
147 struct pf_rectangle mask;
148
149 memcpy(out->pixels, in->pixels, sizeof(union pf_pixel) * in->size.x * in->size.y);
150
151 mask = detect_mask(in, in->size.x / 2, in->size.y /2);
152 pf_apply_mask(out, &mask);
153 }
154
155 #ifndef NO_PYTHON
156
pymasks(PyObject * self,PyObject * args)157 PyObject *pymasks(PyObject *self, PyObject* args)
158 {
159 int img_x, img_y;
160 Py_buffer img_in, img_out;
161 struct pf_bitmap bitmap_in;
162 struct pf_bitmap bitmap_out;
163
164 if (!PyArg_ParseTuple(args, "iiy*y*",
165 &img_x, &img_y,
166 &img_in,
167 &img_out)) {
168 return NULL;
169 }
170
171 assert(img_x * img_y * 4 /* RGBA */ == img_in.len);
172 assert(img_in.len == img_out.len);
173
174 bitmap_in = from_py_buffer(&img_in, img_x, img_y);
175 bitmap_out = from_py_buffer(&img_out, img_x, img_y);
176
177 memset(bitmap_out.pixels, 0xFFFFFFFF, img_out.len);
178 Py_BEGIN_ALLOW_THREADS;
179 pf_unpaper_masks(&bitmap_in, &bitmap_out);
180 Py_END_ALLOW_THREADS;
181
182 PyBuffer_Release(&img_in);
183 PyBuffer_Release(&img_out);
184 Py_RETURN_NONE;
185 }
186
187 #endif // !NO_PYTHON
188