/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /** * aa.c version 1.0 * A plugin that uses libaa (ftp://ftp.ta.jcu.cz/pub/aa) to save images as * ASCII. * NOTE: This plugin *requires* aalib 1.2 or later. Earlier versions will * not work. * Code copied from all over the PIKA source. * Tim Newsome */ #include "config.h" #include #include #include #include #include "libpika/stdplugins-intl.h" #define SAVE_PROC "file-aa-save" #define PLUG_IN_BINARY "file-aa" typedef struct _Ascii Ascii; typedef struct _AsciiClass AsciiClass; struct _Ascii { PikaPlugIn parent_instance; }; struct _AsciiClass { PikaPlugInClass parent_class; }; #define ASCII_TYPE (ascii_get_type ()) #define ASCII(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ASCII_TYPE, Ascii)) GType ascii_get_type (void) G_GNUC_CONST; static GList * ascii_query_procedures (PikaPlugIn *plug_in); static PikaProcedure * ascii_create_procedure (PikaPlugIn *plug_in, const gchar *name); static PikaValueArray * ascii_save (PikaProcedure *procedure, PikaRunMode run_mode, PikaImage *image, gint n_drawables, PikaDrawable **drawables, GFile *file, PikaMetadata *metadata, PikaProcedureConfig *config, gpointer run_data); static gboolean save_aa (GFile *file, PikaDrawable *drawable, GObject *config, GError **error); static void pika2aa (PikaDrawable *drawable, aa_context *context); static gboolean save_dialog (PikaProcedure *procedure, GObject *config, PikaImage *image); G_DEFINE_TYPE (Ascii, ascii, PIKA_TYPE_PLUG_IN) PIKA_MAIN (ASCII_TYPE) DEFINE_STD_SET_I18N static void ascii_class_init (AsciiClass *klass) { PikaPlugInClass *plug_in_class = PIKA_PLUG_IN_CLASS (klass); plug_in_class->query_procedures = ascii_query_procedures; plug_in_class->create_procedure = ascii_create_procedure; plug_in_class->set_i18n = STD_SET_I18N; } static void ascii_init (Ascii *ascii) { } static GList * ascii_query_procedures (PikaPlugIn *plug_in) { return g_list_append (NULL, g_strdup (SAVE_PROC)); } static PikaProcedure * ascii_create_procedure (PikaPlugIn *plug_in, const gchar *name) { PikaProcedure *procedure = NULL; if (! strcmp (name, SAVE_PROC)) { gint i; procedure = pika_save_procedure_new (plug_in, name, PIKA_PDB_PROC_TYPE_PLUGIN, FALSE, ascii_save, NULL, NULL); pika_procedure_set_image_types (procedure, "*"); pika_procedure_set_menu_label (procedure, _("ASCII art")); pika_file_procedure_set_format_name (PIKA_FILE_PROCEDURE (procedure), _("ASCII art")); pika_procedure_set_documentation (procedure, _("Saves grayscale image in various " "text formats"), _("This plug-in uses aalib to save " "grayscale image as ascii art into " "a variety of text formats"), name); pika_procedure_set_attribution (procedure, "Tim Newsome ", "Tim Newsome ", "1997"); pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure), "text/plain"); pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure), "txt,ansi,text"); for (i = 0; aa_formats[i]; i++); PIKA_PROC_ARG_INT (procedure, "file-type", _("_Format"), _("File type to use"), 0, i, 0, G_PARAM_READWRITE); } return procedure; } static PikaValueArray * ascii_save (PikaProcedure *procedure, PikaRunMode run_mode, PikaImage *image, gint n_drawables, PikaDrawable **drawables, GFile *file, PikaMetadata *metadata, PikaProcedureConfig *config, gpointer run_data) { PikaPDBStatusType status = PIKA_PDB_SUCCESS; PikaExportReturn export = PIKA_EXPORT_CANCEL; GError *error = NULL; gegl_init (NULL, NULL); switch (run_mode) { case PIKA_RUN_INTERACTIVE: case PIKA_RUN_WITH_LAST_VALS: pika_ui_init (PLUG_IN_BINARY); export = pika_export_image (&image, &n_drawables, &drawables, "AA", PIKA_EXPORT_CAN_HANDLE_RGB | PIKA_EXPORT_CAN_HANDLE_GRAY | PIKA_EXPORT_CAN_HANDLE_INDEXED | PIKA_EXPORT_CAN_HANDLE_ALPHA); if (export == PIKA_EXPORT_CANCEL) return pika_procedure_new_return_values (procedure, PIKA_PDB_CANCEL, NULL); break; default: break; } if (n_drawables != 1) { g_set_error (&error, G_FILE_ERROR, 0, _("ASCII art does not support multiple layers.")); return pika_procedure_new_return_values (procedure, PIKA_PDB_CALLING_ERROR, error); } if (run_mode == PIKA_RUN_INTERACTIVE) { if (! save_dialog (procedure, G_OBJECT (config), image)) status = PIKA_PDB_CANCEL; } if (status == PIKA_PDB_SUCCESS) { if (! save_aa (file, drawables[0], G_OBJECT (config), &error)) { status = PIKA_PDB_EXECUTION_ERROR; } } if (export == PIKA_EXPORT_EXPORT) { pika_image_delete (image); g_free (drawables); } return pika_procedure_new_return_values (procedure, status, error); } static gboolean save_aa (GFile *file, PikaDrawable *drawable, GObject *config, GError **error) { aa_savedata savedata; aa_context *context; aa_format format; gint output_type; g_object_get (config, "file-type", &output_type, NULL); memcpy (&format, aa_formats[output_type], sizeof (aa_format)); format.width = pika_drawable_get_width (drawable) / 2; format.height = pika_drawable_get_height (drawable) / 2; /* Get a libaa context which will save its output to filename. */ savedata.name = g_file_get_path (file); savedata.format = &format; context = aa_init (&save_d, &aa_defparams, &savedata); if (! context) return FALSE; pika2aa (drawable, context); aa_flush (context); aa_close (context); return TRUE; } static void pika2aa (PikaDrawable *drawable, aa_context *context) { GeglBuffer *buffer; const Babl *format; aa_renderparams *renderparams; gint width; gint height; gint x, y; gint bpp; guchar *buf; guchar *p; buffer = pika_drawable_get_buffer (drawable); width = aa_imgwidth (context); height = aa_imgheight (context); switch (pika_drawable_type (drawable)) { case PIKA_GRAY_IMAGE: format = babl_format ("Y' u8"); break; case PIKA_GRAYA_IMAGE: format = babl_format ("Y'A u8"); break; case PIKA_RGB_IMAGE: case PIKA_INDEXED_IMAGE: format = babl_format ("R'G'B' u8"); break; case PIKA_RGBA_IMAGE: case PIKA_INDEXEDA_IMAGE: format = babl_format ("R'G'B'A u8"); break; default: g_return_if_reached (); break; } bpp = babl_format_get_bytes_per_pixel (format); buf = g_new (guchar, width * bpp); for (y = 0; y < height; y++) { gegl_buffer_get (buffer, GEGL_RECTANGLE (0, y, width, 1), 1.0, format, buf, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); switch (bpp) { case 1: /* GRAY */ for (x = 0, p = buf; x < width; x++, p++) aa_putpixel (context, x, y, *p); break; case 2: /* GRAYA, blend over black */ for (x = 0, p = buf; x < width; x++, p += 2) aa_putpixel (context, x, y, (p[0] * (p[1] + 1)) >> 8); break; case 3: /* RGB */ for (x = 0, p = buf; x < width; x++, p += 3) aa_putpixel (context, x, y, PIKA_RGB_LUMINANCE (p[0], p[1], p[2]) + 0.5); break; case 4: /* RGBA, blend over black */ for (x = 0, p = buf; x < width; x++, p += 4) aa_putpixel (context, x, y, ((guchar) (PIKA_RGB_LUMINANCE (p[0], p[1], p[2]) + 0.5) * (p[3] + 1)) >> 8); break; default: g_assert_not_reached (); break; } } g_free (buf); g_object_unref (buffer); renderparams = aa_getrenderparams (); renderparams->dither = AA_FLOYD_S; aa_render (context, renderparams, 0, 0, aa_scrwidth (context), aa_scrheight (context)); } static gboolean save_dialog (PikaProcedure *procedure, GObject *config, PikaImage *image) { GtkWidget *dialog; GtkListStore *store; GtkWidget *combo; gint i; gboolean run; dialog = pika_save_procedure_dialog_new (PIKA_SAVE_PROCEDURE (procedure), PIKA_PROCEDURE_CONFIG (config), image); store = g_object_new (PIKA_TYPE_INT_STORE, NULL); for (i = 0; aa_formats[i]; i++) gtk_list_store_insert_with_values (store, NULL, -1, PIKA_INT_STORE_VALUE, i, PIKA_INT_STORE_LABEL, aa_formats[i]->formatname, -1); combo = pika_procedure_dialog_get_int_combo (PIKA_PROCEDURE_DIALOG (dialog), "file-type", PIKA_INT_STORE (store)); g_object_set (combo, "margin", 12, NULL); pika_procedure_dialog_fill (PIKA_PROCEDURE_DIALOG (dialog), NULL); gtk_widget_show (dialog); run = pika_procedure_dialog_run (PIKA_PROCEDURE_DIALOG (dialog)); gtk_widget_destroy (dialog); return run; }