/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 | } |