/* This is a plugin for PIKA. * * Copyright (C) 1997 Jochen Friedrich * Parts Copyright (C) 1995 Gert Doering * Parts 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 . */ #include "config.h" #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include /* For G_OS_WIN32 */ #include #ifdef G_OS_WIN32 #include #endif #ifndef _O_BINARY #define _O_BINARY 0 #endif #include #include "g3.h" #include "libpika/stdplugins-intl.h" #define LOAD_PROC "file-faxg3-load" #define VERSION "0.6" typedef struct _Faxg3 Faxg3; typedef struct _Faxg3Class Faxg3Class; struct _Faxg3 { PikaPlugIn parent_instance; }; struct _Faxg3Class { PikaPlugInClass parent_class; }; #define FAXG3_TYPE (faxg3_get_type ()) #define FAXG3 (obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), FAXG3_TYPE, Faxg3)) GType faxg3_get_type (void) G_GNUC_CONST; static GList * faxg3_query_procedures (PikaPlugIn *plug_in); static PikaProcedure * faxg3_create_procedure (PikaPlugIn *plug_in, const gchar *name); static PikaValueArray * faxg3_load (PikaProcedure *procedure, PikaRunMode run_mode, GFile *file, const PikaValueArray *args, gpointer run_data); static PikaImage * load_image (GFile *file, GError **error); static PikaImage * emitpika (gint hcol, gint row, const gchar *bitmap, gint bperrow, GFile *file, GError **error); G_DEFINE_TYPE (Faxg3, faxg3, PIKA_TYPE_PLUG_IN) PIKA_MAIN (FAXG3_TYPE) DEFINE_STD_SET_I18N static void faxg3_class_init (Faxg3Class *klass) { PikaPlugInClass *plug_in_class = PIKA_PLUG_IN_CLASS (klass); plug_in_class->query_procedures = faxg3_query_procedures; plug_in_class->create_procedure = faxg3_create_procedure; plug_in_class->set_i18n = STD_SET_I18N; } static void faxg3_init (Faxg3 *faxg3) { } static GList * faxg3_query_procedures (PikaPlugIn *plug_in) { return g_list_append (NULL, g_strdup (LOAD_PROC)); } static PikaProcedure * faxg3_create_procedure (PikaPlugIn *plug_in, const gchar *name) { PikaProcedure *procedure = NULL; if (! strcmp (name, LOAD_PROC)) { procedure = pika_load_procedure_new (plug_in, name, PIKA_PDB_PROC_TYPE_PLUGIN, faxg3_load, NULL, NULL); pika_procedure_set_menu_label (procedure, _("G3 fax image")); pika_procedure_set_documentation (procedure, "Loads g3 fax files", "This plug-in loads Fax G3 Image files.", name); pika_procedure_set_attribution (procedure, "Jochen Friedrich", "Jochen Friedrich, Gert Doering, " "Spencer Kimball & Peter Mattis", NULL); pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure), "image/g3-fax"); pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure), "g3"); pika_file_procedure_set_magics (PIKA_FILE_PROCEDURE (procedure), "4,string,Research"); } return procedure; } static PikaValueArray * faxg3_load (PikaProcedure *procedure, PikaRunMode run_mode, GFile *file, const PikaValueArray *args, gpointer run_data) { PikaValueArray *return_vals; PikaImage *image; GError *error = NULL; gegl_init (NULL, NULL); image = load_image (file, &error); if (! image) return pika_procedure_new_return_values (procedure, PIKA_PDB_EXECUTION_ERROR, error); return_vals = pika_procedure_new_return_values (procedure, PIKA_PDB_SUCCESS, NULL); PIKA_VALUES_SET_IMAGE (return_vals, 1, image); return return_vals; } #ifdef DEBUG void putbin (unsigned long d) { unsigned long i = 0x80000000; while (i != 0) { putc((d & i) ? '1' : '0', stderr); i >>= 1; } putc('\n', stderr); } #endif static int byte_tab[256]; /* static int o_stretch; */ /* -stretch: double each line */ /* static int o_stretch_force=-1; */ /* -1: guess from filename */ /* static int o_lj; */ /* -l: LJ output */ /* static int o_turn; */ /* -t: turn 90 degrees right */ struct g3_tree * black, * white; #define CHUNK 2048; static char rbuf[2048]; /* read buffer */ static int rp; /* read pointer */ static int rs; /* read buffer size */ #define MAX_ROWS 4300 #define MAX_COLS 1728 /* !! FIXME - command line parameter */ static PikaImage * load_image (GFile *file, GError **error) { int data; int hibit; struct g3_tree *p; int nr_pels; int fd; int color; int i, rr, rsize; int cons_eol; int last_eol_row; PikaImage *image = NULL; gint bperrow = MAX_COLS/8; /* bytes per bit row */ gchar *bitmap; /* MAX_ROWS by (bperrow) bytes */ gchar *bp; /* bitmap pointer */ gint row; gint max_rows; /* max. rows allocated */ gint col, hcol; /* column, highest column ever used */ pika_progress_init_printf (_("Opening '%s'"), pika_file_get_utf8_name (file)); /* initialize lookup trees */ build_tree (&white, t_white); build_tree (&white, m_white); build_tree (&black, t_black); build_tree (&black, m_black); init_byte_tab (0, byte_tab); fd = g_open (g_file_peek_path (file), O_RDONLY | _O_BINARY, 0); if (fd < 0) { g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), _("Could not open '%s' for reading: %s"), pika_file_get_utf8_name (file), g_strerror (errno)); return NULL; } hibit = 0; data = 0; cons_eol = 0; /* consecutive EOLs read - zero yet */ last_eol_row = 0; color = 0; /* start with white */ rr = 0; rsize = lseek (fd, 0L, SEEK_END); lseek (fd, 0L, 0); rs = read (fd, rbuf, sizeof (rbuf)); if (rs < 0) { perror ("read"); close (fd); pika_quit (); } rr += rs; pika_progress_update ((float) rr / rsize / 2.0); /* skip GhostScript header */ rp = (rs >= 64 && strcmp (rbuf + 1, "PC Research, Inc") == 0) ? 64 : 0; /* initialize bitmap */ row = col = hcol = 0; bitmap = g_new0 (gchar, (max_rows = MAX_ROWS) * MAX_COLS / 8); if (! bitmap) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("Could not create buffer to process image data.")); return NULL; } bp = &bitmap[row * MAX_COLS / 8]; while (rs > 0 && cons_eol < 10) /* i.e., while (!EOF) */ { #ifdef DEBUG g_printerr ("hibit=%2d, data=", hibit); putbin (data); #endif while (hibit < 20) { data |= (byte_tab[(int) (unsigned char) rbuf[rp++]] << hibit); hibit += 8; if (rp >= rs) { rs = read (fd, rbuf, sizeof (rbuf)); if (rs < 0) { perror ("read2"); break; } rr += rs; pika_progress_update ((float) rr / rsize / 2.0); rp = 0; if (rs == 0) goto do_write; } #ifdef DEBUG g_printerr ("hibit=%2d, data=", hibit); putbin (data); #endif } if (color == 0) /* white */ p = white->nextb[data & BITM]; else /* black */ p = black->nextb[data & BITM]; while (p != NULL && ! (p->nr_bits)) { data >>= FBITS; hibit -= FBITS; p = p->nextb[data & BITM]; } if (p == NULL) /* invalid code */ { g_printerr ("invalid code, row=%d, col=%d, file offset=%lx, skip to eol\n", row, col, (unsigned long) lseek (fd, 0, 1) - rs + rp); while ((data & 0x03f) != 0) { data >>= 1; hibit--; if ( hibit < 20 ) { data |= (byte_tab[(int) (unsigned char) rbuf[rp++]] << hibit); hibit += 8; if (rp >= rs) /* buffer underrun */ { rs = read (fd, rbuf, sizeof (rbuf)); if (rs < 0) { perror ("read4"); break; } rr += rs; pika_progress_update ((float) rr / rsize / 2.0); rp = 0; if (rs == 0) goto do_write; } } } nr_pels = -1; /* handle as if eol */ } else /* p != NULL <-> valid code */ { data >>= p->nr_bits; hibit -= p->nr_bits; nr_pels = ((struct g3_leaf *) p)->nr_pels; #ifdef DEBUG g_printerr ("PELs: %d (%c)\n", nr_pels, '0' + color); #endif } /* handle EOL (including fill bits) */ if (nr_pels == -1) { #ifdef DEBUG g_printerr ("hibit=%2d, data=", hibit); putbin (data); #endif /* skip filler 0bits -> seek for "1"-bit */ while ((data & 0x01) != 1) { if ((data & 0xf) == 0) /* nibble optimization */ { hibit-= 4; data >>= 4; } else { hibit--; data >>= 1; } /* fill higher bits */ if (hibit < 20) { data |= ( byte_tab[(int) (unsigned char) rbuf[ rp++]] << hibit); hibit += 8; if (rp >= rs) /* buffer underrun */ { rs = read (fd, rbuf, sizeof (rbuf)); if ( rs < 0 ) { perror ("read3"); break; } rr += rs; pika_progress_update ((float) rr / rsize / 2.0); rp = 0; if (rs == 0) goto do_write; } } #ifdef DEBUG g_printerr ("hibit=%2d, data=", hibit ); putbin(data); #endif } /* end skip 0bits */ hibit--; data >>=1; color = 0; if (col == 0) { if (last_eol_row != row) { cons_eol++; /* consecutive EOLs */ last_eol_row = row; } } else { if (col > hcol && col <= MAX_COLS) hcol = col; row++; /* bitmap memory full? make it larger! */ if (row >= max_rows) { gchar *p = g_try_realloc (bitmap, (max_rows += 500) * MAX_COLS / 8); if (p == NULL) { perror ("realloc() failed, page truncated"); rs = 0; } else { bitmap = p; memset (&bitmap[ row * MAX_COLS / 8 ], 0, (max_rows - row) * MAX_COLS / 8); } } col=0; bp = &bitmap[row * MAX_COLS / 8]; cons_eol = 0; } } else /* not eol */ { if (col + nr_pels > MAX_COLS) nr_pels = MAX_COLS - col; if (color == 0) /* white */ { col += nr_pels; } else /* black */ { register int bit = (0x80 >> (col & 07)); register char *w = & bp[col >> 3]; for (i = nr_pels; i > 0; i--) { *w |= bit; bit >>=1; if (bit == 0) { bit = 0x80; w++; } col++; } } if (nr_pels < 64) color = !color; /* terminating code */ } } /* end main loop */ do_write: /* write pbm (or whatever) file */ if (fd != 0) close (fd); /* close input file */ #ifdef DEBUG g_printerr ("consecutive EOLs: %d, max columns: %d\n", cons_eol, hcol); #endif image = emitpika (hcol, row, bitmap, bperrow, file, error); g_free (bitmap); return image; } /* hcol is the number of columns, row the number of rows * bperrow is the number of bytes actually used by hcol, which may * be greater than (hcol+7)/8 [in case of an unscaled g3 image less * than 1728 pixels wide] */ static PikaImage * emitpika (gint hcol, gint row, const gchar *bitmap, gint bperrow, GFile *file, GError **error) { GeglBuffer *buffer; PikaImage *image; PikaLayer *layer; guchar *buf; guchar tmp; gint x, y; gint xx, yy; gint tile_height; /* initialize */ tmp = 0; #ifdef DEBUG g_printerr ("emit pika: %d x %d\n", hcol, row); #endif if (hcol > PIKA_MAX_IMAGE_SIZE || hcol <= 0 || row > PIKA_MAX_IMAGE_SIZE || row <= 0) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("Invalid image dimensions (%d x %d). " "Image may be corrupt."), hcol, row); return NULL; } image = pika_image_new (hcol, row, PIKA_GRAY); if (! image) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("Could not create image.")); return NULL; } layer = pika_layer_new (image, _("Background"), hcol, row, PIKA_GRAY_IMAGE, 100, pika_image_get_default_new_layer_mode (image)); pika_image_insert_layer (image, layer, NULL, 0); buffer = pika_drawable_get_buffer (PIKA_DRAWABLE (layer)); tile_height = pika_tile_height (); #ifdef DEBUG g_printerr ("tile height: %d\n", tile_height); #endif buf = g_new (guchar, hcol * tile_height); if (! buf) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("Could not create buffer to process image data.")); g_object_unref (buffer); pika_image_delete(image); return NULL; } xx = 0; yy = 0; for (y = 0; y < row; y++) { for (x = 0; x < hcol; x++) { if ((x & 7) == 0) tmp = bitmap[y * bperrow + (x >> 3)]; buf[xx++] = tmp&(128 >> (x & 7)) ? 0 : 255; } if ((y - yy) == tile_height - 1) { #ifdef DEBUG g_printerr ("update tile height: %d\n", tile_height); #endif gegl_buffer_set (buffer, GEGL_RECTANGLE (0, yy, hcol, tile_height), 0, NULL, buf, GEGL_AUTO_ROWSTRIDE); pika_progress_update (0.5 + (float) y / row / 2.0); xx = 0; yy += tile_height; } } if (row - yy) { #ifdef DEBUG g_printerr ("update rest: %d\n", row-yy); #endif gegl_buffer_set (buffer, GEGL_RECTANGLE (0, yy, hcol, row - yy), 0, NULL, buf, GEGL_AUTO_ROWSTRIDE); } pika_progress_update (1.0); g_free (buf); g_object_unref (buffer); return image; }