// // Fl_BMP_Image class for the Fast Light Tool Kit (FLTK). // // Copyright 2011-2020 by Bill Spitzak and others. // Copyright 1997-2010 by Easy Software Products. // Image support by Matthias Melcher, Copyright 2000-2009. // // This library is free software. Distribution and use rights are outlined in // the file "COPYING" which should have been included with this file. If this // file is missing or damaged, see the license at: // // https://www.fltk.org/COPYING.php // // Please see the following page on how to report bugs and issues: // // https://www.fltk.org/bugs.php // // // Include necessary header files... // #include #include "Fl_Image_Reader.h" #include #include #include #include // // BMP definitions... // #ifndef BI_RGB # define BI_RGB 0 // No compression - straight BGR data # define BI_RLE8 1 // 8-bit run-length compression # define BI_RLE4 2 // 4-bit run-length compression # define BI_BITFIELDS 3 // RGB bitmap with RGB masks #endif // !BI_RGB /** \brief This constructor loads the named BMP image from the given BMP filename. The destructor frees all memory and server resources that are used by the image. Use Fl_Image::fail() to check if Fl_BMP_Image failed to load. fail() returns ERR_FILE_ACCESS if the file could not be opened or read, ERR_FORMAT if the BMP format could not be decoded, and ERR_NO_IMAGE if the image could not be loaded for another reason. \param[in] filename a full path and name pointing to a valid BMP file. \see Fl_BMP_Image::Fl_BMP_Image(const char *imagename, const unsigned char *data) */ Fl_BMP_Image::Fl_BMP_Image(const char *filename) // I - File to read : Fl_RGB_Image(0,0,0) { Fl_Image_Reader rdr; if (rdr.open(filename) == -1) { ld(ERR_FILE_ACCESS); } else { load_bmp_(rdr); } } /** \brief Read a BMP image from memory. Construct an image from a block of memory inside the application. Fluid offers "binary data" chunks as a great way to add image data into the C++ source code. imagename can be NULL. If a name is given, the image is added to the list of shared images and will be available by that name. Use Fl_Image::fail() to check if Fl_BMP_Image failed to load. fail() returns ERR_FILE_ACCESS if the image could not be read from memory, ERR_FORMAT if the BMP format could not be decoded, and ERR_NO_IMAGE if the image could not be loaded for another reason. \param[in] imagename A name given to this image or NULL \param[in] data Pointer to the start of the BMP image in memory. This code will not check for buffer overruns. \see Fl_BMP_Image::Fl_BMP_Image(const char *filename) \see Fl_Shared_Image */ Fl_BMP_Image::Fl_BMP_Image(const char *imagename, const unsigned char *data) : Fl_RGB_Image(0,0,0) { Fl_Image_Reader rdr; if (rdr.open(imagename, data) == -1) { ld(ERR_FILE_ACCESS); } else { load_bmp_(rdr); } } /* This method reads BMP image data and creates an RGB or RGBA image. The BMP format supports only 1 bit for alpha. To avoid code duplication, we use an Fl_Image_Reader that reads data from either a file or from memory. */ void Fl_BMP_Image::load_bmp_(Fl_Image_Reader &rdr) { int info_size, // Size of info header depth, // Depth of image (bits) bDepth = 3, // Depth of image (bytes) compression, // Type of compression colors_used, // Number of colors used x, y, // Looping vars color, // Color of RLE pixel repcount, // Number of times to repeat temp, // Temporary color align, // Alignment bytes dataSize, // number of bytes in image data set row_order, // 1 = normal; -1 = flipped row order start_y, // Beginning Y end_y; // Ending Y long offbits; // Offset to image data uchar bit, // Bit in image byte; // Byte in image uchar *ptr; // Pointer into pixels uchar colormap[256][3]; // Colormap uchar havemask; // Single bit mask follows image data int use_5_6_5; // Use 5:6:5 for R:G:B channels in 16 bit images // Reader is already open at this point. // Get the header... byte = rdr.read_byte(); // Check "BM" sync chars bit = rdr.read_byte(); if (byte != 'B' || bit != 'M') { ld(ERR_FORMAT); return; } rdr.read_dword(); // Skip size rdr.read_word(); // Skip reserved stuff rdr.read_word(); offbits = (long)rdr.read_dword();// Read offset to image data // Then the bitmap information... info_size = rdr.read_dword(); // printf("offbits = %ld, info_size = %d\n", offbits, info_size); havemask = 0; row_order = -1; use_5_6_5 = 0; if (info_size < 40) { // Old Windows/OS2 BMP header... w(rdr.read_word()); h(rdr.read_word()); rdr.read_word(); depth = rdr.read_word(); compression = BI_RGB; colors_used = 0; repcount = info_size - 12; } else { // New BMP header... w(rdr.read_long()); // If the height is negative, the row order is flipped temp = rdr.read_long(); if (temp < 0) row_order = 1; h(abs(temp)); rdr.read_word(); depth = rdr.read_word(); compression = rdr.read_dword(); dataSize = rdr.read_dword(); rdr.read_long(); rdr.read_long(); colors_used = rdr.read_dword(); rdr.read_dword(); repcount = info_size - 40; if (!compression && depth>=8 && w()>32/depth) { int Bpp = depth/8; int maskSize = (((w()*Bpp+3)&~3)*h()) + (((((w()+7)/8)+3)&~3)*h()); if (maskSize==2*dataSize) { havemask = 1; h(h()/2); bDepth = 4; } } } // printf("w() = %d, h() = %d, depth = %d, compression = %d, colors_used = %d, repcount = %d\n", // w(), h(), depth, compression, colors_used, repcount); // Skip remaining header bytes... while (repcount > 0) { rdr.read_byte(); repcount --; } // Check header data... if (!w() || !h() || !depth) { w(0); h(0); d(0); ld(ERR_FORMAT); return; } // Get colormap... if (colors_used == 0 && depth <= 8) colors_used = 1 << depth; for (repcount = 0; repcount < colors_used; repcount ++) { // Read BGR color... colormap[repcount][0] = rdr.read_byte(); colormap[repcount][1] = rdr.read_byte(); colormap[repcount][2] = rdr.read_byte(); // Skip pad byte for new BMP files... if (info_size > 12) rdr.read_byte(); } // Read first dword of colormap. It tells us if 5:5:5 or 5:6:5 for 16 bit if (depth == 16) use_5_6_5 = (rdr.read_dword() == 0xf800); // Set byte depth for RGBA images if (depth == 32) bDepth=4; // Setup image and buffers... d(bDepth); if (offbits) rdr.seek(offbits); if (((size_t)w()) * h() * d() > max_size() ) { Fl::warning("BMP file \"%s\" is too large!\n", rdr.name()); w(0); h(0); d(0); ld(ERR_FORMAT); return; } array = new uchar[w() * h() * d()]; alloc_array = 1; // Read the image data... color = 0; repcount = 0; align = 0; byte = 0; temp = 0; if (row_order < 0) { start_y = h() - 1; end_y = -1; } else { start_y = 0; end_y = h(); } for (y = start_y; y != end_y; y += row_order) { ptr = (uchar *)array + y * w() * d(); switch (depth) { case 1 : // Bitmap for (x = w(), bit = 128; x > 0; x --) { if (bit == 128) byte = rdr.read_byte(); if (byte & bit) { *ptr++ = colormap[1][2]; *ptr++ = colormap[1][1]; *ptr++ = colormap[1][0]; } else { *ptr++ = colormap[0][2]; *ptr++ = colormap[0][1]; *ptr++ = colormap[0][0]; } if (bit > 1) bit >>= 1; else bit = 128; } // Read remaining bytes to align to 32 bits... for (temp = (w() + 7) / 8; temp & 3; temp ++) { rdr.read_byte(); } break; case 4 : // 16-color for (x = w(), bit = 0xf0; x > 0; x --) { // Get a new repcount as needed... if (repcount == 0) { if (compression != BI_RLE4) { repcount = 2; color = -1; } else { while (align > 0) { align --; rdr.read_byte(); } if ((repcount = rdr.read_byte()) == 0) { if ((repcount = rdr.read_byte()) == 0) { // End of line... x ++; continue; } else if (repcount == 1) { // End of image... break; } else if (repcount == 2) { // Delta... repcount = rdr.read_byte() * rdr.read_byte() * w(); color = 0; } else { // Absolute... color = -1; align = ((4 - (repcount & 3)) / 2) & 1; } } else { color = rdr.read_byte(); } } } // Get a new color as needed... repcount --; // Extract the next pixel... if (bit == 0xf0) { // Get the next color byte as needed... if (color < 0) temp = rdr.read_byte(); else temp = color; // Copy the color value... *ptr++ = colormap[(temp >> 4) & 15][2]; *ptr++ = colormap[(temp >> 4) & 15][1]; *ptr++ = colormap[(temp >> 4) & 15][0]; bit = 0x0f; } else { bit = 0xf0; // Copy the color value... *ptr++ = colormap[temp & 15][2]; *ptr++ = colormap[temp & 15][1]; *ptr++ = colormap[temp & 15][0]; } } if (!compression) { // Read remaining bytes to align to 32 bits... for (temp = (w() + 1) / 2; temp & 3; temp ++) { rdr.read_byte(); } } break; case 8 : // 256-color for (x = w(); x > 0; x --) { // Get a new repcount as needed... if (compression != BI_RLE8) { repcount = 1; color = -1; } if (repcount == 0) { while (align > 0) { align --; rdr.read_byte(); } if ((repcount = rdr.read_byte()) == 0) { if ((repcount = rdr.read_byte()) == 0) { // End of line... x ++; continue; } else if (repcount == 1) { // End of image... break; } else if (repcount == 2) { // Delta... repcount = rdr.read_byte() * rdr.read_byte() * w(); color = 0; } else { // Absolute... color = -1; align = (2 - (repcount & 1)) & 1; } } else { color = rdr.read_byte(); } } // Get a new color as needed... if (color < 0) temp = rdr.read_byte(); else temp = color; repcount --; // Copy the color value... *ptr++ = colormap[temp][2]; *ptr++ = colormap[temp][1]; *ptr++ = colormap[temp][0]; if (havemask) ptr++; } if (!compression) { // Read remaining bytes to align to 32 bits... for (temp = w(); temp & 3; temp ++) { rdr.read_byte(); } } break; case 16 : // 16-bit 5:5:5 or 5:6:5 RGB for (x = w(); x > 0; x --, ptr += bDepth) { uchar b = rdr.read_byte(), a = rdr.read_byte() ; if (use_5_6_5) { ptr[2] = (uchar)(( b << 3 ) & 0xf8); ptr[1] = (uchar)(((a << 5) & 0xe0) | ((b >> 3) & 0x1c)); ptr[0] = (uchar)(a & 0xf8); } else { ptr[2] = (uchar)((b << 3) & 0xf8); ptr[1] = (uchar)(((a << 6) & 0xc0) | ((b >> 2) & 0x38)); ptr[0] = (uchar)((a<<1) & 0xf8); } } // Read remaining bytes to align to 32 bits... for (temp = w() * 2; temp & 3; temp ++) { rdr.read_byte(); } break; case 24 : // 24-bit RGB for (x = w(); x > 0; x --, ptr += bDepth) { ptr[2] = rdr.read_byte(); ptr[1] = rdr.read_byte(); ptr[0] = rdr.read_byte(); } // Read remaining bytes to align to 32 bits... for (temp = w() * 3; temp & 3; temp ++) { rdr.read_byte(); } break; case 32 : // 32-bit RGBA for (x = w(); x > 0; x --, ptr += bDepth) { ptr[2] = rdr.read_byte(); ptr[1] = rdr.read_byte(); ptr[0] = rdr.read_byte(); ptr[3] = rdr.read_byte(); } break; } } if (havemask) { for (y = h() - 1; y >= 0; y --) { ptr = (uchar *)array + y * w() * d() + 3; for (x = w(), bit = 128; x > 0; x --, ptr+=bDepth) { if (bit == 128) byte = rdr.read_byte(); if (byte & bit) *ptr = 0; else *ptr = 255; if (bit > 1) bit >>= 1; else bit = 128; } // Read remaining bytes to align to 32 bits... for (temp = (w() + 7) / 8; temp & 3; temp ++) rdr.read_byte(); } } // File is closed when returning... }