/src/libvips/libvips/conversion/grid.c
Line | Count | Source |
1 | | /* vips_grid |
2 | | * |
3 | | * 4/8/05 |
4 | | * 7/9/05 |
5 | | * - oops, clipping was wrong |
6 | | * 30/1/10 |
7 | | * - gtkdoc |
8 | | * - small cleanups |
9 | | * 30/5/13 |
10 | | * - redo as a class |
11 | | */ |
12 | | |
13 | | /* |
14 | | |
15 | | This file is part of VIPS. |
16 | | |
17 | | VIPS is free software; you can redistribute it and/or modify |
18 | | it under the terms of the GNU Lesser General Public License as published by |
19 | | the Free Software Foundation; either version 2 of the License, or |
20 | | (at your option) any later version. |
21 | | |
22 | | This program is distributed in the hope that it will be useful, |
23 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
24 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
25 | | GNU Lesser General Public License for more details. |
26 | | |
27 | | You should have received a copy of the GNU Lesser General Public License |
28 | | along with this program; if not, write to the Free Software |
29 | | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
30 | | 02110-1301 USA |
31 | | |
32 | | */ |
33 | | |
34 | | /* |
35 | | |
36 | | These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk |
37 | | |
38 | | */ |
39 | | |
40 | | #ifdef HAVE_CONFIG_H |
41 | | #include <config.h> |
42 | | #endif /*HAVE_CONFIG_H*/ |
43 | | #include <glib/gi18n-lib.h> |
44 | | |
45 | | #include <stdio.h> |
46 | | #include <stdlib.h> |
47 | | #include <string.h> |
48 | | |
49 | | #include <vips/vips.h> |
50 | | |
51 | | #include "pconversion.h" |
52 | | |
53 | | typedef struct _VipsGrid { |
54 | | VipsConversion parent_instance; |
55 | | |
56 | | VipsImage *in; |
57 | | |
58 | | int tile_height; |
59 | | int across; |
60 | | int down; |
61 | | |
62 | | } VipsGrid; |
63 | | |
64 | | typedef VipsConversionClass VipsGridClass; |
65 | | |
66 | 36 | G_DEFINE_TYPE(VipsGrid, vips_grid, VIPS_TYPE_CONVERSION); |
67 | 36 | |
68 | 36 | static int |
69 | 36 | vips_grid_gen(VipsRegion *out_region, |
70 | 36 | void *vseq, void *a, void *b, gboolean *stop) |
71 | 36 | { |
72 | 0 | VipsRegion *ir = (VipsRegion *) vseq; |
73 | 0 | VipsGrid *grid = (VipsGrid *) b; |
74 | 0 | VipsRect *r = &out_region->valid; |
75 | 0 | int twidth = grid->in->Xsize; |
76 | 0 | int theight = grid->tile_height; |
77 | |
|
78 | 0 | int x, y; |
79 | 0 | VipsRect tile; |
80 | | |
81 | | /* Find top left of tiles we need. |
82 | | */ |
83 | 0 | int xs = (r->left / twidth) * twidth; |
84 | 0 | int ys = (r->top / theight) * theight; |
85 | | |
86 | | /* The tile enclosing the top-left corner of the requested area. |
87 | | */ |
88 | 0 | tile.left = xs; |
89 | 0 | tile.top = ys; |
90 | 0 | tile.width = twidth; |
91 | 0 | tile.height = theight; |
92 | | |
93 | | /* If the request fits inside a single tile, we can just pointer-copy. |
94 | | */ |
95 | 0 | if (vips_rect_includesrect(&tile, r)) { |
96 | 0 | VipsRect irect; |
97 | | |
98 | | /* Translate request to input space. |
99 | | */ |
100 | 0 | irect = *r; |
101 | 0 | irect.left -= xs; |
102 | 0 | irect.top -= ys; |
103 | 0 | irect.top += grid->across * ys + theight * (xs / twidth); |
104 | |
|
105 | 0 | if (vips_region_prepare(ir, &irect) || |
106 | 0 | vips_region_region(out_region, ir, r, irect.left, irect.top)) |
107 | 0 | return -1; |
108 | | |
109 | 0 | return 0; |
110 | 0 | } |
111 | | |
112 | 0 | for (y = ys; y < VIPS_RECT_BOTTOM(r); y += theight) |
113 | 0 | for (x = xs; x < VIPS_RECT_RIGHT(r); x += twidth) { |
114 | 0 | VipsRect paint; |
115 | 0 | VipsRect input; |
116 | | |
117 | | /* Whole tile at x, y |
118 | | */ |
119 | 0 | tile.left = x; |
120 | 0 | tile.top = y; |
121 | 0 | tile.width = twidth; |
122 | 0 | tile.height = theight; |
123 | | |
124 | | /* Which parts touch the area of the output we are |
125 | | * building. |
126 | | */ |
127 | 0 | vips_rect_intersectrect(&tile, r, &paint); |
128 | |
|
129 | 0 | g_assert(!vips_rect_isempty(&paint)); |
130 | | |
131 | | /* Translate back to ir coordinates. |
132 | | */ |
133 | 0 | input = paint; |
134 | 0 | input.left -= x; |
135 | 0 | input.top -= y; |
136 | 0 | input.top += grid->across * y + theight * (x / twidth); |
137 | | |
138 | | /* Render into out_region. |
139 | | */ |
140 | 0 | if (vips_region_prepare_to(ir, out_region, &input, |
141 | 0 | paint.left, paint.top)) |
142 | 0 | return -1; |
143 | 0 | } |
144 | | |
145 | 0 | return 0; |
146 | 0 | } |
147 | | |
148 | | static int |
149 | | vips_grid_build(VipsObject *object) |
150 | 0 | { |
151 | 0 | VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(object); |
152 | 0 | VipsConversion *conversion = VIPS_CONVERSION(object); |
153 | 0 | VipsGrid *grid = (VipsGrid *) object; |
154 | |
|
155 | 0 | if (VIPS_OBJECT_CLASS(vips_grid_parent_class)->build(object)) |
156 | 0 | return -1; |
157 | | |
158 | 0 | if (vips_check_coding_known(class->nickname, grid->in) || |
159 | 0 | vips_image_pio_input(grid->in)) |
160 | 0 | return -1; |
161 | | |
162 | 0 | if (grid->in->Ysize % grid->tile_height != 0 || |
163 | 0 | grid->in->Ysize / grid->tile_height != |
164 | 0 | grid->across * grid->down) { |
165 | 0 | vips_error(class->nickname, "%s", _("bad grid geometry")); |
166 | 0 | return -1; |
167 | 0 | } |
168 | | |
169 | | /* We can render small tiles with pointer copies. |
170 | | */ |
171 | 0 | if (vips_image_pipelinev(conversion->out, |
172 | 0 | VIPS_DEMAND_STYLE_SMALLTILE, grid->in, NULL)) |
173 | 0 | return -1; |
174 | 0 | conversion->out->Xsize = grid->in->Xsize * grid->across; |
175 | 0 | conversion->out->Ysize = grid->tile_height * grid->down; |
176 | |
|
177 | 0 | if (vips_image_generate(conversion->out, |
178 | 0 | vips_start_one, vips_grid_gen, vips_stop_one, |
179 | 0 | grid->in, grid)) |
180 | 0 | return -1; |
181 | | |
182 | 0 | return 0; |
183 | 0 | } |
184 | | |
185 | | static void |
186 | | vips_grid_class_init(VipsGridClass *class) |
187 | 18 | { |
188 | 18 | GObjectClass *gobject_class = G_OBJECT_CLASS(class); |
189 | 18 | VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS(class); |
190 | | |
191 | 18 | gobject_class->set_property = vips_object_set_property; |
192 | 18 | gobject_class->get_property = vips_object_get_property; |
193 | | |
194 | 18 | vobject_class->nickname = "grid"; |
195 | 18 | vobject_class->description = _("grid an image"); |
196 | 18 | vobject_class->build = vips_grid_build; |
197 | | |
198 | 18 | VIPS_ARG_IMAGE(class, "in", 1, |
199 | 18 | _("Input"), |
200 | 18 | _("Input image"), |
201 | 18 | VIPS_ARGUMENT_REQUIRED_INPUT, |
202 | 18 | G_STRUCT_OFFSET(VipsGrid, in)); |
203 | | |
204 | 18 | VIPS_ARG_INT(class, "tile_height", 3, |
205 | 18 | _("Tile height"), |
206 | 18 | _("Chop into tiles this high"), |
207 | 18 | VIPS_ARGUMENT_REQUIRED_INPUT, |
208 | 18 | G_STRUCT_OFFSET(VipsGrid, tile_height), |
209 | 18 | 1, 10000000, 128); |
210 | | |
211 | 18 | VIPS_ARG_INT(class, "across", 4, |
212 | 18 | _("Across"), |
213 | 18 | _("Number of tiles across"), |
214 | 18 | VIPS_ARGUMENT_REQUIRED_INPUT, |
215 | 18 | G_STRUCT_OFFSET(VipsGrid, across), |
216 | 18 | 1, 10000000, 1); |
217 | | |
218 | 18 | VIPS_ARG_INT(class, "down", 5, |
219 | 18 | _("Down"), |
220 | 18 | _("Number of tiles down"), |
221 | 18 | VIPS_ARGUMENT_REQUIRED_INPUT, |
222 | 18 | G_STRUCT_OFFSET(VipsGrid, down), |
223 | 18 | 1, 10000000, 1); |
224 | 18 | } |
225 | | |
226 | | static void |
227 | | vips_grid_init(VipsGrid *grid) |
228 | 0 | { |
229 | 0 | grid->tile_height = 128; |
230 | 0 | grid->across = 1; |
231 | 0 | grid->down = 1; |
232 | 0 | } |
233 | | |
234 | | /** |
235 | | * vips_grid: (method) |
236 | | * @in: input image |
237 | | * @out: (out): output image |
238 | | * @tile_height: chop into tiles this high |
239 | | * @across: tiles across |
240 | | * @down: tiles down |
241 | | * @...: `NULL`-terminated list of optional named arguments |
242 | | * |
243 | | * Chop a tall thin image up into a set of tiles, lay the tiles out in a grid. |
244 | | * |
245 | | * The input image should be a very tall, thin image containing a list of |
246 | | * smaller images. Volumetric or time-sequence images are often laid out like |
247 | | * this. This image is chopped into a series of tiles, each @tile_height |
248 | | * pixels high and the width of @in. The tiles are then rearranged into a grid |
249 | | * @across tiles across and @down tiles down in row-major order. |
250 | | * |
251 | | * Supplying @tile_height, @across and @down is not strictly necessary, we |
252 | | * only really need two of these. Requiring three is a double-check that the |
253 | | * image has the expected geometry. |
254 | | * |
255 | | * ::: seealso |
256 | | * [method@Image.embed], [method@Image.insert], [method@Image.join]. |
257 | | * |
258 | | * Returns: 0 on success, -1 on error |
259 | | */ |
260 | | int |
261 | | vips_grid(VipsImage *in, VipsImage **out, |
262 | | int tile_height, int across, int down, ...) |
263 | 0 | { |
264 | 0 | va_list ap; |
265 | 0 | int result; |
266 | |
|
267 | 0 | va_start(ap, down); |
268 | 0 | result = vips_call_split("grid", ap, |
269 | 0 | in, out, tile_height, across, down); |
270 | 0 | va_end(ap); |
271 | |
|
272 | 0 | return result; |
273 | 0 | } |