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