diff --git a/plug-ins/file-bmp/bmp-export.c b/plug-ins/file-bmp/bmp-export.c index 103b80b66b..58bbc4d97a 100644 --- a/plug-ins/file-bmp/bmp-export.c +++ b/plug-ins/file-bmp/bmp-export.c @@ -42,45 +42,192 @@ #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); +/* Compatibilty settings: + * ====================== + * + * These settings control how the file headers are written, they do not limit + * the availability of any features. + */ -static gboolean save_dialog (GimpProcedure *procedure, - GObject *config, - GimpImage *image, - gint channels, - gint bpp); +/* + * 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; + + + + +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 img_size; + 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_rgb (const guchar *src, + struct Fileinfo *fi); + +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); + +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; } @@ -110,7 +257,7 @@ warning_dialog (const gchar *primary, } GimpPDBStatusType -export_image (GFile *file, +export_image (GFile *gfile, GimpImage *image, GimpDrawable *drawable, GimpRunMode run_mode, @@ -118,45 +265,43 @@ export_image (GFile *file, GObject *config, GError **error) { - 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 *pixels = NULL; - GeglBuffer *buffer; - const Babl *format; - GimpImageType drawable_type; - gint drawable_width; - gint drawable_height; - gint i; - gint mask_info_size; - gint color_space_size; - guint32 Mask[4]; - gboolean use_rle; - gboolean write_color_space; - RGBMode rgb_format; + GimpPDBStatusType ret = GIMP_PDB_EXECUTION_ERROR; + BitmapFileHead bitmap_file_head; + BitmapHead bitmap_head; + guchar *cmap = NULL; + const Babl *format; + GimpImageType drawable_type; + gint i; + 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; + struct Fileinfo fi; - buffer = gimp_drawable_get_buffer (drawable); + memset (&bitmap_file_head, 0, sizeof bitmap_file_head); + memset (&bitmap_head, 0, sizeof bitmap_head); + memset (&fi, 0, sizeof fi); - drawable_type = gimp_drawable_type (drawable); - drawable_width = gimp_drawable_get_width (drawable); - drawable_height = gimp_drawable_get_height (drawable); + /* 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); + 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"); - colors = 0; - BitsPerPixel = 32; - MapSize = 0; - channels = 4; + fi.channels = 4; + allow_alpha = TRUE; if (run_mode == GIMP_RUN_INTERACTIVE) g_object_set (config, @@ -171,10 +316,7 @@ export_image (GFile *file, case GIMP_RGB_IMAGE: format = babl_format ("R'G'B' u8"); - colors = 0; - BitsPerPixel = 24; - MapSize = 0; - channels = 3; + fi.channels = 3; if (run_mode == GIMP_RUN_INTERACTIVE) g_object_set (config, @@ -192,32 +334,33 @@ 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 */ case GIMP_GRAY_IMAGE: - colors = 256; - BitsPerPixel = 8; - MapSize = 1024; - 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; } - for (i = 0; i < colors; i++) - { - Red[i] = i; - Green[i] = i; - Blue[i] = i; - } + indexed_bmp = TRUE; + fi.ncolors = 256; + + /* create a gray-scale color map */ + 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; case GIMP_INDEXEDA_IMAGE: @@ -225,775 +368,876 @@ 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 */ 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"), &fi.ncolors, NULL); if (drawable_type == GIMP_INDEXEDA_IMAGE) - channels = 2; + fi.channels = 2; else - channels = 1; + fi.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 (run_mode == GIMP_RUN_INTERACTIVE && - (BitsPerPixel == 8 || - BitsPerPixel == 4 || - BitsPerPixel == 1)) + if (indexed_bmp) { - if (! save_dialog (procedure, config, image, 1, BitsPerPixel)) - return GIMP_PDB_CANCEL; + if (fi.ncolors > 2) + allow_rle = TRUE; + else + g_object_set (config, "use-rle", FALSE, NULL); } - else if (BitsPerPixel == 24 || - BitsPerPixel == 32) + + /* display export dialog and retreive selected options */ + + if (run_mode == GIMP_RUN_INTERACTIVE) { - if (run_mode == GIMP_RUN_INTERACTIVE) + if (! save_dialog (procedure, config, image, indexed_bmp, allow_alpha, allow_rle)) { - if (! save_dialog (procedure, config, image, channels, BitsPerPixel)) - return GIMP_PDB_CANCEL; - } - - rgb_format = - gimp_procedure_config_get_choice_id (GIMP_PROCEDURE_CONFIG (config), - "rgb-format"); - - /* mask_info_size is only set to non-zero for 16- and 32-bpp */ - switch (rgb_format) - { - case RGB_888: - BitsPerPixel = 24; - break; - case RGBA_8888: - BitsPerPixel = 32; - mask_info_size = 16; - break; - case RGBX_8888: - BitsPerPixel = 32; - mask_info_size = 16; - break; - case RGB_565: - BitsPerPixel = 16; - mask_info_size = 16; - break; - case RGBA_5551: - BitsPerPixel = 16; - mask_info_size = 16; - break; - case RGB_555: - BitsPerPixel = 16; - mask_info_size = 16; - break; - default: - g_return_val_if_reached (GIMP_PDB_EXECUTION_ERROR); + ret = GIMP_PDB_CANCEL; + goto abort; } } g_object_get (config, - "use-rle", &use_rle, + "use-rle", &fi.use_rle, "write-color-space", &write_color_space, NULL); - rgb_format = - gimp_procedure_config_get_choice_id (GIMP_PROCEDURE_CONFIG (config), - "rgb-format"); - gimp_progress_init_printf (_("Exporting '%s'"), - gimp_file_get_utf8_name (file)); + rgb_format = gimp_procedure_config_get_choice_id (GIMP_PROCEDURE_CONFIG (config), + "rgb-format"); - outfile = g_fopen (g_file_peek_path (file), "wb"); - - if (! outfile) + if (indexed_bmp) { - 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 (fi.ncolors > 16) + { + bitmap_head.biBitCnt = 8; + } + else if (fi.ncolors > 2) + { + bitmap_head.biBitCnt = 4; + } + else + { + g_assert (! fi.use_rle); + bitmap_head.biBitCnt = 1; + } - /* fetch the image */ - pixels = g_new (guchar, (gsize) drawable_width * drawable_height * channels); + bitmap_head.biClrUsed = fi.ncolors; + bitmap_head.biClrImp = fi.ncolors; - gegl_buffer_get (buffer, - GEGL_RECTANGLE (0, 0, drawable_width, drawable_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; - - /* ... 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) 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_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); - - 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; - - 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; + if (fi.use_rle) + bitmap_head.biCompr = bitmap_head.biBitCnt == 8 ? BI_RLE8 : BI_RLE4; else bitmap_head.biCompr = BI_RGB; } - else if (BitsPerPixel == 8) - { - bitmap_head.biCompr = BI_RLE8; - } - else if (BitsPerPixel == 4) - { - bitmap_head.biCompr = BI_RLE4; - } else - { - bitmap_head.biCompr = BI_RGB; - } - - 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; - -#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 - - /* And now write the header and the colormap (if any) to disk */ - - if (fwrite ("BM", 2, 1, outfile) != 1) - 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) - 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) - 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; + calc_masks_from_bits (fi.cmasks, 8, 8, 8, 0); break; - case RGBA_8888: - Mask[0] = 0x00ff0000; - Mask[1] = 0x0000ff00; - Mask[2] = 0x000000ff; - Mask[3] = 0xff000000; + calc_masks_from_bits (fi.cmasks, 8, 8, 8, 8); + break; + case RGBX_8888: + calc_masks_from_bits (fi.cmasks, 8, 8, 8, 0); break; - case RGB_565: - Mask[0] = 0xf800; - Mask[1] = 0x7e0; - Mask[2] = 0x1f; - Mask[3] = 0x0; + calc_masks_from_bits (fi.cmasks, 5, 6, 5, 0); break; - case RGBA_5551: - Mask[0] = 0x7c00; - Mask[1] = 0x3e0; - Mask[2] = 0x1f; - Mask[3] = 0x8000; + calc_masks_from_bits (fi.cmasks, 5, 5, 5, 1); break; - case RGB_555: - Mask[0] = 0x7c00; - Mask[1] = 0x3e0; - Mask[2] = 0x1f; - Mask[3] = 0x0; + 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 (fi.cmasks); - 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]); + /* pointless, but it exists: */ + if (bitmap_head.biBitCnt == 24 && rgb_format == RGBX_8888) + bitmap_head.biBitCnt = 32; - if (fwrite (&Mask, mask_info_size, 1, outfile) != 1) - goto abort; + 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; + } + else + { + bitmap_head.biCompr = BI_BITFIELDS; + for (gint c = 0; c < 4; c++) + bitmap_head.masks[c] = fi.cmasks[c].mask << fi.cmasks[c].shiftin; + + info_version = MAX (comp_min_header_for_bitfields, info_version); + + 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 (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 (gfile)); + if (write_color_space) { - guint32 buf[0x11]; + bitmap_head.bV4CSType = V4CS_sRGB; - /* Write V5 color space fields */ - - /* bV5CSType = LCS_sRGB */ - buf[0x00] = GUINT32_TO_LE (0x73524742); - - /* bV5Endpoints is set to 0 (ignored) */ - for (i = 0; i < 0x09; i++) - buf[i + 1] = 0x00; - - /* bV5GammaRed is set to 0 (ignored) */ - buf[0x0a] = GUINT32_TO_LE (0x0); - - /* bV5GammaGreen is set to 0 (ignored) */ - buf[0x0b] = GUINT32_TO_LE (0x0); - - /* bV5GammaBlue is set to 0 (ignored) */ - buf[0x0c] = GUINT32_TO_LE (0x0); - - /* bV5Intent = LCS_GM_GRAPHICS */ - buf[0x0d] = GUINT32_TO_LE (0x00000002); - - /* bV5ProfileData is set to 0 (ignored) */ - buf[0x0e] = GUINT32_TO_LE (0x0); - - /* bV5ProfileSize is set to 0 (ignored) */ - buf[0x0f] = GUINT32_TO_LE (0x0); - - /* bV5Reserved = 0 */ - buf[0x10] = GUINT32_TO_LE (0x0); - - if (fwrite (buf, color_space_size, 1, outfile) != 1) - goto abort; + info_version = MAX (BMPINFO_V4, info_version); + info_version = MAX (comp_min_header_for_colorspace, info_version); } - if (! write_color_map (outfile, Red, Green, Blue, MapSize)) - goto abort; - - /* After that is done, we write the image ... */ - - 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; - - /* ... and exit normally */ - - fclose (outfile); - g_free (pixels); - - return GIMP_PDB_SUCCESS; - -abort: - if (outfile) - fclose (outfile); - g_free(pixels); - - g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, - _("Error writing to file.")); - - return GIMP_PDB_EXECUTION_ERROR; -} - -static inline void -Make565 (guchar r, - guchar g, - guchar b, - guchar *buf) -{ - gint p; - - 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)))); - - buf[0] = (guchar) (p & 0xff); - buf[1] = (guchar) (p >> 8); -} - -static inline void -Make5551 (guchar r, - guchar g, - guchar b, - guchar a, - guchar *buf) -{ - 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); -} - -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) -{ - guchar buf[16]; - guint32 uint32buf; - guchar *temp, v; - guchar *row = NULL; - guchar *chains = NULL; - gint xpos, ypos, i, j, rowstride, length, thiswidth; - gint breite, k; - guchar n, r, g, b, a; - gint cur_progress; - gint max_progress; - gint padding; - - xpos = 0; - rowstride = width * channels; - - cur_progress = 0; - max_progress = height; - - /* We'll begin with the 16/24/32 bit Bitmaps, they are easy :-) */ - - if (bpp > 8) + if (comp_current_official_headers_only) { - padding = bytes_per_row - (width * (bpp / 8)); - for (ypos = height - 1; ypos >= 0; ypos--) /* for each row */ - { - for (i = 0; i < width; i++) /* for each pixel */ - { - temp = src + (ypos * rowstride) + (xpos * channels); - switch (rgb_format) - { - 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; + /* don't use v2/v3 headers */ + if (info_version >= BMPINFO_V2_ADOBE) + info_version = MAX (BMPINFO_V4, info_version); + } - 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; + /* 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 (fi.width <= (G_MAXSIZE - 31) / bitmap_head.biBitCnt); - 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++; + fi.bytes_per_row = (((guint64) fi.width * bitmap_head.biBitCnt + 31) / 32) * 4; - 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++; + bitmap_head.biSize = info_header_size (info_version); + frontmatter_size = 14 + bitmap_head.biSize + 4 * fi.ncolors; - 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 (info_version < BMPINFO_V2_ADOBE && bitmap_head.biCompr == BI_BITFIELDS) + frontmatter_size += 12; /* V1 header stores RGB masks outside header */ - 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++; + bitmap_file_head.bfOffs = frontmatter_size; - if (fwrite (buf, 1, 2, f) != 2) - goto abort; - break; - } - } - - 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); - - xpos = 0; - } + 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. + * 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 { - /* now it gets more difficult */ - if (! use_run_length_encoding || bpp == 1) + bitmap_file_head.bfSize = frontmatter_size + (fi.bytes_per_row * fi.height); + bitmap_head.biSizeIm = fi.bytes_per_row * fi.height; + } + + bitmap_head.biWidth = fi.width; + bitmap_head.biHeight = fi.height; + bitmap_head.biPlanes = 1; + + set_info_resolution (&bitmap_head, image); + + 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 (gfile), g_strerror (errno)); + goto abort; + } + + bitmap_file_head.zzMagic[0] = 'B'; + bitmap_file_head.zzMagic[1] = 'M'; + if (! write_file_header (fi.file, &bitmap_file_head)) + goto abort_with_standard_message; + + if (! write_info_header (fi.file, &bitmap_head, info_version)) + goto abort_with_standard_message; + + if (fi.ncolors && ! write_color_map (fi.file, cmap, fi.ncolors)) + goto abort_with_standard_message; + + fi.bpp = bitmap_head.biBitCnt; + if (! write_image (&fi, + drawable, + format, + frontmatter_size)) + goto abort_with_standard_message; + + fclose (fi.file); + g_free (cmap); + + 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 (fi.file) + fclose (fi.file); + g_free (cmap); + + return ret; +} + +static gboolean +are_masks_well_known (BitmapChannel *cmasks, gint bpp) +{ + gint c, bitsperchannel; + + /* 16/24/32-bit BMPs each have one 'well-known' BI_RGB representation + * that doesn't require to write the masks with BI_BITFIELDS + */ + + if (cmasks[3].nbits != 0) /* alpha */ + return FALSE; + + if (bpp == 16) + bitsperchannel = 5; + else if (bpp == 24 || bpp == 32) + bitsperchannel = 8; + else + return FALSE; + + for (c = 0; c < 3; c++) + { + if (cmasks[c].nbits != bitsperchannel) + return FALSE; + } + + return TRUE; +} + +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 (cmasks[3].nbits != 0) /* alpha */ + return FALSE; + + if (bpp == 16) + { + if (cmasks[0].nbits == 5 && + (cmasks[1].nbits == 5 || cmasks[1].nbits == 6) && + cmasks[2].nbits == 5) { - /* uncompressed 1,4 and 8 bit */ + return TRUE; + } + } + else if (bpp == 32) + { + if (cmasks[0].nbits == 8 && + cmasks[1].nbits == 8 && + cmasks[2].nbits == 8) + { + return TRUE; + } + } - thiswidth = (width * bpp + 7) / 8; - padding = bytes_per_row - thiswidth; + return FALSE; +} - for (ypos = height - 1; ypos >= 0; ypos--) /* for each row */ +static gint +calc_bitsperpixel_from_masks (BitmapChannel *cmasks) +{ + gint c, bitsum = 0; + + for (c = 0; c < 4; c++) + bitsum += cmasks[c].nbits; + + if (bitsum > 16) + { + if (bitsum == 24 && are_masks_well_known (cmasks, 24)) + return 24; + else + return 32; + } + return 16; +} + +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 void +set_info_resolution (BitmapHead *bih, GimpImage *image) +{ + 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. + * 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 gint +info_header_size (enum BmpInfoVer version) +{ + 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 (struct Fileinfo *fi, + GimpDrawable *drawable, + const Babl *format, + gint frontmatter_size) +{ + guchar *src = NULL; + gint ypos; + gsize rowstride; + gint cur_progress; + gint max_progress; + gint padding; + GeglBuffer *buffer = NULL; + gint tile_height, tile_n = 0; + gsize line_offset; + + /* we currently only export 8-bit images; later, bytesperchannel will be + * properly set according to the actual image precision. + */ + fi->bytesperchannel = 1; + + 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) fi->width * fi->channels * fi->bytesperchannel; + + fi->alpha = fi->channels == 4 || fi->channels == 2 ? 1 : 0; + + if (! fi->use_rle) + { + padding = fi->bytes_per_row - ((guint64) fi->width * fi->bpp + 7) / 8; + } + else + { + 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); + } + + cur_progress = 0; + max_progress = fi->height; + + for (ypos = fi->height - 1; ypos >= 0; ypos--) + { + 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, fi->width, tile_n), + 1.0, format, src, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + g_object_unref (buffer); + buffer = NULL; + } + + line_offset = rowstride * --tile_n; + + if (fi->bpp > 8) + { + if (! write_rgb (src + line_offset, fi)) + goto abort; + } + else /* indexed */ + { + if (! fi->use_rle) { - for (xpos = 0; xpos < width;) /* for each _byte_ */ - { - v = 0; - for (i = 1; - (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 (! write_indexed (src + line_offset, fi)) + goto abort; + } + else + { + if (! write_rle (src + line_offset, fi)) + goto abort; + } + } - if (fwrite (&v, 1, 1, f) != 1) - goto abort; - } + for (int j = 0; j < padding; j++) + { + if (EOF == putc (0, fi->file)) + goto abort; + } - 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); - xpos = 0; + } - cur_progress++; - if ((cur_progress % 5) == 0) - gimp_progress_update ((gdouble) cur_progress / - (gdouble) max_progress); + /* g_object_unref (buffer); */ + + if (fi->use_rle) + { + /* Overwrite last End of row with End of file */ + if (fseek (fi->file, -2, SEEK_CUR)) + goto abort; + if (EOF == putc (0, fi->file) || EOF == putc (1, fi->file)) + goto abort; + + if (fi->img_size <= UINT32_MAX) + { + /* Write size of image data */ + if (fseek (fi->file, 0x22, SEEK_SET)) + goto abort; + if (! write_u32_le (fi->file, fi->img_size)) + goto abort; + + if (fi->img_size <= UINT32_MAX - frontmatter_size) + { + /* Write size of file */ + if (fseek (fi->file, 0x02, SEEK_SET)) + goto abort; + if (! write_u32_le (fi->file, fi->img_size + frontmatter_size)) + goto abort; } } 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--) - { - /* each row separately */ - j = 0; - - /* first copy the pixels to a buffer, making one byte - * from two 4bit pixels - */ - for (xpos = 0; xpos < width;) - { - v = 0; - - for (i = 1; - (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))); - } - - row[j++] = v; - } - - breite = width / (8 / bpp); - if (width % (8 / 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 / bpp))) && - (row[i + j] == row[i])) - j++; - - chains[i] = j; - } - - /* then write the strings and the other pixels to the file */ - for (i = 0; i < breite;) - { - if (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]; - - /* this can only happen if j jumps over the end - * with a 2 in chains[i+j] - */ - if (j > (255 / (8 / bpp))) - j -= 2; - - /* 00 01 and 00 02 are reserved */ - if (j > 2) - { - n = j * (8 / bpp); - if (n + i * (8 / bpp) > width) - n--; - - if (EOF == putc (0, f) || EOF == putc (n, f)) - goto abort; - - length += 2; - - if (fwrite (&row[i], 1, j, f) != j) - goto abort; - - length += j; - if ((j) % 2) - { - if (EOF == putc (0, f)) - goto abort; - length++; - } - } - else - { - for (k = i; k < i + j; k++) - { - n = (8 / bpp); - if (n + i * (8 / bpp) > width) - n--; - - if (EOF == putc (n, f) || EOF == putc (row[k], f)) - goto abort; - length += 2; - } - } - - i += j; - } - else - { - /* strings of equal pixels */ - - n = chains[i] * (8 / bpp); - if (n + i * (8 / bpp) > width) - n--; - - if (EOF == putc (n, f) || EOF == putc (row[i], f)) - goto abort; - - i += chains[i]; - length += 2; - } - } - - if (EOF == putc (0, f) || EOF == putc (0, f)) /* End of row */ - goto abort; - length += 2; - - cur_progress++; - if ((cur_progress % 5) == 0) - gimp_progress_update ((gdouble) cur_progress / - (gdouble) max_progress); - } - - 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 (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 (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; - - g_free (chains); - g_free (row); + /* 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 (fi->chains); + g_free (fi->row); + g_free (src); + gimp_progress_update (1.0); return TRUE; abort: - g_free (chains); - g_free (row); + if (buffer) + g_object_unref (buffer); + g_free (fi->chains); + g_free (fi->row); + g_free (src); return FALSE; } -static gboolean -format_sensitive_callback (GObject *config, - gpointer data) +static guint32 +int32_from_bytes (const guchar *src, gint bytes) { - gint value; - gint channels = GPOINTER_TO_INT (data); + guint32 ret; - value = gimp_procedure_config_get_choice_id (GIMP_PROCEDURE_CONFIG (config), - "rgb-format"); - - switch (value) + switch (bytes) { - case RGBA_5551: - case RGBA_8888: - return channels == 4; - + case 1: + ret = *src; + break; + case 2: + ret = *(guint16 *) src; + break; + case 4: + ret = *(guint32 *) src; + break; default: - return TRUE; - }; + 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_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_rle (const guchar *src, struct Fileinfo *fi) +{ + 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 */ + + /* 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 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; + + while (i + ndup < bytewidth && + ndup < 255 / pxperbyte && + fi->row[i + ndup] == fi->row[i]) + { + ndup++; + } + + fi->chains[i] = ndup; + } + + /* then write the literal runs and repeated pixels to the file */ + for (i = 0; i < bytewidth; i += byte_run) + { + if (fi->chains[i] < 3) + { + /* run of literal pixels */ + + byte_run = 0; + + while (i + byte_run < bytewidth && + byte_run + fi->chains[i + byte_run] < 256 / pxperbyte && + fi->chains[i + byte_run] < 3) + { + byte_run += fi->chains[i + byte_run]; + } + + /* 00 01 and 00 02 are reserved */ + if (byte_run > 2) + { + 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; + + if (fwrite (&fi->row[i], 1, byte_run, fi->file) != byte_run) + return FALSE; + fi->img_size += byte_run; + + if (byte_run % 2) + { + if (EOF == putc (0, fi->file)) + return FALSE; + fi->img_size++; + } + + continue; + } + + /* chain too short for literal pixels, fall through to repeated pixels */ + } + + /* run of repeated pixels */ + + byte_run = fi->chains[i]; + + 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->img_size += 2; + + return TRUE; +} + +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 void @@ -1001,7 +1245,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 +1255,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,15 +1277,17 @@ 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; - 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), @@ -1052,7 +1298,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 */ @@ -1078,17 +1324,15 @@ 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 (channels)); - 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", - (channels >= 3), + ! indexed, NULL, NULL, FALSE); /* Formatting the dialog */ @@ -1108,13 +1352,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 3c030bd62c..26936b11cb 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__ */