/* PIKA - Photo and Image Kooker Application * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * Amiga IFF plug-in * * Copyright (C) 2023 Alex S. * * 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 #include #include #include #include #include #include #include #include "libpika/stdplugins-intl.h" #define LOAD_PROC "file-iff-load" #define PLUG_IN_BINARY "file-iff" #define PLUG_IN_ROLE "pika-file-iff" typedef struct _Iff Iff; typedef struct _IffClass IffClass; struct _Iff { PikaPlugIn parent_instance; }; struct _IffClass { PikaPlugInClass parent_class; }; #define IFF_TYPE (iff_get_type ()) #define IFF (obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IFF_TYPE, Iff)) GType iff_get_type (void) G_GNUC_CONST; static GList * iff_query_procedures (PikaPlugIn *plug_in); static PikaProcedure * iff_create_procedure (PikaPlugIn *plug_in, const gchar *name); static PikaValueArray * iff_load (PikaProcedure *procedure, PikaRunMode run_mode, GFile *file, const PikaValueArray *args, gpointer run_data); static PikaImage * load_image (GFile *file, GObject *config, PikaRunMode run_mode, GError **error); static void deleave_indexed_row (IFF_UByte *bitplanes, guchar *pixel_row, gint width, gint nPlanes); static void deleave_rgb_row (IFF_UByte *bitplanes, guchar *pixel_row, gint width, gint nPlanes, gint pixel_size); static void deleave_ham_row (const guchar *pika_cmap, IFF_UByte *bitplanes, guchar *pixel_row, gint width, gint nPlanes); static void pbm_row (IFF_UByte *bitplanes, guchar *pixel_row, gint width); G_DEFINE_TYPE (Iff, iff, PIKA_TYPE_PLUG_IN) PIKA_MAIN (IFF_TYPE) DEFINE_STD_SET_I18N static void iff_class_init (IffClass *klass) { PikaPlugInClass *plug_in_class = PIKA_PLUG_IN_CLASS (klass); plug_in_class->query_procedures = iff_query_procedures; plug_in_class->create_procedure = iff_create_procedure; plug_in_class->set_i18n = STD_SET_I18N; } static void iff_init (Iff *iff) { } static GList * iff_query_procedures (PikaPlugIn *plug_in) { GList *list = NULL; list = g_list_append (list, g_strdup (LOAD_PROC)); return list; } static PikaProcedure * iff_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, iff_load, NULL, NULL); pika_procedure_set_menu_label (procedure, _("Amiga IFF")); pika_procedure_set_documentation (procedure, _("Load file in the IFF file format"), _("Load file in the IFF file format"), name); pika_procedure_set_attribution (procedure, "Alex S.", "Alex S.", "2023"); pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure), "image/x-ilbm"); pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure), "iff,ilbm,lbm,acbm,ham,ham6,ham8"); pika_file_procedure_set_magics (PIKA_FILE_PROCEDURE (procedure), "0,string,FORM"); } return procedure; } static PikaValueArray * iff_load (PikaProcedure *procedure, PikaRunMode run_mode, GFile *file, const PikaValueArray *args, gpointer run_data) { PikaProcedureConfig *config; PikaValueArray *return_vals; PikaImage *image; GError *error = NULL; gegl_init (NULL, NULL); config = pika_procedure_create_config (procedure); pika_procedure_config_begin_run (config, NULL, run_mode, args); image = load_image (file, G_OBJECT (config), run_mode, &error); if (! image) { pika_procedure_config_end_run (config, PIKA_PDB_EXECUTION_ERROR); g_object_unref (config); return pika_procedure_new_return_values (procedure, PIKA_PDB_EXECUTION_ERROR, error); } pika_procedure_config_end_run (config, PIKA_PDB_SUCCESS); g_object_unref (config); return_vals = pika_procedure_new_return_values (procedure, PIKA_PDB_SUCCESS, NULL); PIKA_VALUES_SET_IMAGE (return_vals, 1, image); return return_vals; } static PikaImage * load_image (GFile *file, GObject *config, PikaRunMode run_mode, GError **error) { PikaImage *image = NULL; PikaLayer *layer; GeglBuffer *buffer; FILE *fp; guint imagesLength; IFF_Chunk *chunk; ILBM_Image **iff_image; fp = g_fopen (g_file_peek_path (file), "rb"); if (! fp) { 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; } fclose (fp); chunk = ILBM_read (g_file_peek_path (file)); iff_image = ILBM_extractImages (chunk, &imagesLength); for (gint i = 0; i < imagesLength; i++) { ILBM_Image *true_image = iff_image[i]; /* Struct representing bitmap header properties */ ILBM_BitMapHeader *bitMapHeader = true_image->bitMapHeader; /* Struct containing the color palette */ ILBM_ColorMap *colorMap = true_image->colorMap; ILBM_Viewport *camg = true_image->viewport; IFF_UByte *bitplanes; PikaImageType image_type; guchar pika_cmap[768]; /* Max index is (2^nplanes) - 1 */ gint width; gint height; gint nPlanes; gint palette_size = 0; gint row_length; gint pixel_size = 1; gint y_height = 0; gboolean ehb_mode = FALSE; gboolean ham_mode = FALSE; if (! true_image || ! bitMapHeader) { g_message (_("Invalid or missing ILBM image")); return image; } if (! true_image->body) { g_message (_("ILBM contains no image data - likely a palette file")); return NULL; } /* Convert ACBM files to ILBM format */ if (ILBM_imageIsACBM (true_image)) ILBM_convertACBMToILBM (true_image); width = bitMapHeader->w; height = bitMapHeader->h; nPlanes = bitMapHeader->nPlanes; row_length = (width + 15) / 16; pixel_size = nPlanes / 8; /* Check for ILBM variants in CMAG chunk */ if (camg) { if (camg->viewportMode & (1 << 7)) ehb_mode = TRUE; if (camg->viewportMode & (1 << 11) && (nPlanes >= 5 && nPlanes <= 8)) ham_mode = TRUE; } /* Load palette if it exists */ if (colorMap) { palette_size = colorMap->colorRegisterLength; for (gint j = 0; j < palette_size; j++) { pika_cmap[j * 3] = colorMap->colorRegister[j].red; pika_cmap[j * 3 + 1] = colorMap->colorRegister[j].green; pika_cmap[j * 3 + 2] = colorMap->colorRegister[j].blue; } if (ehb_mode) { /* EHB mode adds 32 more colors. Each are half the RGB values * of the first 32 colors */ for (gint j = 0; j < palette_size * 2; j++) { gint offset_index = j + 32; pika_cmap[offset_index * 3] = colorMap->colorRegister[j].red / 2; pika_cmap[offset_index * 3 + 1] = colorMap->colorRegister[j].green / 2; pika_cmap[offset_index * 3 + 2] = colorMap->colorRegister[j].blue / 2; } /* EHB mode always has 64 colors */ palette_size = 64; } } if (ham_mode) pixel_size = 3; if (pixel_size == 4) { image_type = PIKA_RGBA_IMAGE; } else if (pixel_size == 3) { image_type = PIKA_RGB_IMAGE; } else { pixel_size = 1; image_type = PIKA_INDEXED_IMAGE; } ILBM_unpackByteRun (true_image); image = pika_image_new (width, height, pixel_size == 1 ? PIKA_INDEXED : PIKA_RGB); layer = pika_layer_new (image, _("Background"), width, height, image_type, 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)); bitplanes = true_image->body->chunkData; /* Loading rows */ for (gint j = 0; j < height; j++) { guchar *pixel_row; pixel_row = g_malloc (width * pixel_size * sizeof (guchar)); /* PBM uses one byte per pixel index */ if (ILBM_imageIsPBM (true_image)) pbm_row (bitplanes, pixel_row, width); else if (pixel_size == 1) deleave_indexed_row (bitplanes, pixel_row, width, nPlanes); else if (ham_mode) deleave_ham_row (pika_cmap, bitplanes, pixel_row, width, nPlanes); else deleave_rgb_row (bitplanes, pixel_row, width, nPlanes, pixel_size); bitplanes += (row_length * 2 * nPlanes); gegl_buffer_set (buffer, GEGL_RECTANGLE (0, y_height, width, 1), 0, NULL, pixel_row, GEGL_AUTO_ROWSTRIDE); y_height++; g_free (pixel_row); } if (pixel_size == 1) pika_image_set_colormap (image, pika_cmap, palette_size); g_object_unref (buffer); } ILBM_freeImages (iff_image, imagesLength); return image; } static void deleave_indexed_row (IFF_UByte *bitplanes, guchar *pixel_row, gint width, gint nPlanes) { guchar index[width]; gint row_length = ((width + 15) / 16) * 2; /* Initialize index array */ for (gint i = 0; i < width; i++) index[i] = 0; /* Deleave rows */ for (gint i = 0; i < row_length; i++) { for (gint j = 0; j < 8; j++) { guint8 bitmask = (1 << (8 - j)) - (1 << (7 - j)); for (gint k = 0; k < nPlanes; k++) { guint8 update = (1 << (k + 1)) - (1 << (k)); if (bitplanes[i + (row_length * k)] & bitmask) index[j + (i * 8)] += update; } } } /* Associate palette with pixels */ for (gint i = 0; i < width; i++) pixel_row[i] = index[i]; } static void deleave_ham_row (const guchar *pika_cmap, IFF_UByte *bitplanes, guchar *pixel_row, gint width, gint nPlanes) { const gint control_index[3] = {2, 0, 1}; const gint row_length = ((width + 15) / 16) * 2; gint prior_rgb[3] = {0, 0, 0}; gint current_index = 0; /* Deleave rows */ for (gint i = 0; i < row_length; i++) { for (gint j = 0; j < 8; j++) { guint8 bitmask = (1 << (8 - j)) - (1 << (7 - j)); guint8 control = 0; guint8 color = 0; guint8 index = 0; for (gint k = 0; k < nPlanes; k++) { if (bitplanes[i + (row_length * k)] & bitmask) { gint limit = nPlanes < 7 ? 4 : 6; /* The last two planes are control values. * Everything else is either an index or a color. * For HAM 5/6 colors, we use the 4 bits as both * upper and lower bit modifiers. For HAM 7/8, * we replace the 6 MSB with the color value. */ if (k < limit) { gint update = 1 << k; index += update; if (limit == 4) color += update + (update << 4); else color += update; } else { control += 1 << (k - limit); } } } if (control == 0) { prior_rgb[0] = pika_cmap[index * 3]; prior_rgb[1] = pika_cmap[index * 3 + 1]; prior_rgb[2] = pika_cmap[index * 3 + 2]; } else { /* Determines which RGB component to modify */ gint modify = control_index[control - 1]; if (nPlanes < 7) prior_rgb[modify] = color; else prior_rgb[modify] = (color << 2) + (prior_rgb[modify] & 3); } pixel_row[current_index * 3] = prior_rgb[0]; pixel_row[current_index * 3 + 1] = prior_rgb[1]; pixel_row[current_index * 3 + 2] = prior_rgb[2]; current_index++; } } } static void deleave_rgb_row (IFF_UByte *bitplanes, guchar *pixel_row, gint width, gint nPlanes, gint pixel_size) { gint row_length = ((width + 15) / 16) * 2; gint current_pixel = 0; /* Initialize index array */ for (gint i = 0; i < (width * pixel_size); i++) pixel_row[i] = 0; /* Deleave rows */ for (gint i = 0; i < row_length; i++) { for (gint j = 0; j < 8; j++) { guint8 bitmask = (1 << (8 - j)) - (1 << (7 - j)); for (gint k = 0; k < pixel_size; k++) { for (gint l = 0; l < 8; l++) { guint8 update = (1 << (l + 1)) - (1 << (l)); if (bitplanes[i + (row_length * (l + k * 8))] & bitmask) pixel_row[current_pixel] += update; } current_pixel++; } } } } static void pbm_row (IFF_UByte *bitplanes, guchar *pixel_row, gint width) { for (gint i = 0; i < width; i++) pixel_row[i] = bitplanes[i]; }