From 02cd151b20e823ebe4175794ae6c16eaf0fcd776 Mon Sep 17 00:00:00 2001 From: Rupert Date: Sun, 12 Jan 2025 17:32:32 +0100 Subject: [PATCH 01/13] plug-ins: bmp export - cleanup, compatibility * reorder code to 'de-weave' into more linear logic flow: 1. get info about image 2. show dialog / retrieve options 3. make decisions about header / format * showcase compatibility options at top of file. (temporarily) * require V4 header, not V5 for writing colorspace info * eliminate duplication of color map * delegate writing of masks to write_info_header() * use endianess-agnostic functions to write headers, don't write whole struct to file (alignment!) * always use masks (internally) for rgb files, remove all hardcoded Make_??? functions * pass flags to dialog proc about which feature to enable rather than (fake) raw info about bits/channels * remove unneeded / duplicated variables * be more diligent about possible int overflows with very large files --- plug-ins/file-bmp/bmp-export.c | 1151 +++++++++++++++++++------------- plug-ins/file-bmp/bmp.h | 30 + 2 files changed, 702 insertions(+), 479 deletions(-) diff --git a/plug-ins/file-bmp/bmp-export.c b/plug-ins/file-bmp/bmp-export.c index 103b80b66b4..aa98f196d66 100644 --- a/plug-ins/file-bmp/bmp-export.c +++ b/plug-ins/file-bmp/bmp-export.c @@ -42,45 +42,169 @@ #include "libgimp/stdplugins-intl.h" -static gboolean write_image (FILE *f, - guchar *src, - gint width, - gint height, - gboolean use_run_length_encoding, - gint channels, - gint bpp, - gint spzeile, - gint MapSize, - RGBMode rgb_format, - gint mask_info_size, - gint color_space_size); - -static gboolean save_dialog (GimpProcedure *procedure, - GObject *config, - GimpImage *image, - gint channels, - gint bpp); +/* Compatibilty settings: + * ====================== + * + * These settings control how the file headers are written, they do not limit + * the availability of any features. + */ + +/* + * comp_current_official_headers_only + * ---------------------------------- + * + * Only allow BITMAPINFOHEADER, BITMAPV4HEADER, BITMAPV5HEADER, do not write + * adobe v2/v3 headers (or even BITMAPCOREHEADER, which we never write, + * anyway). + * + * (GIMP < 3.0: FALSE) + */ +static const gboolean comp_current_official_headers_only = TRUE; + + +/* + * comp_16_and_32_only_as_bitfields + * -------------------------------- + * + * The original Windows 3 BMP had 24bit as the only non-indexed format. + * Windows 95 and NT 4.0 introduced 16 and 32 bit, but apparently only as + * BI_BITFIELDS, not as BI_RGB. (Encyclopedia of Grahics File Formats, 2nd + * ed.) + * + * Currently (at least since Windows 98 / NT 5.0), 16 and 32 bit each have a + * standard BI_RGB representation (5-5-5 and 8-8-8). + * + * There might be old software which cannot read 16/32-bit BI_RGB, but there + * might also be newer (simple) software which cannot read BI_BITFIELDS at + * all. There is no certain most compatible setting. Setting to TRUE gives + * the edge to older but more 'serious' programs. + * + * (GIMP < 3.0: TRUE) + */ +static const gboolean comp_16_and_32_only_as_bitfields = TRUE; + + +/* + * comp_min_header_for_bitfields + * ----------------------------- + * + * Minimum header version when masks (BI_BITFIELDS) are used. BMPINFO_V1 is + * acceptable and is probably the most compatible option. (but see next + * section) + * + * (GIMP < 3.0: BMPINFO_V3_ADOBE) + */ +static const enum BmpInfoVer comp_min_header_for_bitfields = BMPINFO_V1; + + +/* + * comp_min_header_for_non_standard_masks + * -------------------------------------- + * + * It gets better. When BI_BITFIELDS was introduced for the v1 header, you + * couldn't just use any old bitmask. For 16-bit, only 5-5-5 and 5-6-5 were + * allowed, for 32-bit, only 8-8-8 was allowed. Current MS documentation for + * the v1 BITMAPINFOHEADER doesn't mention any limitation on 32-bit masks. + * + * I doubt that writing a V4 header for non-standard bitmasks will help with + * compatibility, if anything it'll probably make it worse. + * + * But in case we'll give some compatitbility indication in the export dialog, + * we might want to remember this tidbit. + * + * (GIMP < 3.0: n.a., as currently the only non-standard masks are those with + * alpha-channel, which require a higher header, anyway) + */ +static const gboolean comp_min_header_for_non_standard_masks = BMPINFO_V1; + + +/* + * comp_min_header_for_colorspace + * ------------------------------ + * + * Minimum header version when color space is written. Should be BMPINFO_V4. + * V5 is only needed when actually writing the icc profile to the file. We + * are currently just flagging as sRGB. + * + * (GIMP < 3.0: BMPINFO_V5) + */ +static const enum BmpInfoVer comp_min_header_for_colorspace = BMPINFO_V4; + + + + +static gboolean write_image (FILE *f, + guchar *src, + gint width, + gint height, + gboolean use_run_length_encoding, + gint channels, + gint bpp, + gsize bytes_per_row, + gint ncolors, + BitmapChannel *cmasks, + gint frontmatter_size); + +static gboolean save_dialog (GimpProcedure *procedure, + GObject *config, + GimpImage *image, + gboolean indexed, + gboolean allow_alpha, + gboolean allow_rle); + +static void calc_masks_from_bits (BitmapChannel *cmasks, + gint r, + gint g, + gint b, + gint a); +static gint calc_bitsperpixel_from_masks (BitmapChannel *cmasks); + +static gboolean are_masks_well_known (BitmapChannel *cmasks, + gint bpp); + +static gboolean are_masks_v1_standard (BitmapChannel *cmasks, + gint bpp); + +static void set_info_resolution (BitmapHead *bih, + GimpImage *image); + +static gint info_header_size (enum BmpInfoVer version); + +static gboolean write_u16_le (FILE *file, + guint16 u16); + +static gboolean write_u32_le (FILE *file, + guint32 u32); + +static gboolean write_s32_le (FILE *file, + gint32 s32); + +static gboolean write_file_header (FILE *file, + BitmapFileHead *bfh); + +static gboolean write_info_header (FILE *file, + BitmapHead *bih, + enum BmpInfoVer version); static gboolean -write_color_map (FILE *f, - gint red[MAXCOLORS], - gint green[MAXCOLORS], - gint blue[MAXCOLORS], - gint size) +write_color_map (FILE *f, + guint8 *cmap, + gint ncolors) { - gchar trgb[4]; gint i; - size /= 4; - trgb[3] = 0; - for (i = 0; i < size; i++) + /* BMP color map entries are 4 bytes per color, in the order B-G-R-0 */ + + for (i = 0; i < ncolors; i++) { - trgb[0] = (guchar) blue[i]; - trgb[1] = (guchar) green[i]; - trgb[2] = (guchar) red[i]; - if (fwrite (trgb, 1, 4, f) != 4) - return FALSE; + if (EOF == putc (cmap[3 * i + 2], f) || + EOF == putc (cmap[3 * i + 1], f) || + EOF == putc (cmap[3 * i + 0], f) || + EOF == putc (0, f)) + { + return FALSE; + } } return TRUE; } @@ -121,42 +245,45 @@ export_image (GFile *file, FILE *outfile = NULL; BitmapFileHead bitmap_file_head; BitmapHead bitmap_head; - gint Red[MAXCOLORS]; - gint Green[MAXCOLORS]; - gint Blue[MAXCOLORS]; - guchar *cmap; - gint rows, cols, channels, MapSize; - gint bytes_per_row; - glong BitsPerPixel; - gint colors; + guchar *cmap = NULL; + gint channels; + gsize bytes_per_row; + BitmapChannel cmasks[4]; + gint ncolors = 0; guchar *pixels = NULL; GeglBuffer *buffer; const Babl *format; GimpImageType drawable_type; - gint drawable_width; - gint drawable_height; + gint width, height; gint i; - gint mask_info_size; - gint color_space_size; - guint32 Mask[4]; gboolean use_rle; gboolean write_color_space; - RGBMode rgb_format; - - buffer = gimp_drawable_get_buffer (drawable); - - drawable_type = gimp_drawable_type (drawable); - drawable_width = gimp_drawable_get_width (drawable); - drawable_height = gimp_drawable_get_height (drawable); + RGBMode rgb_format = RGB_888; + enum BmpInfoVer info_version = BMPINFO_V1; + gint frontmatter_size; + gboolean indexed_bmp = FALSE, allow_alpha = FALSE, allow_rle = FALSE; + + memset (&bitmap_file_head, 0, sizeof bitmap_file_head); + memset (&bitmap_head, 0, sizeof bitmap_head); + memset (&cmasks, 0, sizeof cmasks); + + /* WINDOWS_COLOR_SPACE is the most "don't care" option available for V4+ + * headers, which seems a reasonable default. + * Microsoft chose to make 0 the value for CALIBRATED_RGB, which would + * require specifying gamma and endpoints. + */ + bitmap_head.bV4CSType = V4CS_WINDOWS_COLOR_SPACE; + + drawable_type = gimp_drawable_type (drawable); + width = gimp_drawable_get_width (drawable); + height = gimp_drawable_get_height (drawable); switch (drawable_type) { case GIMP_RGBA_IMAGE: format = babl_format ("R'G'B'A u8"); - colors = 0; - BitsPerPixel = 32; - MapSize = 0; channels = 4; + allow_alpha = TRUE; if (run_mode == GIMP_RUN_INTERACTIVE) g_object_set (config, @@ -171,9 +298,6 @@ export_image (GFile *file, case GIMP_RGB_IMAGE: format = babl_format ("R'G'B' u8"); - colors = 0; - BitsPerPixel = 24; - MapSize = 0; channels = 3; if (run_mode == GIMP_RUN_INTERACTIVE) @@ -197,10 +321,6 @@ export_image (GFile *file, /* fallthrough */ case GIMP_GRAY_IMAGE: - colors = 256; - BitsPerPixel = 8; - MapSize = 1024; - if (drawable_type == GIMP_GRAYA_IMAGE) { format = babl_format ("Y'A u8"); @@ -212,12 +332,14 @@ export_image (GFile *file, channels = 1; } - for (i = 0; i < colors; i++) - { - Red[i] = i; - Green[i] = i; - Blue[i] = i; - } + indexed_bmp = TRUE; + ncolors = 256; + + /* create a gray-scale color map */ + cmap = g_malloc (ncolors * 3); + for (i = 0; i < ncolors; i++) + cmap[3 * i + 0] = cmap[3 * i + 1] = cmap[3 * i + 2] = i; + break; case GIMP_INDEXEDA_IMAGE: @@ -231,430 +353,405 @@ export_image (GFile *file, case GIMP_INDEXED_IMAGE: format = gimp_drawable_get_format (drawable); - cmap = gimp_palette_get_colormap (gimp_image_get_palette (image), babl_format ("R'G'B' u8"), &colors, NULL); - MapSize = 4 * colors; + cmap = gimp_palette_get_colormap (gimp_image_get_palette (image), + babl_format ("R'G'B' u8"), &ncolors, NULL); if (drawable_type == GIMP_INDEXEDA_IMAGE) channels = 2; else channels = 1; - if (colors > 16) - BitsPerPixel = 8; - else if (colors > 2) - BitsPerPixel = 4; - else - { - BitsPerPixel = 1; - g_object_set (config, - "use-rle", FALSE, - NULL); - } - - for (i = 0; i < colors; i++) - { - Red[i] = *cmap++; - Green[i] = *cmap++; - Blue[i] = *cmap++; - } + indexed_bmp = TRUE; break; default: g_assert_not_reached (); } - mask_info_size = 0; + if (indexed_bmp) + { + if (ncolors > 2) + allow_rle = TRUE; + else + g_object_set (config, "use-rle", FALSE, NULL); + } + + /* display export dialog and retreive selected options */ - if (run_mode == GIMP_RUN_INTERACTIVE && - (BitsPerPixel == 8 || - BitsPerPixel == 4 || - BitsPerPixel == 1)) + if (run_mode == GIMP_RUN_INTERACTIVE) { - if (! save_dialog (procedure, config, image, 1, BitsPerPixel)) + if (! save_dialog (procedure, config, image, indexed_bmp, allow_alpha, allow_rle)) return GIMP_PDB_CANCEL; } - else if (BitsPerPixel == 24 || - BitsPerPixel == 32) + + g_object_get (config, + "use-rle", &use_rle, + "write-color-space", &write_color_space, + NULL); + + rgb_format = gimp_procedure_config_get_choice_id (GIMP_PROCEDURE_CONFIG (config), + "rgb-format"); + + if (indexed_bmp) { - if (run_mode == GIMP_RUN_INTERACTIVE) + if (ncolors > 16) { - if (! save_dialog (procedure, config, image, channels, BitsPerPixel)) - return GIMP_PDB_CANCEL; + bitmap_head.biBitCnt = 8; + } + else if (ncolors > 2) + { + bitmap_head.biBitCnt = 4; + } + else + { + g_assert (! use_rle); + bitmap_head.biBitCnt = 1; } - rgb_format = - gimp_procedure_config_get_choice_id (GIMP_PROCEDURE_CONFIG (config), - "rgb-format"); + bitmap_head.biClrUsed = ncolors; + bitmap_head.biClrImp = ncolors; - /* mask_info_size is only set to non-zero for 16- and 32-bpp */ + if (use_rle) + bitmap_head.biCompr = bitmap_head.biBitCnt == 8 ? BI_RLE8 : BI_RLE4; + else + bitmap_head.biCompr = BI_RGB; + } + else + { switch (rgb_format) { case RGB_888: - BitsPerPixel = 24; + calc_masks_from_bits (cmasks, 8, 8, 8, 0); break; case RGBA_8888: - BitsPerPixel = 32; - mask_info_size = 16; + calc_masks_from_bits (cmasks, 8, 8, 8, 8); break; case RGBX_8888: - BitsPerPixel = 32; - mask_info_size = 16; + calc_masks_from_bits (cmasks, 8, 8, 8, 0); break; case RGB_565: - BitsPerPixel = 16; - mask_info_size = 16; + calc_masks_from_bits (cmasks, 5, 6, 5, 0); break; case RGBA_5551: - BitsPerPixel = 16; - mask_info_size = 16; + calc_masks_from_bits (cmasks, 5, 5, 5, 1); break; case RGB_555: - BitsPerPixel = 16; - mask_info_size = 16; + calc_masks_from_bits (cmasks, 5, 5, 5, 0); break; default: g_return_val_if_reached (GIMP_PDB_EXECUTION_ERROR); } - } + bitmap_head.biBitCnt = calc_bitsperpixel_from_masks (cmasks); - g_object_get (config, - "use-rle", &use_rle, - "write-color-space", &write_color_space, - NULL); - rgb_format = - gimp_procedure_config_get_choice_id (GIMP_PROCEDURE_CONFIG (config), - "rgb-format"); + /* pointless, but it exists: */ + if (bitmap_head.biBitCnt == 24 && rgb_format == RGBX_8888) + bitmap_head.biBitCnt = 32; + + if (are_masks_well_known (cmasks, bitmap_head.biBitCnt) && + (bitmap_head.biBitCnt == 24 || ! comp_16_and_32_only_as_bitfields)) + { + bitmap_head.biCompr = BI_RGB; + } + else + { + bitmap_head.biCompr = BI_BITFIELDS; + for (gint c = 0; c < 4; c++) + bitmap_head.masks[c] = cmasks[c].mask << cmasks[c].shiftin; + + info_version = MAX (comp_min_header_for_bitfields, info_version); + + if (cmasks[3].mask) /* have alpha channel, need at least v3 */ + info_version = MAX (BMPINFO_V3_ADOBE, info_version); + + if (! are_masks_v1_standard (cmasks, bitmap_head.biBitCnt)) + info_version = MAX (comp_min_header_for_non_standard_masks, info_version); + } + } gimp_progress_init_printf (_("Exporting '%s'"), gimp_file_get_utf8_name (file)); - outfile = g_fopen (g_file_peek_path (file), "wb"); + if (write_color_space) + { + bitmap_head.bV4CSType = V4CS_sRGB; - if (! outfile) + info_version = MAX (BMPINFO_V4, info_version); + info_version = MAX (comp_min_header_for_colorspace, info_version); + } + + if (comp_current_official_headers_only) { - g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), - _("Could not open '%s' for writing: %s"), - gimp_file_get_utf8_name (file), g_strerror (errno)); - return GIMP_PDB_EXECUTION_ERROR; + /* don't use v2/v3 headers */ + if (info_version >= BMPINFO_V2_ADOBE) + info_version = MAX (BMPINFO_V4, info_version); } /* fetch the image */ - pixels = g_new (guchar, (gsize) drawable_width * drawable_height * channels); + pixels = g_new (guchar, (gsize) width * height * channels); + buffer = gimp_drawable_get_buffer (drawable); gegl_buffer_get (buffer, - GEGL_RECTANGLE (0, 0, drawable_width, drawable_height), 1.0, + GEGL_RECTANGLE (0, 0, width, height), 1.0, format, pixels, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); g_object_unref (buffer); - /* Now, we need some further information ... */ - cols = drawable_width; - rows = drawable_height; + /* We should consider rejecting any width > (INT32_MAX - 31) / BitsPerPixel, + * as the resulting BMP will likely cause integer overflow in other + * readers.(Currently, GIMP's limit is way lower, anyway) + */ + g_assert (width <= (G_MAXSIZE - 31) / bitmap_head.biBitCnt); - /* ... that we write to our headers. */ - /* We should consider rejecting any width > (INT32_MAX - 31) / BitsPerPixel, as - * the resulting BMP will likely cause integer overflow in other readers. - * TODO: Revisit as soon as we can add strings again !!! */ + bytes_per_row = (((guint64) width * bitmap_head.biBitCnt + 31) / 32) * 4; - bytes_per_row = (( (guint64) cols * BitsPerPixel + 31) / 32) * 4; - - if (write_color_space) - { - /* Always include color mask for BITMAPV5HEADER, see #4155. */ - mask_info_size = 16; - color_space_size = 68; - } - else - { - color_space_size = 0; - } + bitmap_head.biSize = info_header_size (info_version); + frontmatter_size = 14 + bitmap_head.biSize + 4 * ncolors; - bitmap_file_head.bfSize = (54 + MapSize + (rows * bytes_per_row) + - mask_info_size + color_space_size); - bitmap_file_head.zzHotX = 0; - bitmap_file_head.zzHotY = 0; - bitmap_file_head.bfOffs = (54 + MapSize + - mask_info_size + color_space_size); + if (info_version < BMPINFO_V2_ADOBE && bitmap_head.biCompr == BI_BITFIELDS) + frontmatter_size += 12; /* V1 header stores RGB masks outside header */ - bitmap_head.biSize = 40 + mask_info_size + color_space_size; - bitmap_head.biWidth = cols; - bitmap_head.biHeight = rows; - bitmap_head.biPlanes = 1; - bitmap_head.biBitCnt = BitsPerPixel; + bitmap_file_head.bfOffs = frontmatter_size; - if (! use_rle) - { - /* The Microsoft specification for BITMAPV5HEADER says that - * BI_BITFIELDS is valid for 16 and 32-bits per pixel, - * Since it doesn't mention 24 bpp or other numbers - * use BI_RGB for that. See issue #6114. */ - if (mask_info_size > 0 && (BitsPerPixel == 16 || BitsPerPixel == 32)) - bitmap_head.biCompr = BI_BITFIELDS; - else - bitmap_head.biCompr = BI_RGB; - } - else if (BitsPerPixel == 8) + if (use_rle || (guint64) bytes_per_row * height + frontmatter_size > UINT32_MAX) { - bitmap_head.biCompr = BI_RLE8; - } - else if (BitsPerPixel == 4) - { - bitmap_head.biCompr = BI_RLE4; + /* for RLE, we don't know the size until after writing the image and + * will update later. + * Also, if the size is larger than UINT32_MAX, we write 0. Most (all?) + * readers will ignore it, anyway. TODO: Might want to issue warning + * in this case. + */ + bitmap_file_head.bfSize = 0; + bitmap_head.biSizeIm = 0; } else { - bitmap_head.biCompr = BI_RGB; + bitmap_file_head.bfSize = frontmatter_size + (bytes_per_row * height); + bitmap_head.biSizeIm = bytes_per_row * height; } - bitmap_head.biSizeIm = bytes_per_row * rows; - - { - gdouble xresolution; - gdouble yresolution; - - gimp_image_get_resolution (image, &xresolution, &yresolution); - - if (xresolution > GIMP_MIN_RESOLUTION && - yresolution > GIMP_MIN_RESOLUTION) - { - /* - * xresolution and yresolution are in dots per inch. - * the BMP spec says that biXPels and biYPels are in - * pixels per meter as long ints (actually, "DWORDS"), - * so... - * n dots inch 100 cm m dots - * ------ * ------- * ------ = ------ - * inch 2.54 cm m inch - * - * We add 0.5 for proper rounding. - */ - bitmap_head.biXPels = (long int) (xresolution * 100.0 / 2.54 + 0.5); - bitmap_head.biYPels = (long int) (yresolution * 100.0 / 2.54 + 0.5); - } - } - - if (BitsPerPixel <= 8) - bitmap_head.biClrUsed = colors; - else - bitmap_head.biClrUsed = 0; - - bitmap_head.biClrImp = bitmap_head.biClrUsed; + bitmap_head.biWidth = width; + bitmap_head.biHeight = height; + bitmap_head.biPlanes = 1; -#ifdef DEBUG - printf ("\nSize: %u, Colors: %u, Bits: %u, Width: %u, Height: %u, Comp: %u, Zeile: %u\n", - (gint) bitmap_file_head.bfSize, - (gint) bitmap_head.biClrUsed, - bitmap_head.biBitCnt, - (gint) bitmap_head.biWidth, - (gint) bitmap_head.biHeight, - (gint) bitmap_head.biCompr, bytes_per_row); -#endif + set_info_resolution (&bitmap_head, image); - /* And now write the header and the colormap (if any) to disk */ + outfile = g_fopen (g_file_peek_path (file), "wb"); + if (! outfile) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for writing: %s"), + gimp_file_get_utf8_name (file), g_strerror (errno)); + return GIMP_PDB_EXECUTION_ERROR; + } - if (fwrite ("BM", 2, 1, outfile) != 1) + bitmap_file_head.zzMagic[0] = 'B'; + bitmap_file_head.zzMagic[1] = 'M'; + if (! write_file_header (outfile, &bitmap_file_head)) goto abort; - bitmap_file_head.bfSize = GUINT32_TO_LE (bitmap_file_head.bfSize); - bitmap_file_head.zzHotX = GUINT16_TO_LE (bitmap_file_head.zzHotX); - bitmap_file_head.zzHotY = GUINT16_TO_LE (bitmap_file_head.zzHotY); - bitmap_file_head.bfOffs = GUINT32_TO_LE (bitmap_file_head.bfOffs); - - if (fwrite (&bitmap_file_head.bfSize, 12, 1, outfile) != 1) + if (! write_info_header (outfile, &bitmap_head, info_version)) goto abort; - bitmap_head.biSize = GUINT32_TO_LE (bitmap_head.biSize); - bitmap_head.biWidth = GINT32_TO_LE (bitmap_head.biWidth); - bitmap_head.biHeight = GINT32_TO_LE (bitmap_head.biHeight); - bitmap_head.biPlanes = GUINT16_TO_LE (bitmap_head.biPlanes); - bitmap_head.biBitCnt = GUINT16_TO_LE (bitmap_head.biBitCnt); - bitmap_head.biCompr = GUINT32_TO_LE (bitmap_head.biCompr); - bitmap_head.biSizeIm = GUINT32_TO_LE (bitmap_head.biSizeIm); - bitmap_head.biXPels = GUINT32_TO_LE (bitmap_head.biXPels); - bitmap_head.biYPels = GUINT32_TO_LE (bitmap_head.biYPels); - bitmap_head.biClrUsed = GUINT32_TO_LE (bitmap_head.biClrUsed); - bitmap_head.biClrImp = GUINT32_TO_LE (bitmap_head.biClrImp); - - if (fwrite (&bitmap_head, 40, 1, outfile) != 1) + if (ncolors && ! write_color_map (outfile, cmap, ncolors)) goto abort; - if (mask_info_size > 0) - { - switch (rgb_format) - { - default: - case RGB_888: - case RGBX_8888: - Mask[0] = 0x00ff0000; - Mask[1] = 0x0000ff00; - Mask[2] = 0x000000ff; - Mask[3] = 0x00000000; - break; - - case RGBA_8888: - Mask[0] = 0x00ff0000; - Mask[1] = 0x0000ff00; - Mask[2] = 0x000000ff; - Mask[3] = 0xff000000; - break; - - case RGB_565: - Mask[0] = 0xf800; - Mask[1] = 0x7e0; - Mask[2] = 0x1f; - Mask[3] = 0x0; - break; - - case RGBA_5551: - Mask[0] = 0x7c00; - Mask[1] = 0x3e0; - Mask[2] = 0x1f; - Mask[3] = 0x8000; - break; - - case RGB_555: - Mask[0] = 0x7c00; - Mask[1] = 0x3e0; - Mask[2] = 0x1f; - Mask[3] = 0x0; - break; - } - - Mask[0] = GUINT32_TO_LE (Mask[0]); - Mask[1] = GUINT32_TO_LE (Mask[1]); - Mask[2] = GUINT32_TO_LE (Mask[2]); - Mask[3] = GUINT32_TO_LE (Mask[3]); - - if (fwrite (&Mask, mask_info_size, 1, outfile) != 1) - goto abort; - } - - if (write_color_space) - { - guint32 buf[0x11]; - - /* Write V5 color space fields */ + if (! write_image (outfile, + pixels, width, height, + use_rle, + channels, bitmap_head.biBitCnt, bytes_per_row, + ncolors, cmasks, + frontmatter_size)) + goto abort; - /* bV5CSType = LCS_sRGB */ - buf[0x00] = GUINT32_TO_LE (0x73524742); + fclose (outfile); + g_free (pixels); + g_free (cmap); - /* bV5Endpoints is set to 0 (ignored) */ - for (i = 0; i < 0x09; i++) - buf[i + 1] = 0x00; + return GIMP_PDB_SUCCESS; - /* bV5GammaRed is set to 0 (ignored) */ - buf[0x0a] = GUINT32_TO_LE (0x0); +abort: + if (outfile) + fclose (outfile); + g_free (pixels); + g_free (cmap); - /* bV5GammaGreen is set to 0 (ignored) */ - buf[0x0b] = GUINT32_TO_LE (0x0); + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error writing to file.")); - /* bV5GammaBlue is set to 0 (ignored) */ - buf[0x0c] = GUINT32_TO_LE (0x0); + return GIMP_PDB_EXECUTION_ERROR; +} - /* bV5Intent = LCS_GM_GRAPHICS */ - buf[0x0d] = GUINT32_TO_LE (0x00000002); +static gboolean +are_masks_well_known (BitmapChannel *cmasks, gint bpp) +{ + gint c, bitsperchannel; - /* bV5ProfileData is set to 0 (ignored) */ - buf[0x0e] = GUINT32_TO_LE (0x0); + /* 16/24/32-bit BMPs each have one 'well-known' BI_RGB representation + * that doesn't require to write the masks with BI_BITFIELDS + */ - /* bV5ProfileSize is set to 0 (ignored) */ - buf[0x0f] = GUINT32_TO_LE (0x0); + if (cmasks[3].nbits != 0) /* alpha */ + return FALSE; - /* bV5Reserved = 0 */ - buf[0x10] = GUINT32_TO_LE (0x0); + if (bpp == 16) + bitsperchannel = 5; + else if (bpp == 24 || bpp == 32) + bitsperchannel = 8; + else + return FALSE; - if (fwrite (buf, color_space_size, 1, outfile) != 1) - goto abort; + for (c = 0; c < 3; c++) + { + if (cmasks[c].nbits != bitsperchannel) + return FALSE; } - if (! write_color_map (outfile, Red, Green, Blue, MapSize)) - goto abort; + return TRUE; +} - /* After that is done, we write the image ... */ +static gboolean +are_masks_v1_standard (BitmapChannel *cmasks, gint bpp) +{ + /* BITMAPINFOHEADER allowed only 5-5-5 or 5-6-5 for 16-bit and only 8-8-8 + * for 32-bit + */ - if (! write_image (outfile, - pixels, cols, rows, - use_rle, - channels, BitsPerPixel, bytes_per_row, - MapSize, rgb_format, - mask_info_size, color_space_size)) - goto abort; + if (cmasks[3].nbits != 0) /* alpha */ + return FALSE; - /* ... and exit normally */ + if (bpp == 16) + { + if (cmasks[0].nbits == 5 && + (cmasks[1].nbits == 5 || cmasks[1].nbits == 6) && + cmasks[2].nbits == 5) + { + return TRUE; + } + } + else if (bpp == 32) + { + if (cmasks[0].nbits == 8 && + cmasks[1].nbits == 8 && + cmasks[2].nbits == 8) + { + return TRUE; + } + } - fclose (outfile); - g_free (pixels); + return FALSE; +} - return GIMP_PDB_SUCCESS; +static gint +calc_bitsperpixel_from_masks (BitmapChannel *cmasks) +{ + gint c, bitsum = 0; -abort: - if (outfile) - fclose (outfile); - g_free(pixels); + for (c = 0; c < 4; c++) + bitsum += cmasks[c].nbits; - g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, - _("Error writing to file.")); + if (bitsum > 16) + { + if (bitsum == 24 && are_masks_well_known (cmasks, 24)) + return 24; + else + return 32; + } + return 16; +} - return GIMP_PDB_EXECUTION_ERROR; +static void +calc_masks_from_bits (BitmapChannel *cmasks, gint r, gint g, gint b, gint a) +{ + gint nbits[4] = { r, g, b, a }; + gint order[4] = { 2, 1, 0, 3 }; /* == B-G-R-A */ + gint c, bitsum = 0; + + /* calculate bitmasks for given channel bit-depths. + * + * Note: while for BI_BITFIELDS we are free to place the masks in any order, + * we also use the masks for the well known 16/24/32 bit formats, we just + * don't write them to the file. So the masks here must be prepared in the + * proper order for those formats which is from high to low: R-G-B. + * BMPs are little endian, so in the file they end up B-G-R-(A). + * Because it is confusing, here in other words: blue has a shift of 0, + * red has the second-highest shift, alpha has the highest shift. + */ + + for (c = 0; c < 4; c++) + { + cmasks[order[c]].nbits = nbits[order[c]]; + cmasks[order[c]].mask = (1UL << nbits[order[c]]) - 1; + cmasks[order[c]].max_value = (gfloat) cmasks[order[c]].mask; + cmasks[order[c]].shiftin = bitsum; + bitsum += nbits[order[c]]; + } } -static inline void -Make565 (guchar r, - guchar g, - guchar b, - guchar *buf) +static void +set_info_resolution (BitmapHead *bih, GimpImage *image) { - gint p; + gdouble xresolution; + gdouble yresolution; - p = ((((gint) (r / 255.0 * 31.0 + 0.5)) << 11) | - (((gint) (g / 255.0 * 63.0 + 0.5)) << 5) | - (((gint) (b / 255.0 * 31.0 + 0.5)))); + gimp_image_get_resolution (image, &xresolution, &yresolution); - buf[0] = (guchar) (p & 0xff); - buf[1] = (guchar) (p >> 8); + if (xresolution > GIMP_MIN_RESOLUTION && yresolution > GIMP_MIN_RESOLUTION) + { + /* + * xresolution and yresolution are in dots per inch. + * BMP biXPels and biYPels are in pixels per meter. + */ + bih->biXPels = (long int) (xresolution * 100.0 / 2.54 + 0.5); + bih->biYPels = (long int) (yresolution * 100.0 / 2.54 + 0.5); + } } -static inline void -Make5551 (guchar r, - guchar g, - guchar b, - guchar a, - guchar *buf) +static gint +info_header_size (enum BmpInfoVer version) { - gint p; - - p = ((((gint) (r / 255.0 * 31.0 + 0.5)) << 10) | - (((gint) (g / 255.0 * 31.0 + 0.5)) << 5) | - (((gint) (b / 255.0 * 31.0 + 0.5))) | - (((gint) (a / 255.0 + 0.5) << 15))); - - buf[0] = (guchar) (p & 0xff); - buf[1] = (guchar) (p >> 8); + switch (version) + { + case BMPINFO_CORE: + return 12; + case BMPINFO_V1: + return 40; + case BMPINFO_V2_ADOBE: + return 52; + case BMPINFO_V3_ADOBE: + return 56; + case BMPINFO_V4: + return 108; + case BMPINFO_V5: + return 124; + default: + g_assert_not_reached (); + } + return 0; } static gboolean -write_image (FILE *f, - guchar *src, - gint width, - gint height, - gboolean use_run_length_encoding, - gint channels, - gint bpp, - gint bytes_per_row, - gint MapSize, - RGBMode rgb_format, - gint mask_info_size, - gint color_space_size) +write_image (FILE *f, + guchar *src, + gint width, + gint height, + gboolean use_run_length_encoding, + gint channels, + gint bpp, + gsize bytes_per_row, + gint ncolors, + BitmapChannel *cmasks, + gint header_size) { - guchar buf[16]; - guint32 uint32buf; guchar *temp, v; guchar *row = NULL; guchar *chains = NULL; - gint xpos, ypos, i, j, rowstride, length, thiswidth; + gint xpos, ypos, i, j, rowstride; + guint32 px32; + guint64 length; gint breite, k; - guchar n, r, g, b, a; + guchar n; + gint channel_val[4]; gint cur_progress; gint max_progress; gint padding; @@ -669,83 +766,42 @@ write_image (FILE *f, if (bpp > 8) { - padding = bytes_per_row - (width * (bpp / 8)); - for (ypos = height - 1; ypos >= 0; ypos--) /* for each row */ + gint alpha = channels == 4 || channels == 2 ? 1 : 0; + + padding = bytes_per_row - ((gsize) width * (bpp / 8)); + channel_val[3] = 0xff; /* default alpha = opaque */ + for (ypos = height - 1; ypos >= 0; ypos--) { - for (i = 0; i < width; i++) /* for each pixel */ + for (xpos = 0; xpos < width; xpos++) { temp = src + (ypos * rowstride) + (xpos * channels); - switch (rgb_format) + channel_val[0] = *temp++; + if (channels > 2) { - default: - case RGB_888: - buf[2] = *temp++; - buf[1] = *temp++; - buf[0] = *temp++; - xpos++; - if (channels > 3 && (guchar) *temp == 0) - buf[0] = buf[1] = buf[2] = 0xff; - - if (fwrite (buf, 1, 3, f) != 3) - goto abort; - break; - case RGBX_8888: - buf[2] = *temp++; - buf[1] = *temp++; - buf[0] = *temp++; - buf[3] = 0; - xpos++; - if (channels > 3 && (guchar) *temp == 0) - buf[0] = buf[1] = buf[2] = 0xff; - - if (fwrite (buf, 1, 4, f) != 4) - goto abort; - break; - case RGBA_8888: - buf[2] = *temp++; - buf[1] = *temp++; - buf[0] = *temp++; - buf[3] = *temp; - xpos++; - - if (fwrite (buf, 1, 4, f) != 4) - goto abort; - break; - case RGB_565: - r = *temp++; - g = *temp++; - b = *temp++; - if (channels > 3 && (guchar) *temp == 0) - r = g = b = 0xff; - Make565 (r, g, b, buf); - xpos++; - - if (fwrite (buf, 1, 2, f) != 2) - goto abort; - break; - case RGB_555: - r = *temp++; - g = *temp++; - b = *temp++; - if (channels > 3 && (guchar) *temp == 0) - r = g = b = 0xff; - Make5551 (r, g, b, 0x0, buf); - xpos++; - - if (fwrite (buf, 1, 2, f) != 2) - goto abort; - break; - case RGBA_5551: - r = *temp++; - g = *temp++; - b = *temp++; - a = *temp; - Make5551 (r, g, b, a, buf); - xpos++; - - if (fwrite (buf, 1, 2, f) != 2) + /* RGB */ + channel_val[1] = *temp++; + channel_val[2] = *temp++; + } + else + { + /* fake grayscale */ + channel_val[1] = channel_val[2] = channel_val[0]; + } + + if (alpha) + channel_val[3] = *temp++; + + px32 = 0; + for (gint c = 0; c < 4; c++) + { + px32 |= (guint32) (channel_val[c] / 255. * cmasks[c].max_value + 0.5) + << cmasks[c].shiftin; + } + + for (j = 0; j < bpp; j += 8) + { + if (EOF == putc ((px32 >> j) & 0xff, f)) goto abort; - break; } } @@ -759,8 +815,6 @@ write_image (FILE *f, if ((cur_progress % 5) == 0) gimp_progress_update ((gdouble) cur_progress / (gdouble) max_progress); - - xpos = 0; } } else @@ -770,8 +824,7 @@ write_image (FILE *f, { /* uncompressed 1,4 and 8 bit */ - thiswidth = (width * bpp + 7) / 8; - padding = bytes_per_row - thiswidth; + padding = bytes_per_row - ((guint64) width * bpp + 7) / 8; for (ypos = height - 1; ypos >= 0; ypos--) /* for each row */ { @@ -947,18 +1000,31 @@ write_image (FILE *f, if (EOF == putc (0, f) || EOF == putc (1, f)) /* ... with End of file */ goto abort; - if (fseek (f, 0x22, SEEK_SET)) /* Write length of image */ - goto abort; - uint32buf = GUINT32_TO_LE (length); - if (fwrite (&uint32buf, 4, 1, f) != 1) - goto abort; + if (length <= UINT32_MAX) + { + /* Write length of image data */ + if (fseek (f, 0x22, SEEK_SET)) + goto abort; + if (! write_u32_le (f, length)) + goto abort; - if (fseek (f, 0x02, SEEK_SET)) /* Write length of file */ - goto abort; - length += (0x36 + MapSize + mask_info_size + color_space_size); - uint32buf = GUINT32_TO_LE (length); - if (fwrite (&uint32buf, 4, 1, f) != 1) - goto abort; + /* Write length of file */ + if (fseek (f, 0x02, SEEK_SET)) + goto abort; + if (! write_u32_le (f, length + header_size)) + goto abort; + } + else + { + /* RLE data is too big to record the size in biSizeImage. + * According to spec, biSizeImage would have to be set for RLE + * bmps. In reality, it is neither necessary for interpreting + * the file, nor do readers seem to mind when field is not set, + * so we just leave it at 0. + * + * TODO: Issue a warning when this happens. + */ + } g_free (chains); g_free (row); @@ -975,12 +1041,138 @@ abort: return FALSE; } +static gboolean +write_little_endian (FILE *file, guint32 u32, gint bytes) +{ + gint i; + + for (i = 0; i < bytes; i++) + { + if (EOF == putc ((u32 >> (8 * i)) & 0xff, file)) + return FALSE; + } + return TRUE; +} + +static gboolean +write_u16_le (FILE *file, guint16 u16) +{ + return write_little_endian (file, (guint32) u16, 2); +} + +static gboolean +write_u32_le (FILE *file, guint32 u32) +{ + return write_little_endian (file, u32, 4); +} + +static gboolean +write_s32_le (FILE *file, gint32 s32) +{ + return write_little_endian (file, (guint32) s32, 4); +} + +static gboolean +write_file_header (FILE *file, BitmapFileHead *bfh) +{ + if (fwrite (bfh->zzMagic, 1, 2, file) != 2 || + ! write_u32_le (file, bfh->bfSize) || + ! write_u16_le (file, bfh->zzHotX) || + ! write_u16_le (file, bfh->zzHotY) || + ! write_u32_le (file, bfh->bfOffs)) + { + return FALSE; + } + return TRUE; +} + +static gboolean +write_info_header (FILE *file, BitmapHead *bih, enum BmpInfoVer version) +{ + guint32 u32; + + g_assert (version >= BMPINFO_V1 && version <= BMPINFO_V5); + + /* write at least 40-byte BITMAPINFOHEADER */ + + if (! write_u32_le (file, bih->biSize) || + ! write_s32_le (file, bih->biWidth) || + ! write_s32_le (file, bih->biHeight) || + ! write_u16_le (file, bih->biPlanes) || + ! write_u16_le (file, bih->biBitCnt) || + ! write_u32_le (file, bih->biCompr) || + ! write_u32_le (file, bih->biSizeIm) || + ! write_s32_le (file, bih->biXPels) || + ! write_s32_le (file, bih->biYPels) || + ! write_u32_le (file, bih->biClrUsed) || + ! write_u32_le (file, bih->biClrImp)) + { + return FALSE; + } + + if (version <= BMPINFO_V1 && bih->biCompr != BI_BITFIELDS) + return TRUE; + + /* continue writing v2+ header or masks for v1 bitfields */ + + for (gint i = 0; i < 3; i++) + { + /* write RGB masks, either as part of v2+ header, or after v1 header */ + if (! write_u32_le (file, bih->masks[i])) + return FALSE; + } + + if (version <= BMPINFO_V2_ADOBE) + return TRUE; + + /* alpha mask only as part of v3+ header */ + if (! write_u32_le (file, bih->masks[3])) + return FALSE; + + if (version <= BMPINFO_V3_ADOBE) + return TRUE; + + if (! write_u32_le (file, bih->bV4CSType)) + return FALSE; + + for (gint i = 0; i < 9; i++) + { + /* endpoints are written as 2.30 fixed point */ + u32 = (guint32) (bih->bV4Endpoints[i] * 0x40000000L + 0.5); + if (! write_u32_le (file, u32)) + return FALSE; + } + + /* gamma is written as 16.16 fixed point */ + if (! write_u32_le (file, (guint32) (bih->bV4GammaRed * 65535.0 + 0.5)) || + ! write_u32_le (file, (guint32) (bih->bV4GammaGreen * 65535.0 + 0.5)) || + ! write_u32_le (file, (guint32) (bih->bV4GammaBlue * 65535.0 + 0.5))) + { + return FALSE; + } + + if (version <= BMPINFO_V4) + return TRUE; + + /* continue writing BITMAPV5HEADER */ + + if (! write_u32_le (file, (guint32) bih->bV5Intent) || + ! write_u32_le (file, (guint32) bih->bV5ProfileData) || + ! write_u32_le (file, (guint32) bih->bV5ProfileSize) || + ! write_u32_le (file, (guint32) bih->bV5Reserved)) + { + return FALSE; + } + + return TRUE; +} + static gboolean format_sensitive_callback (GObject *config, gpointer data) { gint value; - gint channels = GPOINTER_TO_INT (data); + gint allow_alpha = GPOINTER_TO_INT (data); value = gimp_procedure_config_get_choice_id (GIMP_PROCEDURE_CONFIG (config), "rgb-format"); @@ -989,7 +1181,7 @@ format_sensitive_callback (GObject *config, { case RGBA_5551: case RGBA_8888: - return channels == 4; + return allow_alpha; default: return TRUE; @@ -1001,7 +1193,7 @@ config_notify (GObject *config, const GParamSpec *pspec, gpointer data) { - gint channels = GPOINTER_TO_INT (data); + gint allow_alpha = GPOINTER_TO_INT (data); RGBMode format; format = gimp_procedure_config_get_choice_id (GIMP_PROCEDURE_CONFIG (config), @@ -1011,7 +1203,7 @@ config_notify (GObject *config, { case RGBA_5551: case RGBA_8888: - if (channels != 4) + if (! allow_alpha) { g_signal_handlers_block_by_func (config, config_notify, data); @@ -1033,8 +1225,9 @@ static gboolean save_dialog (GimpProcedure *procedure, GObject *config, GimpImage *image, - gint channels, - gint bpp) + gboolean indexed, + gboolean allow_alpha, + gboolean allow_rle) { GtkWidget *dialog; GtkWidget *toggle; @@ -1052,7 +1245,7 @@ save_dialog (GimpProcedure *procedure, /* Run-Length Encoded */ gimp_procedure_dialog_set_sensitive (GIMP_PROCEDURE_DIALOG (dialog), "use-rle", - ! (channels > 1 || bpp == 1), + allow_rle, NULL, NULL, FALSE); /* Compatibility Options */ @@ -1080,7 +1273,7 @@ save_dialog (GimpProcedure *procedure, /* Determine if RGB Format combo should be initially sensitive */ is_format_sensitive = format_sensitive_callback (config, - GINT_TO_POINTER (channels)); + GINT_TO_POINTER (allow_alpha)); gimp_procedure_dialog_set_sensitive (GIMP_PROCEDURE_DIALOG (dialog), "rgb-format", is_format_sensitive, @@ -1088,7 +1281,7 @@ save_dialog (GimpProcedure *procedure, gimp_procedure_dialog_set_sensitive (GIMP_PROCEDURE_DIALOG (dialog), "rgb-format", - (channels >= 3), + ! indexed, NULL, NULL, FALSE); /* Formatting the dialog */ @@ -1108,13 +1301,13 @@ save_dialog (GimpProcedure *procedure, g_signal_connect (config, "notify::rgb-format", G_CALLBACK (config_notify), - GINT_TO_POINTER (channels)); + GINT_TO_POINTER (allow_alpha)); run = gimp_procedure_dialog_run (GIMP_PROCEDURE_DIALOG (dialog)); g_signal_handlers_disconnect_by_func (config, config_notify, - GINT_TO_POINTER (channels)); + GINT_TO_POINTER (allow_alpha)); gtk_widget_destroy (dialog); diff --git a/plug-ins/file-bmp/bmp.h b/plug-ins/file-bmp/bmp.h index 3c030bd62ca..26936b11cb3 100644 --- a/plug-ins/file-bmp/bmp.h +++ b/plug-ins/file-bmp/bmp.h @@ -46,6 +46,13 @@ #define BI_OS2_HUFFMAN (100 + BI_BITFIELDS) #define BI_OS2_RLE24 (100 + BI_JPEG) +/* bV4CSType values */ +#define V4CS_CALIBRATED_RGB 0x00000000 /* = use gamma and endpoint values */ +#define V4CS_sRGB 0x73524742 /* 'sRGB' */ +#define V4CS_WINDOWS_COLOR_SPACE 0x57696e20 /* 'Win ' */ +#define V4CS_PROFILE_LINKED 0x4c494e4b /* 'LINK' */ +#define V4CS_PROFILE_EMBEDDED 0x4d424544 /* 'MBED' */ + typedef struct { gchar zzMagic[2]; /* 00 "BM" */ @@ -88,5 +95,28 @@ typedef struct gint nbits; } BitmapChannel; +enum BmpInfoVer +{ + /* The only bmp info headers that clearly have a version number attributed + * to them are BITMAPV4HEADER and BITMAPV5HEADER. + * BITMAPINFOHEADER is sometimes referred to as v1 and sometimes as v3. MS + * themselves never seemed to give it a version number, but according to + * Adobe, the 52- and 56-byte extensions were versioned v2 and v3 by MS. + * The association of the number 3 with the BITMAPINFOHEADER might stem from + * the fact that it was originally kown as the Windows 3 bitmap. + * Anyway, v1 seems to make sense, so let's call it that for our purposes. + */ + + BMPINFO_NONE, /* not specified */ + BMPINFO_CORE, /* BITMAPCOREHEADER, aka OS21XBITMAPHEADER */ + BMPINFO_OS22X, /* OS22XBITMAPHEADER (actually named BITMAPINFOHEADER2 in OS/2) */ + BMPINFO_V1, /* BITMAPINFOHEADER (Windows 3.x, updated by Win95 / NT 4.0) */ + BMPINFO_V2_ADOBE, /* BITMAPINFOHEADER + RGB masks */ + BMPINFO_V3_ADOBE, /* BITMAPINFOHEADER + RGBA masks */ + BMPINFO_V4, /* BITMAPV4HEADER (Windows 95 / NT 4.0) */ + BMPINFO_V5, /* BITMAPV5HEADER (Windows 98 / NT 5.0) */ + BMPINFO_FUTURE /* future, yet unknown, headers */ +}; + #endif /* __BMP_H__ */ -- GitLab From 4a862ce0ba86fe7616b8afb8dd8b4746f3c06494 Mon Sep 17 00:00:00 2001 From: Rupert Date: Fri, 6 Dec 2024 16:05:03 +0100 Subject: [PATCH 02/13] Issue #12525: plug-ins: bmp-export - gray out invalid rgb-formats reinstate the old behavior of graying out RGBA format when the image doesn't have an alpha channel. Use new libgimpbase function gimp_param_spec_choice_get_choice (see b1acb256e1) --- plug-ins/file-bmp/bmp-export.c | 46 +++++++++------------------------- 1 file changed, 12 insertions(+), 34 deletions(-) diff --git a/plug-ins/file-bmp/bmp-export.c b/plug-ins/file-bmp/bmp-export.c index aa98f196d66..c4018a01bc5 100644 --- a/plug-ins/file-bmp/bmp-export.c +++ b/plug-ins/file-bmp/bmp-export.c @@ -1167,27 +1167,6 @@ write_info_header (FILE *file, BitmapHead *bih, enum BmpInfoVer version) return TRUE; } -static gboolean -format_sensitive_callback (GObject *config, - gpointer data) -{ - gint value; - gint allow_alpha = GPOINTER_TO_INT (data); - - value = gimp_procedure_config_get_choice_id (GIMP_PROCEDURE_CONFIG (config), - "rgb-format"); - - switch (value) - { - case RGBA_5551: - case RGBA_8888: - return allow_alpha; - - default: - return TRUE; - }; -} - static void config_notify (GObject *config, const GParamSpec *pspec, @@ -1229,12 +1208,13 @@ save_dialog (GimpProcedure *procedure, gboolean allow_alpha, gboolean allow_rle) { - GtkWidget *dialog; - GtkWidget *toggle; - GtkWidget *vbox; - GtkWidget *combo; - gboolean is_format_sensitive; - gboolean run; + GtkWidget *dialog; + GtkWidget *toggle; + GtkWidget *vbox; + GtkWidget *combo; + GParamSpec *cspec; + GimpChoice *choice; + gboolean run; dialog = gimp_export_procedure_dialog_new (GIMP_EXPORT_PROCEDURE (procedure), GIMP_PROCEDURE_CONFIG (config), @@ -1271,13 +1251,11 @@ save_dialog (GimpProcedure *procedure, "rgb-format", G_TYPE_NONE); g_object_set (combo, "margin", 12, NULL); - /* Determine if RGB Format combo should be initially sensitive */ - is_format_sensitive = format_sensitive_callback (config, - GINT_TO_POINTER (allow_alpha)); - gimp_procedure_dialog_set_sensitive (GIMP_PROCEDURE_DIALOG (dialog), - "rgb-format", - is_format_sensitive, - NULL, NULL, FALSE); + cspec = g_object_class_find_property (G_OBJECT_GET_CLASS (config), "rgb-format"); + choice = gimp_param_spec_choice_get_choice (cspec); + + gimp_choice_set_sensitive (choice, "rgba-5551", allow_alpha); + gimp_choice_set_sensitive (choice, "rgba-8888", allow_alpha); gimp_procedure_dialog_set_sensitive (GIMP_PROCEDURE_DIALOG (dialog), "rgb-format", -- GitLab From 443dcf7a22a012fe5a77ab16f234d7078e3e5b8c Mon Sep 17 00:00:00 2001 From: Rupert Date: Sun, 5 Jan 2025 20:59:46 +0100 Subject: [PATCH 03/13] plug-ins: bmp-export - cleanup on all error exits --- plug-ins/file-bmp/bmp-export.c | 79 ++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 32 deletions(-) diff --git a/plug-ins/file-bmp/bmp-export.c b/plug-ins/file-bmp/bmp-export.c index c4018a01bc5..f4914035668 100644 --- a/plug-ins/file-bmp/bmp-export.c +++ b/plug-ins/file-bmp/bmp-export.c @@ -242,26 +242,29 @@ export_image (GFile *file, GObject *config, GError **error) { - FILE *outfile = NULL; - BitmapFileHead bitmap_file_head; - BitmapHead bitmap_head; - guchar *cmap = NULL; - gint channels; - gsize bytes_per_row; - BitmapChannel cmasks[4]; - gint ncolors = 0; - guchar *pixels = NULL; - GeglBuffer *buffer; - const Babl *format; - GimpImageType drawable_type; - gint width, height; - gint i; - gboolean use_rle; - gboolean write_color_space; - RGBMode rgb_format = RGB_888; - enum BmpInfoVer info_version = BMPINFO_V1; - gint frontmatter_size; - gboolean indexed_bmp = FALSE, allow_alpha = FALSE, allow_rle = FALSE; + GimpPDBStatusType ret = GIMP_PDB_EXECUTION_ERROR; + FILE *outfile = NULL; + BitmapFileHead bitmap_file_head; + BitmapHead bitmap_head; + guchar *cmap = NULL; + gint channels; + gsize bytes_per_row; + BitmapChannel cmasks[4]; + gint ncolors = 0; + guchar *pixels = NULL; + GeglBuffer *buffer; + const Babl *format; + GimpImageType drawable_type; + gint width, height; + gint i; + gboolean use_rle; + gboolean write_color_space; + RGBMode rgb_format = RGB_888; + enum BmpInfoVer info_version = BMPINFO_V1; + gint frontmatter_size; + gboolean indexed_bmp = FALSE; + gboolean allow_alpha = FALSE; + gboolean allow_rle = FALSE; memset (&bitmap_file_head, 0, sizeof bitmap_file_head); memset (&bitmap_head, 0, sizeof bitmap_head); @@ -316,7 +319,10 @@ export_image (GFile *file, ! warning_dialog (_("Cannot export indexed image with " "transparency in BMP file format."), _("Alpha channel will be ignored."))) - return GIMP_PDB_CANCEL; + { + ret = GIMP_PDB_CANCEL; + goto abort; + } /* fallthrough */ @@ -347,7 +353,10 @@ export_image (GFile *file, ! warning_dialog (_("Cannot export indexed image with " "transparency in BMP file format."), _("Alpha channel will be ignored."))) - return GIMP_PDB_CANCEL; + { + ret = GIMP_PDB_CANCEL; + goto abort; + } /* fallthrough */ @@ -381,7 +390,10 @@ export_image (GFile *file, if (run_mode == GIMP_RUN_INTERACTIVE) { if (! save_dialog (procedure, config, image, indexed_bmp, allow_alpha, allow_rle)) - return GIMP_PDB_CANCEL; + { + ret = GIMP_PDB_CANCEL; + goto abort; + } } g_object_get (config, @@ -542,19 +554,19 @@ export_image (GFile *file, g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), _("Could not open '%s' for writing: %s"), gimp_file_get_utf8_name (file), g_strerror (errno)); - return GIMP_PDB_EXECUTION_ERROR; + goto abort; } bitmap_file_head.zzMagic[0] = 'B'; bitmap_file_head.zzMagic[1] = 'M'; if (! write_file_header (outfile, &bitmap_file_head)) - goto abort; + goto abort_with_standard_message; if (! write_info_header (outfile, &bitmap_head, info_version)) - goto abort; + goto abort_with_standard_message; if (ncolors && ! write_color_map (outfile, cmap, ncolors)) - goto abort; + goto abort_with_standard_message; if (! write_image (outfile, pixels, width, height, @@ -562,7 +574,7 @@ export_image (GFile *file, channels, bitmap_head.biBitCnt, bytes_per_row, ncolors, cmasks, frontmatter_size)) - goto abort; + goto abort_with_standard_message; fclose (outfile); g_free (pixels); @@ -570,16 +582,19 @@ export_image (GFile *file, return GIMP_PDB_SUCCESS; +abort_with_standard_message: + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error writing to file.")); + + /* fall through to abort */ + abort: if (outfile) fclose (outfile); g_free (pixels); g_free (cmap); - g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, - _("Error writing to file.")); - - return GIMP_PDB_EXECUTION_ERROR; + return ret; } static gboolean -- GitLab From b01260ea3a59a6ae92b5d45cec7ee57e505644cd Mon Sep 17 00:00:00 2001 From: Rupert Date: Thu, 12 Dec 2024 22:21:42 +0100 Subject: [PATCH 04/13] plug-ins: bmp-export - piece-wise loading 1/3 step1: move the y-loops (looping over image height) to one single loop on the outside. (Instead of one per bmp format) --- plug-ins/file-bmp/bmp-export.c | 175 +++++++++++++++------------------ 1 file changed, 82 insertions(+), 93 deletions(-) diff --git a/plug-ins/file-bmp/bmp-export.c b/plug-ins/file-bmp/bmp-export.c index f4914035668..b133f8f9da0 100644 --- a/plug-ins/file-bmp/bmp-export.c +++ b/plug-ins/file-bmp/bmp-export.c @@ -770,22 +770,32 @@ write_image (FILE *f, gint cur_progress; gint max_progress; gint padding; + gint alpha; - xpos = 0; rowstride = width * channels; + channel_val[3] = 0xff; /* default alpha = opaque */ + alpha = channels == 4 || channels == 2 ? 1 : 0; + + if (! use_run_length_encoding) + { + padding = bytes_per_row - ((guint64) width * bpp + 7) / 8; + } + else + { + padding = 0; /* RLE does its own pixel-based padding */ + row = g_new (guchar, width / (8 / bpp) + 10); + chains = g_new (guchar, width / (8 / bpp) + 10); + length = 0; + } + cur_progress = 0; max_progress = height; - /* We'll begin with the 16/24/32 bit Bitmaps, they are easy :-) */ - - if (bpp > 8) + for (ypos = height - 1; ypos >= 0; ypos--) { - gint alpha = channels == 4 || channels == 2 ? 1 : 0; - - padding = bytes_per_row - ((gsize) width * (bpp / 8)); - channel_val[3] = 0xff; /* default alpha = opaque */ - for (ypos = height - 1; ypos >= 0; ypos--) + /* We'll begin with the 16/24/32 bit Bitmaps, they are easy :-) */ + if (bpp > 8) { for (xpos = 0; xpos < width; xpos++) { @@ -820,76 +830,41 @@ write_image (FILE *f, } } - for (int j = 0; j < padding; j++) - { - if (EOF == putc (0, f)) - goto abort; - } - - cur_progress++; - if ((cur_progress % 5) == 0) - gimp_progress_update ((gdouble) cur_progress / - (gdouble) max_progress); } - } - else - { - /* now it gets more difficult */ - if (! use_run_length_encoding || bpp == 1) + else /* indexed */ { - /* uncompressed 1,4 and 8 bit */ - - padding = bytes_per_row - ((guint64) width * bpp + 7) / 8; - - for (ypos = height - 1; ypos >= 0; ypos--) /* for each row */ + /* now it gets more difficult */ + if (! use_run_length_encoding || bpp == 1) { + /* uncompressed 1,4 and 8 bit */ + for (xpos = 0; xpos < width;) /* for each _byte_ */ { v = 0; for (i = 1; - (i <= (8 / bpp)) && (xpos < width); + i <= (8 / bpp) && xpos < width; i++, xpos++) /* for each pixel */ { temp = src + (ypos * rowstride) + (xpos * channels); - if (channels > 1 && *(temp+1) == 0) *temp = 0x0; - v=v | ((guchar) *temp << (8 - (i * bpp))); + + if (channels > 1 && *(temp + 1) == 0) + *temp = 0x0; + + v = v | ((guchar) *temp << (8 - (i * bpp))); } if (fwrite (&v, 1, 1, f) != 1) goto abort; } - - for (int j = 0; j < padding; j++) - { - if (EOF == putc (0, f)) - goto abort; - } - - xpos = 0; - - cur_progress++; - if ((cur_progress % 5) == 0) - gimp_progress_update ((gdouble) cur_progress / - (gdouble) max_progress); } - } - else - { - /* Save RLE encoded file, quite difficult */ - - length = 0; - - row = g_new (guchar, width / (8 / bpp) + 10); - chains = g_new (guchar, width / (8 / bpp) + 10); - - for (ypos = height - 1; ypos >= 0; ypos--) + else { - /* each row separately */ - j = 0; + /* Save RLE encoded file, quite difficult */ /* first copy the pixels to a buffer, making one byte * from two 4bit pixels */ + j = 0; for (xpos = 0; xpos < width;) { v = 0; @@ -901,7 +876,10 @@ write_image (FILE *f, /* for each pixel */ temp = src + (ypos * rowstride) + (xpos * channels); - if (channels > 1 && *(temp+1) == 0) *temp = 0x0; + + if (channels > 1 && *(temp + 1) == 0) + *temp = 0x0; + v = v | ((guchar) * temp << (8 - (i * bpp))); } @@ -1004,48 +982,59 @@ write_image (FILE *f, goto abort; length += 2; - cur_progress++; - if ((cur_progress % 5) == 0) - gimp_progress_update ((gdouble) cur_progress / - (gdouble) max_progress); - } + } /* RLE */ + } - if (fseek (f, -2, SEEK_CUR)) /* Overwrite last End of row ... */ - goto abort; - if (EOF == putc (0, f) || EOF == putc (1, f)) /* ... with End of file */ + for (int j = 0; j < padding; j++) + { + if (EOF == putc (0, f)) goto abort; + } - if (length <= UINT32_MAX) - { - /* Write length of image data */ - if (fseek (f, 0x22, SEEK_SET)) - goto abort; - if (! write_u32_le (f, length)) - goto abort; + cur_progress++; + if ((cur_progress % 5) == 0) + gimp_progress_update ((gdouble) cur_progress / + (gdouble) max_progress); - /* Write length of file */ - if (fseek (f, 0x02, SEEK_SET)) - goto abort; - if (! write_u32_le (f, length + header_size)) - goto abort; - } - else - { - /* RLE data is too big to record the size in biSizeImage. - * According to spec, biSizeImage would have to be set for RLE - * bmps. In reality, it is neither necessary for interpreting - * the file, nor do readers seem to mind when field is not set, - * so we just leave it at 0. - * - * TODO: Issue a warning when this happens. - */ - } + } + + if (use_run_length_encoding) + { + if (fseek (f, -2, SEEK_CUR)) /* Overwrite last End of row ... */ + goto abort; + if (EOF == putc (0, f) || EOF == putc (1, f)) /* ... with End of file */ + goto abort; + + if (length <= UINT32_MAX) + { + /* Write length of image data */ + if (fseek (f, 0x22, SEEK_SET)) + goto abort; + if (! write_u32_le (f, length)) + goto abort; - g_free (chains); - g_free (row); + /* Write length of file */ + if (fseek (f, 0x02, SEEK_SET)) + goto abort; + if (! write_u32_le (f, length + header_size)) + goto abort; + } + else + { + /* RLE data is too big to record the size in biSizeImage. + * According to spec, biSizeImage would have to be set for RLE + * bmps. In reality, it is neither necessary for interpreting + * the file, nor do readers seem to mind when field is not set, + * so we just leave it at 0. + * + * TODO: Issue a warning when this happens. + */ } } + g_free (chains); + g_free (row); + gimp_progress_update (1.0); return TRUE; -- GitLab From 6e52bd8e7bc1fb7e4585cecbd65bdbb848885d4b Mon Sep 17 00:00:00 2001 From: Rupert Date: Sat, 14 Dec 2024 12:50:01 +0100 Subject: [PATCH 05/13] plug-ins: bmp-export - piece-wise loading 2/3 step2: move loading of pixels with gegl_buffer_get() from export_image() (which does all header stuff) to write_image() (which does the actual pixel-writing). --- plug-ins/file-bmp/bmp-export.c | 64 ++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/plug-ins/file-bmp/bmp-export.c b/plug-ins/file-bmp/bmp-export.c index b133f8f9da0..6dcfdc0825b 100644 --- a/plug-ins/file-bmp/bmp-export.c +++ b/plug-ins/file-bmp/bmp-export.c @@ -134,7 +134,8 @@ static const enum BmpInfoVer comp_min_header_for_colorspace = BMPINFO_V4; static gboolean write_image (FILE *f, - guchar *src, + GimpDrawable *drawable, + const Babl *format, gint width, gint height, gboolean use_run_length_encoding, @@ -251,8 +252,6 @@ export_image (GFile *file, gsize bytes_per_row; BitmapChannel cmasks[4]; gint ncolors = 0; - guchar *pixels = NULL; - GeglBuffer *buffer; const Babl *format; GimpImageType drawable_type; gint width, height; @@ -498,17 +497,6 @@ export_image (GFile *file, info_version = MAX (BMPINFO_V4, info_version); } - /* fetch the image */ - pixels = g_new (guchar, (gsize) width * height * channels); - buffer = gimp_drawable_get_buffer (drawable); - - gegl_buffer_get (buffer, - GEGL_RECTANGLE (0, 0, width, height), 1.0, - format, pixels, - GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); - - g_object_unref (buffer); - /* We should consider rejecting any width > (INT32_MAX - 31) / BitsPerPixel, * as the resulting BMP will likely cause integer overflow in other * readers.(Currently, GIMP's limit is way lower, anyway) @@ -569,7 +557,8 @@ export_image (GFile *file, goto abort_with_standard_message; if (! write_image (outfile, - pixels, width, height, + drawable, format, + width, height, use_rle, channels, bitmap_head.biBitCnt, bytes_per_row, ncolors, cmasks, @@ -577,7 +566,6 @@ export_image (GFile *file, goto abort_with_standard_message; fclose (outfile); - g_free (pixels); g_free (cmap); return GIMP_PDB_SUCCESS; @@ -591,7 +579,6 @@ abort_with_standard_message: abort: if (outfile) fclose (outfile); - g_free (pixels); g_free (cmap); return ret; @@ -747,7 +734,8 @@ info_header_size (enum BmpInfoVer version) static gboolean write_image (FILE *f, - guchar *src, + GimpDrawable *drawable, + const Babl *format, gint width, gint height, gboolean use_run_length_encoding, @@ -758,19 +746,31 @@ write_image (FILE *f, BitmapChannel *cmasks, gint header_size) { - guchar *temp, v; - guchar *row = NULL; - guchar *chains = NULL; - gint xpos, ypos, i, j, rowstride; - guint32 px32; - guint64 length; - gint breite, k; - guchar n; - gint channel_val[4]; - gint cur_progress; - gint max_progress; - gint padding; - gint alpha; + guchar *temp, *src = NULL, v; + guchar *row = NULL; + guchar *chains = NULL; + gint xpos, ypos, i, j, rowstride; + guint32 px32; + guint64 length; + gint breite, k; + guchar n; + gint channel_val[4]; + gint cur_progress; + gint max_progress; + gint padding; + gint alpha; + GeglBuffer *buffer; + + /* fetch the image */ + src = g_new (guchar, (gsize) width * height * channels); + buffer = gimp_drawable_get_buffer (drawable); + + gegl_buffer_get (buffer, + GEGL_RECTANGLE (0, 0, width, height), 1.0, + format, src, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + g_object_unref (buffer); rowstride = width * channels; @@ -1034,6 +1034,7 @@ write_image (FILE *f, g_free (chains); g_free (row); + g_free (src); gimp_progress_update (1.0); return TRUE; @@ -1041,6 +1042,7 @@ write_image (FILE *f, abort: g_free (chains); g_free (row); + g_free (src); return FALSE; } -- GitLab From b3a16e3e03820f54b7c415c27382447c98067df2 Mon Sep 17 00:00:00 2001 From: Rupert Date: Wed, 18 Dec 2024 13:38:46 +0100 Subject: [PATCH 06/13] plug-ins: bmp-export - piece-wise loading 3/3 step3: load only gimp_tile_height() lines at a time into intermediate buffer --- plug-ins/file-bmp/bmp-export.c | 37 +++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/plug-ins/file-bmp/bmp-export.c b/plug-ins/file-bmp/bmp-export.c index 6dcfdc0825b..2c6ac6194d5 100644 --- a/plug-ins/file-bmp/bmp-export.c +++ b/plug-ins/file-bmp/bmp-export.c @@ -759,19 +759,16 @@ write_image (FILE *f, gint max_progress; gint padding; gint alpha; - GeglBuffer *buffer; + GeglBuffer *buffer = NULL; + gint tile_height, tile_n = 0; + gsize line_offset; + + tile_height = MIN (gimp_tile_height (), height); /* fetch the image */ - src = g_new (guchar, (gsize) width * height * channels); + src = g_new (guchar, (gsize) width * tile_height * channels); buffer = gimp_drawable_get_buffer (drawable); - gegl_buffer_get (buffer, - GEGL_RECTANGLE (0, 0, width, height), 1.0, - format, src, - GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); - - g_object_unref (buffer); - rowstride = width * channels; channel_val[3] = 0xff; /* default alpha = opaque */ @@ -794,12 +791,24 @@ write_image (FILE *f, for (ypos = height - 1; ypos >= 0; ypos--) { + if (tile_n == 0) + { + tile_n = MIN (ypos + 1, tile_height); + gegl_buffer_get (buffer, + GEGL_RECTANGLE (0, ypos + 1 - tile_n, width, tile_n), + 1.0, format, src, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + } + + line_offset = --tile_n * rowstride; + /* We'll begin with the 16/24/32 bit Bitmaps, they are easy :-) */ if (bpp > 8) { for (xpos = 0; xpos < width; xpos++) { - temp = src + (ypos * rowstride) + (xpos * channels); + temp = src + line_offset + xpos * channels; + channel_val[0] = *temp++; if (channels > 2) { @@ -845,7 +854,7 @@ write_image (FILE *f, i <= (8 / bpp) && xpos < width; i++, xpos++) /* for each pixel */ { - temp = src + (ypos * rowstride) + (xpos * channels); + temp = src + line_offset + xpos * channels; if (channels > 1 && *(temp + 1) == 0) *temp = 0x0; @@ -875,7 +884,7 @@ write_image (FILE *f, { /* for each pixel */ - temp = src + (ypos * rowstride) + (xpos * channels); + temp = src + line_offset + (xpos * channels); if (channels > 1 && *(temp + 1) == 0) *temp = 0x0; @@ -998,6 +1007,8 @@ write_image (FILE *f, } + g_object_unref (buffer); + if (use_run_length_encoding) { if (fseek (f, -2, SEEK_CUR)) /* Overwrite last End of row ... */ @@ -1040,6 +1051,8 @@ write_image (FILE *f, return TRUE; abort: + if (buffer) + g_object_unref (buffer); g_free (chains); g_free (row); g_free (src); -- GitLab From ec43e0ef5e60092b3c1ee7c5370b17c348876e6a Mon Sep 17 00:00:00 2001 From: Rupert Date: Sun, 15 Dec 2024 15:43:40 +0100 Subject: [PATCH 07/13] plug-ins: bmp-export - piece-wise loading 4/3 :o) workaround for GEGL#400: Calling gegl_buffer_get() multiple times leads to memory exhaustion for *very* large images. Getting (and releasing) a new gegl buffer for each time we call gegl_buffer_get() solves the problem (for now). --- plug-ins/file-bmp/bmp-export.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/plug-ins/file-bmp/bmp-export.c b/plug-ins/file-bmp/bmp-export.c index 2c6ac6194d5..0a60cddd363 100644 --- a/plug-ins/file-bmp/bmp-export.c +++ b/plug-ins/file-bmp/bmp-export.c @@ -765,9 +765,10 @@ write_image (FILE *f, tile_height = MIN (gimp_tile_height (), height); - /* fetch the image */ - src = g_new (guchar, (gsize) width * tile_height * channels); - buffer = gimp_drawable_get_buffer (drawable); + src = g_new (guchar, (gsize) width * tile_height * channels); + + /* move the following into loop for now, see GEGL#400 */ + /* buffer = gimp_drawable_get_buffer (drawable); */ rowstride = width * channels; @@ -794,10 +795,18 @@ write_image (FILE *f, if (tile_n == 0) { tile_n = MIN (ypos + 1, tile_height); + + /* getting and unrefing the buffer here each time (vs doing it + * outside the loop and only calling gegl_buffer_get() here) avoids + * memory exhaustion for very large images, see GEGL#400 + */ + buffer = gimp_drawable_get_buffer (drawable); gegl_buffer_get (buffer, GEGL_RECTANGLE (0, ypos + 1 - tile_n, width, tile_n), 1.0, format, src, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + g_object_unref (buffer); + buffer = NULL; } line_offset = --tile_n * rowstride; @@ -1007,7 +1016,7 @@ write_image (FILE *f, } - g_object_unref (buffer); + /* g_object_unref (buffer); */ if (use_run_length_encoding) { -- GitLab From 5f96e6a7d527203196c6d0b479f179b273e95768 Mon Sep 17 00:00:00 2001 From: Rupert Date: Sat, 28 Dec 2024 15:40:33 +0100 Subject: [PATCH 08/13] plug-ins: bmp-export - make rowstride explicitly count bytes bytesperchannel is always 1 for now, but might later be extended for higher precision --- plug-ins/file-bmp/bmp-export.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plug-ins/file-bmp/bmp-export.c b/plug-ins/file-bmp/bmp-export.c index 0a60cddd363..95b0f25ecbf 100644 --- a/plug-ins/file-bmp/bmp-export.c +++ b/plug-ins/file-bmp/bmp-export.c @@ -749,7 +749,9 @@ write_image (FILE *f, guchar *temp, *src = NULL, v; guchar *row = NULL; guchar *chains = NULL; - gint xpos, ypos, i, j, rowstride; + gint xpos, ypos, i, j; + gsize rowstride; + gint bytesperchannel = 1; guint32 px32; guint64 length; gint breite, k; @@ -770,7 +772,7 @@ write_image (FILE *f, /* move the following into loop for now, see GEGL#400 */ /* buffer = gimp_drawable_get_buffer (drawable); */ - rowstride = width * channels; + rowstride = (gsize) width * channels * bytesperchannel; channel_val[3] = 0xff; /* default alpha = opaque */ alpha = channels == 4 || channels == 2 ? 1 : 0; @@ -809,7 +811,7 @@ write_image (FILE *f, buffer = NULL; } - line_offset = --tile_n * rowstride; + line_offset = rowstride * --tile_n; /* We'll begin with the 16/24/32 bit Bitmaps, they are easy :-) */ if (bpp > 8) -- GitLab From 026cc0f95fb8c43f95bfb4da4c14458aba78125b Mon Sep 17 00:00:00 2001 From: Rupert Date: Sun, 5 Jan 2025 21:17:38 +0100 Subject: [PATCH 09/13] plug-ins: bmp-export - extract pixel-writing into functions 1/4 prep Prepare for extracting pixel-writing into separate functions per bmp-format by collecting all parameters that will have to be passed down to pixel-writing functions into Fileinfo struct. --- plug-ins/file-bmp/bmp-export.c | 437 +++++++++++++++++---------------- 1 file changed, 219 insertions(+), 218 deletions(-) diff --git a/plug-ins/file-bmp/bmp-export.c b/plug-ins/file-bmp/bmp-export.c index 95b0f25ecbf..ee22bd1d8c4 100644 --- a/plug-ins/file-bmp/bmp-export.c +++ b/plug-ins/file-bmp/bmp-export.c @@ -133,60 +133,73 @@ static const enum BmpInfoVer comp_min_header_for_colorspace = BMPINFO_V4; -static gboolean write_image (FILE *f, - GimpDrawable *drawable, - const Babl *format, - gint width, - gint height, - gboolean use_run_length_encoding, - gint channels, - gint bpp, - gsize bytes_per_row, - gint ncolors, - BitmapChannel *cmasks, - gint frontmatter_size); - -static gboolean save_dialog (GimpProcedure *procedure, - GObject *config, - GimpImage *image, - gboolean indexed, - gboolean allow_alpha, - gboolean allow_rle); - -static void calc_masks_from_bits (BitmapChannel *cmasks, - gint r, - gint g, - gint b, - gint a); - -static gint calc_bitsperpixel_from_masks (BitmapChannel *cmasks); - -static gboolean are_masks_well_known (BitmapChannel *cmasks, - gint bpp); - -static gboolean are_masks_v1_standard (BitmapChannel *cmasks, - gint bpp); - -static void set_info_resolution (BitmapHead *bih, - GimpImage *image); - -static gint info_header_size (enum BmpInfoVer version); - -static gboolean write_u16_le (FILE *file, - guint16 u16); - -static gboolean write_u32_le (FILE *file, - guint32 u32); - -static gboolean write_s32_le (FILE *file, - gint32 s32); - -static gboolean write_file_header (FILE *file, - BitmapFileHead *bfh); - -static gboolean write_info_header (FILE *file, - BitmapHead *bih, - enum BmpInfoVer version); +struct Fileinfo +{ + /* image properties */ + gint width; + gint height; + gint channels; + gint bytesperchannel; + gint alpha; + gint ncolors; + /* file properties */ + BitmapChannel cmasks[4]; + gint bpp; + gboolean use_rle; + gsize bytes_per_row; + FILE *file; + /* RLE state */ + guint64 length; + guchar *row; + guchar *chains; +}; + +static gboolean write_image (struct Fileinfo *fi, + GimpDrawable *drawable, + const Babl *format, + gint frontmatter_size); + +static gboolean save_dialog (GimpProcedure *procedure, + GObject *config, + GimpImage *image, + gboolean indexed, + gboolean allow_alpha, + gboolean allow_rle); + +static void calc_masks_from_bits (BitmapChannel *cmasks, + gint r, + gint g, + gint b, + gint a); + +static gint calc_bitsperpixel_from_masks (BitmapChannel *cmasks); + +static gboolean are_masks_well_known (BitmapChannel *cmasks, + gint bpp); + +static gboolean are_masks_v1_standard (BitmapChannel *cmasks, + gint bpp); + +static void set_info_resolution (BitmapHead *bih, + GimpImage *image); + +static gint info_header_size (enum BmpInfoVer version); + +static gboolean write_u16_le (FILE *file, + guint16 u16); + +static gboolean write_u32_le (FILE *file, + guint32 u32); + +static gboolean write_s32_le (FILE *file, + gint32 s32); + +static gboolean write_file_header (FILE *file, + BitmapFileHead *bfh); + +static gboolean write_info_header (FILE *file, + BitmapHead *bih, + enum BmpInfoVer version); static gboolean write_color_map (FILE *f, @@ -235,7 +248,7 @@ warning_dialog (const gchar *primary, } GimpPDBStatusType -export_image (GFile *file, +export_image (GFile *gfile, GimpImage *image, GimpDrawable *drawable, GimpRunMode run_mode, @@ -244,19 +257,12 @@ export_image (GFile *file, GError **error) { GimpPDBStatusType ret = GIMP_PDB_EXECUTION_ERROR; - FILE *outfile = NULL; BitmapFileHead bitmap_file_head; BitmapHead bitmap_head; guchar *cmap = NULL; - gint channels; - gsize bytes_per_row; - BitmapChannel cmasks[4]; - gint ncolors = 0; const Babl *format; GimpImageType drawable_type; - gint width, height; gint i; - gboolean use_rle; gboolean write_color_space; RGBMode rgb_format = RGB_888; enum BmpInfoVer info_version = BMPINFO_V1; @@ -264,10 +270,11 @@ export_image (GFile *file, gboolean indexed_bmp = FALSE; gboolean allow_alpha = FALSE; gboolean allow_rle = FALSE; + struct Fileinfo fi; memset (&bitmap_file_head, 0, sizeof bitmap_file_head); memset (&bitmap_head, 0, sizeof bitmap_head); - memset (&cmasks, 0, sizeof cmasks); + memset (&fi, 0, sizeof fi); /* WINDOWS_COLOR_SPACE is the most "don't care" option available for V4+ * headers, which seems a reasonable default. @@ -277,14 +284,14 @@ export_image (GFile *file, bitmap_head.bV4CSType = V4CS_WINDOWS_COLOR_SPACE; drawable_type = gimp_drawable_type (drawable); - width = gimp_drawable_get_width (drawable); - height = gimp_drawable_get_height (drawable); + fi.width = gimp_drawable_get_width (drawable); + fi.height = gimp_drawable_get_height (drawable); switch (drawable_type) { case GIMP_RGBA_IMAGE: format = babl_format ("R'G'B'A u8"); - channels = 4; + fi.channels = 4; allow_alpha = TRUE; if (run_mode == GIMP_RUN_INTERACTIVE) @@ -300,7 +307,7 @@ export_image (GFile *file, case GIMP_RGB_IMAGE: format = babl_format ("R'G'B' u8"); - channels = 3; + fi.channels = 3; if (run_mode == GIMP_RUN_INTERACTIVE) g_object_set (config, @@ -328,21 +335,21 @@ export_image (GFile *file, case GIMP_GRAY_IMAGE: if (drawable_type == GIMP_GRAYA_IMAGE) { - format = babl_format ("Y'A u8"); - channels = 2; + format = babl_format ("Y'A u8"); + fi.channels = 2; } else { - format = babl_format ("Y' u8"); - channels = 1; + format = babl_format ("Y' u8"); + fi.channels = 1; } indexed_bmp = TRUE; - ncolors = 256; + fi.ncolors = 256; /* create a gray-scale color map */ - cmap = g_malloc (ncolors * 3); - for (i = 0; i < ncolors; i++) + cmap = g_malloc (fi.ncolors * 3); + for (i = 0; i < fi.ncolors; i++) cmap[3 * i + 0] = cmap[3 * i + 1] = cmap[3 * i + 2] = i; break; @@ -362,12 +369,12 @@ export_image (GFile *file, case GIMP_INDEXED_IMAGE: format = gimp_drawable_get_format (drawable); cmap = gimp_palette_get_colormap (gimp_image_get_palette (image), - babl_format ("R'G'B' u8"), &ncolors, NULL); + babl_format ("R'G'B' u8"), &fi.ncolors, NULL); if (drawable_type == GIMP_INDEXEDA_IMAGE) - channels = 2; + fi.channels = 2; else - channels = 1; + fi.channels = 1; indexed_bmp = TRUE; break; @@ -378,7 +385,7 @@ export_image (GFile *file, if (indexed_bmp) { - if (ncolors > 2) + if (fi.ncolors > 2) allow_rle = TRUE; else g_object_set (config, "use-rle", FALSE, NULL); @@ -396,7 +403,7 @@ export_image (GFile *file, } g_object_get (config, - "use-rle", &use_rle, + "use-rle", &fi.use_rle, "write-color-space", &write_color_space, NULL); @@ -405,24 +412,24 @@ export_image (GFile *file, if (indexed_bmp) { - if (ncolors > 16) + if (fi.ncolors > 16) { bitmap_head.biBitCnt = 8; } - else if (ncolors > 2) + else if (fi.ncolors > 2) { bitmap_head.biBitCnt = 4; } else { - g_assert (! use_rle); + g_assert (! fi.use_rle); bitmap_head.biBitCnt = 1; } - bitmap_head.biClrUsed = ncolors; - bitmap_head.biClrImp = ncolors; + bitmap_head.biClrUsed = fi.ncolors; + bitmap_head.biClrImp = fi.ncolors; - if (use_rle) + if (fi.use_rle) bitmap_head.biCompr = bitmap_head.biBitCnt == 8 ? BI_RLE8 : BI_RLE4; else bitmap_head.biCompr = BI_RGB; @@ -432,33 +439,33 @@ export_image (GFile *file, switch (rgb_format) { case RGB_888: - calc_masks_from_bits (cmasks, 8, 8, 8, 0); + calc_masks_from_bits (fi.cmasks, 8, 8, 8, 0); break; case RGBA_8888: - calc_masks_from_bits (cmasks, 8, 8, 8, 8); + calc_masks_from_bits (fi.cmasks, 8, 8, 8, 8); break; case RGBX_8888: - calc_masks_from_bits (cmasks, 8, 8, 8, 0); + calc_masks_from_bits (fi.cmasks, 8, 8, 8, 0); break; case RGB_565: - calc_masks_from_bits (cmasks, 5, 6, 5, 0); + calc_masks_from_bits (fi.cmasks, 5, 6, 5, 0); break; case RGBA_5551: - calc_masks_from_bits (cmasks, 5, 5, 5, 1); + calc_masks_from_bits (fi.cmasks, 5, 5, 5, 1); break; case RGB_555: - calc_masks_from_bits (cmasks, 5, 5, 5, 0); + calc_masks_from_bits (fi.cmasks, 5, 5, 5, 0); break; default: g_return_val_if_reached (GIMP_PDB_EXECUTION_ERROR); } - bitmap_head.biBitCnt = calc_bitsperpixel_from_masks (cmasks); + bitmap_head.biBitCnt = calc_bitsperpixel_from_masks (fi.cmasks); /* pointless, but it exists: */ if (bitmap_head.biBitCnt == 24 && rgb_format == RGBX_8888) bitmap_head.biBitCnt = 32; - if (are_masks_well_known (cmasks, bitmap_head.biBitCnt) && + if (are_masks_well_known (fi.cmasks, bitmap_head.biBitCnt) && (bitmap_head.biBitCnt == 24 || ! comp_16_and_32_only_as_bitfields)) { bitmap_head.biCompr = BI_RGB; @@ -467,20 +474,20 @@ export_image (GFile *file, { bitmap_head.biCompr = BI_BITFIELDS; for (gint c = 0; c < 4; c++) - bitmap_head.masks[c] = cmasks[c].mask << cmasks[c].shiftin; + bitmap_head.masks[c] = fi.cmasks[c].mask << fi.cmasks[c].shiftin; info_version = MAX (comp_min_header_for_bitfields, info_version); - if (cmasks[3].mask) /* have alpha channel, need at least v3 */ + if (fi.cmasks[3].mask) /* have alpha channel, need at least v3 */ info_version = MAX (BMPINFO_V3_ADOBE, info_version); - if (! are_masks_v1_standard (cmasks, bitmap_head.biBitCnt)) + if (! are_masks_v1_standard (fi.cmasks, bitmap_head.biBitCnt)) info_version = MAX (comp_min_header_for_non_standard_masks, info_version); } } gimp_progress_init_printf (_("Exporting '%s'"), - gimp_file_get_utf8_name (file)); + gimp_file_get_utf8_name (gfile)); if (write_color_space) { @@ -501,19 +508,19 @@ export_image (GFile *file, * as the resulting BMP will likely cause integer overflow in other * readers.(Currently, GIMP's limit is way lower, anyway) */ - g_assert (width <= (G_MAXSIZE - 31) / bitmap_head.biBitCnt); + g_assert (fi.width <= (G_MAXSIZE - 31) / bitmap_head.biBitCnt); - bytes_per_row = (((guint64) width * bitmap_head.biBitCnt + 31) / 32) * 4; + fi.bytes_per_row = (((guint64) fi.width * bitmap_head.biBitCnt + 31) / 32) * 4; bitmap_head.biSize = info_header_size (info_version); - frontmatter_size = 14 + bitmap_head.biSize + 4 * ncolors; + frontmatter_size = 14 + bitmap_head.biSize + 4 * fi.ncolors; if (info_version < BMPINFO_V2_ADOBE && bitmap_head.biCompr == BI_BITFIELDS) frontmatter_size += 12; /* V1 header stores RGB masks outside header */ bitmap_file_head.bfOffs = frontmatter_size; - if (use_rle || (guint64) bytes_per_row * height + frontmatter_size > UINT32_MAX) + if (fi.use_rle || (guint64) fi.bytes_per_row * fi.height + frontmatter_size > UINT32_MAX) { /* for RLE, we don't know the size until after writing the image and * will update later. @@ -526,46 +533,44 @@ export_image (GFile *file, } else { - bitmap_file_head.bfSize = frontmatter_size + (bytes_per_row * height); - bitmap_head.biSizeIm = bytes_per_row * height; + bitmap_file_head.bfSize = frontmatter_size + (fi.bytes_per_row * fi.height); + bitmap_head.biSizeIm = fi.bytes_per_row * fi.height; } - bitmap_head.biWidth = width; - bitmap_head.biHeight = height; + bitmap_head.biWidth = fi.width; + bitmap_head.biHeight = fi.height; bitmap_head.biPlanes = 1; set_info_resolution (&bitmap_head, image); - outfile = g_fopen (g_file_peek_path (file), "wb"); - if (! outfile) + fi.file = g_fopen (g_file_peek_path (gfile), "wb"); + if (! fi.file) { g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), _("Could not open '%s' for writing: %s"), - gimp_file_get_utf8_name (file), g_strerror (errno)); + gimp_file_get_utf8_name (gfile), g_strerror (errno)); goto abort; } bitmap_file_head.zzMagic[0] = 'B'; bitmap_file_head.zzMagic[1] = 'M'; - if (! write_file_header (outfile, &bitmap_file_head)) + if (! write_file_header (fi.file, &bitmap_file_head)) goto abort_with_standard_message; - if (! write_info_header (outfile, &bitmap_head, info_version)) + if (! write_info_header (fi.file, &bitmap_head, info_version)) goto abort_with_standard_message; - if (ncolors && ! write_color_map (outfile, cmap, ncolors)) + if (fi.ncolors && ! write_color_map (fi.file, cmap, fi.ncolors)) goto abort_with_standard_message; - if (! write_image (outfile, - drawable, format, - width, height, - use_rle, - channels, bitmap_head.biBitCnt, bytes_per_row, - ncolors, cmasks, + fi.bpp = bitmap_head.biBitCnt; + if (! write_image (&fi, + drawable, + format, frontmatter_size)) goto abort_with_standard_message; - fclose (outfile); + fclose (fi.file); g_free (cmap); return GIMP_PDB_SUCCESS; @@ -577,8 +582,8 @@ abort_with_standard_message: /* fall through to abort */ abort: - if (outfile) - fclose (outfile); + if (fi.file) + fclose (fi.file); g_free (cmap); return ret; @@ -733,66 +738,58 @@ info_header_size (enum BmpInfoVer version) } static gboolean -write_image (FILE *f, - GimpDrawable *drawable, - const Babl *format, - gint width, - gint height, - gboolean use_run_length_encoding, - gint channels, - gint bpp, - gsize bytes_per_row, - gint ncolors, - BitmapChannel *cmasks, - gint header_size) +write_image (struct Fileinfo *fi, + GimpDrawable *drawable, + const Babl *format, + gint frontmatter_size) { guchar *temp, *src = NULL, v; - guchar *row = NULL; - guchar *chains = NULL; gint xpos, ypos, i, j; gsize rowstride; - gint bytesperchannel = 1; guint32 px32; - guint64 length; gint breite, k; guchar n; gint channel_val[4]; gint cur_progress; gint max_progress; gint padding; - gint alpha; GeglBuffer *buffer = NULL; gint tile_height, tile_n = 0; gsize line_offset; - tile_height = MIN (gimp_tile_height (), height); + /* we currently only export 8-bit images; later, bytesperchannel will be + * properly set according to the actual image precision. + */ + fi->bytesperchannel = 1; - src = g_new (guchar, (gsize) width * tile_height * channels); + tile_height = MIN (gimp_tile_height (), fi->height); + + src = g_new (guchar, (gsize) fi->width * tile_height * fi->channels); /* move the following into loop for now, see GEGL#400 */ /* buffer = gimp_drawable_get_buffer (drawable); */ - rowstride = (gsize) width * channels * bytesperchannel; + rowstride = (gsize) fi->width * fi->channels * fi->bytesperchannel; channel_val[3] = 0xff; /* default alpha = opaque */ - alpha = channels == 4 || channels == 2 ? 1 : 0; + fi->alpha = fi->channels == 4 || fi->channels == 2 ? 1 : 0; - if (! use_run_length_encoding) + if (! fi->use_rle) { - padding = bytes_per_row - ((guint64) width * bpp + 7) / 8; + padding = fi->bytes_per_row - ((guint64) fi->width * fi->bpp + 7) / 8; } else { - padding = 0; /* RLE does its own pixel-based padding */ - row = g_new (guchar, width / (8 / bpp) + 10); - chains = g_new (guchar, width / (8 / bpp) + 10); - length = 0; + padding = 0; /* RLE does its own pixel-based padding */ + fi->row = g_new (guchar, fi->width / (8 / fi->bpp) + 10); + fi->chains = g_new (guchar, fi->width / (8 / fi->bpp) + 10); + fi->length = 0; } cur_progress = 0; - max_progress = height; + max_progress = fi->height; - for (ypos = height - 1; ypos >= 0; ypos--) + for (ypos = fi->height - 1; ypos >= 0; ypos--) { if (tile_n == 0) { @@ -804,7 +801,7 @@ write_image (FILE *f, */ buffer = gimp_drawable_get_buffer (drawable); gegl_buffer_get (buffer, - GEGL_RECTANGLE (0, ypos + 1 - tile_n, width, tile_n), + GEGL_RECTANGLE (0, ypos + 1 - tile_n, fi->width, tile_n), 1.0, format, src, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); g_object_unref (buffer); @@ -814,14 +811,14 @@ write_image (FILE *f, line_offset = rowstride * --tile_n; /* We'll begin with the 16/24/32 bit Bitmaps, they are easy :-) */ - if (bpp > 8) + if (fi->bpp > 8) { - for (xpos = 0; xpos < width; xpos++) + for (xpos = 0; xpos < fi->width; xpos++) { - temp = src + line_offset + xpos * channels; + temp = src + line_offset + xpos * fi->channels; channel_val[0] = *temp++; - if (channels > 2) + if (fi->channels > 2) { /* RGB */ channel_val[1] = *temp++; @@ -833,19 +830,19 @@ write_image (FILE *f, channel_val[1] = channel_val[2] = channel_val[0]; } - if (alpha) + if (fi->alpha) channel_val[3] = *temp++; px32 = 0; for (gint c = 0; c < 4; c++) { - px32 |= (guint32) (channel_val[c] / 255. * cmasks[c].max_value + 0.5) - << cmasks[c].shiftin; + px32 |= (guint32) (channel_val[c] / 255. * fi->cmasks[c].max_value + 0.5) + << fi->cmasks[c].shiftin; } - for (j = 0; j < bpp; j += 8) + for (j = 0; j < fi->bpp; j += 8) { - if (EOF == putc ((px32 >> j) & 0xff, f)) + if (EOF == putc ((px32 >> j) & 0xff, fi->file)) goto abort; } } @@ -854,26 +851,26 @@ write_image (FILE *f, else /* indexed */ { /* now it gets more difficult */ - if (! use_run_length_encoding || bpp == 1) + if (! fi->use_rle || fi->bpp == 1) { /* uncompressed 1,4 and 8 bit */ - for (xpos = 0; xpos < width;) /* for each _byte_ */ + for (xpos = 0; xpos < fi->width;) /* for each _byte_ */ { v = 0; for (i = 1; - i <= (8 / bpp) && xpos < width; - i++, xpos++) /* for each pixel */ + i <= (8 / fi->bpp) && xpos < fi->width; + i++, xpos++) /* for each pixel */ { - temp = src + line_offset + xpos * channels; + temp = src + line_offset + xpos * fi->channels; - if (channels > 1 && *(temp + 1) == 0) + if (fi->channels > 1 && *(temp + 1) == 0) *temp = 0x0; - v = v | ((guchar) *temp << (8 - (i * bpp))); + v = v | ((guchar) *temp << (8 - (i * fi->bpp))); } - if (fwrite (&v, 1, 1, f) != 1) + if (fwrite (&v, 1, 1, fi->file) != 1) goto abort; } } @@ -885,29 +882,29 @@ write_image (FILE *f, * from two 4bit pixels */ j = 0; - for (xpos = 0; xpos < width;) + for (xpos = 0; xpos < fi->width;) { v = 0; for (i = 1; - (i <= (8 / bpp)) && (xpos < width); + (i <= (8 / fi->bpp)) && (xpos < fi->width); i++, xpos++) { /* for each pixel */ - temp = src + line_offset + (xpos * channels); + temp = src + line_offset + (xpos * fi->channels); - if (channels > 1 && *(temp + 1) == 0) + if (fi->channels > 1 && *(temp + 1) == 0) *temp = 0x0; - v = v | ((guchar) * temp << (8 - (i * bpp))); + v = v | ((guchar) *temp << (8 - (i * fi->bpp))); } - row[j++] = v; + fi->row[j++] = v; } - breite = width / (8 / bpp); - if (width % (8 / bpp)) + breite = fi->width / (8 / fi->bpp); + if (fi->width % (8 / fi->bpp)) breite++; /* then check for strings of equal bytes */ @@ -916,67 +913,67 @@ write_image (FILE *f, j = 0; while ((i + j < breite) && - (j < (255 / (8 / bpp))) && - (row[i + j] == row[i])) + (j < (255 / (8 / fi->bpp))) && + (fi->row[i + j] == fi->row[i])) j++; - chains[i] = j; + fi->chains[i] = j; } /* then write the strings and the other pixels to the file */ for (i = 0; i < breite;) { - if (chains[i] < 3) + if (fi->chains[i] < 3) { /* strings of different pixels ... */ j = 0; while ((i + j < breite) && - (j < (255 / (8 / bpp))) && - (chains[i + j] < 3)) - j += chains[i + j]; + (j < (255 / (8 / fi->bpp))) && + (fi->chains[i + j] < 3)) + j += fi->chains[i + j]; /* this can only happen if j jumps over the end * with a 2 in chains[i+j] */ - if (j > (255 / (8 / bpp))) + if (j > (255 / (8 / fi->bpp))) j -= 2; /* 00 01 and 00 02 are reserved */ if (j > 2) { - n = j * (8 / bpp); - if (n + i * (8 / bpp) > width) + n = j * (8 / fi->bpp); + if (n + i * (8 / fi->bpp) > fi->width) n--; - if (EOF == putc (0, f) || EOF == putc (n, f)) + if (EOF == putc (0, fi->file) || EOF == putc (n, fi->file)) goto abort; - length += 2; + fi->length += 2; - if (fwrite (&row[i], 1, j, f) != j) + if (fwrite (&fi->row[i], 1, j, fi->file) != j) goto abort; - length += j; + fi->length += j; if ((j) % 2) { - if (EOF == putc (0, f)) + if (EOF == putc (0, fi->file)) goto abort; - length++; + fi->length++; } } else { for (k = i; k < i + j; k++) { - n = (8 / bpp); - if (n + i * (8 / bpp) > width) + n = (8 / fi->bpp); + if (n + i * (8 / fi->bpp) > fi->width) n--; - if (EOF == putc (n, f) || EOF == putc (row[k], f)) + if (EOF == putc (n, fi->file) || EOF == putc (fi->row[k], fi->file)) goto abort; - length += 2; + fi->length += 2; } } @@ -986,28 +983,28 @@ write_image (FILE *f, { /* strings of equal pixels */ - n = chains[i] * (8 / bpp); - if (n + i * (8 / bpp) > width) + n = fi->chains[i] * (8 / fi->bpp); + if (n + i * (8 / fi->bpp) > fi->width) n--; - if (EOF == putc (n, f) || EOF == putc (row[i], f)) + if (EOF == putc (n, fi->file) || EOF == putc (fi->row[i], fi->file)) goto abort; - i += chains[i]; - length += 2; + i += fi->chains[i]; + fi->length += 2; } } - if (EOF == putc (0, f) || EOF == putc (0, f)) /* End of row */ + if (EOF == putc (0, fi->file) || EOF == putc (0, fi->file)) /* End of row */ goto abort; - length += 2; + fi->length += 2; } /* RLE */ } for (int j = 0; j < padding; j++) { - if (EOF == putc (0, f)) + if (EOF == putc (0, fi->file)) goto abort; } @@ -1020,26 +1017,30 @@ write_image (FILE *f, /* g_object_unref (buffer); */ - if (use_run_length_encoding) + if (fi->use_rle) { - if (fseek (f, -2, SEEK_CUR)) /* Overwrite last End of row ... */ + /* Overwrite last End of row with End of file */ + if (fseek (fi->file, -2, SEEK_CUR)) goto abort; - if (EOF == putc (0, f) || EOF == putc (1, f)) /* ... with End of file */ + if (EOF == putc (0, fi->file) || EOF == putc (1, fi->file)) goto abort; - if (length <= UINT32_MAX) + if (fi->length <= UINT32_MAX) { /* Write length of image data */ - if (fseek (f, 0x22, SEEK_SET)) + if (fseek (fi->file, 0x22, SEEK_SET)) goto abort; - if (! write_u32_le (f, length)) + if (! write_u32_le (fi->file, fi->length)) goto abort; - /* Write length of file */ - if (fseek (f, 0x02, SEEK_SET)) - goto abort; - if (! write_u32_le (f, length + header_size)) - goto abort; + if (fi->length <= UINT32_MAX - frontmatter_size) + { + /* Write length of file */ + if (fseek (fi->file, 0x02, SEEK_SET)) + goto abort; + if (! write_u32_le (fi->file, fi->length + frontmatter_size)) + goto abort; + } } else { @@ -1054,8 +1055,8 @@ write_image (FILE *f, } } - g_free (chains); - g_free (row); + g_free (fi->chains); + g_free (fi->row); g_free (src); gimp_progress_update (1.0); @@ -1064,8 +1065,8 @@ write_image (FILE *f, abort: if (buffer) g_object_unref (buffer); - g_free (chains); - g_free (row); + g_free (fi->chains); + g_free (fi->row); g_free (src); return FALSE; -- GitLab From 5852af2d9774d9498ffcca9814d22d13476b4ad3 Mon Sep 17 00:00:00 2001 From: Rupert Date: Sat, 4 Jan 2025 14:32:59 +0100 Subject: [PATCH 10/13] plug-ins: bmp-export - extract pixel-writing into functions 2/4 rgb Extract pixel-writing of rgb data into separate function. Adapt it to already accept higher than 8-bit precision image data. (Not used yet) --- plug-ins/file-bmp/bmp-export.c | 116 ++++++++++++++++++++++----------- 1 file changed, 77 insertions(+), 39 deletions(-) diff --git a/plug-ins/file-bmp/bmp-export.c b/plug-ins/file-bmp/bmp-export.c index ee22bd1d8c4..0ecac43a475 100644 --- a/plug-ins/file-bmp/bmp-export.c +++ b/plug-ins/file-bmp/bmp-export.c @@ -185,6 +185,9 @@ static void set_info_resolution (BitmapHead *bih, static gint info_header_size (enum BmpInfoVer version); +static gboolean write_rgb (const guchar *src, + struct Fileinfo *fi); + static gboolean write_u16_le (FILE *file, guint16 u16); @@ -746,10 +749,8 @@ write_image (struct Fileinfo *fi, guchar *temp, *src = NULL, v; gint xpos, ypos, i, j; gsize rowstride; - guint32 px32; gint breite, k; guchar n; - gint channel_val[4]; gint cur_progress; gint max_progress; gint padding; @@ -771,8 +772,7 @@ write_image (struct Fileinfo *fi, rowstride = (gsize) fi->width * fi->channels * fi->bytesperchannel; - channel_val[3] = 0xff; /* default alpha = opaque */ - fi->alpha = fi->channels == 4 || fi->channels == 2 ? 1 : 0; + fi->alpha = fi->channels == 4 || fi->channels == 2 ? 1 : 0; if (! fi->use_rle) { @@ -810,43 +810,10 @@ write_image (struct Fileinfo *fi, line_offset = rowstride * --tile_n; - /* We'll begin with the 16/24/32 bit Bitmaps, they are easy :-) */ if (fi->bpp > 8) { - for (xpos = 0; xpos < fi->width; xpos++) - { - temp = src + line_offset + xpos * fi->channels; - - channel_val[0] = *temp++; - if (fi->channels > 2) - { - /* RGB */ - channel_val[1] = *temp++; - channel_val[2] = *temp++; - } - else - { - /* fake grayscale */ - channel_val[1] = channel_val[2] = channel_val[0]; - } - - if (fi->alpha) - channel_val[3] = *temp++; - - px32 = 0; - for (gint c = 0; c < 4; c++) - { - px32 |= (guint32) (channel_val[c] / 255. * fi->cmasks[c].max_value + 0.5) - << fi->cmasks[c].shiftin; - } - - for (j = 0; j < fi->bpp; j += 8) - { - if (EOF == putc ((px32 >> j) & 0xff, fi->file)) - goto abort; - } - } - + if (! write_rgb (src + line_offset, fi)) + goto abort; } else /* indexed */ { @@ -1072,6 +1039,77 @@ abort: return FALSE; } +static guint32 +int32_from_bytes (const guchar *src, gint bytes) +{ + guint32 ret; + + switch (bytes) + { + case 1: + ret = *src; + break; + case 2: + ret = *(guint16 *) src; + break; + case 4: + ret = *(guint32 *) src; + break; + default: + ret = 0; + g_assert_not_reached (); + } + return ret; +} + +static gboolean +write_rgb (const guchar *src, struct Fileinfo *fi) +{ + gint xpos; + guint32 channel_val[4], px32; + + channel_val[3] = 0xff; /* default alpha = opaque */ + + for (xpos = 0; xpos < fi->width; xpos++) + { + for (gint c = 0; c < fi->channels - fi->alpha; c++) + { + channel_val[c] = int32_from_bytes (src, fi->bytesperchannel); + src += fi->bytesperchannel; + } + + if (fi->channels < 3) + { + /* fake grayscale */ + channel_val[1] = channel_val[2] = channel_val[0]; + } + + if (fi->alpha > 0) + { + channel_val[3] = int32_from_bytes (src, fi->bytesperchannel); + src += fi->bytesperchannel; + } + + px32 = 0; + for (gint c = 0; c < 4; c++) + { + gdouble d; + + d = (gdouble) channel_val[c] / ((1 << (fi->bytesperchannel * 8)) - 1); + d *= fi->cmasks[c].max_value; + + px32 |= (guint32) (d + 0.5) << fi->cmasks[c].shiftin; + } + + for (gint i = 0; i < fi->bpp; i += 8) + { + if (EOF == putc ((px32 >> i) & 0xff, fi->file)) + return FALSE; + } + } + return TRUE; +} + static gboolean write_little_endian (FILE *file, guint32 u32, gint bytes) { -- GitLab From 7d9a43848f05587942789000cd2670df51e37f54 Mon Sep 17 00:00:00 2001 From: Rupert Date: Sat, 4 Jan 2025 14:45:38 +0100 Subject: [PATCH 11/13] plug-ins: bmp-export - extract pixel-writing into functions 3/4 indexed Extract pixel-writing of indexed data into separate function. Make the function less tricky / more obvious (don't increment the outer loop counter in the inner loop) --- plug-ins/file-bmp/bmp-export.c | 55 ++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/plug-ins/file-bmp/bmp-export.c b/plug-ins/file-bmp/bmp-export.c index 0ecac43a475..373c6cff466 100644 --- a/plug-ins/file-bmp/bmp-export.c +++ b/plug-ins/file-bmp/bmp-export.c @@ -188,6 +188,9 @@ static gint info_header_size (enum BmpInfoVer version); static gboolean write_rgb (const guchar *src, struct Fileinfo *fi); +static gboolean write_indexed (const guchar *src, + struct Fileinfo *fi); + static gboolean write_u16_le (FILE *file, guint16 u16); @@ -817,29 +820,10 @@ write_image (struct Fileinfo *fi, } else /* indexed */ { - /* now it gets more difficult */ - if (! fi->use_rle || fi->bpp == 1) + if (! fi->use_rle) { - /* uncompressed 1,4 and 8 bit */ - - for (xpos = 0; xpos < fi->width;) /* for each _byte_ */ - { - v = 0; - for (i = 1; - i <= (8 / fi->bpp) && xpos < fi->width; - i++, xpos++) /* for each pixel */ - { - temp = src + line_offset + xpos * fi->channels; - - if (fi->channels > 1 && *(temp + 1) == 0) - *temp = 0x0; - - v = v | ((guchar) *temp << (8 - (i * fi->bpp))); - } - - if (fwrite (&v, 1, 1, fi->file) != 1) - goto abort; - } + if (! write_indexed (src + line_offset, fi)) + goto abort; } else { @@ -1110,6 +1094,33 @@ write_rgb (const guchar *src, struct Fileinfo *fi) return TRUE; } +static gboolean +write_indexed (const guchar *src, struct Fileinfo *fi) +{ + gint xpos, val; + gint byte = 0, pxi = 0; + gint pxperbyte = 8 / fi->bpp; + + for (xpos = 0; xpos < fi->width; xpos++) + { + val = *src++; + + if (fi->alpha > 0 && *src++ == 0) + val = 0; + + byte |= val << (8 - ++pxi * fi->bpp); + + if (pxi == pxperbyte || xpos == fi->width - 1) + { + if (EOF == putc (byte, fi->file)) + return FALSE; + byte = 0; + pxi = 0; + } + } + return TRUE; +} + static gboolean write_little_endian (FILE *file, guint32 u32, gint bytes) { -- GitLab From 33a2744ffe9527693ba0b230a9e3b6bc1e5b4350 Mon Sep 17 00:00:00 2001 From: Rupert Date: Sat, 4 Jan 2025 15:21:02 +0100 Subject: [PATCH 12/13] plug-ins: bmp-export - extract pixel-writing into functions 4/4 rle Extract pixel-writing of rle data into separate function. Simplified the first part analogous to the indexed function. Renamed the German 'breite' to 'bytewidth'. --- plug-ins/file-bmp/bmp-export.c | 264 +++++++++++++++++---------------- 1 file changed, 136 insertions(+), 128 deletions(-) diff --git a/plug-ins/file-bmp/bmp-export.c b/plug-ins/file-bmp/bmp-export.c index 373c6cff466..4db9ae7a8c1 100644 --- a/plug-ins/file-bmp/bmp-export.c +++ b/plug-ins/file-bmp/bmp-export.c @@ -191,6 +191,9 @@ static gboolean write_rgb (const guchar *src, static gboolean write_indexed (const guchar *src, struct Fileinfo *fi); +static gboolean write_rle (const guchar *src, + struct Fileinfo *fi); + static gboolean write_u16_le (FILE *file, guint16 u16); @@ -749,11 +752,9 @@ write_image (struct Fileinfo *fi, const Babl *format, gint frontmatter_size) { - guchar *temp, *src = NULL, v; - gint xpos, ypos, i, j; + guchar *src = NULL; + gint ypos; gsize rowstride; - gint breite, k; - guchar n; gint cur_progress; gint max_progress; gint padding; @@ -786,7 +787,6 @@ write_image (struct Fileinfo *fi, padding = 0; /* RLE does its own pixel-based padding */ fi->row = g_new (guchar, fi->width / (8 / fi->bpp) + 10); fi->chains = g_new (guchar, fi->width / (8 / fi->bpp) + 10); - fi->length = 0; } cur_progress = 0; @@ -827,130 +827,9 @@ write_image (struct Fileinfo *fi, } else { - /* Save RLE encoded file, quite difficult */ - - /* first copy the pixels to a buffer, making one byte - * from two 4bit pixels - */ - j = 0; - for (xpos = 0; xpos < fi->width;) - { - v = 0; - - for (i = 1; - (i <= (8 / fi->bpp)) && (xpos < fi->width); - i++, xpos++) - { - /* for each pixel */ - - temp = src + line_offset + (xpos * fi->channels); - - if (fi->channels > 1 && *(temp + 1) == 0) - *temp = 0x0; - - v = v | ((guchar) *temp << (8 - (i * fi->bpp))); - } - - fi->row[j++] = v; - } - - breite = fi->width / (8 / fi->bpp); - if (fi->width % (8 / fi->bpp)) - breite++; - - /* then check for strings of equal bytes */ - for (i = 0; i < breite; i += j) - { - j = 0; - - while ((i + j < breite) && - (j < (255 / (8 / fi->bpp))) && - (fi->row[i + j] == fi->row[i])) - j++; - - fi->chains[i] = j; - } - - /* then write the strings and the other pixels to the file */ - for (i = 0; i < breite;) - { - if (fi->chains[i] < 3) - { - /* strings of different pixels ... */ - - j = 0; - - while ((i + j < breite) && - (j < (255 / (8 / fi->bpp))) && - (fi->chains[i + j] < 3)) - j += fi->chains[i + j]; - - /* this can only happen if j jumps over the end - * with a 2 in chains[i+j] - */ - if (j > (255 / (8 / fi->bpp))) - j -= 2; - - /* 00 01 and 00 02 are reserved */ - if (j > 2) - { - n = j * (8 / fi->bpp); - if (n + i * (8 / fi->bpp) > fi->width) - n--; - - if (EOF == putc (0, fi->file) || EOF == putc (n, fi->file)) - goto abort; - - fi->length += 2; - - if (fwrite (&fi->row[i], 1, j, fi->file) != j) - goto abort; - - fi->length += j; - if ((j) % 2) - { - if (EOF == putc (0, fi->file)) - goto abort; - fi->length++; - } - } - else - { - for (k = i; k < i + j; k++) - { - n = (8 / fi->bpp); - if (n + i * (8 / fi->bpp) > fi->width) - n--; - - if (EOF == putc (n, fi->file) || EOF == putc (fi->row[k], fi->file)) - goto abort; - fi->length += 2; - } - } - - i += j; - } - else - { - /* strings of equal pixels */ - - n = fi->chains[i] * (8 / fi->bpp); - if (n + i * (8 / fi->bpp) > fi->width) - n--; - - if (EOF == putc (n, fi->file) || EOF == putc (fi->row[i], fi->file)) - goto abort; - - i += fi->chains[i]; - fi->length += 2; - } - } - - if (EOF == putc (0, fi->file) || EOF == putc (0, fi->file)) /* End of row */ + if (! write_rle (src + line_offset, fi)) goto abort; - fi->length += 2; - - } /* RLE */ + } } for (int j = 0; j < padding; j++) @@ -1121,6 +1000,135 @@ write_indexed (const guchar *src, struct Fileinfo *fi) return TRUE; } +static gboolean +write_rle (const guchar *src, struct Fileinfo *fi) +{ + gint xpos, i, j, k, n, val, ndup; + gint byte = 0, pxi = 0; + gint pxperbyte = 8 / fi->bpp; + gint bytewidth; /* width in bytes of packed row */ + + /* first copy the pixels to a buffer, making one byte + * from two 4bit pixels + */ + for (xpos = 0, i = 0; xpos < fi->width; xpos++) + { + val = *src++; + + if (fi->alpha > 0 && *src++ == 0) + val = 0; + + byte |= val << (8 - ++pxi * fi->bpp); + + if (pxi == pxperbyte || xpos == fi->width - 1) + { + fi->row[i++] = byte; + + byte = 0; + pxi = 0; + } + } + + bytewidth = (fi->width + pxperbyte - 1) / pxperbyte; + + /* then check for strings of equal bytes */ + for (i = 0; i < bytewidth; i += ndup) + { + ndup = 0; + + while (i + ndup < bytewidth && + ndup < 255 / pxperbyte && + fi->row[i + ndup] == fi->row[i]) + { + ndup++; + } + + fi->chains[i] = ndup; + } + + /* then write the strings and the other pixels to the file */ + for (i = 0; i < bytewidth;) + { + if (fi->chains[i] < 3) + { + /* strings of different pixels ... */ + + j = 0; + + while (i + j < bytewidth && + j < 255 / pxperbyte && + fi->chains[i + j] < 3) + { + j += fi->chains[i + j]; + } + + /* this can only happen if j jumps over the end + * with a 2 in chains[i+j] + */ + if (j > 255 / pxperbyte) + j -= 2; + + /* 00 01 and 00 02 are reserved */ + if (j > 2) + { + n = j * pxperbyte; + if (n + i * pxperbyte > fi->width) + n--; + + if (EOF == putc (0, fi->file) || EOF == putc (n, fi->file)) + return FALSE; + + fi->length += 2; + + if (fwrite (&fi->row[i], 1, j, fi->file) != j) + return FALSE; + + fi->length += j; + if (j % 2) + { + if (EOF == putc (0, fi->file)) + return FALSE; + fi->length++; + } + } + else + { + for (k = i; k < i + j; k++) + { + n = pxperbyte; + if (n + i * pxperbyte > fi->width) + n--; + + if (EOF == putc (n, fi->file) || EOF == putc (fi->row[k], fi->file)) + return FALSE; + fi->length += 2; + } + } + + i += j; + } + else + { + /* strings of equal pixels */ + + n = fi->chains[i] * pxperbyte; + if (n + i * pxperbyte > fi->width) + n--; + + if (EOF == putc (n, fi->file) || EOF == putc (fi->row[i], fi->file)) + return FALSE; + + i += fi->chains[i]; + fi->length += 2; + } + } + + if (EOF == putc (0, fi->file) || EOF == putc (0, fi->file)) /* End of row */ + return FALSE; + fi->length += 2; + return TRUE; +} + static gboolean write_little_endian (FILE *file, guint32 u32, gint bytes) { -- GitLab From 1fa89cdd6e233f26c780d5a8054a8ebedcc0a1f5 Mon Sep 17 00:00:00 2001 From: Rupert Date: Sat, 4 Jan 2025 23:11:25 +0100 Subject: [PATCH 13/13] plug-ins: bmp-export - RLE cleanup / refactor Try to make the RLE code easier to understand: remove duplicate code, some renames, add comments In the while loop l. 1062, instead of potentially overshooting the max run-length and correcting afterwards, check for the proper limit in the while condition. --- plug-ins/file-bmp/bmp-export.c | 97 ++++++++++++++-------------------- 1 file changed, 41 insertions(+), 56 deletions(-) diff --git a/plug-ins/file-bmp/bmp-export.c b/plug-ins/file-bmp/bmp-export.c index 4db9ae7a8c1..58bbc4d97a8 100644 --- a/plug-ins/file-bmp/bmp-export.c +++ b/plug-ins/file-bmp/bmp-export.c @@ -149,7 +149,7 @@ struct Fileinfo gsize bytes_per_row; FILE *file; /* RLE state */ - guint64 length; + guint64 img_size; guchar *row; guchar *chains; }; @@ -855,20 +855,20 @@ write_image (struct Fileinfo *fi, if (EOF == putc (0, fi->file) || EOF == putc (1, fi->file)) goto abort; - if (fi->length <= UINT32_MAX) + if (fi->img_size <= UINT32_MAX) { - /* Write length of image data */ + /* Write size of image data */ if (fseek (fi->file, 0x22, SEEK_SET)) goto abort; - if (! write_u32_le (fi->file, fi->length)) + if (! write_u32_le (fi->file, fi->img_size)) goto abort; - if (fi->length <= UINT32_MAX - frontmatter_size) + if (fi->img_size <= UINT32_MAX - frontmatter_size) { - /* Write length of file */ + /* Write size of file */ if (fseek (fi->file, 0x02, SEEK_SET)) goto abort; - if (! write_u32_le (fi->file, fi->length + frontmatter_size)) + if (! write_u32_le (fi->file, fi->img_size + frontmatter_size)) goto abort; } } @@ -1003,7 +1003,7 @@ write_indexed (const guchar *src, struct Fileinfo *fi) static gboolean write_rle (const guchar *src, struct Fileinfo *fi) { - gint xpos, i, j, k, n, val, ndup; + gint xpos, i, n, val, byte_run, ndup; gint byte = 0, pxi = 0; gint pxperbyte = 8 / fi->bpp; gint bytewidth; /* width in bytes of packed row */ @@ -1031,7 +1031,11 @@ write_rle (const guchar *src, struct Fileinfo *fi) bytewidth = (fi->width + pxperbyte - 1) / pxperbyte; - /* then check for strings of equal bytes */ + /* then check for runs of identical bytes/pixels + * + * Note: chains[] is used sparsely. Only the indices corresponding to a + * start of a run of repeated bytes/pixels will be used. + */ for (i = 0; i < bytewidth; i += ndup) { ndup = 0; @@ -1046,86 +1050,67 @@ write_rle (const guchar *src, struct Fileinfo *fi) fi->chains[i] = ndup; } - /* then write the strings and the other pixels to the file */ - for (i = 0; i < bytewidth;) + /* then write the literal runs and repeated pixels to the file */ + for (i = 0; i < bytewidth; i += byte_run) { if (fi->chains[i] < 3) { - /* strings of different pixels ... */ + /* run of literal pixels */ - j = 0; + byte_run = 0; - while (i + j < bytewidth && - j < 255 / pxperbyte && - fi->chains[i + j] < 3) + while (i + byte_run < bytewidth && + byte_run + fi->chains[i + byte_run] < 256 / pxperbyte && + fi->chains[i + byte_run] < 3) { - j += fi->chains[i + j]; + byte_run += fi->chains[i + byte_run]; } - /* this can only happen if j jumps over the end - * with a 2 in chains[i+j] - */ - if (j > 255 / pxperbyte) - j -= 2; - /* 00 01 and 00 02 are reserved */ - if (j > 2) + if (byte_run > 2) { - n = j * pxperbyte; + n = byte_run * pxperbyte; if (n + i * pxperbyte > fi->width) n--; if (EOF == putc (0, fi->file) || EOF == putc (n, fi->file)) return FALSE; + fi->img_size += 2; - fi->length += 2; - - if (fwrite (&fi->row[i], 1, j, fi->file) != j) + if (fwrite (&fi->row[i], 1, byte_run, fi->file) != byte_run) return FALSE; + fi->img_size += byte_run; - fi->length += j; - if (j % 2) + if (byte_run % 2) { if (EOF == putc (0, fi->file)) return FALSE; - fi->length++; + fi->img_size++; } - } - else - { - for (k = i; k < i + j; k++) - { - n = pxperbyte; - if (n + i * pxperbyte > fi->width) - n--; - if (EOF == putc (n, fi->file) || EOF == putc (fi->row[k], fi->file)) - return FALSE; - fi->length += 2; - } + continue; } - i += j; + /* chain too short for literal pixels, fall through to repeated pixels */ } - else - { - /* strings of equal pixels */ - n = fi->chains[i] * pxperbyte; - if (n + i * pxperbyte > fi->width) - n--; + /* run of repeated pixels */ - if (EOF == putc (n, fi->file) || EOF == putc (fi->row[i], fi->file)) - return FALSE; + byte_run = fi->chains[i]; - i += fi->chains[i]; - fi->length += 2; - } + n = byte_run * pxperbyte; + if (n + i * pxperbyte > fi->width) + n--; + + if (EOF == putc (n, fi->file) || EOF == putc (fi->row[i], fi->file)) + return FALSE; + fi->img_size += 2; } if (EOF == putc (0, fi->file) || EOF == putc (0, fi->file)) /* End of row */ return FALSE; - fi->length += 2; + fi->img_size += 2; + return TRUE; } -- GitLab