Files
OpenSceneGraph/src/osgPlugins/bmp/ReaderWriterBMP.cpp
2012-03-21 17:36:20 +00:00

649 lines
20 KiB
C++

// -*-c++-*-
/*
* $Id$
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <osg/Image>
#include <osg/Notify>
#include <osg/Image>
#include <osg/GL>
#include <osg/Endian>
#include <osgDB/Registry>
#include <osgDB/FileNameUtils>
#include <osgDB/FileUtils>
#include <vector>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
/****************************************************************************
*
* Follows is code written by GWM and translated to fit with the OSG Ethos.
*
* Ported into the OSG as a plugin, Geoff Michel October 2001.
* For patches, bugs and new features
* please send them direct to the OSG dev team.
*
**********************************************************************/
// find least-significant (lowest) bit position in 16-bit mask
static unsigned int findLeastSignificantBit(unsigned short mask)
{
unsigned int shift = 1;
while ((mask & 0x01) == 0)
{
mask >>= 1;
++shift;
}
return shift;
}
// find most-significant (highest) bit position in 16-bit mask
static unsigned int findMostSignificantBit(unsigned short mask)
{
unsigned int shift = 16;
while ((mask & 0x8000) == 0)
{
mask <<= 1;
--shift;
}
return shift;
}
/*
* BMP header
*/
const unsigned short BMP_MAGIC_BM = 0x424D; // 'BM'
const unsigned short BMP_MAGIC_MB = 0x4D42; // 'MB'
struct BMPHeader {
unsigned short magic; // stored 'BM', but read as ushort
unsigned int fileSize;
unsigned short reserved1;
unsigned short reserved2;
unsigned int imageOffset;
};
/*
* Windows v3 header
*/
enum BMPCOMPRESSION {
BI_RGB = 0,
BI_RLE8,
BI_RLE4,
BI_BITFIELDS,
BI_JPEG,
BI_PNG
};
struct BITMAPINFOHEADER {
//unsigned int hdrSize;
int width, height;
unsigned short colorPlanes;
unsigned short bitsPerPixel;
unsigned int compression;
unsigned int imageSize;
int horizontalPixelPerMeter;
int verticalPixelPerMeter;
unsigned int numColorsInPalette;
unsigned int numImportantColors;
};
/*
* OS/2 v1 header
*/
struct BITMAPCOREHEADER {
//unsigned int hdrSize;
unsigned short width, height;
unsigned short colorPlanes;
unsigned short bitsPerPixel;
};
static unsigned char* bmp_load(std::istream& fin,
int& width_ret, int& height_ret, int& numComponents_ret)
{
// actual file size
fin.seekg(0, std::ios::end);
size_t actFileSize = fin.tellg();
fin.seekg(0, std::ios::beg);
bool swap;
BMPHeader bmp;
{
/*
* read each part individually to avoid struct packing issues (and #pragma)
*/
fin.read((char*) &bmp.magic, sizeof(bmp.magic));
fin.read((char*) &bmp.fileSize, sizeof(bmp.fileSize));
fin.read((char*) &bmp.reserved1, sizeof(bmp.reserved1));
fin.read((char*) &bmp.reserved2, sizeof(bmp.reserved2));
fin.read((char*) &bmp.imageOffset, sizeof(bmp.imageOffset));
if (bmp.magic != BMP_MAGIC_BM && bmp.magic != BMP_MAGIC_MB)
{
OSG_WARN << "Invalid BMP magic\n";
return 0;
}
swap = (bmp.magic == BMP_MAGIC_BM); // means machine is big-endian and must swap
if (swap)
{
OSG_DEBUG << "swap=" << swap << std::endl;
osg::swapBytes4((char*) &bmp.fileSize);
osg::swapBytes4((char*) &bmp.imageOffset);
}
if (bmp.fileSize != actFileSize)
{
OSG_DEBUG << "Stored BMP fileSize=" << bmp.fileSize << " != actual=" << actFileSize << std::endl;
bmp.fileSize = actFileSize;
}
}
BITMAPINFOHEADER dib;
unsigned int dibHdrSize;
/*
* read DIB header
*/
fin.read((char*) &dibHdrSize, sizeof(dibHdrSize));
if (swap)
osg::swapBytes4((char*) &dibHdrSize);
if (dibHdrSize == 12)
{
/*
* OS/2 v1
*/
BITMAPCOREHEADER hdr;
unsigned int expectHdrSize = sizeof(hdr) + sizeof(dibHdrSize);
if (expectHdrSize != dibHdrSize)
{
OSG_WARN << "Invalid BMP OS/2 v1 header size " << expectHdrSize << " != " << dibHdrSize << std::endl;
return 0;
}
fin.read((char*) &hdr, sizeof(hdr));
if (swap)
{
osg::swapBytes2((char*) &hdr.width);
osg::swapBytes2((char*) &hdr.height);
osg::swapBytes2((char*) &hdr.colorPlanes);
osg::swapBytes2((char*) &hdr.bitsPerPixel);
}
// store to BITMAPINFOHEADER
memset(&dib, 0, sizeof(dib));
dib.width = hdr.width;
dib.height = hdr.height;
dib.colorPlanes = hdr.colorPlanes;
dib.bitsPerPixel = hdr.bitsPerPixel;
}
else if (dibHdrSize == 40 || dibHdrSize == 108 || dibHdrSize == 124)
{
/*
* Windows v3/v4/v5 header
* reads only the common part (i.e. v3) and skips over the rest
*/
fin.read((char*) &dib, sizeof(dib));
if (swap)
{
osg::swapBytes4((char*) &dib.width);
osg::swapBytes4((char*) &dib.height);
osg::swapBytes2((char*) &dib.colorPlanes);
osg::swapBytes2((char*) &dib.bitsPerPixel);
osg::swapBytes4((char*) &dib.compression);
osg::swapBytes4((char*) &dib.imageSize);
osg::swapBytes4((char*) &dib.numColorsInPalette);
osg::swapBytes4((char*) &dib.numImportantColors);
}
}
else
{
OSG_WARN << "Unsupported BMP/DIB header size=" << dibHdrSize << std::endl;
return 0;
}
// sanity checks
if (dib.height < 0)
{
OSG_DEBUG << "BMP Image is upside-down\n";
dib.height *= -1;
}
if (dib.colorPlanes != 1)
{
OSG_WARN << "Invalid BMP number of color planes=" << dib.colorPlanes << std::endl;
return 0;
}
if (dib.bitsPerPixel == 0)
{
OSG_WARN << "Invalid BMP bits/pixel=" << dib.bitsPerPixel << std::endl;
return 0;
}
if (dib.compression != BI_RGB && dib.compression != BI_BITFIELDS)
{
OSG_WARN << "Unsupported BMP compression=" << dib.compression << std::endl;
return 0;
}
/*
* color masks
*/
unsigned int redMask, greenMask, blueMask;
if (dib.bitsPerPixel == 16 && dib.compression == BI_BITFIELDS)
{
fin.read((char*) &redMask, sizeof(redMask));
fin.read((char*) &greenMask, sizeof(greenMask));
fin.read((char*) &blueMask, sizeof(blueMask));
if (swap)
{
osg::swapBytes4((char*) &redMask);
osg::swapBytes4((char*) &greenMask);
osg::swapBytes4((char*) &blueMask);
}
}
else
{
redMask = 0x7c00;
greenMask = 0x03e0;
blueMask = 0x001f;
}
// determine shift width...
unsigned int redShift = findLeastSignificantBit(redMask) - 1;
unsigned int greenShift = findLeastSignificantBit(greenMask) - 1;
unsigned int blueShift = findLeastSignificantBit(blueMask) - 1;
// determine mask width
unsigned int redMaskWidth = findMostSignificantBit(redMask) - redShift;
unsigned int greenMaskWidth = findMostSignificantBit(greenMask) - greenShift;
unsigned int blueMaskWidth = findMostSignificantBit(blueMask) - blueShift;
#if 0
printf("redMask=%04x/%d/%d greenMask=%04x/%d/%d blueMask=%04x/%d/%d\n",
redMask, redMaskWidth, redShift,
greenMask, greenMaskWidth, greenShift,
blueMask, blueMaskWidth, blueShift);
#endif
unsigned int imageBytesPerPixel = 0;
/*
* color palette
*/
std::vector<unsigned char> colorPalette;
if (dib.bitsPerPixel < 16)
{
// defaults to 2^n
if (dib.numColorsInPalette == 0)
dib.numColorsInPalette = 1 << dib.bitsPerPixel;
// allocate/read color palette
imageBytesPerPixel = (dibHdrSize == 12 ? 3 : 4); // OS/2 v1 stores RGB, else RGBA
colorPalette.resize(dib.numColorsInPalette * imageBytesPerPixel);
fin.read((char*) &*colorPalette.begin(), colorPalette.size());
}
else
{
if (dib.bitsPerPixel == 16)
imageBytesPerPixel = 3;
else if (dib.bitsPerPixel == 24 || dib.bitsPerPixel == 32)
imageBytesPerPixel = dib.bitsPerPixel / 8;
else
{
OSG_WARN << "Unsupported BMP bit depth " << dib.bitsPerPixel << std::endl;
return 0;
}
}
unsigned int where = fin.tellg();
if (where != bmp.imageOffset)
{
// this can happen because we don't fully parse v4/v5 headers
OSG_DEBUG << "BMP streampos out-of-sync where=" << where << " imageOffset=" << bmp.imageOffset << std::endl;
fin.seekg(bmp.imageOffset, std::ios::beg); // seek to imageOffset and hope for the best
}
/*
* image data
*/
const unsigned int imageBytesPerRow = dib.width * imageBytesPerPixel;
const unsigned int imageBufferSize = imageBytesPerRow * dib.height;
unsigned char* imageBuffer = new unsigned char[imageBufferSize];
//printf("imageBytesPerPixel=%u imageBytesPerRow=%u\n", imageBytesPerPixel, imageBytesPerRow);
// byte/row in BMP image data
unsigned int bytesPerPixel;
unsigned int bytesPerRow;
if (dib.bitsPerPixel >= 8)
{
bytesPerPixel = dib.bitsPerPixel / 8;
bytesPerRow = dib.width * bytesPerPixel;
}
else
{
bytesPerPixel = 1;
bytesPerRow = (unsigned int) ceilf(dib.width * (dib.bitsPerPixel / 8.0f));
}
const unsigned int bytesPerRowAlign = (unsigned int) ceilf(bytesPerRow / 4.0f) * 4;
//printf("bytesPerPixel=%u bytesPerRow=%u bytesPerRowAlign=%u\n", bytesPerPixel, bytesPerRow, bytesPerRowAlign);
std::vector<unsigned char> rowBuffer;
rowBuffer.resize(bytesPerRowAlign);
if (dib.bitsPerPixel >= 16)
{
unsigned char* imgp = imageBuffer;
for (int i = 0; i < dib.height; ++i)
{
// read row
unsigned char* rowp = &*rowBuffer.begin();
fin.read((char*) rowp, rowBuffer.size());
// copy to image buffer, swap/unpack BGR to RGB(A)
for (unsigned int j = 0; j < bytesPerRow; j += bytesPerPixel)
{
if (dib.bitsPerPixel == 16)
{
// 16-bit RGB -> 24-bit RGB
unsigned short rgb16 = (rowp[1] << 8) | rowp[0];
if (swap)
osg::swapBytes2((char*) &rgb16);
imgp[0] = (rgb16 & redMask) >> redShift;
imgp[1] = (rgb16 & greenMask) >> greenShift;
imgp[2] = (rgb16 & blueMask) >> blueShift;
// expand range
imgp[0] <<= (8-redMaskWidth);
imgp[1] <<= (8-greenMaskWidth);
imgp[2] <<= (8-blueMaskWidth);
}
else
{
// BGR -> RGB(A)
imgp[0] = rowp[2];
imgp[1] = rowp[1];
imgp[2] = rowp[0];
if (imageBytesPerPixel == 4)
{
imgp[3] = 0xff;
}
}
imgp += imageBytesPerPixel;
rowp += bytesPerPixel;
}
}
}
else
{
const int idxPerByte = 8 / dib.bitsPerPixel; // color indices per byte
const int idxMask = (1 << dib.bitsPerPixel) - 1; // index mask
//printf("idxPerByte=%d idxMask=%02x\n", idxPerByte, idxMask);
unsigned char* imgp = imageBuffer;
for (int i = 0; i < dib.height; ++i)
{
// read row
unsigned char* rowp = &*rowBuffer.begin();
fin.read((char*) rowp, rowBuffer.size());
int j = 0;
while (j < dib.width)
{
// unpack bytes/indices to image buffer
unsigned char val = rowp[0];
for (int k = 0; k < idxPerByte && j < dib.width; ++k, ++j)
{
unsigned int idx = (val >> ((idxPerByte-1-k) * dib.bitsPerPixel)) & idxMask;
idx *= imageBytesPerPixel;
imgp[0] = colorPalette[idx+2];
imgp[1] = colorPalette[idx+1];
imgp[2] = colorPalette[idx+0];
if (imageBytesPerPixel == 4)
{
imgp[3] = 0xff;
}
imgp += imageBytesPerPixel;
}
++rowp;
}
}
}
// return result
width_ret = dib.width;
height_ret = dib.height;
numComponents_ret = imageBytesPerPixel;
return imageBuffer;
}
static bool bmp_save(const osg::Image& img, std::ostream& fout)
{
BMPHeader bmp;
const unsigned int bmpHdrSize = 14;
BITMAPINFOHEADER dib;
assert(sizeof(dib) == 36);
const unsigned int dibHdrSize = sizeof(dib) + 4;
const unsigned int bytesPerRowAlign = ((img.s() * 3 + 3) / 4) * 4;
bool swap = (osg::getCpuByteOrder() == osg::BigEndian);
// BMP header
{
bmp.magic = BMP_MAGIC_BM;
bmp.reserved1 = bmp.reserved2 = 0;
bmp.imageOffset = bmpHdrSize + dibHdrSize;
bmp.fileSize = bmp.imageOffset + bytesPerRowAlign * img.t();
#if 0
printf("sizeof(bmp)=%u sizeof(dib)=%u dibHdrSize=%u\n", sizeof(bmp), sizeof(dib), dibHdrSize);
printf("fileSize=%u imageOffset=%u\n", bmp.fileSize, bmp.imageOffset);
printf("s=%u t=%u bytesPerRowAlign=%u\n", img.s(), img.t(), bytesPerRowAlign);
#endif
if (swap)
{
// big-endian must swap everything except magic
osg::swapBytes4((char*) &bmp.fileSize);
osg::swapBytes4((char*) &bmp.imageOffset);
}
else
{
// little-endian must swap the magic
osg::swapBytes2((char*) &bmp.magic);
}
fout.write((char*) &bmp.magic, sizeof(bmp.magic));
fout.write((char*) &bmp.fileSize, sizeof(bmp.fileSize));
fout.write((char*) &bmp.reserved1, sizeof(bmp.reserved1));
fout.write((char*) &bmp.reserved2, sizeof(bmp.reserved2));
fout.write((char*) &bmp.imageOffset, sizeof(bmp.imageOffset));
}
// DIB header
{
dib.width = img.s();
dib.height = img.t();
dib.colorPlanes = 1;
dib.bitsPerPixel = 24;
dib.compression = BI_RGB;
dib.imageSize = bytesPerRowAlign * img.t();
dib.horizontalPixelPerMeter = 1000;
dib.verticalPixelPerMeter = 1000;
dib.numColorsInPalette = 0;
dib.numImportantColors = 0;
if (swap) {
osg::swapBytes4((char*) &dibHdrSize);
osg::swapBytes4((char*) &dib.width);
osg::swapBytes4((char*) &dib.height);
osg::swapBytes2((char*) &dib.colorPlanes);
osg::swapBytes2((char*) &dib.bitsPerPixel);
osg::swapBytes4((char*) &dib.imageSize);
osg::swapBytes4((char*) &dib.horizontalPixelPerMeter);
osg::swapBytes4((char*) &dib.verticalPixelPerMeter);
}
fout.write((char*) &dibHdrSize, sizeof(dibHdrSize));
fout.write((char*) &dib, sizeof(dib));
}
unsigned int pixelFormat = img.getPixelFormat();
unsigned int r = 0, g = 1, b = 2;
if ( pixelFormat == GL_BGR || pixelFormat == GL_BGRA )
{
r = 2;
b = 0;
}
const unsigned int channelsPerPixel = img.computeNumComponents(pixelFormat);
std::vector<unsigned char> rowBuffer(bytesPerRowAlign);
for (int y = 0; y < img.t(); ++y)
{
const unsigned char* imgp = img.data(0, y);
for (int x = 0; x < img.s(); ++x)
{
// RGB -> BGR
unsigned int rowOffs = x * 3, imgOffs = x * channelsPerPixel;
rowBuffer[rowOffs + 2] = imgp[imgOffs + r];
rowBuffer[rowOffs + 1] = imgp[imgOffs + g];
rowBuffer[rowOffs + 0] = imgp[imgOffs + b];
}
fout.write((char*) &*rowBuffer.begin(), rowBuffer.size());
}
return true;
}
class ReaderWriterBMP : public osgDB::ReaderWriter
{
public:
ReaderWriterBMP()
{
supportsExtension("bmp","BMP Image format");
}
const char* className() const { return "BMP Image Reader"; }
ReadResult readObject(std::istream& fin, const Options* options = 0) const
{
return readImage(fin, options);
}
ReadResult readObject(const std::string& file, const Options* options = 0) const
{
return readImage(file, options);
}
ReadResult readImage(std::istream& fin, const Options* = 0) const
{
return readBMPStream(fin);
}
ReadResult readImage(const std::string& file, const Options* options = 0) const
{
std::string ext = osgDB::getLowerCaseFileExtension(file);
if (!acceptsExtension(ext)) return ReadResult::FILE_NOT_HANDLED;
std::string fileName = osgDB::findDataFile(file, options);
if (fileName.empty()) return ReadResult::FILE_NOT_FOUND;
osgDB::ifstream istream(fileName.c_str(), std::ios::in | std::ios::binary);
if(!istream) return ReadResult::FILE_NOT_HANDLED;
ReadResult rr = readBMPStream(istream);
if(rr.validImage()) rr.getImage()->setFileName(file);
return rr;
}
WriteResult writeImage(const osg::Image& image, std::ostream& fout, const Options* = 0) const
{
if (bmp_save(image, fout))
return WriteResult::FILE_SAVED;
else
return WriteResult::ERROR_IN_WRITING_FILE;
}
WriteResult writeImage(const osg::Image& img, const std::string& fileName, const Options* options = 0) const
{
std::string ext = osgDB::getFileExtension(fileName);
if (!acceptsExtension(ext)) return WriteResult::FILE_NOT_HANDLED;
osgDB::ofstream fout(fileName.c_str(), std::ios::out | std::ios::binary);
if (!fout) return WriteResult::ERROR_IN_WRITING_FILE;
return writeImage(img, fout, options);
}
private:
static ReadResult readBMPStream(std::istream& fin)
{
int s, t;
int internalFormat;
unsigned char *imageData = bmp_load(fin, s, t, internalFormat);
if (imageData == 0) return ReadResult::ERROR_IN_READING_FILE;
unsigned int pixelFormat;
switch (internalFormat)
{
case 1:
pixelFormat = GL_LUMINANCE;
break;
case 2:
pixelFormat = GL_LUMINANCE_ALPHA;
break;
case 3:
pixelFormat = GL_RGB;
break;
default:
pixelFormat = GL_RGBA;
break;
}
osg::Image* img = new osg::Image;
img->setImage(s, t, 1,
internalFormat, pixelFormat, GL_UNSIGNED_BYTE, imageData,
osg::Image::USE_NEW_DELETE);
return img;
}
};
// now register with Registry to instantiate the above
// reader/writer.
REGISTER_OSGPLUGIN(bmp, ReaderWriterBMP)