1 /**
2  * aa.c version 1.0
3  * A plugin that uses libaa (ftp://ftp.ta.jcu.cz/pub/aa) to save images as
4  * ASCII.
5  * NOTE: This plugin *requires* aalib 1.2 or later. Earlier versions will
6  * not work.
7  * Code copied from all over the GIMP source.
8  * Tim Newsome <nuisance@cmu.edu>
9  */
10 
11 /* GIMP - The GNU Image Manipulation Program
12  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
13  *
14  * This program is free software: you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 3 of the License, or
17  * (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
26  */
27 
28 #include "config.h"
29 
30 #include <string.h>
31 
32 #include <aalib.h>
33 
34 #include <libgimp/gimp.h>
35 #include <libgimp/gimpui.h>
36 
37 #include "libgimp/stdplugins-intl.h"
38 
39 
40 #define SAVE_PROC      "file-aa-save"
41 #define PLUG_IN_BINARY "file-aa"
42 #define PLUG_IN_ROLE   "gimp-file-aa"
43 
44 
45 /*
46  * Declare some local functions.
47  */
48 static void     query       (void);
49 static void     run         (const gchar      *name,
50                              gint              nparams,
51                              const GimpParam  *param,
52                              gint             *nreturn_vals,
53                              GimpParam       **return_vals);
54 static gboolean save_aa     (gint32            drawable_ID,
55                              gchar            *filename,
56                              gint              output_type);
57 static void     gimp2aa     (gint32            drawable_ID,
58                              aa_context       *context);
59 
60 static gint     aa_dialog   (gint              selected);
61 
62 
63 /*
64  * Some global variables.
65  */
66 
67 const GimpPlugInInfo PLUG_IN_INFO =
68 {
69   NULL,  /* init_proc  */
70   NULL,  /* quit_proc  */
71   query, /* query_proc */
72   run,   /* run_proc   */
73 };
74 
75 
MAIN()76 MAIN ()
77 
78 static void
79 query (void)
80 {
81   static const GimpParamDef save_args[] =
82   {
83     {GIMP_PDB_INT32,    "run-mode",     "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }"},
84     {GIMP_PDB_IMAGE,    "image",        "Input image"},
85     {GIMP_PDB_DRAWABLE, "drawable",     "Drawable to save"},
86     {GIMP_PDB_STRING,   "filename",     "The name of the file to save the image in"},
87     {GIMP_PDB_STRING,   "raw-filename", "The name entered"},
88     {GIMP_PDB_STRING,   "file-type",    "File type to use"}
89   };
90 
91   gimp_install_procedure (SAVE_PROC,
92                           "Saves grayscale image in various text formats",
93                           "This plug-in uses aalib to save grayscale image "
94                           "as ascii art into a variety of text formats",
95                           "Tim Newsome <nuisance@cmu.edu>",
96                           "Tim Newsome <nuisance@cmu.edu>",
97                           "1997",
98                           N_("ASCII art"),
99                           "RGB*, GRAY*, INDEXED*",
100                           GIMP_PLUGIN,
101                           G_N_ELEMENTS (save_args), 0,
102                           save_args, NULL);
103 
104   gimp_register_file_handler_mime (SAVE_PROC, "text/plain");
105   gimp_register_save_handler (SAVE_PROC, "txt,ansi,text", "");
106 }
107 
108 /**
109  * Searches aa_formats defined by aalib to find the index of the type
110  * specified by string.
111  * -1 means it wasn't found.
112  */
113 static gint
get_type_from_string(const gchar * string)114 get_type_from_string (const gchar *string)
115 {
116   gint type = 0;
117   aa_format **p = (aa_format **) aa_formats;
118 
119   while (*p && strcmp ((*p)->formatname, string))
120     {
121       p++;
122       type++;
123     }
124 
125   if (*p == NULL)
126     return -1;
127 
128   return type;
129 }
130 
131 static void
run(const gchar * name,gint nparams,const GimpParam * param,gint * nreturn_vals,GimpParam ** return_vals)132 run (const gchar      *name,
133      gint              nparams,
134      const GimpParam  *param,
135      gint             *nreturn_vals,
136      GimpParam       **return_vals)
137 {
138   static GimpParam  values[2];
139   GimpRunMode       run_mode;
140   GimpPDBStatusType status = GIMP_PDB_SUCCESS;
141   gint              output_type = 0;
142   gint32            image_ID;
143   gint32            drawable_ID;
144   GimpExportReturn  export = GIMP_EXPORT_CANCEL;
145 
146   INIT_I18N ();
147   gegl_init (NULL, NULL);
148 
149   *nreturn_vals = 1;
150   *return_vals  = values;
151 
152   values[0].type          = GIMP_PDB_STATUS;
153   values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
154 
155   run_mode    = param[0].data.d_int32;
156   image_ID    = param[1].data.d_int32;
157   drawable_ID = param[2].data.d_int32;
158 
159   switch (run_mode)
160     {
161     case GIMP_RUN_INTERACTIVE:
162     case GIMP_RUN_WITH_LAST_VALS:
163       gimp_ui_init (PLUG_IN_BINARY, FALSE);
164 
165       export = gimp_export_image (&image_ID, &drawable_ID, "AA",
166                                   GIMP_EXPORT_CAN_HANDLE_RGB     |
167                                   GIMP_EXPORT_CAN_HANDLE_GRAY    |
168                                   GIMP_EXPORT_CAN_HANDLE_INDEXED |
169                                   GIMP_EXPORT_CAN_HANDLE_ALPHA);
170 
171       if (export == GIMP_EXPORT_CANCEL)
172         {
173           values[0].data.d_status = GIMP_PDB_CANCEL;
174           return;
175         }
176       break;
177     default:
178       break;
179     }
180 
181   if (! (gimp_drawable_is_rgb (drawable_ID) ||
182          gimp_drawable_is_gray (drawable_ID)))
183     {
184       status = GIMP_PDB_CALLING_ERROR;
185     }
186 
187   if (status == GIMP_PDB_SUCCESS)
188     {
189       switch (run_mode)
190         {
191         case GIMP_RUN_INTERACTIVE:
192           gimp_get_data (SAVE_PROC, &output_type);
193           output_type = aa_dialog (output_type);
194           if (output_type < 0)
195             status = GIMP_PDB_CANCEL;
196           break;
197 
198         case GIMP_RUN_NONINTERACTIVE:
199           /*  Make sure all the arguments are there!  */
200           if (nparams != 6)
201             {
202               status = GIMP_PDB_CALLING_ERROR;
203             }
204           else
205             {
206               output_type = get_type_from_string (param[5].data.d_string);
207               if (output_type < 0)
208                 status = GIMP_PDB_CALLING_ERROR;
209             }
210           break;
211 
212         case GIMP_RUN_WITH_LAST_VALS:
213           gimp_get_data (SAVE_PROC, &output_type);
214           break;
215 
216         default:
217           break;
218         }
219     }
220 
221   if (status == GIMP_PDB_SUCCESS)
222     {
223       if (save_aa (drawable_ID, param[3].data.d_string, output_type))
224         {
225           gimp_set_data (SAVE_PROC, &output_type, sizeof (output_type));
226         }
227       else
228         {
229           status = GIMP_PDB_EXECUTION_ERROR;
230         }
231     }
232 
233   if (export == GIMP_EXPORT_EXPORT)
234     gimp_image_delete (image_ID);
235 
236   values[0].data.d_status = status;
237 }
238 
239 /**
240  * The actual save function. What it's all about.
241  * The image type has to be GRAY.
242  */
243 static gboolean
save_aa(gint32 drawable_ID,gchar * filename,gint output_type)244 save_aa (gint32  drawable_ID,
245          gchar  *filename,
246          gint    output_type)
247 {
248   aa_savedata  savedata;
249   aa_context  *context;
250   aa_format    format = *aa_formats[output_type];
251 
252   format.width  = gimp_drawable_width (drawable_ID)  / 2;
253   format.height = gimp_drawable_height (drawable_ID) / 2;
254 
255   /* Get a libaa context which will save its output to filename. */
256   savedata.name   = filename;
257   savedata.format = &format;
258 
259   context = aa_init (&save_d, &aa_defparams, &savedata);
260   if (!context)
261     return FALSE;
262 
263   gimp2aa (drawable_ID, context);
264   aa_flush (context);
265   aa_close (context);
266 
267   return TRUE;
268 }
269 
270 static void
gimp2aa(gint32 drawable_ID,aa_context * context)271 gimp2aa (gint32      drawable_ID,
272          aa_context *context)
273 {
274   GeglBuffer      *buffer;
275   const Babl      *format;
276   aa_renderparams *renderparams;
277   gint             width;
278   gint             height;
279   gint             x, y;
280   gint             bpp;
281   guchar          *buf;
282   guchar          *p;
283 
284   buffer = gimp_drawable_get_buffer (drawable_ID);
285 
286   width  = aa_imgwidth  (context);
287   height = aa_imgheight (context);
288 
289   switch (gimp_drawable_type (drawable_ID))
290     {
291     case GIMP_GRAY_IMAGE:
292       format = babl_format ("Y' u8");
293       break;
294 
295     case GIMP_GRAYA_IMAGE:
296       format = babl_format ("Y'A u8");
297       break;
298 
299     case GIMP_RGB_IMAGE:
300     case GIMP_INDEXED_IMAGE:
301       format = babl_format ("R'G'B' u8");
302       break;
303 
304     case GIMP_RGBA_IMAGE:
305     case GIMP_INDEXEDA_IMAGE:
306       format = babl_format ("R'G'B'A u8");
307       break;
308 
309     default:
310       g_return_if_reached ();
311       break;
312     }
313 
314   bpp = babl_format_get_bytes_per_pixel (format);
315 
316   buf = g_new (guchar, width * bpp);
317 
318   for (y = 0; y < height; y++)
319     {
320       gegl_buffer_get (buffer, GEGL_RECTANGLE (0, y, width, 1), 1.0,
321                        format, buf,
322                        GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
323 
324       switch (bpp)
325         {
326         case 1:  /* GRAY */
327           for (x = 0, p = buf; x < width; x++, p++)
328             aa_putpixel (context, x, y, *p);
329           break;
330 
331         case 2:  /* GRAYA, blend over black */
332           for (x = 0, p = buf; x < width; x++, p += 2)
333             aa_putpixel (context, x, y, (p[0] * (p[1] + 1)) >> 8);
334           break;
335 
336         case 3:  /* RGB */
337           for (x = 0, p = buf; x < width; x++, p += 3)
338             aa_putpixel (context, x, y,
339                          GIMP_RGB_LUMINANCE (p[0], p[1], p[2]) + 0.5);
340           break;
341 
342         case 4:  /* RGBA, blend over black */
343           for (x = 0, p = buf; x < width; x++, p += 4)
344             aa_putpixel (context, x, y,
345                          ((guchar) (GIMP_RGB_LUMINANCE (p[0], p[1], p[2]) + 0.5)
346                           * (p[3] + 1)) >> 8);
347           break;
348 
349         default:
350           g_assert_not_reached ();
351           break;
352         }
353     }
354 
355   g_free (buf);
356 
357   g_object_unref (buffer);
358 
359   renderparams = aa_getrenderparams ();
360   renderparams->dither = AA_FLOYD_S;
361 
362   aa_render (context, renderparams, 0, 0,
363              aa_scrwidth (context), aa_scrheight (context));
364 }
365 
366 static gint
aa_dialog(gint selected)367 aa_dialog (gint selected)
368 {
369   GtkWidget *dialog;
370   GtkWidget *hbox;
371   GtkWidget *label;
372   GtkWidget *combo;
373   gint       i;
374 
375   /* Create the actual window. */
376   dialog = gimp_export_dialog_new (_("Text"), PLUG_IN_BINARY, SAVE_PROC);
377 
378   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
379   gtk_container_set_border_width (GTK_CONTAINER (hbox), 12);
380   gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)),
381                       hbox, FALSE, FALSE, 0);
382   gtk_widget_show (hbox);
383 
384   label = gtk_label_new_with_mnemonic (_("_Format:"));
385   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
386   gtk_widget_show (label);
387 
388   combo = g_object_new (GIMP_TYPE_INT_COMBO_BOX, NULL);
389   gtk_box_pack_start (GTK_BOX (hbox), combo, TRUE, TRUE, 0);
390   gtk_widget_show (combo);
391 
392   gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo);
393 
394   for (i = 0; aa_formats[i]; i++)
395     gimp_int_combo_box_append (GIMP_INT_COMBO_BOX (combo),
396                                GIMP_INT_STORE_VALUE, i,
397                                GIMP_INT_STORE_LABEL, aa_formats[i]->formatname,
398                                -1);
399 
400   gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), selected,
401                               G_CALLBACK (gimp_int_combo_box_get_active),
402                               &selected);
403 
404   gtk_widget_show (dialog);
405 
406   if (gimp_dialog_run (GIMP_DIALOG (dialog)) != GTK_RESPONSE_OK)
407     selected = -1;
408 
409   gtk_widget_destroy (dialog);
410 
411   return selected;
412 }
413