Coverage Report

Created: 2026-02-09 06:54

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}