/** @file * @author Edouard DUPIN * @copyright 2011, Edouard DUPIN, all right reserved * @license MPL v2.0 (see license file) */ #include #include #include #include #include extern "C" { #pragma pack(push,1) struct bitmapFileHeader { int16_t bfType; int32_t bfSize; int32_t bfReserved; int32_t bfOffBits; }; struct bitmapInfoHeader { int32_t biSize; int32_t biWidth; int32_t biHeight; int16_t biPlanes; int16_t biBitCount; int32_t biCompression; int32_t biSizeImage; int32_t biXPelsPerMeter; int32_t biYPelsPerMeter; int32_t biPaletteNumber; int32_t biImportantColor; }; struct bitmapInfoHeaderExtended { int32_t biSize; int32_t biWidth; int32_t biHeight; int16_t biPlanes; int16_t biBitCount; int32_t biCompression; int32_t biSizeImage; int32_t biXPelsPerMeter; int32_t biYPelsPerMeter; // https://en.wikipedia.org/wiki/BMP_file_format / example 2 int32_t biPaletteNumber; int32_t biImportantColor; int32_t biLCSColorSpace; // This is at this position, inspection of "gimp" output ... int32_t biBitMaskRed; int32_t biBitMaskGreen; int32_t biBitMaskBlue; int32_t biBitMaskAlpha; int32_t biUnused[12]; }; #pragma pack(pop) } enum modeBitmap { BITS_16_R5G6B5, BITS_16_X1R5G5B5, BITS_24_R8G8B8, BITS_32_X8R8G8B8, BITS_32_A8R8G8B8 }; static void display(struct bitmapFileHeader _header, struct bitmapInfoHeader _info) { EGAMI_DEBUG(" -----------------------------------------------------------"); EGAMI_DEBUG("Display caracteristic of the bitmap : "); EGAMI_DEBUG(" Header of file :"); EGAMI_DEBUG(" bfType =" << _header.bfType << " 19778 : must always be set to 'BM' to declare that this is a .bmp-file."); EGAMI_DEBUG(" bfSize =" << _header.bfSize << " specifies the size of the file in bytes."); EGAMI_DEBUG(" bfReserved=" << _header.bfReserved << " must always be set to zero."); EGAMI_DEBUG(" bfOffBits =" << _header.bfOffBits << " 1078 : specifies the offset from the beginning of the file to the bitmap data."); EGAMI_DEBUG(" info header of file :"); EGAMI_DEBUG(" biSize =" << _info.biSize << " specifies the size of the BITMAPINFOHEADER structure, in bytes."); EGAMI_DEBUG(" biWidth =" << _info.biWidth << " specifies the width of the image, in pixels."); EGAMI_DEBUG(" biHeight =" << _info.biHeight << " specifies the height of the image, in pixels."); EGAMI_DEBUG(" biPlanes =" << _info.biPlanes << " specifies the number of planes of the target device, must be set to zero."); EGAMI_DEBUG(" biBitCount =" << _info.biBitCount << " specifies the number of bits per pixel."); EGAMI_DEBUG(" biCompression =" << _info.biCompression << " Specifies the type of compression, usually set to zero (no compression)."); EGAMI_DEBUG(" biSizeImage =" << _info.biSizeImage << " specifies the size of the image data, in bytes. If there is no compression, it is valid to set this member to zero."); EGAMI_DEBUG(" biXPelsPerMeter=" << _info.biXPelsPerMeter << " specifies the the horizontal pixels per meter on the designated targer device, usually set to zero."); EGAMI_DEBUG(" biYPelsPerMeter=" << _info.biYPelsPerMeter << " specifies the the vertical pixels per meter on the designated targer device, usually set to zero."); EGAMI_DEBUG(" biClrUsed =" << _info.biPaletteNumber << " Pallet color number."); EGAMI_DEBUG(" biClrImportant =" << _info.biImportantColor << " Important color ID."); /* EGAMI_DEBUG("Bitmap : " << m_width << "x" << m_height); switch(m_dataMode) { case BITS_16_R5G6B5: EGAMI_DEBUG(" mode = 16 bits R5G6B5"); break; case BITS_16_X1R5G5B5: EGAMI_DEBUG(" mode = 16 bits X1R5G5B5"); break; case BITS_24_R8G8B8: EGAMI_DEBUG(" mode = 24 bits R8G8B8"); break; case BITS_32_X8R8G8B8: EGAMI_DEBUG(" mode = 32 bits X8R8G8B8"); break; case BITS_32_A8R8G8B8: EGAMI_DEBUG(" mode = 32 bits A8R8G8B8"); break; default: EGAMI_DEBUG(" mode = ERROR"); break; }*/ } egami::Image egami::loadBMP(const etk::String& _inputFile) { etk::FSNode fileName(_inputFile); EGAMI_VERBOSE("File='" << _inputFile << "' ==> " << fileName << " ==> " << fileName.getFileSystemName()); if (fileName.exist() == false) { EGAMI_ERROR("File does not existed='" << fileName << "'"); return egami::Image(); } if(fileName.fileOpenRead() == false) { EGAMI_ERROR("Can not find the file name='" << fileName << "'"); return egami::Image(); } etk::Vector allData = fileName.fileReadAll(); fileName.fileClose(); return egami::loadBMP(allData); } egami::Image egami::loadBMP(const etk::Vector& _buffer) { egami::Image out; enum modeBitmap m_dataMode = BITS_16_R5G6B5; int32_t m_width = 0; int32_t m_height = 0; struct bitmapFileHeader m_FileHeader; bool useExtended = false; struct bitmapInfoHeader m_InfoHeader; struct bitmapInfoHeaderExtended m_InfoHeaderExtended; if (_buffer.size() < sizeof(struct bitmapFileHeader)) { EGAMI_ERROR("error loading file header, not enough data"); return out; } memcpy(&m_FileHeader, &_buffer[0], sizeof(struct bitmapFileHeader)); // check the header error : if (m_FileHeader.bfType != 0x4D42) { EGAMI_ERROR("the Buffer is not a bitmap file ... " << m_FileHeader.bfType << " != " << 0x4D42); return out; } if (m_FileHeader.bfOffBits > sizeof(struct bitmapFileHeader) + sizeof(struct bitmapInfoHeader)) { EGAMI_DEBUG("Read bitmap in EXTENDED mode ..."); if (_buffer.size() < sizeof(struct bitmapFileHeader) + sizeof(struct bitmapInfoHeaderExtended)) { EGAMI_ERROR("error loading file header, not enough data"); return out; } memcpy(&m_InfoHeaderExtended, &_buffer[sizeof(struct bitmapFileHeader)], sizeof(struct bitmapInfoHeaderExtended)); useExtended = true; m_InfoHeader.biSize = m_InfoHeaderExtended.biSize; m_InfoHeader.biWidth = m_InfoHeaderExtended.biWidth; m_InfoHeader.biHeight = m_InfoHeaderExtended.biHeight; m_InfoHeader.biHeight = m_InfoHeaderExtended.biHeight; m_InfoHeader.biHeight = m_InfoHeaderExtended.biHeight; m_InfoHeader.biBitCount = m_InfoHeaderExtended.biBitCount; m_InfoHeader.biCompression = m_InfoHeaderExtended.biCompression; m_InfoHeader.biSizeImage = m_InfoHeaderExtended.biSizeImage; m_InfoHeader.biXPelsPerMeter = m_InfoHeaderExtended.biXPelsPerMeter; m_InfoHeader.biYPelsPerMeter = m_InfoHeaderExtended.biYPelsPerMeter; } else { EGAMI_DEBUG("Read bitmap in BASIC mode ..."); if (_buffer.size() < sizeof(struct bitmapFileHeader) + sizeof(struct bitmapInfoHeader)) { EGAMI_ERROR("error loading file header, not enough data"); return out; } memcpy(&m_InfoHeader, &_buffer[sizeof(struct bitmapFileHeader)], sizeof(struct bitmapInfoHeader)); useExtended = false; } int32_t offsetHeader = m_FileHeader.bfOffBits; display(m_FileHeader, m_InfoHeader); // check the header error : if (m_FileHeader.bfType != 0x4D42) { EGAMI_ERROR("the Buffer is not a bitmap file ... " << m_FileHeader.bfType << " != " << 0x4D42); return out; } if (m_FileHeader.bfReserved != 0x00000000) { EGAMI_ERROR("the bfReserved feald is not at 0 == > not supported format ..."); return out; } m_width = m_InfoHeader.biWidth; m_height = m_InfoHeader.biHeight; if( m_InfoHeader.biBitCount == 16 && m_InfoHeader.biCompression == 0) { m_dataMode = BITS_16_X1R5G5B5; out.configure(ivec2(m_width,m_height), egami::colorType::RGB8); } else if( m_InfoHeader.biBitCount == 16 && m_InfoHeader.biCompression == 3) { m_dataMode = BITS_16_R5G6B5; out.configure(ivec2(m_width,m_height), egami::colorType::RGB8); } else if( m_InfoHeader.biBitCount == 24 && m_InfoHeader.biCompression == 0) { m_dataMode = BITS_24_R8G8B8; out.configure(ivec2(m_width,m_height), egami::colorType::RGB8); } else if( m_InfoHeader.biBitCount == 32 && m_InfoHeader.biCompression == 3) { m_dataMode = BITS_32_X8R8G8B8; out.configure(ivec2(m_width,m_height), egami::colorType::RGB8); } else if( m_InfoHeader.biBitCount == 32 && m_InfoHeader.biCompression == 0) { m_dataMode = BITS_32_A8R8G8B8; out.configure(ivec2(m_width,m_height), egami::colorType::RGBA8); } else { EGAMI_ERROR("the biBitCount & biCompression fealds are unknow == > not supported format ..."); return out; } if(m_InfoHeader.biSizeImage != 0) { if (_buffer.size() < offsetHeader + m_InfoHeader.biSizeImage) { EGAMI_CRITICAL("Can not read the file with the good size..."); } } etk::Color<> tmpColor(0,0,0,0); // need now to generate RGBA data ... switch(m_dataMode) { case BITS_16_R5G6B5: { const uint16_t * pointer = (const uint16_t*)(&_buffer[offsetHeader]); for(int32_t yyy=0; yyy> 8)); tmpColor.setG((uint8_t)((*pointer & 0x07E0) >> 3)); tmpColor.setR((uint8_t)(*pointer << 3)); tmpColor.setA(0xFF); out.set(ivec2(xxx,m_height-yyy-1), tmpColor); pointer++; } } } break; case BITS_16_X1R5G5B5: { const uint16_t * pointer = (const uint16_t*)(&_buffer[offsetHeader]); for(int32_t yyy=0; yyy> 7)); tmpColor.setG((int8_t)((*pointer & 0x03E0) >> 2)); tmpColor.setR((int8_t)(*pointer << 3)); tmpColor.setA(0xFF); out.set(ivec2(xxx,m_height-yyy-1), tmpColor); pointer++; } } } break; case BITS_24_R8G8B8: { int32_t offset = 0; int32_t baseLine = m_width * 3; if ((baseLine%4) == 1) { offset = 3; } else if ((baseLine%4) == 2) { offset = 2; } else if ((baseLine%4) == 3) { offset = 1; } const uint8_t * pointer = (&_buffer[offsetHeader]); for(int32_t yyy=0; yyy " << fileName << " ==> " << fileName.getFileSystemName()); if(fileName.fileOpenWrite() == false) { EGAMI_ERROR("Can not crete the output file name='" << fileName << "'"); return false; } etk::Vector allData; bool ret = storeBMP(allData, _inputImage); fileName.fileWriteAll(allData); fileName.fileClose(); return ret; } bool egami::storeBMP(etk::Vector& _buffer, const egami::Image& _inputImage) { _buffer.clear(); _buffer.reserve( _inputImage.getSize().x()*_inputImage.getSize().y()*getFormatColorSize(_inputImage.getType()) + sizeof(struct bitmapInfoHeaderExtended) + sizeof(struct bitmapFileHeader)); struct bitmapFileHeader m_FileHeader; struct bitmapInfoHeaderExtended m_InfoHeaderExtended; memset(&m_InfoHeaderExtended, 0, sizeof(bitmapInfoHeaderExtended)); m_FileHeader.bfType = 0x4D42; m_FileHeader.bfSize = sizeof(struct bitmapFileHeader) + sizeof(struct bitmapInfoHeaderExtended); m_FileHeader.bfReserved = 0; m_FileHeader.bfOffBits = sizeof(struct bitmapFileHeader) + sizeof(struct bitmapInfoHeaderExtended); //EGAMI_ERROR("plopppppppppppppp " << m_FileHeader.bfOffBits); m_InfoHeaderExtended.biSize = sizeof(struct bitmapInfoHeaderExtended); m_InfoHeaderExtended.biWidth = _inputImage.getSize().x(); m_InfoHeaderExtended.biHeight = _inputImage.getSize().y(); m_InfoHeaderExtended.biPlanes = 1; int32_t offset = 0; if (_inputImage.getType() == egami::colorType::RGBA8) { m_InfoHeaderExtended.biBitCount = 32; m_InfoHeaderExtended.biCompression = 0; m_InfoHeaderExtended.biSizeImage = _inputImage.getSize().x()*_inputImage.getSize().y()*4; } else { m_InfoHeaderExtended.biBitCount = 24; m_InfoHeaderExtended.biCompression = 0; int32_t baseLine = _inputImage.getSize().x() * 3; if ((baseLine%4) == 1) { offset = 3; } else if ((baseLine%4) == 2) { offset = 2; } else if ((baseLine%4) == 3) { offset = 1; } m_InfoHeaderExtended.biSizeImage = (baseLine+offset)*_inputImage.getSize().y(); } m_FileHeader.bfSize += m_InfoHeaderExtended.biSizeImage; m_InfoHeaderExtended.biXPelsPerMeter = 72*39.3701+1; m_InfoHeaderExtended.biYPelsPerMeter = 72*39.3701+1; //m_InfoHeaderExtended.biClrUsed = 0; //m_InfoHeaderExtended.biClrImportant = 0; m_InfoHeaderExtended.biLCSColorSpace = 0x73524742; // "Win " //display(m_FileHeader, m_InfoHeaderExtended); m_InfoHeaderExtended.biBitMaskRed = 0x0000FF00; m_InfoHeaderExtended.biBitMaskGreen = 0x00FF0000; m_InfoHeaderExtended.biBitMaskBlue = 0xFF000000; m_InfoHeaderExtended.biBitMaskAlpha = 0x000000FF; _buffer.pushBack((uint8_t*)&m_FileHeader, sizeof(struct bitmapFileHeader)); _buffer.pushBack((uint8_t*)&m_InfoHeaderExtended, sizeof(struct bitmapInfoHeaderExtended)); /* TODO: Avec ca, ca ne fonctionne pas ... ==> check if(fileName.fileSeek(m_FileHeader.bfOffBits, etk::FSN_SEEK_START) == false) { EGAMI_ERROR("error with the 'bfOffBits' in the file named=\"" << fileName << "\""); fileName.fileClose(); return false; } */ if (_inputImage.getType() == egami::colorType::RGBA8) { uint8_t data[16]; for(int32_t yyy=0; yyy<_inputImage.getSize().y(); ++yyy) { for(int32_t xxx=0; xxx<_inputImage.getSize().x(); ++xxx) { const etk::Color<>& tmpColor = _inputImage.get(ivec2(xxx,_inputImage.getSize().y()-yyy-1)); uint8_t* pointer = data; *pointer++ = tmpColor.r(); *pointer++ = tmpColor.g(); *pointer++ = tmpColor.b(); *pointer++ = tmpColor.a(); _buffer.pushBack(data, 4); } } } else { uint8_t data[16]; for(int32_t yyy=0; yyy<_inputImage.getSize().y(); ++yyy) { for(int32_t xxx=0; xxx<_inputImage.getSize().x(); ++xxx) { const etk::Color<>& tmpColor = _inputImage.get(ivec2(xxx,_inputImage.getSize().y()-yyy-1)); uint8_t* pointer = data; *pointer++ = tmpColor.b(); *pointer++ = tmpColor.g(); *pointer++ = tmpColor.r(); _buffer.pushBack(data, 3); } if (offset != 0) { uint8_t pointer[4]; pointer[0] = 0; pointer[1] = 0; pointer[2] = 0; pointer[3] = 0; _buffer.pushBack(pointer, offset); } } } return true; } #else // old mode: bool egami::storeBMP(const etk::String& _fileName, const egami::Image& _inputImage) { struct bitmapFileHeader m_FileHeader; struct bitmapInfoHeader m_InfoHeader; memset(&m_InfoHeader, 0, sizeof(bitmapInfoHeader)); m_FileHeader.bfType = 0x4D42; m_FileHeader.bfSize = sizeof(struct bitmapFileHeader) + sizeof(struct bitmapInfoHeader); m_FileHeader.bfReserved = 0; m_FileHeader.bfOffBits = sizeof(struct bitmapFileHeader) + sizeof(struct bitmapInfoHeader); //EGAMI_ERROR("plopppppppppppppp " << m_FileHeader.bfOffBits); m_InfoHeader.biSize = sizeof(struct bitmapInfoHeader); m_InfoHeader.biWidth = _inputImage.getSize().x(); m_InfoHeader.biHeight = _inputImage.getSize().y(); m_InfoHeader.biPlanes = 1; int32_t offset = 0; if (_inputImage.getType() == egami::colorType::RGBA8) { m_InfoHeader.biBitCount = 32; m_InfoHeader.biCompression = 0; m_InfoHeader.biSizeImage = _inputImage.getSize().x()*_inputImage.getSize().y()*4; } else { m_InfoHeader.biBitCount = 24; m_InfoHeader.biCompression = 0; int32_t baseLine = _inputImage.getSize().x() * 3; if ((baseLine%4) == 1) { offset = 3; } else if ((baseLine%4) == 2) { offset = 2; } else if ((baseLine%4) == 3) { offset = 1; } m_InfoHeader.biSizeImage = (baseLine+offset)*_inputImage.getSize().y(); } m_FileHeader.bfSize += m_InfoHeader.biSizeImage; m_InfoHeader.biXPelsPerMeter = 72*39.3701+1; m_InfoHeader.biYPelsPerMeter = 72*39.3701+1; //m_InfoHeader.biClrUsed = 0; //m_InfoHeader.biClrImportant = 0; etk::FSNode fileName(_fileName); if(false == fileName.fileOpenWrite() ) { EGAMI_ERROR("Can not find the file name=\"" << fileName << "\""); return false; } // Write header: if (fileName.fileWrite(&m_FileHeader,sizeof(struct bitmapFileHeader),1) != 1) { EGAMI_ERROR("error loading file header"); fileName.fileClose(); return false; } if (fileName.fileWrite(&m_InfoHeader,sizeof(struct bitmapInfoHeader),1) != 1) { EGAMI_ERROR("error loading file header"); fileName.fileClose(); return false; } EGAMI_ERROR("header size = " << sizeof(struct bitmapFileHeader) << " + " << sizeof(struct bitmapInfoHeader) << " = " << (sizeof(struct bitmapFileHeader)+sizeof(struct bitmapInfoHeader)) ); /* TODO: Avec ca, ca ne fonctionne pas ... ==> check if(fileName.fileSeek(m_FileHeader.bfOffBits, etk::FSN_SEEK_START) == false) { EGAMI_ERROR("error with the 'bfOffBits' in the file named=\"" << fileName << "\""); fileName.fileClose(); return false; } */ if (_inputImage.getType() == egami::colorType::RGBA8) { uint8_t data[16]; for(int32_t yyy=0; yyy<_inputImage.getSize().y(); ++yyy) { for(int32_t xxx=0; xxx<_inputImage.getSize().x(); ++xxx) { const etk::Color<>& tmpColor = _inputImage.get(ivec2(xxx,_inputImage.getSize().y()-yyy-1)); uint8_t* pointer = data; *pointer++ = tmpColor.r(); *pointer++ = tmpColor.g(); *pointer++ = tmpColor.b(); *pointer++ = tmpColor.a(); fileName.fileWrite(data,4,1); } } } else { uint8_t data[16]; for(int32_t yyy=0; yyy<_inputImage.getSize().y(); ++yyy) { for(int32_t xxx=0; xxx<_inputImage.getSize().x(); ++xxx) { const etk::Color<>& tmpColor = _inputImage.get(ivec2(xxx,_inputImage.getSize().y()-yyy-1)); uint8_t* pointer = data; *pointer++ = tmpColor.b(); *pointer++ = tmpColor.g(); *pointer++ = tmpColor.r(); fileName.fileWrite(data,3,1); } if (offset != 0) { uint8_t pointer[4]; pointer[0] = 0; pointer[1] = 0; pointer[2] = 0; pointer[3] = 0; fileName.fileWrite(pointer,1,offset); } } } fileName.fileClose(); return true; } #endif