parameter in osg::Image. To support this Image::setData(..) now has a new optional rowLength parameter which defaults to 0, which provides the original behaviour, Image::setRowLength(int) and int Image::getRowLength() are also provided. With the introduction of RowLength support in osg::Image it is now possible to create a sub image where the t size of the image are smaller than the row length, useful for when you have a large image on the CPU and which to use a small portion of it on the GPU. However, when these sub images are created the data within the image is no longer contiguous so data access can no longer assume that all the data is in one block. The new method Image::isDataContiguous() enables the user to check whether the data is contiguous, and if not one can either access the data row by row using Image::data(column,row,image) accessor, or use the new Image::DataIterator for stepping through each block on memory assocatied with the image. To support the possibility of non contiguous osg::Image usage of image objects has had to be updated to check DataContiguous and handle the case or use access via the DataIerator or by row by row. To achieve this a relatively large number of files has had to be modified, in particular the texture classes and image plugins that doing writing.
628 lines
18 KiB
C++
628 lines
18 KiB
C++
#include <osg/Image>
|
|
#include <osg/Notify>
|
|
#include <osg/Geode>
|
|
#include <osg/GL>
|
|
|
|
#include <osgDB/Registry>
|
|
#include <osgDB/FileNameUtils>
|
|
#include <osgDB/FileUtils>
|
|
#include <osgDB/fstream>
|
|
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
/****************************************************************************
|
|
*
|
|
* Follows is code extracted from the simage library. Original Authors:
|
|
*
|
|
* Systems in Motion,
|
|
* <URL:http://www.sim.no>
|
|
*
|
|
* Peder Blekken <pederb@sim.no>
|
|
* Morten Eriksen <mortene@sim.no>
|
|
* Marius Bugge Monsen <mariusbu@sim.no>
|
|
*
|
|
* The original COPYING notice
|
|
*
|
|
* All files in this library are public domain, except simage_rgb.cpp which is
|
|
* Copyright (c) Mark J Kilgard <mjk@nvidia.com>. I will contact Mark
|
|
* very soon to hear if this source also can become public domain.
|
|
*
|
|
* Please send patches for bugs and new features to: <pederb@sim.no>.
|
|
*
|
|
* Peder Blekken
|
|
*
|
|
*
|
|
* Ported into the OSG as a plugin, Robert Osfield Decemeber 2000.
|
|
* Note, reference above to license of simage_rgb is not relevent to the OSG
|
|
* as the OSG does not use it. Also for patches, bugs and new features
|
|
* please send them direct to the OSG dev team rather than address above.
|
|
*
|
|
**********************************************************************/
|
|
|
|
/* */
|
|
/* Supported types: */
|
|
/* */
|
|
/* 1 (Uncompressed, color-mapped images) */
|
|
/* 2 (RGB uncompressed) */
|
|
/* 9 RLE color-mapped */
|
|
/* 10 RLE RGB */
|
|
/* */
|
|
|
|
#define ERR_NO_ERROR 0
|
|
#define ERR_OPEN 1
|
|
#define ERR_READ 2
|
|
#define ERR_MEM 3
|
|
#define ERR_UNSUPPORTED 4
|
|
|
|
static int tgaerror = ERR_NO_ERROR;
|
|
int
|
|
simage_tga_error(char * buffer, int buflen)
|
|
{
|
|
switch (tgaerror)
|
|
{
|
|
case ERR_OPEN:
|
|
strncpy(buffer, "TGA loader: Error opening file", buflen);
|
|
break;
|
|
case ERR_READ:
|
|
strncpy(buffer, "TGA loader: Error reading file", buflen);
|
|
break;
|
|
case ERR_MEM:
|
|
strncpy(buffer, "TGA loader: Out of memory error", buflen);
|
|
break;
|
|
}
|
|
return tgaerror;
|
|
}
|
|
|
|
|
|
/* TODO: */
|
|
/* - bottom-up images */
|
|
/* - huffman, delta encoding */
|
|
static void
|
|
convert_16_to_24(const unsigned char * const src, unsigned char * const dest)
|
|
{
|
|
/* RGB for opengl, lo-hi 16 bit for TGA */
|
|
unsigned int t0 = src[0];
|
|
unsigned int t1 = src[1];
|
|
/* r */
|
|
dest[0] = (unsigned char) (t0 & 0x1f) << 2;
|
|
/* g */
|
|
dest[1] = (unsigned char) (t1 & 0x7c) >> 2;
|
|
/*b */
|
|
dest[2] = (unsigned char) ((t1 & 0x3)<<3) | ((t0&0xe)>>5);
|
|
}
|
|
|
|
|
|
static void
|
|
convert_16_to_32(const unsigned char * const src, unsigned char * const dest)
|
|
{
|
|
/* RGBA for opengl, lo-hi 16 bit for TGA */
|
|
unsigned int t0 = src[0];
|
|
unsigned int t1 = src[1];
|
|
/* r */
|
|
dest[0] = (unsigned char) (t0 & 0x1f) << 2;
|
|
/* g */
|
|
dest[1] = (unsigned char) (t1 & 0x7c) >> 2;
|
|
/*b */
|
|
dest[2] = (unsigned char) ((t1 & 0x3)<<3) | ((t0&0xe)>>5);
|
|
dest[3] = (t1&0x70)?255:0; /* a */
|
|
}
|
|
|
|
|
|
static void
|
|
convert_24_to_24(const unsigned char * const src, unsigned char * const dest)
|
|
{
|
|
/* RGB for opengl */
|
|
/* BGR for TGA */
|
|
dest[0] = src[2];
|
|
dest[1] = src[1];
|
|
dest[2] = src[0];
|
|
}
|
|
|
|
|
|
static void
|
|
convert_32_to_32(const unsigned char * const src, unsigned char * const dest)
|
|
{
|
|
/* opengl image format is RGBA, not ARGB */
|
|
/* TGA image format is BGRA for 32 bit */
|
|
dest[0] = src[2];
|
|
dest[1] = src[1];
|
|
dest[2] = src[0];
|
|
dest[3] = src[3];
|
|
}
|
|
|
|
|
|
static void
|
|
convert_data(const unsigned char * const src, unsigned char * const dest,
|
|
const int x, const int srcformat,
|
|
const int destformat)
|
|
{
|
|
if (srcformat == 2)
|
|
{
|
|
if (destformat == 3)
|
|
convert_16_to_24(src+x*srcformat,
|
|
dest+x*destformat);
|
|
else
|
|
{
|
|
assert(destformat == 4);
|
|
convert_16_to_32(src+x*srcformat,
|
|
dest+x*destformat);
|
|
}
|
|
}
|
|
else if (srcformat == 3)
|
|
{
|
|
assert(destformat == 3);
|
|
convert_24_to_24(src+x*srcformat,
|
|
dest+x*destformat);
|
|
}
|
|
else
|
|
{
|
|
assert(srcformat == 4 && destformat == 4);
|
|
convert_32_to_32(src+x*srcformat,
|
|
dest+x*destformat);
|
|
}
|
|
}
|
|
|
|
|
|
/* Intel byte order workaround */
|
|
static int getInt16(unsigned char *ptr)
|
|
{
|
|
int res = ptr[0];
|
|
int tmp = ptr[1];
|
|
return res | (tmp<<8);
|
|
}
|
|
|
|
|
|
/* */
|
|
/* decode a new rle packet */
|
|
/* */
|
|
static void
|
|
rle_new_packet(unsigned char ** src,
|
|
int * rleRemaining,
|
|
int * rleIsCompressed,
|
|
unsigned char *rleCurrent,
|
|
const int rleEntrySize)
|
|
{
|
|
int i;
|
|
unsigned char code = *(*src)++;
|
|
/* number of bytes left in this packet */
|
|
*rleRemaining = (code & 127) + 1;
|
|
if (code & 128) /* rle */
|
|
{
|
|
*rleIsCompressed = 1;
|
|
for (i = 0; i < rleEntrySize; i++)
|
|
rleCurrent[i] = *(*src)++;
|
|
}
|
|
else /* uncompressed */
|
|
{
|
|
*rleIsCompressed = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/* */
|
|
/* decode the # of specified bytes */
|
|
/* */
|
|
static void
|
|
rle_decode(unsigned char ** src,
|
|
unsigned char *dest,
|
|
const int numbytes,
|
|
int * rleRemaining,
|
|
int * rleIsCompressed,
|
|
unsigned char *rleCurrent,
|
|
const int rleEntrySize)
|
|
{
|
|
int i;
|
|
int size = rleEntrySize;
|
|
unsigned char *stop = dest + numbytes;
|
|
while (dest < stop)
|
|
{
|
|
if (*rleRemaining == 0) /* start new packet */
|
|
rle_new_packet(src, rleRemaining, rleIsCompressed,
|
|
rleCurrent, rleEntrySize);
|
|
|
|
if (*rleIsCompressed)
|
|
{
|
|
for (i = 0; i < size; i++)
|
|
{
|
|
*dest++ = rleCurrent[i];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < size; i++)
|
|
{
|
|
*dest++ = *(*src)++;
|
|
}
|
|
}
|
|
// original code : *rleRemaining)--;
|
|
(*rleRemaining)--;
|
|
}
|
|
}
|
|
|
|
|
|
unsigned char *
|
|
simage_tga_load(std::istream& fin,
|
|
int *width_ret,
|
|
int *height_ret,
|
|
int *numComponents_ret)
|
|
{
|
|
unsigned char header[18];
|
|
int type;
|
|
int width;
|
|
int height;
|
|
int depth;
|
|
int flags;
|
|
int format;
|
|
unsigned char *colormap;
|
|
int indexsize;
|
|
int rleIsCompressed;
|
|
int rleRemaining;
|
|
int rleEntrySize;
|
|
unsigned char rleCurrent[4];
|
|
unsigned char *buffer;
|
|
unsigned char *dest;
|
|
int bpr;
|
|
unsigned char *linebuf;
|
|
|
|
tgaerror = ERR_NO_ERROR; /* clear error */
|
|
|
|
fin.read((char*)header,18);
|
|
if (fin.gcount() != 18)
|
|
{
|
|
tgaerror = ERR_READ;
|
|
return NULL;
|
|
}
|
|
|
|
type = header[2];
|
|
width = getInt16(&header[12]);
|
|
height = getInt16(&header[14]);
|
|
depth = header[16] >> 3;
|
|
flags = header[17];
|
|
|
|
/* check for reasonable values in case this is not a tga file */
|
|
if ((type != 2 && type != 10) ||
|
|
(width < 0 || width > 4096) ||
|
|
(height < 0 || height > 4096) ||
|
|
(depth < 2 || depth > 4))
|
|
{
|
|
tgaerror = ERR_UNSUPPORTED;
|
|
return NULL;
|
|
}
|
|
|
|
if (header[0]) /* skip identification field */
|
|
fin.seekg(header[0],std::ios::cur);
|
|
|
|
colormap = NULL;
|
|
if (header[1] == 1) /* there is a colormap */
|
|
{
|
|
int len = getInt16(&header[5]);
|
|
indexsize = header[7]>>3;
|
|
colormap = new unsigned char [len*indexsize];
|
|
fin.read((char*)colormap,len*indexsize);
|
|
}
|
|
|
|
if (depth == 2) /* 16 bits */
|
|
{
|
|
if (flags & 1) format = 4;
|
|
else format = 3;
|
|
}
|
|
else format = depth;
|
|
|
|
/* SoDebugError::postInfo("simage_tga_load", "TARGA file: %d %d %d %d %d\n", */
|
|
/* type, width, height, depth, format); */
|
|
|
|
rleIsCompressed = 0;
|
|
rleRemaining = 0;
|
|
rleEntrySize = depth;
|
|
buffer = new unsigned char [width*height*format];
|
|
dest = buffer;
|
|
bpr = format * width;
|
|
linebuf = new unsigned char [width*depth];
|
|
|
|
//check the intended image orientation
|
|
bool bLeftToRight = (flags&0x10)==0;
|
|
bool bTopToBottom = (flags&0x20)!=0;
|
|
int lineoffset = bTopToBottom ? -bpr : bpr;
|
|
if (bTopToBottom) //move start point to last line in buffer
|
|
dest += (bpr*(height-1));
|
|
|
|
switch(type)
|
|
{
|
|
case 1: /* colormap, uncompressed */
|
|
{
|
|
/* FIXME: write code */
|
|
/* should never get here because simage_tga_identify returns 0 */
|
|
/* for this filetype */
|
|
/* I need example files in this format to write the code... */
|
|
tgaerror = ERR_UNSUPPORTED;
|
|
}
|
|
break;
|
|
case 2: /* RGB, uncompressed */
|
|
{
|
|
int x, y;
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
fin.read((char*)linebuf,width*depth);
|
|
if (fin.gcount() != (std::streamsize) (width*depth))
|
|
{
|
|
tgaerror = ERR_READ;
|
|
break;
|
|
}
|
|
for (x = 0; x < width; x++)
|
|
{
|
|
convert_data(linebuf, dest, bLeftToRight ? x : (width-1) - x, depth, format);
|
|
}
|
|
dest += lineoffset;
|
|
}
|
|
}
|
|
break;
|
|
case 9: /* colormap, compressed */
|
|
{
|
|
/* FIXME: write code */
|
|
/* should never get here because simage_tga_identify returns 0 */
|
|
/* for this filetype */
|
|
/* I need example files in this format to write the code... */
|
|
tgaerror = ERR_UNSUPPORTED;
|
|
}
|
|
break;
|
|
case 10: /* RGB, compressed */
|
|
{
|
|
int size, x, y;
|
|
unsigned char *buf;
|
|
unsigned char *src;
|
|
int pos = fin.tellg();
|
|
fin.seekg(0,std::ios::end);
|
|
size = (int)fin.tellg() - pos;
|
|
fin.seekg(pos,std::ios::beg);
|
|
buf = new unsigned char [size];
|
|
if (buf == NULL)
|
|
{
|
|
tgaerror = ERR_MEM;
|
|
break;
|
|
}
|
|
src = buf;
|
|
fin.read((char*)buf,size);
|
|
if (fin.gcount() != (std::streamsize) size)
|
|
{
|
|
tgaerror = ERR_READ;
|
|
break;
|
|
}
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
rle_decode(&src, linebuf, width*depth, &rleRemaining,
|
|
&rleIsCompressed, rleCurrent, rleEntrySize);
|
|
assert(src <= buf + size);
|
|
for (x = 0; x < width; x++)
|
|
{
|
|
convert_data(linebuf, dest, bLeftToRight ? x : (width-1) - x, depth, format);
|
|
}
|
|
dest += lineoffset;
|
|
}
|
|
if (buf) delete [] buf;
|
|
}
|
|
break;
|
|
default:
|
|
tgaerror = ERR_UNSUPPORTED;
|
|
}
|
|
|
|
if (linebuf) delete [] linebuf;
|
|
|
|
if (tgaerror)
|
|
{
|
|
if (buffer) delete [] buffer;
|
|
return NULL;
|
|
}
|
|
|
|
*width_ret = width;
|
|
*height_ret = height;
|
|
*numComponents_ret = format;
|
|
return buffer;
|
|
}
|
|
|
|
|
|
int
|
|
simage_tga_identify(const char *filename,
|
|
const unsigned char *buf,
|
|
int headerlen)
|
|
{
|
|
char * ptr;
|
|
if (headerlen < 18) return 0;
|
|
ptr = (char *)strrchr(filename, '.');
|
|
if (!ptr) return 0; /* TGA files must end with .tga|.TGA */
|
|
|
|
if (strcmp(ptr, ".tga") && strcmp(ptr, ".TGA")) return 0;
|
|
|
|
if (buf[1] == 1 && buf[2] == 1 && buf[17] < 64)
|
|
{
|
|
/* SoDebugError::postInfo("simage_tga_identify", */
|
|
/* "TARGA colormap file: %s\n", filename); */
|
|
return 0;
|
|
}
|
|
if ((buf[1] == 0 || buf[1] == 1) && buf[2] == 2 && buf[17] < 64) return 1;
|
|
if (buf[1] == 1 && buf[2] == 9 && buf[17] < 64)
|
|
{
|
|
/* SoDebugError::postInfo("simage_tga_identity", */
|
|
/* "TARGA RLE and colormap file: %s\n", filename); */
|
|
|
|
/* will soon be supported */
|
|
return 0;
|
|
}
|
|
if ((buf[1] == 0 || buf[1] == 1) && buf[2] == 10 && buf[17] < 64)
|
|
{
|
|
/* RLE and RGB */
|
|
return 1;
|
|
}
|
|
else /* unsupported */
|
|
{
|
|
/* SoDebugError::postInfo("simage_tga_identify", */
|
|
/* "Unsupported TARGA type.\n"); */
|
|
}
|
|
/* not a TGA, or not supported type */
|
|
return 0;
|
|
}
|
|
|
|
|
|
class ReaderWriterTGA : public osgDB::ReaderWriter
|
|
{
|
|
public:
|
|
|
|
ReaderWriterTGA()
|
|
{
|
|
supportsExtension("tga","Tga Image format");
|
|
}
|
|
|
|
virtual const char* className() const { return "TGA Image Reader"; }
|
|
|
|
ReadResult readTGAStream(std::istream& fin) const
|
|
{
|
|
unsigned char *imageData = NULL;
|
|
int width_ret;
|
|
int height_ret;
|
|
int numComponents_ret;
|
|
|
|
imageData = simage_tga_load(fin,&width_ret,&height_ret,&numComponents_ret);
|
|
|
|
if (imageData==NULL) return ReadResult::FILE_NOT_HANDLED;
|
|
|
|
int s = width_ret;
|
|
int t = height_ret;
|
|
int r = 1;
|
|
|
|
int internalFormat = numComponents_ret;
|
|
|
|
unsigned int pixelFormat =
|
|
numComponents_ret == 1 ? GL_LUMINANCE :
|
|
numComponents_ret == 2 ? GL_LUMINANCE_ALPHA :
|
|
numComponents_ret == 3 ? GL_RGB :
|
|
numComponents_ret == 4 ? GL_RGBA : (GLenum)-1;
|
|
|
|
unsigned int dataType = GL_UNSIGNED_BYTE;
|
|
|
|
osg::Image* pOsgImage = new osg::Image;
|
|
pOsgImage->setImage(s,t,r,
|
|
internalFormat,
|
|
pixelFormat,
|
|
dataType,
|
|
imageData,
|
|
osg::Image::USE_NEW_DELETE);
|
|
|
|
return pOsgImage;
|
|
|
|
}
|
|
|
|
virtual ReadResult readObject(std::istream& fin,const osgDB::ReaderWriter::Options* options =NULL) const
|
|
{
|
|
return readImage(fin, options);
|
|
}
|
|
|
|
virtual ReadResult readObject(const std::string& file, const osgDB::ReaderWriter::Options* options =NULL) const
|
|
{
|
|
return readImage(file, options);
|
|
}
|
|
|
|
virtual ReadResult readImage(std::istream& fin,const Options* =NULL) const
|
|
{
|
|
return readTGAStream(fin);
|
|
}
|
|
|
|
virtual ReadResult readImage(const std::string& file, const osgDB::ReaderWriter::Options* options) 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 = readTGAStream(istream);
|
|
if(rr.validImage()) rr.getImage()->setFileName(file);
|
|
return rr;
|
|
}
|
|
|
|
bool saveTGAStream(const osg::Image& image, std::ostream& fout) const
|
|
{
|
|
if (!image.data()) return false;
|
|
|
|
// At present, I will only save the image to unmapped RGB format
|
|
// Other data types can be added soon with different options
|
|
// The format description can be found at:
|
|
// http://local.wasp.uwa.edu.au/~pbourke/dataformats/tga/
|
|
unsigned int pixelFormat = image.getPixelFormat();
|
|
int width = image.s(), height = image.t();
|
|
int numPerPixel = image.computeNumComponents(pixelFormat);
|
|
int pixelMultiplier = (image.getDataType()==GL_FLOAT ? 255 : 1);
|
|
|
|
// Headers
|
|
fout.put(0); // Identification field size
|
|
fout.put(0); // Color map type
|
|
fout.put(2); // Image type
|
|
fout.put(0); fout.put(0); // Color map origin
|
|
fout.put(0); fout.put(0); // Color map length
|
|
fout.put(0); // Color map entry size
|
|
fout.put(0); fout.put(0); // X origin of image
|
|
fout.put(0); fout.put(0); // Y origin of image
|
|
fout.put(width&0xff); fout.put((width&0xff00)>>8); // Width of image
|
|
fout.put(height&0xff); fout.put((height&0xff00)>>8); // Height of image
|
|
fout.put(numPerPixel * 8); // Image pixel size
|
|
fout.put(0); // Image descriptor
|
|
|
|
// Swap red/blue channels for BGR images
|
|
int r = 0, g = 1, b = 2;
|
|
if( pixelFormat == GL_BGR || pixelFormat == GL_BGRA )
|
|
{
|
|
r = 2;
|
|
b = 0;
|
|
}
|
|
|
|
// Data
|
|
for (int y=0; y<height; ++y)
|
|
{
|
|
const unsigned char* ptr = image.data(0,y);
|
|
for (int x=0; x<width; ++x)
|
|
{
|
|
int off = x * numPerPixel;
|
|
switch ( numPerPixel )
|
|
{
|
|
case 3: // BGR
|
|
fout.put(ptr[off+b] * pixelMultiplier); fout.put(ptr[off+g] * pixelMultiplier);
|
|
fout.put(ptr[off+r] * pixelMultiplier);
|
|
break;
|
|
case 4: // BGRA
|
|
fout.put(ptr[off+b] * pixelMultiplier); fout.put(ptr[off+g] * pixelMultiplier);
|
|
fout.put(ptr[off+r] * pixelMultiplier); fout.put(ptr[off+3] * pixelMultiplier);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
virtual WriteResult writeImage(const osg::Image& image, std::ostream& fout, const Options*) const
|
|
{
|
|
if (saveTGAStream(image, fout))
|
|
return WriteResult::FILE_SAVED;
|
|
else
|
|
return WriteResult::ERROR_IN_WRITING_FILE;
|
|
}
|
|
|
|
virtual WriteResult writeImage(const osg::Image& image, const std::string& fileName, const Options* options) 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(image, fout, options);
|
|
}
|
|
};
|
|
|
|
// now register with Registry to instantiate the above
|
|
// reader/writer.
|
|
REGISTER_OSGPLUGIN(tga, ReaderWriterTGA)
|