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