1 /*
2 * ZealousCrop plug-in version 1.00
3 * by Adam D. Moss <adam@foxbox.org>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19 #include "config.h"
20
21 #include <libgimp/gimp.h>
22
23 #include "libgimp/stdplugins-intl.h"
24
25 #define PLUG_IN_PROC "plug-in-zealouscrop"
26
27 #define EPSILON (1e-5)
28 #define FLOAT_IS_ZERO(value) (value > -EPSILON && value < EPSILON)
29 #define FLOAT_EQUAL(v1, v2) ((v1 - v2) > -EPSILON && (v1 - v2) < EPSILON)
30
31 /* Declare local functions. */
32
33 static void query (void);
34 static void run (const gchar *name,
35 gint nparams,
36 const GimpParam *param,
37 gint *nreturn_vals,
38 GimpParam **return_vals);
39
40 static inline gboolean colors_equal (const gfloat *col1,
41 const gfloat *col2,
42 gint components,
43 gboolean has_alpha);
44 static void do_zcrop (gint32 drawable_id,
45 gint32 image_id);
46
47 const GimpPlugInInfo PLUG_IN_INFO =
48 {
49 NULL, /* init_proc */
50 NULL, /* quit_proc */
51 query, /* query_proc */
52 run, /* run_proc */
53 };
54
55
MAIN()56 MAIN ()
57
58 static void
59 query (void)
60 {
61 static const GimpParamDef args[] =
62 {
63 { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
64 { GIMP_PDB_IMAGE, "image", "Input image" },
65 { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }
66 };
67
68 gimp_install_procedure (PLUG_IN_PROC,
69 N_("Autocrop unused space from edges and middle"),
70 "",
71 "Adam D. Moss",
72 "Adam D. Moss",
73 "1997",
74 N_("_Zealous Crop"),
75 "RGB*, GRAY*, INDEXED*",
76 GIMP_PLUGIN,
77 G_N_ELEMENTS (args), 0,
78 args, NULL);
79
80 gimp_plugin_menu_register (PLUG_IN_PROC, "<Image>/Image/Crop");
81 }
82
83 static void
run(const gchar * name,gint n_params,const GimpParam * param,gint * nreturn_vals,GimpParam ** return_vals)84 run (const gchar *name,
85 gint n_params,
86 const GimpParam *param,
87 gint *nreturn_vals,
88 GimpParam **return_vals)
89 {
90 static GimpParam values[1];
91 GimpRunMode run_mode;
92 GimpPDBStatusType status = GIMP_PDB_SUCCESS;
93 gint32 drawable_id;
94 gint32 image_id;
95
96 INIT_I18N ();
97 gegl_init (NULL, NULL);
98
99 *nreturn_vals = 1;
100 *return_vals = values;
101
102 run_mode = param[0].data.d_int32;
103
104 if (run_mode == GIMP_RUN_NONINTERACTIVE)
105 {
106 if (n_params != 3)
107 {
108 status = GIMP_PDB_CALLING_ERROR;
109 }
110 }
111
112 if (status == GIMP_PDB_SUCCESS)
113 {
114 /* Get the specified drawable */
115 image_id = param[1].data.d_int32;
116 drawable_id = param[2].data.d_int32;
117
118 /* Make sure that the drawable is gray or RGB or indexed */
119 if (gimp_drawable_is_rgb (drawable_id) ||
120 gimp_drawable_is_gray (drawable_id) ||
121 gimp_drawable_is_indexed (drawable_id))
122 {
123 gimp_progress_init (_("Zealous cropping"));
124
125 do_zcrop (drawable_id, image_id);
126
127 if (run_mode != GIMP_RUN_NONINTERACTIVE)
128 gimp_displays_flush ();
129 }
130 else
131 {
132 status = GIMP_PDB_EXECUTION_ERROR;
133 }
134 }
135
136 values[0].type = GIMP_PDB_STATUS;
137 values[0].data.d_status = status;
138
139 gegl_exit ();
140 }
141
142 static inline gboolean
colors_equal(const gfloat * col1,const gfloat * col2,gint components,gboolean has_alpha)143 colors_equal (const gfloat *col1,
144 const gfloat *col2,
145 gint components,
146 gboolean has_alpha)
147 {
148 if (has_alpha &&
149 FLOAT_IS_ZERO (col1[components - 1]) &&
150 FLOAT_IS_ZERO (col2[components - 1]))
151 {
152 return TRUE;
153 }
154 else
155 {
156 gint b;
157
158 for (b = 0; b < components; b++)
159 {
160 if (! FLOAT_EQUAL (col1[b], col2[b]))
161 return FALSE;
162 }
163
164 return TRUE;
165 }
166 }
167
168 static void
do_zcrop(gint32 drawable_id,gint32 image_id)169 do_zcrop (gint32 drawable_id,
170 gint32 image_id)
171 {
172 GeglBuffer *drawable_buffer;
173 GeglBuffer *shadow_buffer;
174 gfloat *linear_buf;
175 const Babl *format;
176
177 gint x, y, width, height;
178 gint components;
179 gint8 *killrows;
180 gint8 *killcols;
181 gint32 livingrows, livingcols, destrow, destcol;
182 gint32 selection_copy_id;
183 gboolean has_alpha;
184
185 drawable_buffer = gimp_drawable_get_buffer (drawable_id);
186 shadow_buffer = gimp_drawable_get_shadow_buffer (drawable_id);
187
188 width = gegl_buffer_get_width (drawable_buffer);
189 height = gegl_buffer_get_height (drawable_buffer);
190 has_alpha = gimp_drawable_has_alpha (drawable_id);
191
192 if (has_alpha)
193 format = babl_format ("R'G'B'A float");
194 else
195 format = babl_format ("R'G'B' float");
196
197 components = babl_format_get_n_components (format);
198
199 killrows = g_new (gint8, height);
200 killcols = g_new (gint8, width);
201
202 linear_buf = g_new (gfloat, (width > height ? width : height) * components);
203
204 /* search which rows to remove */
205
206 livingrows = 0;
207 for (y = 0; y < height; y++)
208 {
209 gegl_buffer_get (drawable_buffer, GEGL_RECTANGLE (0, y, width, 1),
210 1.0, format, linear_buf,
211 GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
212
213 killrows[y] = TRUE;
214
215 for (x = components; x < width * components; x += components)
216 {
217 if (! colors_equal (linear_buf, &linear_buf[x], components, has_alpha))
218 {
219 livingrows++;
220 killrows[y] = FALSE;
221 break;
222 }
223 }
224 }
225
226 gimp_progress_update (0.25);
227
228 /* search which columns to remove */
229
230 livingcols = 0;
231 for (x = 0; x < width; x++)
232 {
233 gegl_buffer_get (drawable_buffer, GEGL_RECTANGLE (x, 0, 1, height),
234 1.0, format, linear_buf,
235 GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
236
237 killcols[x] = TRUE;
238
239 for (y = components; y < height * components; y += components)
240 {
241 if (! colors_equal (linear_buf, &linear_buf[y], components, has_alpha))
242 {
243 livingcols++;
244 killcols[x] = FALSE;
245 break;
246 }
247 }
248 }
249
250 gimp_progress_update (0.5);
251
252 if ((livingcols == 0 || livingrows == 0) ||
253 (livingcols == width && livingrows == height))
254 {
255 g_message (_("Nothing to crop."));
256
257 g_object_unref (shadow_buffer);
258 g_object_unref (drawable_buffer);
259
260 g_free (linear_buf);
261 g_free (killrows);
262 g_free (killcols);
263 return;
264 }
265
266 /* restitute living rows */
267
268 destrow = 0;
269 for (y = 0; y < height; y++)
270 {
271 if (!killrows[y])
272 {
273 gegl_buffer_copy (drawable_buffer,
274 GEGL_RECTANGLE (0, y, width, 1),
275 GEGL_ABYSS_NONE,
276 shadow_buffer,
277 GEGL_RECTANGLE (0, destrow, width, 1));
278
279 destrow++;
280 }
281 }
282
283 gimp_progress_update (0.75);
284
285 /* restitute living columns */
286
287 destcol = 0;
288 for (x = 0; x < width; x++)
289 {
290 if (!killcols[x])
291 {
292 gegl_buffer_copy (shadow_buffer,
293 GEGL_RECTANGLE (x, 0, 1, height),
294 GEGL_ABYSS_NONE,
295 shadow_buffer,
296 GEGL_RECTANGLE (destcol, 0, 1, height));
297
298 destcol++;
299 }
300 }
301
302 gimp_progress_update (1.00);
303
304 gimp_image_undo_group_start (image_id);
305
306 selection_copy_id = gimp_selection_save (image_id);
307 gimp_selection_none (image_id);
308
309 gegl_buffer_flush (shadow_buffer);
310 gimp_drawable_merge_shadow (drawable_id, TRUE);
311 gegl_buffer_flush (drawable_buffer);
312
313 gimp_image_select_item (image_id, GIMP_CHANNEL_OP_REPLACE, selection_copy_id);
314 gimp_image_remove_channel (image_id, selection_copy_id);
315
316 gimp_image_crop (image_id, livingcols, livingrows, 0, 0);
317
318 gimp_image_undo_group_end (image_id);
319
320 g_object_unref (shadow_buffer);
321 g_object_unref (drawable_buffer);
322
323 g_free (linear_buf);
324 g_free (killrows);
325 g_free (killcols);
326 }
327