Home | History | Annotate | Download | only in libjpeg-turbo
      1 /*
      2  * wrbmp.c
      3  *
      4  * This file was part of the Independent JPEG Group's software:
      5  * Copyright (C) 1994-1996, Thomas G. Lane.
      6  * libjpeg-turbo Modifications:
      7  * Copyright (C) 2013, Linaro Limited.
      8  * Copyright (C) 2014-2015, 2017, 2019, D. R. Commander.
      9  * For conditions of distribution and use, see the accompanying README.ijg
     10  * file.
     11  *
     12  * This file contains routines to write output images in Microsoft "BMP"
     13  * format (MS Windows 3.x and OS/2 1.x flavors).
     14  * Either 8-bit colormapped or 24-bit full-color format can be written.
     15  * No compression is supported.
     16  *
     17  * These routines may need modification for non-Unix environments or
     18  * specialized applications.  As they stand, they assume output to
     19  * an ordinary stdio stream.
     20  *
     21  * This code contributed by James Arthur Boucher.
     22  */
     23 
     24 #include "cmyk.h"
     25 #include "cdjpeg.h"             /* Common decls for cjpeg/djpeg applications */
     26 #include "jconfigint.h"
     27 
     28 #ifdef BMP_SUPPORTED
     29 
     30 
     31 /*
     32  * To support 12-bit JPEG data, we'd have to scale output down to 8 bits.
     33  * This is not yet implemented.
     34  */
     35 
     36 #if BITS_IN_JSAMPLE != 8
     37   Sorry, this code only copes with 8-bit JSAMPLEs. /* deliberate syntax err */
     38 #endif
     39 
     40 /*
     41  * Since BMP stores scanlines bottom-to-top, we have to invert the image
     42  * from JPEG's top-to-bottom order.  To do this, we save the outgoing data
     43  * in a virtual array during put_pixel_row calls, then actually emit the
     44  * BMP file during finish_output.  The virtual array contains one JSAMPLE per
     45  * pixel if the output is grayscale or colormapped, three if it is full color.
     46  */
     47 
     48 /* Private version of data destination object */
     49 
     50 typedef struct {
     51   struct djpeg_dest_struct pub; /* public fields */
     52 
     53   boolean is_os2;               /* saves the OS2 format request flag */
     54 
     55   jvirt_sarray_ptr whole_image; /* needed to reverse row order */
     56   JDIMENSION data_width;        /* JSAMPLEs per row */
     57   JDIMENSION row_width;         /* physical width of one row in the BMP file */
     58   int pad_bytes;                /* number of padding bytes needed per row */
     59   JDIMENSION cur_output_row;    /* next row# to write to virtual array */
     60 
     61   boolean use_inversion_array;  /* TRUE = buffer the whole image, which is
     62                                    stored to disk in bottom-up order, and
     63                                    receive rows from the calling program in
     64                                    top-down order
     65 
     66                                    FALSE = the calling program will maintain
     67                                    its own image buffer and write the rows in
     68                                    bottom-up order */
     69 
     70   JSAMPLE *iobuffer;            /* I/O buffer (used to buffer a single row to
     71                                    disk if use_inversion_array == FALSE) */
     72 } bmp_dest_struct;
     73 
     74 typedef bmp_dest_struct *bmp_dest_ptr;
     75 
     76 
     77 /* Forward declarations */
     78 LOCAL(void) write_colormap(j_decompress_ptr cinfo, bmp_dest_ptr dest,
     79                            int map_colors, int map_entry_size);
     80 
     81 
     82 static INLINE boolean is_big_endian(void)
     83 {
     84   int test_value = 1;
     85   if (*(char *)&test_value != 1)
     86     return TRUE;
     87   return FALSE;
     88 }
     89 
     90 
     91 /*
     92  * Write some pixel data.
     93  * In this module rows_supplied will always be 1.
     94  */
     95 
     96 METHODDEF(void)
     97 put_pixel_rows(j_decompress_ptr cinfo, djpeg_dest_ptr dinfo,
     98                JDIMENSION rows_supplied)
     99 /* This version is for writing 24-bit pixels */
    100 {
    101   bmp_dest_ptr dest = (bmp_dest_ptr)dinfo;
    102   JSAMPARRAY image_ptr;
    103   register JSAMPROW inptr, outptr;
    104   register JDIMENSION col;
    105   int pad;
    106 
    107   if (dest->use_inversion_array) {
    108     /* Access next row in virtual array */
    109     image_ptr = (*cinfo->mem->access_virt_sarray)
    110       ((j_common_ptr)cinfo, dest->whole_image,
    111        dest->cur_output_row, (JDIMENSION)1, TRUE);
    112     dest->cur_output_row++;
    113     outptr = image_ptr[0];
    114   } else {
    115     outptr = dest->iobuffer;
    116   }
    117 
    118   /* Transfer data.  Note destination values must be in BGR order
    119    * (even though Microsoft's own documents say the opposite).
    120    */
    121   inptr = dest->pub.buffer[0];
    122 
    123   if (cinfo->out_color_space == JCS_EXT_BGR) {
    124     MEMCOPY(outptr, inptr, dest->row_width);
    125     outptr += cinfo->output_width * 3;
    126   } else if (cinfo->out_color_space == JCS_RGB565) {
    127     boolean big_endian = is_big_endian();
    128     unsigned short *inptr2 = (unsigned short *)inptr;
    129     for (col = cinfo->output_width; col > 0; col--) {
    130       if (big_endian) {
    131         outptr[0] = (*inptr2 >> 5) & 0xF8;
    132         outptr[1] = ((*inptr2 << 5) & 0xE0) | ((*inptr2 >> 11) & 0x1C);
    133         outptr[2] = *inptr2 & 0xF8;
    134       } else {
    135         outptr[0] = (*inptr2 << 3) & 0xF8;
    136         outptr[1] = (*inptr2 >> 3) & 0xFC;
    137         outptr[2] = (*inptr2 >> 8) & 0xF8;
    138       }
    139       outptr += 3;
    140       inptr2++;
    141     }
    142   } else if (cinfo->out_color_space == JCS_CMYK) {
    143     for (col = cinfo->output_width; col > 0; col--) {
    144       /* can omit GETJSAMPLE() safely */
    145       JSAMPLE c = *inptr++, m = *inptr++, y = *inptr++, k = *inptr++;
    146       cmyk_to_rgb(c, m, y, k, outptr + 2, outptr + 1, outptr);
    147       outptr += 3;
    148     }
    149   } else {
    150     register int rindex = rgb_red[cinfo->out_color_space];
    151     register int gindex = rgb_green[cinfo->out_color_space];
    152     register int bindex = rgb_blue[cinfo->out_color_space];
    153     register int ps = rgb_pixelsize[cinfo->out_color_space];
    154 
    155     for (col = cinfo->output_width; col > 0; col--) {
    156       /* can omit GETJSAMPLE() safely */
    157       outptr[0] = inptr[bindex];
    158       outptr[1] = inptr[gindex];
    159       outptr[2] = inptr[rindex];
    160       outptr += 3;  inptr += ps;
    161     }
    162   }
    163 
    164   /* Zero out the pad bytes. */
    165   pad = dest->pad_bytes;
    166   while (--pad >= 0)
    167     *outptr++ = 0;
    168 
    169   if (!dest->use_inversion_array)
    170     (void)JFWRITE(dest->pub.output_file, dest->iobuffer, dest->row_width);
    171 }
    172 
    173 METHODDEF(void)
    174 put_gray_rows(j_decompress_ptr cinfo, djpeg_dest_ptr dinfo,
    175               JDIMENSION rows_supplied)
    176 /* This version is for grayscale OR quantized color output */
    177 {
    178   bmp_dest_ptr dest = (bmp_dest_ptr)dinfo;
    179   JSAMPARRAY image_ptr;
    180   register JSAMPROW inptr, outptr;
    181   int pad;
    182 
    183   if (dest->use_inversion_array) {
    184     /* Access next row in virtual array */
    185     image_ptr = (*cinfo->mem->access_virt_sarray)
    186       ((j_common_ptr)cinfo, dest->whole_image,
    187        dest->cur_output_row, (JDIMENSION)1, TRUE);
    188     dest->cur_output_row++;
    189     outptr = image_ptr[0];
    190   } else {
    191     outptr = dest->iobuffer;
    192   }
    193 
    194   /* Transfer data. */
    195   inptr = dest->pub.buffer[0];
    196   MEMCOPY(outptr, inptr, cinfo->output_width);
    197   outptr += cinfo->output_width;
    198 
    199   /* Zero out the pad bytes. */
    200   pad = dest->pad_bytes;
    201   while (--pad >= 0)
    202     *outptr++ = 0;
    203 
    204   if (!dest->use_inversion_array)
    205     (void)JFWRITE(dest->pub.output_file, dest->iobuffer, dest->row_width);
    206 }
    207 
    208 
    209 /*
    210  * Finish up at the end of the file.
    211  *
    212  * Here is where we really output the BMP file.
    213  *
    214  * First, routines to write the Windows and OS/2 variants of the file header.
    215  */
    216 
    217 LOCAL(void)
    218 write_bmp_header(j_decompress_ptr cinfo, bmp_dest_ptr dest)
    219 /* Write a Windows-style BMP file header, including colormap if needed */
    220 {
    221   char bmpfileheader[14];
    222   char bmpinfoheader[40];
    223 
    224 #define PUT_2B(array, offset, value) \
    225   (array[offset] = (char)((value) & 0xFF), \
    226    array[offset + 1] = (char)(((value) >> 8) & 0xFF))
    227 #define PUT_4B(array, offset, value) \
    228   (array[offset] = (char)((value) & 0xFF), \
    229    array[offset + 1] = (char)(((value) >> 8) & 0xFF), \
    230    array[offset + 2] = (char)(((value) >> 16) & 0xFF), \
    231    array[offset + 3] = (char)(((value) >> 24) & 0xFF))
    232 
    233   long headersize, bfSize;
    234   int bits_per_pixel, cmap_entries;
    235 
    236   /* Compute colormap size and total file size */
    237   if (IsExtRGB(cinfo->out_color_space)) {
    238     if (cinfo->quantize_colors) {
    239       /* Colormapped RGB */
    240       bits_per_pixel = 8;
    241       cmap_entries = 256;
    242     } else {
    243       /* Unquantized, full color RGB */
    244       bits_per_pixel = 24;
    245       cmap_entries = 0;
    246     }
    247   } else if (cinfo->out_color_space == JCS_RGB565 ||
    248              cinfo->out_color_space == JCS_CMYK) {
    249     bits_per_pixel = 24;
    250     cmap_entries   = 0;
    251   } else {
    252     /* Grayscale output.  We need to fake a 256-entry colormap. */
    253     bits_per_pixel = 8;
    254     cmap_entries = 256;
    255   }
    256   /* File size */
    257   headersize = 14 + 40 + cmap_entries * 4; /* Header and colormap */
    258   bfSize = headersize + (long)dest->row_width * (long)cinfo->output_height;
    259 
    260   /* Set unused fields of header to 0 */
    261   MEMZERO(bmpfileheader, sizeof(bmpfileheader));
    262   MEMZERO(bmpinfoheader, sizeof(bmpinfoheader));
    263 
    264   /* Fill the file header */
    265   bmpfileheader[0] = 0x42;      /* first 2 bytes are ASCII 'B', 'M' */
    266   bmpfileheader[1] = 0x4D;
    267   PUT_4B(bmpfileheader, 2, bfSize); /* bfSize */
    268   /* we leave bfReserved1 & bfReserved2 = 0 */
    269   PUT_4B(bmpfileheader, 10, headersize); /* bfOffBits */
    270 
    271   /* Fill the info header (Microsoft calls this a BITMAPINFOHEADER) */
    272   PUT_2B(bmpinfoheader, 0, 40); /* biSize */
    273   PUT_4B(bmpinfoheader, 4, cinfo->output_width); /* biWidth */
    274   PUT_4B(bmpinfoheader, 8, cinfo->output_height); /* biHeight */
    275   PUT_2B(bmpinfoheader, 12, 1); /* biPlanes - must be 1 */
    276   PUT_2B(bmpinfoheader, 14, bits_per_pixel); /* biBitCount */
    277   /* we leave biCompression = 0, for none */
    278   /* we leave biSizeImage = 0; this is correct for uncompressed data */
    279   if (cinfo->density_unit == 2) { /* if have density in dots/cm, then */
    280     PUT_4B(bmpinfoheader, 24, (long)(cinfo->X_density * 100)); /* XPels/M */
    281     PUT_4B(bmpinfoheader, 28, (long)(cinfo->Y_density * 100)); /* XPels/M */
    282   }
    283   PUT_2B(bmpinfoheader, 32, cmap_entries); /* biClrUsed */
    284   /* we leave biClrImportant = 0 */
    285 
    286   if (JFWRITE(dest->pub.output_file, bmpfileheader, 14) != (size_t)14)
    287     ERREXIT(cinfo, JERR_FILE_WRITE);
    288   if (JFWRITE(dest->pub.output_file, bmpinfoheader, 40) != (size_t)40)
    289     ERREXIT(cinfo, JERR_FILE_WRITE);
    290 
    291   if (cmap_entries > 0)
    292     write_colormap(cinfo, dest, cmap_entries, 4);
    293 }
    294 
    295 
    296 LOCAL(void)
    297 write_os2_header(j_decompress_ptr cinfo, bmp_dest_ptr dest)
    298 /* Write an OS2-style BMP file header, including colormap if needed */
    299 {
    300   char bmpfileheader[14];
    301   char bmpcoreheader[12];
    302   long headersize, bfSize;
    303   int bits_per_pixel, cmap_entries;
    304 
    305   /* Compute colormap size and total file size */
    306   if (IsExtRGB(cinfo->out_color_space)) {
    307     if (cinfo->quantize_colors) {
    308       /* Colormapped RGB */
    309       bits_per_pixel = 8;
    310       cmap_entries = 256;
    311     } else {
    312       /* Unquantized, full color RGB */
    313       bits_per_pixel = 24;
    314       cmap_entries = 0;
    315     }
    316   } else if (cinfo->out_color_space == JCS_RGB565 ||
    317              cinfo->out_color_space == JCS_CMYK) {
    318     bits_per_pixel = 24;
    319     cmap_entries   = 0;
    320   } else {
    321     /* Grayscale output.  We need to fake a 256-entry colormap. */
    322     bits_per_pixel = 8;
    323     cmap_entries = 256;
    324   }
    325   /* File size */
    326   headersize = 14 + 12 + cmap_entries * 3; /* Header and colormap */
    327   bfSize = headersize + (long)dest->row_width * (long)cinfo->output_height;
    328 
    329   /* Set unused fields of header to 0 */
    330   MEMZERO(bmpfileheader, sizeof(bmpfileheader));
    331   MEMZERO(bmpcoreheader, sizeof(bmpcoreheader));
    332 
    333   /* Fill the file header */
    334   bmpfileheader[0] = 0x42;      /* first 2 bytes are ASCII 'B', 'M' */
    335   bmpfileheader[1] = 0x4D;
    336   PUT_4B(bmpfileheader, 2, bfSize); /* bfSize */
    337   /* we leave bfReserved1 & bfReserved2 = 0 */
    338   PUT_4B(bmpfileheader, 10, headersize); /* bfOffBits */
    339 
    340   /* Fill the info header (Microsoft calls this a BITMAPCOREHEADER) */
    341   PUT_2B(bmpcoreheader, 0, 12); /* bcSize */
    342   PUT_2B(bmpcoreheader, 4, cinfo->output_width); /* bcWidth */
    343   PUT_2B(bmpcoreheader, 6, cinfo->output_height); /* bcHeight */
    344   PUT_2B(bmpcoreheader, 8, 1);  /* bcPlanes - must be 1 */
    345   PUT_2B(bmpcoreheader, 10, bits_per_pixel); /* bcBitCount */
    346 
    347   if (JFWRITE(dest->pub.output_file, bmpfileheader, 14) != (size_t)14)
    348     ERREXIT(cinfo, JERR_FILE_WRITE);
    349   if (JFWRITE(dest->pub.output_file, bmpcoreheader, 12) != (size_t)12)
    350     ERREXIT(cinfo, JERR_FILE_WRITE);
    351 
    352   if (cmap_entries > 0)
    353     write_colormap(cinfo, dest, cmap_entries, 3);
    354 }
    355 
    356 
    357 /*
    358  * Write the colormap.
    359  * Windows uses BGR0 map entries; OS/2 uses BGR entries.
    360  */
    361 
    362 LOCAL(void)
    363 write_colormap(j_decompress_ptr cinfo, bmp_dest_ptr dest, int map_colors,
    364                int map_entry_size)
    365 {
    366   JSAMPARRAY colormap = cinfo->colormap;
    367   int num_colors = cinfo->actual_number_of_colors;
    368   FILE *outfile = dest->pub.output_file;
    369   int i;
    370 
    371   if (colormap != NULL) {
    372     if (cinfo->out_color_components == 3) {
    373       /* Normal case with RGB colormap */
    374       for (i = 0; i < num_colors; i++) {
    375         putc(GETJSAMPLE(colormap[2][i]), outfile);
    376         putc(GETJSAMPLE(colormap[1][i]), outfile);
    377         putc(GETJSAMPLE(colormap[0][i]), outfile);
    378         if (map_entry_size == 4)
    379           putc(0, outfile);
    380       }
    381     } else {
    382       /* Grayscale colormap (only happens with grayscale quantization) */
    383       for (i = 0; i < num_colors; i++) {
    384         putc(GETJSAMPLE(colormap[0][i]), outfile);
    385         putc(GETJSAMPLE(colormap[0][i]), outfile);
    386         putc(GETJSAMPLE(colormap[0][i]), outfile);
    387         if (map_entry_size == 4)
    388           putc(0, outfile);
    389       }
    390     }
    391   } else {
    392     /* If no colormap, must be grayscale data.  Generate a linear "map". */
    393     for (i = 0; i < 256; i++) {
    394       putc(i, outfile);
    395       putc(i, outfile);
    396       putc(i, outfile);
    397       if (map_entry_size == 4)
    398         putc(0, outfile);
    399     }
    400   }
    401   /* Pad colormap with zeros to ensure specified number of colormap entries */
    402   if (i > map_colors)
    403     ERREXIT1(cinfo, JERR_TOO_MANY_COLORS, i);
    404   for (; i < map_colors; i++) {
    405     putc(0, outfile);
    406     putc(0, outfile);
    407     putc(0, outfile);
    408     if (map_entry_size == 4)
    409       putc(0, outfile);
    410   }
    411 }
    412 
    413 
    414 /*
    415  * Startup: write the file header unless the inversion array is being used.
    416  */
    417 
    418 METHODDEF(void)
    419 start_output_bmp(j_decompress_ptr cinfo, djpeg_dest_ptr dinfo)
    420 {
    421   bmp_dest_ptr dest = (bmp_dest_ptr)dinfo;
    422 
    423   if (!dest->use_inversion_array) {
    424     /* Write the header and colormap */
    425     if (dest->is_os2)
    426       write_os2_header(cinfo, dest);
    427     else
    428       write_bmp_header(cinfo, dest);
    429   }
    430 }
    431 
    432 
    433 METHODDEF(void)
    434 finish_output_bmp(j_decompress_ptr cinfo, djpeg_dest_ptr dinfo)
    435 {
    436   bmp_dest_ptr dest = (bmp_dest_ptr)dinfo;
    437   register FILE *outfile = dest->pub.output_file;
    438   JSAMPARRAY image_ptr;
    439   register JSAMPROW data_ptr;
    440   JDIMENSION row;
    441   register JDIMENSION col;
    442   cd_progress_ptr progress = (cd_progress_ptr)cinfo->progress;
    443 
    444   if (dest->use_inversion_array) {
    445     /* Write the header and colormap */
    446     if (dest->is_os2)
    447       write_os2_header(cinfo, dest);
    448     else
    449       write_bmp_header(cinfo, dest);
    450 
    451     /* Write the file body from our virtual array */
    452     for (row = cinfo->output_height; row > 0; row--) {
    453       if (progress != NULL) {
    454         progress->pub.pass_counter = (long)(cinfo->output_height - row);
    455         progress->pub.pass_limit = (long)cinfo->output_height;
    456         (*progress->pub.progress_monitor) ((j_common_ptr)cinfo);
    457       }
    458       image_ptr = (*cinfo->mem->access_virt_sarray)
    459         ((j_common_ptr)cinfo, dest->whole_image, row - 1, (JDIMENSION)1,
    460          FALSE);
    461       data_ptr = image_ptr[0];
    462       for (col = dest->row_width; col > 0; col--) {
    463         putc(GETJSAMPLE(*data_ptr), outfile);
    464         data_ptr++;
    465       }
    466     }
    467     if (progress != NULL)
    468       progress->completed_extra_passes++;
    469   }
    470 
    471   /* Make sure we wrote the output file OK */
    472   fflush(outfile);
    473   if (ferror(outfile))
    474     ERREXIT(cinfo, JERR_FILE_WRITE);
    475 }
    476 
    477 
    478 /*
    479  * The module selection routine for BMP format output.
    480  */
    481 
    482 GLOBAL(djpeg_dest_ptr)
    483 jinit_write_bmp(j_decompress_ptr cinfo, boolean is_os2,
    484                 boolean use_inversion_array)
    485 {
    486   bmp_dest_ptr dest;
    487   JDIMENSION row_width;
    488 
    489   /* Create module interface object, fill in method pointers */
    490   dest = (bmp_dest_ptr)
    491     (*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_IMAGE,
    492                                 sizeof(bmp_dest_struct));
    493   dest->pub.start_output = start_output_bmp;
    494   dest->pub.finish_output = finish_output_bmp;
    495   dest->pub.calc_buffer_dimensions = NULL;
    496   dest->is_os2 = is_os2;
    497 
    498   if (cinfo->out_color_space == JCS_GRAYSCALE) {
    499     dest->pub.put_pixel_rows = put_gray_rows;
    500   } else if (IsExtRGB(cinfo->out_color_space)) {
    501     if (cinfo->quantize_colors)
    502       dest->pub.put_pixel_rows = put_gray_rows;
    503     else
    504       dest->pub.put_pixel_rows = put_pixel_rows;
    505   } else if (!cinfo->quantize_colors &&
    506              (cinfo->out_color_space == JCS_RGB565 ||
    507               cinfo->out_color_space == JCS_CMYK)) {
    508     dest->pub.put_pixel_rows = put_pixel_rows;
    509   } else {
    510     ERREXIT(cinfo, JERR_BMP_COLORSPACE);
    511   }
    512 
    513   /* Calculate output image dimensions so we can allocate space */
    514   jpeg_calc_output_dimensions(cinfo);
    515 
    516   /* Determine width of rows in the BMP file (padded to 4-byte boundary). */
    517   if (cinfo->out_color_space == JCS_RGB565) {
    518     row_width = cinfo->output_width * 2;
    519     dest->row_width = dest->data_width = cinfo->output_width * 3;
    520     while ((row_width & 3) != 0) row_width++;
    521   } else if (!cinfo->quantize_colors &&
    522              (IsExtRGB(cinfo->out_color_space) ||
    523               cinfo->out_color_space == JCS_CMYK)) {
    524     row_width = cinfo->output_width * cinfo->output_components;
    525     dest->row_width = dest->data_width = cinfo->output_width * 3;
    526   } else {
    527     row_width = cinfo->output_width * cinfo->output_components;
    528     dest->row_width = dest->data_width = row_width;
    529   }
    530   while ((dest->row_width & 3) != 0) dest->row_width++;
    531   dest->pad_bytes = (int)(dest->row_width - dest->data_width);
    532 
    533 
    534   if (use_inversion_array) {
    535     /* Allocate space for inversion array, prepare for write pass */
    536     dest->whole_image = (*cinfo->mem->request_virt_sarray)
    537       ((j_common_ptr)cinfo, JPOOL_IMAGE, FALSE,
    538        dest->row_width, cinfo->output_height, (JDIMENSION)1);
    539     dest->cur_output_row = 0;
    540     if (cinfo->progress != NULL) {
    541       cd_progress_ptr progress = (cd_progress_ptr)cinfo->progress;
    542       progress->total_extra_passes++; /* count file input as separate pass */
    543     }
    544   } else {
    545     dest->iobuffer = (JSAMPLE *)(*cinfo->mem->alloc_small)
    546       ((j_common_ptr)cinfo, JPOOL_IMAGE, dest->row_width);
    547   }
    548   dest->use_inversion_array = use_inversion_array;
    549 
    550   /* Create decompressor output buffer. */
    551   dest->pub.buffer = (*cinfo->mem->alloc_sarray)
    552     ((j_common_ptr)cinfo, JPOOL_IMAGE, row_width, (JDIMENSION)1);
    553   dest->pub.buffer_height = 1;
    554 
    555   return (djpeg_dest_ptr)dest;
    556 }
    557 
    558 #endif /* BMP_SUPPORTED */
    559