1 /* GIMP - The GNU Image Manipulation Program
2 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18 /*
19 * This filter tiles an image to arbitrary width and height
20 */
21 #include "config.h"
22
23 #include <string.h>
24
25 #include <libgimp/gimp.h>
26 #include <libgimp/gimpui.h>
27
28 #include "libgimp/stdplugins-intl.h"
29
30
31 #define PLUG_IN_PROC "plug-in-tile"
32 #define PLUG_IN_BINARY "tile"
33 #define PLUG_IN_ROLE "gimp-tile"
34
35
36 typedef struct
37 {
38 gint new_width;
39 gint new_height;
40 gint constrain;
41 gint new_image;
42 } TileVals;
43
44
45 /* Declare local functions.
46 */
47 static void query (void);
48
49 static void run (const gchar *name,
50 gint nparams,
51 const GimpParam *param,
52 gint *nreturn_vals,
53 GimpParam **return_vals);
54
55 static void tile (gint32 image_id,
56 gint32 drawable_id,
57 gint32 *new_image_id,
58 gint32 *new_layer_id);
59
60 static void tile_gegl (GeglBuffer *src,
61 gint src_width,
62 gint src_height,
63 GeglBuffer *dst,
64 gint dst_width,
65 gint dst_height);
66
67 static gboolean tile_dialog (gint32 image_ID,
68 gint32 drawable_ID);
69
70
71 const GimpPlugInInfo PLUG_IN_INFO =
72 {
73 NULL, /* init_proc */
74 NULL, /* quit_proc */
75 query, /* query_proc */
76 run, /* run_proc */
77 };
78
79 static TileVals tvals =
80 {
81 1, /* new_width */
82 1, /* new_height */
83 TRUE, /* constrain */
84 TRUE /* new_image */
85 };
86
87
MAIN()88 MAIN ()
89
90 static void
91 query (void)
92 {
93 static const GimpParamDef args[] =
94 {
95 { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
96 { GIMP_PDB_IMAGE, "image", "Input image (unused)" },
97 { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" },
98 { GIMP_PDB_INT32, "new-width", "New (tiled) image width" },
99 { GIMP_PDB_INT32, "new-height", "New (tiled) image height" },
100 { GIMP_PDB_INT32, "new-image", "Create a new image?" }
101 };
102
103 static const GimpParamDef return_vals[] =
104 {
105 { GIMP_PDB_IMAGE, "new-image", "Output image (-1 if new-image == FALSE)" },
106 { GIMP_PDB_LAYER, "new-layer", "Output layer (-1 if new-image == FALSE)" }
107 };
108
109 gimp_install_procedure (PLUG_IN_PROC,
110 N_("Create an array of copies of the image"),
111 "This function creates a new image with a single "
112 "layer sized to the specified 'new_width' and "
113 "'new_height' parameters. The specified drawable "
114 "is tiled into this layer. The new layer will have "
115 "the same type as the specified drawable and the "
116 "new image will have a corresponding base type.",
117 "Spencer Kimball & Peter Mattis",
118 "Spencer Kimball & Peter Mattis",
119 "1996-1997",
120 N_("_Tile..."),
121 "RGB*, GRAY*, INDEXED*",
122 GIMP_PLUGIN,
123 G_N_ELEMENTS (args),
124 G_N_ELEMENTS (return_vals),
125 args, return_vals);
126
127 gimp_plugin_menu_register (PLUG_IN_PROC, "<Image>/Filters/Map");
128 }
129
130 static void
run(const gchar * name,gint nparams,const GimpParam * param,gint * nreturn_vals,GimpParam ** return_vals)131 run (const gchar *name,
132 gint nparams,
133 const GimpParam *param,
134 gint *nreturn_vals,
135 GimpParam **return_vals)
136 {
137 static GimpParam values[3];
138 GimpRunMode run_mode;
139 GimpPDBStatusType status = GIMP_PDB_SUCCESS;
140
141 run_mode = param[0].data.d_int32;
142
143 INIT_I18N ();
144 gegl_init (NULL, NULL);
145
146 *nreturn_vals = 3;
147 *return_vals = values;
148
149 values[0].type = GIMP_PDB_STATUS;
150 values[0].data.d_status = status;
151 values[1].type = GIMP_PDB_IMAGE;
152 values[2].type = GIMP_PDB_LAYER;
153
154 switch (run_mode)
155 {
156 case GIMP_RUN_INTERACTIVE:
157 /* Possibly retrieve data */
158 gimp_get_data (PLUG_IN_PROC, &tvals);
159
160 /* First acquire information with a dialog */
161 if (! tile_dialog (param[1].data.d_image,
162 param[2].data.d_drawable))
163 return;
164 break;
165
166 case GIMP_RUN_NONINTERACTIVE:
167 /* Make sure all the arguments are there! */
168 if (nparams != 6)
169 {
170 status = GIMP_PDB_CALLING_ERROR;
171 }
172 else
173 {
174 tvals.new_width = param[3].data.d_int32;
175 tvals.new_height = param[4].data.d_int32;
176 tvals.new_image = param[5].data.d_int32 ? TRUE : FALSE;
177
178 if (tvals.new_width < 1 || tvals.new_height < 1)
179 status = GIMP_PDB_CALLING_ERROR;
180 }
181 break;
182
183 case GIMP_RUN_WITH_LAST_VALS:
184 /* Possibly retrieve data */
185 gimp_get_data (PLUG_IN_PROC, &tvals);
186 break;
187
188 default:
189 break;
190 }
191
192 if (status == GIMP_PDB_SUCCESS)
193 {
194 gint32 new_layer_id;
195 gint32 new_image_id;
196
197 gimp_progress_init (_("Tiling"));
198
199 tile (param[1].data.d_image,
200 param[2].data.d_drawable,
201 &new_image_id,
202 &new_layer_id);
203
204 values[1].data.d_image = new_image_id;
205 values[2].data.d_layer = new_layer_id;
206
207 /* Store data */
208 if (run_mode == GIMP_RUN_INTERACTIVE)
209 gimp_set_data (PLUG_IN_PROC, &tvals, sizeof (TileVals));
210
211 if (run_mode != GIMP_RUN_NONINTERACTIVE)
212 {
213 if (tvals.new_image)
214 gimp_display_new (values[1].data.d_image);
215 else
216 gimp_displays_flush ();
217 }
218 }
219
220 values[0].data.d_status = status;
221
222 gegl_exit ();
223 }
224
225 static void
tile_gegl(GeglBuffer * src,gint src_width,gint src_height,GeglBuffer * dst,gint dst_width,gint dst_height)226 tile_gegl (GeglBuffer *src,
227 gint src_width,
228 gint src_height,
229 GeglBuffer *dst,
230 gint dst_width,
231 gint dst_height)
232 {
233 GeglNode *node;
234 GeglNode *buffer_src_node;
235 GeglNode *tile_node;
236 GeglNode *crop_src_node;
237 GeglNode *crop_dst_node;
238 GeglNode *buffer_dst_node;
239
240 GeglProcessor *processor;
241 gdouble progress;
242
243 node = gegl_node_new ();
244
245 buffer_src_node = gegl_node_new_child (node,
246 "operation", "gegl:buffer-source",
247 "buffer", src,
248 NULL);
249
250 crop_src_node = gegl_node_new_child (node,
251 "operation", "gegl:crop",
252 "width", (gdouble) src_width,
253 "height", (gdouble) src_height,
254 NULL);
255
256 tile_node = gegl_node_new_child (node,
257 "operation", "gegl:tile",
258 NULL);
259
260 crop_dst_node = gegl_node_new_child (node,
261 "operation", "gegl:crop",
262 "width", (gdouble) dst_width,
263 "height", (gdouble) dst_height,
264 NULL);
265
266 buffer_dst_node = gegl_node_new_child (node,
267 "operation", "gegl:write-buffer",
268 "buffer", dst,
269 NULL);
270
271 gegl_node_link_many (buffer_src_node,
272 crop_src_node,
273 tile_node,
274 crop_dst_node,
275 buffer_dst_node,
276 NULL);
277
278 processor = gegl_node_new_processor (buffer_dst_node, NULL);
279
280 while (gegl_processor_work (processor, &progress))
281 if (!((gint) (progress * 100.0) % 10))
282 gimp_progress_update (progress);
283
284 gimp_progress_update (1.0);
285
286 g_object_unref (processor);
287 g_object_unref (node);
288 }
289
290 static void
tile(gint32 image_id,gint32 drawable_id,gint32 * new_image_id,gint32 * new_layer_id)291 tile (gint32 image_id,
292 gint32 drawable_id,
293 gint32 *new_image_id,
294 gint32 *new_layer_id)
295 {
296 gint32 dst_drawable_id;
297 GeglBuffer *dst_buffer;
298 GeglBuffer *src_buffer;
299 gint dst_width = tvals.new_width;
300 gint dst_height = tvals.new_height;
301 gint src_width = gimp_drawable_width (drawable_id);
302 gint src_height = gimp_drawable_height (drawable_id);
303
304 GimpImageBaseType image_type = GIMP_RGB;
305
306 /* sanity check parameters */
307 if (dst_width < 1 || dst_height < 1)
308 {
309 *new_image_id = -1;
310 *new_layer_id = -1;
311 return;
312 }
313
314 if (tvals.new_image)
315 {
316 /* create a new image */
317 gint32 precision = gimp_image_get_precision (image_id);
318
319 switch (gimp_drawable_type (drawable_id))
320 {
321 case GIMP_RGB_IMAGE:
322 case GIMP_RGBA_IMAGE:
323 image_type = GIMP_RGB;
324 break;
325
326 case GIMP_GRAY_IMAGE:
327 case GIMP_GRAYA_IMAGE:
328 image_type = GIMP_GRAY;
329 break;
330
331 case GIMP_INDEXED_IMAGE:
332 case GIMP_INDEXEDA_IMAGE:
333 image_type = GIMP_INDEXED;
334 break;
335 }
336
337 *new_image_id = gimp_image_new_with_precision (dst_width,
338 dst_height,
339 image_type,
340 precision);
341 gimp_image_undo_disable (*new_image_id);
342
343 /* copy the colormap, if necessary */
344 if (image_type == GIMP_INDEXED)
345 {
346 guchar *cmap;
347 gint ncols;
348
349 cmap = gimp_image_get_colormap (image_id, &ncols);
350 gimp_image_set_colormap (*new_image_id, cmap, ncols);
351 g_free (cmap);
352 }
353
354 *new_layer_id = gimp_layer_new (*new_image_id, _("Background"),
355 dst_width, dst_height,
356 gimp_drawable_type (drawable_id),
357 100,
358 gimp_image_get_default_new_layer_mode (*new_image_id));
359
360 if (*new_layer_id == -1)
361 return;
362
363 gimp_image_insert_layer (*new_image_id, *new_layer_id, -1, 0);
364 dst_drawable_id = *new_layer_id;
365 }
366 else
367 {
368 *new_image_id = -1;
369 *new_layer_id = -1;
370
371 gimp_image_undo_group_start (image_id);
372 gimp_image_resize (image_id, dst_width, dst_height, 0, 0);
373
374 if (gimp_item_is_layer (drawable_id))
375 gimp_layer_resize (drawable_id, dst_width, dst_height, 0, 0);
376 else if (gimp_item_is_layer_mask (drawable_id))
377 {
378 gint32 layer_id = gimp_layer_from_mask (drawable_id);
379 gimp_layer_resize (layer_id, dst_width, dst_height, 0, 0);
380 }
381
382 dst_drawable_id = drawable_id;
383 }
384
385 src_buffer = gimp_drawable_get_buffer (drawable_id);
386 dst_buffer = gimp_drawable_get_buffer (dst_drawable_id);
387
388 tile_gegl (src_buffer, src_width, src_height,
389 dst_buffer, dst_width, dst_height);
390
391 gegl_buffer_flush (dst_buffer);
392 gimp_drawable_update (dst_drawable_id, 0, 0, dst_width, dst_height);
393
394 if (tvals.new_image)
395 {
396 gimp_image_undo_enable (*new_image_id);
397 }
398 else
399 {
400 gimp_image_undo_group_end (image_id);
401 }
402
403 g_object_unref (src_buffer);
404 g_object_unref (dst_buffer);
405 }
406
407 static gboolean
tile_dialog(gint32 image_ID,gint32 drawable_ID)408 tile_dialog (gint32 image_ID,
409 gint32 drawable_ID)
410 {
411 GtkWidget *dlg;
412 GtkWidget *vbox;
413 GtkWidget *frame;
414 GtkWidget *sizeentry;
415 GtkWidget *chainbutton;
416 GtkWidget *toggle;
417 gint width;
418 gint height;
419 gdouble xres;
420 gdouble yres;
421 GimpUnit unit;
422 gboolean run;
423
424 gimp_ui_init (PLUG_IN_BINARY, FALSE);
425
426 width = gimp_drawable_width (drawable_ID);
427 height = gimp_drawable_height (drawable_ID);
428 unit = gimp_image_get_unit (image_ID);
429 gimp_image_get_resolution (image_ID, &xres, &yres);
430
431 tvals.new_width = width;
432 tvals.new_height = height;
433
434 dlg = gimp_dialog_new (_("Tile"), PLUG_IN_ROLE,
435 NULL, 0,
436 gimp_standard_help_func, PLUG_IN_PROC,
437
438 _("_Cancel"), GTK_RESPONSE_CANCEL,
439 _("_OK"), GTK_RESPONSE_OK,
440
441 NULL);
442
443 gtk_dialog_set_alternative_button_order (GTK_DIALOG (dlg),
444 GTK_RESPONSE_OK,
445 GTK_RESPONSE_CANCEL,
446 -1);
447
448 gimp_window_set_transient (GTK_WINDOW (dlg));
449
450 vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
451 gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);
452 gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))),
453 vbox, TRUE, TRUE, 0);
454 gtk_widget_show (vbox);
455
456 frame = gimp_frame_new (_("Tile to New Size"));
457 gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
458 gtk_widget_show (frame);
459
460 sizeentry = gimp_coordinates_new (unit, "%a", TRUE, TRUE, 8,
461 GIMP_SIZE_ENTRY_UPDATE_SIZE,
462
463 tvals.constrain, TRUE,
464
465 _("_Width:"), width, xres,
466 1, GIMP_MAX_IMAGE_SIZE,
467 0, width,
468
469 _("_Height:"), height, yres,
470 1, GIMP_MAX_IMAGE_SIZE,
471 0, height);
472 gtk_container_add (GTK_CONTAINER (frame), sizeentry);
473 gtk_table_set_row_spacing (GTK_TABLE (sizeentry), 1, 6);
474 gtk_widget_show (sizeentry);
475
476 chainbutton = GTK_WIDGET (GIMP_COORDINATES_CHAINBUTTON (sizeentry));
477
478 toggle = gtk_check_button_new_with_mnemonic (_("C_reate new image"));
479 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), tvals.new_image);
480 gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0);
481 gtk_widget_show (toggle);
482
483 g_signal_connect (toggle, "toggled",
484 G_CALLBACK (gimp_toggle_button_update),
485 &tvals.new_image);
486
487 gtk_widget_show (dlg);
488
489 run = (gimp_dialog_run (GIMP_DIALOG (dlg)) == GTK_RESPONSE_OK);
490
491 if (run)
492 {
493 tvals.new_width =
494 RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (sizeentry), 0));
495 tvals.new_height =
496 RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (sizeentry), 1));
497
498 tvals.constrain =
499 gimp_chain_button_get_active (GIMP_CHAIN_BUTTON (chainbutton));
500 }
501
502 gtk_widget_destroy (dlg);
503
504 return run;
505 }
506