Coverage Report

Created: 2025-01-28 06:31

/src/libvips/libvips/conversion/sequential.c
Line
Count
Source (jump to first uncovered line)
1
/* Like copy, but ensure sequential access.
2
 *
3
 * Handy with sequential for loading files formats which are strictly
4
 * top-to-bottom, like PNG.
5
 *
6
 * 15/2/12
7
 *  - from VipsForeignLoad
8
 * 14/7/12
9
 *  - support skip forwards as well, so we can do extract/insert
10
 * 10/8/12
11
 *  - add @trace option
12
 * 21/8/12
13
 *  - remove skip forward, instead do thread stalling and have an
14
 *    integrated cache
15
 *  - use linecache
16
 * 4/9/12
17
 *  - stop all threads on error
18
 *  - don't stall forever, just delay ahead threads
19
 * 25/2/14
20
 *  - we were allowing skipahead if the first request was for y>0, but
21
 *    this broke on some busy, many-core systems, see comment below
22
 * 10/6/14
23
 *  - re-enable skipahead now we have the single-thread-first-tile idea
24
 * 6/3/17
25
 *  - deprecate @trace, @access now seq is much simpler
26
 * 6/9/21
27
 *  - don't set "persistent", it can cause huge memory use
28
 */
29
30
/*
31
32
  This file is part of VIPS.
33
34
  VIPS is free software; you can redistribute it and/or modify
35
  it under the terms of the GNU Lesser General Public License as published by
36
  the Free Software Foundation; either version 2 of the License, or
37
  (at your option) any later version.
38
39
  This program is distributed in the hope that it will be useful,
40
  but WITHOUT ANY WARRANTY; without even the implied warranty of
41
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
42
  GNU Lesser General Public License for more details.
43
44
  You should have received a cache of the GNU Lesser General Public License
45
  along with this program; if not, write to the Free Software
46
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
47
  02110-1301  USA
48
49
 */
50
51
/*
52
53
  These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
54
55
 */
56
57
/*
58
#define VIPS_DEBUG_GREEN
59
#define VIPS_DEBUG
60
 */
61
62
#ifdef HAVE_CONFIG_H
63
#include <config.h>
64
#endif /*HAVE_CONFIG_H*/
65
#include <glib/gi18n-lib.h>
66
67
#include <stdio.h>
68
#include <stdlib.h>
69
#include <string.h>
70
71
#include <vips/vips.h>
72
#include <vips/internal.h>
73
#include <vips/debug.h>
74
75
#include "pconversion.h"
76
77
typedef struct _VipsSequential {
78
  VipsConversion parent_instance;
79
80
  VipsImage *in;
81
  int tile_height;
82
  VipsAccess access;
83
  gboolean trace;
84
85
  /* Lock access to y_pos with this.
86
   */
87
  GMutex *lock;
88
89
  /* The next read from our source will fetch this scanline, ie. it's 0
90
   * when we start.
91
   */
92
  int y_pos;
93
94
  /* If one thread gets an error, we must stop all threads, otherwise we
95
   * can stall and never wake.
96
   */
97
  int error;
98
} VipsSequential;
99
100
typedef VipsConversionClass VipsSequentialClass;
101
102
G_DEFINE_TYPE(VipsSequential, vips_sequential, VIPS_TYPE_CONVERSION);
103
104
static void
105
vips_sequential_dispose(GObject *gobject)
106
9.05k
{
107
9.05k
  VipsSequential *sequential = (VipsSequential *) gobject;
108
109
9.05k
  VIPS_FREEF(vips_g_mutex_free, sequential->lock);
110
111
9.05k
  G_OBJECT_CLASS(vips_sequential_parent_class)->dispose(gobject);
112
9.05k
}
113
114
static int
115
vips_sequential_generate(VipsRegion *out_region,
116
  void *seq, void *a, void *b, gboolean *stop)
