/src/libvips/libvips/convolution/edge.c
Line | Count | Source |
1 | | /* Edge detector |
2 | | * |
3 | | * 12/4/23 |
4 | | * - from vips_sobel() |
5 | | */ |
6 | | |
7 | | /* |
8 | | |
9 | | This file is part of VIPS. |
10 | | |
11 | | VIPS is free software; you can redistribute it and/or modify |
12 | | it under the terms of the GNU Lesser General Public License as published by |
13 | | the Free Software Foundation; either version 2 of the License, or |
14 | | (at your option) any later version. |
15 | | |
16 | | This program is distributed in the hope that it will be useful, |
17 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
18 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
19 | | GNU Lesser General Public License for more details. |
20 | | |
21 | | You should have received a copy of the GNU Lesser General Public License |
22 | | along with this program; if not, write to the Free Software |
23 | | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
24 | | 02110-1301 USA |
25 | | |
26 | | */ |
27 | | |
28 | | /* |
29 | | |
30 | | These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk |
31 | | |
32 | | */ |
33 | | |
34 | | /* |
35 | | #define DEBUG |
36 | | */ |
37 | | |
38 | | #ifdef HAVE_CONFIG_H |
39 | | #include <config.h> |
40 | | #endif /*HAVE_CONFIG_H*/ |
41 | | #include <glib/gi18n-lib.h> |
42 | | |
43 | | #include <stdio.h> |
44 | | #include <stdlib.h> |
45 | | #include <math.h> |
46 | | |
47 | | #include <vips/vips.h> |
48 | | |
49 | | typedef struct _VipsEdge { |
50 | | VipsOperation parent_instance; |
51 | | |
52 | | VipsImage *in; |
53 | | VipsImage *out; |
54 | | VipsImage *mask; |
55 | | |
56 | | /* Need an image vector for start_many. |
57 | | */ |
58 | | VipsImage *args[3]; |
59 | | } VipsEdge; |
60 | | |
61 | | typedef VipsOperationClass VipsEdgeClass; |
62 | | |
63 | 108 | G_DEFINE_ABSTRACT_TYPE(VipsEdge, vips_edge, VIPS_TYPE_OPERATION); |
64 | 108 | |
65 | 108 | static void |
66 | 108 | vips_edge_dispose(GObject *gobject) |
67 | 108 | { |
68 | 0 | VipsEdge *edge = (VipsEdge *) gobject; |
69 | |
|
70 | 0 | VIPS_UNREF(edge->mask); |
71 | |
|
72 | 0 | G_OBJECT_CLASS(vips_edge_parent_class)->dispose(gobject); |
73 | 0 | } |
74 | | |
75 | | static int |
76 | | vips_edge_uchar_gen(VipsRegion *out_region, |
77 | | void *vseq, void *a, void *b, gboolean *stop) |
78 | 0 | { |
79 | 0 | VipsRegion **in = (VipsRegion **) vseq; |
80 | 0 | VipsRect *r = &out_region->valid; |
81 | 0 | int sz = r->width * in[0]->im->Bands; |
82 | |
|
83 | 0 | int x, y; |
84 | |
|
85 | 0 | if (vips_reorder_prepare_many(out_region->im, in, r)) |
86 | 0 | return -1; |
87 | | |
88 | 0 | for (y = 0; y < r->height; y++) { |
89 | 0 | VipsPel *p1 = (VipsPel *restrict) |
90 | 0 | VIPS_REGION_ADDR(in[0], r->left, r->top + y); |
91 | 0 | VipsPel *p2 = (VipsPel *restrict) |
92 | 0 | VIPS_REGION_ADDR(in[1], r->left, r->top + y); |
93 | 0 | VipsPel *q = (VipsPel *restrict) |
94 | 0 | VIPS_REGION_ADDR(out_region, r->left, r->top + y); |
95 | |
|
96 | 0 | for (x = 0; x < sz; x++) { |
97 | 0 | int v1 = 2 * (p1[x] - 128); |
98 | 0 | int v2 = 2 * (p2[x] - 128); |
99 | | /* Avoid the sqrt() for uchar. |
100 | | */ |
101 | 0 | int v = abs(v1) + abs(v2); |
102 | |
|
103 | 0 | q[x] = v > 255 ? 255 : v; |
104 | 0 | } |
105 | 0 | } |
106 | |
|
107 | 0 | return 0; |
108 | 0 | } |
109 | | |
110 | | /* Fast uchar path. |
111 | | */ |
112 | | static int |
113 | | vips_edge_build_uchar(VipsEdge *edge) |
114 | 0 | { |
115 | 0 | VipsImage **t = (VipsImage **) |
116 | 0 | vips_object_local_array(VIPS_OBJECT(edge), 20); |
117 | |
|
118 | 0 | g_info("vips_edge: uchar path"); |
119 | | |
120 | | /* For uchar, use 128 as the zero and divide the result by 2 to |
121 | | * prevent overflow. |
122 | | */ |
123 | 0 | if (vips_copy(edge->mask, &t[1], NULL)) |
124 | 0 | return -1; |
125 | 0 | vips_image_set_double(t[1], "offset", 128.0); |
126 | 0 | vips_image_set_double(t[1], "scale", 2.0); |
127 | 0 | if (vips_conv(edge->in, &t[3], t[1], |
128 | 0 | "precision", VIPS_PRECISION_INTEGER, |
129 | 0 | NULL)) |
130 | 0 | return -1; |
131 | | |
132 | 0 | if (vips_rot90(t[1], &t[5], NULL) || |
133 | 0 | vips_conv(edge->in, &t[7], t[5], |
134 | 0 | "precision", VIPS_PRECISION_INTEGER, |
135 | 0 | NULL)) |
136 | 0 | return -1; |
137 | | |
138 | 0 | g_object_set(edge, "out", vips_image_new(), NULL); |
139 | |
|
140 | 0 | edge->args[0] = t[3]; |
141 | 0 | edge->args[1] = t[7]; |
142 | 0 | edge->args[2] = NULL; |
143 | 0 | if (vips_image_pipeline_array(edge->out, |
144 | 0 | VIPS_DEMAND_STYLE_FATSTRIP, edge->args)) |
145 | 0 | return -1; |
146 | | |
147 | 0 | if (vips_image_generate(edge->out, |
148 | 0 | vips_start_many, vips_edge_uchar_gen, vips_stop_many, |
149 | 0 | edge->args, NULL)) |
150 | 0 | return -1; |
151 | | |
152 | 0 | return 0; |
153 | 0 | } |
154 | | |
155 | | /* Accurate but slow path. |
156 | | */ |
157 | | static int |
158 | | vips_edge_build_float(VipsEdge *edge) |
159 | 0 | { |
160 | 0 | VipsImage **t = (VipsImage **) |
161 | 0 | vips_object_local_array(VIPS_OBJECT(edge), 20); |
162 | |
|
163 | 0 | g_info("vips_edge: float path"); |
164 | |
|
165 | 0 | if (vips_rot90(edge->mask, &t[0], NULL) || |
166 | 0 | vips_conv(edge->in, &t[1], edge->mask, NULL) || |
167 | 0 | vips_conv(edge->in, &t[2], t[0], NULL)) |
168 | 0 | return -1; |
169 | | |
170 | 0 | if (vips_multiply(t[1], t[1], &t[3], NULL) || |
171 | 0 | vips_multiply(t[2], t[2], &t[4], NULL) || |
172 | 0 | vips_add(t[3], t[4], &t[5], NULL) || |
173 | 0 | vips_pow_const1(t[5], &t[6], 0.5, NULL) || |
174 | 0 | vips_cast_uchar(t[6], &t[7], NULL)) |
175 | 0 | return -1; |
176 | | |
177 | 0 | g_object_set(edge, "out", vips_image_new(), NULL); |
178 | |
|
179 | 0 | if (vips_image_write(t[7], edge->out)) |
180 | 0 | return -1; |
181 | | |
182 | 0 | return 0; |
183 | 0 | } |
184 | | |
185 | | static int |
186 | | vips_edge_build(VipsObject *object) |
187 | 0 | { |
188 | 0 | VipsEdge *edge = (VipsEdge *) object; |
189 | |
|
190 | 0 | if (edge->in->BandFmt == VIPS_FORMAT_UCHAR) { |
191 | 0 | if (vips_edge_build_uchar(edge)) |
192 | 0 | return -1; |
193 | 0 | } |
194 | 0 | else { |
195 | 0 | if (vips_edge_build_float(edge)) |
196 | 0 | return -1; |
197 | 0 | } |
198 | | |
199 | 0 | return 0; |
200 | 0 | } |
201 | | |
202 | | static void |
203 | | vips_edge_class_init(VipsEdgeClass *class) |
204 | 18 | { |
205 | 18 | GObjectClass *gobject_class = G_OBJECT_CLASS(class); |
206 | 18 | VipsObjectClass *object_class = (VipsObjectClass *) class; |
207 | | |
208 | 18 | gobject_class->dispose = vips_edge_dispose; |
209 | 18 | gobject_class->set_property = vips_object_set_property; |
210 | 18 | gobject_class->get_property = vips_object_get_property; |
211 | | |
212 | 18 | object_class->nickname = "edge"; |
213 | 18 | object_class->description = _("Edge detector"); |
214 | 18 | object_class->build = vips_edge_build; |
215 | | |
216 | 18 | VIPS_ARG_IMAGE(class, "in", 1, |
217 | 18 | _("Input"), |
218 | 18 | _("Input image"), |
219 | 18 | VIPS_ARGUMENT_REQUIRED_INPUT, |
220 | 18 | G_STRUCT_OFFSET(VipsEdge, in)); |
221 | | |
222 | 18 | VIPS_ARG_IMAGE(class, "out", 2, |
223 | 18 | _("Output"), |
224 | 18 | _("Output image"), |
225 | 18 | VIPS_ARGUMENT_REQUIRED_OUTPUT, |
226 | 18 | G_STRUCT_OFFSET(VipsEdge, out)); |
227 | 18 | } |
228 | | |
229 | | static void |
230 | | vips_edge_init(VipsEdge *edge) |
231 | 0 | { |
232 | 0 | } |
233 | | |
234 | | typedef VipsEdge VipsSobel; |
235 | | typedef VipsEdgeClass VipsSobelClass; |
236 | | |
237 | 36 | G_DEFINE_TYPE(VipsSobel, vips_sobel, vips_edge_get_type()); |
238 | 36 | |
239 | 36 | static int |
240 | 36 | vips_sobel_build(VipsObject *object) |
241 | 36 | { |
242 | 0 | VipsEdge *edge = (VipsEdge *) object; |
243 | |
|
244 | 0 | edge->mask = vips_image_new_matrixv(3, 3, |
245 | 0 | 1.0, 2.0, 1.0, |
246 | 0 | 0.0, 0.0, 0.0, |
247 | 0 | -1.0, -2.0, -1.0); |
248 | |
|
249 | 0 | return VIPS_OBJECT_CLASS(vips_sobel_parent_class)->build(object); |
250 | 0 | } |
251 | | |
252 | | static void |
253 | | vips_sobel_class_init(VipsSobelClass *class) |
254 | 18 | { |
255 | 18 | VipsObjectClass *object_class = (VipsObjectClass *) class; |
256 | | |
257 | 18 | object_class->nickname = "sobel"; |
258 | 18 | object_class->description = _("Sobel edge detector"); |
259 | 18 | object_class->build = vips_sobel_build; |
260 | 18 | } |
261 | | |
262 | | static void |
263 | | vips_sobel_init(VipsEdge *sobel) |
264 | 0 | { |
265 | 0 | } |
266 | | |
267 | | typedef VipsEdge VipsScharr; |
268 | | typedef VipsEdgeClass VipsScharrClass; |
269 | | |
270 | 36 | G_DEFINE_TYPE(VipsScharr, vips_scharr, vips_edge_get_type()); |
271 | 36 | |
272 | 36 | static int |
273 | 36 | vips_scharr_build(VipsObject *object) |
274 | 36 | { |
275 | 0 | VipsEdge *edge = (VipsEdge *) object; |
276 | |
|
277 | 0 | edge->mask = vips_image_new_matrixv(3, 3, |
278 | 0 | -3.0, 0.0, 3.0, |
279 | 0 | -10.0, 0.0, 10.0, |
280 | 0 | -3.0, 0.0, 3.0); |
281 | |
|
282 | 0 | return VIPS_OBJECT_CLASS(vips_scharr_parent_class)->build(object); |
283 | 0 | } |
284 | | |
285 | | static void |
286 | | vips_scharr_class_init(VipsSobelClass *class) |
287 | 18 | { |
288 | 18 | VipsObjectClass *object_class = (VipsObjectClass *) class; |
289 | | |
290 | 18 | object_class->nickname = "scharr"; |
291 | 18 | object_class->description = _("Scharr edge detector"); |
292 | 18 | object_class->build = vips_scharr_build; |
293 | 18 | } |
294 | | |
295 | | static void |
296 | | vips_scharr_init(VipsScharr *scharr) |
297 | 0 | { |
298 | 0 | } |
299 | | |
300 | | typedef VipsEdge VipsPrewitt; |
301 | | typedef VipsEdgeClass VipsPrewittClass; |
302 | | |
303 | 36 | G_DEFINE_TYPE(VipsPrewitt, vips_prewitt, vips_edge_get_type()); |
304 | 36 | |
305 | 36 | static int |
306 | 36 | vips_prewitt_build(VipsObject *object) |
307 | 36 | { |
308 | 0 | VipsEdge *edge = (VipsEdge *) object; |
309 | |
|
310 | 0 | edge->mask = vips_image_new_matrixv(3, 3, |
311 | 0 | -1.0, 0.0, 1.0, |
312 | 0 | -1.0, 0.0, 1.0, |
313 | 0 | -1.0, 0.0, 1.0); |
314 | |
|
315 | 0 | return VIPS_OBJECT_CLASS(vips_prewitt_parent_class)->build(object); |
316 | 0 | } |
317 | | |
318 | | static void |
319 | | vips_prewitt_class_init(VipsSobelClass *class) |
320 | 18 | { |
321 | 18 | VipsObjectClass *object_class = (VipsObjectClass *) class; |
322 | | |
323 | 18 | object_class->nickname = "prewitt"; |
324 | 18 | object_class->description = _("Prewitt edge detector"); |
325 | 18 | object_class->build = vips_prewitt_build; |
326 | 18 | } |
327 | | |
328 | | static void |
329 | | vips_prewitt_init(VipsPrewitt *prewitt) |
330 | 0 | { |
331 | 0 | } |
332 | | |
333 | | /** |
334 | | * vips_sobel: (method) |
335 | | * @in: input image |
336 | | * @out: (out): output image |
337 | | * @...: `NULL`-terminated list of optional named arguments |
338 | | * |
339 | | * Sobel edge detector. |
340 | | * |
341 | | * uchar images are computed using a fast, low-precision path. Cast to float |
342 | | * for a high-precision implementation. |
343 | | * |
344 | | * ::: seealso |
345 | | * [method@Image.canny], [method@Image.sobel], [method@Image.prewitt], |
346 | | * [method@Image.scharr]. |
347 | | * |
348 | | * Returns: 0 on success, -1 on error. |
349 | | */ |
350 | | int |
351 | | vips_sobel(VipsImage *in, VipsImage **out, ...) |
352 | 0 | { |
353 | 0 | va_list ap; |
354 | 0 | int result; |
355 | |
|
356 | 0 | va_start(ap, out); |
357 | 0 | result = vips_call_split("sobel", ap, in, out); |
358 | 0 | va_end(ap); |
359 | |
|
360 | 0 | return result; |
361 | 0 | } |
362 | | |
363 | | /** |
364 | | * vips_scharr: (method) |
365 | | * @in: input image |
366 | | * @out: (out): output image |
367 | | * @...: `NULL`-terminated list of optional named arguments |
368 | | * |
369 | | * Scharr edge detector. |
370 | | * |
371 | | * uchar images are computed using a fast, low-precision path. Cast to float |
372 | | * for a high-precision implementation. |
373 | | * |
374 | | * ::: seealso |
375 | | * [method@Image.canny], [method@Image.sobel], [method@Image.prewitt], |
376 | | * [method@Image.scharr]. |
377 | | * |
378 | | * Returns: 0 on success, -1 on error. |
379 | | */ |
380 | | int |
381 | | vips_scharr(VipsImage *in, VipsImage **out, ...) |
382 | 0 | { |
383 | 0 | va_list ap; |
384 | 0 | int result; |
385 | |
|
386 | 0 | va_start(ap, out); |
387 | 0 | result = vips_call_split("scharr", ap, in, out); |
388 | 0 | va_end(ap); |
389 | |
|
390 | 0 | return result; |
391 | 0 | } |
392 | | |
393 | | /** |
394 | | * vips_prewitt: (method) |
395 | | * @in: input image |
396 | | * @out: (out): output image |
397 | | * @...: `NULL`-terminated list of optional named arguments |
398 | | * |
399 | | * Prewitt edge detector. |
400 | | * |
401 | | * uchar images are computed using a fast, low-precision path. Cast to float |
402 | | * for a high-precision implementation. |
403 | | * |
404 | | * ::: seealso |
405 | | * [method@Image.canny], [method@Image.sobel], [method@Image.prewitt], |
406 | | * [method@Image.scharr]. |
407 | | * |
408 | | * Returns: 0 on success, -1 on error. |
409 | | */ |
410 | | int |
411 | | vips_prewitt(VipsImage *in, VipsImage **out, ...) |
412 | 0 | { |
413 | 0 | va_list ap; |
414 | 0 | int result; |
415 | |
|
416 | 0 | va_start(ap, out); |
417 | 0 | result = vips_call_split("prewitt", ap, in, out); |
418 | 0 | va_end(ap); |
419 | |
|
420 | 0 | return result; |
421 | 0 | } |