117
24.1k
{
118
24.1k
  VipsSequential *sequential = (VipsSequential *) b;
119
24.1k
  VipsRect *r = &out_region->valid;
120
24.1k
  VipsRegion *ir = (VipsRegion *) seq;
121
122
  /*
123
  printf("vips_sequential_generate %p: request for line %d, height %d\n",
124
    sequential, r->top, r->height);
125
   */
126
127
24.1k
  VIPS_GATE_START("vips_sequential_generate: wait");
128
129
24.1k
  vips__worker_lock(sequential->lock);
130
131
24.1k
  VIPS_GATE_STOP("vips_sequential_generate: wait");
132
133
  /* If we've seen an error, everything must stop.
134
   */
135
24.1k
  if (sequential->error) {
136
0
    g_mutex_unlock(sequential->lock);
137
0
    return -1;
138
0
  }
139
140
24.1k
  if (r->top > sequential->y_pos) {
141
    /* This is a request for something some way down the image.
142
     * Probably the operation is something like extract_area and
143
     * we should skip the initial part of the image. In fact,
144
     * we read to cache, since it may be useful.
145
     *
146
     * Read in chunks, since we may be skipping *many* lines of image from
147
     * a file source.
148
     */
149
0
    int y;
150
151
0
    for (y = sequential->y_pos; y < r->top; y += sequential->tile_height) {
152
0
      VipsRect area;
153
154
0
      area.left = 0;
155
0
      area.top = y;
156
0
      area.width = 1;
157
0
      area.height = VIPS_MIN(sequential->tile_height, r->top - area.top);
158
0
      if (vips_region_prepare(ir, &area)) {
159
0
        sequential->error = -1;
160
0
        g_mutex_unlock(sequential->lock);
161
0
        return -1;
162
0
      }
163
164
0
      sequential->y_pos += area.height;
165
0
    }
166
0
  }
167
168
  /* This is a request for old pixels, or for pixels exactly at the read
169
   * point. This might trigger a generate from the thing feeding the cache,
170
   * eg. a loader.
171
   */
172
24.1k
  if (vips_region_prepare(ir, r) ||
173
24.1k
    vips_region_region(out_region, ir, r, r->left, r->top)) {
174
1.91k
    sequential->error = -1;
175
1.91k
    g_mutex_unlock(sequential->lock);
176
1.91k
    return -1;
177
1.91k
  }
178
179
22.2k
  sequential->y_pos = VIPS_MAX(sequential->y_pos, VIPS_RECT_BOTTOM(r));
180
181
22.2k
  g_mutex_unlock(sequential->lock);
182
183
22.2k
  return 0;
184
24.1k
}
185
186
static int
187
vips_sequential_build(VipsObject *object)
188
9.07k
{
189
9.07k
  VipsConversion *conversion = VIPS_CONVERSION(object);
190
9.07k
  VipsSequential *sequential = (VipsSequential *) object;
191
192
9.07k
  VipsImage *t;
193
194
9.07k
  VIPS_DEBUG_MSG("vips_sequential_build\n");
195
196
9.07k
  if (VIPS_OBJECT_CLASS(vips_sequential_parent_class)->build(object))
197
0
    return -1;
198
199
  /* We've gone forwards and backwards on sequential caches being
200
   * persistent. Persistent caches can be useful if you want to eg.
201
   * make several crop() operations on a seq image source, but they use
202
   * a lot of memory with eg. arrayjoin.
203
   *
204
   * On balance, if you want to make many crops from one source, use a
205
   * RANDOM image.
206
   */
207
9.07k
  if (vips_linecache(sequential->in, &t,
208
9.07k
      "tile_height", sequential->tile_height,
209
9.07k
      "access", VIPS_ACCESS_SEQUENTIAL,
210
9.07k
      NULL))
211
0
    return -1;
212
213
9.07k
  vips_object_local(object, t);
214
215
9.07k
  if (vips_image_pipelinev(conversion->out,
216
9.07k
      VIPS_DEMAND_STYLE_THINSTRIP, t, NULL))
217
0
    return -1;
218
9.07k
  if (vips_image_generate(conversion->out,
219
9.07k
      vips_start_one, vips_sequential_generate, vips_stop_one,
220
9.07k
      t, sequential))
221
0
    return -1;
222
223
9.07k
  return 0;
224
9.07k
}
225
226
static void
227
vips_sequential_class_init(VipsSequentialClass *class)
228
1
{
229
1
  GObjectClass *gobject_class = G_OBJECT_CLASS(class);
230
1
  VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS(class);
231
232
1
  VIPS_DEBUG_MSG("vips_sequential_class_init\n");
233
234
1
  gobject_class->dispose = vips_sequential_dispose;
235
1
  gobject_class->set_property = vips_object_set_property;
236
1
  gobject_class->get_property = vips_object_get_property;
237
238
1
  vobject_class->nickname = "sequential";
239
1
  vobject_class->description = _("check sequential access");
240
1
  vobject_class->build = vips_sequential_build;
241
242
1
  VIPS_ARG_IMAGE(class, "in", 1,
243
1
    _("Input"),
244
1
    _("Input image"),
245
1
    VIPS_ARGUMENT_REQUIRED_INPUT,
246
1
    G_STRUCT_OFFSET(VipsSequential, in));
247
248
1
  VIPS_ARG_INT(class, "tile_height", 3,
249
1
    _("Tile height"),
250
1
    _("Tile height in pixels"),
251
1
    VIPS_ARGUMENT_OPTIONAL_INPUT,
252
1
    G_STRUCT_OFFSET(VipsSequential, tile_height),
253
1
    1, 1000000, 1);
254
255
1
  VIPS_ARG_ENUM(class, "access", 6,
256
1
    _("Strategy"),
257
1
    _("Expected access pattern"),
258
1
    VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED,
259
1
    G_STRUCT_OFFSET(VipsSequential, access),
260
1
    VIPS_TYPE_ACCESS, VIPS_ACCESS_SEQUENTIAL);
261
262
1
  VIPS_ARG_BOOL(class, "trace", 2,
263
1
    _("Trace"),
264
1
    _("Trace pixel requests"),
265
1
    VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED,
266
1
    G_STRUCT_OFFSET(VipsSequential, trace),
267
1
    TRUE);
268
1
}
269
270
static void
271
vips_sequential_init(VipsSequential *sequential)
272
9.07k
{
273
9.07k
  sequential->lock = vips_g_mutex_new();
274
9.07k
  sequential->tile_height = 1;
275
9.07k
  sequential->error = 0;
276
9.07k
  sequential->trace = FALSE;
277
9.07k
}
278
279
/**
280
 * vips_sequential: (method)
281
 * @in: input image
282
 * @out: (out): output image
283
 * @...: %NULL-terminated list of optional named arguments
284
 *
285
 * Optional arguments:
286
 *
287
 * * @tile_height: height of cache strips
288
 *
289
 * This operation behaves rather like vips_copy() between images
290
 * @in and @out, except that it checks that pixels on @in are only requested
291
 * top-to-bottom. This operation is useful for loading file formats which are
292
 * strictly top-to-bottom, like PNG.
293
 *
294
 * @tile_height can be used to set the size of the tiles that
295
 * vips_sequential() uses. The default value is 1.
296
 *
297
 * See also: vips_cache(), vips_linecache(), vips_tilecache().
298
 *
299
 * Returns: 0 on success, -1 on error.
300
 */
301
int
302
vips_sequential(VipsImage *in, VipsImage **out, ...)
303
9.07k
{
304
9.07k
  va_list ap;
305
9.07k
  int result;
306
307
9.07k
  va_start(ap, out);
308
9.07k
  result = vips_call_split("sequential", ap, in, out);
309
9.07k
  va_end(ap);
310
311
9.07k
  return result;
312
9.07k
}